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