blob: f620b3f62489dc10a6ee88e94aa9200bdf3ec0ab [file] [log] [blame]
/* 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;
}