1/* 2 * update.c : entry point for update RA functions for ra_serf 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_version.h> 28#include <apr_want.h> 29 30#include <apr_uri.h> 31 32#include <serf.h> 33 34#include "svn_hash.h" 35#include "svn_pools.h" 36#include "svn_ra.h" 37#include "svn_dav.h" 38#include "svn_xml.h" 39#include "svn_delta.h" 40#include "svn_path.h" 41#include "svn_base64.h" 42#include "svn_props.h" 43 44#include "svn_private_config.h" 45#include "private/svn_dep_compat.h" 46#include "private/svn_fspath.h" 47#include "private/svn_string_private.h" 48 49#include "ra_serf.h" 50#include "../libsvn_ra/ra_loader.h" 51 52 53/* 54 * This enum represents the current state of our XML parsing for a REPORT. 55 * 56 * A little explanation of how the parsing works. Every time we see 57 * an open-directory tag, we enter the OPEN_DIR state. Likewise, for 58 * add-directory, open-file, etc. When we see the closing variant of the 59 * open-directory tag, we'll 'pop' out of that state. 60 * 61 * Each state has a pool associated with it that can have temporary 62 * allocations that will live as long as the tag is opened. Once 63 * the tag is 'closed', the pool will be reused. 64 */ 65typedef enum report_state_e { 66 NONE = 0, 67 INITIAL = 0, 68 UPDATE_REPORT, 69 TARGET_REVISION, 70 OPEN_DIR, 71 ADD_DIR, 72 ABSENT_DIR, 73 OPEN_FILE, 74 ADD_FILE, 75 ABSENT_FILE, 76 PROP, 77 IGNORE_PROP_NAME, 78 NEED_PROP_NAME, 79 TXDELTA 80} report_state_e; 81 82 83/* While we process the REPORT response, we will queue up GET and PROPFIND 84 requests. For a very large checkout, it is very easy to queue requests 85 faster than they are resolved. Thus, we need to pause the XML processing 86 (which queues more requests) to avoid queueing too many, with their 87 attendant memory costs. When the queue count drops low enough, we will 88 resume XML processing. 89 90 Note that we don't want the count to drop to zero. We have multiple 91 connections that we want to keep busy. These are also heuristic numbers 92 since network and parsing behavior (ie. it doesn't pause immediately) 93 can make the measurements quite imprecise. 94 95 We measure outstanding requests as the sum of NUM_ACTIVE_FETCHES and 96 NUM_ACTIVE_PROPFINDS in the report_context_t structure. */ 97#define REQUEST_COUNT_TO_PAUSE 50 98#define REQUEST_COUNT_TO_RESUME 40 99 100 101/* Forward-declare our report context. */ 102typedef struct report_context_t report_context_t; 103 104/* 105 * This structure represents the information for a directory. 106 */ 107typedef struct report_dir_t 108{ 109 /* Our parent directory. 110 * 111 * This value is NULL when we are the root. 112 */ 113 struct report_dir_t *parent_dir; 114 115 apr_pool_t *pool; 116 117 /* Pointer back to our original report context. */ 118 report_context_t *report_context; 119 120 /* Our name sans any parents. */ 121 const char *base_name; 122 123 /* the expanded directory name (including all parent names) */ 124 const char *name; 125 126 /* the canonical url for this directory after updating. (received) */ 127 const char *url; 128 129 /* The original repos_relpath of this url (from the working copy) 130 or NULL if the repos_relpath can be calculated from the edit root. */ 131 const char *repos_relpath; 132 133 /* Our base revision - SVN_INVALID_REVNUM if we're adding this dir. */ 134 svn_revnum_t base_rev; 135 136 /* controlling dir baton - this is only created in ensure_dir_opened() */ 137 void *dir_baton; 138 apr_pool_t *dir_baton_pool; 139 140 /* How many references to this directory do we still have open? */ 141 apr_size_t ref_count; 142 143 /* Namespace list allocated out of this ->pool. */ 144 svn_ra_serf__ns_t *ns_list; 145 146 /* hashtable for all of the properties (shared within a dir) */ 147 apr_hash_t *props; 148 149 /* hashtable for all to-be-removed properties (shared within a dir) */ 150 apr_hash_t *removed_props; 151 152 /* The propfind request for our current directory */ 153 svn_ra_serf__handler_t *propfind_handler; 154 155 /* Has the server told us to fetch the dir props? */ 156 svn_boolean_t fetch_props; 157 158 /* Have we closed the directory tag (meaning no more additions)? */ 159 svn_boolean_t tag_closed; 160 161 /* The children of this directory */ 162 struct report_dir_t *children; 163 164 /* The next sibling of this directory */ 165 struct report_dir_t *sibling; 166} report_dir_t; 167 168/* 169 * This structure represents the information for a file. 170 * 171 * A directory may have a report_info_t associated with it as well. 172 * 173 * This structure is created as we parse the REPORT response and 174 * once the element is completed, we create a report_fetch_t structure 175 * to give to serf to retrieve this file. 176 */ 177typedef struct report_info_t 178{ 179 apr_pool_t *pool; 180 181 /* The enclosing directory. 182 * 183 * If this structure refers to a directory, the dir it points to will be 184 * itself. 185 */ 186 report_dir_t *dir; 187 188 /* Our name sans any directory info. */ 189 const char *base_name; 190 191 /* the expanded file name (including all parent directory names) */ 192 const char *name; 193 194 /* the canonical url for this file. */ 195 const char *url; 196 197 /* lock token, if we had one to start off with. */ 198 const char *lock_token; 199 200 /* Our base revision - SVN_INVALID_REVNUM if we're adding this file. */ 201 svn_revnum_t base_rev; 202 203 /* our delta base, if present (NULL if we're adding the file) */ 204 const char *delta_base; 205 206 /* Path of original item if add with history */ 207 const char *copyfrom_path; 208 209 /* Revision of original item if add with history */ 210 svn_revnum_t copyfrom_rev; 211 212 /* The propfind request for our current file (if present) */ 213 svn_ra_serf__handler_t *propfind_handler; 214 215 /* Has the server told us to fetch the file props? */ 216 svn_boolean_t fetch_props; 217 218 /* Has the server told us to go fetch - only valid if we had it already */ 219 svn_boolean_t fetch_file; 220 221 /* The properties for this file */ 222 apr_hash_t *props; 223 224 /* pool passed to update->add_file, etc. */ 225 apr_pool_t *editor_pool; 226 227 /* controlling file_baton and textdelta handler */ 228 void *file_baton; 229 const char *base_checksum; 230 const char *final_sha1_checksum; 231 svn_txdelta_window_handler_t textdelta; 232 void *textdelta_baton; 233 svn_stream_t *svndiff_decoder; 234 svn_stream_t *base64_decoder; 235 236 /* Checksum for close_file */ 237 const char *final_checksum; 238 239 /* Stream containing file contents already cached in the working 240 copy (which may be used to avoid a GET request for the same). */ 241 svn_stream_t *cached_contents; 242 243 /* temporary property for this file which is currently being parsed 244 * It will eventually be stored in our parent directory's property hash. 245 */ 246 const char *prop_ns; 247 const char *prop_name; 248 svn_stringbuf_t *prop_value; 249 const char *prop_encoding; 250} report_info_t; 251 252/* 253 * This structure represents a single request to GET (fetch) a file with 254 * its associated Serf session/connection. 255 */ 256typedef struct report_fetch_t { 257 258 /* The handler representing this particular fetch. */ 259 svn_ra_serf__handler_t *handler; 260 261 /* The session we should use to fetch the file. */ 262 svn_ra_serf__session_t *sess; 263 264 /* The connection we should use to fetch file. */ 265 svn_ra_serf__connection_t *conn; 266 267 /* Stores the information for the file we want to fetch. */ 268 report_info_t *info; 269 270 /* Have we read our response headers yet? */ 271 svn_boolean_t read_headers; 272 273 /* This flag is set when our response is aborted before we reach the 274 * end and we decide to requeue this request. 275 */ 276 svn_boolean_t aborted_read; 277 apr_off_t aborted_read_size; 278 279 /* This is the amount of data that we have read so far. */ 280 apr_off_t read_size; 281 282 /* If we're receiving an svndiff, this will be non-NULL. */ 283 svn_stream_t *delta_stream; 284 285 /* If we're writing this file to a stream, this will be non-NULL. */ 286 svn_stream_t *target_stream; 287 288 /* Are we done fetching this file? */ 289 svn_boolean_t done; 290 291 /* Discard the rest of the content? */ 292 svn_boolean_t discard; 293 294 svn_ra_serf__list_t **done_list; 295 svn_ra_serf__list_t done_item; 296 297} report_fetch_t; 298 299/* 300 * The master structure for a REPORT request and response. 301 */ 302struct report_context_t { 303 apr_pool_t *pool; 304 305 svn_ra_serf__session_t *sess; 306 svn_ra_serf__connection_t *conn; 307 308 /* Source path and destination path */ 309 const char *source; 310 const char *destination; 311 312 /* Our update target. */ 313 const char *update_target; 314 315 /* What is the target revision that we want for this REPORT? */ 316 svn_revnum_t target_rev; 317 318 /* Have we been asked to ignore ancestry or textdeltas? */ 319 svn_boolean_t ignore_ancestry; 320 svn_boolean_t text_deltas; 321 322 /* Do we want the server to send copyfrom args or not? */ 323 svn_boolean_t send_copyfrom_args; 324 325 /* Is the server sending everything in one response? */ 326 svn_boolean_t send_all_mode; 327 328 /* Is the server including properties inline for newly added 329 files/dirs? */ 330 svn_boolean_t add_props_included; 331 332 /* Path -> lock token mapping. */ 333 apr_hash_t *lock_path_tokens; 334 335 /* Path -> const char *repos_relpath mapping */ 336 apr_hash_t *switched_paths; 337 338 /* Boolean indicating whether "" is switched. 339 (This indicates that the we are updating a single file) */ 340 svn_boolean_t root_is_switched; 341 342 /* Our master update editor and baton. */ 343 const svn_delta_editor_t *update_editor; 344 void *update_baton; 345 346 /* The file holding request body for the REPORT. 347 * 348 * ### todo: It will be better for performance to store small 349 * request bodies (like 4k) in memory and bigger bodies on disk. 350 */ 351 apr_file_t *body_file; 352 353 /* root directory object */ 354 report_dir_t *root_dir; 355 356 /* number of pending GET requests */ 357 unsigned int num_active_fetches; 358 359 /* completed fetches (contains report_fetch_t) */ 360 svn_ra_serf__list_t *done_fetches; 361 362 /* number of pending PROPFIND requests */ 363 unsigned int num_active_propfinds; 364 365 /* completed PROPFIND requests (contains svn_ra_serf__handler_t) */ 366 svn_ra_serf__list_t *done_propfinds; 367 svn_ra_serf__list_t *done_dir_propfinds; 368 369 /* list of outstanding prop changes (contains report_dir_t) */ 370 svn_ra_serf__list_t *active_dir_propfinds; 371 372 /* list of files that only have prop changes (contains report_info_t) */ 373 svn_ra_serf__list_t *file_propchanges_only; 374 375 /* The path to the REPORT request */ 376 const char *path; 377 378 /* Are we done parsing the REPORT response? */ 379 svn_boolean_t done; 380 381 /* Did we receive all data from the network? */ 382 svn_boolean_t report_received; 383 384 /* Did we get a complete (non-truncated) report? */ 385 svn_boolean_t report_completed; 386 387 /* The XML parser context for the REPORT response. */ 388 svn_ra_serf__xml_parser_t *parser_ctx; 389 390 /* Did we close the root directory? */ 391 svn_boolean_t closed_root; 392}; 393 394 395#ifdef NOT_USED_YET 396 397#define D_ "DAV:" 398#define S_ SVN_XML_NAMESPACE 399static const svn_ra_serf__xml_transition_t update_ttable[] = { 400 { INITIAL, S_, "update-report", UPDATE_REPORT, 401 FALSE, { NULL }, FALSE }, 402 403 { UPDATE_REPORT, S_, "target-revision", TARGET_REVISION, 404 FALSE, { "rev", NULL }, TRUE }, 405 406 { UPDATE_REPORT, S_, "open-directory", OPEN_DIR, 407 FALSE, { "rev", NULL }, TRUE }, 408 409 { OPEN_DIR, S_, "open-directory", OPEN_DIR, 410 FALSE, { "rev", "name", NULL }, TRUE }, 411 412 { OPEN_DIR, S_, "add-directory", ADD_DIR, 413 FALSE, { "rev", "name", "?copyfrom-path", "?copyfrom-rev", NULL }, TRUE }, 414 415 { ADD_DIR, S_, "add-directory", ADD_DIR, 416 FALSE, { "rev", "name", "?copyfrom-path", "?copyfrom-rev", NULL }, TRUE }, 417 418 { OPEN_DIR, S_, "open-file", OPEN_FILE, 419 FALSE, { "rev", "name", NULL }, TRUE }, 420 421 { OPEN_DIR, S_, "add-file", ADD_FILE, 422 FALSE, { "rev", "name", "?copyfrom-path", "?copyfrom-rev", NULL }, TRUE }, 423 424 { ADD_DIR, S_, "add-file", ADD_FILE, 425 FALSE, { "rev", "name", "?copyfrom-path", "?copyfrom-rev", NULL }, TRUE }, 426 427 { OPEN_DIR, S_, "delete-entry", OPEN_FILE, 428 FALSE, { "?rev", "name", NULL }, TRUE }, 429 430 { OPEN_DIR, S_, "absent-directory", ABSENT_DIR, 431 FALSE, { "name", NULL }, TRUE }, 432 433 { ADD_DIR, S_, "absent-directory", ABSENT_DIR, 434 FALSE, { "name", NULL }, TRUE }, 435 436 { OPEN_DIR, S_, "absent-file", ABSENT_FILE, 437 FALSE, { "name", NULL }, TRUE }, 438 439 { ADD_DIR, S_, "absent-file", ABSENT_FILE, 440 FALSE, { "name", NULL }, TRUE }, 441 442 { 0 } 443}; 444 445 446 447/* Conforms to svn_ra_serf__xml_opened_t */ 448static svn_error_t * 449update_opened(svn_ra_serf__xml_estate_t *xes, 450 void *baton, 451 int entered_state, 452 const svn_ra_serf__dav_props_t *tag, 453 apr_pool_t *scratch_pool) 454{ 455 report_context_t *ctx = baton; 456 457 return SVN_NO_ERROR; 458} 459 460 461 462/* Conforms to svn_ra_serf__xml_closed_t */ 463static svn_error_t * 464update_closed(svn_ra_serf__xml_estate_t *xes, 465 void *baton, 466 int leaving_state, 467 const svn_string_t *cdata, 468 apr_hash_t *attrs, 469 apr_pool_t *scratch_pool) 470{ 471 report_context_t *ctx = baton; 472 473 if (leaving_state == TARGET_REVISION) 474 { 475 const char *rev = svn_hash_gets(attrs, "rev"); 476 477 SVN_ERR(ctx->update_editor->set_target_revision(ctx->update_baton, 478 SVN_STR_TO_REV(rev), 479 ctx->sess->pool)); 480 } 481 482 return SVN_NO_ERROR; 483} 484 485 486/* Conforms to svn_ra_serf__xml_cdata_t */ 487static svn_error_t * 488update_cdata(svn_ra_serf__xml_estate_t *xes, 489 void *baton, 490 int current_state, 491 const char *data, 492 apr_size_t len, 493 apr_pool_t *scratch_pool) 494{ 495 report_context_t *ctx = baton; 496 497 return SVN_NO_ERROR; 498} 499 500#endif /* NOT_USED_YET */ 501 502 503/* Returns best connection for fetching files/properties. */ 504static svn_ra_serf__connection_t * 505get_best_connection(report_context_t *ctx) 506{ 507 svn_ra_serf__connection_t *conn; 508 int first_conn = 1; 509 510 /* Skip the first connection if the REPORT response hasn't been completely 511 received yet or if we're being told to limit our connections to 512 2 (because this could be an attempt to ensure that we do all our 513 auxiliary GETs/PROPFINDs on a single connection). 514 515 ### FIXME: This latter requirement (max_connections > 2) is 516 ### really just a hack to work around the fact that some update 517 ### editor implementations (such as svnrdump's dump editor) 518 ### simply can't handle the way ra_serf violates the editor v1 519 ### drive ordering requirements. 520 ### 521 ### See http://subversion.tigris.org/issues/show_bug.cgi?id=4116. 522 */ 523 if (ctx->report_received && (ctx->sess->max_connections > 2)) 524 first_conn = 0; 525 526 /* Currently, we just cycle connections. In the future we could 527 store the number of pending requests on each connection, or 528 perform other heuristics, to achieve better connection usage. 529 (As an optimization, if there's only one available auxiliary 530 connection to use, don't bother doing all the cur_conn math -- 531 just return that one connection.) */ 532 if (ctx->sess->num_conns - first_conn == 1) 533 { 534 conn = ctx->sess->conns[first_conn]; 535 } 536 else 537 { 538 conn = ctx->sess->conns[ctx->sess->cur_conn]; 539 ctx->sess->cur_conn++; 540 if (ctx->sess->cur_conn >= ctx->sess->num_conns) 541 ctx->sess->cur_conn = first_conn; 542 } 543 return conn; 544} 545 546 547/** Report state management helper **/ 548 549static report_info_t * 550push_state(svn_ra_serf__xml_parser_t *parser, 551 report_context_t *ctx, 552 report_state_e state) 553{ 554 report_info_t *info; 555 apr_pool_t *info_parent_pool; 556 557 svn_ra_serf__xml_push_state(parser, state); 558 559 info = parser->state->private; 560 561 /* Our private pool needs to be disjoint from the state pool. */ 562 if (!info) 563 { 564 info_parent_pool = ctx->pool; 565 } 566 else 567 { 568 info_parent_pool = info->pool; 569 } 570 571 if (state == OPEN_DIR || state == ADD_DIR) 572 { 573 report_info_t *new_info; 574 575 new_info = apr_pcalloc(info_parent_pool, sizeof(*new_info)); 576 new_info->pool = svn_pool_create(info_parent_pool); 577 new_info->lock_token = NULL; 578 new_info->prop_value = svn_stringbuf_create_empty(new_info->pool); 579 580 new_info->dir = apr_pcalloc(new_info->pool, sizeof(*new_info->dir)); 581 new_info->dir->pool = new_info->pool; 582 583 /* Create the root property tree. */ 584 new_info->dir->props = apr_hash_make(new_info->pool); 585 new_info->props = new_info->dir->props; 586 new_info->dir->removed_props = apr_hash_make(new_info->pool); 587 588 new_info->dir->report_context = ctx; 589 590 if (info) 591 { 592 info->dir->ref_count++; 593 594 new_info->dir->parent_dir = info->dir; 595 596 /* Point our ns_list at our parents to try to reuse it. */ 597 new_info->dir->ns_list = info->dir->ns_list; 598 599 /* Add ourselves to our parent's list */ 600 new_info->dir->sibling = info->dir->children; 601 info->dir->children = new_info->dir; 602 } 603 else 604 { 605 /* Allow us to be found later. */ 606 ctx->root_dir = new_info->dir; 607 } 608 609 parser->state->private = new_info; 610 } 611 else if (state == OPEN_FILE || state == ADD_FILE) 612 { 613 report_info_t *new_info; 614 615 new_info = apr_pcalloc(info_parent_pool, sizeof(*new_info)); 616 new_info->pool = svn_pool_create(info_parent_pool); 617 new_info->file_baton = NULL; 618 new_info->lock_token = NULL; 619 new_info->fetch_file = FALSE; 620 new_info->prop_value = svn_stringbuf_create_empty(new_info->pool); 621 622 /* Point at our parent's directory state. */ 623 new_info->dir = info->dir; 624 info->dir->ref_count++; 625 626 new_info->props = apr_hash_make(new_info->pool); 627 628 parser->state->private = new_info; 629 } 630 631 return parser->state->private; 632} 633 634 635/** Wrappers around our various property walkers **/ 636 637static svn_error_t * 638set_file_props(void *baton, 639 const char *ns, 640 const char *name, 641 const svn_string_t *val, 642 apr_pool_t *scratch_pool) 643{ 644 report_info_t *info = baton; 645 const svn_delta_editor_t *editor = info->dir->report_context->update_editor; 646 const char *prop_name; 647 648 prop_name = svn_ra_serf__svnname_from_wirename(ns, name, scratch_pool); 649 if (prop_name != NULL) 650 return svn_error_trace(editor->change_file_prop(info->file_baton, 651 prop_name, 652 val, 653 scratch_pool)); 654 return SVN_NO_ERROR; 655} 656 657 658static svn_error_t * 659set_dir_props(void *baton, 660 const char *ns, 661 const char *name, 662 const svn_string_t *val, 663 apr_pool_t *scratch_pool) 664{ 665 report_dir_t *dir = baton; 666 const svn_delta_editor_t *editor = dir->report_context->update_editor; 667 const char *prop_name; 668 669 prop_name = svn_ra_serf__svnname_from_wirename(ns, name, scratch_pool); 670 if (prop_name != NULL) 671 return svn_error_trace(editor->change_dir_prop(dir->dir_baton, 672 prop_name, 673 val, 674 scratch_pool)); 675 return SVN_NO_ERROR; 676} 677 678 679static svn_error_t * 680remove_file_props(void *baton, 681 const char *ns, 682 const char *name, 683 const svn_string_t *val, 684 apr_pool_t *scratch_pool) 685{ 686 report_info_t *info = baton; 687 const svn_delta_editor_t *editor = info->dir->report_context->update_editor; 688 const char *prop_name; 689 690 prop_name = svn_ra_serf__svnname_from_wirename(ns, name, scratch_pool); 691 if (prop_name != NULL) 692 return svn_error_trace(editor->change_file_prop(info->file_baton, 693 prop_name, 694 NULL, 695 scratch_pool)); 696 return SVN_NO_ERROR; 697} 698 699 700static svn_error_t * 701remove_dir_props(void *baton, 702 const char *ns, 703 const char *name, 704 const svn_string_t *val, 705 apr_pool_t *scratch_pool) 706{ 707 report_dir_t *dir = baton; 708 const svn_delta_editor_t *editor = dir->report_context->update_editor; 709 const char *prop_name; 710 711 prop_name = svn_ra_serf__svnname_from_wirename(ns, name, scratch_pool); 712 if (prop_name != NULL) 713 return svn_error_trace(editor->change_dir_prop(dir->dir_baton, 714 prop_name, 715 NULL, 716 scratch_pool)); 717 return SVN_NO_ERROR; 718} 719 720 721/** Helpers to open and close directories */ 722 723static svn_error_t* 724ensure_dir_opened(report_dir_t *dir) 725{ 726 report_context_t *ctx = dir->report_context; 727 728 /* if we're already open, return now */ 729 if (dir->dir_baton) 730 { 731 return SVN_NO_ERROR; 732 } 733 734 if (dir->base_name[0] == '\0') 735 { 736 dir->dir_baton_pool = svn_pool_create(dir->pool); 737 738 if (ctx->destination 739 && ctx->sess->wc_callbacks->invalidate_wc_props) 740 { 741 SVN_ERR(ctx->sess->wc_callbacks->invalidate_wc_props( 742 ctx->sess->wc_callback_baton, 743 ctx->update_target, 744 SVN_RA_SERF__WC_CHECKED_IN_URL, dir->pool)); 745 } 746 747 SVN_ERR(ctx->update_editor->open_root(ctx->update_baton, dir->base_rev, 748 dir->dir_baton_pool, 749 &dir->dir_baton)); 750 } 751 else 752 { 753 SVN_ERR(ensure_dir_opened(dir->parent_dir)); 754 755 dir->dir_baton_pool = svn_pool_create(dir->parent_dir->dir_baton_pool); 756 757 if (SVN_IS_VALID_REVNUM(dir->base_rev)) 758 { 759 SVN_ERR(ctx->update_editor->open_directory(dir->name, 760 dir->parent_dir->dir_baton, 761 dir->base_rev, 762 dir->dir_baton_pool, 763 &dir->dir_baton)); 764 } 765 else 766 { 767 SVN_ERR(ctx->update_editor->add_directory(dir->name, 768 dir->parent_dir->dir_baton, 769 NULL, SVN_INVALID_REVNUM, 770 dir->dir_baton_pool, 771 &dir->dir_baton)); 772 } 773 } 774 775 return SVN_NO_ERROR; 776} 777 778static svn_error_t * 779close_dir(report_dir_t *dir) 780{ 781 report_dir_t *prev; 782 report_dir_t *sibling; 783 784 /* ### is there a better pool... this is tossed at end-of-func */ 785 apr_pool_t *scratch_pool = dir->dir_baton_pool; 786 787 SVN_ERR_ASSERT(! dir->ref_count); 788 789 SVN_ERR(svn_ra_serf__walk_all_props(dir->props, dir->base_name, 790 dir->base_rev, 791 set_dir_props, dir, 792 scratch_pool)); 793 794 SVN_ERR(svn_ra_serf__walk_all_props(dir->removed_props, dir->base_name, 795 dir->base_rev, remove_dir_props, dir, 796 scratch_pool)); 797 798 if (dir->fetch_props) 799 { 800 SVN_ERR(svn_ra_serf__walk_all_props(dir->props, dir->url, 801 dir->report_context->target_rev, 802 set_dir_props, dir, 803 scratch_pool)); 804 } 805 806 SVN_ERR(dir->report_context->update_editor->close_directory( 807 dir->dir_baton, scratch_pool)); 808 809 /* remove us from our parent's children list */ 810 if (dir->parent_dir) 811 { 812 prev = NULL; 813 sibling = dir->parent_dir->children; 814 815 while (sibling != dir) 816 { 817 prev = sibling; 818 sibling = sibling->sibling; 819 if (!sibling) 820 SVN_ERR_MALFUNCTION(); 821 } 822 823 if (!prev) 824 { 825 dir->parent_dir->children = dir->sibling; 826 } 827 else 828 { 829 prev->sibling = dir->sibling; 830 } 831 } 832 833 svn_pool_destroy(dir->dir_baton_pool); 834 svn_pool_destroy(dir->pool); 835 836 return SVN_NO_ERROR; 837} 838 839static svn_error_t *close_all_dirs(report_dir_t *dir) 840{ 841 while (dir->children) 842 { 843 SVN_ERR(close_all_dirs(dir->children)); 844 dir->ref_count--; 845 } 846 847 SVN_ERR_ASSERT(! dir->ref_count); 848 849 SVN_ERR(ensure_dir_opened(dir)); 850 851 return close_dir(dir); 852} 853 854 855/** Routines called when we are fetching a file */ 856 857/* This function works around a bug in some older versions of 858 * mod_dav_svn in that it will not send remove-prop in the update 859 * report when a lock property disappears when send-all is false. 860 * 861 * Therefore, we'll try to look at our properties and see if there's 862 * an active lock. If not, then we'll assume there isn't a lock 863 * anymore. 864 */ 865static void 866check_lock(report_info_t *info) 867{ 868 const char *lock_val; 869 870 lock_val = svn_ra_serf__get_ver_prop(info->props, info->url, 871 info->dir->report_context->target_rev, 872 "DAV:", "lockdiscovery"); 873 874 if (lock_val) 875 { 876 char *new_lock; 877 new_lock = apr_pstrdup(info->editor_pool, lock_val); 878 apr_collapse_spaces(new_lock, new_lock); 879 lock_val = new_lock; 880 } 881 882 if (!lock_val || lock_val[0] == '\0') 883 { 884 svn_string_t *str; 885 886 str = svn_string_ncreate("", 1, info->editor_pool); 887 888 svn_ra_serf__set_ver_prop(info->dir->removed_props, info->base_name, 889 info->base_rev, "DAV:", "lock-token", 890 str, info->dir->pool); 891 } 892} 893 894static svn_error_t * 895headers_fetch(serf_bucket_t *headers, 896 void *baton, 897 apr_pool_t *pool) 898{ 899 report_fetch_t *fetch_ctx = baton; 900 901 /* note that we have old VC URL */ 902 if (SVN_IS_VALID_REVNUM(fetch_ctx->info->base_rev) && 903 fetch_ctx->info->delta_base) 904 { 905 serf_bucket_headers_setn(headers, SVN_DAV_DELTA_BASE_HEADER, 906 fetch_ctx->info->delta_base); 907 serf_bucket_headers_setn(headers, "Accept-Encoding", 908 "svndiff1;q=0.9,svndiff;q=0.8"); 909 } 910 else if (fetch_ctx->sess->using_compression) 911 { 912 serf_bucket_headers_setn(headers, "Accept-Encoding", "gzip"); 913 } 914 915 return SVN_NO_ERROR; 916} 917 918static svn_error_t * 919cancel_fetch(serf_request_t *request, 920 serf_bucket_t *response, 921 int status_code, 922 void *baton) 923{ 924 report_fetch_t *fetch_ctx = baton; 925 926 /* Uh-oh. Our connection died on us. 927 * 928 * The core ra_serf layer will requeue our request - we just need to note 929 * that we got cut off in the middle of our song. 930 */ 931 if (!response) 932 { 933 /* If we already started the fetch and opened the file handle, we need 934 * to hold subsequent read() ops until we get back to where we were 935 * before the close and we can then resume the textdelta() calls. 936 */ 937 if (fetch_ctx->read_headers) 938 { 939 if (!fetch_ctx->aborted_read && fetch_ctx->read_size) 940 { 941 fetch_ctx->aborted_read = TRUE; 942 fetch_ctx->aborted_read_size = fetch_ctx->read_size; 943 } 944 fetch_ctx->read_size = 0; 945 } 946 947 return SVN_NO_ERROR; 948 } 949 950 /* We have no idea what went wrong. */ 951 SVN_ERR_MALFUNCTION(); 952} 953 954static svn_error_t * 955error_fetch(serf_request_t *request, 956 report_fetch_t *fetch_ctx, 957 svn_error_t *err) 958{ 959 fetch_ctx->done = TRUE; 960 961 fetch_ctx->done_item.data = fetch_ctx; 962 fetch_ctx->done_item.next = *fetch_ctx->done_list; 963 *fetch_ctx->done_list = &fetch_ctx->done_item; 964 965 /* Discard the rest of this request 966 (This makes sure it doesn't error when the request is aborted later) */ 967 serf_request_set_handler(request, 968 svn_ra_serf__response_discard_handler, NULL); 969 970 /* Some errors would be handled by serf; make sure they really make 971 the update fail by wrapping it in a different error. */ 972 if (!SERF_BUCKET_READ_ERROR(err->apr_err)) 973 return svn_error_create(SVN_ERR_RA_SERF_WRAPPED_ERROR, err, NULL); 974 975 return err; 976} 977 978/* Wield the editor referenced by INFO to open (or add) the file 979 file also associated with INFO, setting properties on the file and 980 calling the editor's apply_textdelta() function on it if necessary 981 (or if FORCE_APPLY_TEXTDELTA is set). 982 983 Callers will probably want to also see the function that serves 984 the opposite purpose of this one, close_updated_file(). */ 985static svn_error_t * 986open_updated_file(report_info_t *info, 987 svn_boolean_t force_apply_textdelta, 988 apr_pool_t *scratch_pool) 989{ 990 report_context_t *ctx = info->dir->report_context; 991 const svn_delta_editor_t *update_editor = ctx->update_editor; 992 993 /* Ensure our parent is open. */ 994 SVN_ERR(ensure_dir_opened(info->dir)); 995 info->editor_pool = svn_pool_create(info->dir->dir_baton_pool); 996 997 /* Expand our full name now if we haven't done so yet. */ 998 if (!info->name) 999 { 1000 info->name = svn_relpath_join(info->dir->name, info->base_name, 1001 info->editor_pool); 1002 } 1003 1004 /* Open (or add) the file. */ 1005 if (SVN_IS_VALID_REVNUM(info->base_rev)) 1006 { 1007 SVN_ERR(update_editor->open_file(info->name, 1008 info->dir->dir_baton, 1009 info->base_rev, 1010 info->editor_pool, 1011 &info->file_baton)); 1012 } 1013 else 1014 { 1015 SVN_ERR(update_editor->add_file(info->name, 1016 info->dir->dir_baton, 1017 info->copyfrom_path, 1018 info->copyfrom_rev, 1019 info->editor_pool, 1020 &info->file_baton)); 1021 } 1022 1023 /* Check for lock information. */ 1024 if (info->lock_token) 1025 check_lock(info); 1026 1027 /* Get (maybe) a textdelta window handler for transmitting file 1028 content changes. */ 1029 if (info->fetch_file || force_apply_textdelta) 1030 { 1031 SVN_ERR(update_editor->apply_textdelta(info->file_baton, 1032 info->base_checksum, 1033 info->editor_pool, 1034 &info->textdelta, 1035 &info->textdelta_baton)); 1036 } 1037 1038 return SVN_NO_ERROR; 1039} 1040 1041/* Close the file associated with INFO->file_baton, and cleanup other 1042 bits of that structure managed by open_updated_file(). */ 1043static svn_error_t * 1044close_updated_file(report_info_t *info, 1045 apr_pool_t *scratch_pool) 1046{ 1047 report_context_t *ctx = info->dir->report_context; 1048 1049 /* Set all of the properties we received */ 1050 SVN_ERR(svn_ra_serf__walk_all_props(info->props, 1051 info->base_name, 1052 info->base_rev, 1053 set_file_props, info, 1054 scratch_pool)); 1055 SVN_ERR(svn_ra_serf__walk_all_props(info->dir->removed_props, 1056 info->base_name, 1057 info->base_rev, 1058 remove_file_props, info, 1059 scratch_pool)); 1060 if (info->fetch_props) 1061 { 1062 SVN_ERR(svn_ra_serf__walk_all_props(info->props, 1063 info->url, 1064 ctx->target_rev, 1065 set_file_props, info, 1066 scratch_pool)); 1067 } 1068 1069 /* Close the file via the editor. */ 1070 SVN_ERR(info->dir->report_context->update_editor->close_file( 1071 info->file_baton, info->final_checksum, scratch_pool)); 1072 1073 /* We're done with our editor pool. */ 1074 svn_pool_destroy(info->editor_pool); 1075 1076 return SVN_NO_ERROR; 1077} 1078 1079/* Implements svn_ra_serf__response_handler_t */ 1080static svn_error_t * 1081handle_fetch(serf_request_t *request, 1082 serf_bucket_t *response, 1083 void *handler_baton, 1084 apr_pool_t *pool) 1085{ 1086 const char *data; 1087 apr_size_t len; 1088 apr_status_t status; 1089 report_fetch_t *fetch_ctx = handler_baton; 1090 svn_error_t *err; 1091 1092 /* ### new field. make sure we didn't miss some initialization. */ 1093 SVN_ERR_ASSERT(fetch_ctx->handler != NULL); 1094 1095 if (!fetch_ctx->read_headers) 1096 { 1097 serf_bucket_t *hdrs; 1098 const char *val; 1099 report_info_t *info; 1100 1101 hdrs = serf_bucket_response_get_headers(response); 1102 val = serf_bucket_headers_get(hdrs, "Content-Type"); 1103 info = fetch_ctx->info; 1104 1105 if (val && svn_cstring_casecmp(val, SVN_SVNDIFF_MIME_TYPE) == 0) 1106 { 1107 fetch_ctx->delta_stream = 1108 svn_txdelta_parse_svndiff(info->textdelta, 1109 info->textdelta_baton, 1110 TRUE, info->editor_pool); 1111 1112 /* Validate the delta base claimed by the server matches 1113 what we asked for! */ 1114 val = serf_bucket_headers_get(hdrs, SVN_DAV_DELTA_BASE_HEADER); 1115 if (val && (strcmp(val, info->delta_base) != 0)) 1116 { 1117 err = svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL, 1118 _("GET request returned unexpected " 1119 "delta base: %s"), val); 1120 return error_fetch(request, fetch_ctx, err); 1121 } 1122 } 1123 else 1124 { 1125 fetch_ctx->delta_stream = NULL; 1126 } 1127 1128 fetch_ctx->read_headers = TRUE; 1129 } 1130 1131 /* If the error code wasn't 200, something went wrong. Don't use the returned 1132 data as its probably an error message. Just bail out instead. */ 1133 if (fetch_ctx->handler->sline.code != 200) 1134 { 1135 err = svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL, 1136 _("GET request failed: %d %s"), 1137 fetch_ctx->handler->sline.code, 1138 fetch_ctx->handler->sline.reason); 1139 return error_fetch(request, fetch_ctx, err); 1140 } 1141 1142 while (1) 1143 { 1144 svn_txdelta_window_t delta_window = { 0 }; 1145 svn_txdelta_op_t delta_op; 1146 svn_string_t window_data; 1147 1148 status = serf_bucket_read(response, 8000, &data, &len); 1149 if (SERF_BUCKET_READ_ERROR(status)) 1150 { 1151 return svn_ra_serf__wrap_err(status, NULL); 1152 } 1153 1154 fetch_ctx->read_size += len; 1155 1156 if (fetch_ctx->aborted_read) 1157 { 1158 apr_off_t skip; 1159 /* We haven't caught up to where we were before. */ 1160 if (fetch_ctx->read_size < fetch_ctx->aborted_read_size) 1161 { 1162 /* Eek. What did the file shrink or something? */ 1163 if (APR_STATUS_IS_EOF(status)) 1164 { 1165 SVN_ERR_MALFUNCTION(); 1166 } 1167 1168 /* Skip on to the next iteration of this loop. */ 1169 if (APR_STATUS_IS_EAGAIN(status)) 1170 { 1171 return svn_ra_serf__wrap_err(status, NULL); 1172 } 1173 continue; 1174 } 1175 1176 /* Woo-hoo. We're back. */ 1177 fetch_ctx->aborted_read = FALSE; 1178 1179 /* Update data and len to just provide the new data. */ 1180 skip = len - (fetch_ctx->read_size - fetch_ctx->aborted_read_size); 1181 data += skip; 1182 len -= skip; 1183 } 1184 1185 if (fetch_ctx->delta_stream) 1186 { 1187 err = svn_stream_write(fetch_ctx->delta_stream, data, &len); 1188 if (err) 1189 { 1190 return error_fetch(request, fetch_ctx, err); 1191 } 1192 } 1193 /* otherwise, manually construct the text delta window. */ 1194 else if (len) 1195 { 1196 window_data.data = data; 1197 window_data.len = len; 1198 1199 delta_op.action_code = svn_txdelta_new; 1200 delta_op.offset = 0; 1201 delta_op.length = len; 1202 1203 delta_window.tview_len = len; 1204 delta_window.num_ops = 1; 1205 delta_window.ops = &delta_op; 1206 delta_window.new_data = &window_data; 1207 1208 /* write to the file located in the info. */ 1209 err = fetch_ctx->info->textdelta(&delta_window, 1210 fetch_ctx->info->textdelta_baton); 1211 if (err) 1212 { 1213 return error_fetch(request, fetch_ctx, err); 1214 } 1215 } 1216 1217 if (APR_STATUS_IS_EOF(status)) 1218 { 1219 report_info_t *info = fetch_ctx->info; 1220 1221 if (fetch_ctx->delta_stream) 1222 err = svn_error_trace(svn_stream_close(fetch_ctx->delta_stream)); 1223 else 1224 err = svn_error_trace(info->textdelta(NULL, 1225 info->textdelta_baton)); 1226 if (err) 1227 { 1228 return error_fetch(request, fetch_ctx, err); 1229 } 1230 1231 err = close_updated_file(info, info->pool); 1232 if (err) 1233 { 1234 return svn_error_trace(error_fetch(request, fetch_ctx, err)); 1235 } 1236 1237 fetch_ctx->done = TRUE; 1238 1239 fetch_ctx->done_item.data = fetch_ctx; 1240 fetch_ctx->done_item.next = *fetch_ctx->done_list; 1241 *fetch_ctx->done_list = &fetch_ctx->done_item; 1242 1243 /* We're done with our pool. */ 1244 svn_pool_destroy(info->pool); 1245 1246 if (status) 1247 return svn_ra_serf__wrap_err(status, NULL); 1248 } 1249 if (APR_STATUS_IS_EAGAIN(status)) 1250 { 1251 return svn_ra_serf__wrap_err(status, NULL); 1252 } 1253 } 1254 /* not reached */ 1255} 1256 1257/* Implements svn_ra_serf__response_handler_t */ 1258static svn_error_t * 1259handle_stream(serf_request_t *request, 1260 serf_bucket_t *response, 1261 void *handler_baton, 1262 apr_pool_t *pool) 1263{ 1264 report_fetch_t *fetch_ctx = handler_baton; 1265 svn_error_t *err; 1266 apr_status_t status; 1267 1268 /* ### new field. make sure we didn't miss some initialization. */ 1269 SVN_ERR_ASSERT(fetch_ctx->handler != NULL); 1270 1271 err = svn_ra_serf__error_on_status(fetch_ctx->handler->sline, 1272 fetch_ctx->info->name, 1273 fetch_ctx->handler->location); 1274 if (err) 1275 { 1276 fetch_ctx->handler->done = TRUE; 1277 1278 err = svn_error_compose_create( 1279 err, 1280 svn_ra_serf__handle_discard_body(request, response, NULL, pool)); 1281 1282 return svn_error_trace(err); 1283 } 1284 1285 while (1) 1286 { 1287 const char *data; 1288 apr_size_t len; 1289 1290 status = serf_bucket_read(response, 8000, &data, &len); 1291 if (SERF_BUCKET_READ_ERROR(status)) 1292 { 1293 return svn_ra_serf__wrap_err(status, NULL); 1294 } 1295 1296 fetch_ctx->read_size += len; 1297 1298 if (fetch_ctx->aborted_read) 1299 { 1300 /* We haven't caught up to where we were before. */ 1301 if (fetch_ctx->read_size < fetch_ctx->aborted_read_size) 1302 { 1303 /* Eek. What did the file shrink or something? */ 1304 if (APR_STATUS_IS_EOF(status)) 1305 { 1306 SVN_ERR_MALFUNCTION(); 1307 } 1308 1309 /* Skip on to the next iteration of this loop. */ 1310 if (APR_STATUS_IS_EAGAIN(status)) 1311 { 1312 return svn_ra_serf__wrap_err(status, NULL); 1313 } 1314 continue; 1315 } 1316 1317 /* Woo-hoo. We're back. */ 1318 fetch_ctx->aborted_read = FALSE; 1319 1320 /* Increment data and len by the difference. */ 1321 data += fetch_ctx->read_size - fetch_ctx->aborted_read_size; 1322 len += fetch_ctx->read_size - fetch_ctx->aborted_read_size; 1323 } 1324 1325 if (len) 1326 { 1327 apr_size_t written_len; 1328 1329 written_len = len; 1330 1331 SVN_ERR(svn_stream_write(fetch_ctx->target_stream, data, 1332 &written_len)); 1333 } 1334 1335 if (APR_STATUS_IS_EOF(status)) 1336 { 1337 fetch_ctx->done = TRUE; 1338 } 1339 1340 if (status) 1341 { 1342 return svn_ra_serf__wrap_err(status, NULL); 1343 } 1344 } 1345 /* not reached */ 1346} 1347 1348/* Close the directory represented by DIR -- and any suitable parents 1349 thereof -- if we are able to do so. This is the case whenever: 1350 1351 - there are no remaining open items within the directory, and 1352 - the directory's XML close tag has been processed (so we know 1353 there are no more children to worry about in the future), and 1354 - either: 1355 - we aren't fetching properties for this directory, or 1356 - we've already finished fetching those properties. 1357*/ 1358static svn_error_t * 1359maybe_close_dir_chain(report_dir_t *dir) 1360{ 1361 report_dir_t *cur_dir = dir; 1362 1363 SVN_ERR(ensure_dir_opened(cur_dir)); 1364 1365 while (cur_dir 1366 && !cur_dir->ref_count 1367 && cur_dir->tag_closed 1368 && (!cur_dir->fetch_props || cur_dir->propfind_handler->done)) 1369 { 1370 report_dir_t *parent = cur_dir->parent_dir; 1371 report_context_t *report_context = cur_dir->report_context; 1372 svn_boolean_t propfind_in_done_list = FALSE; 1373 svn_ra_serf__list_t *done_list; 1374 1375 /* Make sure there are no references to this dir in the 1376 active_dir_propfinds list. If there are, don't close the 1377 directory -- which would delete the pool from which the 1378 relevant active_dir_propfinds list item is allocated -- and 1379 of course don't crawl upward to check the parents for 1380 a closure opportunity, either. */ 1381 done_list = report_context->active_dir_propfinds; 1382 while (done_list) 1383 { 1384 if (done_list->data == cur_dir) 1385 { 1386 propfind_in_done_list = TRUE; 1387 break; 1388 } 1389 done_list = done_list->next; 1390 } 1391 if (propfind_in_done_list) 1392 break; 1393 1394 SVN_ERR(close_dir(cur_dir)); 1395 if (parent) 1396 { 1397 parent->ref_count--; 1398 } 1399 else 1400 { 1401 report_context->closed_root = TRUE; 1402 } 1403 cur_dir = parent; 1404 } 1405 1406 return SVN_NO_ERROR; 1407} 1408 1409/* Open the file associated with INFO for editing, pass along any 1410 propchanges we've recorded for it, and then close the file. */ 1411static svn_error_t * 1412handle_propchange_only(report_info_t *info, 1413 apr_pool_t *scratch_pool) 1414{ 1415 SVN_ERR(open_updated_file(info, FALSE, scratch_pool)); 1416 SVN_ERR(close_updated_file(info, scratch_pool)); 1417 1418 /* We're done with our pool. */ 1419 svn_pool_destroy(info->pool); 1420 1421 info->dir->ref_count--; 1422 1423 /* See if the parent directory of this file (and perhaps even 1424 parents of that) can be closed now. */ 1425 SVN_ERR(maybe_close_dir_chain(info->dir)); 1426 1427 return SVN_NO_ERROR; 1428} 1429 1430/* "Fetch" a file whose contents were made available via the 1431 get_wc_contents() callback (as opposed to requiring a GET to the 1432 server), and feed the information through the associated update 1433 editor. In editor-speak, this will add/open the file, transmit any 1434 property changes, handle the contents, and then close the file. */ 1435static svn_error_t * 1436handle_local_content(report_info_t *info, 1437 apr_pool_t *scratch_pool) 1438{ 1439 SVN_ERR(svn_txdelta_send_stream(info->cached_contents, info->textdelta, 1440 info->textdelta_baton, NULL, scratch_pool)); 1441 SVN_ERR(svn_stream_close(info->cached_contents)); 1442 info->cached_contents = NULL; 1443 SVN_ERR(close_updated_file(info, scratch_pool)); 1444 1445 /* We're done with our pool. */ 1446 svn_pool_destroy(info->pool); 1447 1448 info->dir->ref_count--; 1449 1450 /* See if the parent directory of this fetched item (and 1451 perhaps even parents of that) can be closed now. */ 1452 SVN_ERR(maybe_close_dir_chain(info->dir)); 1453 1454 return SVN_NO_ERROR; 1455} 1456 1457/* --------------------------------------------------------- */ 1458 1459static svn_error_t * 1460fetch_file(report_context_t *ctx, report_info_t *info) 1461{ 1462 svn_ra_serf__connection_t *conn; 1463 svn_ra_serf__handler_t *handler; 1464 1465 /* What connection should we go on? */ 1466 conn = get_best_connection(ctx); 1467 1468 /* If needed, create the PROPFIND to retrieve the file's properties. */ 1469 info->propfind_handler = NULL; 1470 if (info->fetch_props) 1471 { 1472 SVN_ERR(svn_ra_serf__deliver_props(&info->propfind_handler, info->props, 1473 ctx->sess, conn, info->url, 1474 ctx->target_rev, "0", all_props, 1475 &ctx->done_propfinds, 1476 info->dir->pool)); 1477 SVN_ERR_ASSERT(info->propfind_handler); 1478 1479 /* Create a serf request for the PROPFIND. */ 1480 svn_ra_serf__request_create(info->propfind_handler); 1481 1482 ctx->num_active_propfinds++; 1483 } 1484 1485 /* If we've been asked to fetch the file or it's an add, do so. 1486 * Otherwise, handle the case where only the properties changed. 1487 */ 1488 if (info->fetch_file && ctx->text_deltas) 1489 { 1490 svn_stream_t *contents = NULL; 1491 1492 /* Open the file for editing. */ 1493 SVN_ERR(open_updated_file(info, FALSE, info->pool)); 1494 1495 if (info->textdelta == svn_delta_noop_window_handler) 1496 { 1497 /* There is nobody looking for an actual stream. 1498 1499 Just report an empty stream instead of fetching 1500 to be ingored data */ 1501 info->cached_contents = svn_stream_empty(info->pool); 1502 } 1503 else if (ctx->sess->wc_callbacks->get_wc_contents 1504 && info->final_sha1_checksum) 1505 { 1506 svn_error_t *err = NULL; 1507 svn_checksum_t *checksum = NULL; 1508 1509 /* Parse the optional SHA1 checksum (1.7+) */ 1510 err = svn_checksum_parse_hex(&checksum, svn_checksum_sha1, 1511 info->final_sha1_checksum, 1512 info->pool); 1513 1514 /* Okay so far? Let's try to get a stream on some readily 1515 available matching content. */ 1516 if (!err && checksum) 1517 { 1518 err = ctx->sess->wc_callbacks->get_wc_contents( 1519 ctx->sess->wc_callback_baton, &contents, 1520 checksum, info->pool); 1521 1522 if (! err) 1523 info->cached_contents = contents; 1524 } 1525 1526 if (err) 1527 { 1528 /* Meh. Maybe we'll care one day why we're in an 1529 errorful state, but this codepath is optional. */ 1530 svn_error_clear(err); 1531 } 1532 } 1533 1534 /* If the working copy can provide cached contents for this 1535 file, we don't have to fetch them from the server. */ 1536 if (info->cached_contents) 1537 { 1538 /* If we'll be doing a PROPFIND for this file... */ 1539 if (info->propfind_handler) 1540 { 1541 /* ... then we'll just leave ourselves a little "todo" 1542 about that fact (and we'll deal with the file content 1543 stuff later, after we've handled that PROPFIND 1544 response. */ 1545 svn_ra_serf__list_t *list_item; 1546 1547 list_item = apr_pcalloc(info->dir->pool, sizeof(*list_item)); 1548 list_item->data = info; 1549 list_item->next = ctx->file_propchanges_only; 1550 ctx->file_propchanges_only = list_item; 1551 } 1552 else 1553 { 1554 /* Otherwise, if we've no PROPFIND to do, we might as 1555 well take care of those locally accessible file 1556 contents now. */ 1557 SVN_ERR(handle_local_content(info, info->pool)); 1558 } 1559 } 1560 else 1561 { 1562 /* Otherwise, we use a GET request for the file's contents. */ 1563 report_fetch_t *fetch_ctx; 1564 1565 fetch_ctx = apr_pcalloc(info->dir->pool, sizeof(*fetch_ctx)); 1566 fetch_ctx->info = info; 1567 fetch_ctx->done_list = &ctx->done_fetches; 1568 fetch_ctx->sess = ctx->sess; 1569 fetch_ctx->conn = conn; 1570 1571 handler = apr_pcalloc(info->dir->pool, sizeof(*handler)); 1572 1573 handler->handler_pool = info->dir->pool; 1574 handler->method = "GET"; 1575 handler->path = fetch_ctx->info->url; 1576 1577 handler->conn = conn; 1578 handler->session = ctx->sess; 1579 1580 handler->custom_accept_encoding = TRUE; 1581 handler->header_delegate = headers_fetch; 1582 handler->header_delegate_baton = fetch_ctx; 1583 1584 handler->response_handler = handle_fetch; 1585 handler->response_baton = fetch_ctx; 1586 1587 handler->response_error = cancel_fetch; 1588 handler->response_error_baton = fetch_ctx; 1589 1590 fetch_ctx->handler = handler; 1591 1592 svn_ra_serf__request_create(handler); 1593 1594 ctx->num_active_fetches++; 1595 } 1596 } 1597 else if (info->propfind_handler) 1598 { 1599 svn_ra_serf__list_t *list_item; 1600 1601 list_item = apr_pcalloc(info->dir->pool, sizeof(*list_item)); 1602 list_item->data = info; 1603 list_item->next = ctx->file_propchanges_only; 1604 ctx->file_propchanges_only = list_item; 1605 } 1606 else 1607 { 1608 /* No propfind or GET request. Just handle the prop changes now. */ 1609 SVN_ERR(handle_propchange_only(info, info->pool)); 1610 } 1611 1612 if (ctx->num_active_fetches + ctx->num_active_propfinds 1613 > REQUEST_COUNT_TO_PAUSE) 1614 ctx->parser_ctx->paused = TRUE; 1615 1616 return SVN_NO_ERROR; 1617} 1618 1619 1620/** XML callbacks for our update-report response parsing */ 1621 1622static svn_error_t * 1623start_report(svn_ra_serf__xml_parser_t *parser, 1624 svn_ra_serf__dav_props_t name, 1625 const char **attrs, 1626 apr_pool_t *scratch_pool) 1627{ 1628 report_context_t *ctx = parser->user_data; 1629 report_state_e state; 1630 1631 state = parser->state->current_state; 1632 1633 if (state == NONE && strcmp(name.name, "update-report") == 0) 1634 { 1635 const char *val; 1636 1637 val = svn_xml_get_attr_value("inline-props", attrs); 1638 if (val && (strcmp(val, "true") == 0)) 1639 ctx->add_props_included = TRUE; 1640 1641 val = svn_xml_get_attr_value("send-all", attrs); 1642 if (val && (strcmp(val, "true") == 0)) 1643 { 1644 ctx->send_all_mode = TRUE; 1645 1646 /* All properties are included in send-all mode. */ 1647 ctx->add_props_included = TRUE; 1648 } 1649 } 1650 else if (state == NONE && strcmp(name.name, "target-revision") == 0) 1651 { 1652 const char *rev; 1653 1654 rev = svn_xml_get_attr_value("rev", attrs); 1655 1656 if (!rev) 1657 { 1658 return svn_error_create( 1659 SVN_ERR_RA_DAV_MALFORMED_DATA, NULL, 1660 _("Missing revision attr in target-revision element")); 1661 } 1662 1663 SVN_ERR(ctx->update_editor->set_target_revision(ctx->update_baton, 1664 SVN_STR_TO_REV(rev), 1665 ctx->sess->pool)); 1666 } 1667 else if (state == NONE && strcmp(name.name, "open-directory") == 0) 1668 { 1669 const char *rev; 1670 report_info_t *info; 1671 1672 rev = svn_xml_get_attr_value("rev", attrs); 1673 1674 if (!rev) 1675 { 1676 return svn_error_create( 1677 SVN_ERR_RA_DAV_MALFORMED_DATA, NULL, 1678 _("Missing revision attr in open-directory element")); 1679 } 1680 1681 info = push_state(parser, ctx, OPEN_DIR); 1682 1683 info->base_rev = SVN_STR_TO_REV(rev); 1684 info->dir->base_rev = info->base_rev; 1685 info->fetch_props = TRUE; 1686 1687 info->dir->base_name = ""; 1688 info->dir->name = ""; 1689 1690 info->base_name = info->dir->base_name; 1691 info->name = info->dir->name; 1692 1693 info->dir->repos_relpath = svn_hash_gets(ctx->switched_paths, ""); 1694 1695 if (!info->dir->repos_relpath) 1696 SVN_ERR(svn_ra_serf__get_relative_path(&info->dir->repos_relpath, 1697 ctx->sess->session_url.path, 1698 ctx->sess, ctx->conn, 1699 info->dir->pool)); 1700 } 1701 else if (state == NONE) 1702 { 1703 /* do nothing as we haven't seen our valid start tag yet. */ 1704 } 1705 else if ((state == OPEN_DIR || state == ADD_DIR) && 1706 strcmp(name.name, "open-directory") == 0) 1707 { 1708 const char *rev, *dirname; 1709 report_dir_t *dir; 1710 report_info_t *info; 1711 1712 rev = svn_xml_get_attr_value("rev", attrs); 1713 1714 if (!rev) 1715 { 1716 return svn_error_create( 1717 SVN_ERR_RA_DAV_MALFORMED_DATA, NULL, 1718 _("Missing revision attr in open-directory element")); 1719 } 1720 1721 dirname = svn_xml_get_attr_value("name", attrs); 1722 1723 if (!dirname) 1724 { 1725 return svn_error_create( 1726 SVN_ERR_RA_DAV_MALFORMED_DATA, NULL, 1727 _("Missing name attr in open-directory element")); 1728 } 1729 1730 info = push_state(parser, ctx, OPEN_DIR); 1731 1732 dir = info->dir; 1733 1734 info->base_rev = SVN_STR_TO_REV(rev); 1735 dir->base_rev = info->base_rev; 1736 1737 info->fetch_props = FALSE; 1738 1739 dir->base_name = apr_pstrdup(dir->pool, dirname); 1740 info->base_name = dir->base_name; 1741 1742 /* Expand our name. */ 1743 dir->name = svn_relpath_join(dir->parent_dir->name, dir->base_name, 1744 dir->pool); 1745 info->name = dir->name; 1746 1747 dir->repos_relpath = svn_hash_gets(ctx->switched_paths, dir->name); 1748 1749 if (!dir->repos_relpath) 1750 dir->repos_relpath = svn_relpath_join(dir->parent_dir->repos_relpath, 1751 dir->base_name, dir->pool); 1752 } 1753 else if ((state == OPEN_DIR || state == ADD_DIR) && 1754 strcmp(name.name, "add-directory") == 0) 1755 { 1756 const char *dir_name, *cf, *cr; 1757 report_dir_t *dir; 1758 report_info_t *info; 1759 1760 dir_name = svn_xml_get_attr_value("name", attrs); 1761 if (!dir_name) 1762 { 1763 return svn_error_create( 1764 SVN_ERR_RA_DAV_MALFORMED_DATA, NULL, 1765 _("Missing name attr in add-directory element")); 1766 } 1767 cf = svn_xml_get_attr_value("copyfrom-path", attrs); 1768 cr = svn_xml_get_attr_value("copyfrom-rev", attrs); 1769 1770 info = push_state(parser, ctx, ADD_DIR); 1771 1772 dir = info->dir; 1773 1774 dir->base_name = apr_pstrdup(dir->pool, dir_name); 1775 info->base_name = dir->base_name; 1776 1777 /* Expand our name. */ 1778 dir->name = svn_relpath_join(dir->parent_dir->name, dir->base_name, 1779 dir->pool); 1780 info->name = dir->name; 1781 1782 info->copyfrom_path = cf ? apr_pstrdup(info->pool, cf) : NULL; 1783 info->copyfrom_rev = cr ? SVN_STR_TO_REV(cr) : SVN_INVALID_REVNUM; 1784 1785 /* Mark that we don't have a base. */ 1786 info->base_rev = SVN_INVALID_REVNUM; 1787 dir->base_rev = info->base_rev; 1788 1789 /* If the server isn't included properties for added items, 1790 we'll need to fetch them ourselves. */ 1791 if (! ctx->add_props_included) 1792 dir->fetch_props = TRUE; 1793 1794 dir->repos_relpath = svn_relpath_join(dir->parent_dir->repos_relpath, 1795 dir->base_name, dir->pool); 1796 } 1797 else if ((state == OPEN_DIR || state == ADD_DIR) && 1798 strcmp(name.name, "open-file") == 0) 1799 { 1800 const char *file_name, *rev; 1801 report_info_t *info; 1802 1803 file_name = svn_xml_get_attr_value("name", attrs); 1804 1805 if (!file_name) 1806 { 1807 return svn_error_create( 1808 SVN_ERR_RA_DAV_MALFORMED_DATA, NULL, 1809 _("Missing name attr in open-file element")); 1810 } 1811 1812 rev = svn_xml_get_attr_value("rev", attrs); 1813 1814 if (!rev) 1815 { 1816 return svn_error_create( 1817 SVN_ERR_RA_DAV_MALFORMED_DATA, NULL, 1818 _("Missing revision attr in open-file element")); 1819 } 1820 1821 info = push_state(parser, ctx, OPEN_FILE); 1822 1823 info->base_rev = SVN_STR_TO_REV(rev); 1824 info->fetch_props = FALSE; 1825 1826 info->base_name = apr_pstrdup(info->pool, file_name); 1827 info->name = NULL; 1828 } 1829 else if ((state == OPEN_DIR || state == ADD_DIR) && 1830 strcmp(name.name, "add-file") == 0) 1831 { 1832 const char *file_name, *cf, *cr; 1833 report_info_t *info; 1834 1835 file_name = svn_xml_get_attr_value("name", attrs); 1836 cf = svn_xml_get_attr_value("copyfrom-path", attrs); 1837 cr = svn_xml_get_attr_value("copyfrom-rev", attrs); 1838 1839 if (!file_name) 1840 { 1841 return svn_error_create( 1842 SVN_ERR_RA_DAV_MALFORMED_DATA, NULL, 1843 _("Missing name attr in add-file element")); 1844 } 1845 1846 info = push_state(parser, ctx, ADD_FILE); 1847 1848 info->base_rev = SVN_INVALID_REVNUM; 1849 1850 /* If the server isn't in "send-all" mode, we should expect to 1851 fetch contents for added files. */ 1852 if (! ctx->send_all_mode) 1853 info->fetch_file = TRUE; 1854 1855 /* If the server isn't included properties for added items, 1856 we'll need to fetch them ourselves. */ 1857 if (! ctx->add_props_included) 1858 info->fetch_props = TRUE; 1859 1860 info->base_name = apr_pstrdup(info->pool, file_name); 1861 info->name = NULL; 1862 1863 info->copyfrom_path = cf ? apr_pstrdup(info->pool, cf) : NULL; 1864 info->copyfrom_rev = cr ? SVN_STR_TO_REV(cr) : SVN_INVALID_REVNUM; 1865 1866 info->final_sha1_checksum = 1867 svn_xml_get_attr_value("sha1-checksum", attrs); 1868 if (info->final_sha1_checksum) 1869 info->final_sha1_checksum = apr_pstrdup(info->pool, 1870 info->final_sha1_checksum); 1871 } 1872 else if ((state == OPEN_DIR || state == ADD_DIR) && 1873 strcmp(name.name, "delete-entry") == 0) 1874 { 1875 const char *file_name; 1876 const char *rev_str; 1877 report_info_t *info; 1878 apr_pool_t *tmppool; 1879 const char *full_path; 1880 svn_revnum_t delete_rev = SVN_INVALID_REVNUM; 1881 1882 file_name = svn_xml_get_attr_value("name", attrs); 1883 1884 if (!file_name) 1885 { 1886 return svn_error_create( 1887 SVN_ERR_RA_DAV_MALFORMED_DATA, NULL, 1888 _("Missing name attr in delete-entry element")); 1889 } 1890 1891 rev_str = svn_xml_get_attr_value("rev", attrs); 1892 if (rev_str) /* Not available on older repositories! */ 1893 delete_rev = SVN_STR_TO_REV(rev_str); 1894 1895 info = parser->state->private; 1896 1897 SVN_ERR(ensure_dir_opened(info->dir)); 1898 1899 tmppool = svn_pool_create(info->dir->dir_baton_pool); 1900 1901 full_path = svn_relpath_join(info->dir->name, file_name, tmppool); 1902 1903 SVN_ERR(ctx->update_editor->delete_entry(full_path, 1904 delete_rev, 1905 info->dir->dir_baton, 1906 tmppool)); 1907 1908 svn_pool_destroy(tmppool); 1909 } 1910 else if ((state == OPEN_DIR || state == ADD_DIR) && 1911 strcmp(name.name, "absent-directory") == 0) 1912 { 1913 const char *file_name; 1914 report_info_t *info; 1915 1916 file_name = svn_xml_get_attr_value("name", attrs); 1917 1918 if (!file_name) 1919 { 1920 return svn_error_create( 1921 SVN_ERR_RA_DAV_MALFORMED_DATA, NULL, 1922 _("Missing name attr in absent-directory element")); 1923 } 1924 1925 info = parser->state->private; 1926 1927 SVN_ERR(ensure_dir_opened(info->dir)); 1928 1929 SVN_ERR(ctx->update_editor->absent_directory( 1930 svn_relpath_join(info->name, file_name, 1931 info->dir->pool), 1932 info->dir->dir_baton, 1933 info->dir->pool)); 1934 } 1935 else if ((state == OPEN_DIR || state == ADD_DIR) && 1936 strcmp(name.name, "absent-file") == 0) 1937 { 1938 const char *file_name; 1939 report_info_t *info; 1940 1941 file_name = svn_xml_get_attr_value("name", attrs); 1942 1943 if (!file_name) 1944 { 1945 return svn_error_create( 1946 SVN_ERR_RA_DAV_MALFORMED_DATA, NULL, 1947 _("Missing name attr in absent-file element")); 1948 } 1949 1950 info = parser->state->private; 1951 1952 SVN_ERR(ensure_dir_opened(info->dir)); 1953 1954 SVN_ERR(ctx->update_editor->absent_file( 1955 svn_relpath_join(info->name, file_name, 1956 info->dir->pool), 1957 info->dir->dir_baton, 1958 info->dir->pool)); 1959 } 1960 else if (state == OPEN_DIR || state == ADD_DIR) 1961 { 1962 report_info_t *info; 1963 1964 if (strcmp(name.name, "checked-in") == 0) 1965 { 1966 info = push_state(parser, ctx, IGNORE_PROP_NAME); 1967 info->prop_ns = name.namespace; 1968 info->prop_name = apr_pstrdup(parser->state->pool, name.name); 1969 info->prop_encoding = NULL; 1970 svn_stringbuf_setempty(info->prop_value); 1971 } 1972 else if (strcmp(name.name, "set-prop") == 0 || 1973 strcmp(name.name, "remove-prop") == 0) 1974 { 1975 const char *full_prop_name; 1976 const char *colon; 1977 1978 info = push_state(parser, ctx, PROP); 1979 1980 full_prop_name = svn_xml_get_attr_value("name", attrs); 1981 if (!full_prop_name) 1982 { 1983 return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL, 1984 _("Missing name attr in %s element"), 1985 name.name); 1986 } 1987 1988 colon = strchr(full_prop_name, ':'); 1989 1990 if (colon) 1991 colon++; 1992 else 1993 colon = full_prop_name; 1994 1995 info->prop_ns = apr_pstrmemdup(info->dir->pool, full_prop_name, 1996 colon - full_prop_name); 1997 info->prop_name = apr_pstrdup(parser->state->pool, colon); 1998 info->prop_encoding = svn_xml_get_attr_value("encoding", attrs); 1999 svn_stringbuf_setempty(info->prop_value); 2000 } 2001 else if (strcmp(name.name, "prop") == 0) 2002 { 2003 /* need to fetch it. */ 2004 push_state(parser, ctx, NEED_PROP_NAME); 2005 } 2006 else if (strcmp(name.name, "fetch-props") == 0) 2007 { 2008 info = parser->state->private; 2009 2010 info->dir->fetch_props = TRUE; 2011 } 2012 else 2013 { 2014 return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL, 2015 _("Unknown tag '%s' while at state %d"), 2016 name.name, state); 2017 } 2018 2019 } 2020 else if (state == OPEN_FILE || state == ADD_FILE) 2021 { 2022 report_info_t *info; 2023 2024 if (strcmp(name.name, "checked-in") == 0) 2025 { 2026 info = push_state(parser, ctx, IGNORE_PROP_NAME); 2027 info->prop_ns = name.namespace; 2028 info->prop_name = apr_pstrdup(parser->state->pool, name.name); 2029 info->prop_encoding = NULL; 2030 svn_stringbuf_setempty(info->prop_value); 2031 } 2032 else if (strcmp(name.name, "prop") == 0) 2033 { 2034 /* need to fetch it. */ 2035 push_state(parser, ctx, NEED_PROP_NAME); 2036 } 2037 else if (strcmp(name.name, "fetch-props") == 0) 2038 { 2039 info = parser->state->private; 2040 2041 info->fetch_props = TRUE; 2042 } 2043 else if (strcmp(name.name, "fetch-file") == 0) 2044 { 2045 info = parser->state->private; 2046 info->base_checksum = svn_xml_get_attr_value("base-checksum", attrs); 2047 2048 if (info->base_checksum) 2049 info->base_checksum = apr_pstrdup(info->pool, info->base_checksum); 2050 2051 info->final_sha1_checksum = 2052 svn_xml_get_attr_value("sha1-checksum", attrs); 2053 if (info->final_sha1_checksum) 2054 info->final_sha1_checksum = apr_pstrdup(info->pool, 2055 info->final_sha1_checksum); 2056 2057 info->fetch_file = TRUE; 2058 } 2059 else if (strcmp(name.name, "set-prop") == 0 || 2060 strcmp(name.name, "remove-prop") == 0) 2061 { 2062 const char *full_prop_name; 2063 const char *colon; 2064 2065 info = push_state(parser, ctx, PROP); 2066 2067 full_prop_name = svn_xml_get_attr_value("name", attrs); 2068 if (!full_prop_name) 2069 { 2070 return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL, 2071 _("Missing name attr in %s element"), 2072 name.name); 2073 } 2074 colon = strchr(full_prop_name, ':'); 2075 2076 if (colon) 2077 colon++; 2078 else 2079 colon = full_prop_name; 2080 2081 info->prop_ns = apr_pstrmemdup(info->dir->pool, full_prop_name, 2082 colon - full_prop_name); 2083 info->prop_name = apr_pstrdup(parser->state->pool, colon); 2084 info->prop_encoding = svn_xml_get_attr_value("encoding", attrs); 2085 svn_stringbuf_setempty(info->prop_value); 2086 } 2087 else if (strcmp(name.name, "txdelta") == 0) 2088 { 2089 /* Pre 1.2, mod_dav_svn was using <txdelta> tags (in 2090 addition to <fetch-file>s and such) when *not* in 2091 "send-all" mode. As a client, we're smart enough to know 2092 that's wrong, so we'll just ignore these tags. */ 2093 if (ctx->send_all_mode) 2094 { 2095 const svn_delta_editor_t *update_editor = ctx->update_editor; 2096 2097 info = push_state(parser, ctx, TXDELTA); 2098 2099 if (! info->file_baton) 2100 { 2101 SVN_ERR(open_updated_file(info, FALSE, info->pool)); 2102 } 2103 2104 info->base_checksum = svn_xml_get_attr_value("base-checksum", 2105 attrs); 2106 SVN_ERR(update_editor->apply_textdelta(info->file_baton, 2107 info->base_checksum, 2108 info->editor_pool, 2109 &info->textdelta, 2110 &info->textdelta_baton)); 2111 info->svndiff_decoder = svn_txdelta_parse_svndiff( 2112 info->textdelta, 2113 info->textdelta_baton, 2114 TRUE, info->pool); 2115 info->base64_decoder = svn_base64_decode(info->svndiff_decoder, 2116 info->pool); 2117 } 2118 } 2119 else 2120 { 2121 return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL, 2122 _("Unknown tag '%s' while at state %d"), 2123 name.name, state); 2124 } 2125 } 2126 else if (state == IGNORE_PROP_NAME) 2127 { 2128 report_info_t *info = push_state(parser, ctx, PROP); 2129 info->prop_encoding = svn_xml_get_attr_value("encoding", attrs); 2130 } 2131 else if (state == NEED_PROP_NAME) 2132 { 2133 report_info_t *info; 2134 2135 info = push_state(parser, ctx, PROP); 2136 2137 info->prop_ns = name.namespace; 2138 info->prop_name = apr_pstrdup(parser->state->pool, name.name); 2139 info->prop_encoding = svn_xml_get_attr_value("encoding", attrs); 2140 svn_stringbuf_setempty(info->prop_value); 2141 } 2142 2143 return SVN_NO_ERROR; 2144} 2145 2146static svn_error_t * 2147end_report(svn_ra_serf__xml_parser_t *parser, 2148 svn_ra_serf__dav_props_t name, 2149 apr_pool_t *scratch_pool) 2150{ 2151 report_context_t *ctx = parser->user_data; 2152 report_state_e state; 2153 2154 state = parser->state->current_state; 2155 2156 if (state == NONE) 2157 { 2158 if (strcmp(name.name, "update-report") == 0) 2159 { 2160 ctx->report_completed = TRUE; 2161 } 2162 else 2163 { 2164 /* nothing to close yet. */ 2165 return SVN_NO_ERROR; 2166 } 2167 } 2168 2169 if (((state == OPEN_DIR && (strcmp(name.name, "open-directory") == 0)) || 2170 (state == ADD_DIR && (strcmp(name.name, "add-directory") == 0)))) 2171 { 2172 const char *checked_in_url; 2173 report_info_t *info = parser->state->private; 2174 2175 /* We've now closed this directory; note it. */ 2176 info->dir->tag_closed = TRUE; 2177 2178 /* go fetch info->file_name from DAV:checked-in */ 2179 checked_in_url = 2180 svn_ra_serf__get_ver_prop(info->dir->props, info->base_name, 2181 info->base_rev, "DAV:", "checked-in"); 2182 2183 /* If we were expecting to have the properties and we aren't able to 2184 * get it, bail. 2185 */ 2186 if (!checked_in_url && 2187 (!SVN_IS_VALID_REVNUM(info->dir->base_rev) || info->dir->fetch_props)) 2188 { 2189 return svn_error_create(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL, 2190 _("The REPORT or PROPFIND response did not " 2191 "include the requested checked-in value")); 2192 } 2193 2194 info->dir->url = checked_in_url; 2195 2196 /* At this point, we should have the checked-in href. 2197 * If needed, create the PROPFIND to retrieve the dir's properties. 2198 */ 2199 if (info->dir->fetch_props) 2200 { 2201 svn_ra_serf__list_t *list_item; 2202 2203 SVN_ERR(svn_ra_serf__deliver_props(&info->dir->propfind_handler, 2204 info->dir->props, ctx->sess, 2205 get_best_connection(ctx), 2206 info->dir->url, 2207 ctx->target_rev, "0", 2208 all_props, 2209 &ctx->done_dir_propfinds, 2210 info->dir->pool)); 2211 SVN_ERR_ASSERT(info->dir->propfind_handler); 2212 2213 /* Create a serf request for the PROPFIND. */ 2214 svn_ra_serf__request_create(info->dir->propfind_handler); 2215 2216 ctx->num_active_propfinds++; 2217 2218 list_item = apr_pcalloc(info->dir->pool, sizeof(*list_item)); 2219 list_item->data = info->dir; 2220 list_item->next = ctx->active_dir_propfinds; 2221 ctx->active_dir_propfinds = list_item; 2222 2223 if (ctx->num_active_fetches + ctx->num_active_propfinds 2224 > REQUEST_COUNT_TO_PAUSE) 2225 ctx->parser_ctx->paused = TRUE; 2226 } 2227 else 2228 { 2229 info->dir->propfind_handler = NULL; 2230 } 2231 2232 /* See if this directory (and perhaps even parents of that) can 2233 be closed now. This is likely to be the case only if we 2234 didn't need to contact the server for supplemental 2235 information required to handle any of this directory's 2236 children. */ 2237 SVN_ERR(maybe_close_dir_chain(info->dir)); 2238 svn_ra_serf__xml_pop_state(parser); 2239 } 2240 else if (state == OPEN_FILE && strcmp(name.name, "open-file") == 0) 2241 { 2242 report_info_t *info = parser->state->private; 2243 2244 /* Expand our full name now if we haven't done so yet. */ 2245 if (!info->name) 2246 { 2247 info->name = svn_relpath_join(info->dir->name, info->base_name, 2248 info->pool); 2249 } 2250 2251 info->lock_token = svn_hash_gets(ctx->lock_path_tokens, info->name); 2252 2253 if (info->lock_token && !info->fetch_props) 2254 info->fetch_props = TRUE; 2255 2256 /* If possible, we'd like to fetch only a delta against a 2257 * version of the file we already have in our working copy, 2258 * rather than fetching a fulltext. 2259 * 2260 * In HTTP v2, we can simply construct the URL we need given the 2261 * repos_relpath and base revision number. 2262 */ 2263 if (SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(ctx->sess)) 2264 { 2265 const char *repos_relpath; 2266 2267 /* If this file is switched vs the editor root we should provide 2268 its real url instead of the one calculated from the session root. 2269 */ 2270 repos_relpath = svn_hash_gets(ctx->switched_paths, info->name); 2271 2272 if (!repos_relpath) 2273 { 2274 if (ctx->root_is_switched) 2275 { 2276 /* We are updating a direct target (most likely a file) 2277 that is switched vs its parent url */ 2278 SVN_ERR_ASSERT(*svn_relpath_dirname(info->name, info->pool) 2279 == '\0'); 2280 2281 repos_relpath = svn_hash_gets(ctx->switched_paths, ""); 2282 } 2283 else 2284 repos_relpath = svn_relpath_join(info->dir->repos_relpath, 2285 info->base_name, info->pool); 2286 } 2287 2288 info->delta_base = apr_psprintf(info->pool, "%s/%ld/%s", 2289 ctx->sess->rev_root_stub, 2290 info->base_rev, 2291 svn_path_uri_encode(repos_relpath, 2292 info->pool)); 2293 } 2294 else if (ctx->sess->wc_callbacks->get_wc_prop) 2295 { 2296 /* If we have a WC, we might be able to dive all the way into the WC 2297 * to get the previous URL so we can do a differential GET with the 2298 * base URL. 2299 */ 2300 const svn_string_t *value = NULL; 2301 SVN_ERR(ctx->sess->wc_callbacks->get_wc_prop( 2302 ctx->sess->wc_callback_baton, info->name, 2303 SVN_RA_SERF__WC_CHECKED_IN_URL, &value, info->pool)); 2304 2305 info->delta_base = value ? value->data : NULL; 2306 } 2307 2308 /* go fetch info->name from DAV:checked-in */ 2309 info->url = svn_ra_serf__get_ver_prop(info->props, info->base_name, 2310 info->base_rev, "DAV:", "checked-in"); 2311 if (!info->url) 2312 { 2313 return svn_error_create(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL, 2314 _("The REPORT or PROPFIND response did not " 2315 "include the requested checked-in value")); 2316 } 2317 2318 /* If the server is in "send-all" mode, we might have opened the 2319 file when we started seeing content for it. If we didn't get 2320 any content for it, we still need to open the file. But in 2321 any case, we can then immediately close it. */ 2322 if (ctx->send_all_mode) 2323 { 2324 if (! info->file_baton) 2325 { 2326 SVN_ERR(open_updated_file(info, FALSE, info->pool)); 2327 } 2328 SVN_ERR(close_updated_file(info, info->pool)); 2329 info->dir->ref_count--; 2330 } 2331 /* Otherwise, if the server is *not* in "send-all" mode, we 2332 should be at a point where we can queue up any auxiliary 2333 content-fetching requests. */ 2334 else 2335 { 2336 SVN_ERR(fetch_file(ctx, info)); 2337 } 2338 2339 svn_ra_serf__xml_pop_state(parser); 2340 } 2341 else if (state == ADD_FILE && strcmp(name.name, "add-file") == 0) 2342 { 2343 report_info_t *info = parser->state->private; 2344 2345 /* go fetch info->name from DAV:checked-in */ 2346 info->url = svn_ra_serf__get_ver_prop(info->props, info->base_name, 2347 info->base_rev, "DAV:", "checked-in"); 2348 if (!info->url) 2349 { 2350 return svn_error_create(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL, 2351 _("The REPORT or PROPFIND response did not " 2352 "include the requested checked-in value")); 2353 } 2354 2355 /* If the server is in "send-all" mode, we might have opened the 2356 file when we started seeing content for it. If we didn't get 2357 any content for it, we still need to open the file. But in 2358 any case, we can then immediately close it. */ 2359 if (ctx->send_all_mode) 2360 { 2361 if (! info->file_baton) 2362 { 2363 SVN_ERR(open_updated_file(info, FALSE, info->pool)); 2364 } 2365 SVN_ERR(close_updated_file(info, info->pool)); 2366 info->dir->ref_count--; 2367 } 2368 /* Otherwise, if the server is *not* in "send-all" mode, we 2369 should be at a point where we can queue up any auxiliary 2370 content-fetching requests. */ 2371 else 2372 { 2373 SVN_ERR(fetch_file(ctx, info)); 2374 } 2375 2376 svn_ra_serf__xml_pop_state(parser); 2377 } 2378 else if (state == TXDELTA && strcmp(name.name, "txdelta") == 0) 2379 { 2380 report_info_t *info = parser->state->private; 2381 2382 /* Pre 1.2, mod_dav_svn was using <txdelta> tags (in addition to 2383 <fetch-file>s and such) when *not* in "send-all" mode. As a 2384 client, we're smart enough to know that's wrong, so when not 2385 in "receiving-all" mode, we'll ignore these tags. */ 2386 if (ctx->send_all_mode) 2387 { 2388 SVN_ERR(svn_stream_close(info->base64_decoder)); 2389 } 2390 2391 svn_ra_serf__xml_pop_state(parser); 2392 } 2393 else if (state == PROP) 2394 { 2395 /* We need to move the prop_ns, prop_name, and prop_value into the 2396 * same lifetime as the dir->pool. 2397 */ 2398 svn_ra_serf__ns_t *ns, *ns_name_match; 2399 svn_boolean_t found = FALSE; 2400 report_info_t *info; 2401 report_dir_t *dir; 2402 apr_hash_t *props; 2403 const svn_string_t *set_val_str; 2404 apr_pool_t *pool; 2405 2406 info = parser->state->private; 2407 dir = info->dir; 2408 2409 /* We're going to be slightly tricky. We don't care what the ->url 2410 * field is here at this point. So, we're going to stick a single 2411 * copy of the property name inside of the ->url field. 2412 */ 2413 ns_name_match = NULL; 2414 for (ns = dir->ns_list; ns; ns = ns->next) 2415 { 2416 if (strcmp(ns->namespace, info->prop_ns) == 0) 2417 { 2418 ns_name_match = ns; 2419 if (strcmp(ns->url, info->prop_name) == 0) 2420 { 2421 found = TRUE; 2422 break; 2423 } 2424 } 2425 } 2426 2427 if (!found) 2428 { 2429 ns = apr_palloc(dir->pool, sizeof(*ns)); 2430 if (!ns_name_match) 2431 { 2432 ns->namespace = apr_pstrdup(dir->pool, info->prop_ns); 2433 } 2434 else 2435 { 2436 ns->namespace = ns_name_match->namespace; 2437 } 2438 ns->url = apr_pstrdup(dir->pool, info->prop_name); 2439 2440 ns->next = dir->ns_list; 2441 dir->ns_list = ns; 2442 } 2443 2444 if (strcmp(name.name, "remove-prop") != 0) 2445 { 2446 props = info->props; 2447 pool = info->pool; 2448 } 2449 else 2450 { 2451 props = dir->removed_props; 2452 pool = dir->pool; 2453 svn_stringbuf_setempty(info->prop_value); 2454 } 2455 2456 if (info->prop_encoding) 2457 { 2458 if (strcmp(info->prop_encoding, "base64") == 0) 2459 { 2460 svn_string_t tmp; 2461 2462 /* Don't use morph_info_string cuz we need prop_value to 2463 remain usable. */ 2464 tmp.data = info->prop_value->data; 2465 tmp.len = info->prop_value->len; 2466 2467 set_val_str = svn_base64_decode_string(&tmp, pool); 2468 } 2469 else 2470 { 2471 return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, 2472 NULL, 2473 _("Got unrecognized encoding '%s'"), 2474 info->prop_encoding); 2475 } 2476 } 2477 else 2478 { 2479 set_val_str = svn_string_create_from_buf(info->prop_value, pool); 2480 } 2481 2482 svn_ra_serf__set_ver_prop(props, info->base_name, info->base_rev, 2483 ns->namespace, ns->url, set_val_str, pool); 2484 2485 /* Advance handling: if we spotted the md5-checksum property on 2486 the wire, remember it's value. */ 2487 if (strcmp(ns->url, "md5-checksum") == 0 2488 && strcmp(ns->namespace, SVN_DAV_PROP_NS_DAV) == 0) 2489 info->final_checksum = apr_pstrdup(info->pool, set_val_str->data); 2490 2491 svn_ra_serf__xml_pop_state(parser); 2492 } 2493 else if (state == IGNORE_PROP_NAME || state == NEED_PROP_NAME) 2494 { 2495 svn_ra_serf__xml_pop_state(parser); 2496 } 2497 2498 return SVN_NO_ERROR; 2499} 2500 2501static svn_error_t * 2502cdata_report(svn_ra_serf__xml_parser_t *parser, 2503 const char *data, 2504 apr_size_t len, 2505 apr_pool_t *scratch_pool) 2506{ 2507 report_context_t *ctx = parser->user_data; 2508 2509 UNUSED_CTX(ctx); 2510 2511 if (parser->state->current_state == PROP) 2512 { 2513 report_info_t *info = parser->state->private; 2514 2515 svn_stringbuf_appendbytes(info->prop_value, data, len); 2516 } 2517 else if (parser->state->current_state == TXDELTA) 2518 { 2519 /* Pre 1.2, mod_dav_svn was using <txdelta> tags (in addition to 2520 <fetch-file>s and such) when *not* in "send-all" mode. As a 2521 client, we're smart enough to know that's wrong, so when not 2522 in "receiving-all" mode, we'll ignore these tags. */ 2523 if (ctx->send_all_mode) 2524 { 2525 apr_size_t nlen = len; 2526 report_info_t *info = parser->state->private; 2527 2528 SVN_ERR(svn_stream_write(info->base64_decoder, data, &nlen)); 2529 if (nlen != len) 2530 { 2531 /* Short write without associated error? "Can't happen." */ 2532 return svn_error_createf(SVN_ERR_STREAM_UNEXPECTED_EOF, NULL, 2533 _("Error writing to '%s': unexpected EOF"), 2534 info->name); 2535 } 2536 } 2537 } 2538 2539 return SVN_NO_ERROR; 2540} 2541 2542 2543/** Editor callbacks given to callers to create request body */ 2544 2545/* Helper to create simple xml tag without attributes. */ 2546static void 2547make_simple_xml_tag(svn_stringbuf_t **buf_p, 2548 const char *tagname, 2549 const char *cdata, 2550 apr_pool_t *pool) 2551{ 2552 svn_xml_make_open_tag(buf_p, pool, svn_xml_protect_pcdata, tagname, NULL); 2553 svn_xml_escape_cdata_cstring(buf_p, cdata, pool); 2554 svn_xml_make_close_tag(buf_p, pool, tagname); 2555} 2556 2557static svn_error_t * 2558set_path(void *report_baton, 2559 const char *path, 2560 svn_revnum_t revision, 2561 svn_depth_t depth, 2562 svn_boolean_t start_empty, 2563 const char *lock_token, 2564 apr_pool_t *pool) 2565{ 2566 report_context_t *report = report_baton; 2567 svn_stringbuf_t *buf = NULL; 2568 2569 svn_xml_make_open_tag(&buf, pool, svn_xml_protect_pcdata, "S:entry", 2570 "rev", apr_ltoa(pool, revision), 2571 "lock-token", lock_token, 2572 "depth", svn_depth_to_word(depth), 2573 "start-empty", start_empty ? "true" : NULL, 2574 NULL); 2575 svn_xml_escape_cdata_cstring(&buf, path, pool); 2576 svn_xml_make_close_tag(&buf, pool, "S:entry"); 2577 2578 SVN_ERR(svn_io_file_write_full(report->body_file, buf->data, buf->len, 2579 NULL, pool)); 2580 2581 if (lock_token) 2582 { 2583 svn_hash_sets(report->lock_path_tokens, 2584 apr_pstrdup(report->pool, path), 2585 apr_pstrdup(report->pool, lock_token)); 2586 } 2587 2588 return SVN_NO_ERROR; 2589} 2590 2591static svn_error_t * 2592delete_path(void *report_baton, 2593 const char *path, 2594 apr_pool_t *pool) 2595{ 2596 report_context_t *report = report_baton; 2597 svn_stringbuf_t *buf = NULL; 2598 2599 make_simple_xml_tag(&buf, "S:missing", path, pool); 2600 2601 SVN_ERR(svn_io_file_write_full(report->body_file, buf->data, buf->len, 2602 NULL, pool)); 2603 2604 return SVN_NO_ERROR; 2605} 2606 2607static svn_error_t * 2608link_path(void *report_baton, 2609 const char *path, 2610 const char *url, 2611 svn_revnum_t revision, 2612 svn_depth_t depth, 2613 svn_boolean_t start_empty, 2614 const char *lock_token, 2615 apr_pool_t *pool) 2616{ 2617 report_context_t *report = report_baton; 2618 const char *link, *report_target; 2619 apr_uri_t uri; 2620 apr_status_t status; 2621 svn_stringbuf_t *buf = NULL; 2622 2623 /* We need to pass in the baseline relative path. 2624 * 2625 * TODO Confirm that it's on the same server? 2626 */ 2627 status = apr_uri_parse(pool, url, &uri); 2628 if (status) 2629 { 2630 return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL, 2631 _("Unable to parse URL '%s'"), url); 2632 } 2633 2634 SVN_ERR(svn_ra_serf__report_resource(&report_target, report->sess, 2635 NULL, pool)); 2636 SVN_ERR(svn_ra_serf__get_relative_path(&link, uri.path, report->sess, 2637 NULL, pool)); 2638 2639 link = apr_pstrcat(pool, "/", link, (char *)NULL); 2640 2641 svn_xml_make_open_tag(&buf, pool, svn_xml_protect_pcdata, "S:entry", 2642 "rev", apr_ltoa(pool, revision), 2643 "lock-token", lock_token, 2644 "depth", svn_depth_to_word(depth), 2645 "linkpath", link, 2646 "start-empty", start_empty ? "true" : NULL, 2647 NULL); 2648 svn_xml_escape_cdata_cstring(&buf, path, pool); 2649 svn_xml_make_close_tag(&buf, pool, "S:entry"); 2650 2651 SVN_ERR(svn_io_file_write_full(report->body_file, buf->data, buf->len, 2652 NULL, pool)); 2653 2654 /* Store the switch roots to allow generating repos_relpaths from just 2655 the working copy paths. (Needed for HTTPv2) */ 2656 path = apr_pstrdup(report->pool, path); 2657 svn_hash_sets(report->switched_paths, 2658 path, apr_pstrdup(report->pool, link + 1)); 2659 2660 if (!*path) 2661 report->root_is_switched = TRUE; 2662 2663 if (lock_token) 2664 { 2665 svn_hash_sets(report->lock_path_tokens, 2666 path, apr_pstrdup(report->pool, lock_token)); 2667 } 2668 2669 return APR_SUCCESS; 2670} 2671 2672/** Minimum nr. of outstanding requests needed before a new connection is 2673 * opened. */ 2674#define REQS_PER_CONN 8 2675 2676/** This function creates a new connection for this serf session, but only 2677 * if the number of NUM_ACTIVE_REQS > REQS_PER_CONN or if there currently is 2678 * only one main connection open. 2679 */ 2680static svn_error_t * 2681open_connection_if_needed(svn_ra_serf__session_t *sess, int num_active_reqs) 2682{ 2683 /* For each REQS_PER_CONN outstanding requests open a new connection, with 2684 * a minimum of 1 extra connection. */ 2685 if (sess->num_conns == 1 || 2686 ((num_active_reqs / REQS_PER_CONN) > sess->num_conns)) 2687 { 2688 int cur = sess->num_conns; 2689 apr_status_t status; 2690 2691 sess->conns[cur] = apr_pcalloc(sess->pool, sizeof(*sess->conns[cur])); 2692 sess->conns[cur]->bkt_alloc = serf_bucket_allocator_create(sess->pool, 2693 NULL, NULL); 2694 sess->conns[cur]->last_status_code = -1; 2695 sess->conns[cur]->session = sess; 2696 status = serf_connection_create2(&sess->conns[cur]->conn, 2697 sess->context, 2698 sess->session_url, 2699 svn_ra_serf__conn_setup, 2700 sess->conns[cur], 2701 svn_ra_serf__conn_closed, 2702 sess->conns[cur], 2703 sess->pool); 2704 if (status) 2705 return svn_ra_serf__wrap_err(status, NULL); 2706 2707 sess->num_conns++; 2708 } 2709 2710 return SVN_NO_ERROR; 2711} 2712 2713/* Serf callback to create update request body bucket. */ 2714static svn_error_t * 2715create_update_report_body(serf_bucket_t **body_bkt, 2716 void *baton, 2717 serf_bucket_alloc_t *alloc, 2718 apr_pool_t *pool) 2719{ 2720 report_context_t *report = baton; 2721 apr_off_t offset; 2722 2723 offset = 0; 2724 apr_file_seek(report->body_file, APR_SET, &offset); 2725 2726 *body_bkt = serf_bucket_file_create(report->body_file, alloc); 2727 2728 return SVN_NO_ERROR; 2729} 2730 2731/* Serf callback to setup update request headers. */ 2732static svn_error_t * 2733setup_update_report_headers(serf_bucket_t *headers, 2734 void *baton, 2735 apr_pool_t *pool) 2736{ 2737 report_context_t *report = baton; 2738 2739 if (report->sess->using_compression) 2740 { 2741 serf_bucket_headers_setn(headers, "Accept-Encoding", 2742 "gzip,svndiff1;q=0.9,svndiff;q=0.8"); 2743 } 2744 else 2745 { 2746 serf_bucket_headers_setn(headers, "Accept-Encoding", 2747 "svndiff1;q=0.9,svndiff;q=0.8"); 2748 } 2749 2750 return SVN_NO_ERROR; 2751} 2752 2753static svn_error_t * 2754finish_report(void *report_baton, 2755 apr_pool_t *pool) 2756{ 2757 report_context_t *report = report_baton; 2758 svn_ra_serf__session_t *sess = report->sess; 2759 svn_ra_serf__handler_t *handler; 2760 svn_ra_serf__xml_parser_t *parser_ctx; 2761 const char *report_target; 2762 svn_stringbuf_t *buf = NULL; 2763 apr_pool_t *iterpool = svn_pool_create(pool); 2764 svn_error_t *err; 2765 apr_interval_time_t waittime_left = sess->timeout; 2766 2767 svn_xml_make_close_tag(&buf, iterpool, "S:update-report"); 2768 SVN_ERR(svn_io_file_write_full(report->body_file, buf->data, buf->len, 2769 NULL, iterpool)); 2770 2771 /* We need to flush the file, make it unbuffered (so that it can be 2772 * zero-copied via mmap), and reset the position before attempting to 2773 * deliver the file. 2774 * 2775 * N.B. If we have APR 1.3+, we can unbuffer the file to let us use mmap 2776 * and zero-copy the PUT body. However, on older APR versions, we can't 2777 * check the buffer status; but serf will fall through and create a file 2778 * bucket for us on the buffered svndiff handle. 2779 */ 2780 apr_file_flush(report->body_file); 2781#if APR_VERSION_AT_LEAST(1, 3, 0) 2782 apr_file_buffer_set(report->body_file, NULL, 0); 2783#endif 2784 2785 SVN_ERR(svn_ra_serf__report_resource(&report_target, sess, NULL, pool)); 2786 2787 /* create and deliver request */ 2788 report->path = report_target; 2789 2790 handler = apr_pcalloc(pool, sizeof(*handler)); 2791 2792 handler->handler_pool = pool; 2793 handler->method = "REPORT"; 2794 handler->path = report->path; 2795 handler->body_delegate = create_update_report_body; 2796 handler->body_delegate_baton = report; 2797 handler->body_type = "text/xml"; 2798 handler->custom_accept_encoding = TRUE; 2799 handler->header_delegate = setup_update_report_headers; 2800 handler->header_delegate_baton = report; 2801 handler->conn = sess->conns[0]; 2802 handler->session = sess; 2803 2804 parser_ctx = apr_pcalloc(pool, sizeof(*parser_ctx)); 2805 2806 parser_ctx->pool = pool; 2807 parser_ctx->response_type = "update-report"; 2808 parser_ctx->user_data = report; 2809 parser_ctx->start = start_report; 2810 parser_ctx->end = end_report; 2811 parser_ctx->cdata = cdata_report; 2812 parser_ctx->done = &report->done; 2813 2814 handler->response_handler = svn_ra_serf__handle_xml_parser; 2815 handler->response_baton = parser_ctx; 2816 2817 report->parser_ctx = parser_ctx; 2818 2819 svn_ra_serf__request_create(handler); 2820 2821 /* Open the first extra connection. */ 2822 SVN_ERR(open_connection_if_needed(sess, 0)); 2823 2824 sess->cur_conn = 1; 2825 2826 /* Note that we may have no active GET or PROPFIND requests, yet the 2827 processing has not been completed. This could be from a delay on the 2828 network or because we've spooled the entire response into our "pending" 2829 content of the XML parser. The DONE flag will get set when all the 2830 XML content has been received *and* parsed. */ 2831 while (!report->done 2832 || report->num_active_fetches 2833 || report->num_active_propfinds) 2834 { 2835 apr_pool_t *iterpool_inner; 2836 svn_ra_serf__list_t *done_list; 2837 int i; 2838 apr_status_t status; 2839 2840 /* Note: this throws out the old ITERPOOL_INNER. */ 2841 svn_pool_clear(iterpool); 2842 2843 if (sess->cancel_func) 2844 SVN_ERR(sess->cancel_func(sess->cancel_baton)); 2845 2846 /* We need to be careful between the outer and inner ITERPOOLs, 2847 and what items are allocated within. */ 2848 iterpool_inner = svn_pool_create(iterpool); 2849 2850 status = serf_context_run(sess->context, 2851 SVN_RA_SERF__CONTEXT_RUN_DURATION, 2852 iterpool_inner); 2853 2854 err = sess->pending_error; 2855 sess->pending_error = SVN_NO_ERROR; 2856 2857 if (!err && handler->done && handler->server_error) 2858 { 2859 err = handler->server_error->error; 2860 } 2861 2862 /* If the context duration timeout is up, we'll subtract that 2863 duration from the total time alloted for such things. If 2864 there's no time left, we fail with a message indicating that 2865 the connection timed out. */ 2866 if (APR_STATUS_IS_TIMEUP(status)) 2867 { 2868 svn_error_clear(err); 2869 err = SVN_NO_ERROR; 2870 status = 0; 2871 2872 if (sess->timeout) 2873 { 2874 if (waittime_left > SVN_RA_SERF__CONTEXT_RUN_DURATION) 2875 { 2876 waittime_left -= SVN_RA_SERF__CONTEXT_RUN_DURATION; 2877 } 2878 else 2879 { 2880 return svn_error_create(SVN_ERR_RA_DAV_CONN_TIMEOUT, NULL, 2881 _("Connection timed out")); 2882 } 2883 } 2884 } 2885 else 2886 { 2887 waittime_left = sess->timeout; 2888 } 2889 2890 if (status && handler->sline.code != 200) 2891 { 2892 return svn_error_trace( 2893 svn_error_compose_create( 2894 svn_ra_serf__error_on_status(handler->sline, 2895 handler->path, 2896 handler->location), 2897 err)); 2898 } 2899 SVN_ERR(err); 2900 if (status) 2901 { 2902 return svn_ra_serf__wrap_err(status, _("Error retrieving REPORT")); 2903 } 2904 2905 /* Open extra connections if we have enough requests to send. */ 2906 if (sess->num_conns < sess->max_connections) 2907 SVN_ERR(open_connection_if_needed(sess, report->num_active_fetches + 2908 report->num_active_propfinds)); 2909 2910 /* Prune completed file PROPFINDs. */ 2911 done_list = report->done_propfinds; 2912 while (done_list) 2913 { 2914 svn_ra_serf__list_t *next_done = done_list->next; 2915 2916 svn_pool_clear(iterpool_inner); 2917 2918 report->num_active_propfinds--; 2919 2920 /* If we have some files that we won't be fetching the content 2921 * for, ensure that we update the file with any altered props. 2922 */ 2923 if (report->file_propchanges_only) 2924 { 2925 svn_ra_serf__list_t *cur, *prev; 2926 2927 prev = NULL; 2928 cur = report->file_propchanges_only; 2929 2930 while (cur) 2931 { 2932 report_info_t *item = cur->data; 2933 2934 if (item->propfind_handler == done_list->data) 2935 { 2936 break; 2937 } 2938 2939 prev = cur; 2940 cur = cur->next; 2941 } 2942 2943 /* If we found a match, set the new props and remove this 2944 * propchange from our list. 2945 */ 2946 if (cur) 2947 { 2948 report_info_t *info = cur->data; 2949 2950 if (!prev) 2951 { 2952 report->file_propchanges_only = cur->next; 2953 } 2954 else 2955 { 2956 prev->next = cur->next; 2957 } 2958 2959 /* If we've got cached file content for this file, 2960 take care of the locally collected properties and 2961 file content at once. Otherwise, just deal with 2962 the collected properties. 2963 2964 NOTE: These functions below could delete 2965 info->dir->pool (via maybe_close_dir_chain()), 2966 from which is allocated the list item in 2967 report->file_propchanges_only. 2968 */ 2969 if (info->cached_contents) 2970 { 2971 SVN_ERR(handle_local_content(info, iterpool_inner)); 2972 } 2973 else 2974 { 2975 SVN_ERR(handle_propchange_only(info, iterpool_inner)); 2976 } 2977 } 2978 } 2979 2980 done_list = next_done; 2981 } 2982 report->done_propfinds = NULL; 2983 2984 /* Prune completed fetches from our list. */ 2985 done_list = report->done_fetches; 2986 while (done_list) 2987 { 2988 report_fetch_t *done_fetch = done_list->data; 2989 svn_ra_serf__list_t *next_done = done_list->next; 2990 report_dir_t *cur_dir; 2991 2992 /* Decrease the refcount in the parent directory of the file 2993 whose fetch has completed. */ 2994 cur_dir = done_fetch->info->dir; 2995 cur_dir->ref_count--; 2996 2997 /* Decrement our active fetch count. */ 2998 report->num_active_fetches--; 2999 3000 /* See if the parent directory of this fetched item (and 3001 perhaps even parents of that) can be closed now. 3002 3003 NOTE: This could delete cur_dir->pool, from which is 3004 allocated the list item in report->done_fetches. 3005 */ 3006 SVN_ERR(maybe_close_dir_chain(cur_dir)); 3007 3008 done_list = next_done; 3009 } 3010 report->done_fetches = NULL; 3011 3012 /* Prune completed directory PROPFINDs. */ 3013 done_list = report->done_dir_propfinds; 3014 while (done_list) 3015 { 3016 svn_ra_serf__list_t *next_done = done_list->next; 3017 3018 report->num_active_propfinds--; 3019 3020 if (report->active_dir_propfinds) 3021 { 3022 svn_ra_serf__list_t *cur, *prev; 3023 3024 prev = NULL; 3025 cur = report->active_dir_propfinds; 3026 3027 while (cur) 3028 { 3029 report_dir_t *item = cur->data; 3030 3031 if (item->propfind_handler == done_list->data) 3032 { 3033 break; 3034 } 3035 3036 prev = cur; 3037 cur = cur->next; 3038 } 3039 SVN_ERR_ASSERT(cur); /* we expect to find a matching propfind! */ 3040 3041 /* If we found a match, set the new props and remove this 3042 * propchange from our list. 3043 */ 3044 if (cur) 3045 { 3046 report_dir_t *cur_dir = cur->data; 3047 3048 if (!prev) 3049 { 3050 report->active_dir_propfinds = cur->next; 3051 } 3052 else 3053 { 3054 prev->next = cur->next; 3055 } 3056 3057 /* See if this directory (and perhaps even parents of that) 3058 can be closed now. 3059 3060 NOTE: This could delete cur_dir->pool, from which is 3061 allocated the list item in report->active_dir_propfinds. 3062 */ 3063 SVN_ERR(maybe_close_dir_chain(cur_dir)); 3064 } 3065 } 3066 3067 done_list = next_done; 3068 } 3069 report->done_dir_propfinds = NULL; 3070 3071 /* If the parser is paused, and the number of active requests has 3072 dropped far enough, then resume parsing. */ 3073 if (parser_ctx->paused 3074 && (report->num_active_fetches + report->num_active_propfinds 3075 < REQUEST_COUNT_TO_RESUME)) 3076 parser_ctx->paused = FALSE; 3077 3078 /* If we have not paused the parser and it looks like data MAY be 3079 present (we can't know for sure because of the private structure), 3080 then go process the pending content. */ 3081 if (!parser_ctx->paused && parser_ctx->pending != NULL) 3082 SVN_ERR(svn_ra_serf__process_pending(parser_ctx, 3083 &report->report_received, 3084 iterpool_inner)); 3085 3086 /* Debugging purposes only! */ 3087 for (i = 0; i < sess->num_conns; i++) 3088 { 3089 serf_debug__closed_conn(sess->conns[i]->bkt_alloc); 3090 } 3091 } 3092 3093 /* If we got a complete report, close the edit. Otherwise, abort it. */ 3094 if (report->report_completed) 3095 { 3096 /* Ensure that we opened and closed our root dir and that we closed 3097 * all of our children. */ 3098 if (!report->closed_root && report->root_dir != NULL) 3099 { 3100 SVN_ERR(close_all_dirs(report->root_dir)); 3101 } 3102 3103 err = report->update_editor->close_edit(report->update_baton, iterpool); 3104 } 3105 else 3106 { 3107 /* Tell the editor that something failed */ 3108 err = report->update_editor->abort_edit(report->update_baton, iterpool); 3109 3110 err = svn_error_create(SVN_ERR_RA_DAV_MALFORMED_DATA, err, 3111 _("Missing update-report close tag")); 3112 } 3113 3114 svn_pool_destroy(iterpool); 3115 return svn_error_trace(err); 3116} 3117 3118 3119static svn_error_t * 3120abort_report(void *report_baton, 3121 apr_pool_t *pool) 3122{ 3123#if 0 3124 report_context_t *report = report_baton; 3125#endif 3126 3127 /* Should we perform some cleanup here? */ 3128 3129 return SVN_NO_ERROR; 3130} 3131 3132static const svn_ra_reporter3_t ra_serf_reporter = { 3133 set_path, 3134 delete_path, 3135 link_path, 3136 finish_report, 3137 abort_report 3138}; 3139 3140 3141/** RA function implementations and body */ 3142 3143static svn_error_t * 3144make_update_reporter(svn_ra_session_t *ra_session, 3145 const svn_ra_reporter3_t **reporter, 3146 void **report_baton, 3147 svn_revnum_t revision, 3148 const char *src_path, 3149 const char *dest_path, 3150 const char *update_target, 3151 svn_depth_t depth, 3152 svn_boolean_t ignore_ancestry, 3153 svn_boolean_t text_deltas, 3154 svn_boolean_t send_copyfrom_args, 3155 const svn_delta_editor_t *update_editor, 3156 void *update_baton, 3157 apr_pool_t *result_pool, 3158 apr_pool_t *scratch_pool) 3159{ 3160 report_context_t *report; 3161 const svn_delta_editor_t *filter_editor; 3162 void *filter_baton; 3163 svn_boolean_t has_target = *update_target != '\0'; 3164 svn_boolean_t server_supports_depth; 3165 svn_ra_serf__session_t *sess = ra_session->priv; 3166 svn_stringbuf_t *buf = NULL; 3167 svn_boolean_t use_bulk_updates; 3168 3169 SVN_ERR(svn_ra_serf__has_capability(ra_session, &server_supports_depth, 3170 SVN_RA_CAPABILITY_DEPTH, scratch_pool)); 3171 /* We can skip the depth filtering when the user requested 3172 depth_files or depth_infinity because the server will 3173 transmit the right stuff anyway. */ 3174 if ((depth != svn_depth_files) 3175 && (depth != svn_depth_infinity) 3176 && ! server_supports_depth) 3177 { 3178 SVN_ERR(svn_delta_depth_filter_editor(&filter_editor, 3179 &filter_baton, 3180 update_editor, 3181 update_baton, 3182 depth, has_target, 3183 sess->pool)); 3184 update_editor = filter_editor; 3185 update_baton = filter_baton; 3186 } 3187 3188 report = apr_pcalloc(result_pool, sizeof(*report)); 3189 report->pool = result_pool; 3190 report->sess = sess; 3191 report->conn = report->sess->conns[0]; 3192 report->target_rev = revision; 3193 report->ignore_ancestry = ignore_ancestry; 3194 report->send_copyfrom_args = send_copyfrom_args; 3195 report->text_deltas = text_deltas; 3196 report->lock_path_tokens = apr_hash_make(report->pool); 3197 report->switched_paths = apr_hash_make(report->pool); 3198 3199 report->source = src_path; 3200 report->destination = dest_path; 3201 report->update_target = update_target; 3202 3203 report->update_editor = update_editor; 3204 report->update_baton = update_baton; 3205 report->done = FALSE; 3206 3207 *reporter = &ra_serf_reporter; 3208 *report_baton = report; 3209 3210 SVN_ERR(svn_io_open_unique_file3(&report->body_file, NULL, NULL, 3211 svn_io_file_del_on_pool_cleanup, 3212 report->pool, scratch_pool)); 3213 3214 if (sess->bulk_updates == svn_tristate_true) 3215 { 3216 /* User would like to use bulk updates. */ 3217 use_bulk_updates = TRUE; 3218 } 3219 else if (sess->bulk_updates == svn_tristate_false) 3220 { 3221 /* User doesn't want bulk updates. */ 3222 use_bulk_updates = FALSE; 3223 } 3224 else 3225 { 3226 /* User doesn't have any preferences on bulk updates. Decide on server 3227 preferences and capabilities. */ 3228 if (sess->server_allows_bulk) 3229 { 3230 if (apr_strnatcasecmp(sess->server_allows_bulk, "off") == 0) 3231 { 3232 /* Server doesn't want bulk updates */ 3233 use_bulk_updates = FALSE; 3234 } 3235 else if (apr_strnatcasecmp(sess->server_allows_bulk, "prefer") == 0) 3236 { 3237 /* Server prefers bulk updates, and we respect that */ 3238 use_bulk_updates = TRUE; 3239 } 3240 else 3241 { 3242 /* Server allows bulk updates, but doesn't dictate its use. Do 3243 whatever is the default. */ 3244 use_bulk_updates = FALSE; 3245 } 3246 } 3247 else 3248 { 3249 /* Pre-1.8 server didn't send the bulk_updates header. Check if server 3250 supports inlining properties in update editor report. */ 3251 if (sess->supports_inline_props) 3252 { 3253 /* Inline props supported: do not use bulk updates. */ 3254 use_bulk_updates = FALSE; 3255 } 3256 else 3257 { 3258 /* Inline props are not supported: use bulk updates to avoid 3259 * PROPFINDs for every added node. */ 3260 use_bulk_updates = TRUE; 3261 } 3262 } 3263 } 3264 3265 if (use_bulk_updates) 3266 { 3267 svn_xml_make_open_tag(&buf, scratch_pool, svn_xml_normal, 3268 "S:update-report", 3269 "xmlns:S", SVN_XML_NAMESPACE, "send-all", "true", 3270 NULL); 3271 } 3272 else 3273 { 3274 svn_xml_make_open_tag(&buf, scratch_pool, svn_xml_normal, 3275 "S:update-report", 3276 "xmlns:S", SVN_XML_NAMESPACE, 3277 NULL); 3278 /* Subversion 1.8+ servers can be told to send properties for newly 3279 added items inline even when doing a skelta response. */ 3280 make_simple_xml_tag(&buf, "S:include-props", "yes", scratch_pool); 3281 } 3282 3283 make_simple_xml_tag(&buf, "S:src-path", report->source, scratch_pool); 3284 3285 if (SVN_IS_VALID_REVNUM(report->target_rev)) 3286 { 3287 make_simple_xml_tag(&buf, "S:target-revision", 3288 apr_ltoa(scratch_pool, report->target_rev), 3289 scratch_pool); 3290 } 3291 3292 if (report->destination && *report->destination) 3293 { 3294 make_simple_xml_tag(&buf, "S:dst-path", report->destination, 3295 scratch_pool); 3296 } 3297 3298 if (report->update_target && *report->update_target) 3299 { 3300 make_simple_xml_tag(&buf, "S:update-target", report->update_target, 3301 scratch_pool); 3302 } 3303 3304 if (report->ignore_ancestry) 3305 { 3306 make_simple_xml_tag(&buf, "S:ignore-ancestry", "yes", scratch_pool); 3307 } 3308 3309 if (report->send_copyfrom_args) 3310 { 3311 make_simple_xml_tag(&buf, "S:send-copyfrom-args", "yes", scratch_pool); 3312 } 3313 3314 /* Old servers know "recursive" but not "depth"; help them DTRT. */ 3315 if (depth == svn_depth_files || depth == svn_depth_empty) 3316 { 3317 make_simple_xml_tag(&buf, "S:recursive", "no", scratch_pool); 3318 } 3319 3320 /* When in 'send-all' mode, mod_dav_svn will assume that it should 3321 calculate and transmit real text-deltas (instead of empty windows 3322 that merely indicate "text is changed") unless it finds this 3323 element. 3324 3325 NOTE: Do NOT count on servers actually obeying this, as some exist 3326 which obey send-all, but do not check for this directive at all! 3327 3328 NOTE 2: When not in 'send-all' mode, mod_dav_svn can still be configured to 3329 override our request and send text-deltas. */ 3330 if (! text_deltas) 3331 { 3332 make_simple_xml_tag(&buf, "S:text-deltas", "no", scratch_pool); 3333 } 3334 3335 make_simple_xml_tag(&buf, "S:depth", svn_depth_to_word(depth), scratch_pool); 3336 3337 SVN_ERR(svn_io_file_write_full(report->body_file, buf->data, buf->len, 3338 NULL, scratch_pool)); 3339 3340 return SVN_NO_ERROR; 3341} 3342 3343svn_error_t * 3344svn_ra_serf__do_update(svn_ra_session_t *ra_session, 3345 const svn_ra_reporter3_t **reporter, 3346 void **report_baton, 3347 svn_revnum_t revision_to_update_to, 3348 const char *update_target, 3349 svn_depth_t depth, 3350 svn_boolean_t send_copyfrom_args, 3351 svn_boolean_t ignore_ancestry, 3352 const svn_delta_editor_t *update_editor, 3353 void *update_baton, 3354 apr_pool_t *result_pool, 3355 apr_pool_t *scratch_pool) 3356{ 3357 svn_ra_serf__session_t *session = ra_session->priv; 3358 3359 SVN_ERR(make_update_reporter(ra_session, reporter, report_baton, 3360 revision_to_update_to, 3361 session->session_url.path, NULL, update_target, 3362 depth, ignore_ancestry, TRUE /* text_deltas */, 3363 send_copyfrom_args, 3364 update_editor, update_baton, 3365 result_pool, scratch_pool)); 3366 return SVN_NO_ERROR; 3367} 3368 3369svn_error_t * 3370svn_ra_serf__do_diff(svn_ra_session_t *ra_session, 3371 const svn_ra_reporter3_t **reporter, 3372 void **report_baton, 3373 svn_revnum_t revision, 3374 const char *diff_target, 3375 svn_depth_t depth, 3376 svn_boolean_t ignore_ancestry, 3377 svn_boolean_t text_deltas, 3378 const char *versus_url, 3379 const svn_delta_editor_t *diff_editor, 3380 void *diff_baton, 3381 apr_pool_t *pool) 3382{ 3383 svn_ra_serf__session_t *session = ra_session->priv; 3384 apr_pool_t *scratch_pool = svn_pool_create(pool); 3385 3386 SVN_ERR(make_update_reporter(ra_session, reporter, report_baton, 3387 revision, 3388 session->session_url.path, versus_url, diff_target, 3389 depth, ignore_ancestry, text_deltas, FALSE, 3390 diff_editor, diff_baton, 3391 pool, scratch_pool)); 3392 svn_pool_destroy(scratch_pool); 3393 return SVN_NO_ERROR; 3394} 3395 3396svn_error_t * 3397svn_ra_serf__do_status(svn_ra_session_t *ra_session, 3398 const svn_ra_reporter3_t **reporter, 3399 void **report_baton, 3400 const char *status_target, 3401 svn_revnum_t revision, 3402 svn_depth_t depth, 3403 const svn_delta_editor_t *status_editor, 3404 void *status_baton, 3405 apr_pool_t *pool) 3406{ 3407 svn_ra_serf__session_t *session = ra_session->priv; 3408 apr_pool_t *scratch_pool = svn_pool_create(pool); 3409 3410 SVN_ERR(make_update_reporter(ra_session, reporter, report_baton, 3411 revision, 3412 session->session_url.path, NULL, status_target, 3413 depth, FALSE, FALSE, FALSE, 3414 status_editor, status_baton, 3415 pool, scratch_pool)); 3416 svn_pool_destroy(scratch_pool); 3417 return SVN_NO_ERROR; 3418} 3419 3420svn_error_t * 3421svn_ra_serf__do_switch(svn_ra_session_t *ra_session, 3422 const svn_ra_reporter3_t **reporter, 3423 void **report_baton, 3424 svn_revnum_t revision_to_switch_to, 3425 const char *switch_target, 3426 svn_depth_t depth, 3427 const char *switch_url, 3428 svn_boolean_t send_copyfrom_args, 3429 svn_boolean_t ignore_ancestry, 3430 const svn_delta_editor_t *switch_editor, 3431 void *switch_baton, 3432 apr_pool_t *result_pool, 3433 apr_pool_t *scratch_pool) 3434{ 3435 svn_ra_serf__session_t *session = ra_session->priv; 3436 3437 return make_update_reporter(ra_session, reporter, report_baton, 3438 revision_to_switch_to, 3439 session->session_url.path, 3440 switch_url, switch_target, 3441 depth, 3442 ignore_ancestry, 3443 TRUE /* text_deltas */, 3444 send_copyfrom_args, 3445 switch_editor, switch_baton, 3446 result_pool, scratch_pool); 3447} 3448 3449/* Helper svn_ra_serf__get_file(). Attempts to fetch file contents 3450 * using SESSION->wc_callbacks->get_wc_contents() if sha1 property is 3451 * present in PROPS. 3452 * 3453 * Sets *FOUND_P to TRUE if file contents was successfuly fetched. 3454 * 3455 * Performs all temporary allocations in POOL. 3456 */ 3457static svn_error_t * 3458try_get_wc_contents(svn_boolean_t *found_p, 3459 svn_ra_serf__session_t *session, 3460 apr_hash_t *props, 3461 svn_stream_t *dst_stream, 3462 apr_pool_t *pool) 3463{ 3464 apr_hash_t *svn_props; 3465 const char *sha1_checksum_prop; 3466 svn_checksum_t *checksum; 3467 svn_stream_t *wc_stream; 3468 svn_error_t *err; 3469 3470 /* No contents found by default. */ 3471 *found_p = FALSE; 3472 3473 if (!session->wc_callbacks->get_wc_contents) 3474 { 3475 /* No callback, nothing to do. */ 3476 return SVN_NO_ERROR; 3477 } 3478 3479 3480 svn_props = svn_hash_gets(props, SVN_DAV_PROP_NS_DAV); 3481 if (!svn_props) 3482 { 3483 /* No properties -- therefore no checksum property -- in response. */ 3484 return SVN_NO_ERROR; 3485 } 3486 3487 sha1_checksum_prop = svn_prop_get_value(svn_props, "sha1-checksum"); 3488 if (sha1_checksum_prop == NULL) 3489 { 3490 /* No checksum property in response. */ 3491 return SVN_NO_ERROR; 3492 } 3493 3494 SVN_ERR(svn_checksum_parse_hex(&checksum, svn_checksum_sha1, 3495 sha1_checksum_prop, pool)); 3496 3497 err = session->wc_callbacks->get_wc_contents( 3498 session->wc_callback_baton, &wc_stream, checksum, pool); 3499 3500 if (err) 3501 { 3502 svn_error_clear(err); 3503 3504 /* Ignore errors for now. */ 3505 return SVN_NO_ERROR; 3506 } 3507 3508 if (wc_stream) 3509 { 3510 SVN_ERR(svn_stream_copy3(wc_stream, 3511 svn_stream_disown(dst_stream, pool), 3512 NULL, NULL, pool)); 3513 *found_p = TRUE; 3514 } 3515 3516 return SVN_NO_ERROR; 3517} 3518 3519svn_error_t * 3520svn_ra_serf__get_file(svn_ra_session_t *ra_session, 3521 const char *path, 3522 svn_revnum_t revision, 3523 svn_stream_t *stream, 3524 svn_revnum_t *fetched_rev, 3525 apr_hash_t **props, 3526 apr_pool_t *pool) 3527{ 3528 svn_ra_serf__session_t *session = ra_session->priv; 3529 svn_ra_serf__connection_t *conn; 3530 const char *fetch_url; 3531 apr_hash_t *fetch_props; 3532 svn_node_kind_t res_kind; 3533 const svn_ra_serf__dav_props_t *which_props; 3534 3535 /* What connection should we go on? */ 3536 conn = session->conns[session->cur_conn]; 3537 3538 /* Fetch properties. */ 3539 3540 fetch_url = svn_path_url_add_component2(session->session_url.path, path, pool); 3541 3542 /* The simple case is if we want HEAD - then a GET on the fetch_url is fine. 3543 * 3544 * Otherwise, we need to get the baseline version for this particular 3545 * revision and then fetch that file. 3546 */ 3547 if (SVN_IS_VALID_REVNUM(revision) || fetched_rev) 3548 { 3549 SVN_ERR(svn_ra_serf__get_stable_url(&fetch_url, fetched_rev, 3550 session, conn, 3551 fetch_url, revision, 3552 pool, pool)); 3553 revision = SVN_INVALID_REVNUM; 3554 } 3555 /* REVISION is always SVN_INVALID_REVNUM */ 3556 SVN_ERR_ASSERT(!SVN_IS_VALID_REVNUM(revision)); 3557 3558 if (props) 3559 { 3560 which_props = all_props; 3561 } 3562 else if (stream && session->wc_callbacks->get_wc_contents) 3563 { 3564 which_props = type_and_checksum_props; 3565 } 3566 else 3567 { 3568 which_props = check_path_props; 3569 } 3570 3571 SVN_ERR(svn_ra_serf__fetch_node_props(&fetch_props, conn, fetch_url, 3572 SVN_INVALID_REVNUM, 3573 which_props, 3574 pool, pool)); 3575 3576 /* Verify that resource type is not collection. */ 3577 SVN_ERR(svn_ra_serf__get_resource_type(&res_kind, fetch_props)); 3578 if (res_kind != svn_node_file) 3579 { 3580 return svn_error_create(SVN_ERR_FS_NOT_FILE, NULL, 3581 _("Can't get text contents of a directory")); 3582 } 3583 3584 /* TODO Filter out all of our props into a usable format. */ 3585 if (props) 3586 { 3587 /* ### flatten_props() does not copy PROPVALUE, but fetch_node_props() 3588 ### put them into POOL, so we're okay. */ 3589 SVN_ERR(svn_ra_serf__flatten_props(props, fetch_props, 3590 pool, pool)); 3591 } 3592 3593 if (stream) 3594 { 3595 svn_boolean_t found; 3596 SVN_ERR(try_get_wc_contents(&found, session, fetch_props, stream, pool)); 3597 3598 /* No contents found in the WC, let's fetch from server. */ 3599 if (!found) 3600 { 3601 report_fetch_t *stream_ctx; 3602 svn_ra_serf__handler_t *handler; 3603 3604 /* Create the fetch context. */ 3605 stream_ctx = apr_pcalloc(pool, sizeof(*stream_ctx)); 3606 stream_ctx->target_stream = stream; 3607 stream_ctx->sess = session; 3608 stream_ctx->conn = conn; 3609 stream_ctx->info = apr_pcalloc(pool, sizeof(*stream_ctx->info)); 3610 stream_ctx->info->name = fetch_url; 3611 3612 handler = apr_pcalloc(pool, sizeof(*handler)); 3613 3614 handler->handler_pool = pool; 3615 handler->method = "GET"; 3616 handler->path = fetch_url; 3617 handler->conn = conn; 3618 handler->session = session; 3619 3620 handler->custom_accept_encoding = TRUE; 3621 handler->header_delegate = headers_fetch; 3622 handler->header_delegate_baton = stream_ctx; 3623 3624 handler->response_handler = handle_stream; 3625 handler->response_baton = stream_ctx; 3626 3627 handler->response_error = cancel_fetch; 3628 handler->response_error_baton = stream_ctx; 3629 3630 stream_ctx->handler = handler; 3631 3632 svn_ra_serf__request_create(handler); 3633 3634 SVN_ERR(svn_ra_serf__context_run_wait(&stream_ctx->done, session, pool)); 3635 } 3636 } 3637 3638 return SVN_NO_ERROR; 3639} 3640