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