2
* config_pool.c : pool of configuration objects
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
13
* http://www.apache.org/licenses/LICENSE-2.0
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
21
* ====================================================================
27
#include "svn_checksum.h"
28
#include "svn_config.h"
29
#include "svn_error.h"
32
#include "svn_pools.h"
33
#include "svn_repos.h"
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"
41
#include "svn_private_config.h"
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
48
typedef struct config_object_t
50
/* UUID of the configuration contents.
51
* This is a SHA1 checksum of the parsed textual representation of CFG. */
54
/* Parsed and expanded configuration. At least one of the following
55
* must not be NULL. */
57
/* Case-sensitive config. May be NULL */
60
/* Case-insensitive config. May be NULL */
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
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
74
typedef struct in_repo_config_t
76
/* URL used to open the configuration */
79
/* Path of the repository that contained URL */
80
const char *repo_root;
82
/* Head revision of that repository when last read */
83
svn_revnum_t revision;
85
/* Contents checksum of the file stored under URL@REVISION */
90
/* Core data structure extending the encapsulated OBJECT_POOL. All access
91
* to it must be serialized using the OBJECT_POOL->MUTEX.
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.
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.
102
struct svn_repos__config_pool_t
104
svn_object_pool__t *object_pool;
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;
110
/* allocate the IN_REPO_CONFIGS index and in_repo_config_t here */
111
apr_pool_t *in_repo_hash_pool;
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.
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;
128
/* we need to duplicate the root structure as it contains temp. buffers */
129
return config ? svn_config__shallow_copy(config, pool) : NULL;
132
/* Return a memory buffer structure allocated in POOL and containing the
133
* data from CHECKSUM.
135
static svn_membuf_t *
136
checksum_as_key(svn_checksum_t *checksum,
139
svn_membuf_t *result = apr_pcalloc(pool, sizeof(*result));
140
apr_size_t size = svn_checksum_size(checksum);
142
svn_membuf__create(result, size, pool);
143
result->size = size; /* exact length is required! */
144
memcpy(result->data, checksum->digest, size);
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.
156
setter(void **target,
161
svn_boolean_t *case_sensitive = baton;
162
config_object_t *target_cfg = *(config_object_t **)target;
163
config_object_t *source_cfg = source;
165
/* Maybe, we created a variant with different case sensitivity? */
166
if (*case_sensitive && target_cfg->cs_cfg == NULL)
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);
171
else if (!*case_sensitive && target_cfg->ci_cfg == NULL)
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);
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.
185
* RESULT_POOL determines the lifetime of the returned reference and
186
* SCRATCH_POOL is being used for temporary allocations.
189
auto_parse(svn_config_t **cfg,
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)
197
svn_checksum_t *checksum;
198
config_object_t *config_object;
199
apr_pool_t *cfg_pool;
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)));
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));
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);
217
config_object = apr_pcalloc(cfg_pool, sizeof(*config_object));
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));
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,
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));
237
/* Store a URL@REVISION to CHECKSUM, REPOS_ROOT in CONFIG_POOL.
240
add_checksum(svn_repos__config_pool_t *config_pool,
242
const char *repos_root,
243
svn_revnum_t revision,
244
svn_checksum_t *checksum)
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,
252
/* update the existing entry */
253
memcpy((void *)config->key->digest, checksum->digest,
254
svn_checksum_size(checksum));
255
config->revision = revision;
257
/* duplicate the string only if necessary */
258
if (strcmp(config->repo_root, repos_root))
259
config->repo_root = apr_pstrdup(pool, repos_root);
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))
268
svn_pool_clear(pool);
269
config_pool->in_repo_configs = svn_hash__make(pool);
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;
280
apr_hash_set(config_pool->in_repo_configs, url, path_len, config);
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.
291
* RESULT_POOL determines the lifetime of the returned reference and
292
* SCRATCH_POOL is being used for temporary allocations.
295
find_repos_config(svn_config_t **cfg,
297
svn_repos__config_pool_t *config_pool,
299
svn_boolean_t case_sensitive,
300
svn_repos_t *preferred_repos,
301
apr_pool_t *result_pool,
302
apr_pool_t *scratch_pool)
304
svn_repos_t *repos = NULL;
307
svn_revnum_t youngest_rev;
308
svn_node_kind_t node_kind;
310
svn_stream_t *stream;
312
const char *repos_root_dirent;
313
svn_checksum_t *checksum;
314
svn_stringbuf_t *contents;
317
SVN_ERR(svn_uri_get_dirent_from_file_url(&dirent, url, scratch_pool));
319
/* maybe we can use the preferred repos instance instead of creating a
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,
329
if (svn_dirent_is_ancestor(repos_root_dirent, dirent))
330
repos = preferred_repos;
333
/* open repos if no suitable preferred repos was provided. */
336
/* Search for a repository in the full path. */
337
repos_root_dirent = svn_repos_find_root_path(dirent, scratch_pool);
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));
344
fs_path = &dirent[strlen(repos_root_dirent)];
346
/* Get the filesystem. */
347
fs = svn_repos_fs(repos);
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));
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));
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));
363
/* not parsed, yet? */
366
svn_filesize_t length;
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)
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));
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));
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));
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.
396
* Requires external serialization on CONFIG_POOL.
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.
402
key_by_url(svn_membuf_t **key,
403
svn_repos__config_pool_t *config_pool,
408
svn_stringbuf_t *contents;
411
/* hash lookup url -> sha1 -> config */
412
in_repo_config_t *config = svn_hash_gets(config_pool->in_repo_configs, url);
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,
424
err = svn_cstring_atoi64(¤t, contents->data);
427
svn_error_clear(err);
428
else if (current == config->revision)
429
*key = checksum_as_key(config->key, pool);
434
/* API implementation */
437
svn_repos__config_pool_create(svn_repos__config_pool_t **config_pool,
438
svn_boolean_t thread_safe,
441
svn_repos__config_pool_t *result;
442
svn_object_pool__t *object_pool;
444
SVN_ERR(svn_object_pool__create(&object_pool, getter, setter,
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));
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);
455
*config_pool = result;
460
svn_repos__config_pool_get(svn_config_t **cfg,
462
svn_repos__config_pool_t *config_pool,
464
svn_boolean_t must_exist,
465
svn_boolean_t case_sensitive,
466
svn_repos_t *preferred_repos,
469
svn_error_t *err = SVN_NO_ERROR;
470
apr_pool_t *scratch_pool = svn_pool_create(pool);
472
/* make sure we always have a *KEY object */
473
svn_membuf_t *local_key = NULL;
479
if (svn_path_is_url(path))
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));
487
SVN_ERR(svn_object_pool__lookup((void **)cfg,
488
config_pool->object_pool,
489
*key, &case_sensitive, pool));
492
svn_pool_destroy(scratch_pool);
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);
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);
510
/* Outside of repo file. Read it. */
511
svn_stringbuf_t *contents;
512
err = svn_stringbuf_from_file2(&contents, path, scratch_pool);
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);
522
/* parsing and caching will always succeed */
523
err = auto_parse(cfg, key, config_pool, contents, case_sensitive,
528
svn_pool_destroy(scratch_pool);