summaryrefslogtreecommitdiff
path: root/subversion/libsvn_repos/config_pool.c
diff options
context:
space:
mode:
Diffstat (limited to 'subversion/libsvn_repos/config_pool.c')
-rw-r--r--subversion/libsvn_repos/config_pool.c531
1 files changed, 531 insertions, 0 deletions
diff --git a/subversion/libsvn_repos/config_pool.c b/subversion/libsvn_repos/config_pool.c
new file mode 100644
index 0000000..164bd98
--- /dev/null
+++ b/subversion/libsvn_repos/config_pool.c
@@ -0,0 +1,531 @@
+/*
+ * config_pool.c : pool of configuration objects
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+
+
+#include "svn_checksum.h"
+#include "svn_config.h"
+#include "svn_error.h"
+#include "svn_hash.h"
+#include "svn_path.h"
+#include "svn_pools.h"
+#include "svn_repos.h"
+
+#include "private/svn_dep_compat.h"
+#include "private/svn_mutex.h"
+#include "private/svn_subr_private.h"
+#include "private/svn_repos_private.h"
+#include "private/svn_object_pool.h"
+
+#include "svn_private_config.h"
+
+
+/* Our wrapper structure for parsed svn_config_t* instances. All data in
+ * CS_CFG and CI_CFG is expanded (to make it thread-safe) and considered
+ * read-only.
+ */
+typedef struct config_object_t
+{
+ /* UUID of the configuration contents.
+ * This is a SHA1 checksum of the parsed textual representation of CFG. */
+ svn_checksum_t *key;
+
+ /* Parsed and expanded configuration. At least one of the following
+ * must not be NULL. */
+
+ /* Case-sensitive config. May be NULL */
+ svn_config_t *cs_cfg;
+
+ /* Case-insensitive config. May be NULL */
+ svn_config_t *ci_cfg;
+} config_object_t;
+
+
+/* Data structure used to short-circuit the repository access for configs
+ * read via URL. After reading such a config successfully, we store key
+ * repository information here and will validate it without actually opening
+ * the repository.
+ *
+ * As this is only an optimization and may create many entries in
+ * svn_repos__config_pool_t's IN_REPO_HASH_POOL index, we clean them up
+ * once in a while.
+ */
+typedef struct in_repo_config_t
+{
+ /* URL used to open the configuration */
+ const char *url;
+
+ /* Path of the repository that contained URL */
+ const char *repo_root;
+
+ /* Head revision of that repository when last read */
+ svn_revnum_t revision;
+
+ /* Contents checksum of the file stored under URL@REVISION */
+ svn_checksum_t *key;
+} in_repo_config_t;
+
+
+/* Core data structure extending the encapsulated OBJECT_POOL. All access
+ * to it must be serialized using the OBJECT_POOL->MUTEX.
+ *
+ * To speed up URL@HEAD lookups, we maintain IN_REPO_CONFIGS as a secondary
+ * hash index. It maps URLs as provided by the caller onto in_repo_config_t
+ * instances. If that is still up-to-date, a further lookup into CONFIG
+ * may yield the desired configuration without the need to actually open
+ * the respective repository.
+ *
+ * Unused configurations that are kept in the IN_REPO_CONFIGS hash and may
+ * be cleaned up when the hash is about to grow.
+ */
+struct svn_repos__config_pool_t
+{
+ svn_object_pool__t *object_pool;
+
+ /* URL -> in_repo_config_t* mapping.
+ * This is only a partial index and will get cleared regularly. */
+ apr_hash_t *in_repo_configs;
+
+ /* allocate the IN_REPO_CONFIGS index and in_repo_config_t here */
+ apr_pool_t *in_repo_hash_pool;
+};
+
+
+/* Return an automatic reference to the CFG member in CONFIG that will be
+ * released when POOL gets cleaned up. The case sensitivity flag in *BATON
+ * selects the desired option and section name matching mode.
+ */
+static void *
+getter(void *object,
+ void *baton,
+ apr_pool_t *pool)
+{
+ config_object_t *wrapper = object;
+ svn_boolean_t *case_sensitive = baton;
+ svn_config_t *config = *case_sensitive ? wrapper->cs_cfg : wrapper->ci_cfg;
+
+ /* we need to duplicate the root structure as it contains temp. buffers */
+ return config ? svn_config__shallow_copy(config, pool) : NULL;
+}
+
+/* Return a memory buffer structure allocated in POOL and containing the
+ * data from CHECKSUM.
+ */
+static svn_membuf_t *
+checksum_as_key(svn_checksum_t *checksum,
+ apr_pool_t *pool)
+{
+ svn_membuf_t *result = apr_pcalloc(pool, sizeof(*result));
+ apr_size_t size = svn_checksum_size(checksum);
+
+ svn_membuf__create(result, size, pool);
+ result->size = size; /* exact length is required! */
+ memcpy(result->data, checksum->digest, size);
+
+ return result;
+}
+
+/* Copy the configuration from the wrapper in SOURCE to the wrapper in
+ * *TARGET with the case sensitivity flag in *BATON selecting the config
+ * to copy. This is usually done to add the missing case-(in)-sensitive
+ * variant. Since we must hold all data in *TARGET from the same POOL,
+ * a deep copy is required.
+ */
+static svn_error_t *
+setter(void **target,
+ void *source,
+ void *baton,
+ apr_pool_t *pool)
+{
+ svn_boolean_t *case_sensitive = baton;
+ config_object_t *target_cfg = *(config_object_t **)target;
+ config_object_t *source_cfg = source;
+
+ /* Maybe, we created a variant with different case sensitivity? */
+ if (*case_sensitive && target_cfg->cs_cfg == NULL)
+ {
+ SVN_ERR(svn_config_dup(&target_cfg->cs_cfg, source_cfg->cs_cfg, pool));
+ svn_config__set_read_only(target_cfg->cs_cfg, pool);
+ }
+ else if (!*case_sensitive && target_cfg->ci_cfg == NULL)
+ {
+ SVN_ERR(svn_config_dup(&target_cfg->ci_cfg, source_cfg->ci_cfg, pool));
+ svn_config__set_read_only(target_cfg->ci_cfg, pool);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Set *CFG to the configuration passed in as text in CONTENTS and *KEY to
+ * the corresponding object pool key. If no such configuration exists in
+ * CONFIG_POOL, yet, parse CONTENTS and cache the result. CASE_SENSITIVE
+ * controls option and section name matching.
+ *
+ * RESULT_POOL determines the lifetime of the returned reference and
+ * SCRATCH_POOL is being used for temporary allocations.
+ */
+static svn_error_t *
+auto_parse(svn_config_t **cfg,
+ svn_membuf_t **key,
+ svn_repos__config_pool_t *config_pool,
+ svn_stringbuf_t *contents,
+ svn_boolean_t case_sensitive,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_checksum_t *checksum;
+ config_object_t *config_object;
+ apr_pool_t *cfg_pool;
+
+ /* calculate SHA1 over the whole file contents */
+ SVN_ERR(svn_stream_close
+ (svn_stream_checksummed2
+ (svn_stream_from_stringbuf(contents, scratch_pool),
+ &checksum, NULL, svn_checksum_sha1, TRUE, scratch_pool)));
+
+ /* return reference to suitable config object if that already exists */
+ *key = checksum_as_key(checksum, result_pool);
+ SVN_ERR(svn_object_pool__lookup((void **)cfg, config_pool->object_pool,
+ *key, &case_sensitive, result_pool));
+ if (*cfg)
+ return SVN_NO_ERROR;
+
+ /* create a pool for the new config object and parse the data into it */
+ cfg_pool = svn_object_pool__new_wrapper_pool(config_pool->object_pool);
+
+ config_object = apr_pcalloc(cfg_pool, sizeof(*config_object));
+
+ SVN_ERR(svn_config_parse(case_sensitive ? &config_object->cs_cfg
+ : &config_object->ci_cfg,
+ svn_stream_from_stringbuf(contents, scratch_pool),
+ case_sensitive, case_sensitive, cfg_pool));
+
+ /* switch config data to r/o mode to guarantee thread-safe access */
+ svn_config__set_read_only(case_sensitive ? config_object->cs_cfg
+ : config_object->ci_cfg,
+ cfg_pool);
+
+ /* add config in pool, handle loads races and return the right config */
+ SVN_ERR(svn_object_pool__insert((void **)cfg, config_pool->object_pool,
+ *key, config_object, &case_sensitive,
+ cfg_pool, result_pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* Store a URL@REVISION to CHECKSUM, REPOS_ROOT in CONFIG_POOL.
+ */
+static svn_error_t *
+add_checksum(svn_repos__config_pool_t *config_pool,
+ const char *url,
+ const char *repos_root,
+ svn_revnum_t revision,
+ svn_checksum_t *checksum)
+{
+ apr_size_t path_len = strlen(url);
+ apr_pool_t *pool = config_pool->in_repo_hash_pool;
+ in_repo_config_t *config = apr_hash_get(config_pool->in_repo_configs,
+ url, path_len);
+ if (config)
+ {
+ /* update the existing entry */
+ memcpy((void *)config->key->digest, checksum->digest,
+ svn_checksum_size(checksum));
+ config->revision = revision;
+
+ /* duplicate the string only if necessary */
+ if (strcmp(config->repo_root, repos_root))
+ config->repo_root = apr_pstrdup(pool, repos_root);
+ }
+ else
+ {
+ /* insert a new entry.
+ * Limit memory consumption by cyclically clearing pool and hash. */
+ if (2 * svn_object_pool__count(config_pool->object_pool)
+ < apr_hash_count(config_pool->in_repo_configs))
+ {
+ svn_pool_clear(pool);
+ config_pool->in_repo_configs = svn_hash__make(pool);
+ }
+
+ /* construct the new entry */
+ config = apr_pcalloc(pool, sizeof(*config));
+ config->key = svn_checksum_dup(checksum, pool);
+ config->url = apr_pstrmemdup(pool, url, path_len);
+ config->repo_root = apr_pstrdup(pool, repos_root);
+ config->revision = revision;
+
+ /* add to index */
+ apr_hash_set(config_pool->in_repo_configs, url, path_len, config);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Set *CFG to the configuration stored in URL@HEAD and cache it in
+ * CONFIG_POOL. CASE_SENSITIVE controls
+ * option and section name matching. If PREFERRED_REPOS is given,
+ * use that if it also matches URL.
+ *
+ * RESULT_POOL determines the lifetime of the returned reference and
+ * SCRATCH_POOL is being used for temporary allocations.
+ */
+static svn_error_t *
+find_repos_config(svn_config_t **cfg,
+ svn_membuf_t **key,
+ svn_repos__config_pool_t *config_pool,
+ const char *url,
+ svn_boolean_t case_sensitive,
+ svn_repos_t *preferred_repos,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_repos_t *repos = NULL;
+ svn_fs_t *fs;
+ svn_fs_root_t *root;
+ svn_revnum_t youngest_rev;
+ svn_node_kind_t node_kind;
+ const char *dirent;
+ svn_stream_t *stream;
+ const char *fs_path;
+ const char *repos_root_dirent;
+ svn_checksum_t *checksum;
+ svn_stringbuf_t *contents;
+
+ *cfg = NULL;
+ SVN_ERR(svn_uri_get_dirent_from_file_url(&dirent, url, scratch_pool));
+
+ /* maybe we can use the preferred repos instance instead of creating a
+ * new one */
+ if (preferred_repos)
+ {
+ repos_root_dirent = svn_repos_path(preferred_repos, scratch_pool);
+ if (!svn_dirent_is_absolute(repos_root_dirent))
+ SVN_ERR(svn_dirent_get_absolute(&repos_root_dirent,
+ repos_root_dirent,
+ scratch_pool));
+
+ if (svn_dirent_is_ancestor(repos_root_dirent, dirent))
+ repos = preferred_repos;
+ }
+
+ /* open repos if no suitable preferred repos was provided. */
+ if (!repos)
+ {
+ /* Search for a repository in the full path. */
+ repos_root_dirent = svn_repos_find_root_path(dirent, scratch_pool);
+
+ /* Attempt to open a repository at repos_root_dirent. */
+ SVN_ERR(svn_repos_open3(&repos, repos_root_dirent, NULL,
+ scratch_pool, scratch_pool));
+ }
+
+ fs_path = &dirent[strlen(repos_root_dirent)];
+
+ /* Get the filesystem. */
+ fs = svn_repos_fs(repos);
+
+ /* Find HEAD and the revision root */
+ SVN_ERR(svn_fs_youngest_rev(&youngest_rev, fs, scratch_pool));
+ SVN_ERR(svn_fs_revision_root(&root, fs, youngest_rev, scratch_pool));
+
+ /* Fetch checksum and see whether we already have a matching config */
+ SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_sha1, root, fs_path,
+ FALSE, scratch_pool));
+ if (checksum)
+ {
+ *key = checksum_as_key(checksum, scratch_pool);
+ SVN_ERR(svn_object_pool__lookup((void **)cfg, config_pool->object_pool,
+ *key, &case_sensitive, result_pool));
+ }
+
+ /* not parsed, yet? */
+ if (!*cfg)
+ {
+ svn_filesize_t length;
+
+ /* fetch the file contents */
+ SVN_ERR(svn_fs_check_path(&node_kind, root, fs_path, scratch_pool));
+ if (node_kind != svn_node_file)
+ return SVN_NO_ERROR;
+
+ SVN_ERR(svn_fs_file_length(&length, root, fs_path, scratch_pool));
+ SVN_ERR(svn_fs_file_contents(&stream, root, fs_path, scratch_pool));
+ SVN_ERR(svn_stringbuf_from_stream(&contents, stream,
+ (apr_size_t)length, scratch_pool));
+
+ /* handle it like ordinary file contents and cache it */
+ SVN_ERR(auto_parse(cfg, key, config_pool, contents, case_sensitive,
+ result_pool, scratch_pool));
+ }
+
+ /* store the (path,rev) -> checksum mapping as well */
+ if (*cfg && checksum)
+ SVN_MUTEX__WITH_LOCK(svn_object_pool__mutex(config_pool->object_pool),
+ add_checksum(config_pool, url, repos_root_dirent,
+ youngest_rev, checksum));
+
+ return SVN_NO_ERROR;
+}
+
+/* Given the URL, search the CONFIG_POOL for an entry that maps it URL to
+ * a content checksum and is still up-to-date. If this could be found,
+ * return the object's *KEY. Use POOL for allocations.
+ *
+ * Requires external serialization on CONFIG_POOL.
+ *
+ * Note that this is only the URL(+rev) -> Checksum lookup and does not
+ * guarantee that there is actually a config object available for *KEY.
+ */
+static svn_error_t *
+key_by_url(svn_membuf_t **key,
+ svn_repos__config_pool_t *config_pool,
+ const char *url,
+ apr_pool_t *pool)
+{
+ svn_error_t *err;
+ svn_stringbuf_t *contents;
+ apr_int64_t current;
+
+ /* hash lookup url -> sha1 -> config */
+ in_repo_config_t *config = svn_hash_gets(config_pool->in_repo_configs, url);
+ *key = NULL;
+ if (!config)
+ return SVN_NO_ERROR;
+
+ /* found *some* reference to a configuration.
+ * Verify that it is still current. Will fail for BDB repos. */
+ err = svn_stringbuf_from_file2(&contents,
+ svn_dirent_join(config->repo_root,
+ "db/current", pool),
+ pool);
+ if (!err)
+ err = svn_cstring_atoi64(&current, contents->data);
+
+ if (err)
+ svn_error_clear(err);
+ else if (current == config->revision)
+ *key = checksum_as_key(config->key, pool);
+
+ return SVN_NO_ERROR;
+}
+
+/* API implementation */
+
+svn_error_t *
+svn_repos__config_pool_create(svn_repos__config_pool_t **config_pool,
+ svn_boolean_t thread_safe,
+ apr_pool_t *pool)
+{
+ svn_repos__config_pool_t *result;
+ svn_object_pool__t *object_pool;
+
+ SVN_ERR(svn_object_pool__create(&object_pool, getter, setter,
+ thread_safe, pool));
+
+ /* construct the config pool in our private ROOT_POOL to survive POOL
+ * cleanup and to prevent threading issues with the allocator */
+ result = apr_pcalloc(pool, sizeof(*result));
+
+ result->object_pool = object_pool;
+ result->in_repo_hash_pool = svn_pool_create(pool);
+ result->in_repo_configs = svn_hash__make(result->in_repo_hash_pool);
+
+ *config_pool = result;
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_repos__config_pool_get(svn_config_t **cfg,
+ svn_membuf_t **key,
+ svn_repos__config_pool_t *config_pool,
+ const char *path,
+ svn_boolean_t must_exist,
+ svn_boolean_t case_sensitive,
+ svn_repos_t *preferred_repos,
+ apr_pool_t *pool)
+{
+ svn_error_t *err = SVN_NO_ERROR;
+ apr_pool_t *scratch_pool = svn_pool_create(pool);
+
+ /* make sure we always have a *KEY object */
+ svn_membuf_t *local_key = NULL;
+ if (key == NULL)
+ key = &local_key;
+ else
+ *key = NULL;
+
+ if (svn_path_is_url(path))
+ {
+ /* Read config file from repository.
+ * Attempt a quick lookup first. */
+ SVN_MUTEX__WITH_LOCK(svn_object_pool__mutex(config_pool->object_pool),
+ key_by_url(key, config_pool, path, pool));
+ if (*key)
+ {
+ SVN_ERR(svn_object_pool__lookup((void **)cfg,
+ config_pool->object_pool,
+ *key, &case_sensitive, pool));
+ if (*cfg)
+ {
+ svn_pool_destroy(scratch_pool);
+ return SVN_NO_ERROR;
+ }
+ }
+
+ /* Read and cache the configuration. This may fail. */
+ err = find_repos_config(cfg, key, config_pool, path, case_sensitive,
+ preferred_repos, pool, scratch_pool);
+ if (err || !*cfg)
+ {
+ /* let the standard implementation handle all the difficult cases */
+ svn_error_clear(err);
+ err = svn_repos__retrieve_config(cfg, path, must_exist,
+ case_sensitive, pool);
+ }
+ }
+ else
+ {
+ /* Outside of repo file. Read it. */
+ svn_stringbuf_t *contents;
+ err = svn_stringbuf_from_file2(&contents, path, scratch_pool);
+ if (err)
+ {
+ /* let the standard implementation handle all the difficult cases */
+ svn_error_clear(err);
+ err = svn_config_read3(cfg, path, must_exist, case_sensitive,
+ case_sensitive, pool);
+ }
+ else
+ {
+ /* parsing and caching will always succeed */
+ err = auto_parse(cfg, key, config_pool, contents, case_sensitive,
+ pool, scratch_pool);
+ }
+ }
+
+ svn_pool_destroy(scratch_pool);
+
+ return err;
+}