1 /* list.c : listing repository contents
3 * ====================================================================
4 * Licensed to the Apache Software Foundation (ASF) under one
5 * or more contributor license agreements. See the NOTICE file
6 * distributed with this work for additional information
7 * regarding copyright ownership. The ASF licenses this file
8 * to you under the Apache License, Version 2.0 (the
9 * "License"); you may not use this file except in compliance
10 * with the License. You may obtain a copy of the License at
12 * http://www.apache.org/licenses/LICENSE-2.0
14 * Unless required by applicable law or agreed to in writing,
15 * software distributed under the License is distributed on an
16 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17 * KIND, either express or implied. See the License for the
18 * specific language governing permissions and limitations
20 * ====================================================================
23 #include <apr_pools.h>
24 #include <apr_fnmatch.h>
26 #include "svn_pools.h"
27 #include "svn_error.h"
28 #include "svn_dirent_uri.h"
31 #include "private/svn_repos_private.h"
32 #include "private/svn_sorts_private.h"
33 #include "private/svn_utf_private.h"
34 #include "svn_private_config.h" /* for SVN_TEMPLATE_ROOT_DIR */
40 /* Utility function. Given DIRENT->KIND, set all other elements of *DIRENT
41 * with the values retrieved for PATH under ROOT. Allocate them in POOL.
44 fill_dirent(svn_dirent_t *dirent,
47 apr_pool_t *scratch_pool)
49 const char *datestring;
51 if (dirent->kind == svn_node_file)
52 SVN_ERR(svn_fs_file_length(&(dirent->size), root, path, scratch_pool));
54 dirent->size = SVN_INVALID_FILESIZE;
56 SVN_ERR(svn_fs_node_has_props(&dirent->has_props, root, path,
59 SVN_ERR(svn_repos_get_committed_info(&(dirent->created_rev),
61 &(dirent->last_author),
62 root, path, scratch_pool));
64 SVN_ERR(svn_time_from_cstring(&(dirent->time), datestring,
71 svn_repos_stat(svn_dirent_t **dirent,
79 SVN_ERR(svn_fs_check_path(&kind, root, path, pool));
81 if (kind == svn_node_none)
87 ent = svn_dirent_create(pool);
90 SVN_ERR(fill_dirent(ent, root, path, pool));
96 /* Return TRUE of DIRNAME matches any of the const char * in PATTERNS.
97 * Note that any DIRNAME will match if PATTERNS is empty.
98 * Use SCRATCH_BUFFER for temporary string contents. */
100 matches_any(const char *dirname,
101 const apr_array_header_t *patterns,
102 svn_membuf_t *scratch_buffer)
105 ? svn_utf__fuzzy_glob_match(dirname, patterns, scratch_buffer)
109 /* Utility to prevent code duplication.
111 * Construct a svn_dirent_t for PATH of type KIND under ROOT and, if
112 * PATH_INFO_ONLY is not set, fill it. Call RECEIVER with the result
113 * and RECEIVER_BATON.
115 * Use SCRATCH_POOL for temporary allocations.
118 report_dirent(svn_fs_root_t *root,
120 svn_node_kind_t kind,
121 svn_boolean_t path_info_only,
122 svn_repos_dirent_receiver_t receiver,
123 void *receiver_baton,
124 apr_pool_t *scratch_pool)
126 svn_dirent_t dirent = { 0 };
128 /* Fetch the details to report - if required. */
131 SVN_ERR(fill_dirent(&dirent, root, path, scratch_pool));
133 /* Report the entry. */
134 SVN_ERR(receiver(path, &dirent, receiver_baton, scratch_pool));
139 /* Utility data struct, used to attach a filter result flag to a dirent. */
140 typedef struct filtered_dirent_t
142 /* Actual dirent, never NULL. */
143 svn_fs_dirent_t *dirent;
145 /* DIRENT passed the filter. */
146 svn_boolean_t is_match;
149 /* Implement a standard sort function for filtered_dirent_t *, sorting them
152 compare_filtered_dirent(const void *lhs,
155 const filtered_dirent_t *lhs_dirent = (const filtered_dirent_t *)lhs;
156 const filtered_dirent_t *rhs_dirent = (const filtered_dirent_t *)rhs;
158 return strcmp(lhs_dirent->dirent->name, rhs_dirent->dirent->name);
161 /* Core of svn_repos_list with the same parameter list.
163 * However, DEPTH is not svn_depth_empty and PATH has already been reported.
164 * Therefore, we can call this recursively.
166 * Uses SCRATCH_BUFFER for temporary string contents.
169 do_list(svn_fs_root_t *root,
171 const apr_array_header_t *patterns,
173 svn_boolean_t path_info_only,
174 svn_repos_authz_func_t authz_read_func,
175 void *authz_read_baton,
176 svn_repos_dirent_receiver_t receiver,
177 void *receiver_baton,
178 svn_cancel_func_t cancel_func,
180 svn_membuf_t *scratch_buffer,
181 apr_pool_t *scratch_pool)
184 apr_pool_t *iterpool = svn_pool_create(scratch_pool);
185 apr_hash_index_t *hi;
186 apr_array_header_t *sorted;
189 /* Fetch all directory entries, filter and sort them.
191 * Performance trade-off:
192 * Constructing a full path vs. faster sort due to authz filtering.
193 * We filter according to DEPTH and PATTERNS only because constructing
194 * the full path required for authz is somewhat expensive and we don't
195 * want to do this twice while authz will rarely filter paths out.
197 SVN_ERR(svn_fs_dir_entries(&entries, root, path, scratch_pool));
198 sorted = apr_array_make(scratch_pool, apr_hash_count(entries),
199 sizeof(filtered_dirent_t));
200 for (hi = apr_hash_first(scratch_pool, entries); hi; hi = apr_hash_next(hi))
202 filtered_dirent_t filtered;
203 svn_pool_clear(iterpool);
205 filtered.dirent = apr_hash_this_val(hi);
207 /* Skip directories if we want to report files only. */
208 if (filtered.dirent->kind == svn_node_dir && depth == svn_depth_files)
211 /* We can skip files that don't match any of the search patterns. */
212 filtered.is_match = matches_any(filtered.dirent->name, patterns,
214 if (!filtered.is_match && filtered.dirent->kind == svn_node_file)
217 APR_ARRAY_PUSH(sorted, filtered_dirent_t) = filtered;
220 svn_sort__array(sorted, compare_filtered_dirent);
222 /* Iterate over all remaining directory entries and report them.
223 * Recurse into sub-directories if requested. */
224 for (i = 0; i < sorted->nelts; ++i)
226 const char *sub_path;
227 filtered_dirent_t *filtered;
228 svn_fs_dirent_t *dirent;
230 svn_pool_clear(iterpool);
232 filtered = &APR_ARRAY_IDX(sorted, i, filtered_dirent_t);
233 dirent = filtered->dirent;
235 /* Skip paths that we don't have access to? */
236 sub_path = svn_dirent_join(path, dirent->name, iterpool);
239 svn_boolean_t has_access;
240 SVN_ERR(authz_read_func(&has_access, root, sub_path,
241 authz_read_baton, iterpool));
246 /* Report entry, if it passed the filter. */
247 if (filtered->is_match)
248 SVN_ERR(report_dirent(root, sub_path, dirent->kind, path_info_only,
249 receiver, receiver_baton, iterpool));
251 /* Check for cancellation before recursing down. This should be
252 * slightly more responsive for deep trees. */
254 SVN_ERR(cancel_func(cancel_baton));
256 /* Recurse on directories. */
257 if (depth == svn_depth_infinity && dirent->kind == svn_node_dir)
258 SVN_ERR(do_list(root, sub_path, patterns, svn_depth_infinity,
259 path_info_only, authz_read_func, authz_read_baton,
260 receiver, receiver_baton, cancel_func,
261 cancel_baton, scratch_buffer, iterpool));
264 svn_pool_destroy(iterpool);
270 svn_repos_list(svn_fs_root_t *root,
272 const apr_array_header_t *patterns,
274 svn_boolean_t path_info_only,
275 svn_repos_authz_func_t authz_read_func,
276 void *authz_read_baton,
277 svn_repos_dirent_receiver_t receiver,
278 void *receiver_baton,
279 svn_cancel_func_t cancel_func,
281 apr_pool_t *scratch_pool)
283 svn_membuf_t scratch_buffer;
285 /* Parameter check. */
286 svn_node_kind_t kind;
287 if (depth < svn_depth_empty)
288 return svn_error_createf(SVN_ERR_REPOS_BAD_ARGS, NULL,
289 "Invalid depth '%d' in svn_repos_list", depth);
291 /* Do we have access this sub-tree? */
294 svn_boolean_t has_access;
295 SVN_ERR(authz_read_func(&has_access, root, path, authz_read_baton,
301 /* Does the sub-tree even exist?
303 * Note that we must do this after the authz check to not indirectly
304 * confirm the existence of PATH. */
305 SVN_ERR(svn_fs_check_path(&kind, root, path, scratch_pool));
306 if (kind == svn_node_file)
308 /* There is no recursion on files. */
309 depth = svn_depth_empty;
311 else if (kind != svn_node_dir)
313 return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL,
314 _("Path '%s' not found"), path);
317 /* Special case: Empty pattern list.
318 * We don't want the server to waste time here. */
319 if (patterns && patterns->nelts == 0)
322 /* We need a scratch buffer for temporary string data.
323 * Create one with a reasonable initial size. */
324 svn_membuf__create(&scratch_buffer, 256, scratch_pool);
326 /* Actually report PATH, if it passes the filters. */
327 if (matches_any(svn_dirent_dirname(path, scratch_pool), patterns,
329 SVN_ERR(report_dirent(root, path, kind, path_info_only,
330 receiver, receiver_baton, scratch_pool));
332 /* Report directory contents if requested. */
333 if (depth > svn_depth_empty)
334 SVN_ERR(do_list(root, path, patterns, depth,
335 path_info_only, authz_read_func, authz_read_baton,
336 receiver, receiver_baton, cancel_func, cancel_baton,
337 &scratch_buffer, scratch_pool));