/* * 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; }