~ubuntu-branches/ubuntu/trusty/subversion/trusty-proposed

« back to all changes in this revision

Viewing changes to subversion/bindings/javahl/native/ClientContext.cpp

  • Committer: Package Import Robot
  • Author(s): Andy Whitcroft
  • Date: 2012-06-21 15:36:36 UTC
  • mfrom: (0.4.13 sid)
  • Revision ID: package-import@ubuntu.com-20120621153636-amqqmuidgwgxz1ly
Tags: 1.7.5-1ubuntu1
* Merge from Debian unstable.  Remaining changes:
  - Create pot file on build.
  - Build a python-subversion-dbg package.
  - Build-depend on python-dbg.
  - Build-depend on default-jre-headless/-jdk.
  - Do not apply java-build patch.
  - debian/rules: Manually create the doxygen output directory, otherwise
    we get weird build failures when running parallel builds.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/**
 
2
 * @copyright
 
3
 * ====================================================================
 
4
 *    Licensed to the Apache Software Foundation (ASF) under one
 
5
 *    or more contributor license agreements.  See the NOTICE file
 
6
 *    distributed with this work for additional information
 
7
 *    regarding copyright ownership.  The ASF licenses this file
 
8
 *    to you under the Apache License, Version 2.0 (the
 
9
 *    "License"); you may not use this file except in compliance
 
10
 *    with the License.  You may obtain a copy of the License at
 
11
 *
 
12
 *      http://www.apache.org/licenses/LICENSE-2.0
 
13
 *
 
14
 *    Unless required by applicable law or agreed to in writing,
 
15
 *    software distributed under the License is distributed on an
 
16
 *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 
17
 *    KIND, either express or implied.  See the License for the
 
18
 *    specific language governing permissions and limitations
 
19
 *    under the License.
 
20
 * ====================================================================
 
21
 * @endcopyright
 
22
 *
 
23
 * @file ClientContext.cpp
 
24
 * @brief Implementation of the class ClientContext
 
25
 */
 
26
 
 
27
#include "svn_client.h"
 
28
#include "private/svn_wc_private.h"
 
29
#include "svn_private_config.h"
 
30
 
 
31
#include "ClientContext.h"
 
32
#include "JNIUtil.h"
 
33
#include "JNICriticalSection.h"
 
34
 
 
35
#include "Prompter.h"
 
36
#include "CreateJ.h"
 
37
#include "EnumMapper.h"
 
38
#include "CommitMessage.h"
 
39
 
 
40
 
 
41
ClientContext::ClientContext(jobject jsvnclient, SVN::Pool &pool)
 
42
    : m_prompter(NULL),
 
43
      m_cancelOperation(false)
 
44
{
 
45
    JNIEnv *env = JNIUtil::getEnv();
 
46
 
 
47
    /* Grab a global reference to the Java object embedded in the parent Java
 
48
       object. */
 
49
    static jfieldID ctxFieldID = 0;
 
50
    if (ctxFieldID == 0)
 
51
    {
 
52
        jclass clazz = env->GetObjectClass(jsvnclient);
 
53
        if (JNIUtil::isJavaExceptionThrown())
 
54
            return;
 
55
 
 
56
        ctxFieldID = env->GetFieldID(clazz, "clientContext",
 
57
                                "L"JAVA_PACKAGE"/SVNClient$ClientContext;");
 
58
        if (JNIUtil::isJavaExceptionThrown() || ctxFieldID == 0)
 
59
            return;
 
60
 
 
61
        env->DeleteLocalRef(clazz);
 
62
    }
 
63
 
 
64
    jobject jctx = env->GetObjectField(jsvnclient, ctxFieldID);
 
65
    if (JNIUtil::isJavaExceptionThrown())
 
66
        return;
 
67
 
 
68
    m_jctx = env->NewGlobalRef(jctx);
 
69
    if (JNIUtil::isJavaExceptionThrown())
 
70
        return;
 
71
 
 
72
    env->DeleteLocalRef(jctx);
 
73
 
 
74
    SVN_JNI_ERR(svn_client_create_context(&m_context, pool.getPool()),
 
75
                );
 
76
 
 
77
    /* Clear the wc_ctx as we don't want to maintain this unconditionally
 
78
       for compatibility reasons */
 
79
    SVN_JNI_ERR(svn_wc_context_destroy(m_context->wc_ctx),
 
80
                );
 
81
    m_context->wc_ctx = NULL;
 
82
 
 
83
    /* None of the following members change during the lifetime of
 
84
       this object. */
 
85
    m_context->notify_func = NULL;
 
86
    m_context->notify_baton = NULL;
 
87
    m_context->log_msg_func3 = CommitMessage::callback;
 
88
    m_context->log_msg_baton3 = NULL;
 
89
    m_context->cancel_func = checkCancel;
 
90
    m_context->cancel_baton = this;
 
91
    m_context->notify_func2= notify;
 
92
    m_context->notify_baton2 = m_jctx;
 
93
    m_context->progress_func = progress;
 
94
    m_context->progress_baton = m_jctx;
 
95
    m_context->conflict_func2 = resolve;
 
96
    m_context->conflict_baton2 = m_jctx;
 
97
 
 
98
    m_context->client_name = "javahl";
 
99
    m_pool = &pool;
 
100
}
 
101
 
 
102
ClientContext::~ClientContext()
 
103
{
 
104
    delete m_prompter;
 
105
 
 
106
    JNIEnv *env = JNIUtil::getEnv();
 
107
    env->DeleteGlobalRef(m_jctx);
 
108
}
 
109
 
 
110
 
 
111
/* Helper function to make sure that we don't keep dangling pointers in ctx.
 
112
   Note that this function might be called multiple times if getContext()
 
113
   is called on the same pool.
 
114
   
 
115
   The use of this function assumes a proper subpool behavior by its user,
 
116
   (read: SVNClient) usually per request.
 
117
 */
 
118
extern "C" {
 
119
 
 
120
struct clearctx_baton_t
 
121
{
 
122
  svn_client_ctx_t *ctx;
 
123
  svn_client_ctx_t *backup;
 
124
};
 
125
 
 
126
static apr_status_t clear_ctx_ptrs(void *ptr)
 
127
{
 
128
    clearctx_baton_t *bt = (clearctx_baton_t*)ptr;
 
129
 
 
130
    /* Reset all values to those before overwriting by getContext. */
 
131
    *bt->ctx = *bt->backup;
 
132
 
 
133
    return APR_SUCCESS;
 
134
}
 
135
 
 
136
};
 
137
 
 
138
svn_client_ctx_t *
 
139
ClientContext::getContext(CommitMessage *message, SVN::Pool &in_pool)
 
140
{
 
141
    apr_pool_t *pool = in_pool.getPool();
 
142
    svn_auth_baton_t *ab;
 
143
    svn_client_ctx_t *ctx = m_context;
 
144
 
 
145
    /* Make a temporary copy of ctx to restore at pool cleanup to avoid
 
146
       leaving references to dangling pointers.
 
147
 
 
148
       Note that this allows creating a stack of context changes if
 
149
       the function is invoked multiple times with different pools.
 
150
     */
 
151
    clearctx_baton_t *bt = (clearctx_baton_t *)apr_pcalloc(pool, sizeof(*bt));
 
152
    bt->ctx = ctx;
 
153
    bt->backup = (svn_client_ctx_t*)apr_pmemdup(pool, ctx, sizeof(*ctx));
 
154
    apr_pool_cleanup_register(in_pool.getPool(), bt, clear_ctx_ptrs,
 
155
                              clear_ctx_ptrs);
 
156
 
 
157
 
 
158
    if (!ctx->config)
 
159
      {
 
160
        const char *configDir = m_configDir.c_str();
 
161
        if (m_configDir.empty())
 
162
            configDir = NULL;
 
163
        SVN_JNI_ERR(svn_config_get_config(&(ctx->config), configDir,
 
164
                                          m_pool->getPool()),
 
165
                    NULL);
 
166
 
 
167
        bt->backup->config = ctx->config;
 
168
      }
 
169
    svn_config_t *config = (svn_config_t *) apr_hash_get(ctx->config,
 
170
                                                         SVN_CONFIG_CATEGORY_CONFIG,
 
171
                                                         APR_HASH_KEY_STRING);
 
172
 
 
173
 
 
174
    /* The whole list of registered providers */
 
175
    apr_array_header_t *providers;
 
176
 
 
177
    /* Populate the registered providers with the platform-specific providers */
 
178
    SVN_JNI_ERR(svn_auth_get_platform_specific_client_providers(&providers,
 
179
                                                                config,
 
180
                                                                pool),
 
181
                NULL);
 
182
 
 
183
    /* Use the prompter (if available) to prompt for password and cert
 
184
     * caching. */
 
185
    svn_auth_plaintext_prompt_func_t plaintext_prompt_func = NULL;
 
186
    void *plaintext_prompt_baton = NULL;
 
187
    svn_auth_plaintext_passphrase_prompt_func_t plaintext_passphrase_prompt_func;
 
188
    void *plaintext_passphrase_prompt_baton = NULL;
 
189
 
 
190
    if (m_prompter != NULL)
 
191
    {
 
192
        plaintext_prompt_func = Prompter::plaintext_prompt;
 
193
        plaintext_prompt_baton = m_prompter;
 
194
        plaintext_passphrase_prompt_func = Prompter::plaintext_passphrase_prompt;
 
195
        plaintext_passphrase_prompt_baton = m_prompter;
 
196
    }
 
197
 
 
198
    /* The main disk-caching auth providers, for both
 
199
     * 'username/password' creds and 'username' creds.  */
 
200
    svn_auth_provider_object_t *provider;
 
201
 
 
202
    svn_auth_get_simple_provider2(&provider, plaintext_prompt_func,
 
203
                                  plaintext_prompt_baton, pool);
 
204
    APR_ARRAY_PUSH(providers, svn_auth_provider_object_t *) = provider;
 
205
 
 
206
    svn_auth_get_username_provider(&provider, pool);
 
207
    APR_ARRAY_PUSH(providers, svn_auth_provider_object_t *) = provider;
 
208
 
 
209
    /* The server-cert, client-cert, and client-cert-password providers. */
 
210
    SVN_JNI_ERR(svn_auth_get_platform_specific_provider(&provider,
 
211
                                                        "windows",
 
212
                                                        "ssl_server_trust",
 
213
                                                        pool),
 
214
                NULL);
 
215
 
 
216
    if (provider)
 
217
        APR_ARRAY_PUSH(providers, svn_auth_provider_object_t *) = provider;
 
218
 
 
219
    svn_auth_get_ssl_server_trust_file_provider(&provider, pool);
 
220
    APR_ARRAY_PUSH(providers, svn_auth_provider_object_t *) = provider;
 
221
    svn_auth_get_ssl_client_cert_file_provider(&provider, pool);
 
222
    APR_ARRAY_PUSH(providers, svn_auth_provider_object_t *) = provider;
 
223
    svn_auth_get_ssl_client_cert_pw_file_provider2(&provider,
 
224
                        plaintext_passphrase_prompt_func,
 
225
                        plaintext_passphrase_prompt_baton, pool);
 
226
    APR_ARRAY_PUSH(providers, svn_auth_provider_object_t *) = provider;
 
227
 
 
228
    if (m_prompter != NULL)
 
229
    {
 
230
        /* Two basic prompt providers: username/password, and just username.*/
 
231
        provider = m_prompter->getProviderSimple(in_pool);
 
232
 
 
233
        APR_ARRAY_PUSH(providers, svn_auth_provider_object_t *) = provider;
 
234
 
 
235
        provider = m_prompter->getProviderUsername(in_pool);
 
236
        APR_ARRAY_PUSH(providers, svn_auth_provider_object_t *) = provider;
 
237
 
 
238
        /* Three ssl prompt providers, for server-certs, client-certs,
 
239
         * and client-cert-passphrases.  */
 
240
        provider = m_prompter->getProviderServerSSLTrust(in_pool);
 
241
        APR_ARRAY_PUSH(providers, svn_auth_provider_object_t *) = provider;
 
242
 
 
243
        provider = m_prompter->getProviderClientSSL(in_pool);
 
244
        APR_ARRAY_PUSH(providers, svn_auth_provider_object_t *) = provider;
 
245
 
 
246
        provider = m_prompter->getProviderClientSSLPassword(in_pool);
 
247
        APR_ARRAY_PUSH(providers, svn_auth_provider_object_t *) = provider;
 
248
    }
 
249
 
 
250
    /* Build an authentication baton to give to libsvn_client. */
 
251
    svn_auth_open(&ab, providers, pool);
 
252
 
 
253
    /* Place any default --username or --password credentials into the
 
254
     * auth_baton's run-time parameter hash.  ### Same with --no-auth-cache? */
 
255
    if (!m_userName.empty())
 
256
        svn_auth_set_parameter(ab, SVN_AUTH_PARAM_DEFAULT_USERNAME,
 
257
                               apr_pstrdup(in_pool.getPool(),
 
258
                                           m_userName.c_str()));
 
259
    if (!m_passWord.empty())
 
260
        svn_auth_set_parameter(ab, SVN_AUTH_PARAM_DEFAULT_PASSWORD,
 
261
                               apr_pstrdup(in_pool.getPool(),
 
262
                                           m_passWord.c_str()));
 
263
    /* Store where to retrieve authentication data? */
 
264
    if (!m_configDir.empty())
 
265
        svn_auth_set_parameter(ab, SVN_AUTH_PARAM_CONFIG_DIR,
 
266
                               apr_pstrdup(in_pool.getPool(),
 
267
                                           m_configDir.c_str()));
 
268
 
 
269
    ctx->auth_baton = ab;
 
270
    ctx->log_msg_baton3 = message;
 
271
    m_cancelOperation = false;
 
272
 
 
273
    SVN_JNI_ERR(svn_wc_context_create(&ctx->wc_ctx, NULL,
 
274
                                      in_pool.getPool(), in_pool.getPool()),
 
275
                NULL);
 
276
 
 
277
    return ctx;
 
278
}
 
279
 
 
280
void
 
281
ClientContext::username(const char *pi_username)
 
282
{
 
283
    m_userName = (pi_username == NULL ? "" : pi_username);
 
284
}
 
285
 
 
286
void
 
287
ClientContext::password(const char *pi_password)
 
288
{
 
289
    m_passWord = (pi_password == NULL ? "" : pi_password);
 
290
}
 
291
 
 
292
void
 
293
ClientContext::setPrompt(Prompter *prompter)
 
294
{
 
295
    delete m_prompter;
 
296
    m_prompter = prompter;
 
297
}
 
298
 
 
299
void
 
300
ClientContext::setConfigDirectory(const char *configDir)
 
301
{
 
302
    // A change to the config directory may necessitate creation of
 
303
    // the config templates.
 
304
    SVN::Pool requestPool;
 
305
    SVN_JNI_ERR(svn_config_ensure(configDir, requestPool.getPool()), );
 
306
 
 
307
    m_configDir = (configDir == NULL ? "" : configDir);
 
308
    m_context->config = NULL;
 
309
}
 
310
 
 
311
const char *
 
312
ClientContext::getConfigDirectory() const
 
313
{
 
314
    return m_configDir.c_str();
 
315
}
 
316
 
 
317
void
 
318
ClientContext::cancelOperation()
 
319
{
 
320
    m_cancelOperation = true;
 
321
}
 
322
 
 
323
svn_error_t *
 
324
ClientContext::checkCancel(void *cancelBaton)
 
325
{
 
326
    ClientContext *that = (ClientContext *)cancelBaton;
 
327
    if (that->m_cancelOperation)
 
328
        return svn_error_create(SVN_ERR_CANCELLED, NULL,
 
329
                                _("Operation cancelled"));
 
330
    else
 
331
        return SVN_NO_ERROR;
 
332
}
 
333
 
 
334
void
 
335
ClientContext::notify(void *baton,
 
336
                      const svn_wc_notify_t *notify,
 
337
                      apr_pool_t *pool)
 
338
{
 
339
  jobject jctx = (jobject) baton;
 
340
  JNIEnv *env = JNIUtil::getEnv();
 
341
 
 
342
  static jmethodID mid = 0;
 
343
  if (mid == 0)
 
344
    {
 
345
      jclass clazz = env->GetObjectClass(jctx);
 
346
      if (JNIUtil::isJavaExceptionThrown())
 
347
        return;
 
348
 
 
349
      mid = env->GetMethodID(clazz, "onNotify",
 
350
                             "(L"JAVA_PACKAGE"/ClientNotifyInformation;)V");
 
351
      if (JNIUtil::isJavaExceptionThrown() || mid == 0)
 
352
        return;
 
353
 
 
354
      env->DeleteLocalRef(clazz);
 
355
    }
 
356
 
 
357
  jobject jInfo = CreateJ::ClientNotifyInformation(notify);
 
358
  if (JNIUtil::isJavaExceptionThrown())
 
359
    return;
 
360
 
 
361
  env->CallVoidMethod(jctx, mid, jInfo);
 
362
  if (JNIUtil::isJavaExceptionThrown())
 
363
    return;
 
364
 
 
365
  env->DeleteLocalRef(jInfo);
 
366
}
 
367
 
 
368
void
 
369
ClientContext::progress(apr_off_t progressVal, apr_off_t total,
 
370
                        void *baton, apr_pool_t *pool)
 
371
{
 
372
  jobject jctx = (jobject) baton;
 
373
  JNIEnv *env = JNIUtil::getEnv();
 
374
 
 
375
  // Create a local frame for our references
 
376
  env->PushLocalFrame(LOCAL_FRAME_SIZE);
 
377
  if (JNIUtil::isJavaExceptionThrown())
 
378
    return;
 
379
 
 
380
  static jmethodID mid = 0;
 
381
  if (mid == 0)
 
382
    {
 
383
      jclass clazz = env->GetObjectClass(jctx);
 
384
      if (JNIUtil::isJavaExceptionThrown())
 
385
        POP_AND_RETURN_NOTHING();
 
386
 
 
387
      mid = env->GetMethodID(clazz, "onProgress",
 
388
                             "(L"JAVA_PACKAGE"/ProgressEvent;)V");
 
389
      if (JNIUtil::isJavaExceptionThrown() || mid == 0)
 
390
        POP_AND_RETURN_NOTHING();
 
391
    }
 
392
 
 
393
  static jmethodID midCT = 0;
 
394
  jclass clazz = env->FindClass(JAVA_PACKAGE"/ProgressEvent");
 
395
  if (JNIUtil::isJavaExceptionThrown())
 
396
    POP_AND_RETURN_NOTHING();
 
397
 
 
398
  if (midCT == 0)
 
399
    {
 
400
      midCT = env->GetMethodID(clazz, "<init>", "(JJ)V");
 
401
      if (JNIUtil::isJavaExceptionThrown() || midCT == 0)
 
402
        POP_AND_RETURN_NOTHING();
 
403
    }
 
404
 
 
405
  // Call the Java method.
 
406
  jobject jevent = env->NewObject(clazz, midCT,
 
407
                                  (jlong) progressVal, (jlong) total);
 
408
  if (JNIUtil::isJavaExceptionThrown())
 
409
    POP_AND_RETURN_NOTHING();
 
410
 
 
411
  env->CallVoidMethod(jctx, mid, jevent);
 
412
 
 
413
  POP_AND_RETURN_NOTHING();
 
414
}
 
415
 
 
416
svn_error_t *
 
417
ClientContext::resolve(svn_wc_conflict_result_t **result,
 
418
                       const svn_wc_conflict_description2_t *desc,
 
419
                       void *baton,
 
420
                       apr_pool_t *result_pool,
 
421
                       apr_pool_t *scratch_pool)
 
422
{
 
423
  jobject jctx = (jobject) baton;
 
424
  JNIEnv *env = JNIUtil::getEnv();
 
425
 
 
426
  // Create a local frame for our references
 
427
  env->PushLocalFrame(LOCAL_FRAME_SIZE);
 
428
  if (JNIUtil::isJavaExceptionThrown())
 
429
    return SVN_NO_ERROR;
 
430
 
 
431
  static jmethodID mid = 0;
 
432
  if (mid == 0)
 
433
    {
 
434
      jclass clazz = env->GetObjectClass(jctx);
 
435
      if (JNIUtil::isJavaExceptionThrown())
 
436
        POP_AND_RETURN(SVN_NO_ERROR);
 
437
 
 
438
      mid = env->GetMethodID(clazz, "resolve",
 
439
                             "(L"JAVA_PACKAGE"/ConflictDescriptor;)"
 
440
                             "L"JAVA_PACKAGE"/ConflictResult;");
 
441
      if (JNIUtil::isJavaExceptionThrown() || mid == 0)
 
442
        POP_AND_RETURN(SVN_NO_ERROR);
 
443
    }
 
444
 
 
445
  // Create an instance of the conflict descriptor.
 
446
  jobject jdesc = CreateJ::ConflictDescriptor(desc);
 
447
  if (JNIUtil::isJavaExceptionThrown())
 
448
    POP_AND_RETURN(SVN_NO_ERROR);
 
449
 
 
450
  // Invoke the Java conflict resolver callback method using the descriptor.
 
451
  jobject jresult = env->CallObjectMethod(jctx, mid, jdesc);
 
452
  if (JNIUtil::isJavaExceptionThrown())
 
453
    {
 
454
      // If an exception is thrown by our conflict resolver, remove it
 
455
      // from the JNI env, and convert it into a Subversion error.
 
456
      SVN::Pool tmpPool(scratch_pool);
 
457
      const char *msg = JNIUtil::thrownExceptionToCString(tmpPool);
 
458
      svn_error_t *err = svn_error_create(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE,
 
459
                                          NULL, msg);
 
460
      env->PopLocalFrame(NULL);
 
461
      return err;
 
462
    }
 
463
 
 
464
  *result = javaResultToC(jresult, result_pool);
 
465
  if (*result == NULL)
 
466
    {
 
467
      // Unable to convert the result into a C representation.
 
468
      env->PopLocalFrame(NULL);
 
469
      return svn_error_create(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL, NULL);
 
470
    }
 
471
 
 
472
  env->PopLocalFrame(NULL);
 
473
  return SVN_NO_ERROR;
 
474
}
 
475
 
 
476
svn_wc_conflict_result_t *
 
477
ClientContext::javaResultToC(jobject jresult, apr_pool_t *pool)
 
478
{
 
479
  JNIEnv *env = JNIUtil::getEnv();
 
480
 
 
481
  // Create a local frame for our references
 
482
  env->PushLocalFrame(LOCAL_FRAME_SIZE);
 
483
  if (JNIUtil::isJavaExceptionThrown())
 
484
    return SVN_NO_ERROR;
 
485
 
 
486
  static jmethodID getChoice = 0;
 
487
  static jmethodID getMergedPath = 0;
 
488
 
 
489
  jclass clazz = NULL;
 
490
  if (getChoice == 0 || getMergedPath == 0)
 
491
    {
 
492
      clazz = env->FindClass(JAVA_PACKAGE "/ConflictResult");
 
493
      if (JNIUtil::isJavaExceptionThrown())
 
494
        POP_AND_RETURN_NULL;
 
495
    }
 
496
 
 
497
  if (getChoice == 0)
 
498
    {
 
499
      getChoice = env->GetMethodID(clazz, "getChoice",
 
500
                                   "()L"JAVA_PACKAGE"/ConflictResult$Choice;");
 
501
      if (JNIUtil::isJavaExceptionThrown() || getChoice == 0)
 
502
        POP_AND_RETURN_NULL;
 
503
    }
 
504
  if (getMergedPath == 0)
 
505
    {
 
506
      getMergedPath = env->GetMethodID(clazz, "getMergedPath",
 
507
                                       "()Ljava/lang/String;");
 
508
      if (JNIUtil::isJavaExceptionThrown() || getMergedPath == 0)
 
509
        POP_AND_RETURN_NULL;
 
510
    }
 
511
 
 
512
  jobject jchoice = env->CallObjectMethod(jresult, getChoice);
 
513
  if (JNIUtil::isJavaExceptionThrown())
 
514
    POP_AND_RETURN_NULL;
 
515
 
 
516
  jstring jmergedPath = (jstring) env->CallObjectMethod(jresult, getMergedPath);
 
517
  if (JNIUtil::isJavaExceptionThrown())
 
518
    POP_AND_RETURN_NULL;
 
519
 
 
520
  JNIStringHolder mergedPath(jmergedPath);
 
521
  if (JNIUtil::isJavaExceptionThrown())
 
522
    POP_AND_RETURN_NULL;
 
523
 
 
524
  svn_wc_conflict_result_t *result =
 
525
         svn_wc_create_conflict_result(EnumMapper::toConflictChoice(jchoice),
 
526
                                       mergedPath.pstrdup(pool),
 
527
                                       pool);
 
528
 
 
529
  env->PopLocalFrame(NULL);
 
530
  return result;
 
531
}