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 apr_int64_t deadprop_count;
197 SVN_ERR(svn_cstring_atoi64(&deadprop_count, val->data));
198 fdb->entry->has_props = deadprop_count > 0;
199 if (fdb->supports_deadprop_count)
200 *fdb->supports_deadprop_count = svn_tristate_true;
202 else if (fdb->supports_deadprop_count)
203 *fdb->supports_deadprop_count = svn_tristate_false;
210 static const svn_ra_serf__dav_props_t *
211 get_dirent_props(apr_uint32_t dirent_fields,
212 svn_ra_serf__session_t *session,
215 svn_ra_serf__dav_props_t *prop;
216 apr_array_header_t *props = apr_array_make
217 (pool, 7, sizeof(svn_ra_serf__dav_props_t));
219 if (session->supports_deadprop_count != svn_tristate_false
220 || ! (dirent_fields & SVN_DIRENT_HAS_PROPS))
222 if (dirent_fields & SVN_DIRENT_KIND)
224 prop = apr_array_push(props);
225 prop->xmlns = "DAV:";
226 prop->name = "resourcetype";
229 if (dirent_fields & SVN_DIRENT_SIZE)
231 prop = apr_array_push(props);
232 prop->xmlns = "DAV:";
233 prop->name = "getcontentlength";
236 if (dirent_fields & SVN_DIRENT_HAS_PROPS)
238 prop = apr_array_push(props);
239 prop->xmlns = SVN_DAV_PROP_NS_DAV;
240 prop->name = "deadprop-count";
243 if (dirent_fields & SVN_DIRENT_CREATED_REV)
245 svn_ra_serf__dav_props_t *p = apr_array_push(props);
247 p->name = SVN_DAV__VERSION_NAME;
250 if (dirent_fields & SVN_DIRENT_TIME)
252 prop = apr_array_push(props);
253 prop->xmlns = "DAV:";
254 prop->name = SVN_DAV__CREATIONDATE;
257 if (dirent_fields & SVN_DIRENT_LAST_AUTHOR)
259 prop = apr_array_push(props);
260 prop->xmlns = "DAV:";
261 prop->name = "creator-displayname";
266 /* We found an old subversion server that can't handle
267 the deadprop-count property in the way we expect.
269 The neon behavior is to retrieve all properties in this case */
270 prop = apr_array_push(props);
271 prop->xmlns = "DAV:";
272 prop->name = "allprop";
275 prop = apr_array_push(props);
279 return (svn_ra_serf__dav_props_t *) props->elts;
282 /* Implements svn_ra__vtable_t.stat(). */
284 svn_ra_serf__stat(svn_ra_session_t *ra_session,
286 svn_revnum_t revision,
287 svn_dirent_t **dirent,
290 svn_ra_serf__session_t *session = ra_session->priv;
292 struct fill_dirent_baton_t fdb;
293 svn_tristate_t deadprop_count = svn_tristate_unknown;
294 svn_ra_serf__handler_t *handler;
297 url = session->session_url.path;
299 /* If we have a relative path, append it. */
301 url = svn_path_url_add_component2(url, relpath, pool);
303 /* If we were given a specific revision, get a URL that refers to that
304 specific revision (rather than floating with HEAD). */
305 if (SVN_IS_VALID_REVNUM(revision))
307 SVN_ERR(svn_ra_serf__get_stable_url(&url, NULL /* latest_revnum */,
313 fdb.entry = svn_dirent_create(pool);
314 fdb.supports_deadprop_count = &deadprop_count;
315 fdb.result_pool = pool;
317 SVN_ERR(svn_ra_serf__create_propfind_handler(&handler, session, url,
318 SVN_INVALID_REVNUM, "0",
319 get_dirent_props(SVN_DIRENT_ALL,
322 fill_dirent_propfunc, &fdb, pool));
324 err = svn_ra_serf__context_run_one(handler, pool);
328 if (err->apr_err == SVN_ERR_FS_NOT_FOUND)
330 svn_error_clear(err);
335 return svn_error_trace(err);
338 if (deadprop_count == svn_tristate_false
339 && session->supports_deadprop_count == svn_tristate_unknown
340 && !fdb.entry->has_props)
342 /* We have to requery as the server didn't give us the right
344 session->supports_deadprop_count = svn_tristate_false;
346 /* Run the same handler again */
347 SVN_ERR(svn_ra_serf__context_run_one(handler, pool));
350 if (deadprop_count != svn_tristate_unknown)
351 session->supports_deadprop_count = deadprop_count;
358 /* Baton for get_dir_dirents_cb and get_dir_props_cb */
359 struct get_dir_baton_t
361 apr_pool_t *result_pool;
363 apr_hash_t *ret_props;
364 svn_boolean_t is_directory;
365 svn_tristate_t supports_deadprop_count;
369 /* Implements svn_ra_serf__prop_func_t */
371 get_dir_dirents_cb(void *baton,
375 const svn_string_t *value,
376 apr_pool_t *scratch_pool)
378 struct get_dir_baton_t *db = baton;
381 relpath = svn_fspath__skip_ancestor(db->path, path);
383 if (relpath && relpath[0] != '\0')
385 struct fill_dirent_baton_t fdb;
387 relpath = svn_path_uri_decode(relpath, scratch_pool);
388 fdb.entry = svn_hash_gets(db->dirents, relpath);
392 fdb.entry = svn_dirent_create(db->result_pool);
393 svn_hash_sets(db->dirents,
394 apr_pstrdup(db->result_pool, relpath),
398 fdb.result_pool = db->result_pool;
399 fdb.supports_deadprop_count = &db->supports_deadprop_count;
400 SVN_ERR(fill_dirent_propfunc(&fdb, path, ns, name, value, scratch_pool));
402 else if (relpath && !db->is_directory)
404 if (strcmp(ns, "DAV:") == 0 && strcmp(name, "resourcetype") == 0)
406 if (strcmp(value->data, "collection") != 0)
408 /* Tell a lie to exit early */
409 return svn_error_create(SVN_ERR_FS_NOT_DIRECTORY, NULL,
410 _("Can't get properties of non-directory"));
413 db->is_directory = TRUE;
420 /* Implements svn_ra_serf__prop_func */
422 get_dir_props_cb(void *baton,
426 const svn_string_t *value,
427 apr_pool_t *scratch_pool)
429 struct get_dir_baton_t *db = baton;
430 const char *propname;
432 propname = svn_ra_serf__svnname_from_wirename(ns, name, db->result_pool);
435 svn_hash_sets(db->ret_props, propname,
436 svn_string_dup(value, db->result_pool));
440 if (!db->is_directory)
442 if (strcmp(ns, "DAV:") == 0 && strcmp(name, "resourcetype") == 0)
444 if (strcmp(value->data, "collection") != 0)
446 /* Tell a lie to exit early */
447 return svn_error_create(SVN_ERR_FS_NOT_DIRECTORY, NULL,
448 _("Can't get properties of non-directory"));
451 db->is_directory = TRUE;
458 /* Implements svn_ra__vtable_t.get_dir(). */
460 svn_ra_serf__get_dir(svn_ra_session_t *ra_session,
461 apr_hash_t **dirents,
462 svn_revnum_t *fetched_rev,
463 apr_hash_t **ret_props,
464 const char *rel_path,
465 svn_revnum_t revision,
466 apr_uint32_t dirent_fields,
467 apr_pool_t *result_pool)
469 svn_ra_serf__session_t *session = ra_session->priv;
470 apr_pool_t *scratch_pool = svn_pool_create(result_pool);
471 svn_ra_serf__handler_t *dirent_handler = NULL;
472 svn_ra_serf__handler_t *props_handler = NULL;
474 struct get_dir_baton_t gdb;
475 svn_error_t *err = SVN_NO_ERROR;
477 gdb.result_pool = result_pool;
478 gdb.is_directory = FALSE;
479 gdb.supports_deadprop_count = svn_tristate_unknown;
481 path = session->session_url.path;
483 /* If we have a relative path, URI encode and append it. */
486 path = svn_path_url_add_component2(path, rel_path, scratch_pool);
489 /* If the user specified a peg revision other than HEAD, we have to fetch
490 the baseline collection url for that revision. If not, we can use the
492 if (SVN_IS_VALID_REVNUM(revision) || fetched_rev)
494 SVN_ERR(svn_ra_serf__get_stable_url(&path, fetched_rev,
497 scratch_pool, scratch_pool));
498 revision = SVN_INVALID_REVNUM;
500 /* REVISION is always SVN_INVALID_REVNUM */
501 SVN_ERR_ASSERT(!SVN_IS_VALID_REVNUM(revision));
505 /* If we're asked for children, fetch them now. */
508 /* Always request node kind to check that path is really a
511 dirent_fields |= SVN_DIRENT_KIND;
513 gdb.dirents = apr_hash_make(result_pool);
515 SVN_ERR(svn_ra_serf__create_propfind_handler(
516 &dirent_handler, session,
517 path, SVN_INVALID_REVNUM, "1",
518 get_dirent_props(dirent_fields,
521 get_dir_dirents_cb, &gdb,
524 svn_ra_serf__request_create(dirent_handler);
531 gdb.ret_props = apr_hash_make(result_pool);
532 SVN_ERR(svn_ra_serf__create_propfind_handler(
533 &props_handler, session,
534 path, SVN_INVALID_REVNUM, "0",
536 get_dir_props_cb, &gdb,
539 svn_ra_serf__request_create(props_handler);
542 gdb.ret_props = NULL;
546 err = svn_error_trace(
547 svn_ra_serf__context_run_wait(&dirent_handler->done,
553 svn_pool_clear(scratch_pool); /* Unregisters outstanding requests */
557 if (gdb.supports_deadprop_count == svn_tristate_false
558 && session->supports_deadprop_count == svn_tristate_unknown
559 && dirent_fields & SVN_DIRENT_HAS_PROPS)
561 /* We have to requery as the server didn't give us the right
563 session->supports_deadprop_count = svn_tristate_false;
565 apr_hash_clear(gdb.dirents);
567 SVN_ERR(svn_ra_serf__create_propfind_handler(
568 &dirent_handler, session,
569 path, SVN_INVALID_REVNUM, "1",
570 get_dirent_props(dirent_fields,
573 get_dir_dirents_cb, &gdb,
576 svn_ra_serf__request_create(dirent_handler);
582 err = svn_error_trace(
583 svn_ra_serf__context_run_wait(&props_handler->done,
588 /* And dirent again for the case when we had to send the request again */
589 if (! err && dirent_handler)
591 err = svn_error_trace(
592 svn_ra_serf__context_run_wait(&dirent_handler->done,
597 if (!err && gdb.supports_deadprop_count != svn_tristate_unknown)
598 session->supports_deadprop_count = gdb.supports_deadprop_count;
600 svn_pool_destroy(scratch_pool); /* Unregisters outstanding requests */
604 if (!gdb.is_directory)
605 return svn_error_create(SVN_ERR_FS_NOT_DIRECTORY, NULL,
606 _("Can't get entries of non-directory"));
609 *ret_props = gdb.ret_props;
612 *dirents = gdb.dirents;