]> CyberLeo.Net >> Repos - FreeBSD/stable/10.git/blob - contrib/subversion/subversion/libsvn_repos/config_pool.c
MFC r275385 (by bapt):
[FreeBSD/stable/10.git] / contrib / subversion / subversion / libsvn_repos / config_pool.c
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 \f
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 \f
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 \f
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 }