~ubuntu-branches/debian/sid/subversion/sid

« back to all changes in this revision

Viewing changes to subversion/libsvn_repos/config_pool.c

  • Committer: Package Import Robot
  • Author(s): James McCoy
  • Date: 2015-08-07 21:32:47 UTC
  • mfrom: (0.2.15) (4.1.7 experimental)
  • Revision ID: package-import@ubuntu.com-20150807213247-ozyewtmgsr6tkewl
Tags: 1.9.0-1
* Upload to unstable
* New upstream release.
  + Security fixes
    - CVE-2015-3184: Mixed anonymous/authenticated path-based authz with
      httpd 2.4
    - CVE-2015-3187: svn_repos_trace_node_locations() reveals paths hidden
      by authz
* Add >= 2.7 requirement for python-all-dev Build-Depends, needed to run
  tests.
* Remove Build-Conflicts against ruby-test-unit.  (Closes: #791844)
* Remove patches/apache_module_dependency in favor of expressing the
  dependencies in authz_svn.load/dav_svn.load.
* Build-Depend on apache2-dev (>= 2.4.16) to ensure ap_some_authn_required()
  is available when building mod_authz_svn and Depend on apache2-bin (>=
  2.4.16) for runtime support.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*
 
2
 * config_pool.c :  pool of configuration objects
 
3
 *
 
4
 * ====================================================================
 
5
 *    Licensed to the Apache Software Foundation (ASF) under one
 
6
 *    or more contributor license agreements.  See the NOTICE file
 
7
 *    distributed with this work for additional information
 
8
 *    regarding copyright ownership.  The ASF licenses this file
 
9
 *    to you under the Apache License, Version 2.0 (the
 
10
 *    "License"); you may not use this file except in compliance
 
11
 *    with the License.  You may obtain a copy of the License at
 
12
 *
 
13
 *      http://www.apache.org/licenses/LICENSE-2.0
 
14
 *
 
15
 *    Unless required by applicable law or agreed to in writing,
 
16
 *    software distributed under the License is distributed on an
 
17
 *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 
18
 *    KIND, either express or implied.  See the License for the
 
19
 *    specific language governing permissions and limitations
 
20
 *    under the License.
 
21
 * ====================================================================
 
22
 */
 
23
 
 
24
 
 
25
 
 
26
 
 
27
#include "svn_checksum.h"
 
28
#include "svn_config.h"
 
29
#include "svn_error.h"
 
30
#include "svn_hash.h"
 
31
#include "svn_path.h"
 
32
#include "svn_pools.h"
 
33
#include "svn_repos.h"
 
34
 
 
35
#include "private/svn_dep_compat.h"
 
36
#include "private/svn_mutex.h"
 
37
#include "private/svn_subr_private.h"
 
38
#include "private/svn_repos_private.h"
 
39
#include "private/svn_object_pool.h"
 
40
 
 
41
#include "svn_private_config.h"
 
42
 
 
43
 
 
44
/* Our wrapper structure for parsed svn_config_t* instances.  All data in
 
45
 * CS_CFG and CI_CFG is expanded (to make it thread-safe) and considered
 
46
 * read-only.
 
47
 */
 
48
typedef struct config_object_t
 
49
{
 
50
  /* UUID of the configuration contents.
 
51
   * This is a SHA1 checksum of the parsed textual representation of CFG. */
 
52
  svn_checksum_t *key;
 
53
 
 
54
  /* Parsed and expanded configuration.  At least one of the following
 
55
   * must not be NULL. */
 
56
 
 
57
  /* Case-sensitive config. May be NULL */
 
58
  svn_config_t *cs_cfg;
 
59
 
 
60
  /* Case-insensitive config. May be NULL */
 
61
  svn_config_t *ci_cfg;
 
62
} config_object_t;
 
63
 
 
64
 
 
65
/* Data structure used to short-circuit the repository access for configs
 
66
 * read via URL.  After reading such a config successfully, we store key
 
67
 * repository information here and will validate it without actually opening
 
68
 * the repository.
 
69
 *
 
70
 * As this is only an optimization and may create many entries in
 
71
 * svn_repos__config_pool_t's IN_REPO_HASH_POOL index, we clean them up
 
72
 * once in a while.
 
73
 */
 
74
typedef struct in_repo_config_t
 
75
{
 
76
  /* URL used to open the configuration */
 
77
  const char *url;
 
78
 
 
79
  /* Path of the repository that contained URL */
 
80
  const char *repo_root;
 
81
 
 
82
  /* Head revision of that repository when last read */
 
83
  svn_revnum_t revision;
 
84
 
 
85
  /* Contents checksum of the file stored under URL@REVISION */
 
86
  svn_checksum_t *key;
 
87
} in_repo_config_t;
 
88
 
 
89
 
 
90
/* Core data structure extending the encapsulated OBJECT_POOL.  All access
 
91
 * to it must be serialized using the OBJECT_POOL->MUTEX.
 
92
 *
 
93
 * To speed up URL@HEAD lookups, we maintain IN_REPO_CONFIGS as a secondary
 
94
 * hash index.  It maps URLs as provided by the caller onto in_repo_config_t
 
95
 * instances.  If that is still up-to-date, a further lookup into CONFIG
 
96
 * may yield the desired configuration without the need to actually open
 
97
 * the respective repository.
 
98
 *
 
99
 * Unused configurations that are kept in the IN_REPO_CONFIGS hash and may
 
100
 * be cleaned up when the hash is about to grow.
 
101
 */
 
102
struct svn_repos__config_pool_t
 
103
{
 
104
  svn_object_pool__t *object_pool;
 
105
 
 
106
  /* URL -> in_repo_config_t* mapping.
 
107
   * This is only a partial index and will get cleared regularly. */
 
108
  apr_hash_t *in_repo_configs;
 
109
 
 
110
  /* allocate the IN_REPO_CONFIGS index and in_repo_config_t here */
 
111
  apr_pool_t *in_repo_hash_pool;
 
112
};
 
113
 
 
114
 
 
115
/* Return an automatic reference to the CFG member in CONFIG that will be
 
116
 * released when POOL gets cleaned up.  The case sensitivity flag in *BATON
 
117
 * selects the desired option and section name matching mode.
 
118
 */
 
119
static void *
 
120
getter(void *object,
 
121
       void *baton,
 
122
       apr_pool_t *pool)
 
123
{
 
124
  config_object_t *wrapper = object;
 
125
  svn_boolean_t *case_sensitive = baton;
 
126
  svn_config_t *config = *case_sensitive ? wrapper->cs_cfg : wrapper->ci_cfg;
 
127
 
 
128
  /* we need to duplicate the root structure as it contains temp. buffers */
 
129
  return config ? svn_config__shallow_copy(config, pool) : NULL;
 
130
}
 
131
 
 
132
/* Return a memory buffer structure allocated in POOL and containing the
 
133
 * data from CHECKSUM.
 
134
 */
 
135
static svn_membuf_t *
 
136
checksum_as_key(svn_checksum_t *checksum,
 
137
                apr_pool_t *pool)
 
138
{
 
139
  svn_membuf_t *result = apr_pcalloc(pool, sizeof(*result));
 
140
  apr_size_t size = svn_checksum_size(checksum);
 
141
 
 
142
  svn_membuf__create(result, size, pool);
 
143
  result->size = size; /* exact length is required! */
 
144
  memcpy(result->data, checksum->digest, size);
 
145
 
 
146
  return result;
 
147
}
 
148
 
 
149
/* Copy the configuration from the wrapper in SOURCE to the wrapper in
 
150
 * *TARGET with the case sensitivity flag in *BATON selecting the config
 
151
 * to copy.  This is usually done to add the missing case-(in)-sensitive
 
152
 * variant.  Since we must hold all data in *TARGET from the same POOL,
 
153
 * a deep copy is required.
 
154
 */
 
155
static svn_error_t *
 
156
setter(void **target,
 
157
       void *source,
 
158
       void *baton,
 
159
       apr_pool_t *pool)
 
160
{
 
161
  svn_boolean_t *case_sensitive = baton;
 
162
  config_object_t *target_cfg = *(config_object_t **)target;
 
163
  config_object_t *source_cfg = source;
 
164
 
 
165
  /* Maybe, we created a variant with different case sensitivity? */
 
166
  if (*case_sensitive && target_cfg->cs_cfg == NULL)
 
167
    {
 
168
      SVN_ERR(svn_config_dup(&target_cfg->cs_cfg, source_cfg->cs_cfg, pool));
 
169
      svn_config__set_read_only(target_cfg->cs_cfg, pool);
 
170
    }
 
171
  else if (!*case_sensitive && target_cfg->ci_cfg == NULL)
 
172
    {
 
173
      SVN_ERR(svn_config_dup(&target_cfg->ci_cfg, source_cfg->ci_cfg, pool));
 
174
      svn_config__set_read_only(target_cfg->ci_cfg, pool);
 
175
    }
 
176
 
 
177
  return SVN_NO_ERROR;
 
178
}
 
179
 
 
180
/* Set *CFG to the configuration passed in as text in CONTENTS and *KEY to
 
181
 * the corresponding object pool key.  If no such configuration exists in
 
182
 * CONFIG_POOL, yet, parse CONTENTS and cache the result.  CASE_SENSITIVE
 
183
 * controls option and section name matching.
 
184
 *
 
185
 * RESULT_POOL determines the lifetime of the returned reference and
 
186
 * SCRATCH_POOL is being used for temporary allocations.
 
187
 */
 
188
static svn_error_t *
 
189
auto_parse(svn_config_t **cfg,
 
190
           svn_membuf_t **key,
 
191
           svn_repos__config_pool_t *config_pool,
 
192
           svn_stringbuf_t *contents,
 
193
           svn_boolean_t case_sensitive,
 
194
           apr_pool_t *result_pool,
 
195
           apr_pool_t *scratch_pool)
 
196
{
 
197
  svn_checksum_t *checksum;
 
198
  config_object_t *config_object;
 
199
  apr_pool_t *cfg_pool;
 
200
 
 
201
  /* calculate SHA1 over the whole file contents */
 
202
  SVN_ERR(svn_stream_close
 
203
              (svn_stream_checksummed2
 
204
                  (svn_stream_from_stringbuf(contents, scratch_pool),
 
205
                   &checksum, NULL, svn_checksum_sha1, TRUE, scratch_pool)));
 
206
 
 
207
  /* return reference to suitable config object if that already exists */
 
208
  *key = checksum_as_key(checksum, result_pool);
 
209
  SVN_ERR(svn_object_pool__lookup((void **)cfg, config_pool->object_pool,
 
210
                                  *key, &case_sensitive, result_pool));
 
211
  if (*cfg)
 
212
    return SVN_NO_ERROR;
 
213
 
 
214
  /* create a pool for the new config object and parse the data into it  */
 
215
  cfg_pool = svn_object_pool__new_wrapper_pool(config_pool->object_pool);
 
216
 
 
217
  config_object = apr_pcalloc(cfg_pool, sizeof(*config_object));
 
218
 
 
219
  SVN_ERR(svn_config_parse(case_sensitive ? &config_object->cs_cfg
 
220
                                          : &config_object->ci_cfg,
 
221
                           svn_stream_from_stringbuf(contents, scratch_pool),
 
222
                           case_sensitive, case_sensitive, cfg_pool));
 
223
 
 
224
  /* switch config data to r/o mode to guarantee thread-safe access */
 
225
  svn_config__set_read_only(case_sensitive ? config_object->cs_cfg
 
226
                                           : config_object->ci_cfg,
 
227
                            cfg_pool);
 
228
 
 
229
  /* add config in pool, handle loads races and return the right config */
 
230
  SVN_ERR(svn_object_pool__insert((void **)cfg, config_pool->object_pool,
 
231
                                  *key, config_object, &case_sensitive,
 
232
                                  cfg_pool, result_pool));
 
233
 
 
234
  return SVN_NO_ERROR;
 
235
}
 
236
 
 
237
/* Store a URL@REVISION to CHECKSUM, REPOS_ROOT in CONFIG_POOL.
 
238
 */
 
239
static svn_error_t *
 
240
add_checksum(svn_repos__config_pool_t *config_pool,
 
241
             const char *url,
 
242
             const char *repos_root,
 
243
             svn_revnum_t revision,
 
244
             svn_checksum_t *checksum)
 
245
{
 
246
  apr_size_t path_len = strlen(url);
 
247
  apr_pool_t *pool = config_pool->in_repo_hash_pool;
 
248
  in_repo_config_t *config = apr_hash_get(config_pool->in_repo_configs,
 
249
                                          url, path_len);
 
250
  if (config)
 
251
    {
 
252
      /* update the existing entry */
 
253
      memcpy((void *)config->key->digest, checksum->digest,
 
254
             svn_checksum_size(checksum));
 
255
      config->revision = revision;
 
256
 
 
257
      /* duplicate the string only if necessary */
 
258
      if (strcmp(config->repo_root, repos_root))
 
259
        config->repo_root = apr_pstrdup(pool, repos_root);
 
260
    }
 
261
  else
 
262
    {
 
263
      /* insert a new entry.
 
264
       * Limit memory consumption by cyclically clearing pool and hash. */
 
265
      if (2 * svn_object_pool__count(config_pool->object_pool)
 
266
          < apr_hash_count(config_pool->in_repo_configs))
 
267
        {
 
268
          svn_pool_clear(pool);
 
269
          config_pool->in_repo_configs = svn_hash__make(pool);
 
270
        }
 
271
 
 
272
      /* construct the new entry */
 
273
      config = apr_pcalloc(pool, sizeof(*config));
 
274
      config->key = svn_checksum_dup(checksum, pool);
 
275
      config->url = apr_pstrmemdup(pool, url, path_len);
 
276
      config->repo_root = apr_pstrdup(pool, repos_root);
 
277
      config->revision = revision;
 
278
 
 
279
      /* add to index */
 
280
      apr_hash_set(config_pool->in_repo_configs, url, path_len, config);
 
281
    }
 
282
 
 
283
  return SVN_NO_ERROR;
 
284
}
 
285
 
 
286
/* Set *CFG to the configuration stored in URL@HEAD and cache it in
 
287
 * CONFIG_POOL.  CASE_SENSITIVE controls
 
288
 * option and section name matching.  If PREFERRED_REPOS is given,
 
289
 * use that if it also matches URL.
 
290
 *
 
291
 * RESULT_POOL determines the lifetime of the returned reference and
 
292
 * SCRATCH_POOL is being used for temporary allocations.
 
293
 */
 
294
static svn_error_t *
 
295
find_repos_config(svn_config_t **cfg,
 
296
                  svn_membuf_t **key,
 
297
                  svn_repos__config_pool_t *config_pool,
 
298
                  const char *url,
 
299
                  svn_boolean_t case_sensitive,
 
300
                  svn_repos_t *preferred_repos,
 
301
                  apr_pool_t *result_pool,
 
302
                  apr_pool_t *scratch_pool)
 
303
{
 
304
  svn_repos_t *repos = NULL;
 
305
  svn_fs_t *fs;
 
306
  svn_fs_root_t *root;
 
307
  svn_revnum_t youngest_rev;
 
308
  svn_node_kind_t node_kind;
 
309
  const char *dirent;
 
310
  svn_stream_t *stream;
 
311
  const char *fs_path;
 
312
  const char *repos_root_dirent;
 
313
  svn_checksum_t *checksum;
 
314
  svn_stringbuf_t *contents;
 
315
 
 
316
  *cfg = NULL;
 
317
  SVN_ERR(svn_uri_get_dirent_from_file_url(&dirent, url, scratch_pool));
 
318
 
 
319
  /* maybe we can use the preferred repos instance instead of creating a
 
320
   * new one */
 
321
  if (preferred_repos)
 
322
    {
 
323
      repos_root_dirent = svn_repos_path(preferred_repos, scratch_pool);
 
324
      if (!svn_dirent_is_absolute(repos_root_dirent))
 
325
        SVN_ERR(svn_dirent_get_absolute(&repos_root_dirent,
 
326
                                        repos_root_dirent,
 
327
                                        scratch_pool));
 
328
 
 
329
      if (svn_dirent_is_ancestor(repos_root_dirent, dirent))
 
330
        repos = preferred_repos;
 
331
    }
 
332
 
 
333
  /* open repos if no suitable preferred repos was provided. */
 
334
  if (!repos)
 
335
    {
 
336
      /* Search for a repository in the full path. */
 
337
      repos_root_dirent = svn_repos_find_root_path(dirent, scratch_pool);
 
338
 
 
339
      /* Attempt to open a repository at repos_root_dirent. */
 
340
      SVN_ERR(svn_repos_open3(&repos, repos_root_dirent, NULL,
 
341
                              scratch_pool, scratch_pool));
 
342
    }
 
343
 
 
344
  fs_path = &dirent[strlen(repos_root_dirent)];
 
345
 
 
346
  /* Get the filesystem. */
 
347
  fs = svn_repos_fs(repos);
 
348
 
 
349
  /* Find HEAD and the revision root */
 
350
  SVN_ERR(svn_fs_youngest_rev(&youngest_rev, fs, scratch_pool));
 
351
  SVN_ERR(svn_fs_revision_root(&root, fs, youngest_rev, scratch_pool));
 
352
 
 
353
  /* Fetch checksum and see whether we already have a matching config */
 
354
  SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_sha1, root, fs_path,
 
355
                               FALSE, scratch_pool));
 
356
  if (checksum)
 
357
    {
 
358
      *key = checksum_as_key(checksum, scratch_pool);
 
359
      SVN_ERR(svn_object_pool__lookup((void **)cfg, config_pool->object_pool,
 
360
                                      *key, &case_sensitive, result_pool));
 
361
    }
 
362
 
 
363
  /* not parsed, yet? */
 
364
  if (!*cfg)
 
365
    {
 
366
      svn_filesize_t length;
 
367
 
 
368
      /* fetch the file contents */
 
369
      SVN_ERR(svn_fs_check_path(&node_kind, root, fs_path, scratch_pool));
 
370
      if (node_kind != svn_node_file)
 
371
        return SVN_NO_ERROR;
 
372
 
 
373
      SVN_ERR(svn_fs_file_length(&length, root, fs_path, scratch_pool));
 
374
      SVN_ERR(svn_fs_file_contents(&stream, root, fs_path, scratch_pool));
 
375
      SVN_ERR(svn_stringbuf_from_stream(&contents, stream,
 
376
                                        (apr_size_t)length, scratch_pool));
 
377
 
 
378
      /* handle it like ordinary file contents and cache it */
 
379
      SVN_ERR(auto_parse(cfg, key, config_pool, contents, case_sensitive,
 
380
                         result_pool, scratch_pool));
 
381
    }
 
382
 
 
383
  /* store the (path,rev) -> checksum mapping as well */
 
384
  if (*cfg && checksum)
 
385
    SVN_MUTEX__WITH_LOCK(svn_object_pool__mutex(config_pool->object_pool),
 
386
                         add_checksum(config_pool, url, repos_root_dirent,
 
387
                                      youngest_rev, checksum));
 
388
 
 
389
  return SVN_NO_ERROR;
 
390
}
 
391
 
 
392
/* Given the URL, search the CONFIG_POOL for an entry that maps it URL to
 
393
 * a content checksum and is still up-to-date.  If this could be found,
 
394
 * return the object's *KEY.  Use POOL for allocations.
 
395
 *
 
396
 * Requires external serialization on CONFIG_POOL.
 
397
 *
 
398
 * Note that this is only the URL(+rev) -> Checksum lookup and does not
 
399
 * guarantee that there is actually a config object available for *KEY.
 
400
 */
 
401
static svn_error_t *
 
402
key_by_url(svn_membuf_t **key,
 
403
           svn_repos__config_pool_t *config_pool,
 
404
           const char *url,
 
405
           apr_pool_t *pool)
 
406
{
 
407
  svn_error_t *err;
 
408
  svn_stringbuf_t *contents;
 
409
  apr_int64_t current;
 
410
 
 
411
  /* hash lookup url -> sha1 -> config */
 
412
  in_repo_config_t *config = svn_hash_gets(config_pool->in_repo_configs, url);
 
413
  *key = NULL;
 
414
  if (!config)
 
415
    return SVN_NO_ERROR;
 
416
 
 
417
  /* found *some* reference to a configuration.
 
418
   * Verify that it is still current.  Will fail for BDB repos. */
 
419
  err = svn_stringbuf_from_file2(&contents,
 
420
                                 svn_dirent_join(config->repo_root,
 
421
                                                 "db/current", pool),
 
422
                                 pool);
 
423
  if (!err)
 
424
    err = svn_cstring_atoi64(&current, contents->data);
 
425
 
 
426
  if (err)
 
427
    svn_error_clear(err);
 
428
  else if (current == config->revision)
 
429
    *key = checksum_as_key(config->key, pool);
 
430
 
 
431
  return SVN_NO_ERROR;
 
432
}
 
433
 
 
434
/* API implementation */
 
435
 
 
436
svn_error_t *
 
437
svn_repos__config_pool_create(svn_repos__config_pool_t **config_pool,
 
438
                              svn_boolean_t thread_safe,
 
439
                              apr_pool_t *pool)
 
440
{
 
441
  svn_repos__config_pool_t *result;
 
442
  svn_object_pool__t *object_pool;
 
443
 
 
444
  SVN_ERR(svn_object_pool__create(&object_pool, getter, setter,
 
445
                                  thread_safe, pool));
 
446
 
 
447
  /* construct the config pool in our private ROOT_POOL to survive POOL
 
448
   * cleanup and to prevent threading issues with the allocator */
 
449
  result = apr_pcalloc(pool, sizeof(*result));
 
450
 
 
451
  result->object_pool = object_pool;
 
452
  result->in_repo_hash_pool = svn_pool_create(pool);
 
453
  result->in_repo_configs = svn_hash__make(result->in_repo_hash_pool);
 
454
 
 
455
  *config_pool = result;
 
456
  return SVN_NO_ERROR;
 
457
}
 
458
 
 
459
svn_error_t *
 
460
svn_repos__config_pool_get(svn_config_t **cfg,
 
461
                           svn_membuf_t **key,
 
462
                           svn_repos__config_pool_t *config_pool,
 
463
                           const char *path,
 
464
                           svn_boolean_t must_exist,
 
465
                           svn_boolean_t case_sensitive,
 
466
                           svn_repos_t *preferred_repos,
 
467
                           apr_pool_t *pool)
 
468
{
 
469
  svn_error_t *err = SVN_NO_ERROR;
 
470
  apr_pool_t *scratch_pool = svn_pool_create(pool);
 
471
 
 
472
  /* make sure we always have a *KEY object */
 
473
  svn_membuf_t *local_key = NULL;
 
474
  if (key == NULL)
 
475
    key = &local_key;
 
476
  else
 
477
    *key = NULL;
 
478
 
 
479
  if (svn_path_is_url(path))
 
480
    {
 
481
      /* Read config file from repository.
 
482
       * Attempt a quick lookup first. */
 
483
      SVN_MUTEX__WITH_LOCK(svn_object_pool__mutex(config_pool->object_pool),
 
484
                           key_by_url(key, config_pool, path, pool));
 
485
      if (*key)
 
486
        {
 
487
          SVN_ERR(svn_object_pool__lookup((void **)cfg,
 
488
                                          config_pool->object_pool,
 
489
                                          *key, &case_sensitive, pool));
 
490
          if (*cfg)
 
491
            {
 
492
              svn_pool_destroy(scratch_pool);
 
493
              return SVN_NO_ERROR;
 
494
            }
 
495
        }
 
496
 
 
497
      /* Read and cache the configuration.  This may fail. */
 
498
      err = find_repos_config(cfg, key, config_pool, path, case_sensitive,
 
499
                              preferred_repos, pool, scratch_pool);
 
500
      if (err || !*cfg)
 
501
        {
 
502
          /* let the standard implementation handle all the difficult cases */
 
503
          svn_error_clear(err);
 
504
          err = svn_repos__retrieve_config(cfg, path, must_exist,
 
505
                                           case_sensitive, pool);
 
506
        }
 
507
    }
 
508
  else
 
509
    {
 
510
      /* Outside of repo file.  Read it. */
 
511
      svn_stringbuf_t *contents;
 
512
      err = svn_stringbuf_from_file2(&contents, path, scratch_pool);
 
513
      if (err)
 
514
        {
 
515
          /* let the standard implementation handle all the difficult cases */
 
516
          svn_error_clear(err);
 
517
          err = svn_config_read3(cfg, path, must_exist, case_sensitive,
 
518
                                 case_sensitive, pool);
 
519
        }
 
520
      else
 
521
        {
 
522
          /* parsing and caching will always succeed */
 
523
          err = auto_parse(cfg, key, config_pool, contents, case_sensitive,
 
524
                           pool, scratch_pool);
 
525
        }
 
526
    }
 
527
 
 
528
  svn_pool_destroy(scratch_pool);
 
529
 
 
530
  return err;
 
531
}