aboutsummaryrefslogtreecommitdiff
path: root/subversion/libsvn_repos/list.c
diff options
context:
space:
mode:
Diffstat (limited to 'subversion/libsvn_repos/list.c')
-rw-r--r--subversion/libsvn_repos/list.c340
1 files changed, 340 insertions, 0 deletions
diff --git a/subversion/libsvn_repos/list.c b/subversion/libsvn_repos/list.c
new file mode 100644
index 000000000000..ef8ac32b1b38
--- /dev/null
+++ b/subversion/libsvn_repos/list.c
@@ -0,0 +1,340 @@
+/* list.c : listing repository contents
+ *
+ * ====================================================================
+ * 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 <apr_pools.h>
+#include <apr_fnmatch.h>
+
+#include "svn_pools.h"
+#include "svn_error.h"
+#include "svn_dirent_uri.h"
+#include "svn_time.h"
+
+#include "private/svn_repos_private.h"
+#include "private/svn_sorts_private.h"
+#include "private/svn_utf_private.h"
+#include "svn_private_config.h" /* for SVN_TEMPLATE_ROOT_DIR */
+
+#include "repos.h"
+
+
+
+/* Utility function. Given DIRENT->KIND, set all other elements of *DIRENT
+ * with the values retrieved for PATH under ROOT. Allocate them in POOL.
+ */
+static svn_error_t *
+fill_dirent(svn_dirent_t *dirent,
+ svn_fs_root_t *root,
+ const char *path,
+ apr_pool_t *scratch_pool)
+{
+ const char *datestring;
+
+ if (dirent->kind == svn_node_file)
+ SVN_ERR(svn_fs_file_length(&(dirent->size), root, path, scratch_pool));
+ else
+ dirent->size = SVN_INVALID_FILESIZE;
+
+ SVN_ERR(svn_fs_node_has_props(&dirent->has_props, root, path,
+ scratch_pool));
+
+ SVN_ERR(svn_repos_get_committed_info(&(dirent->created_rev),
+ &datestring,
+ &(dirent->last_author),
+ root, path, scratch_pool));
+ if (datestring)
+ SVN_ERR(svn_time_from_cstring(&(dirent->time), datestring,
+ scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_repos_stat(svn_dirent_t **dirent,
+ svn_fs_root_t *root,
+ const char *path,
+ apr_pool_t *pool)
+{
+ svn_node_kind_t kind;
+ svn_dirent_t *ent;
+
+ SVN_ERR(svn_fs_check_path(&kind, root, path, pool));
+
+ if (kind == svn_node_none)
+ {
+ *dirent = NULL;
+ return SVN_NO_ERROR;
+ }
+
+ ent = svn_dirent_create(pool);
+ ent->kind = kind;
+
+ SVN_ERR(fill_dirent(ent, root, path, pool));
+
+ *dirent = ent;
+ return SVN_NO_ERROR;
+}
+
+/* Return TRUE of DIRNAME matches any of the const char * in PATTERNS.
+ * Note that any DIRNAME will match if PATTERNS is empty.
+ * Use SCRATCH_BUFFER for temporary string contents. */
+static svn_boolean_t
+matches_any(const char *dirname,
+ const apr_array_header_t *patterns,
+ svn_membuf_t *scratch_buffer)
+{
+ return patterns
+ ? svn_utf__fuzzy_glob_match(dirname, patterns, scratch_buffer)
+ : TRUE;
+}
+
+/* Utility to prevent code duplication.
+ *
+ * Construct a svn_dirent_t for PATH of type KIND under ROOT and, if
+ * PATH_INFO_ONLY is not set, fill it. Call RECEIVER with the result
+ * and RECEIVER_BATON.
+ *
+ * Use SCRATCH_POOL for temporary allocations.
+ */
+static svn_error_t *
+report_dirent(svn_fs_root_t *root,
+ const char *path,
+ svn_node_kind_t kind,
+ svn_boolean_t path_info_only,
+ svn_repos_dirent_receiver_t receiver,
+ void *receiver_baton,
+ apr_pool_t *scratch_pool)
+{
+ svn_dirent_t dirent = { 0 };
+
+ /* Fetch the details to report - if required. */
+ dirent.kind = kind;
+ if (!path_info_only)
+ SVN_ERR(fill_dirent(&dirent, root, path, scratch_pool));
+
+ /* Report the entry. */
+ SVN_ERR(receiver(path, &dirent, receiver_baton, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* Utility data struct, used to attach a filter result flag to a dirent. */
+typedef struct filtered_dirent_t
+{
+ /* Actual dirent, never NULL. */
+ svn_fs_dirent_t *dirent;
+
+ /* DIRENT passed the filter. */
+ svn_boolean_t is_match;
+} filtered_dirent_t;
+
+/* Implement a standard sort function for filtered_dirent_t *, sorting them
+ * by entry name. */
+static int
+compare_filtered_dirent(const void *lhs,
+ const void *rhs)
+{
+ const filtered_dirent_t *lhs_dirent = (const filtered_dirent_t *)lhs;
+ const filtered_dirent_t *rhs_dirent = (const filtered_dirent_t *)rhs;
+
+ return strcmp(lhs_dirent->dirent->name, rhs_dirent->dirent->name);
+}
+
+/* Core of svn_repos_list with the same parameter list.
+ *
+ * However, DEPTH is not svn_depth_empty and PATH has already been reported.
+ * Therefore, we can call this recursively.
+ *
+ * Uses SCRATCH_BUFFER for temporary string contents.
+ */
+static svn_error_t *
+do_list(svn_fs_root_t *root,
+ const char *path,
+ const apr_array_header_t *patterns,
+ svn_depth_t depth,
+ svn_boolean_t path_info_only,
+ svn_repos_authz_func_t authz_read_func,
+ void *authz_read_baton,
+ svn_repos_dirent_receiver_t receiver,
+ void *receiver_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ svn_membuf_t *scratch_buffer,
+ apr_pool_t *scratch_pool)
+{
+ apr_hash_t *entries;
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+ apr_hash_index_t *hi;
+ apr_array_header_t *sorted;
+ int i;
+
+ /* Fetch all directory entries, filter and sort them.
+ *
+ * Performance trade-off:
+ * Constructing a full path vs. faster sort due to authz filtering.
+ * We filter according to DEPTH and PATTERNS only because constructing
+ * the full path required for authz is somewhat expensive and we don't
+ * want to do this twice while authz will rarely filter paths out.
+ */
+ SVN_ERR(svn_fs_dir_entries(&entries, root, path, scratch_pool));
+ sorted = apr_array_make(scratch_pool, apr_hash_count(entries),
+ sizeof(filtered_dirent_t));
+ for (hi = apr_hash_first(scratch_pool, entries); hi; hi = apr_hash_next(hi))
+ {
+ filtered_dirent_t filtered;
+ svn_pool_clear(iterpool);
+
+ filtered.dirent = apr_hash_this_val(hi);
+
+ /* Skip directories if we want to report files only. */
+ if (filtered.dirent->kind == svn_node_dir && depth == svn_depth_files)
+ continue;
+
+ /* We can skip files that don't match any of the search patterns. */
+ filtered.is_match = matches_any(filtered.dirent->name, patterns,
+ scratch_buffer);
+ if (!filtered.is_match && filtered.dirent->kind == svn_node_file)
+ continue;
+
+ APR_ARRAY_PUSH(sorted, filtered_dirent_t) = filtered;
+ }
+
+ svn_sort__array(sorted, compare_filtered_dirent);
+
+ /* Iterate over all remaining directory entries and report them.
+ * Recurse into sub-directories if requested. */
+ for (i = 0; i < sorted->nelts; ++i)
+ {
+ const char *sub_path;
+ filtered_dirent_t *filtered;
+ svn_fs_dirent_t *dirent;
+
+ svn_pool_clear(iterpool);
+
+ filtered = &APR_ARRAY_IDX(sorted, i, filtered_dirent_t);
+ dirent = filtered->dirent;
+
+ /* Skip paths that we don't have access to? */
+ sub_path = svn_dirent_join(path, dirent->name, iterpool);
+ if (authz_read_func)
+ {
+ svn_boolean_t has_access;
+ SVN_ERR(authz_read_func(&has_access, root, sub_path,
+ authz_read_baton, iterpool));
+ if (!has_access)
+ continue;
+ }
+
+ /* Report entry, if it passed the filter. */
+ if (filtered->is_match)
+ SVN_ERR(report_dirent(root, sub_path, dirent->kind, path_info_only,
+ receiver, receiver_baton, iterpool));
+
+ /* Check for cancellation before recursing down. This should be
+ * slightly more responsive for deep trees. */
+ if (cancel_func)
+ SVN_ERR(cancel_func(cancel_baton));
+
+ /* Recurse on directories. */
+ if (depth == svn_depth_infinity && dirent->kind == svn_node_dir)
+ SVN_ERR(do_list(root, sub_path, patterns, svn_depth_infinity,
+ path_info_only, authz_read_func, authz_read_baton,
+ receiver, receiver_baton, cancel_func,
+ cancel_baton, scratch_buffer, iterpool));
+ }
+
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_repos_list(svn_fs_root_t *root,
+ const char *path,
+ const apr_array_header_t *patterns,
+ svn_depth_t depth,
+ svn_boolean_t path_info_only,
+ svn_repos_authz_func_t authz_read_func,
+ void *authz_read_baton,
+ svn_repos_dirent_receiver_t receiver,
+ void *receiver_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool)
+{
+ svn_membuf_t scratch_buffer;
+
+ /* Parameter check. */
+ svn_node_kind_t kind;
+ if (depth < svn_depth_empty)
+ return svn_error_createf(SVN_ERR_REPOS_BAD_ARGS, NULL,
+ "Invalid depth '%d' in svn_repos_list", depth);
+
+ /* Do we have access this sub-tree? */
+ if (authz_read_func)
+ {
+ svn_boolean_t has_access;
+ SVN_ERR(authz_read_func(&has_access, root, path, authz_read_baton,
+ scratch_pool));
+ if (!has_access)
+ return SVN_NO_ERROR;
+ }
+
+ /* Does the sub-tree even exist?
+ *
+ * Note that we must do this after the authz check to not indirectly
+ * confirm the existence of PATH. */
+ SVN_ERR(svn_fs_check_path(&kind, root, path, scratch_pool));
+ if (kind == svn_node_file)
+ {
+ /* There is no recursion on files. */
+ depth = svn_depth_empty;
+ }
+ else if (kind != svn_node_dir)
+ {
+ return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL,
+ _("Path '%s' not found"), path);
+ }
+
+ /* Special case: Empty pattern list.
+ * We don't want the server to waste time here. */
+ if (patterns && patterns->nelts == 0)
+ return SVN_NO_ERROR;
+
+ /* We need a scratch buffer for temporary string data.
+ * Create one with a reasonable initial size. */
+ svn_membuf__create(&scratch_buffer, 256, scratch_pool);
+
+ /* Actually report PATH, if it passes the filters. */
+ if (matches_any(svn_dirent_dirname(path, scratch_pool), patterns,
+ &scratch_buffer))
+ SVN_ERR(report_dirent(root, path, kind, path_info_only,
+ receiver, receiver_baton, scratch_pool));
+
+ /* Report directory contents if requested. */
+ if (depth > svn_depth_empty)
+ SVN_ERR(do_list(root, path, patterns, depth,
+ path_info_only, authz_read_func, authz_read_baton,
+ receiver, receiver_baton, cancel_func, cancel_baton,
+ &scratch_buffer, scratch_pool));
+
+ return SVN_NO_ERROR;
+}