2 * stat.c : file and directory stat and read functions
4 * ====================================================================
5 * Licensed to the Apache Software Foundation (ASF) under one
6 * or more contributor license agreements. See the NOTICE file
7 * distributed with this work for additional information
8 * regarding copyright ownership. The ASF licenses this file
9 * to you under the Apache License, Version 2.0 (the
10 * "License"); you may not use this file except in compliance
11 * with the License. You may obtain a copy of the License at
13 * http://www.apache.org/licenses/LICENSE-2.0
15 * Unless required by applicable law or agreed to in writing,
16 * software distributed under the License is distributed on an
17 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18 * KIND, either express or implied. See the License for the
19 * specific language governing permissions and limitations
21 * ====================================================================
26 #define APR_WANT_STRFUNC
31 #include "svn_private_config.h"
32 #include "svn_pools.h"
34 #include "../libsvn_ra/ra_loader.h"
35 #include "svn_config.h"
36 #include "svn_dirent_uri.h"
39 #include "svn_props.h"
41 #include "svn_version.h"
43 #include "private/svn_dav_protocol.h"
44 #include "private/svn_dep_compat.h"
45 #include "private/svn_fspath.h"
51 /* Implements svn_ra__vtable_t.check_path(). */
53 svn_ra_serf__check_path(svn_ra_session_t *ra_session,
55 svn_revnum_t revision,
56 svn_node_kind_t *kind,
57 apr_pool_t *scratch_pool)
59 svn_ra_serf__session_t *session = ra_session->priv;
64 url = session->session_url.path;
66 /* If we have a relative path, append it. */
68 url = svn_path_url_add_component2(url, relpath, scratch_pool);
70 /* If we were given a specific revision, get a URL that refers to that
71 specific revision (rather than floating with HEAD). */
72 if (SVN_IS_VALID_REVNUM(revision))
74 SVN_ERR(svn_ra_serf__get_stable_url(&url, NULL /* latest_revnum */,
77 scratch_pool, scratch_pool));
80 /* URL is stable, so we use SVN_INVALID_REVNUM since it is now irrelevant.
81 Or we started with SVN_INVALID_REVNUM and URL may be floating. */
82 err = svn_ra_serf__fetch_node_props(&props, session,
83 url, SVN_INVALID_REVNUM,
85 scratch_pool, scratch_pool);
87 if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND)
90 *kind = svn_node_none;
94 apr_hash_t *dav_props;
97 /* Any other error, raise to caller. */
100 dav_props = apr_hash_get(props, "DAV:", 4);
101 res_type = svn_prop_get_value(dav_props, "resourcetype");
104 /* How did this happen? */
105 return svn_error_create(SVN_ERR_RA_DAV_PROPS_NOT_FOUND, NULL,
106 _("The PROPFIND response did not include the "
107 "requested resourcetype value"));
110 if (strcmp(res_type, "collection") == 0)
111 *kind = svn_node_dir;
113 *kind = svn_node_file;
120 /* Baton for fill_dirent_propfunc() */
121 struct fill_dirent_baton_t
123 /* Update the fields in this entry. */
126 svn_tristate_t *supports_deadprop_count;
128 /* If allocations are necessary, then use this pool. */
129 apr_pool_t *result_pool;
132 /* Implements svn_ra_serf__prop_func_t */
134 fill_dirent_propfunc(void *baton,
138 const svn_string_t *val,
139 apr_pool_t *scratch_pool)
141 struct fill_dirent_baton_t *fdb = baton;
143 if (strcmp(ns, "DAV:") == 0)
145 if (strcmp(name, SVN_DAV__VERSION_NAME) == 0)
148 SVN_ERR(svn_cstring_atoi64(&rev, val->data));
150 fdb->entry->created_rev = (svn_revnum_t)rev;
152 else if (strcmp(name, "creator-displayname") == 0)
154 fdb->entry->last_author = apr_pstrdup(fdb->result_pool, val->data);
156 else if (strcmp(name, SVN_DAV__CREATIONDATE) == 0)
158 SVN_ERR(svn_time_from_cstring(&fdb->entry->time,
162 else if (strcmp(name, "getcontentlength") == 0)
164 /* 'getcontentlength' property is empty for directories. */
167 SVN_ERR(svn_cstring_atoi64(&fdb->entry->size, val->data));
170 else if (strcmp(name, "resourcetype") == 0)
172 if (strcmp(val->data, "collection") == 0)
174 fdb->entry->kind = svn_node_dir;
178 fdb->entry->kind = svn_node_file;
182 else if (strcmp(ns, SVN_DAV_PROP_NS_CUSTOM) == 0)
184 fdb->entry->has_props = TRUE;
186 else if (strcmp(ns, SVN_DAV_PROP_NS_SVN) == 0)
188 fdb->entry->has_props = TRUE;
190 else if (strcmp(ns, SVN_DAV_PROP_NS_DAV) == 0)
192 if(strcmp(name, "deadprop-count") == 0)
196 /* Note: 1.8.x and earlier servers send the count proper; 1.9.0
197 * and newer send "1" if there are properties and "0" otherwise.
199 apr_int64_t deadprop_count;
200 SVN_ERR(svn_cstring_atoi64(&deadprop_count, val->data));
201 fdb->entry->has_props = deadprop_count > 0;
202 if (fdb->supports_deadprop_count)
203 *fdb->supports_deadprop_count = svn_tristate_true;
205 else if (fdb->supports_deadprop_count)
206 *fdb->supports_deadprop_count = svn_tristate_false;
213 static const svn_ra_serf__dav_props_t *
214 get_dirent_props(apr_uint32_t dirent_fields,
215 svn_ra_serf__session_t *session,
218 svn_ra_serf__dav_props_t *prop;
219 apr_array_header_t *props = svn_ra_serf__get_dirent_props(dirent_fields,
222 prop = apr_array_push(props);
226 return (svn_ra_serf__dav_props_t *) props->elts;
229 /* Implements svn_ra__vtable_t.stat(). */
231 svn_ra_serf__stat(svn_ra_session_t *ra_session,
233 svn_revnum_t revision,
234 svn_dirent_t **dirent,
237 svn_ra_serf__session_t *session = ra_session->priv;
239 struct fill_dirent_baton_t fdb;
240 svn_tristate_t deadprop_count = svn_tristate_unknown;
241 svn_ra_serf__handler_t *handler;
244 url = session->session_url.path;
246 /* If we have a relative path, append it. */
248 url = svn_path_url_add_component2(url, relpath, pool);
250 /* If we were given a specific revision, get a URL that refers to that
251 specific revision (rather than floating with HEAD). */
252 if (SVN_IS_VALID_REVNUM(revision))
254 SVN_ERR(svn_ra_serf__get_stable_url(&url, NULL /* latest_revnum */,
260 fdb.entry = svn_dirent_create(pool);
261 fdb.supports_deadprop_count = &deadprop_count;
262 fdb.result_pool = pool;
264 SVN_ERR(svn_ra_serf__create_propfind_handler(&handler, session, url,
265 SVN_INVALID_REVNUM, "0",
266 get_dirent_props(SVN_DIRENT_ALL,
269 fill_dirent_propfunc, &fdb, pool));
271 err = svn_ra_serf__context_run_one(handler, pool);
275 if (err->apr_err == SVN_ERR_FS_NOT_FOUND)
277 svn_error_clear(err);
282 return svn_error_trace(err);
285 if (deadprop_count == svn_tristate_false
286 && session->supports_deadprop_count == svn_tristate_unknown
287 && !fdb.entry->has_props)
289 /* We have to requery as the server didn't give us the right
291 session->supports_deadprop_count = svn_tristate_false;
293 /* Run the same handler again */
294 SVN_ERR(svn_ra_serf__context_run_one(handler, pool));
297 if (deadprop_count != svn_tristate_unknown)
298 session->supports_deadprop_count = deadprop_count;
305 /* Baton for get_dir_dirents_cb and get_dir_props_cb */
306 struct get_dir_baton_t
308 apr_pool_t *result_pool;
310 apr_hash_t *ret_props;
311 svn_boolean_t is_directory;
312 svn_tristate_t supports_deadprop_count;
316 /* Implements svn_ra_serf__prop_func_t */
318 get_dir_dirents_cb(void *baton,
322 const svn_string_t *value,
323 apr_pool_t *scratch_pool)
325 struct get_dir_baton_t *db = baton;
328 relpath = svn_fspath__skip_ancestor(db->path, path);
330 if (relpath && relpath[0] != '\0')
332 struct fill_dirent_baton_t fdb;
334 relpath = svn_path_uri_decode(relpath, scratch_pool);
335 fdb.entry = svn_hash_gets(db->dirents, relpath);
339 fdb.entry = svn_dirent_create(db->result_pool);
340 svn_hash_sets(db->dirents,
341 apr_pstrdup(db->result_pool, relpath),
345 fdb.result_pool = db->result_pool;
346 fdb.supports_deadprop_count = &db->supports_deadprop_count;
347 SVN_ERR(fill_dirent_propfunc(&fdb, path, ns, name, value, scratch_pool));
349 else if (relpath && !db->is_directory)
351 if (strcmp(ns, "DAV:") == 0 && strcmp(name, "resourcetype") == 0)
353 if (strcmp(value->data, "collection") != 0)
355 /* Tell a lie to exit early */
356 return svn_error_create(SVN_ERR_FS_NOT_DIRECTORY, NULL,
357 _("Can't get properties of non-directory"));
360 db->is_directory = TRUE;
367 /* Implements svn_ra_serf__prop_func */
369 get_dir_props_cb(void *baton,
373 const svn_string_t *value,
374 apr_pool_t *scratch_pool)
376 struct get_dir_baton_t *db = baton;
377 const char *propname;
379 propname = svn_ra_serf__svnname_from_wirename(ns, name, db->result_pool);
382 svn_hash_sets(db->ret_props, propname,
383 svn_string_dup(value, db->result_pool));
387 if (!db->is_directory)
389 if (strcmp(ns, "DAV:") == 0 && strcmp(name, "resourcetype") == 0)
391 if (strcmp(value->data, "collection") != 0)
393 /* Tell a lie to exit early */
394 return svn_error_create(SVN_ERR_FS_NOT_DIRECTORY, NULL,
395 _("Can't get properties of non-directory"));
398 db->is_directory = TRUE;
405 /* Implements svn_ra__vtable_t.get_dir(). */
407 svn_ra_serf__get_dir(svn_ra_session_t *ra_session,
408 apr_hash_t **dirents,
409 svn_revnum_t *fetched_rev,
410 apr_hash_t **ret_props,
411 const char *rel_path,
412 svn_revnum_t revision,
413 apr_uint32_t dirent_fields,
414 apr_pool_t *result_pool)
416 svn_ra_serf__session_t *session = ra_session->priv;
417 apr_pool_t *scratch_pool = svn_pool_create(result_pool);
418 svn_ra_serf__handler_t *dirent_handler = NULL;
419 svn_ra_serf__handler_t *props_handler = NULL;
421 struct get_dir_baton_t gdb;
422 svn_error_t *err = SVN_NO_ERROR;
424 gdb.result_pool = result_pool;
425 gdb.is_directory = FALSE;
426 gdb.supports_deadprop_count = svn_tristate_unknown;
428 path = session->session_url.path;
430 /* If we have a relative path, URI encode and append it. */
433 path = svn_path_url_add_component2(path, rel_path, scratch_pool);
436 /* If the user specified a peg revision other than HEAD, we have to fetch
437 the baseline collection url for that revision. If not, we can use the
439 if (SVN_IS_VALID_REVNUM(revision) || fetched_rev)
441 SVN_ERR(svn_ra_serf__get_stable_url(&path, fetched_rev,
444 scratch_pool, scratch_pool));
445 revision = SVN_INVALID_REVNUM;
447 /* REVISION is always SVN_INVALID_REVNUM */
448 SVN_ERR_ASSERT(!SVN_IS_VALID_REVNUM(revision));
452 /* If we're asked for children, fetch them now. */
455 /* Always request node kind to check that path is really a
458 dirent_fields |= SVN_DIRENT_KIND;
460 gdb.dirents = apr_hash_make(result_pool);
462 SVN_ERR(svn_ra_serf__create_propfind_handler(
463 &dirent_handler, session,
464 path, SVN_INVALID_REVNUM, "1",
465 get_dirent_props(dirent_fields,
468 get_dir_dirents_cb, &gdb,
471 svn_ra_serf__request_create(dirent_handler);
478 gdb.ret_props = apr_hash_make(result_pool);
479 SVN_ERR(svn_ra_serf__create_propfind_handler(
480 &props_handler, session,
481 path, SVN_INVALID_REVNUM, "0",
483 get_dir_props_cb, &gdb,
486 svn_ra_serf__request_create(props_handler);
489 gdb.ret_props = NULL;
493 err = svn_error_trace(
494 svn_ra_serf__context_run_wait(&dirent_handler->done,
500 svn_pool_clear(scratch_pool); /* Unregisters outstanding requests */
504 if (gdb.supports_deadprop_count == svn_tristate_false
505 && session->supports_deadprop_count == svn_tristate_unknown
506 && dirent_fields & SVN_DIRENT_HAS_PROPS)
508 /* We have to requery as the server didn't give us the right
510 session->supports_deadprop_count = svn_tristate_false;
512 apr_hash_clear(gdb.dirents);
514 SVN_ERR(svn_ra_serf__create_propfind_handler(
515 &dirent_handler, session,
516 path, SVN_INVALID_REVNUM, "1",
517 get_dirent_props(dirent_fields,
520 get_dir_dirents_cb, &gdb,
523 svn_ra_serf__request_create(dirent_handler);
529 err = svn_error_trace(
530 svn_ra_serf__context_run_wait(&props_handler->done,
535 /* And dirent again for the case when we had to send the request again */
536 if (! err && dirent_handler)
538 err = svn_error_trace(
539 svn_ra_serf__context_run_wait(&dirent_handler->done,
544 if (!err && gdb.supports_deadprop_count != svn_tristate_unknown)
545 session->supports_deadprop_count = gdb.supports_deadprop_count;
547 svn_pool_destroy(scratch_pool); /* Unregisters outstanding requests */
551 if (!gdb.is_directory)
552 return svn_error_create(SVN_ERR_FS_NOT_DIRECTORY, NULL,
553 _("Can't get entries of non-directory"));
556 *ret_props = gdb.ret_props;
559 *dirents = gdb.dirents;