diff options
author | Lorry Tar Creator <lorry-tar-importer@lorry> | 2017-08-05 16:22:51 +0000 |
---|---|---|
committer | Lorry Tar Creator <lorry-tar-importer@lorry> | 2017-08-05 16:22:51 +0000 |
commit | cf46733632c7279a9fd0fe6ce26f9185a4ae82a9 (patch) | |
tree | da27775a2161723ef342e91af41a8b51fedef405 /subversion/libsvn_repos/config_pool.c | |
parent | bb0ef45f7c46b0ae221b26265ef98a768c33f820 (diff) | |
download | subversion-tarball-master.tar.gz |
subversion-1.9.7HEADsubversion-1.9.7master
Diffstat (limited to 'subversion/libsvn_repos/config_pool.c')
-rw-r--r-- | subversion/libsvn_repos/config_pool.c | 531 |
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(¤t, 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; +} |