Deleted Added
full compact
config_pool.c (302408) config_pool.c (362181)
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

--- 11 unchanged lines hidden (view full) ---

20 * under the License.
21 * ====================================================================
22 */
23
24
25
26
27#include "svn_checksum.h"
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

--- 11 unchanged lines hidden (view full) ---

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"
28#include "svn_path.h"
29#include "svn_pools.h"
33#include "svn_repos.h"
34
30
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"
31#include "private/svn_subr_private.h"
32#include "private/svn_repos_private.h"
39#include "private/svn_object_pool.h"
40
41#include "svn_private_config.h"
42
33
34#include "svn_private_config.h"
35
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 */
48typedef 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;
36#include "config_file.h"
53
37
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 */
74typedef 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 */
102struct 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
38
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 */
119static void *
120getter(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 */
135static svn_membuf_t *
136checksum_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
39/* Return a memory buffer structure allocated in POOL and containing the
40 * data from CHECKSUM.
41 */
42static svn_membuf_t *
43checksum_as_key(svn_checksum_t *checksum,
44 apr_pool_t *pool)
45{
46 svn_membuf_t *result = apr_pcalloc(pool, sizeof(*result));
47 apr_size_t size = svn_checksum_size(checksum);
48
49 svn_membuf__create(result, size, pool);
50 result->size = size; /* exact length is required! */
51 memcpy(result->data, checksum->digest, size);
52
53 return result;
54}
55
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 */
155static svn_error_t *
156setter(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.
56/* Set *CFG to the configuration serialized in STREAM and cache it in
57 * CONFIG_POOL under CHECKSUM. The configuration will only be parsed if
58 * we can't find it the CONFIG_POOL already.
184 *
185 * RESULT_POOL determines the lifetime of the returned reference and
186 * SCRATCH_POOL is being used for temporary allocations.
187 */
188static svn_error_t *
59 *
60 * RESULT_POOL determines the lifetime of the returned reference and
61 * SCRATCH_POOL is being used for temporary allocations.
62 */
63static svn_error_t *
189auto_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)
64find_config(svn_config_t **cfg,
65 svn_repos__config_pool_t *config_pool,
66 svn_stream_t *stream,
67 svn_checksum_t *checksum,
68 apr_pool_t *result_pool,
69 apr_pool_t *scratch_pool)
196{
70{
197 svn_checksum_t *checksum;
198 config_object_t *config_object;
199 apr_pool_t *cfg_pool;
71 /* First, attempt the cache lookup. */
72 svn_membuf_t *key = checksum_as_key(checksum, scratch_pool);
73 SVN_ERR(svn_object_pool__lookup((void **)cfg, config_pool, key,
74 result_pool));
200
75
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 */
239static svn_error_t *
240add_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 */
294static svn_error_t *
295find_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? */
76 /* Not found? => parse and cache */
364 if (!*cfg)
365 {
77 if (!*cfg)
78 {
366 svn_filesize_t length;
79 svn_config_t *config;
367
80
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;
81 /* create a pool for the new config object and parse the data into it */
82 apr_pool_t *cfg_pool = svn_object_pool__new_item_pool(config_pool);
83 SVN_ERR(svn_config_parse(&config, stream, FALSE, FALSE, cfg_pool));
372
84
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));
85 /* switch config data to r/o mode to guarantee thread-safe access */
86 svn_config__set_read_only(config, cfg_pool);
377
87
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));
88 /* add config in pool, handle loads races and return the right config */
89 SVN_ERR(svn_object_pool__insert((void **)cfg, config_pool, key,
90 config, cfg_pool, result_pool));
381 }
382
91 }
92
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
93 return SVN_NO_ERROR;
94}
95
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 */
401static svn_error_t *
402key_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
436svn_error_t *
437svn_repos__config_pool_create(svn_repos__config_pool_t **config_pool,
438 svn_boolean_t thread_safe,
439 apr_pool_t *pool)
440{
96/* API implementation */
97
98svn_error_t *
99svn_repos__config_pool_create(svn_repos__config_pool_t **config_pool,
100 svn_boolean_t thread_safe,
101 apr_pool_t *pool)
102{
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;
103 return svn_error_trace(svn_object_pool__create(config_pool,
104 thread_safe, pool));
457}
458
459svn_error_t *
460svn_repos__config_pool_get(svn_config_t **cfg,
105}
106
107svn_error_t *
108svn_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,
109 svn_repos__config_pool_t *config_pool,
110 const char *path,
111 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);
112 svn_repos_t *preferred_repos,
113 apr_pool_t *pool)
114{
115 svn_error_t *err = SVN_NO_ERROR;
116 apr_pool_t *scratch_pool = svn_pool_create(pool);
117 config_access_t *access = svn_repos__create_config_access(preferred_repos,
118 scratch_pool);
119 svn_stream_t *stream;
120 svn_checksum_t *checksum;
471
121
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;
122 *cfg = NULL;
123 err = svn_repos__get_config(&stream, &checksum, access, path, must_exist,
124 scratch_pool);
125 if (!err)
126 err = svn_error_quick_wrapf(find_config(cfg, config_pool, stream,
127 checksum, pool, scratch_pool),
128 "Error while parsing config file: '%s':",
129 path);
478
130
479 if (svn_path_is_url(path))
131 /* Let the standard implementation handle all the difficult cases.
132 * Note that for in-repo configs, there are no further special cases to
133 * check for and deal with. */
134 if (!*cfg && !svn_path_is_url(path))
480 {
135 {
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 }
136 svn_error_clear(err);
137 err = svn_config_read3(cfg, path, must_exist, FALSE, FALSE, pool);
507 }
138 }
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
139
140 svn_repos__destroy_config_access(access);
528 svn_pool_destroy(scratch_pool);
529
141 svn_pool_destroy(scratch_pool);
142
530 return err;
143 /* we need to duplicate the root structure as it contains temp. buffers */
144 if (*cfg)
145 *cfg = svn_config__shallow_copy(*cfg, pool);
146
147 return svn_error_trace(err);
531}
148}