/* * stat.c : file and directory stat and read functions * * ==================================================================== * 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. * ==================================================================== */ #define APR_WANT_STRFUNC #include #include #include "svn_private_config.h" #include "svn_pools.h" #include "svn_xml.h" #include "../libsvn_ra/ra_loader.h" #include "svn_config.h" #include "svn_dirent_uri.h" #include "svn_hash.h" #include "svn_path.h" #include "svn_props.h" #include "svn_time.h" #include "svn_version.h" #include "private/svn_dav_protocol.h" #include "private/svn_dep_compat.h" #include "private/svn_fspath.h" #include "ra_serf.h" /* Implements svn_ra__vtable_t.check_path(). */ svn_error_t * svn_ra_serf__check_path(svn_ra_session_t *ra_session, const char *relpath, svn_revnum_t revision, svn_node_kind_t *kind, apr_pool_t *scratch_pool) { svn_ra_serf__session_t *session = ra_session->priv; apr_hash_t *props; svn_error_t *err; const char *url; url = session->session_url.path; /* If we have a relative path, append it. */ if (relpath) url = svn_path_url_add_component2(url, relpath, scratch_pool); /* If we were given a specific revision, get a URL that refers to that specific revision (rather than floating with HEAD). */ if (SVN_IS_VALID_REVNUM(revision)) { SVN_ERR(svn_ra_serf__get_stable_url(&url, NULL /* latest_revnum */, session, url, revision, scratch_pool, scratch_pool)); } /* URL is stable, so we use SVN_INVALID_REVNUM since it is now irrelevant. Or we started with SVN_INVALID_REVNUM and URL may be floating. */ err = svn_ra_serf__fetch_node_props(&props, session, url, SVN_INVALID_REVNUM, check_path_props, scratch_pool, scratch_pool); if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND) { svn_error_clear(err); *kind = svn_node_none; } else { apr_hash_t *dav_props; const char *res_type; /* Any other error, raise to caller. */ SVN_ERR(err); dav_props = apr_hash_get(props, "DAV:", 4); res_type = svn_prop_get_value(dav_props, "resourcetype"); if (!res_type) { /* How did this happen? */ return svn_error_create(SVN_ERR_RA_DAV_PROPS_NOT_FOUND, NULL, _("The PROPFIND response did not include the " "requested resourcetype value")); } if (strcmp(res_type, "collection") == 0) *kind = svn_node_dir; else *kind = svn_node_file; } return SVN_NO_ERROR; } /* Baton for fill_dirent_propfunc() */ struct fill_dirent_baton_t { /* Update the fields in this entry. */ svn_dirent_t *entry; svn_tristate_t *supports_deadprop_count; /* If allocations are necessary, then use this pool. */ apr_pool_t *result_pool; }; /* Implements svn_ra_serf__prop_func_t */ static svn_error_t * fill_dirent_propfunc(void *baton, const char *path, const char *ns, const char *name, const svn_string_t *val, apr_pool_t *scratch_pool) { struct fill_dirent_baton_t *fdb = baton; if (strcmp(ns, "DAV:") == 0) { if (strcmp(name, SVN_DAV__VERSION_NAME) == 0) { apr_int64_t rev; SVN_ERR(svn_cstring_atoi64(&rev, val->data)); fdb->entry->created_rev = (svn_revnum_t)rev; } else if (strcmp(name, "creator-displayname") == 0) { fdb->entry->last_author = apr_pstrdup(fdb->result_pool, val->data); } else if (strcmp(name, SVN_DAV__CREATIONDATE) == 0) { SVN_ERR(svn_time_from_cstring(&fdb->entry->time, val->data, fdb->result_pool)); } else if (strcmp(name, "getcontentlength") == 0) { /* 'getcontentlength' property is empty for directories. */ if (val->len) { SVN_ERR(svn_cstring_atoi64(&fdb->entry->size, val->data)); } } else if (strcmp(name, "resourcetype") == 0) { if (strcmp(val->data, "collection") == 0) { fdb->entry->kind = svn_node_dir; } else { fdb->entry->kind = svn_node_file; } } } else if (strcmp(ns, SVN_DAV_PROP_NS_CUSTOM) == 0) { fdb->entry->has_props = TRUE; } else if (strcmp(ns, SVN_DAV_PROP_NS_SVN) == 0) { fdb->entry->has_props = TRUE; } else if (strcmp(ns, SVN_DAV_PROP_NS_DAV) == 0) { if(strcmp(name, "deadprop-count") == 0) { if (*val->data) { /* Note: 1.8.x and earlier servers send the count proper; 1.9.0 * and newer send "1" if there are properties and "0" otherwise. */ apr_int64_t deadprop_count; SVN_ERR(svn_cstring_atoi64(&deadprop_count, val->data)); fdb->entry->has_props = deadprop_count > 0; if (fdb->supports_deadprop_count) *fdb->supports_deadprop_count = svn_tristate_true; } else if (fdb->supports_deadprop_count) *fdb->supports_deadprop_count = svn_tristate_false; } } return SVN_NO_ERROR; } static const svn_ra_serf__dav_props_t * get_dirent_props(apr_uint32_t dirent_fields, svn_ra_serf__session_t *session, apr_pool_t *pool) { svn_ra_serf__dav_props_t *prop; apr_array_header_t *props = svn_ra_serf__get_dirent_props(dirent_fields, session, pool); prop = apr_array_push(props); prop->xmlns = NULL; prop->name = NULL; return (svn_ra_serf__dav_props_t *) props->elts; } /* Implements svn_ra__vtable_t.stat(). */ svn_error_t * svn_ra_serf__stat(svn_ra_session_t *ra_session, const char *relpath, svn_revnum_t revision, svn_dirent_t **dirent, apr_pool_t *pool) { svn_ra_serf__session_t *session = ra_session->priv; svn_error_t *err; struct fill_dirent_baton_t fdb; svn_tristate_t deadprop_count = svn_tristate_unknown; svn_ra_serf__handler_t *handler; const char *url; url = session->session_url.path; /* If we have a relative path, append it. */ if (relpath) url = svn_path_url_add_component2(url, relpath, pool); /* If we were given a specific revision, get a URL that refers to that specific revision (rather than floating with HEAD). */ if (SVN_IS_VALID_REVNUM(revision)) { SVN_ERR(svn_ra_serf__get_stable_url(&url, NULL /* latest_revnum */, session, url, revision, pool, pool)); } fdb.entry = svn_dirent_create(pool); fdb.supports_deadprop_count = &deadprop_count; fdb.result_pool = pool; SVN_ERR(svn_ra_serf__create_propfind_handler(&handler, session, url, SVN_INVALID_REVNUM, "0", get_dirent_props(SVN_DIRENT_ALL, session, pool), fill_dirent_propfunc, &fdb, pool)); err = svn_ra_serf__context_run_one(handler, pool); if (err) { if (err->apr_err == SVN_ERR_FS_NOT_FOUND) { svn_error_clear(err); *dirent = NULL; return SVN_NO_ERROR; } else return svn_error_trace(err); } if (deadprop_count == svn_tristate_false && session->supports_deadprop_count == svn_tristate_unknown && !fdb.entry->has_props) { /* We have to requery as the server didn't give us the right information */ session->supports_deadprop_count = svn_tristate_false; /* Run the same handler again */ SVN_ERR(svn_ra_serf__context_run_one(handler, pool)); } if (deadprop_count != svn_tristate_unknown) session->supports_deadprop_count = deadprop_count; *dirent = fdb.entry; return SVN_NO_ERROR; } /* Baton for get_dir_dirents_cb and get_dir_props_cb */ struct get_dir_baton_t { apr_pool_t *result_pool; apr_hash_t *dirents; apr_hash_t *ret_props; svn_boolean_t is_directory; svn_tristate_t supports_deadprop_count; const char *path; }; /* Implements svn_ra_serf__prop_func_t */ static svn_error_t * get_dir_dirents_cb(void *baton, const char *path, const char *ns, const char *name, const svn_string_t *value, apr_pool_t *scratch_pool) { struct get_dir_baton_t *db = baton; const char *relpath; relpath = svn_fspath__skip_ancestor(db->path, path); if (relpath && relpath[0] != '\0') { struct fill_dirent_baton_t fdb; relpath = svn_path_uri_decode(relpath, scratch_pool); fdb.entry = svn_hash_gets(db->dirents, relpath); if (!fdb.entry) { fdb.entry = svn_dirent_create(db->result_pool); svn_hash_sets(db->dirents, apr_pstrdup(db->result_pool, relpath), fdb.entry); } fdb.result_pool = db->result_pool; fdb.supports_deadprop_count = &db->supports_deadprop_count; SVN_ERR(fill_dirent_propfunc(&fdb, path, ns, name, value, scratch_pool)); } else if (relpath && !db->is_directory) { if (strcmp(ns, "DAV:") == 0 && strcmp(name, "resourcetype") == 0) { if (strcmp(value->data, "collection") != 0) { /* Tell a lie to exit early */ return svn_error_create(SVN_ERR_FS_NOT_DIRECTORY, NULL, _("Can't get properties of non-directory")); } else db->is_directory = TRUE; } } return SVN_NO_ERROR; } /* Implements svn_ra_serf__prop_func */ static svn_error_t * get_dir_props_cb(void *baton, const char *path, const char *ns, const char *name, const svn_string_t *value, apr_pool_t *scratch_pool) { struct get_dir_baton_t *db = baton; const char *propname; propname = svn_ra_serf__svnname_from_wirename(ns, name, db->result_pool); if (propname) { svn_hash_sets(db->ret_props, propname, svn_string_dup(value, db->result_pool)); return SVN_NO_ERROR; } if (!db->is_directory) { if (strcmp(ns, "DAV:") == 0 && strcmp(name, "resourcetype") == 0) { if (strcmp(value->data, "collection") != 0) { /* Tell a lie to exit early */ return svn_error_create(SVN_ERR_FS_NOT_DIRECTORY, NULL, _("Can't get properties of non-directory")); } else db->is_directory = TRUE; } } return SVN_NO_ERROR; } /* Implements svn_ra__vtable_t.get_dir(). */ svn_error_t * svn_ra_serf__get_dir(svn_ra_session_t *ra_session, apr_hash_t **dirents, svn_revnum_t *fetched_rev, apr_hash_t **ret_props, const char *rel_path, svn_revnum_t revision, apr_uint32_t dirent_fields, apr_pool_t *result_pool) { svn_ra_serf__session_t *session = ra_session->priv; apr_pool_t *scratch_pool = svn_pool_create(result_pool); svn_ra_serf__handler_t *dirent_handler = NULL; svn_ra_serf__handler_t *props_handler = NULL; const char *path; struct get_dir_baton_t gdb; svn_error_t *err = SVN_NO_ERROR; gdb.result_pool = result_pool; gdb.is_directory = FALSE; gdb.supports_deadprop_count = svn_tristate_unknown; path = session->session_url.path; /* If we have a relative path, URI encode and append it. */ if (rel_path) { path = svn_path_url_add_component2(path, rel_path, scratch_pool); } /* If the user specified a peg revision other than HEAD, we have to fetch the baseline collection url for that revision. If not, we can use the public url. */ if (SVN_IS_VALID_REVNUM(revision) || fetched_rev) { SVN_ERR(svn_ra_serf__get_stable_url(&path, fetched_rev, session, path, revision, scratch_pool, scratch_pool)); revision = SVN_INVALID_REVNUM; } /* REVISION is always SVN_INVALID_REVNUM */ SVN_ERR_ASSERT(!SVN_IS_VALID_REVNUM(revision)); gdb.path = path; /* If we're asked for children, fetch them now. */ if (dirents) { /* Always request node kind to check that path is really a * directory. */ if (!ret_props) dirent_fields |= SVN_DIRENT_KIND; gdb.dirents = apr_hash_make(result_pool); SVN_ERR(svn_ra_serf__create_propfind_handler( &dirent_handler, session, path, SVN_INVALID_REVNUM, "1", get_dirent_props(dirent_fields, session, scratch_pool), get_dir_dirents_cb, &gdb, scratch_pool)); svn_ra_serf__request_create(dirent_handler); } else gdb.dirents = NULL; if (ret_props) { gdb.ret_props = apr_hash_make(result_pool); SVN_ERR(svn_ra_serf__create_propfind_handler( &props_handler, session, path, SVN_INVALID_REVNUM, "0", all_props, get_dir_props_cb, &gdb, scratch_pool)); svn_ra_serf__request_create(props_handler); } else gdb.ret_props = NULL; if (dirent_handler) { err = svn_error_trace( svn_ra_serf__context_run_wait(&dirent_handler->done, session, scratch_pool)); if (err) { svn_pool_clear(scratch_pool); /* Unregisters outstanding requests */ return err; } if (gdb.supports_deadprop_count == svn_tristate_false && session->supports_deadprop_count == svn_tristate_unknown && dirent_fields & SVN_DIRENT_HAS_PROPS) { /* We have to requery as the server didn't give us the right information */ session->supports_deadprop_count = svn_tristate_false; apr_hash_clear(gdb.dirents); SVN_ERR(svn_ra_serf__create_propfind_handler( &dirent_handler, session, path, SVN_INVALID_REVNUM, "1", get_dirent_props(dirent_fields, session, scratch_pool), get_dir_dirents_cb, &gdb, scratch_pool)); svn_ra_serf__request_create(dirent_handler); } } if (props_handler) { err = svn_error_trace( svn_ra_serf__context_run_wait(&props_handler->done, session, scratch_pool)); } /* And dirent again for the case when we had to send the request again */ if (! err && dirent_handler) { err = svn_error_trace( svn_ra_serf__context_run_wait(&dirent_handler->done, session, scratch_pool)); } if (!err && gdb.supports_deadprop_count != svn_tristate_unknown) session->supports_deadprop_count = gdb.supports_deadprop_count; svn_pool_destroy(scratch_pool); /* Unregisters outstanding requests */ SVN_ERR(err); if (!gdb.is_directory) return svn_error_create(SVN_ERR_FS_NOT_DIRECTORY, NULL, _("Can't get entries of non-directory")); if (ret_props) *ret_props = gdb.ret_props; if (dirents) *dirents = gdb.dirents; return SVN_NO_ERROR; }