summaryrefslogtreecommitdiff
path: root/subversion/libsvn_repos/config_file.c
diff options
context:
space:
mode:
Diffstat (limited to 'subversion/libsvn_repos/config_file.c')
-rw-r--r--subversion/libsvn_repos/config_file.c386
1 files changed, 386 insertions, 0 deletions
diff --git a/subversion/libsvn_repos/config_file.c b/subversion/libsvn_repos/config_file.c
new file mode 100644
index 0000000000000..918727796eb79
--- /dev/null
+++ b/subversion/libsvn_repos/config_file.c
@@ -0,0 +1,386 @@
+/*
+ * config_file.c : efficiently read config files from disk or repo
+ *
+ * ====================================================================
+ * 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_path.h"
+#include "svn_pools.h"
+
+#include "private/svn_subr_private.h"
+#include "private/svn_repos_private.h"
+#include "private/svn_config_private.h"
+
+#include "config_file.h"
+
+#include "svn_private_config.h"
+
+
+
+struct config_access_t
+{
+ /* The last repository that we found the requested URL in. May be NULL. */
+ svn_repos_t *repos;
+
+ /* Owning pool of this structure and is private to this structure.
+ * All objects with the lifetime of this access object will be allocated
+ * from this pool. */
+ apr_pool_t *pool;
+};
+
+
+
+/* A stream object that gives access to a representation's content but
+ * delays accessing the repository data until the stream is first used.
+ * IOW, the stream object is cheap as long as it is not accessed.
+ */
+typedef struct presentation_stream_baton_t
+{
+ svn_fs_root_t *root;
+ const char *fs_path;
+ apr_pool_t *pool;
+ svn_stream_t *inner;
+} presentation_stream_baton_t;
+
+static svn_error_t *
+auto_open_inner_stream(presentation_stream_baton_t *b)
+{
+ if (!b->inner)
+ {
+ svn_filesize_t length;
+ svn_stream_t *stream;
+ svn_stringbuf_t *contents;
+
+ SVN_ERR(svn_fs_file_length(&length, b->root, b->fs_path, b->pool));
+ SVN_ERR(svn_fs_file_contents(&stream, b->root, b->fs_path, b->pool));
+ SVN_ERR(svn_stringbuf_from_stream(&contents, stream,
+ (apr_size_t)length, b->pool));
+ b->inner = svn_stream_from_stringbuf(contents, b->pool);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+read_handler_rep(void *baton, char *buffer, apr_size_t *len)
+{
+ presentation_stream_baton_t *b = baton;
+ SVN_ERR(auto_open_inner_stream(b));
+
+ return svn_error_trace(svn_stream_read2(b->inner, buffer, len));
+}
+
+static svn_error_t *
+mark_handler_rep(void *baton, svn_stream_mark_t **mark, apr_pool_t *pool)
+{
+ presentation_stream_baton_t *b = baton;
+ SVN_ERR(auto_open_inner_stream(b));
+
+ return svn_error_trace(svn_stream_mark(b->inner, mark, pool));
+}
+
+static svn_error_t *
+seek_handler_rep(void *baton, const svn_stream_mark_t *mark)
+{
+ presentation_stream_baton_t *b = baton;
+ SVN_ERR(auto_open_inner_stream(b));
+
+ return svn_error_trace(svn_stream_seek(b->inner, mark));
+}
+
+static svn_error_t *
+skip_handler_rep(void *baton, apr_size_t len)
+{
+ presentation_stream_baton_t *b = baton;
+ SVN_ERR(auto_open_inner_stream(b));
+
+ return svn_error_trace(svn_stream_skip(b->inner, len));
+}
+
+static svn_error_t *
+data_available_handler_rep(void *baton, svn_boolean_t *data_available)
+{
+ presentation_stream_baton_t *b = baton;
+ SVN_ERR(auto_open_inner_stream(b));
+
+ return svn_error_trace(svn_stream_data_available(b->inner, data_available));
+}
+
+static svn_error_t *
+readline_handler_rep(void *baton,
+ svn_stringbuf_t **stringbuf,
+ const char *eol,
+ svn_boolean_t *eof,
+ apr_pool_t *pool)
+{
+ presentation_stream_baton_t *b = baton;
+ SVN_ERR(auto_open_inner_stream(b));
+
+ return svn_error_trace(svn_stream_readline(b->inner, stringbuf, eol, eof,
+ pool));
+}
+
+/* Return a lazy access stream for FS_PATH under ROOT, allocated in POOL. */
+static svn_stream_t *
+representation_stream(svn_fs_root_t *root,
+ const char *fs_path,
+ apr_pool_t *pool)
+{
+ svn_stream_t *stream;
+ presentation_stream_baton_t *baton;
+
+ baton = apr_pcalloc(pool, sizeof(*baton));
+ baton->root = root;
+ baton->fs_path = fs_path;
+ baton->pool = pool;
+
+ stream = svn_stream_create(baton, pool);
+ svn_stream_set_read2(stream, read_handler_rep, read_handler_rep);
+ svn_stream_set_mark(stream, mark_handler_rep);
+ svn_stream_set_seek(stream, seek_handler_rep);
+ svn_stream_set_skip(stream, skip_handler_rep);
+ svn_stream_set_data_available(stream, data_available_handler_rep);
+ svn_stream_set_readline(stream, readline_handler_rep);
+ return stream;
+}
+
+/* Handle the case of a file PATH / url pointing to anything that is either
+ * not a file or does not exist at all. The case is given by NODE_KIND.
+ *
+ * If MUST_EXIST is not set and the file does not exist at all, return a
+ * default *STREAM and *CHECKSUM allocated in the context of ACCESS, or an
+ * error otherwise.
+ */
+static svn_error_t *
+handle_missing_file(svn_stream_t **stream,
+ svn_checksum_t **checksum,
+ config_access_t *access,
+ const char *path,
+ svn_boolean_t must_exist,
+ svn_node_kind_t node_kind)
+{
+ if (node_kind == svn_node_none && !must_exist)
+ {
+ *stream = svn_stream_empty(access->pool);
+ SVN_ERR(svn_checksum(checksum, svn_checksum_md5, "", 0, access->pool));
+ }
+ else if (node_kind != svn_node_file)
+ {
+ return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
+ "'%s' is not a file", path);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Open the in-repository file at URL, return its content checksum in
+ * *CHECKSUM and the content itself through *STREAM. Allocate those with
+ * the lifetime of ACCESS and use SCRATCH_POOL for temporaries.
+ *
+ * Error out when the file does not exist but MUST_EXIST is set.
+ */
+static svn_error_t *
+get_repos_config(svn_stream_t **stream,
+ svn_checksum_t **checksum,
+ config_access_t *access,
+ const char *url,
+ svn_boolean_t must_exist,
+ apr_pool_t *scratch_pool)
+{
+ svn_fs_t *fs;
+ svn_fs_root_t *root;
+ svn_revnum_t youngest_rev;
+ svn_node_kind_t node_kind;
+ const char *dirent;
+ const char *fs_path;
+ const char *repos_root_dirent;
+
+ SVN_ERR(svn_uri_get_dirent_from_file_url(&dirent, url, access->pool));
+
+ /* Maybe we can use the repos hint instance instead of creating a
+ * new one. */
+ if (access->repos)
+ {
+ repos_root_dirent = svn_repos_path(access->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))
+ access->repos = NULL;
+ }
+
+ /* Open repos if no suitable repos is available. */
+ if (!access->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(&access->repos, repos_root_dirent, NULL,
+ access->pool, scratch_pool));
+ }
+
+ fs_path = &dirent[strlen(repos_root_dirent)];
+
+ /* Get the filesystem. */
+ fs = svn_repos_fs(access->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, access->pool));
+
+ /* Special case: non-existent paths may be handled as "empty" contents. */
+ SVN_ERR(svn_fs_check_path(&node_kind, root, fs_path, scratch_pool));
+ if (node_kind != svn_node_file)
+ return svn_error_trace(handle_missing_file(stream, checksum, access,
+ url, must_exist, node_kind));
+
+ /* Fetch checksum and see whether we already have a matching config */
+ SVN_ERR(svn_fs_file_checksum(checksum, svn_checksum_md5, root, fs_path,
+ TRUE, access->pool));
+
+ *stream = representation_stream(root, fs_path, access->pool);
+
+ return SVN_NO_ERROR;
+}
+
+/* Open the file at PATH, return its content checksum in CHECKSUM and the
+ * content itself through *STREAM. Allocate those with the lifetime of
+ * ACCESS.
+ */
+static svn_error_t *
+get_file_config(svn_stream_t **stream,
+ svn_checksum_t **checksum,
+ config_access_t *access,
+ const char *path,
+ svn_boolean_t must_exist,
+ apr_pool_t *scratch_pool)
+{
+ svn_stringbuf_t *contents;
+ svn_node_kind_t node_kind;
+
+ /* Special case: non-existent paths may be handled as "empty" contents. */
+ SVN_ERR(svn_io_check_path(path, &node_kind, scratch_pool));
+ if (node_kind != svn_node_file)
+ return svn_error_trace(handle_missing_file(stream, checksum, access,
+ path, must_exist, node_kind));
+
+ /* Now, we should be able to read the file. */
+ SVN_ERR(svn_stringbuf_from_file2(&contents, path, access->pool));
+
+ /* calculate MD5 over the whole file contents */
+ SVN_ERR(svn_checksum(checksum, svn_checksum_md5,
+ contents->data, contents->len, access->pool));
+ *stream = svn_stream_from_stringbuf(contents, access->pool);
+
+ return SVN_NO_ERROR;
+}
+
+/* Read the configuration from path, URL or registry sub-tree PATH, return
+ * its content checksum in CHECKSUM and the content itself through *STREAM.
+ * Allocate those with the lifetime of ACCESS.
+ */
+static svn_error_t *
+get_generic_config(svn_stream_t **stream,
+ svn_checksum_t **checksum,
+ config_access_t *access,
+ const char *path,
+ svn_boolean_t must_exist,
+ apr_pool_t *scratch_pool)
+{
+ svn_stringbuf_t *contents = svn_stringbuf_create_empty(access->pool);
+ svn_config_t *config;
+
+ /* Read the configuration and serialize it into CONTENTS.
+ * That copy can then be processed by the authz parser etc. */
+ SVN_ERR(svn_config_read3(&config, path, must_exist, TRUE, TRUE,
+ scratch_pool));
+ SVN_ERR(svn_config__write(svn_stream_from_stringbuf(contents, scratch_pool),
+ config, scratch_pool));
+
+ /* calculate MD5 over the whole file contents */
+ SVN_ERR(svn_checksum(checksum, svn_checksum_md5,
+ contents->data, contents->len, access->pool));
+ *stream = svn_stream_from_stringbuf(contents, access->pool);
+
+ return SVN_NO_ERROR;
+}
+
+config_access_t *
+svn_repos__create_config_access(svn_repos_t *repos_hint,
+ apr_pool_t *result_pool)
+{
+ apr_pool_t *pool = svn_pool_create(result_pool);
+ config_access_t *result = apr_pcalloc(pool, sizeof(*result));
+
+ result->repos = repos_hint;
+ result->pool = pool;
+
+ return result;
+}
+
+void
+svn_repos__destroy_config_access(config_access_t *access)
+{
+ svn_pool_destroy(access->pool);
+}
+
+svn_error_t *
+svn_repos__get_config(svn_stream_t **stream,
+ svn_checksum_t **checksum,
+ config_access_t *access,
+ const char *path,
+ svn_boolean_t must_exist,
+ apr_pool_t *scratch_pool)
+{
+ svn_error_t *err;
+ /* Directly access the config data. */
+ if (svn_path_is_url(path))
+ err = get_repos_config(stream, checksum, access, path, must_exist,
+ scratch_pool);
+ else
+ err = get_file_config(stream, checksum, access, path, must_exist,
+ scratch_pool);
+
+ /* Fallback to indirect access using the generic config file parser.
+ * This is mainly used for registry support under Win32. */
+ if (err)
+ {
+ svn_error_t *err2 = get_generic_config(stream, checksum, access, path,
+ must_exist, scratch_pool);
+ if (err2)
+ {
+ svn_error_clear(err2);
+ }
+ else
+ {
+ svn_error_clear(err);
+ err = SVN_NO_ERROR;
+ }
+ }
+
+ return svn_error_trace(err);
+}