stat.c revision 302408
1/* 2 * stat.c : file and directory stat and read functions 3 * 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 12 * 13 * http://www.apache.org/licenses/LICENSE-2.0 14 * 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 20 * under the License. 21 * ==================================================================== 22 */ 23 24 25 26#define APR_WANT_STRFUNC 27#include <apr_want.h> 28 29#include <serf.h> 30 31#include "svn_private_config.h" 32#include "svn_pools.h" 33#include "svn_xml.h" 34#include "../libsvn_ra/ra_loader.h" 35#include "svn_config.h" 36#include "svn_dirent_uri.h" 37#include "svn_hash.h" 38#include "svn_path.h" 39#include "svn_props.h" 40#include "svn_time.h" 41#include "svn_version.h" 42 43#include "private/svn_dav_protocol.h" 44#include "private/svn_dep_compat.h" 45#include "private/svn_fspath.h" 46 47#include "ra_serf.h" 48 49 50 51/* Implements svn_ra__vtable_t.check_path(). */ 52svn_error_t * 53svn_ra_serf__check_path(svn_ra_session_t *ra_session, 54 const char *relpath, 55 svn_revnum_t revision, 56 svn_node_kind_t *kind, 57 apr_pool_t *scratch_pool) 58{ 59 svn_ra_serf__session_t *session = ra_session->priv; 60 apr_hash_t *props; 61 svn_error_t *err; 62 const char *url; 63 64 url = session->session_url.path; 65 66 /* If we have a relative path, append it. */ 67 if (relpath) 68 url = svn_path_url_add_component2(url, relpath, scratch_pool); 69 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)) 73 { 74 SVN_ERR(svn_ra_serf__get_stable_url(&url, NULL /* latest_revnum */, 75 session, 76 url, revision, 77 scratch_pool, scratch_pool)); 78 } 79 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, 84 check_path_props, 85 scratch_pool, scratch_pool); 86 87 if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND) 88 { 89 svn_error_clear(err); 90 *kind = svn_node_none; 91 } 92 else 93 { 94 apr_hash_t *dav_props; 95 const char *res_type; 96 97 /* Any other error, raise to caller. */ 98 SVN_ERR(err); 99 100 dav_props = apr_hash_get(props, "DAV:", 4); 101 res_type = svn_prop_get_value(dav_props, "resourcetype"); 102 if (!res_type) 103 { 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")); 108 } 109 110 if (strcmp(res_type, "collection") == 0) 111 *kind = svn_node_dir; 112 else 113 *kind = svn_node_file; 114 } 115 116 return SVN_NO_ERROR; 117} 118 119 120/* Baton for fill_dirent_propfunc() */ 121struct fill_dirent_baton_t 122{ 123 /* Update the fields in this entry. */ 124 svn_dirent_t *entry; 125 126 svn_tristate_t *supports_deadprop_count; 127 128 /* If allocations are necessary, then use this pool. */ 129 apr_pool_t *result_pool; 130}; 131 132/* Implements svn_ra_serf__prop_func_t */ 133static svn_error_t * 134fill_dirent_propfunc(void *baton, 135 const char *path, 136 const char *ns, 137 const char *name, 138 const svn_string_t *val, 139 apr_pool_t *scratch_pool) 140{ 141 struct fill_dirent_baton_t *fdb = baton; 142 143 if (strcmp(ns, "DAV:") == 0) 144 { 145 if (strcmp(name, SVN_DAV__VERSION_NAME) == 0) 146 { 147 apr_int64_t rev; 148 SVN_ERR(svn_cstring_atoi64(&rev, val->data)); 149 150 fdb->entry->created_rev = (svn_revnum_t)rev; 151 } 152 else if (strcmp(name, "creator-displayname") == 0) 153 { 154 fdb->entry->last_author = apr_pstrdup(fdb->result_pool, val->data); 155 } 156 else if (strcmp(name, SVN_DAV__CREATIONDATE) == 0) 157 { 158 SVN_ERR(svn_time_from_cstring(&fdb->entry->time, 159 val->data, 160 fdb->result_pool)); 161 } 162 else if (strcmp(name, "getcontentlength") == 0) 163 { 164 /* 'getcontentlength' property is empty for directories. */ 165 if (val->len) 166 { 167 SVN_ERR(svn_cstring_atoi64(&fdb->entry->size, val->data)); 168 } 169 } 170 else if (strcmp(name, "resourcetype") == 0) 171 { 172 if (strcmp(val->data, "collection") == 0) 173 { 174 fdb->entry->kind = svn_node_dir; 175 } 176 else 177 { 178 fdb->entry->kind = svn_node_file; 179 } 180 } 181 } 182 else if (strcmp(ns, SVN_DAV_PROP_NS_CUSTOM) == 0) 183 { 184 fdb->entry->has_props = TRUE; 185 } 186 else if (strcmp(ns, SVN_DAV_PROP_NS_SVN) == 0) 187 { 188 fdb->entry->has_props = TRUE; 189 } 190 else if (strcmp(ns, SVN_DAV_PROP_NS_DAV) == 0) 191 { 192 if(strcmp(name, "deadprop-count") == 0) 193 { 194 if (*val->data) 195 { 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; 201 } 202 else if (fdb->supports_deadprop_count) 203 *fdb->supports_deadprop_count = svn_tristate_false; 204 } 205 } 206 207 return SVN_NO_ERROR; 208} 209 210static const svn_ra_serf__dav_props_t * 211get_dirent_props(apr_uint32_t dirent_fields, 212 svn_ra_serf__session_t *session, 213 apr_pool_t *pool) 214{ 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)); 218 219 if (session->supports_deadprop_count != svn_tristate_false 220 || ! (dirent_fields & SVN_DIRENT_HAS_PROPS)) 221 { 222 if (dirent_fields & SVN_DIRENT_KIND) 223 { 224 prop = apr_array_push(props); 225 prop->xmlns = "DAV:"; 226 prop->name = "resourcetype"; 227 } 228 229 if (dirent_fields & SVN_DIRENT_SIZE) 230 { 231 prop = apr_array_push(props); 232 prop->xmlns = "DAV:"; 233 prop->name = "getcontentlength"; 234 } 235 236 if (dirent_fields & SVN_DIRENT_HAS_PROPS) 237 { 238 prop = apr_array_push(props); 239 prop->xmlns = SVN_DAV_PROP_NS_DAV; 240 prop->name = "deadprop-count"; 241 } 242 243 if (dirent_fields & SVN_DIRENT_CREATED_REV) 244 { 245 svn_ra_serf__dav_props_t *p = apr_array_push(props); 246 p->xmlns = "DAV:"; 247 p->name = SVN_DAV__VERSION_NAME; 248 } 249 250 if (dirent_fields & SVN_DIRENT_TIME) 251 { 252 prop = apr_array_push(props); 253 prop->xmlns = "DAV:"; 254 prop->name = SVN_DAV__CREATIONDATE; 255 } 256 257 if (dirent_fields & SVN_DIRENT_LAST_AUTHOR) 258 { 259 prop = apr_array_push(props); 260 prop->xmlns = "DAV:"; 261 prop->name = "creator-displayname"; 262 } 263 } 264 else 265 { 266 /* We found an old subversion server that can't handle 267 the deadprop-count property in the way we expect. 268 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"; 273 } 274 275 prop = apr_array_push(props); 276 prop->xmlns = NULL; 277 prop->name = NULL; 278 279 return (svn_ra_serf__dav_props_t *) props->elts; 280} 281 282/* Implements svn_ra__vtable_t.stat(). */ 283svn_error_t * 284svn_ra_serf__stat(svn_ra_session_t *ra_session, 285 const char *relpath, 286 svn_revnum_t revision, 287 svn_dirent_t **dirent, 288 apr_pool_t *pool) 289{ 290 svn_ra_serf__session_t *session = ra_session->priv; 291 svn_error_t *err; 292 struct fill_dirent_baton_t fdb; 293 svn_tristate_t deadprop_count = svn_tristate_unknown; 294 svn_ra_serf__handler_t *handler; 295 const char *url; 296 297 url = session->session_url.path; 298 299 /* If we have a relative path, append it. */ 300 if (relpath) 301 url = svn_path_url_add_component2(url, relpath, pool); 302 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)) 306 { 307 SVN_ERR(svn_ra_serf__get_stable_url(&url, NULL /* latest_revnum */, 308 session, 309 url, revision, 310 pool, pool)); 311 } 312 313 fdb.entry = svn_dirent_create(pool); 314 fdb.supports_deadprop_count = &deadprop_count; 315 fdb.result_pool = pool; 316 317 SVN_ERR(svn_ra_serf__create_propfind_handler(&handler, session, url, 318 SVN_INVALID_REVNUM, "0", 319 get_dirent_props(SVN_DIRENT_ALL, 320 session, 321 pool), 322 fill_dirent_propfunc, &fdb, pool)); 323 324 err = svn_ra_serf__context_run_one(handler, pool); 325 326 if (err) 327 { 328 if (err->apr_err == SVN_ERR_FS_NOT_FOUND) 329 { 330 svn_error_clear(err); 331 *dirent = NULL; 332 return SVN_NO_ERROR; 333 } 334 else 335 return svn_error_trace(err); 336 } 337 338 if (deadprop_count == svn_tristate_false 339 && session->supports_deadprop_count == svn_tristate_unknown 340 && !fdb.entry->has_props) 341 { 342 /* We have to requery as the server didn't give us the right 343 information */ 344 session->supports_deadprop_count = svn_tristate_false; 345 346 /* Run the same handler again */ 347 SVN_ERR(svn_ra_serf__context_run_one(handler, pool)); 348 } 349 350 if (deadprop_count != svn_tristate_unknown) 351 session->supports_deadprop_count = deadprop_count; 352 353 *dirent = fdb.entry; 354 355 return SVN_NO_ERROR; 356} 357 358/* Baton for get_dir_dirents_cb and get_dir_props_cb */ 359struct get_dir_baton_t 360{ 361 apr_pool_t *result_pool; 362 apr_hash_t *dirents; 363 apr_hash_t *ret_props; 364 svn_boolean_t is_directory; 365 svn_tristate_t supports_deadprop_count; 366 const char *path; 367}; 368 369/* Implements svn_ra_serf__prop_func_t */ 370static svn_error_t * 371get_dir_dirents_cb(void *baton, 372 const char *path, 373 const char *ns, 374 const char *name, 375 const svn_string_t *value, 376 apr_pool_t *scratch_pool) 377{ 378 struct get_dir_baton_t *db = baton; 379 const char *relpath; 380 381 relpath = svn_fspath__skip_ancestor(db->path, path); 382 383 if (relpath && relpath[0] != '\0') 384 { 385 struct fill_dirent_baton_t fdb; 386 387 relpath = svn_path_uri_decode(relpath, scratch_pool); 388 fdb.entry = svn_hash_gets(db->dirents, relpath); 389 390 if (!fdb.entry) 391 { 392 fdb.entry = svn_dirent_create(db->result_pool); 393 svn_hash_sets(db->dirents, 394 apr_pstrdup(db->result_pool, relpath), 395 fdb.entry); 396 } 397 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)); 401 } 402 else if (relpath && !db->is_directory) 403 { 404 if (strcmp(ns, "DAV:") == 0 && strcmp(name, "resourcetype") == 0) 405 { 406 if (strcmp(value->data, "collection") != 0) 407 { 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")); 411 } 412 else 413 db->is_directory = TRUE; 414 } 415 } 416 417 return SVN_NO_ERROR; 418} 419 420/* Implements svn_ra_serf__prop_func */ 421static svn_error_t * 422get_dir_props_cb(void *baton, 423 const char *path, 424 const char *ns, 425 const char *name, 426 const svn_string_t *value, 427 apr_pool_t *scratch_pool) 428{ 429 struct get_dir_baton_t *db = baton; 430 const char *propname; 431 432 propname = svn_ra_serf__svnname_from_wirename(ns, name, db->result_pool); 433 if (propname) 434 { 435 svn_hash_sets(db->ret_props, propname, 436 svn_string_dup(value, db->result_pool)); 437 return SVN_NO_ERROR; 438 } 439 440 if (!db->is_directory) 441 { 442 if (strcmp(ns, "DAV:") == 0 && strcmp(name, "resourcetype") == 0) 443 { 444 if (strcmp(value->data, "collection") != 0) 445 { 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")); 449 } 450 else 451 db->is_directory = TRUE; 452 } 453 } 454 455 return SVN_NO_ERROR; 456} 457 458/* Implements svn_ra__vtable_t.get_dir(). */ 459svn_error_t * 460svn_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) 468{ 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; 473 const char *path; 474 struct get_dir_baton_t gdb; 475 svn_error_t *err = SVN_NO_ERROR; 476 477 gdb.result_pool = result_pool; 478 gdb.is_directory = FALSE; 479 gdb.supports_deadprop_count = svn_tristate_unknown; 480 481 path = session->session_url.path; 482 483 /* If we have a relative path, URI encode and append it. */ 484 if (rel_path) 485 { 486 path = svn_path_url_add_component2(path, rel_path, scratch_pool); 487 } 488 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 491 public url. */ 492 if (SVN_IS_VALID_REVNUM(revision) || fetched_rev) 493 { 494 SVN_ERR(svn_ra_serf__get_stable_url(&path, fetched_rev, 495 session, 496 path, revision, 497 scratch_pool, scratch_pool)); 498 revision = SVN_INVALID_REVNUM; 499 } 500 /* REVISION is always SVN_INVALID_REVNUM */ 501 SVN_ERR_ASSERT(!SVN_IS_VALID_REVNUM(revision)); 502 503 gdb.path = path; 504 505 /* If we're asked for children, fetch them now. */ 506 if (dirents) 507 { 508 /* Always request node kind to check that path is really a 509 * directory. */ 510 if (!ret_props) 511 dirent_fields |= SVN_DIRENT_KIND; 512 513 gdb.dirents = apr_hash_make(result_pool); 514 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, 519 session, 520 scratch_pool), 521 get_dir_dirents_cb, &gdb, 522 scratch_pool)); 523 524 svn_ra_serf__request_create(dirent_handler); 525 } 526 else 527 gdb.dirents = NULL; 528 529 if (ret_props) 530 { 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", 535 all_props, 536 get_dir_props_cb, &gdb, 537 scratch_pool)); 538 539 svn_ra_serf__request_create(props_handler); 540 } 541 else 542 gdb.ret_props = NULL; 543 544 if (dirent_handler) 545 { 546 err = svn_error_trace( 547 svn_ra_serf__context_run_wait(&dirent_handler->done, 548 session, 549 scratch_pool)); 550 551 if (err) 552 { 553 svn_pool_clear(scratch_pool); /* Unregisters outstanding requests */ 554 return err; 555 } 556 557 if (gdb.supports_deadprop_count == svn_tristate_false 558 && session->supports_deadprop_count == svn_tristate_unknown 559 && dirent_fields & SVN_DIRENT_HAS_PROPS) 560 { 561 /* We have to requery as the server didn't give us the right 562 information */ 563 session->supports_deadprop_count = svn_tristate_false; 564 565 apr_hash_clear(gdb.dirents); 566 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, 571 session, 572 scratch_pool), 573 get_dir_dirents_cb, &gdb, 574 scratch_pool)); 575 576 svn_ra_serf__request_create(dirent_handler); 577 } 578 } 579 580 if (props_handler) 581 { 582 err = svn_error_trace( 583 svn_ra_serf__context_run_wait(&props_handler->done, 584 session, 585 scratch_pool)); 586 } 587 588 /* And dirent again for the case when we had to send the request again */ 589 if (! err && dirent_handler) 590 { 591 err = svn_error_trace( 592 svn_ra_serf__context_run_wait(&dirent_handler->done, 593 session, 594 scratch_pool)); 595 } 596 597 if (!err && gdb.supports_deadprop_count != svn_tristate_unknown) 598 session->supports_deadprop_count = gdb.supports_deadprop_count; 599 600 svn_pool_destroy(scratch_pool); /* Unregisters outstanding requests */ 601 602 SVN_ERR(err); 603 604 if (!gdb.is_directory) 605 return svn_error_create(SVN_ERR_FS_NOT_DIRECTORY, NULL, 606 _("Can't get entries of non-directory")); 607 608 if (ret_props) 609 *ret_props = gdb.ret_props; 610 611 if (dirents) 612 *dirents = gdb.dirents; 613 614 return SVN_NO_ERROR; 615} 616