1251881Speter/* 2251881Speter * update.c : entry point for update RA functions for ra_serf 3251881Speter * 4251881Speter * ==================================================================== 5251881Speter * Licensed to the Apache Software Foundation (ASF) under one 6251881Speter * or more contributor license agreements. See the NOTICE file 7251881Speter * distributed with this work for additional information 8251881Speter * regarding copyright ownership. The ASF licenses this file 9251881Speter * to you under the Apache License, Version 2.0 (the 10251881Speter * "License"); you may not use this file except in compliance 11251881Speter * with the License. You may obtain a copy of the License at 12251881Speter * 13251881Speter * http://www.apache.org/licenses/LICENSE-2.0 14251881Speter * 15251881Speter * Unless required by applicable law or agreed to in writing, 16251881Speter * software distributed under the License is distributed on an 17251881Speter * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 18251881Speter * KIND, either express or implied. See the License for the 19251881Speter * specific language governing permissions and limitations 20251881Speter * under the License. 21251881Speter * ==================================================================== 22251881Speter */ 23251881Speter 24251881Speter 25251881Speter 26251881Speter#define APR_WANT_STRFUNC 27251881Speter#include <apr_version.h> 28251881Speter#include <apr_want.h> 29251881Speter 30251881Speter#include <apr_uri.h> 31251881Speter 32251881Speter#include <serf.h> 33251881Speter 34251881Speter#include "svn_hash.h" 35251881Speter#include "svn_pools.h" 36251881Speter#include "svn_ra.h" 37251881Speter#include "svn_dav.h" 38251881Speter#include "svn_xml.h" 39251881Speter#include "svn_delta.h" 40251881Speter#include "svn_path.h" 41251881Speter#include "svn_base64.h" 42251881Speter#include "svn_props.h" 43251881Speter 44251881Speter#include "svn_private_config.h" 45251881Speter#include "private/svn_dep_compat.h" 46251881Speter#include "private/svn_fspath.h" 47251881Speter#include "private/svn_string_private.h" 48251881Speter 49251881Speter#include "ra_serf.h" 50251881Speter#include "../libsvn_ra/ra_loader.h" 51251881Speter 52299742Sdim 53251881Speter 54251881Speter/* 55251881Speter * This enum represents the current state of our XML parsing for a REPORT. 56251881Speter * 57251881Speter * A little explanation of how the parsing works. Every time we see 58251881Speter * an open-directory tag, we enter the OPEN_DIR state. Likewise, for 59251881Speter * add-directory, open-file, etc. When we see the closing variant of the 60251881Speter * open-directory tag, we'll 'pop' out of that state. 61251881Speter * 62251881Speter * Each state has a pool associated with it that can have temporary 63251881Speter * allocations that will live as long as the tag is opened. Once 64251881Speter * the tag is 'closed', the pool will be reused. 65251881Speter */ 66251881Spetertypedef enum report_state_e { 67299742Sdim INITIAL = XML_STATE_INITIAL /* = 0 */, 68299742Sdim UPDATE_REPORT, 69299742Sdim TARGET_REVISION, 70299742Sdim 71299742Sdim OPEN_DIR, 72299742Sdim ADD_DIR, 73299742Sdim 74299742Sdim OPEN_FILE, 75299742Sdim ADD_FILE, 76299742Sdim 77299742Sdim DELETE_ENTRY, 78299742Sdim ABSENT_DIR, 79299742Sdim ABSENT_FILE, 80299742Sdim 81299742Sdim SET_PROP, 82299742Sdim REMOVE_PROP, 83299742Sdim 84299742Sdim PROP, 85299742Sdim 86299742Sdim FETCH_FILE, 87299742Sdim FETCH_PROPS, 88299742Sdim TXDELTA, 89299742Sdim 90299742Sdim CHECKED_IN, 91299742Sdim CHECKED_IN_HREF, 92299742Sdim 93299742Sdim MD5_CHECKSUM, 94299742Sdim 95299742Sdim VERSION_NAME, 96299742Sdim CREATIONDATE, 97299742Sdim CREATOR_DISPLAYNAME 98251881Speter} report_state_e; 99251881Speter 100251881Speter 101299742Sdim#define D_ "DAV:" 102299742Sdim#define S_ SVN_XML_NAMESPACE 103299742Sdim#define V_ SVN_DAV_PROP_NS_DAV 104299742Sdimstatic const svn_ra_serf__xml_transition_t update_ttable[] = { 105299742Sdim { INITIAL, S_, "update-report", UPDATE_REPORT, 106299742Sdim FALSE, { "?inline-props", "?send-all", NULL }, TRUE }, 107299742Sdim 108299742Sdim { UPDATE_REPORT, S_, "target-revision", TARGET_REVISION, 109299742Sdim FALSE, { "rev", NULL }, TRUE }, 110299742Sdim 111299742Sdim { UPDATE_REPORT, S_, "open-directory", OPEN_DIR, 112299742Sdim FALSE, { "rev", NULL }, TRUE }, 113299742Sdim 114299742Sdim { OPEN_DIR, S_, "open-directory", OPEN_DIR, 115299742Sdim FALSE, { "rev", "name", NULL }, TRUE }, 116299742Sdim 117299742Sdim { ADD_DIR, S_, "open-directory", OPEN_DIR, 118299742Sdim FALSE, { "rev", "name", NULL }, TRUE }, 119299742Sdim 120299742Sdim { OPEN_DIR, S_, "add-directory", ADD_DIR, 121299742Sdim FALSE, { "name", "?copyfrom-path", "?copyfrom-rev", /*"?bc-url",*/ 122299742Sdim NULL }, TRUE }, 123299742Sdim 124299742Sdim { ADD_DIR, S_, "add-directory", ADD_DIR, 125299742Sdim FALSE, { "name", "?copyfrom-path", "?copyfrom-rev", /*"?bc-url",*/ 126299742Sdim NULL }, TRUE }, 127299742Sdim 128299742Sdim { OPEN_DIR, S_, "open-file", OPEN_FILE, 129299742Sdim FALSE, { "rev", "name", NULL }, TRUE }, 130299742Sdim 131299742Sdim { ADD_DIR, S_, "open-file", OPEN_FILE, 132299742Sdim FALSE, { "rev", "name", NULL }, TRUE }, 133299742Sdim 134299742Sdim { OPEN_DIR, S_, "add-file", ADD_FILE, 135299742Sdim FALSE, { "name", "?copyfrom-path", "?copyfrom-rev", 136299742Sdim "?sha1-checksum", NULL }, TRUE }, 137299742Sdim 138299742Sdim { ADD_DIR, S_, "add-file", ADD_FILE, 139299742Sdim FALSE, { "name", "?copyfrom-path", "?copyfrom-rev", 140299742Sdim "?sha1-checksum", NULL }, TRUE }, 141299742Sdim 142299742Sdim { OPEN_DIR, S_, "delete-entry", DELETE_ENTRY, 143299742Sdim FALSE, { "?rev", "name", NULL }, TRUE }, 144299742Sdim 145299742Sdim { ADD_DIR, S_, "delete-entry", DELETE_ENTRY, 146299742Sdim FALSE, { "?rev", "name", NULL }, TRUE }, 147299742Sdim 148299742Sdim { OPEN_DIR, S_, "absent-directory", ABSENT_DIR, 149299742Sdim FALSE, { "name", NULL }, TRUE }, 150299742Sdim 151299742Sdim { ADD_DIR, S_, "absent-directory", ABSENT_DIR, 152299742Sdim FALSE, { "name", NULL }, TRUE }, 153299742Sdim 154299742Sdim { OPEN_DIR, S_, "absent-file", ABSENT_FILE, 155299742Sdim FALSE, { "name", NULL }, TRUE }, 156299742Sdim 157299742Sdim { ADD_DIR, S_, "absent-file", ABSENT_FILE, 158299742Sdim FALSE, { "name", NULL }, TRUE }, 159299742Sdim 160299742Sdim 161299742Sdim { OPEN_DIR, D_, "checked-in", CHECKED_IN, 162299742Sdim FALSE, { NULL }, FALSE }, 163299742Sdim 164299742Sdim { ADD_DIR, D_, "checked-in", CHECKED_IN, 165299742Sdim FALSE, { NULL }, FALSE }, 166299742Sdim 167299742Sdim { OPEN_FILE, D_, "checked-in", CHECKED_IN, 168299742Sdim FALSE, { NULL }, FALSE }, 169299742Sdim 170299742Sdim { ADD_FILE, D_, "checked-in", CHECKED_IN, 171299742Sdim FALSE, { NULL }, FALSE }, 172299742Sdim 173299742Sdim 174299742Sdim { OPEN_DIR, S_, "set-prop", SET_PROP, 175299742Sdim TRUE, { "name", "?encoding", NULL }, TRUE }, 176299742Sdim 177299742Sdim { ADD_DIR, S_, "set-prop", SET_PROP, 178299742Sdim TRUE, { "name", "?encoding", NULL }, TRUE }, 179299742Sdim 180299742Sdim { OPEN_FILE, S_, "set-prop", SET_PROP, 181299742Sdim TRUE, { "name", "?encoding", NULL }, TRUE }, 182299742Sdim 183299742Sdim { ADD_FILE, S_, "set-prop", SET_PROP, 184299742Sdim TRUE, { "name", "?encoding", NULL }, TRUE }, 185299742Sdim 186299742Sdim 187299742Sdim { OPEN_DIR, S_, "remove-prop", REMOVE_PROP, 188299742Sdim TRUE, { "name", NULL }, TRUE }, 189299742Sdim 190299742Sdim { ADD_DIR, S_, "remove-prop", REMOVE_PROP, 191299742Sdim TRUE, { "name", NULL }, TRUE }, 192299742Sdim 193299742Sdim { OPEN_FILE, S_, "remove-prop", REMOVE_PROP, 194299742Sdim TRUE, { "name", NULL }, TRUE }, 195299742Sdim 196299742Sdim { ADD_FILE, S_, "remove-prop", REMOVE_PROP, 197299742Sdim TRUE, { "name", NULL }, TRUE }, 198299742Sdim 199299742Sdim { OPEN_FILE, S_, "prop", PROP, 200299742Sdim FALSE, { NULL }, FALSE }, 201299742Sdim { OPEN_DIR, S_, "prop", PROP, 202299742Sdim FALSE, { NULL }, FALSE }, 203299742Sdim { ADD_FILE, S_, "prop", PROP, 204299742Sdim FALSE, { NULL }, FALSE }, 205299742Sdim { ADD_DIR, S_, "prop", PROP, 206299742Sdim FALSE, { NULL }, FALSE }, 207299742Sdim 208299742Sdim { OPEN_FILE, S_, "txdelta", TXDELTA, 209299742Sdim FALSE, { "?base-checksum" }, TRUE }, 210299742Sdim 211299742Sdim { ADD_FILE, S_, "txdelta", TXDELTA, 212299742Sdim FALSE, { "?base-checksum" }, TRUE }, 213299742Sdim 214299742Sdim { OPEN_FILE, S_, "fetch-file", FETCH_FILE, 215299742Sdim FALSE, { "?base-checksum", "?sha1-checksum", NULL }, TRUE}, 216299742Sdim 217299742Sdim { ADD_FILE, S_, "fetch-file", FETCH_FILE, 218299742Sdim FALSE, { "?base-checksum", "?sha1-checksum", NULL }, TRUE }, 219299742Sdim 220299742Sdim { CHECKED_IN, D_, "href", CHECKED_IN_HREF, 221299742Sdim TRUE, { NULL }, TRUE }, 222299742Sdim 223299742Sdim { PROP, V_, "md5-checksum", MD5_CHECKSUM, 224299742Sdim TRUE, { NULL }, TRUE }, 225299742Sdim 226299742Sdim /* These are only reported for <= 1.6.x mod_dav_svn */ 227299742Sdim { OPEN_DIR, S_, "fetch-props", FETCH_PROPS, 228299742Sdim FALSE, { NULL }, FALSE }, 229299742Sdim { OPEN_FILE, S_, "fetch-props", FETCH_PROPS, 230299742Sdim FALSE, { NULL }, FALSE }, 231299742Sdim 232299742Sdim { PROP, D_, "version-name", VERSION_NAME, 233299742Sdim TRUE, { NULL }, TRUE }, 234299742Sdim { PROP, D_, "creationdate", CREATIONDATE, 235299742Sdim TRUE, { NULL }, TRUE }, 236299742Sdim { PROP, D_, "creator-displayname", CREATOR_DISPLAYNAME, 237299742Sdim TRUE, { NULL }, TRUE }, 238299742Sdim { 0 } 239299742Sdim}; 240299742Sdim 241251881Speter/* While we process the REPORT response, we will queue up GET and PROPFIND 242251881Speter requests. For a very large checkout, it is very easy to queue requests 243251881Speter faster than they are resolved. Thus, we need to pause the XML processing 244251881Speter (which queues more requests) to avoid queueing too many, with their 245251881Speter attendant memory costs. When the queue count drops low enough, we will 246251881Speter resume XML processing. 247251881Speter 248251881Speter Note that we don't want the count to drop to zero. We have multiple 249251881Speter connections that we want to keep busy. These are also heuristic numbers 250251881Speter since network and parsing behavior (ie. it doesn't pause immediately) 251251881Speter can make the measurements quite imprecise. 252251881Speter 253251881Speter We measure outstanding requests as the sum of NUM_ACTIVE_FETCHES and 254251881Speter NUM_ACTIVE_PROPFINDS in the report_context_t structure. */ 255251881Speter#define REQUEST_COUNT_TO_PAUSE 50 256251881Speter#define REQUEST_COUNT_TO_RESUME 40 257251881Speter 258299742Sdim#define SPILLBUF_BLOCKSIZE 4096 259299742Sdim#define SPILLBUF_MAXBUFFSIZE 131072 260251881Speter 261299742Sdim#define PARSE_CHUNK_SIZE 8000 /* Copied from xml.c ### Needs tuning */ 262299742Sdim 263251881Speter/* Forward-declare our report context. */ 264251881Spetertypedef struct report_context_t report_context_t; 265299742Sdimtypedef struct body_create_baton_t body_create_baton_t; 266251881Speter/* 267251881Speter * This structure represents the information for a directory. 268251881Speter */ 269299742Sdimtypedef struct dir_baton_t 270251881Speter{ 271299742Sdim struct dir_baton_t *parent_dir; /* NULL when root */ 272251881Speter 273299742Sdim apr_pool_t *pool; /* Subpool for this directory */ 274251881Speter 275251881Speter /* Pointer back to our original report context. */ 276299742Sdim report_context_t *ctx; 277251881Speter 278299742Sdim const char *relpath; /* session relative path */ 279299742Sdim const char *base_name; /* Name of item "" for root */ 280251881Speter 281251881Speter /* the canonical url for this directory after updating. (received) */ 282251881Speter const char *url; 283251881Speter 284299742Sdim /* The original repos_relpath of this url (via the reporter) 285299742Sdim directly, or via an ancestor. */ 286251881Speter const char *repos_relpath; 287251881Speter 288299742Sdim svn_revnum_t base_rev; /* base revision or NULL for Add */ 289251881Speter 290299742Sdim const char *copyfrom_path; /* NULL for open */ 291299742Sdim svn_revnum_t copyfrom_rev; /* SVN_INVALID_REVNUM for open */ 292299742Sdim 293251881Speter /* controlling dir baton - this is only created in ensure_dir_opened() */ 294299742Sdim svn_boolean_t dir_opened; 295251881Speter void *dir_baton; 296251881Speter 297251881Speter /* How many references to this directory do we still have open? */ 298251881Speter apr_size_t ref_count; 299251881Speter 300299742Sdim svn_boolean_t fetch_props; /* Use PROPFIND request? */ 301251881Speter svn_ra_serf__handler_t *propfind_handler; 302299742Sdim apr_hash_t *remove_props; 303251881Speter 304299742Sdim} dir_baton_t; 305251881Speter 306251881Speter/* 307299742Sdim* This structure represents the information for a file. 308299742Sdim* 309299742Sdim* This structure is created as we parse the REPORT response and 310299742Sdim* once the element is completed, we may create a fetch_ctx_t structure 311299742Sdim* to give to serf to retrieve this file. 312299742Sdim*/ 313299742Sdimtypedef struct file_baton_t 314251881Speter{ 315299742Sdim dir_baton_t *parent_dir; /* The parent */ 316299742Sdim apr_pool_t *pool; /* Subpool for this file*/ 317251881Speter 318299742Sdim const char *relpath; /* session relative path */ 319251881Speter const char *base_name; 320251881Speter 321299742Sdim /* the canonical url for this directory after updating. (received) */ 322251881Speter const char *url; 323251881Speter 324299742Sdim /* The original repos_relpath of this url as reported. */ 325299742Sdim const char *repos_relpath; 326299742Sdim 327251881Speter /* lock token, if we had one to start off with. */ 328251881Speter const char *lock_token; 329251881Speter 330299742Sdim svn_revnum_t base_rev; /* SVN_INVALID_REVNUM for Add */ 331251881Speter 332299742Sdim const char *copyfrom_path; /* NULL for open */ 333299742Sdim svn_revnum_t copyfrom_rev; /* SVN_INVALID_REVNUM for open */ 334251881Speter 335299742Sdim /* controlling dir baton - this is only created in ensure_file_opened() */ 336299742Sdim svn_boolean_t file_opened; 337299742Sdim void *file_baton; 338251881Speter 339299742Sdim svn_boolean_t fetch_props; /* Use PROPFIND request? */ 340251881Speter svn_ra_serf__handler_t *propfind_handler; 341299742Sdim svn_boolean_t found_lock_prop; 342299742Sdim apr_hash_t *remove_props; 343251881Speter 344251881Speter /* Has the server told us to go fetch - only valid if we had it already */ 345251881Speter svn_boolean_t fetch_file; 346251881Speter 347251881Speter /* controlling file_baton and textdelta handler */ 348299742Sdim svn_txdelta_window_handler_t txdelta; 349299742Sdim void *txdelta_baton; 350251881Speter 351299742Sdim svn_checksum_t *base_md5_checksum; 352299742Sdim svn_checksum_t *final_md5_checksum; 353299742Sdim svn_checksum_t *final_sha1_checksum; 354251881Speter 355299742Sdim svn_stream_t *txdelta_stream; /* Stream that feeds windows when 356299742Sdim written to within txdelta*/ 357299742Sdim} file_baton_t; 358251881Speter 359251881Speter/* 360251881Speter * This structure represents a single request to GET (fetch) a file with 361251881Speter * its associated Serf session/connection. 362251881Speter */ 363299742Sdimtypedef struct fetch_ctx_t { 364251881Speter 365251881Speter /* The handler representing this particular fetch. */ 366251881Speter svn_ra_serf__handler_t *handler; 367251881Speter 368299742Sdim svn_boolean_t using_compression; 369251881Speter 370251881Speter /* Stores the information for the file we want to fetch. */ 371299742Sdim file_baton_t *file; 372251881Speter 373251881Speter /* Have we read our response headers yet? */ 374251881Speter svn_boolean_t read_headers; 375251881Speter 376251881Speter /* This flag is set when our response is aborted before we reach the 377251881Speter * end and we decide to requeue this request. 378251881Speter */ 379251881Speter svn_boolean_t aborted_read; 380251881Speter apr_off_t aborted_read_size; 381251881Speter 382251881Speter /* This is the amount of data that we have read so far. */ 383251881Speter apr_off_t read_size; 384251881Speter 385251881Speter /* If we're writing this file to a stream, this will be non-NULL. */ 386299742Sdim svn_stream_t *result_stream; 387251881Speter 388299742Sdim /* The base-rev header */ 389299742Sdim const char *delta_base; 390251881Speter 391299742Sdim} fetch_ctx_t; 392251881Speter 393251881Speter/* 394251881Speter * The master structure for a REPORT request and response. 395251881Speter */ 396251881Speterstruct report_context_t { 397251881Speter apr_pool_t *pool; 398251881Speter 399251881Speter svn_ra_serf__session_t *sess; 400251881Speter 401251881Speter /* Source path and destination path */ 402251881Speter const char *source; 403251881Speter const char *destination; 404251881Speter 405251881Speter /* Our update target. */ 406251881Speter const char *update_target; 407251881Speter 408251881Speter /* What is the target revision that we want for this REPORT? */ 409251881Speter svn_revnum_t target_rev; 410251881Speter 411299742Sdim /* Where are we (used while parsing) */ 412299742Sdim dir_baton_t *cur_dir; 413299742Sdim file_baton_t *cur_file; 414299742Sdim 415251881Speter /* Have we been asked to ignore ancestry or textdeltas? */ 416251881Speter svn_boolean_t ignore_ancestry; 417251881Speter svn_boolean_t text_deltas; 418251881Speter 419251881Speter /* Do we want the server to send copyfrom args or not? */ 420251881Speter svn_boolean_t send_copyfrom_args; 421251881Speter 422251881Speter /* Is the server sending everything in one response? */ 423251881Speter svn_boolean_t send_all_mode; 424251881Speter 425251881Speter /* Is the server including properties inline for newly added 426251881Speter files/dirs? */ 427251881Speter svn_boolean_t add_props_included; 428251881Speter 429251881Speter /* Path -> const char *repos_relpath mapping */ 430251881Speter apr_hash_t *switched_paths; 431251881Speter 432251881Speter /* Our master update editor and baton. */ 433299742Sdim const svn_delta_editor_t *editor; 434299742Sdim void *editor_baton; 435251881Speter 436251881Speter /* The file holding request body for the REPORT. 437251881Speter * 438251881Speter * ### todo: It will be better for performance to store small 439251881Speter * request bodies (like 4k) in memory and bigger bodies on disk. 440251881Speter */ 441299742Sdim svn_stream_t *body_template; 442299742Sdim body_create_baton_t *body; 443251881Speter 444251881Speter /* number of pending GET requests */ 445251881Speter unsigned int num_active_fetches; 446251881Speter 447251881Speter /* number of pending PROPFIND requests */ 448251881Speter unsigned int num_active_propfinds; 449251881Speter 450251881Speter /* Are we done parsing the REPORT response? */ 451251881Speter svn_boolean_t done; 452251881Speter 453251881Speter /* Did we receive all data from the network? */ 454251881Speter svn_boolean_t report_received; 455251881Speter 456251881Speter /* Did we close the root directory? */ 457251881Speter svn_boolean_t closed_root; 458251881Speter}; 459251881Speter 460299742Sdim/* Baton for collecting REPORT body. Depending on the size this 461299742Sdim work is backed by a memory buffer (via serf buckets) or by 462299742Sdim a file */ 463299742Sdimstruct body_create_baton_t 464299742Sdim{ 465299742Sdim apr_pool_t *result_pool; 466299742Sdim apr_size_t total_bytes; 467251881Speter 468299742Sdim apr_pool_t *scratch_pool; 469251881Speter 470299742Sdim serf_bucket_alloc_t *alloc; 471299742Sdim serf_bucket_t *collect_bucket; 472251881Speter 473299742Sdim const void *all_data; 474299742Sdim apr_file_t *file; 475299742Sdim}; 476251881Speter 477251881Speter 478299742Sdim#define MAX_BODY_IN_RAM (256*1024) 479251881Speter 480299742Sdim/* Fold all previously collected data in a single buffer allocated in 481299742Sdim RESULT_POOL and clear all intermediate state */ 482299742Sdimstatic const char * 483299742Sdimbody_allocate_all(body_create_baton_t *body, 484299742Sdim apr_pool_t *result_pool) 485299742Sdim{ 486299742Sdim char *buffer = apr_pcalloc(result_pool, body->total_bytes); 487299742Sdim const char *data; 488299742Sdim apr_size_t sz; 489299742Sdim apr_status_t s; 490299742Sdim apr_size_t remaining = body->total_bytes; 491299742Sdim char *next = buffer; 492251881Speter 493299742Sdim while (!(s = serf_bucket_read(body->collect_bucket, remaining, &data, &sz))) 494299742Sdim { 495299742Sdim memcpy(next, data, sz); 496299742Sdim remaining -= sz; 497299742Sdim next += sz; 498251881Speter 499299742Sdim if (! remaining) 500299742Sdim break; 501299742Sdim } 502251881Speter 503299742Sdim if (!SERF_BUCKET_READ_ERROR(s)) 504299742Sdim { 505299742Sdim memcpy(next, data, sz); 506299742Sdim } 507251881Speter 508299742Sdim serf_bucket_destroy(body->collect_bucket); 509299742Sdim body->collect_bucket = NULL; 510251881Speter 511299742Sdim return (s != APR_EOF) ? NULL : buffer; 512299742Sdim} 513251881Speter 514299742Sdim/* Noop function. Make serf take care of freeing in error situations */ 515299742Sdimstatic void serf_free_no_error(void *unfreed_baton, void *block) {} 516251881Speter 517299742Sdim/* Stream write function for body creation */ 518299742Sdimstatic svn_error_t * 519299742Sdimbody_write_fn(void *baton, 520299742Sdim const char *data, 521299742Sdim apr_size_t *len) 522299742Sdim{ 523299742Sdim body_create_baton_t *bcb = baton; 524251881Speter 525299742Sdim if (!bcb->scratch_pool) 526299742Sdim bcb->scratch_pool = svn_pool_create(bcb->result_pool); 527251881Speter 528299742Sdim if (bcb->file) 529299742Sdim { 530299742Sdim SVN_ERR(svn_io_file_write_full(bcb->file, data, *len, NULL, 531299742Sdim bcb->scratch_pool)); 532299742Sdim svn_pool_clear(bcb->scratch_pool); 533251881Speter 534299742Sdim bcb->total_bytes += *len; 535299742Sdim } 536299742Sdim else if (*len + bcb->total_bytes > MAX_BODY_IN_RAM) 537299742Sdim { 538299742Sdim SVN_ERR(svn_io_open_unique_file3(&bcb->file, NULL, NULL, 539299742Sdim svn_io_file_del_on_pool_cleanup, 540299742Sdim bcb->result_pool, bcb->scratch_pool)); 541251881Speter 542299742Sdim if (bcb->total_bytes) 543299742Sdim { 544299742Sdim const char *all = body_allocate_all(bcb, bcb->scratch_pool); 545251881Speter 546299742Sdim SVN_ERR(svn_io_file_write_full(bcb->file, all, bcb->total_bytes, 547299742Sdim NULL, bcb->scratch_pool)); 548299742Sdim } 549251881Speter 550299742Sdim SVN_ERR(svn_io_file_write_full(bcb->file, data, *len, NULL, 551299742Sdim bcb->scratch_pool)); 552299742Sdim bcb->total_bytes += *len; 553299742Sdim } 554299742Sdim else 555299742Sdim { 556299742Sdim if (!bcb->alloc) 557299742Sdim bcb->alloc = serf_bucket_allocator_create(bcb->scratch_pool, 558299742Sdim serf_free_no_error, NULL); 559299742Sdim 560299742Sdim if (!bcb->collect_bucket) 561299742Sdim bcb->collect_bucket = serf_bucket_aggregate_create(bcb->alloc); 562299742Sdim 563299742Sdim serf_bucket_aggregate_append(bcb->collect_bucket, 564299742Sdim serf_bucket_simple_copy_create(data, *len, 565299742Sdim bcb->alloc)); 566299742Sdim 567299742Sdim bcb->total_bytes += *len; 568299742Sdim } 569299742Sdim 570299742Sdim return SVN_NO_ERROR; 571299742Sdim} 572299742Sdim 573299742Sdim/* Stream close function for collecting body */ 574251881Speterstatic svn_error_t * 575299742Sdimbody_done_fn(void *baton) 576251881Speter{ 577299742Sdim body_create_baton_t *bcb = baton; 578299742Sdim if (bcb->file) 579299742Sdim { 580299742Sdim /* We need to flush the file, make it unbuffered (so that it can be 581299742Sdim * zero-copied via mmap), and reset the position before attempting 582299742Sdim * to deliver the file. 583299742Sdim * 584299742Sdim * N.B. If we have APR 1.3+, we can unbuffer the file to let us use 585299742Sdim * mmap and zero-copy the PUT body. However, on older APR versions, 586299742Sdim * we can't check the buffer status; but serf will fall through and 587299742Sdim * create a file bucket for us on the buffered handle. 588299742Sdim */ 589251881Speter 590299742Sdim SVN_ERR(svn_io_file_flush(bcb->file, bcb->scratch_pool)); 591299742Sdim apr_file_buffer_set(bcb->file, NULL, 0); 592299742Sdim } 593299742Sdim else if (bcb->collect_bucket) 594299742Sdim bcb->all_data = body_allocate_all(bcb, bcb->result_pool); 595299742Sdim 596299742Sdim if (bcb->scratch_pool) 597299742Sdim svn_pool_destroy(bcb->scratch_pool); 598299742Sdim 599251881Speter return SVN_NO_ERROR; 600251881Speter} 601251881Speter 602251881Speterstatic svn_error_t * 603299742Sdimcreate_dir_baton(dir_baton_t **new_dir, 604299742Sdim report_context_t *ctx, 605299742Sdim const char *name, 606299742Sdim apr_pool_t *scratch_pool) 607251881Speter{ 608299742Sdim dir_baton_t *parent = ctx->cur_dir; 609299742Sdim apr_pool_t *dir_pool; 610299742Sdim dir_baton_t *dir; 611251881Speter 612299742Sdim if (parent) 613299742Sdim dir_pool = svn_pool_create(parent->pool); 614299742Sdim else 615299742Sdim dir_pool = svn_pool_create(ctx->pool); 616299742Sdim 617299742Sdim dir = apr_pcalloc(dir_pool, sizeof(*dir)); 618299742Sdim dir->pool = dir_pool; 619299742Sdim dir->ctx = ctx; 620299742Sdim 621299742Sdim if (parent) 622251881Speter { 623299742Sdim dir->parent_dir = parent; 624299742Sdim parent->ref_count++; 625299742Sdim } 626251881Speter 627299742Sdim dir->relpath = parent ? svn_relpath_join(parent->relpath, name, dir_pool) 628299742Sdim : apr_pstrdup(dir_pool, name); 629299742Sdim dir->base_name = svn_relpath_basename(dir->relpath, NULL); 630299742Sdim 631299742Sdim dir->repos_relpath = svn_hash_gets(ctx->switched_paths, dir->relpath); 632299742Sdim if (!dir->repos_relpath) 633299742Sdim { 634299742Sdim if (parent) 635299742Sdim dir->repos_relpath = svn_relpath_join(parent->repos_relpath, name, 636299742Sdim dir_pool); 637299742Sdim else 638299742Sdim dir->repos_relpath = svn_uri_skip_ancestor(ctx->sess->repos_root_str, 639299742Sdim ctx->sess->session_url_str, 640299742Sdim dir_pool); 641251881Speter } 642251881Speter 643299742Sdim dir->base_rev = SVN_INVALID_REVNUM; 644299742Sdim dir->copyfrom_rev = SVN_INVALID_REVNUM; 645299742Sdim 646299742Sdim dir->ref_count = 1; 647299742Sdim 648299742Sdim ctx->cur_dir = dir; 649299742Sdim 650299742Sdim *new_dir = dir; 651251881Speter return SVN_NO_ERROR; 652251881Speter} 653251881Speter 654251881Speterstatic svn_error_t * 655299742Sdimcreate_file_baton(file_baton_t **new_file, 656299742Sdim report_context_t *ctx, 657299742Sdim const char *name, 658299742Sdim apr_pool_t *scratch_pool) 659251881Speter{ 660299742Sdim dir_baton_t *parent = ctx->cur_dir; 661299742Sdim apr_pool_t *file_pool; 662299742Sdim file_baton_t *file; 663251881Speter 664299742Sdim file_pool = svn_pool_create(parent->pool); 665299742Sdim 666299742Sdim file = apr_pcalloc(file_pool, sizeof(*file)); 667299742Sdim file->pool = file_pool; 668299742Sdim 669299742Sdim file->parent_dir = parent; 670299742Sdim parent->ref_count++; 671299742Sdim 672299742Sdim file->relpath = svn_relpath_join(parent->relpath, name, file_pool); 673299742Sdim file->base_name = svn_relpath_basename(file->relpath, NULL); 674299742Sdim 675299742Sdim file->repos_relpath = svn_hash_gets(ctx->switched_paths, file->relpath); 676299742Sdim if (!file->repos_relpath) 677299742Sdim file->repos_relpath = svn_relpath_join(parent->repos_relpath, name, 678299742Sdim file_pool); 679299742Sdim 680299742Sdim /* Sane defaults */ 681299742Sdim file->base_rev = SVN_INVALID_REVNUM; 682299742Sdim file->copyfrom_rev = SVN_INVALID_REVNUM; 683299742Sdim 684299742Sdim *new_file = file; 685299742Sdim 686299742Sdim ctx->cur_file = file; 687299742Sdim 688251881Speter return SVN_NO_ERROR; 689251881Speter} 690251881Speter 691299742Sdim/** Minimum nr. of outstanding requests needed before a new connection is 692299742Sdim * opened. */ 693299742Sdim#define REQS_PER_CONN 8 694251881Speter 695299742Sdim/** This function creates a new connection for this serf session, but only 696299742Sdim * if the number of NUM_ACTIVE_REQS > REQS_PER_CONN or if there currently is 697299742Sdim * only one main connection open. 698299742Sdim */ 699299742Sdimstatic svn_error_t * 700299742Sdimopen_connection_if_needed(svn_ra_serf__session_t *sess, int num_active_reqs) 701299742Sdim{ 702299742Sdim /* For each REQS_PER_CONN outstanding requests open a new connection, with 703299742Sdim * a minimum of 1 extra connection. */ 704299742Sdim if (sess->num_conns == 1 || 705299742Sdim ((num_active_reqs / REQS_PER_CONN) > sess->num_conns)) 706299742Sdim { 707299742Sdim int cur = sess->num_conns; 708299742Sdim apr_status_t status; 709251881Speter 710299742Sdim sess->conns[cur] = apr_pcalloc(sess->pool, sizeof(*sess->conns[cur])); 711299742Sdim sess->conns[cur]->bkt_alloc = serf_bucket_allocator_create(sess->pool, 712299742Sdim NULL, NULL); 713299742Sdim sess->conns[cur]->last_status_code = -1; 714299742Sdim sess->conns[cur]->session = sess; 715299742Sdim status = serf_connection_create2(&sess->conns[cur]->conn, 716299742Sdim sess->context, 717299742Sdim sess->session_url, 718299742Sdim svn_ra_serf__conn_setup, 719299742Sdim sess->conns[cur], 720299742Sdim svn_ra_serf__conn_closed, 721299742Sdim sess->conns[cur], 722299742Sdim sess->pool); 723299742Sdim if (status) 724299742Sdim return svn_ra_serf__wrap_err(status, NULL); 725299742Sdim 726299742Sdim sess->num_conns++; 727299742Sdim } 728299742Sdim 729299742Sdim return SVN_NO_ERROR; 730299742Sdim} 731299742Sdim 732251881Speter/* Returns best connection for fetching files/properties. */ 733251881Speterstatic svn_ra_serf__connection_t * 734251881Speterget_best_connection(report_context_t *ctx) 735251881Speter{ 736251881Speter svn_ra_serf__connection_t *conn; 737251881Speter int first_conn = 1; 738251881Speter 739251881Speter /* Skip the first connection if the REPORT response hasn't been completely 740251881Speter received yet or if we're being told to limit our connections to 741251881Speter 2 (because this could be an attempt to ensure that we do all our 742251881Speter auxiliary GETs/PROPFINDs on a single connection). 743251881Speter 744251881Speter ### FIXME: This latter requirement (max_connections > 2) is 745251881Speter ### really just a hack to work around the fact that some update 746251881Speter ### editor implementations (such as svnrdump's dump editor) 747251881Speter ### simply can't handle the way ra_serf violates the editor v1 748251881Speter ### drive ordering requirements. 749251881Speter ### 750251881Speter ### See http://subversion.tigris.org/issues/show_bug.cgi?id=4116. 751251881Speter */ 752251881Speter if (ctx->report_received && (ctx->sess->max_connections > 2)) 753251881Speter first_conn = 0; 754251881Speter 755299742Sdim /* If there's only one available auxiliary connection to use, don't bother 756299742Sdim doing all the cur_conn math -- just return that one connection. */ 757251881Speter if (ctx->sess->num_conns - first_conn == 1) 758251881Speter { 759251881Speter conn = ctx->sess->conns[first_conn]; 760251881Speter } 761251881Speter else 762251881Speter { 763299742Sdim#if SERF_VERSION_AT_LEAST(1, 4, 0) 764299742Sdim /* Often one connection is slower than others, e.g. because the server 765299742Sdim process/thread has to do more work for the particular set of requests. 766299742Sdim In the worst case, when REQUEST_COUNT_TO_RESUME requests are queued 767299742Sdim on such a slow connection, ra_serf will completely stop sending 768299742Sdim requests. 769299742Sdim 770299742Sdim The method used here selects the connection with the least amount of 771299742Sdim pending requests, thereby giving more work to lightly loaded server 772299742Sdim processes. 773299742Sdim */ 774299742Sdim int i, best_conn = first_conn; 775299742Sdim unsigned int min = INT_MAX; 776299742Sdim for (i = first_conn; i < ctx->sess->num_conns; i++) 777299742Sdim { 778299742Sdim serf_connection_t *sc = ctx->sess->conns[i]->conn; 779299742Sdim unsigned int pending = serf_connection_pending_requests(sc); 780299742Sdim if (pending < min) 781299742Sdim { 782299742Sdim min = pending; 783299742Sdim best_conn = i; 784299742Sdim } 785299742Sdim } 786299742Sdim conn = ctx->sess->conns[best_conn]; 787299742Sdim#else 788299742Sdim /* We don't know how many requests are pending per connection, so just 789299742Sdim cycle them. */ 790251881Speter conn = ctx->sess->conns[ctx->sess->cur_conn]; 791251881Speter ctx->sess->cur_conn++; 792251881Speter if (ctx->sess->cur_conn >= ctx->sess->num_conns) 793251881Speter ctx->sess->cur_conn = first_conn; 794299742Sdim#endif 795251881Speter } 796251881Speter return conn; 797251881Speter} 798251881Speter 799251881Speter/** Helpers to open and close directories */ 800251881Speter 801251881Speterstatic svn_error_t* 802299742Sdimensure_dir_opened(dir_baton_t *dir, 803299742Sdim apr_pool_t *scratch_pool) 804251881Speter{ 805299742Sdim report_context_t *ctx = dir->ctx; 806251881Speter 807299742Sdim if (dir->dir_opened) 808299742Sdim return SVN_NO_ERROR; 809251881Speter 810251881Speter if (dir->base_name[0] == '\0') 811251881Speter { 812251881Speter if (ctx->destination 813251881Speter && ctx->sess->wc_callbacks->invalidate_wc_props) 814251881Speter { 815251881Speter SVN_ERR(ctx->sess->wc_callbacks->invalidate_wc_props( 816251881Speter ctx->sess->wc_callback_baton, 817251881Speter ctx->update_target, 818299742Sdim SVN_RA_SERF__WC_CHECKED_IN_URL, scratch_pool)); 819251881Speter } 820251881Speter 821299742Sdim SVN_ERR(ctx->editor->open_root(ctx->editor_baton, dir->base_rev, 822299742Sdim dir->pool, 823299742Sdim &dir->dir_baton)); 824251881Speter } 825251881Speter else 826251881Speter { 827299742Sdim SVN_ERR(ensure_dir_opened(dir->parent_dir, scratch_pool)); 828251881Speter 829251881Speter if (SVN_IS_VALID_REVNUM(dir->base_rev)) 830251881Speter { 831299742Sdim SVN_ERR(ctx->editor->open_directory(dir->relpath, 832299742Sdim dir->parent_dir->dir_baton, 833299742Sdim dir->base_rev, 834299742Sdim dir->pool, 835299742Sdim &dir->dir_baton)); 836251881Speter } 837251881Speter else 838251881Speter { 839299742Sdim SVN_ERR(ctx->editor->add_directory(dir->relpath, 840299742Sdim dir->parent_dir->dir_baton, 841299742Sdim dir->copyfrom_path, 842299742Sdim dir->copyfrom_rev, 843299742Sdim dir->pool, 844299742Sdim &dir->dir_baton)); 845251881Speter } 846251881Speter } 847251881Speter 848299742Sdim dir->dir_opened = TRUE; 849299742Sdim 850251881Speter return SVN_NO_ERROR; 851251881Speter} 852251881Speter 853251881Speterstatic svn_error_t * 854299742Sdimmaybe_close_dir(dir_baton_t *dir) 855251881Speter{ 856299742Sdim apr_pool_t *scratch_pool = dir->pool; 857299742Sdim dir_baton_t *parent = dir->parent_dir; 858299742Sdim report_context_t *ctx = dir->ctx; 859251881Speter 860299742Sdim if (--dir->ref_count) 861251881Speter { 862299742Sdim return SVN_NO_ERROR; 863251881Speter } 864251881Speter 865299742Sdim SVN_ERR(ensure_dir_opened(dir, dir->pool)); 866251881Speter 867299742Sdim if (dir->remove_props) 868251881Speter { 869299742Sdim apr_hash_index_t *hi; 870251881Speter 871299742Sdim for (hi = apr_hash_first(scratch_pool, dir->remove_props); 872299742Sdim hi; 873299742Sdim hi = apr_hash_next(hi)) 874251881Speter { 875299742Sdim SVN_ERR(ctx->editor->change_file_prop(dir->dir_baton, 876299742Sdim apr_hash_this_key(hi), 877299742Sdim NULL /* value */, 878299742Sdim scratch_pool)); 879251881Speter } 880251881Speter } 881251881Speter 882299742Sdim SVN_ERR(dir->ctx->editor->close_directory(dir->dir_baton, scratch_pool)); 883251881Speter 884299742Sdim svn_pool_destroy(dir->pool /* scratch_pool */); 885299742Sdim 886299742Sdim if (parent) 887299742Sdim return svn_error_trace(maybe_close_dir(parent)); 888299742Sdim else 889299742Sdim return SVN_NO_ERROR; 890251881Speter} 891251881Speter 892299742Sdimstatic svn_error_t * 893299742Sdimensure_file_opened(file_baton_t *file, 894299742Sdim apr_pool_t *scratch_pool) 895251881Speter{ 896299742Sdim const svn_delta_editor_t *editor = file->parent_dir->ctx->editor; 897251881Speter 898299742Sdim if (file->file_opened) 899299742Sdim return SVN_NO_ERROR; 900251881Speter 901299742Sdim /* Ensure our parent is open. */ 902299742Sdim SVN_ERR(ensure_dir_opened(file->parent_dir, scratch_pool)); 903251881Speter 904299742Sdim /* Open (or add) the file. */ 905299742Sdim if (SVN_IS_VALID_REVNUM(file->base_rev)) 906251881Speter { 907299742Sdim SVN_ERR(editor->open_file(file->relpath, 908299742Sdim file->parent_dir->dir_baton, 909299742Sdim file->base_rev, 910299742Sdim file->pool, 911299742Sdim &file->file_baton)); 912251881Speter } 913299742Sdim else 914251881Speter { 915299742Sdim SVN_ERR(editor->add_file(file->relpath, 916299742Sdim file->parent_dir->dir_baton, 917299742Sdim file->copyfrom_path, 918299742Sdim file->copyfrom_rev, 919299742Sdim file->pool, 920299742Sdim &file->file_baton)); 921299742Sdim } 922251881Speter 923299742Sdim file->file_opened = TRUE; 924251881Speter 925299742Sdim return SVN_NO_ERROR; 926251881Speter} 927251881Speter 928299742Sdim 929299742Sdim/** Routines called when we are fetching a file */ 930299742Sdim 931251881Speterstatic svn_error_t * 932251881Speterheaders_fetch(serf_bucket_t *headers, 933251881Speter void *baton, 934299742Sdim apr_pool_t *pool /* request pool */, 935299742Sdim apr_pool_t *scratch_pool) 936251881Speter{ 937299742Sdim fetch_ctx_t *fetch_ctx = baton; 938251881Speter 939251881Speter /* note that we have old VC URL */ 940299742Sdim if (fetch_ctx->delta_base) 941251881Speter { 942251881Speter serf_bucket_headers_setn(headers, SVN_DAV_DELTA_BASE_HEADER, 943299742Sdim fetch_ctx->delta_base); 944251881Speter serf_bucket_headers_setn(headers, "Accept-Encoding", 945251881Speter "svndiff1;q=0.9,svndiff;q=0.8"); 946251881Speter } 947299742Sdim else if (fetch_ctx->using_compression) 948251881Speter { 949251881Speter serf_bucket_headers_setn(headers, "Accept-Encoding", "gzip"); 950251881Speter } 951251881Speter 952251881Speter return SVN_NO_ERROR; 953251881Speter} 954251881Speter 955251881Speterstatic svn_error_t * 956251881Spetercancel_fetch(serf_request_t *request, 957251881Speter serf_bucket_t *response, 958251881Speter int status_code, 959251881Speter void *baton) 960251881Speter{ 961299742Sdim fetch_ctx_t *fetch_ctx = baton; 962251881Speter 963251881Speter /* Uh-oh. Our connection died on us. 964251881Speter * 965251881Speter * The core ra_serf layer will requeue our request - we just need to note 966251881Speter * that we got cut off in the middle of our song. 967251881Speter */ 968251881Speter if (!response) 969251881Speter { 970251881Speter /* If we already started the fetch and opened the file handle, we need 971251881Speter * to hold subsequent read() ops until we get back to where we were 972251881Speter * before the close and we can then resume the textdelta() calls. 973251881Speter */ 974251881Speter if (fetch_ctx->read_headers) 975251881Speter { 976251881Speter if (!fetch_ctx->aborted_read && fetch_ctx->read_size) 977251881Speter { 978251881Speter fetch_ctx->aborted_read = TRUE; 979251881Speter fetch_ctx->aborted_read_size = fetch_ctx->read_size; 980251881Speter } 981251881Speter fetch_ctx->read_size = 0; 982251881Speter } 983251881Speter 984251881Speter return SVN_NO_ERROR; 985251881Speter } 986251881Speter 987251881Speter /* We have no idea what went wrong. */ 988251881Speter SVN_ERR_MALFUNCTION(); 989251881Speter} 990251881Speter 991251881Speter/* Wield the editor referenced by INFO to open (or add) the file 992251881Speter file also associated with INFO, setting properties on the file and 993251881Speter calling the editor's apply_textdelta() function on it if necessary 994251881Speter (or if FORCE_APPLY_TEXTDELTA is set). 995251881Speter 996251881Speter Callers will probably want to also see the function that serves 997251881Speter the opposite purpose of this one, close_updated_file(). */ 998251881Speterstatic svn_error_t * 999299742Sdimopen_file_txdelta(file_baton_t *file, 1000251881Speter apr_pool_t *scratch_pool) 1001251881Speter{ 1002299742Sdim const svn_delta_editor_t *editor = file->parent_dir->ctx->editor; 1003251881Speter 1004299742Sdim SVN_ERR_ASSERT(file->txdelta == NULL); 1005251881Speter 1006299742Sdim SVN_ERR(ensure_file_opened(file, scratch_pool)); 1007251881Speter 1008251881Speter /* Get (maybe) a textdelta window handler for transmitting file 1009251881Speter content changes. */ 1010299742Sdim SVN_ERR(editor->apply_textdelta(file->file_baton, 1011299742Sdim svn_checksum_to_cstring( 1012299742Sdim file->base_md5_checksum, 1013299742Sdim scratch_pool), 1014299742Sdim file->pool, 1015299742Sdim &file->txdelta, 1016299742Sdim &file->txdelta_baton)); 1017251881Speter 1018251881Speter return SVN_NO_ERROR; 1019251881Speter} 1020251881Speter 1021299742Sdim/* Close the file, handling loose ends and cleanup */ 1022251881Speterstatic svn_error_t * 1023299742Sdimclose_file(file_baton_t *file, 1024299742Sdim apr_pool_t *scratch_pool) 1025251881Speter{ 1026299742Sdim dir_baton_t *parent_dir = file->parent_dir; 1027299742Sdim report_context_t *ctx = parent_dir->ctx; 1028251881Speter 1029299742Sdim SVN_ERR(ensure_file_opened(file, scratch_pool)); 1030299742Sdim 1031251881Speter /* Set all of the properties we received */ 1032299742Sdim if (file->remove_props) 1033251881Speter { 1034299742Sdim apr_hash_index_t *hi; 1035299742Sdim 1036299742Sdim for (hi = apr_hash_first(scratch_pool, file->remove_props); 1037299742Sdim hi; 1038299742Sdim hi = apr_hash_next(hi)) 1039299742Sdim { 1040299742Sdim SVN_ERR(ctx->editor->change_file_prop(file->file_baton, 1041299742Sdim apr_hash_this_key(hi), 1042299742Sdim NULL /* value */, 1043299742Sdim scratch_pool)); 1044299742Sdim } 1045251881Speter } 1046251881Speter 1047299742Sdim /* Check for lock information. */ 1048299742Sdim 1049299742Sdim /* This works around a bug in some older versions of mod_dav_svn in that it 1050299742Sdim * will not send remove-prop in the update report when a lock property 1051299742Sdim * disappears when send-all is false. 1052299742Sdim 1053299742Sdim ### Given that we only fetch props on additions, is this really necessary? 1054299742Sdim Or is it covering up old local copy bugs where we copied locks to other 1055299742Sdim paths? */ 1056299742Sdim if (!ctx->add_props_included 1057299742Sdim && file->lock_token && !file->found_lock_prop 1058299742Sdim && SVN_IS_VALID_REVNUM(file->base_rev) /* file_is_added */) 1059299742Sdim { 1060299742Sdim SVN_ERR(ctx->editor->change_file_prop(file->file_baton, 1061299742Sdim SVN_PROP_ENTRY_LOCK_TOKEN, 1062299742Sdim NULL, 1063299742Sdim scratch_pool)); 1064299742Sdim } 1065299742Sdim 1066299742Sdim if (file->url) 1067299742Sdim { 1068299742Sdim SVN_ERR(ctx->editor->change_file_prop(file->file_baton, 1069299742Sdim SVN_RA_SERF__WC_CHECKED_IN_URL, 1070299742Sdim svn_string_create(file->url, 1071299742Sdim scratch_pool), 1072299742Sdim scratch_pool)); 1073299742Sdim } 1074299742Sdim 1075251881Speter /* Close the file via the editor. */ 1076299742Sdim SVN_ERR(ctx->editor->close_file(file->file_baton, 1077299742Sdim svn_checksum_to_cstring( 1078299742Sdim file->final_md5_checksum, 1079299742Sdim scratch_pool), 1080299742Sdim scratch_pool)); 1081251881Speter 1082299742Sdim svn_pool_destroy(file->pool); 1083251881Speter 1084299742Sdim SVN_ERR(maybe_close_dir(parent_dir)); /* Remove reference */ 1085299742Sdim 1086251881Speter return SVN_NO_ERROR; 1087251881Speter} 1088251881Speter 1089251881Speter/* Implements svn_ra_serf__response_handler_t */ 1090251881Speterstatic svn_error_t * 1091251881Speterhandle_fetch(serf_request_t *request, 1092251881Speter serf_bucket_t *response, 1093251881Speter void *handler_baton, 1094251881Speter apr_pool_t *pool) 1095251881Speter{ 1096251881Speter const char *data; 1097251881Speter apr_size_t len; 1098251881Speter apr_status_t status; 1099299742Sdim fetch_ctx_t *fetch_ctx = handler_baton; 1100299742Sdim file_baton_t *file = fetch_ctx->file; 1101251881Speter 1102251881Speter /* ### new field. make sure we didn't miss some initialization. */ 1103251881Speter SVN_ERR_ASSERT(fetch_ctx->handler != NULL); 1104251881Speter 1105251881Speter if (!fetch_ctx->read_headers) 1106251881Speter { 1107251881Speter serf_bucket_t *hdrs; 1108251881Speter const char *val; 1109251881Speter 1110299742Sdim /* If the error code wasn't 200, something went wrong. Don't use the 1111299742Sdim * returned data as its probably an error message. Just bail out instead. 1112299742Sdim */ 1113299742Sdim if (fetch_ctx->handler->sline.code != 200) 1114299742Sdim { 1115299742Sdim fetch_ctx->handler->discard_body = TRUE; 1116299742Sdim return SVN_NO_ERROR; /* Will return an error in the DONE handler */ 1117299742Sdim } 1118299742Sdim 1119251881Speter hdrs = serf_bucket_response_get_headers(response); 1120251881Speter val = serf_bucket_headers_get(hdrs, "Content-Type"); 1121251881Speter 1122251881Speter if (val && svn_cstring_casecmp(val, SVN_SVNDIFF_MIME_TYPE) == 0) 1123251881Speter { 1124299742Sdim fetch_ctx->result_stream = 1125299742Sdim svn_txdelta_parse_svndiff(file->txdelta, 1126299742Sdim file->txdelta_baton, 1127299742Sdim TRUE, file->pool); 1128251881Speter 1129251881Speter /* Validate the delta base claimed by the server matches 1130251881Speter what we asked for! */ 1131251881Speter val = serf_bucket_headers_get(hdrs, SVN_DAV_DELTA_BASE_HEADER); 1132299742Sdim if (val && fetch_ctx->delta_base == NULL) 1133251881Speter { 1134299742Sdim /* We recieved response with delta base header while we didn't 1135299742Sdim requested it -- report it as error. */ 1136299742Sdim return svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL, 1137299742Sdim _("GET request returned unexpected " 1138299742Sdim "delta base: %s"), val); 1139251881Speter } 1140299742Sdim else if (val && (strcmp(val, fetch_ctx->delta_base) != 0)) 1141299742Sdim { 1142299742Sdim return svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL, 1143299742Sdim _("GET request returned unexpected " 1144299742Sdim "delta base: %s"), val); 1145299742Sdim } 1146251881Speter } 1147251881Speter else 1148251881Speter { 1149299742Sdim fetch_ctx->result_stream = NULL; 1150251881Speter } 1151251881Speter 1152251881Speter fetch_ctx->read_headers = TRUE; 1153251881Speter } 1154251881Speter 1155299742Sdim while (TRUE) 1156251881Speter { 1157251881Speter svn_txdelta_window_t delta_window = { 0 }; 1158251881Speter svn_txdelta_op_t delta_op; 1159251881Speter svn_string_t window_data; 1160251881Speter 1161251881Speter status = serf_bucket_read(response, 8000, &data, &len); 1162251881Speter if (SERF_BUCKET_READ_ERROR(status)) 1163251881Speter { 1164251881Speter return svn_ra_serf__wrap_err(status, NULL); 1165251881Speter } 1166251881Speter 1167251881Speter fetch_ctx->read_size += len; 1168251881Speter 1169251881Speter if (fetch_ctx->aborted_read) 1170251881Speter { 1171251881Speter apr_off_t skip; 1172251881Speter /* We haven't caught up to where we were before. */ 1173251881Speter if (fetch_ctx->read_size < fetch_ctx->aborted_read_size) 1174251881Speter { 1175251881Speter /* Eek. What did the file shrink or something? */ 1176251881Speter if (APR_STATUS_IS_EOF(status)) 1177251881Speter { 1178251881Speter SVN_ERR_MALFUNCTION(); 1179251881Speter } 1180251881Speter 1181251881Speter /* Skip on to the next iteration of this loop. */ 1182299742Sdim if (status /* includes EAGAIN */) 1183299742Sdim return svn_ra_serf__wrap_err(status, NULL); 1184299742Sdim 1185251881Speter continue; 1186251881Speter } 1187251881Speter 1188251881Speter /* Woo-hoo. We're back. */ 1189251881Speter fetch_ctx->aborted_read = FALSE; 1190251881Speter 1191251881Speter /* Update data and len to just provide the new data. */ 1192251881Speter skip = len - (fetch_ctx->read_size - fetch_ctx->aborted_read_size); 1193251881Speter data += skip; 1194299742Sdim len -= (apr_size_t)skip; 1195251881Speter } 1196251881Speter 1197299742Sdim if (fetch_ctx->result_stream) 1198299742Sdim SVN_ERR(svn_stream_write(fetch_ctx->result_stream, data, &len)); 1199299742Sdim 1200251881Speter /* otherwise, manually construct the text delta window. */ 1201251881Speter else if (len) 1202251881Speter { 1203251881Speter window_data.data = data; 1204251881Speter window_data.len = len; 1205251881Speter 1206251881Speter delta_op.action_code = svn_txdelta_new; 1207251881Speter delta_op.offset = 0; 1208251881Speter delta_op.length = len; 1209251881Speter 1210251881Speter delta_window.tview_len = len; 1211251881Speter delta_window.num_ops = 1; 1212251881Speter delta_window.ops = &delta_op; 1213251881Speter delta_window.new_data = &window_data; 1214251881Speter 1215251881Speter /* write to the file located in the info. */ 1216299742Sdim SVN_ERR(file->txdelta(&delta_window, file->txdelta_baton)); 1217251881Speter } 1218251881Speter 1219251881Speter if (APR_STATUS_IS_EOF(status)) 1220251881Speter { 1221299742Sdim if (fetch_ctx->result_stream) 1222299742Sdim SVN_ERR(svn_stream_close(fetch_ctx->result_stream)); 1223251881Speter else 1224299742Sdim SVN_ERR(file->txdelta(NULL, file->txdelta_baton)); 1225299742Sdim } 1226251881Speter 1227299742Sdim /* Report EOF, EEAGAIN and other special errors to serf */ 1228299742Sdim if (status) 1229299742Sdim return svn_ra_serf__wrap_err(status, NULL); 1230299742Sdim } 1231299742Sdim} 1232251881Speter 1233299742Sdim/* --------------------------------------------------------- */ 1234251881Speter 1235299742Sdim/** Wrappers around our various property walkers **/ 1236251881Speter 1237299742Sdim/* Implements svn_ra_serf__prop_func */ 1238251881Speterstatic svn_error_t * 1239299742Sdimset_file_props(void *baton, 1240299742Sdim const char *path, 1241299742Sdim const char *ns, 1242299742Sdim const char *name, 1243299742Sdim const svn_string_t *val, 1244299742Sdim apr_pool_t *scratch_pool) 1245251881Speter{ 1246299742Sdim file_baton_t *file = baton; 1247299742Sdim report_context_t *ctx = file->parent_dir->ctx; 1248299742Sdim const char *prop_name; 1249251881Speter 1250299742Sdim prop_name = svn_ra_serf__svnname_from_wirename(ns, name, scratch_pool); 1251251881Speter 1252299742Sdim if (!prop_name) 1253251881Speter { 1254299742Sdim /* This works around a bug in some older versions of 1255299742Sdim * mod_dav_svn in that it will not send remove-prop in the update 1256299742Sdim * report when a lock property disappears when send-all is false. 1257299742Sdim * 1258299742Sdim * Therefore, we'll try to look at our properties and see if there's 1259299742Sdim * an active lock. If not, then we'll assume there isn't a lock 1260299742Sdim * anymore. 1261299742Sdim */ 1262299742Sdim /* assert(!ctx->add_props_included); // Or we wouldn't be here */ 1263299742Sdim if (file->lock_token 1264299742Sdim && !file->found_lock_prop 1265299742Sdim && val 1266299742Sdim && strcmp(ns, "DAV:") == 0 1267299742Sdim && strcmp(name, "lockdiscovery") == 0) 1268251881Speter { 1269299742Sdim char *new_lock; 1270299742Sdim new_lock = apr_pstrdup(scratch_pool, val->data); 1271299742Sdim apr_collapse_spaces(new_lock, new_lock); 1272251881Speter 1273299742Sdim if (new_lock[0] != '\0') 1274299742Sdim file->found_lock_prop = TRUE; 1275251881Speter } 1276251881Speter 1277299742Sdim return SVN_NO_ERROR; 1278299742Sdim } 1279251881Speter 1280299742Sdim SVN_ERR(ensure_file_opened(file, scratch_pool)); 1281251881Speter 1282299742Sdim SVN_ERR(ctx->editor->change_file_prop(file->file_baton, 1283299742Sdim prop_name, val, 1284299742Sdim scratch_pool)); 1285251881Speter 1286299742Sdim return SVN_NO_ERROR; 1287251881Speter} 1288251881Speter 1289299742Sdim/* Implements svn_ra_serf__response_done_delegate_t */ 1290251881Speterstatic svn_error_t * 1291299742Sdimfile_props_done(serf_request_t *request, 1292299742Sdim void *baton, 1293299742Sdim apr_pool_t *scratch_pool) 1294251881Speter{ 1295299742Sdim file_baton_t *file = baton; 1296299742Sdim svn_ra_serf__handler_t *handler = file->propfind_handler; 1297251881Speter 1298299742Sdim if (handler->server_error) 1299299742Sdim return svn_error_trace(svn_ra_serf__server_error_create(handler, 1300299742Sdim scratch_pool)); 1301251881Speter 1302299742Sdim if (handler->sline.code != 207) 1303299742Sdim return svn_error_trace(svn_ra_serf__unexpected_status(handler)); 1304251881Speter 1305299742Sdim file->parent_dir->ctx->num_active_propfinds--; 1306251881Speter 1307299742Sdim file->fetch_props = FALSE; 1308251881Speter 1309299742Sdim if (file->fetch_file) 1310299742Sdim return SVN_NO_ERROR; /* Still processing file request */ 1311299742Sdim 1312299742Sdim /* Closing the file will automatically deliver the propfind props. 1313299742Sdim * 1314299742Sdim * Note that closing the directory may dispose the pool containing the 1315299742Sdim * handler, which is only a valid operation in this callback, as only 1316299742Sdim * after this callback our serf plumbing assumes the request is done. */ 1317299742Sdim 1318299742Sdim return svn_error_trace(close_file(file, scratch_pool)); 1319251881Speter} 1320251881Speter 1321251881Speterstatic svn_error_t * 1322299742Sdimfile_fetch_done(serf_request_t *request, 1323299742Sdim void *baton, 1324299742Sdim apr_pool_t *scratch_pool) 1325251881Speter{ 1326299742Sdim fetch_ctx_t *fetch_ctx = baton; 1327299742Sdim file_baton_t *file = fetch_ctx->file; 1328299742Sdim svn_ra_serf__handler_t *handler = fetch_ctx->handler; 1329251881Speter 1330299742Sdim if (handler->server_error) 1331299742Sdim return svn_error_trace(svn_ra_serf__server_error_create(handler, 1332299742Sdim scratch_pool)); 1333251881Speter 1334299742Sdim if (handler->sline.code != 200) 1335299742Sdim return svn_error_trace(svn_ra_serf__unexpected_status(handler)); 1336251881Speter 1337299742Sdim file->parent_dir->ctx->num_active_fetches--; 1338251881Speter 1339299742Sdim file->fetch_file = FALSE; 1340251881Speter 1341299742Sdim if (file->fetch_props) 1342299742Sdim return SVN_NO_ERROR; /* Still processing PROPFIND request */ 1343251881Speter 1344299742Sdim /* Closing the file will automatically deliver the propfind props. 1345299742Sdim * 1346299742Sdim * Note that closing the directory may dispose the pool containing the 1347299742Sdim * handler, fetch_ctx, etc. which is only a valid operation in this 1348299742Sdim * callback, as only after this callback our serf plumbing assumes the 1349299742Sdim * request is done. */ 1350299742Sdim return svn_error_trace(close_file(file, scratch_pool)); 1351251881Speter} 1352251881Speter 1353299742Sdim/* Initiates additional requests needed for a file when not in "send-all" mode. 1354299742Sdim */ 1355251881Speterstatic svn_error_t * 1356299742Sdimfetch_for_file(file_baton_t *file, 1357299742Sdim apr_pool_t *scratch_pool) 1358251881Speter{ 1359299742Sdim report_context_t *ctx = file->parent_dir->ctx; 1360251881Speter svn_ra_serf__connection_t *conn; 1361251881Speter svn_ra_serf__handler_t *handler; 1362251881Speter 1363299742Sdim /* Open extra connections if we have enough requests to send. */ 1364299742Sdim if (ctx->sess->num_conns < ctx->sess->max_connections) 1365299742Sdim SVN_ERR(open_connection_if_needed(ctx->sess, ctx->num_active_fetches + 1366299742Sdim ctx->num_active_propfinds)); 1367299742Sdim 1368251881Speter /* What connection should we go on? */ 1369251881Speter conn = get_best_connection(ctx); 1370251881Speter 1371299742Sdim /* Note that we (still) use conn for both requests.. Should we send 1372299742Sdim them out on different connections? */ 1373251881Speter 1374299742Sdim if (file->fetch_file) 1375251881Speter { 1376299742Sdim SVN_ERR(open_file_txdelta(file, scratch_pool)); 1377251881Speter 1378299742Sdim if (!ctx->text_deltas 1379299742Sdim || file->txdelta == svn_delta_noop_window_handler) 1380251881Speter { 1381299742Sdim SVN_ERR(file->txdelta(NULL, file->txdelta_baton)); 1382299742Sdim file->fetch_file = FALSE; 1383299742Sdim } 1384251881Speter 1385299742Sdim if (file->fetch_file 1386299742Sdim && file->final_sha1_checksum 1387299742Sdim && ctx->sess->wc_callbacks->get_wc_contents) 1388251881Speter { 1389299742Sdim svn_error_t *err; 1390299742Sdim svn_stream_t *cached_contents = NULL; 1391251881Speter 1392299742Sdim err = ctx->sess->wc_callbacks->get_wc_contents( 1393299742Sdim ctx->sess->wc_callback_baton, 1394299742Sdim &cached_contents, 1395299742Sdim file->final_sha1_checksum, 1396299742Sdim scratch_pool); 1397251881Speter 1398299742Sdim if (err || !cached_contents) 1399299742Sdim svn_error_clear(err); /* ### Can we return some/most errors? */ 1400299742Sdim else 1401251881Speter { 1402299742Sdim /* ### For debugging purposes we could validate the md5 here, 1403299742Sdim but our implementations in libsvn_client already do that 1404299742Sdim for us... */ 1405299742Sdim SVN_ERR(svn_txdelta_send_stream(cached_contents, 1406299742Sdim file->txdelta, 1407299742Sdim file->txdelta_baton, 1408299742Sdim NULL, scratch_pool)); 1409299742Sdim SVN_ERR(svn_stream_close(cached_contents)); 1410299742Sdim file->fetch_file = FALSE; 1411251881Speter } 1412251881Speter } 1413251881Speter 1414299742Sdim if (file->fetch_file) 1415251881Speter { 1416299742Sdim fetch_ctx_t *fetch_ctx; 1417299742Sdim 1418299742Sdim /* Let's fetch the file with a GET request... */ 1419299742Sdim SVN_ERR_ASSERT(file->url && file->repos_relpath); 1420299742Sdim 1421299742Sdim /* Otherwise, we use a GET request for the file's contents. */ 1422299742Sdim 1423299742Sdim fetch_ctx = apr_pcalloc(file->pool, sizeof(*fetch_ctx)); 1424299742Sdim fetch_ctx->file = file; 1425299742Sdim fetch_ctx->using_compression = ctx->sess->using_compression; 1426299742Sdim 1427299742Sdim /* Can we somehow get away with just obtaining a DIFF? */ 1428299742Sdim if (SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(ctx->sess)) 1429251881Speter { 1430299742Sdim /* If this file is switched vs the editor root we should provide 1431299742Sdim its real url instead of the one calculated from the session root. 1432299742Sdim */ 1433299742Sdim if (SVN_IS_VALID_REVNUM(file->base_rev)) 1434299742Sdim { 1435299742Sdim fetch_ctx->delta_base = apr_psprintf(file->pool, "%s/%ld/%s", 1436299742Sdim ctx->sess->rev_root_stub, 1437299742Sdim file->base_rev, 1438299742Sdim svn_path_uri_encode( 1439299742Sdim file->repos_relpath, 1440299742Sdim scratch_pool)); 1441299742Sdim } 1442299742Sdim else if (file->copyfrom_path) 1443299742Sdim { 1444299742Sdim SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(file->copyfrom_rev)); 1445251881Speter 1446299742Sdim fetch_ctx->delta_base = apr_psprintf(file->pool, "%s/%ld/%s", 1447299742Sdim ctx->sess->rev_root_stub, 1448299742Sdim file->copyfrom_rev, 1449299742Sdim svn_path_uri_encode( 1450299742Sdim file->copyfrom_path+1, 1451299742Sdim scratch_pool)); 1452299742Sdim } 1453251881Speter } 1454299742Sdim else if (ctx->sess->wc_callbacks->get_wc_prop) 1455251881Speter { 1456299742Sdim /* If we have a WC, we might be able to dive all the way into the WC 1457299742Sdim * to get the previous URL so we can do a differential GET with the 1458299742Sdim * base URL. 1459299742Sdim */ 1460299742Sdim const svn_string_t *value = NULL; 1461299742Sdim SVN_ERR(ctx->sess->wc_callbacks->get_wc_prop( 1462299742Sdim ctx->sess->wc_callback_baton, 1463299742Sdim file->relpath, 1464299742Sdim SVN_RA_SERF__WC_CHECKED_IN_URL, 1465299742Sdim &value, scratch_pool)); 1466299742Sdim 1467299742Sdim fetch_ctx->delta_base = value 1468299742Sdim ? apr_pstrdup(file->pool, value->data) 1469299742Sdim : NULL; 1470251881Speter } 1471251881Speter 1472299742Sdim handler = svn_ra_serf__create_handler(ctx->sess, file->pool); 1473251881Speter 1474251881Speter handler->method = "GET"; 1475299742Sdim handler->path = file->url; 1476251881Speter 1477299742Sdim handler->conn = conn; /* Explicit scheduling */ 1478251881Speter 1479251881Speter handler->custom_accept_encoding = TRUE; 1480299742Sdim handler->no_dav_headers = TRUE; 1481251881Speter handler->header_delegate = headers_fetch; 1482251881Speter handler->header_delegate_baton = fetch_ctx; 1483251881Speter 1484251881Speter handler->response_handler = handle_fetch; 1485251881Speter handler->response_baton = fetch_ctx; 1486251881Speter 1487251881Speter handler->response_error = cancel_fetch; 1488251881Speter handler->response_error_baton = fetch_ctx; 1489251881Speter 1490299742Sdim handler->done_delegate = file_fetch_done; 1491299742Sdim handler->done_delegate_baton = fetch_ctx; 1492299742Sdim 1493251881Speter fetch_ctx->handler = handler; 1494251881Speter 1495251881Speter svn_ra_serf__request_create(handler); 1496251881Speter 1497251881Speter ctx->num_active_fetches++; 1498251881Speter } 1499251881Speter } 1500299742Sdim 1501299742Sdim /* If needed, create the PROPFIND to retrieve the file's properties. */ 1502299742Sdim if (file->fetch_props) 1503251881Speter { 1504299742Sdim SVN_ERR(svn_ra_serf__create_propfind_handler(&file->propfind_handler, 1505299742Sdim ctx->sess, file->url, 1506299742Sdim ctx->target_rev, "0", 1507299742Sdim all_props, 1508299742Sdim set_file_props, file, 1509299742Sdim file->pool)); 1510299742Sdim file->propfind_handler->conn = conn; /* Explicit scheduling */ 1511251881Speter 1512299742Sdim file->propfind_handler->done_delegate = file_props_done; 1513299742Sdim file->propfind_handler->done_delegate_baton = file; 1514299742Sdim 1515299742Sdim /* Create a serf request for the PROPFIND. */ 1516299742Sdim svn_ra_serf__request_create(file->propfind_handler); 1517299742Sdim 1518299742Sdim ctx->num_active_propfinds++; 1519251881Speter } 1520251881Speter 1521299742Sdim if (file->fetch_props || file->fetch_file) 1522299742Sdim return SVN_NO_ERROR; 1523251881Speter 1524299742Sdim 1525299742Sdim /* Somehow we are done; probably via the local cache. 1526299742Sdim Close the file and release memory, etc. */ 1527299742Sdim 1528299742Sdim return svn_error_trace(close_file(file, scratch_pool)); 1529251881Speter} 1530251881Speter 1531299742Sdim/* Implements svn_ra_serf__prop_func */ 1532251881Speterstatic svn_error_t * 1533299742Sdimset_dir_prop(void *baton, 1534299742Sdim const char *path, 1535299742Sdim const char *ns, 1536299742Sdim const char *name, 1537299742Sdim const svn_string_t *val, 1538251881Speter apr_pool_t *scratch_pool) 1539251881Speter{ 1540299742Sdim dir_baton_t *dir = baton; 1541299742Sdim report_context_t *ctx = dir->ctx; 1542299742Sdim const char *prop_name; 1543251881Speter 1544299742Sdim prop_name = svn_ra_serf__svnname_from_wirename(ns, name, scratch_pool); 1545299742Sdim if (prop_name == NULL) 1546299742Sdim return SVN_NO_ERROR; 1547251881Speter 1548299742Sdim SVN_ERR(ensure_dir_opened(dir, scratch_pool)); 1549251881Speter 1550299742Sdim SVN_ERR(ctx->editor->change_dir_prop(dir->dir_baton, 1551299742Sdim prop_name, val, 1552299742Sdim scratch_pool)); 1553299742Sdim return SVN_NO_ERROR; 1554299742Sdim} 1555251881Speter 1556299742Sdim/* Implements svn_ra_serf__response_done_delegate_t */ 1557299742Sdimstatic svn_error_t * 1558299742Sdimdir_props_done(serf_request_t *request, 1559299742Sdim void *baton, 1560299742Sdim apr_pool_t *scratch_pool) 1561299742Sdim{ 1562299742Sdim dir_baton_t *dir = baton; 1563299742Sdim svn_ra_serf__handler_t *handler = dir->propfind_handler; 1564251881Speter 1565299742Sdim if (handler->server_error) 1566299742Sdim return svn_ra_serf__server_error_create(handler, scratch_pool); 1567251881Speter 1568299742Sdim if (handler->sline.code != 207) 1569299742Sdim return svn_error_trace(svn_ra_serf__unexpected_status(handler)); 1570251881Speter 1571299742Sdim dir->ctx->num_active_propfinds--; 1572251881Speter 1573299742Sdim /* Closing the directory will automatically deliver the propfind props. 1574299742Sdim * 1575299742Sdim * Note that closing the directory may dispose the pool containing the 1576299742Sdim * handler, which is only a valid operation in this callback, as after 1577299742Sdim * this callback serf assumes the request is done. */ 1578251881Speter 1579299742Sdim return svn_error_trace(maybe_close_dir(dir)); 1580299742Sdim} 1581251881Speter 1582299742Sdim/* Initiates additional requests needed for a directory when not in "send-all" 1583299742Sdim * mode */ 1584299742Sdimstatic svn_error_t * 1585299742Sdimfetch_for_dir(dir_baton_t *dir, 1586299742Sdim apr_pool_t *scratch) 1587299742Sdim{ 1588299742Sdim report_context_t *ctx = dir->ctx; 1589299742Sdim svn_ra_serf__connection_t *conn; 1590251881Speter 1591299742Sdim /* Open extra connections if we have enough requests to send. */ 1592299742Sdim if (ctx->sess->num_conns < ctx->sess->max_connections) 1593299742Sdim SVN_ERR(open_connection_if_needed(ctx->sess, ctx->num_active_fetches + 1594299742Sdim ctx->num_active_propfinds)); 1595251881Speter 1596299742Sdim /* What connection should we go on? */ 1597299742Sdim conn = get_best_connection(ctx); 1598251881Speter 1599299742Sdim /* If needed, create the PROPFIND to retrieve the file's properties. */ 1600299742Sdim if (dir->fetch_props) 1601299742Sdim { 1602299742Sdim SVN_ERR(svn_ra_serf__create_propfind_handler(&dir->propfind_handler, 1603299742Sdim ctx->sess, dir->url, 1604299742Sdim ctx->target_rev, "0", 1605299742Sdim all_props, 1606299742Sdim set_dir_prop, dir, 1607299742Sdim dir->pool)); 1608251881Speter 1609299742Sdim dir->propfind_handler->conn = conn; 1610299742Sdim dir->propfind_handler->done_delegate = dir_props_done; 1611299742Sdim dir->propfind_handler->done_delegate_baton = dir; 1612251881Speter 1613299742Sdim /* Create a serf request for the PROPFIND. */ 1614299742Sdim svn_ra_serf__request_create(dir->propfind_handler); 1615251881Speter 1616299742Sdim ctx->num_active_propfinds++; 1617251881Speter } 1618299742Sdim else 1619299742Sdim SVN_ERR_MALFUNCTION(); 1620251881Speter 1621299742Sdim return SVN_NO_ERROR; 1622299742Sdim} 1623251881Speter 1624299742Sdim 1625299742Sdim/** XML callbacks for our update-report response parsing */ 1626251881Speter 1627299742Sdim/* Conforms to svn_ra_serf__xml_opened_t */ 1628299742Sdimstatic svn_error_t * 1629299742Sdimupdate_opened(svn_ra_serf__xml_estate_t *xes, 1630299742Sdim void *baton, 1631299742Sdim int entered_state, 1632299742Sdim const svn_ra_serf__dav_props_t *tag, 1633299742Sdim apr_pool_t *scratch_pool) 1634299742Sdim{ 1635299742Sdim report_context_t *ctx = baton; 1636299742Sdim apr_hash_t *attrs; 1637251881Speter 1638299742Sdim switch (entered_state) 1639299742Sdim { 1640299742Sdim case UPDATE_REPORT: 1641251881Speter { 1642299742Sdim const char *val; 1643251881Speter 1644299742Sdim attrs = svn_ra_serf__xml_gather_since(xes, UPDATE_REPORT); 1645299742Sdim val = svn_hash_gets(attrs, "inline-props"); 1646251881Speter 1647299742Sdim if (val && (strcmp(val, "true") == 0)) 1648299742Sdim ctx->add_props_included = TRUE; 1649251881Speter 1650299742Sdim val = svn_hash_gets(attrs, "send-all"); 1651251881Speter 1652299742Sdim if (val && (strcmp(val, "true") == 0)) 1653299742Sdim { 1654299742Sdim ctx->send_all_mode = TRUE; 1655251881Speter 1656299742Sdim /* All properties are included in send-all mode. */ 1657299742Sdim ctx->add_props_included = TRUE; 1658299742Sdim } 1659299742Sdim } 1660299742Sdim break; 1661251881Speter 1662299742Sdim case OPEN_DIR: 1663299742Sdim case ADD_DIR: 1664251881Speter { 1665299742Sdim dir_baton_t *dir; 1666299742Sdim const char *name; 1667299742Sdim attrs = svn_ra_serf__xml_gather_since(xes, entered_state); 1668251881Speter 1669299742Sdim name = svn_hash_gets(attrs, "name"); 1670299742Sdim if (!name) 1671299742Sdim name = ""; 1672251881Speter 1673299742Sdim SVN_ERR(create_dir_baton(&dir, ctx, name, scratch_pool)); 1674251881Speter 1675299742Sdim if (entered_state == OPEN_DIR) 1676299742Sdim { 1677299742Sdim apr_int64_t base_rev; 1678251881Speter 1679299742Sdim SVN_ERR(svn_cstring_atoi64(&base_rev, 1680299742Sdim svn_hash_gets(attrs, "rev"))); 1681299742Sdim dir->base_rev = (svn_revnum_t)base_rev; 1682299742Sdim } 1683299742Sdim else 1684299742Sdim { 1685299742Sdim dir->copyfrom_path = svn_hash_gets(attrs, "copyfrom-path"); 1686251881Speter 1687299742Sdim if (dir->copyfrom_path) 1688299742Sdim { 1689299742Sdim apr_int64_t copyfrom_rev; 1690299742Sdim const char *copyfrom_rev_str; 1691299742Sdim dir->copyfrom_path = svn_fspath__canonicalize( 1692299742Sdim dir->copyfrom_path, 1693299742Sdim dir->pool); 1694251881Speter 1695299742Sdim copyfrom_rev_str = svn_hash_gets(attrs, "copyfrom-rev"); 1696251881Speter 1697299742Sdim if (!copyfrom_rev_str) 1698299742Sdim return svn_error_createf(SVN_ERR_XML_ATTRIB_NOT_FOUND, 1699299742Sdim NULL, 1700299742Sdim _("Missing '%s' attribute"), 1701299742Sdim "copyfrom-rev"); 1702251881Speter 1703299742Sdim SVN_ERR(svn_cstring_atoi64(©from_rev, copyfrom_rev_str)); 1704251881Speter 1705299742Sdim dir->copyfrom_rev = (svn_revnum_t)copyfrom_rev; 1706299742Sdim } 1707251881Speter 1708299742Sdim if (! ctx->add_props_included) 1709299742Sdim dir->fetch_props = TRUE; 1710299742Sdim } 1711251881Speter } 1712299742Sdim break; 1713299742Sdim case OPEN_FILE: 1714299742Sdim case ADD_FILE: 1715251881Speter { 1716299742Sdim file_baton_t *file; 1717251881Speter 1718299742Sdim attrs = svn_ra_serf__xml_gather_since(xes, entered_state); 1719251881Speter 1720299742Sdim SVN_ERR(create_file_baton(&file, ctx, svn_hash_gets(attrs, "name"), 1721299742Sdim scratch_pool)); 1722251881Speter 1723299742Sdim if (entered_state == OPEN_FILE) 1724299742Sdim { 1725299742Sdim apr_int64_t base_rev; 1726251881Speter 1727299742Sdim SVN_ERR(svn_cstring_atoi64(&base_rev, 1728299742Sdim svn_hash_gets(attrs, "rev"))); 1729299742Sdim file->base_rev = (svn_revnum_t)base_rev; 1730299742Sdim } 1731299742Sdim else 1732299742Sdim { 1733299742Sdim const char *sha1_checksum; 1734299742Sdim file->copyfrom_path = svn_hash_gets(attrs, "copyfrom-path"); 1735251881Speter 1736299742Sdim if (file->copyfrom_path) 1737299742Sdim { 1738299742Sdim apr_int64_t copyfrom_rev; 1739299742Sdim const char *copyfrom_rev_str; 1740251881Speter 1741299742Sdim file->copyfrom_path = svn_fspath__canonicalize( 1742299742Sdim file->copyfrom_path, 1743299742Sdim file->pool); 1744251881Speter 1745299742Sdim copyfrom_rev_str = svn_hash_gets(attrs, "copyfrom-rev"); 1746251881Speter 1747299742Sdim if (!copyfrom_rev_str) 1748299742Sdim return svn_error_createf(SVN_ERR_XML_ATTRIB_NOT_FOUND, 1749299742Sdim NULL, 1750299742Sdim _("Missing '%s' attribute"), 1751299742Sdim "copyfrom-rev"); 1752251881Speter 1753299742Sdim SVN_ERR(svn_cstring_atoi64(©from_rev, copyfrom_rev_str)); 1754251881Speter 1755299742Sdim file->copyfrom_rev = (svn_revnum_t)copyfrom_rev; 1756299742Sdim } 1757251881Speter 1758299742Sdim sha1_checksum = svn_hash_gets(attrs, "sha1-checksum"); 1759299742Sdim if (sha1_checksum) 1760299742Sdim { 1761299742Sdim SVN_ERR(svn_checksum_parse_hex(&file->final_sha1_checksum, 1762299742Sdim svn_checksum_sha1, 1763299742Sdim sha1_checksum, 1764299742Sdim file->pool)); 1765299742Sdim } 1766251881Speter 1767299742Sdim /* If the server isn't in "send-all" mode, we should expect to 1768299742Sdim fetch contents for added files. */ 1769299742Sdim if (! ctx->send_all_mode) 1770299742Sdim file->fetch_file = TRUE; 1771251881Speter 1772299742Sdim /* If the server isn't included properties for added items, 1773299742Sdim we'll need to fetch them ourselves. */ 1774299742Sdim if (! ctx->add_props_included) 1775299742Sdim file->fetch_props = TRUE; 1776299742Sdim } 1777299742Sdim } 1778299742Sdim break; 1779251881Speter 1780299742Sdim case TXDELTA: 1781251881Speter { 1782299742Sdim file_baton_t *file = ctx->cur_file; 1783299742Sdim const char *base_checksum; 1784251881Speter 1785299742Sdim /* Pre 1.2, mod_dav_svn was using <txdelta> tags (in 1786299742Sdim addition to <fetch-file>s and such) when *not* in 1787299742Sdim "send-all" mode. As a client, we're smart enough to know 1788299742Sdim that's wrong, so we'll just ignore these tags. */ 1789299742Sdim if (! ctx->send_all_mode) 1790299742Sdim break; 1791251881Speter 1792299742Sdim file->fetch_file = FALSE; 1793251881Speter 1794299742Sdim attrs = svn_ra_serf__xml_gather_since(xes, entered_state); 1795299742Sdim base_checksum = svn_hash_gets(attrs, "base-checksum"); 1796251881Speter 1797299742Sdim if (base_checksum) 1798299742Sdim SVN_ERR(svn_checksum_parse_hex(&file->base_md5_checksum, 1799299742Sdim svn_checksum_md5, base_checksum, 1800299742Sdim file->pool)); 1801251881Speter 1802299742Sdim SVN_ERR(open_file_txdelta(ctx->cur_file, scratch_pool)); 1803251881Speter 1804299742Sdim if (ctx->cur_file->txdelta != svn_delta_noop_window_handler) 1805299742Sdim { 1806299742Sdim svn_stream_t *decoder; 1807251881Speter 1808299742Sdim decoder = svn_txdelta_parse_svndiff(file->txdelta, 1809299742Sdim file->txdelta_baton, 1810299742Sdim TRUE /* error early close*/, 1811299742Sdim file->pool); 1812251881Speter 1813299742Sdim file->txdelta_stream = svn_base64_decode(decoder, file->pool); 1814299742Sdim } 1815299742Sdim } 1816299742Sdim break; 1817251881Speter 1818299742Sdim case FETCH_PROPS: 1819251881Speter { 1820299742Sdim /* Subversion <= 1.6 servers will return a fetch-props element on 1821299742Sdim open-file and open-dir when non entry props were changed in 1822299742Sdim !send-all mode. In turn we fetch the full set of properties 1823299742Sdim and send all of those as *changes* to the editor. So these 1824299742Sdim editors have to be aware that they receive-non property changes. 1825299742Sdim (In case of incomplete directories they have to be aware anyway) 1826251881Speter 1827299742Sdim In r1063337 this behavior was changed in mod_dav_svn to always 1828299742Sdim send property changes inline in these cases. (See issue #3657) 1829251881Speter 1830299742Sdim Note that before that change the property changes to the last_* 1831299742Sdim entry props were already inlined via specific xml elements. */ 1832299742Sdim if (ctx->cur_file) 1833299742Sdim ctx->cur_file->fetch_props = TRUE; 1834299742Sdim else if (ctx->cur_dir) 1835299742Sdim ctx->cur_dir->fetch_props = TRUE; 1836299742Sdim } 1837299742Sdim break; 1838251881Speter } 1839251881Speter 1840299742Sdim return SVN_NO_ERROR; 1841299742Sdim} 1842251881Speter 1843251881Speter 1844251881Speter 1845299742Sdim/* Conforms to svn_ra_serf__xml_closed_t */ 1846299742Sdimstatic svn_error_t * 1847299742Sdimupdate_closed(svn_ra_serf__xml_estate_t *xes, 1848299742Sdim void *baton, 1849299742Sdim int leaving_state, 1850299742Sdim const svn_string_t *cdata, 1851299742Sdim apr_hash_t *attrs, 1852299742Sdim apr_pool_t *scratch_pool) 1853299742Sdim{ 1854299742Sdim report_context_t *ctx = baton; 1855251881Speter 1856299742Sdim switch (leaving_state) 1857251881Speter { 1858299742Sdim case UPDATE_REPORT: 1859299742Sdim ctx->done = TRUE; 1860299742Sdim break; 1861299742Sdim case TARGET_REVISION: 1862251881Speter { 1863299742Sdim const char *revstr = svn_hash_gets(attrs, "rev"); 1864299742Sdim apr_int64_t rev; 1865251881Speter 1866299742Sdim SVN_ERR(svn_cstring_atoi64(&rev, revstr)); 1867251881Speter 1868299742Sdim SVN_ERR(ctx->editor->set_target_revision(ctx->editor_baton, 1869299742Sdim (svn_revnum_t)rev, 1870299742Sdim scratch_pool)); 1871251881Speter } 1872299742Sdim break; 1873251881Speter 1874299742Sdim case CHECKED_IN_HREF: 1875299742Sdim if (ctx->cur_file) 1876299742Sdim ctx->cur_file->url = apr_pstrdup(ctx->cur_file->pool, cdata->data); 1877299742Sdim else 1878299742Sdim ctx->cur_dir->url = apr_pstrdup(ctx->cur_dir->pool, cdata->data); 1879299742Sdim break; 1880251881Speter 1881299742Sdim case SET_PROP: 1882299742Sdim case REMOVE_PROP: 1883251881Speter { 1884299742Sdim const char *name = svn_hash_gets(attrs, "name"); 1885299742Sdim const char *encoding; 1886299742Sdim const svn_string_t *value; 1887251881Speter 1888299742Sdim if (leaving_state == REMOVE_PROP) 1889299742Sdim value = NULL; 1890299742Sdim else if ((encoding = svn_hash_gets(attrs, "encoding"))) 1891299742Sdim { 1892299742Sdim if (strcmp(encoding, "base64") != 0) 1893299742Sdim return svn_error_createf(SVN_ERR_XML_UNKNOWN_ENCODING, NULL, 1894299742Sdim _("Got unrecognized encoding '%s'"), 1895299742Sdim encoding); 1896251881Speter 1897299742Sdim value = svn_base64_decode_string(cdata, scratch_pool); 1898299742Sdim } 1899299742Sdim else 1900299742Sdim value = cdata; 1901251881Speter 1902299742Sdim if (ctx->cur_file) 1903299742Sdim { 1904299742Sdim file_baton_t *file = ctx->cur_file; 1905251881Speter 1906299742Sdim if (value 1907299742Sdim || ctx->add_props_included 1908299742Sdim || SVN_IS_VALID_REVNUM(file->base_rev)) 1909299742Sdim { 1910299742Sdim SVN_ERR(ensure_file_opened(file, scratch_pool)); 1911251881Speter 1912299742Sdim SVN_ERR(ctx->editor->change_file_prop(file->file_baton, 1913299742Sdim name, 1914299742Sdim value, 1915299742Sdim scratch_pool)); 1916299742Sdim } 1917299742Sdim else 1918299742Sdim { 1919299742Sdim if (!file->remove_props) 1920299742Sdim file->remove_props = apr_hash_make(file->pool); 1921251881Speter 1922299742Sdim svn_hash_sets(file->remove_props, 1923299742Sdim apr_pstrdup(file->pool, name), 1924299742Sdim ""); 1925299742Sdim } 1926251881Speter } 1927251881Speter else 1928251881Speter { 1929299742Sdim dir_baton_t *dir = ctx->cur_dir; 1930251881Speter 1931299742Sdim if (value 1932299742Sdim || ctx->add_props_included 1933299742Sdim || SVN_IS_VALID_REVNUM(dir->base_rev)) 1934299742Sdim { 1935299742Sdim SVN_ERR(ensure_dir_opened(dir, scratch_pool)); 1936251881Speter 1937299742Sdim SVN_ERR(ctx->editor->change_dir_prop(dir->dir_baton, 1938299742Sdim name, 1939299742Sdim value, 1940299742Sdim scratch_pool)); 1941299742Sdim } 1942299742Sdim else 1943251881Speter { 1944299742Sdim if (!dir->remove_props) 1945299742Sdim dir->remove_props = apr_hash_make(dir->pool); 1946299742Sdim 1947299742Sdim svn_hash_sets(dir->remove_props, 1948299742Sdim apr_pstrdup(dir->pool, name), 1949299742Sdim ""); 1950251881Speter } 1951251881Speter } 1952251881Speter } 1953299742Sdim break; 1954299742Sdim 1955299742Sdim case OPEN_DIR: 1956299742Sdim case ADD_DIR: 1957251881Speter { 1958299742Sdim dir_baton_t *dir = ctx->cur_dir; 1959299742Sdim ctx->cur_dir = ctx->cur_dir->parent_dir; 1960251881Speter 1961299742Sdim if (dir->fetch_props && ! dir->url) 1962299742Sdim { 1963299742Sdim return svn_error_create(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL, 1964299742Sdim _("The REPORT response did not " 1965299742Sdim "include the requested checked-in " 1966299742Sdim "value")); 1967299742Sdim } 1968251881Speter 1969299742Sdim if (!dir->fetch_props) 1970299742Sdim { 1971299742Sdim SVN_ERR(maybe_close_dir(dir)); 1972299742Sdim break; /* dir potentially no longer valid */ 1973299742Sdim } 1974299742Sdim else 1975299742Sdim { 1976299742Sdim /* Otherwise, if the server is *not* in "send-all" mode, we 1977299742Sdim are at a point where we can queue up the PROPFIND request */ 1978299742Sdim SVN_ERR(fetch_for_dir(dir, scratch_pool)); 1979299742Sdim } 1980299742Sdim } 1981299742Sdim break; 1982251881Speter 1983299742Sdim case OPEN_FILE: 1984299742Sdim case ADD_FILE: 1985251881Speter { 1986299742Sdim file_baton_t *file = ctx->cur_file; 1987251881Speter 1988299742Sdim ctx->cur_file = NULL; 1989299742Sdim /* go fetch info->name from DAV:checked-in */ 1990251881Speter 1991299742Sdim if ((file->fetch_file || file->fetch_props) && ! file->url) 1992299742Sdim { 1993299742Sdim return svn_error_create(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL, 1994299742Sdim _("The REPORT response did not " 1995299742Sdim "include the requested checked-in " 1996299742Sdim "value")); 1997299742Sdim } 1998251881Speter 1999299742Sdim /* If the server is in "send-all" mode or didn't get further work, 2000299742Sdim we can now close the file */ 2001299742Sdim if (! file->fetch_file && ! file->fetch_props) 2002299742Sdim { 2003299742Sdim SVN_ERR(close_file(file, scratch_pool)); 2004299742Sdim break; /* file is no longer valid */ 2005299742Sdim } 2006299742Sdim else 2007299742Sdim { 2008299742Sdim /* Otherwise, if the server is *not* in "send-all" mode, we 2009299742Sdim should be at a point where we can queue up any auxiliary 2010299742Sdim content-fetching requests. */ 2011299742Sdim SVN_ERR(fetch_for_file(file, scratch_pool)); 2012299742Sdim } 2013251881Speter } 2014299742Sdim break; 2015251881Speter 2016299742Sdim case MD5_CHECKSUM: 2017299742Sdim SVN_ERR(svn_checksum_parse_hex(&ctx->cur_file->final_md5_checksum, 2018299742Sdim svn_checksum_md5, 2019299742Sdim cdata->data, 2020299742Sdim ctx->cur_file->pool)); 2021299742Sdim break; 2022251881Speter 2023299742Sdim case FETCH_FILE: 2024251881Speter { 2025299742Sdim file_baton_t *file = ctx->cur_file; 2026299742Sdim const char *base_checksum = svn_hash_gets(attrs, "base-checksum"); 2027299742Sdim const char *sha1_checksum = svn_hash_gets(attrs, "sha1-checksum"); 2028251881Speter 2029299742Sdim if (base_checksum) 2030299742Sdim SVN_ERR(svn_checksum_parse_hex(&file->base_md5_checksum, 2031299742Sdim svn_checksum_md5, base_checksum, 2032299742Sdim file->pool)); 2033251881Speter 2034299742Sdim /* Property is duplicated between add-file and fetch-file */ 2035299742Sdim if (sha1_checksum && !file->final_sha1_checksum) 2036299742Sdim SVN_ERR(svn_checksum_parse_hex(&file->final_sha1_checksum, 2037299742Sdim svn_checksum_sha1, 2038299742Sdim sha1_checksum, 2039299742Sdim file->pool)); 2040251881Speter 2041299742Sdim /* Some 0.3x mod_dav_svn wrote both txdelta and fetch-file 2042299742Sdim elements in send-all mode. (See neon for history) */ 2043299742Sdim if (! ctx->send_all_mode) 2044299742Sdim file->fetch_file = TRUE; 2045251881Speter } 2046299742Sdim break; 2047251881Speter 2048299742Sdim case DELETE_ENTRY: 2049251881Speter { 2050299742Sdim const char *name = svn_hash_gets(attrs, "name"); 2051299742Sdim const char *revstr; 2052299742Sdim apr_int64_t delete_rev; 2053251881Speter 2054299742Sdim SVN_ERR(ensure_dir_opened(ctx->cur_dir, scratch_pool)); 2055251881Speter 2056299742Sdim revstr = svn_hash_gets(attrs, "rev"); 2057251881Speter 2058299742Sdim if (revstr) 2059299742Sdim SVN_ERR(svn_cstring_atoi64(&delete_rev, revstr)); 2060299742Sdim else 2061299742Sdim delete_rev = SVN_INVALID_REVNUM; 2062251881Speter 2063299742Sdim SVN_ERR(ctx->editor->delete_entry( 2064299742Sdim svn_relpath_join(ctx->cur_dir->relpath, 2065299742Sdim name, 2066299742Sdim scratch_pool), 2067299742Sdim (svn_revnum_t)delete_rev, 2068299742Sdim ctx->cur_dir->dir_baton, 2069299742Sdim scratch_pool)); 2070299742Sdim } 2071299742Sdim break; 2072251881Speter 2073299742Sdim case ABSENT_DIR: 2074251881Speter { 2075299742Sdim const char *name = svn_hash_gets(attrs, "name"); 2076251881Speter 2077299742Sdim SVN_ERR(ensure_dir_opened(ctx->cur_dir, scratch_pool)); 2078251881Speter 2079299742Sdim SVN_ERR(ctx->editor->absent_directory( 2080299742Sdim svn_relpath_join(ctx->cur_dir->relpath, 2081299742Sdim name, scratch_pool), 2082299742Sdim ctx->cur_dir->dir_baton, 2083299742Sdim scratch_pool)); 2084251881Speter } 2085299742Sdim break; 2086299742Sdim case ABSENT_FILE: 2087251881Speter { 2088299742Sdim const char *name = svn_hash_gets(attrs, "name"); 2089251881Speter 2090299742Sdim SVN_ERR(ensure_dir_opened(ctx->cur_dir, scratch_pool)); 2091251881Speter 2092299742Sdim SVN_ERR(ctx->editor->absent_file( 2093299742Sdim svn_relpath_join(ctx->cur_dir->relpath, 2094299742Sdim name, scratch_pool), 2095299742Sdim ctx->cur_dir->dir_baton, 2096299742Sdim scratch_pool)); 2097251881Speter } 2098299742Sdim break; 2099251881Speter 2100299742Sdim case TXDELTA: 2101251881Speter { 2102299742Sdim file_baton_t *file = ctx->cur_file; 2103299742Sdim 2104299742Sdim if (file->txdelta_stream) 2105251881Speter { 2106299742Sdim SVN_ERR(svn_stream_close(file->txdelta_stream)); 2107299742Sdim file->txdelta_stream = NULL; 2108251881Speter } 2109251881Speter } 2110299742Sdim break; 2111251881Speter 2112299742Sdim case VERSION_NAME: 2113299742Sdim case CREATIONDATE: 2114299742Sdim case CREATOR_DISPLAYNAME: 2115251881Speter { 2116299742Sdim /* Subversion <= 1.6 servers would return a fetch-props element on 2117299742Sdim open-file and open-dir when non entry props were changed in 2118299742Sdim !send-all mode. In turn we fetch the full set of properties and 2119299742Sdim send those as *changes* to the editor. So these editors have to 2120299742Sdim be aware that they receive non property changes. 2121299742Sdim (In case of incomplete directories they have to be aware anyway) 2122251881Speter 2123299742Sdim In that case the last_* entry props are posted as 3 specific xml 2124299742Sdim elements, which we handle here. 2125251881Speter 2126299742Sdim In r1063337 this behavior was changed in mod_dav_svn to always 2127299742Sdim send property changes inline in these cases. (See issue #3657) 2128299742Sdim */ 2129251881Speter 2130299742Sdim const char *propname; 2131251881Speter 2132299742Sdim if (ctx->cur_file) 2133299742Sdim SVN_ERR(ensure_file_opened(ctx->cur_file, scratch_pool)); 2134299742Sdim else if (ctx->cur_dir) 2135299742Sdim SVN_ERR(ensure_dir_opened(ctx->cur_dir, scratch_pool)); 2136251881Speter else 2137299742Sdim break; 2138299742Sdim 2139299742Sdim switch (leaving_state) 2140251881Speter { 2141299742Sdim case VERSION_NAME: 2142299742Sdim propname = SVN_PROP_ENTRY_COMMITTED_REV; 2143299742Sdim break; 2144299742Sdim case CREATIONDATE: 2145299742Sdim propname = SVN_PROP_ENTRY_COMMITTED_DATE; 2146299742Sdim break; 2147299742Sdim case CREATOR_DISPLAYNAME: 2148299742Sdim propname = SVN_PROP_ENTRY_LAST_AUTHOR; 2149299742Sdim break; 2150299742Sdim default: 2151299742Sdim SVN_ERR_MALFUNCTION(); /* Impossible to reach */ 2152251881Speter } 2153251881Speter 2154299742Sdim if (ctx->cur_file) 2155299742Sdim SVN_ERR(ctx->editor->change_file_prop(ctx->cur_file->file_baton, 2156299742Sdim propname, cdata, 2157299742Sdim scratch_pool)); 2158251881Speter else 2159299742Sdim SVN_ERR(ctx->editor->change_dir_prop(ctx->cur_dir->dir_baton, 2160299742Sdim propname, cdata, 2161299742Sdim scratch_pool)); 2162251881Speter } 2163299742Sdim break; 2164251881Speter } 2165251881Speter 2166251881Speter return SVN_NO_ERROR; 2167251881Speter} 2168251881Speter 2169299742Sdim 2170299742Sdim/* Conforms to svn_ra_serf__xml_cdata_t */ 2171251881Speterstatic svn_error_t * 2172299742Sdimupdate_cdata(svn_ra_serf__xml_estate_t *xes, 2173299742Sdim void *baton, 2174299742Sdim int current_state, 2175251881Speter const char *data, 2176251881Speter apr_size_t len, 2177251881Speter apr_pool_t *scratch_pool) 2178251881Speter{ 2179299742Sdim report_context_t *ctx = baton; 2180251881Speter 2181299742Sdim if (current_state == TXDELTA && ctx->cur_file 2182299742Sdim && ctx->cur_file->txdelta_stream) 2183251881Speter { 2184299742Sdim SVN_ERR(svn_stream_write(ctx->cur_file->txdelta_stream, data, &len)); 2185251881Speter } 2186251881Speter 2187251881Speter return SVN_NO_ERROR; 2188251881Speter} 2189251881Speter 2190251881Speter 2191251881Speter/** Editor callbacks given to callers to create request body */ 2192251881Speter 2193251881Speter/* Helper to create simple xml tag without attributes. */ 2194251881Speterstatic void 2195251881Spetermake_simple_xml_tag(svn_stringbuf_t **buf_p, 2196251881Speter const char *tagname, 2197251881Speter const char *cdata, 2198251881Speter apr_pool_t *pool) 2199251881Speter{ 2200299742Sdim svn_xml_make_open_tag(buf_p, pool, svn_xml_protect_pcdata, tagname, 2201299742Sdim SVN_VA_NULL); 2202251881Speter svn_xml_escape_cdata_cstring(buf_p, cdata, pool); 2203251881Speter svn_xml_make_close_tag(buf_p, pool, tagname); 2204251881Speter} 2205251881Speter 2206251881Speterstatic svn_error_t * 2207251881Speterset_path(void *report_baton, 2208251881Speter const char *path, 2209251881Speter svn_revnum_t revision, 2210251881Speter svn_depth_t depth, 2211251881Speter svn_boolean_t start_empty, 2212251881Speter const char *lock_token, 2213251881Speter apr_pool_t *pool) 2214251881Speter{ 2215251881Speter report_context_t *report = report_baton; 2216251881Speter svn_stringbuf_t *buf = NULL; 2217251881Speter 2218251881Speter svn_xml_make_open_tag(&buf, pool, svn_xml_protect_pcdata, "S:entry", 2219251881Speter "rev", apr_ltoa(pool, revision), 2220251881Speter "lock-token", lock_token, 2221251881Speter "depth", svn_depth_to_word(depth), 2222251881Speter "start-empty", start_empty ? "true" : NULL, 2223299742Sdim SVN_VA_NULL); 2224251881Speter svn_xml_escape_cdata_cstring(&buf, path, pool); 2225251881Speter svn_xml_make_close_tag(&buf, pool, "S:entry"); 2226251881Speter 2227299742Sdim SVN_ERR(svn_stream_write(report->body_template, buf->data, &buf->len)); 2228251881Speter 2229251881Speter return SVN_NO_ERROR; 2230251881Speter} 2231251881Speter 2232251881Speterstatic svn_error_t * 2233251881Speterdelete_path(void *report_baton, 2234251881Speter const char *path, 2235251881Speter apr_pool_t *pool) 2236251881Speter{ 2237251881Speter report_context_t *report = report_baton; 2238251881Speter svn_stringbuf_t *buf = NULL; 2239251881Speter 2240251881Speter make_simple_xml_tag(&buf, "S:missing", path, pool); 2241251881Speter 2242299742Sdim SVN_ERR(svn_stream_write(report->body_template, buf->data, &buf->len)); 2243251881Speter 2244251881Speter return SVN_NO_ERROR; 2245251881Speter} 2246251881Speter 2247251881Speterstatic svn_error_t * 2248251881Speterlink_path(void *report_baton, 2249251881Speter const char *path, 2250251881Speter const char *url, 2251251881Speter svn_revnum_t revision, 2252251881Speter svn_depth_t depth, 2253251881Speter svn_boolean_t start_empty, 2254251881Speter const char *lock_token, 2255251881Speter apr_pool_t *pool) 2256251881Speter{ 2257251881Speter report_context_t *report = report_baton; 2258251881Speter const char *link, *report_target; 2259251881Speter apr_uri_t uri; 2260251881Speter apr_status_t status; 2261251881Speter svn_stringbuf_t *buf = NULL; 2262251881Speter 2263251881Speter /* We need to pass in the baseline relative path. 2264251881Speter * 2265251881Speter * TODO Confirm that it's on the same server? 2266251881Speter */ 2267251881Speter status = apr_uri_parse(pool, url, &uri); 2268251881Speter if (status) 2269251881Speter { 2270251881Speter return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL, 2271251881Speter _("Unable to parse URL '%s'"), url); 2272251881Speter } 2273251881Speter 2274299742Sdim SVN_ERR(svn_ra_serf__report_resource(&report_target, report->sess, pool)); 2275299742Sdim SVN_ERR(svn_ra_serf__get_relative_path(&link, uri.path, report->sess, pool)); 2276251881Speter 2277299742Sdim link = apr_pstrcat(pool, "/", link, SVN_VA_NULL); 2278251881Speter 2279251881Speter svn_xml_make_open_tag(&buf, pool, svn_xml_protect_pcdata, "S:entry", 2280251881Speter "rev", apr_ltoa(pool, revision), 2281251881Speter "lock-token", lock_token, 2282251881Speter "depth", svn_depth_to_word(depth), 2283251881Speter "linkpath", link, 2284251881Speter "start-empty", start_empty ? "true" : NULL, 2285299742Sdim SVN_VA_NULL); 2286251881Speter svn_xml_escape_cdata_cstring(&buf, path, pool); 2287251881Speter svn_xml_make_close_tag(&buf, pool, "S:entry"); 2288251881Speter 2289299742Sdim SVN_ERR(svn_stream_write(report->body_template, buf->data, &buf->len)); 2290251881Speter 2291251881Speter /* Store the switch roots to allow generating repos_relpaths from just 2292251881Speter the working copy paths. (Needed for HTTPv2) */ 2293251881Speter path = apr_pstrdup(report->pool, path); 2294299742Sdim link = apr_pstrdup(report->pool, link + 1); 2295299742Sdim svn_hash_sets(report->switched_paths, path, link); 2296251881Speter 2297299742Sdim if (!path[0] && report->update_target[0]) 2298251881Speter { 2299299742Sdim /* The update root is switched. Make sure we store it the way 2300299742Sdim we expect it to find */ 2301299742Sdim svn_hash_sets(report->switched_paths, report->update_target, link); 2302251881Speter } 2303251881Speter 2304299742Sdim return APR_SUCCESS; 2305251881Speter} 2306251881Speter 2307299742Sdim/* Serf callback to create update request body bucket. 2308299742Sdim Implements svn_ra_serf__request_body_delegate_t */ 2309251881Speterstatic svn_error_t * 2310251881Spetercreate_update_report_body(serf_bucket_t **body_bkt, 2311251881Speter void *baton, 2312251881Speter serf_bucket_alloc_t *alloc, 2313299742Sdim apr_pool_t *pool /* request pool */, 2314299742Sdim apr_pool_t *scratch_pool) 2315251881Speter{ 2316251881Speter report_context_t *report = baton; 2317299742Sdim body_create_baton_t *body = report->body; 2318251881Speter 2319299742Sdim if (body->file) 2320299742Sdim { 2321299742Sdim apr_off_t offset; 2322251881Speter 2323299742Sdim offset = 0; 2324299742Sdim SVN_ERR(svn_io_file_seek(body->file, APR_SET, &offset, pool)); 2325251881Speter 2326299742Sdim *body_bkt = serf_bucket_file_create(report->body->file, alloc); 2327299742Sdim } 2328299742Sdim else 2329299742Sdim { 2330299742Sdim *body_bkt = serf_bucket_simple_create(body->all_data, 2331299742Sdim body->total_bytes, 2332299742Sdim NULL, NULL, alloc); 2333299742Sdim } 2334299742Sdim 2335251881Speter return SVN_NO_ERROR; 2336251881Speter} 2337251881Speter 2338251881Speter/* Serf callback to setup update request headers. */ 2339251881Speterstatic svn_error_t * 2340251881Spetersetup_update_report_headers(serf_bucket_t *headers, 2341251881Speter void *baton, 2342299742Sdim apr_pool_t *pool /* request pool */, 2343299742Sdim apr_pool_t *scratch_pool) 2344251881Speter{ 2345251881Speter report_context_t *report = baton; 2346251881Speter 2347251881Speter if (report->sess->using_compression) 2348251881Speter { 2349251881Speter serf_bucket_headers_setn(headers, "Accept-Encoding", 2350253734Speter "gzip,svndiff1;q=0.9,svndiff;q=0.8"); 2351251881Speter } 2352251881Speter else 2353251881Speter { 2354251881Speter serf_bucket_headers_setn(headers, "Accept-Encoding", 2355251881Speter "svndiff1;q=0.9,svndiff;q=0.8"); 2356251881Speter } 2357251881Speter 2358251881Speter return SVN_NO_ERROR; 2359251881Speter} 2360251881Speter 2361299742Sdim/* Baton for update_delay_handler */ 2362299742Sdimtypedef struct update_delay_baton_t 2363299742Sdim{ 2364299742Sdim report_context_t *report; 2365299742Sdim svn_spillbuf_t *spillbuf; 2366299742Sdim svn_ra_serf__response_handler_t inner_handler; 2367299742Sdim void *inner_handler_baton; 2368299742Sdim} update_delay_baton_t; 2369299742Sdim 2370299742Sdim/* Helper for update_delay_handler() and process_pending() to 2371299742Sdim call UDB->INNER_HANDLER with buffer pointed by DATA. */ 2372251881Speterstatic svn_error_t * 2373299742Sdimprocess_buffer(update_delay_baton_t *udb, 2374299742Sdim serf_request_t *request, 2375299742Sdim const void *data, 2376299742Sdim apr_size_t len, 2377299742Sdim svn_boolean_t at_eof, 2378299742Sdim serf_bucket_alloc_t *alloc, 2379299742Sdim apr_pool_t *pool) 2380251881Speter{ 2381299742Sdim serf_bucket_t *tmp_bucket; 2382251881Speter svn_error_t *err; 2383251881Speter 2384299742Sdim /* ### This code (and the eagain bucket code) can probably be 2385299742Sdim ### simplified by using a bit of aggregate bucket magic. 2386299742Sdim ### See mail from Ivan to dev@s.a.o. */ 2387299742Sdim if (at_eof) 2388299742Sdim { 2389299742Sdim tmp_bucket = serf_bucket_simple_create(data, len, NULL, NULL, 2390299742Sdim alloc); 2391299742Sdim } 2392299742Sdim else 2393299742Sdim { 2394299742Sdim tmp_bucket = svn_ra_serf__create_bucket_with_eagain(data, len, 2395299742Sdim alloc); 2396299742Sdim } 2397251881Speter 2398299742Sdim /* If not at EOF create a bucket that finishes with EAGAIN, otherwise 2399299742Sdim use a standard bucket with default EOF handling */ 2400299742Sdim err = udb->inner_handler(request, tmp_bucket, 2401299742Sdim udb->inner_handler_baton, pool); 2402251881Speter 2403299742Sdim /* And free the bucket explicitly to avoid growing request allocator 2404299742Sdim storage (in a loop) */ 2405299742Sdim serf_bucket_destroy(tmp_bucket); 2406251881Speter 2407299742Sdim return svn_error_trace(err); 2408299742Sdim} 2409251881Speter 2410251881Speter 2411299742Sdim/* Delaying wrapping reponse handler, to avoid creating too many 2412299742Sdim requests to deliver efficiently */ 2413299742Sdimstatic svn_error_t * 2414299742Sdimupdate_delay_handler(serf_request_t *request, 2415299742Sdim serf_bucket_t *response, 2416299742Sdim void *handler_baton, 2417299742Sdim apr_pool_t *scratch_pool) 2418299742Sdim{ 2419299742Sdim update_delay_baton_t *udb = handler_baton; 2420299742Sdim apr_status_t status; 2421299742Sdim apr_pool_t *iterpool = NULL; 2422251881Speter 2423299742Sdim if (! udb->spillbuf) 2424251881Speter { 2425299742Sdim if (udb->report->send_all_mode) 2426299742Sdim { 2427299742Sdim /* Easy out... We only have one request, so avoid everything and just 2428299742Sdim call the inner handler. 2429251881Speter 2430299742Sdim We will always get in the loop (below) on the first chunk, as only 2431299742Sdim the server can get us in true send-all mode */ 2432251881Speter 2433299742Sdim return svn_error_trace(udb->inner_handler(request, response, 2434299742Sdim udb->inner_handler_baton, 2435299742Sdim scratch_pool)); 2436299742Sdim } 2437251881Speter 2438299742Sdim while ((udb->report->num_active_fetches + udb->report->num_active_propfinds) 2439299742Sdim < REQUEST_COUNT_TO_RESUME) 2440299742Sdim { 2441299742Sdim const char *data; 2442299742Sdim apr_size_t len; 2443299742Sdim svn_boolean_t at_eof = FALSE; 2444299742Sdim svn_error_t *err; 2445251881Speter 2446299742Sdim status = serf_bucket_read(response, PARSE_CHUNK_SIZE, &data, &len); 2447299742Sdim if (SERF_BUCKET_READ_ERROR(status)) 2448299742Sdim return svn_ra_serf__wrap_err(status, NULL); 2449299742Sdim else if (APR_STATUS_IS_EOF(status)) 2450299742Sdim udb->report->report_received = at_eof = TRUE; 2451251881Speter 2452299742Sdim if (!iterpool) 2453299742Sdim iterpool = svn_pool_create(scratch_pool); 2454299742Sdim else 2455299742Sdim svn_pool_clear(iterpool); 2456251881Speter 2457299742Sdim if (len == 0 && !at_eof) 2458299742Sdim return svn_ra_serf__wrap_err(status, NULL); 2459251881Speter 2460299742Sdim err = process_buffer(udb, request, data, len, at_eof, 2461299742Sdim serf_request_get_alloc(request), 2462299742Sdim iterpool); 2463251881Speter 2464299742Sdim if (err && SERF_BUCKET_READ_ERROR(err->apr_err)) 2465299742Sdim return svn_error_trace(err); 2466299742Sdim else if (err && APR_STATUS_IS_EAGAIN(err->apr_err)) 2467251881Speter { 2468299742Sdim svn_error_clear(err); /* Throttling is working ok */ 2469251881Speter } 2470299742Sdim else if (err && (APR_STATUS_IS_EOF(err->apr_err))) 2471299742Sdim { 2472299742Sdim svn_pool_destroy(iterpool); 2473299742Sdim return svn_error_trace(err); /* No buffering was necessary */ 2474299742Sdim } 2475299742Sdim else 2476299742Sdim { 2477299742Sdim /* SERF_ERROR_WAIT_CONN should be impossible? */ 2478299742Sdim return svn_error_trace(err); 2479299742Sdim } 2480251881Speter } 2481251881Speter 2482299742Sdim /* Let's start using the spill infrastructure */ 2483299742Sdim udb->spillbuf = svn_spillbuf__create(SPILLBUF_BLOCKSIZE, 2484299742Sdim SPILLBUF_MAXBUFFSIZE, 2485299742Sdim udb->report->pool); 2486299742Sdim } 2487251881Speter 2488299742Sdim /* Read everything we can to a spillbuffer */ 2489299742Sdim do 2490299742Sdim { 2491299742Sdim const char *data; 2492299742Sdim apr_size_t len; 2493251881Speter 2494299742Sdim /* ### What blocksize should we pass? */ 2495299742Sdim status = serf_bucket_read(response, 8*PARSE_CHUNK_SIZE, &data, &len); 2496251881Speter 2497299742Sdim if (!SERF_BUCKET_READ_ERROR(status)) 2498299742Sdim SVN_ERR(svn_spillbuf__write(udb->spillbuf, data, len, scratch_pool)); 2499299742Sdim } 2500299742Sdim while (status == APR_SUCCESS); 2501251881Speter 2502299742Sdim if (APR_STATUS_IS_EOF(status)) 2503299742Sdim udb->report->report_received = TRUE; 2504251881Speter 2505299742Sdim /* We handle feeding the data from the main context loop, which will be right 2506299742Sdim after processing the pending data */ 2507251881Speter 2508299742Sdim if (status) 2509299742Sdim return svn_ra_serf__wrap_err(status, NULL); 2510299742Sdim else 2511299742Sdim return SVN_NO_ERROR; 2512299742Sdim} 2513251881Speter 2514299742Sdim/* Process pending data from the update report, if any */ 2515299742Sdimstatic svn_error_t * 2516299742Sdimprocess_pending(update_delay_baton_t *udb, 2517299742Sdim apr_pool_t *scratch_pool) 2518299742Sdim{ 2519299742Sdim apr_pool_t *iterpool = NULL; 2520299742Sdim serf_bucket_alloc_t *alloc = NULL; 2521251881Speter 2522299742Sdim while ((udb->report->num_active_fetches + udb->report->num_active_propfinds) 2523299742Sdim < REQUEST_COUNT_TO_RESUME) 2524299742Sdim { 2525299742Sdim const char *data; 2526299742Sdim apr_size_t len; 2527299742Sdim svn_boolean_t at_eof; 2528299742Sdim svn_error_t *err; 2529251881Speter 2530299742Sdim if (!iterpool) 2531299742Sdim { 2532299742Sdim iterpool = svn_pool_create(scratch_pool); 2533299742Sdim alloc = serf_bucket_allocator_create(scratch_pool, NULL, NULL); 2534299742Sdim } 2535299742Sdim else 2536299742Sdim svn_pool_clear(iterpool); 2537251881Speter 2538299742Sdim SVN_ERR(svn_spillbuf__read(&data, &len, udb->spillbuf, iterpool)); 2539251881Speter 2540299742Sdim if (data == NULL && !udb->report->report_received) 2541299742Sdim break; 2542299742Sdim else if (data == NULL) 2543299742Sdim at_eof = TRUE; 2544299742Sdim else 2545299742Sdim at_eof = FALSE; 2546251881Speter 2547299742Sdim err = process_buffer(udb, NULL /* allowed? */, data, len, 2548299742Sdim at_eof, alloc, iterpool); 2549251881Speter 2550299742Sdim if (err && APR_STATUS_IS_EAGAIN(err->apr_err)) 2551299742Sdim { 2552299742Sdim svn_error_clear(err); /* Throttling is working */ 2553251881Speter } 2554299742Sdim else if (err && APR_STATUS_IS_EOF(err->apr_err)) 2555251881Speter { 2556299742Sdim svn_error_clear(err); 2557251881Speter 2558299742Sdim svn_pool_destroy(iterpool); 2559299742Sdim udb->spillbuf = NULL; 2560299742Sdim return SVN_NO_ERROR; 2561299742Sdim } 2562299742Sdim else if (err) 2563299742Sdim return svn_error_trace(err); 2564299742Sdim } 2565251881Speter 2566299742Sdim if (iterpool) 2567299742Sdim svn_pool_destroy(iterpool); 2568251881Speter 2569299742Sdim return SVN_NO_ERROR; 2570299742Sdim} 2571251881Speter 2572299742Sdim/* Process the 'update' editor report */ 2573299742Sdimstatic svn_error_t * 2574299742Sdimprocess_editor_report(report_context_t *ctx, 2575299742Sdim svn_ra_serf__handler_t *handler, 2576299742Sdim apr_pool_t *scratch_pool) 2577299742Sdim{ 2578299742Sdim svn_ra_serf__session_t *sess = ctx->sess; 2579299742Sdim apr_pool_t *iterpool = svn_pool_create(scratch_pool); 2580299742Sdim apr_interval_time_t waittime_left = sess->timeout; 2581299742Sdim update_delay_baton_t *ud; 2582251881Speter 2583299742Sdim /* Now wrap the response handler with delay support to avoid sending 2584299742Sdim out too many requests at once */ 2585299742Sdim ud = apr_pcalloc(scratch_pool, sizeof(*ud)); 2586299742Sdim ud->report = ctx; 2587251881Speter 2588299742Sdim ud->inner_handler = handler->response_handler; 2589299742Sdim ud->inner_handler_baton = handler->response_baton; 2590251881Speter 2591299742Sdim handler->response_handler = update_delay_handler; 2592299742Sdim handler->response_baton = ud; 2593251881Speter 2594299742Sdim /* Open the first extra connection. */ 2595299742Sdim SVN_ERR(open_connection_if_needed(sess, 0)); 2596251881Speter 2597299742Sdim sess->cur_conn = 1; 2598251881Speter 2599299742Sdim /* Note that we may have no active GET or PROPFIND requests, yet the 2600299742Sdim processing has not been completed. This could be from a delay on the 2601299742Sdim network or because we've spooled the entire response into our "pending" 2602299742Sdim content of the XML parser. The DONE flag will get set when all the 2603299742Sdim XML content has been received *and* parsed. */ 2604299742Sdim while (!handler->done 2605299742Sdim || ctx->num_active_fetches 2606299742Sdim || ctx->num_active_propfinds 2607299742Sdim || !ctx->done) 2608299742Sdim { 2609299742Sdim svn_error_t *err; 2610299742Sdim int i; 2611251881Speter 2612299742Sdim svn_pool_clear(iterpool); 2613251881Speter 2614299742Sdim err = svn_ra_serf__context_run(sess, &waittime_left, iterpool); 2615251881Speter 2616299742Sdim if (handler->done && handler->server_error) 2617299742Sdim { 2618299742Sdim svn_error_clear(err); 2619299742Sdim err = svn_ra_serf__server_error_create(handler, iterpool); 2620251881Speter 2621299742Sdim SVN_ERR_ASSERT(err != NULL); 2622251881Speter } 2623251881Speter 2624299742Sdim SVN_ERR(err); 2625251881Speter 2626299742Sdim /* If there is pending REPORT data, process it now. */ 2627299742Sdim if (ud->spillbuf) 2628299742Sdim SVN_ERR(process_pending(ud, iterpool)); 2629251881Speter 2630251881Speter /* Debugging purposes only! */ 2631251881Speter for (i = 0; i < sess->num_conns; i++) 2632251881Speter { 2633251881Speter serf_debug__closed_conn(sess->conns[i]->bkt_alloc); 2634251881Speter } 2635251881Speter } 2636251881Speter 2637299742Sdim svn_pool_clear(iterpool); 2638299742Sdim 2639251881Speter /* If we got a complete report, close the edit. Otherwise, abort it. */ 2640299742Sdim if (ctx->done) 2641299742Sdim SVN_ERR(ctx->editor->close_edit(ctx->editor_baton, iterpool)); 2642299742Sdim else 2643299742Sdim return svn_error_create(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL, 2644299742Sdim _("Missing update-report close tag")); 2645251881Speter 2646299742Sdim svn_pool_destroy(iterpool); 2647299742Sdim return SVN_NO_ERROR; 2648299742Sdim} 2649299742Sdim 2650299742Sdimstatic svn_error_t * 2651299742Sdimfinish_report(void *report_baton, 2652299742Sdim apr_pool_t *pool) 2653299742Sdim{ 2654299742Sdim report_context_t *report = report_baton; 2655299742Sdim svn_ra_serf__session_t *sess = report->sess; 2656299742Sdim svn_ra_serf__handler_t *handler; 2657299742Sdim svn_ra_serf__xml_context_t *xmlctx; 2658299742Sdim const char *report_target; 2659299742Sdim svn_stringbuf_t *buf = NULL; 2660299742Sdim apr_pool_t *scratch_pool = svn_pool_create(pool); 2661299742Sdim svn_error_t *err; 2662299742Sdim 2663299742Sdim svn_xml_make_close_tag(&buf, scratch_pool, "S:update-report"); 2664299742Sdim SVN_ERR(svn_stream_write(report->body_template, buf->data, &buf->len)); 2665299742Sdim SVN_ERR(svn_stream_close(report->body_template)); 2666299742Sdim 2667299742Sdim SVN_ERR(svn_ra_serf__report_resource(&report_target, sess, scratch_pool)); 2668299742Sdim 2669299742Sdim xmlctx = svn_ra_serf__xml_context_create(update_ttable, 2670299742Sdim update_opened, update_closed, 2671299742Sdim update_cdata, 2672299742Sdim report, 2673299742Sdim scratch_pool); 2674299742Sdim handler = svn_ra_serf__create_expat_handler(sess, xmlctx, NULL, 2675299742Sdim scratch_pool); 2676299742Sdim 2677299742Sdim handler->method = "REPORT"; 2678299742Sdim handler->path = report_target; 2679299742Sdim handler->body_delegate = create_update_report_body; 2680299742Sdim handler->body_delegate_baton = report; 2681299742Sdim handler->body_type = "text/xml"; 2682299742Sdim handler->custom_accept_encoding = TRUE; 2683299742Sdim handler->header_delegate = setup_update_report_headers; 2684299742Sdim handler->header_delegate_baton = report; 2685299742Sdim 2686299742Sdim svn_ra_serf__request_create(handler); 2687299742Sdim 2688299742Sdim err = process_editor_report(report, handler, scratch_pool); 2689299742Sdim 2690299742Sdim if (err) 2691251881Speter { 2692299742Sdim err = svn_error_trace(err); 2693299742Sdim err = svn_error_compose_create( 2694299742Sdim err, 2695299742Sdim svn_error_trace( 2696299742Sdim report->editor->abort_edit(report->editor_baton, 2697299742Sdim scratch_pool))); 2698251881Speter } 2699251881Speter 2700299742Sdim svn_pool_destroy(scratch_pool); 2701299742Sdim 2702251881Speter return svn_error_trace(err); 2703251881Speter} 2704251881Speter 2705251881Speter 2706251881Speterstatic svn_error_t * 2707251881Speterabort_report(void *report_baton, 2708251881Speter apr_pool_t *pool) 2709251881Speter{ 2710251881Speter#if 0 2711251881Speter report_context_t *report = report_baton; 2712251881Speter#endif 2713251881Speter 2714251881Speter /* Should we perform some cleanup here? */ 2715251881Speter 2716251881Speter return SVN_NO_ERROR; 2717251881Speter} 2718251881Speter 2719251881Speterstatic const svn_ra_reporter3_t ra_serf_reporter = { 2720251881Speter set_path, 2721251881Speter delete_path, 2722251881Speter link_path, 2723251881Speter finish_report, 2724251881Speter abort_report 2725251881Speter}; 2726251881Speter 2727251881Speter 2728251881Speter/** RA function implementations and body */ 2729251881Speter 2730251881Speterstatic svn_error_t * 2731251881Spetermake_update_reporter(svn_ra_session_t *ra_session, 2732251881Speter const svn_ra_reporter3_t **reporter, 2733251881Speter void **report_baton, 2734251881Speter svn_revnum_t revision, 2735251881Speter const char *src_path, 2736251881Speter const char *dest_path, 2737251881Speter const char *update_target, 2738251881Speter svn_depth_t depth, 2739251881Speter svn_boolean_t ignore_ancestry, 2740251881Speter svn_boolean_t text_deltas, 2741251881Speter svn_boolean_t send_copyfrom_args, 2742251881Speter const svn_delta_editor_t *update_editor, 2743251881Speter void *update_baton, 2744251881Speter apr_pool_t *result_pool, 2745251881Speter apr_pool_t *scratch_pool) 2746251881Speter{ 2747251881Speter report_context_t *report; 2748251881Speter const svn_delta_editor_t *filter_editor; 2749251881Speter void *filter_baton; 2750251881Speter svn_boolean_t has_target = *update_target != '\0'; 2751251881Speter svn_boolean_t server_supports_depth; 2752251881Speter svn_ra_serf__session_t *sess = ra_session->priv; 2753251881Speter svn_stringbuf_t *buf = NULL; 2754251881Speter svn_boolean_t use_bulk_updates; 2755251881Speter 2756251881Speter SVN_ERR(svn_ra_serf__has_capability(ra_session, &server_supports_depth, 2757251881Speter SVN_RA_CAPABILITY_DEPTH, scratch_pool)); 2758251881Speter /* We can skip the depth filtering when the user requested 2759251881Speter depth_files or depth_infinity because the server will 2760251881Speter transmit the right stuff anyway. */ 2761251881Speter if ((depth != svn_depth_files) 2762251881Speter && (depth != svn_depth_infinity) 2763251881Speter && ! server_supports_depth) 2764251881Speter { 2765251881Speter SVN_ERR(svn_delta_depth_filter_editor(&filter_editor, 2766251881Speter &filter_baton, 2767251881Speter update_editor, 2768251881Speter update_baton, 2769251881Speter depth, has_target, 2770299742Sdim result_pool)); 2771251881Speter update_editor = filter_editor; 2772251881Speter update_baton = filter_baton; 2773251881Speter } 2774251881Speter 2775251881Speter report = apr_pcalloc(result_pool, sizeof(*report)); 2776251881Speter report->pool = result_pool; 2777251881Speter report->sess = sess; 2778251881Speter report->target_rev = revision; 2779251881Speter report->ignore_ancestry = ignore_ancestry; 2780251881Speter report->send_copyfrom_args = send_copyfrom_args; 2781251881Speter report->text_deltas = text_deltas; 2782251881Speter report->switched_paths = apr_hash_make(report->pool); 2783251881Speter 2784251881Speter report->source = src_path; 2785251881Speter report->destination = dest_path; 2786251881Speter report->update_target = update_target; 2787251881Speter 2788299742Sdim report->editor = update_editor; 2789299742Sdim report->editor_baton = update_baton; 2790251881Speter report->done = FALSE; 2791251881Speter 2792251881Speter *reporter = &ra_serf_reporter; 2793251881Speter *report_baton = report; 2794251881Speter 2795299742Sdim report->body = apr_pcalloc(report->pool, sizeof(*report->body)); 2796299742Sdim report->body->result_pool = report->pool; 2797299742Sdim report->body_template = svn_stream_create(report->body, report->pool); 2798299742Sdim svn_stream_set_write(report->body_template, body_write_fn); 2799299742Sdim svn_stream_set_close(report->body_template, body_done_fn); 2800251881Speter 2801251881Speter if (sess->bulk_updates == svn_tristate_true) 2802251881Speter { 2803251881Speter /* User would like to use bulk updates. */ 2804251881Speter use_bulk_updates = TRUE; 2805251881Speter } 2806251881Speter else if (sess->bulk_updates == svn_tristate_false) 2807251881Speter { 2808251881Speter /* User doesn't want bulk updates. */ 2809251881Speter use_bulk_updates = FALSE; 2810251881Speter } 2811251881Speter else 2812251881Speter { 2813251881Speter /* User doesn't have any preferences on bulk updates. Decide on server 2814251881Speter preferences and capabilities. */ 2815251881Speter if (sess->server_allows_bulk) 2816251881Speter { 2817251881Speter if (apr_strnatcasecmp(sess->server_allows_bulk, "off") == 0) 2818251881Speter { 2819251881Speter /* Server doesn't want bulk updates */ 2820251881Speter use_bulk_updates = FALSE; 2821251881Speter } 2822251881Speter else if (apr_strnatcasecmp(sess->server_allows_bulk, "prefer") == 0) 2823251881Speter { 2824251881Speter /* Server prefers bulk updates, and we respect that */ 2825251881Speter use_bulk_updates = TRUE; 2826251881Speter } 2827251881Speter else 2828251881Speter { 2829251881Speter /* Server allows bulk updates, but doesn't dictate its use. Do 2830251881Speter whatever is the default. */ 2831251881Speter use_bulk_updates = FALSE; 2832251881Speter } 2833251881Speter } 2834251881Speter else 2835251881Speter { 2836251881Speter /* Pre-1.8 server didn't send the bulk_updates header. Check if server 2837251881Speter supports inlining properties in update editor report. */ 2838251881Speter if (sess->supports_inline_props) 2839251881Speter { 2840299742Sdim /* NOTE: both inlined properties and server->allows_bulk_update 2841299742Sdim (flag SVN_DAV_ALLOW_BULK_UPDATES) were added in 1.8.0, so 2842299742Sdim this code is never reached with a released version of 2843299742Sdim mod_dav_svn. 2844299742Sdim 2845299742Sdim Basically by default a 1.8.0 client connecting to a 1.7.x or 2846299742Sdim older server will always use bulk updates. */ 2847299742Sdim 2848251881Speter /* Inline props supported: do not use bulk updates. */ 2849251881Speter use_bulk_updates = FALSE; 2850251881Speter } 2851251881Speter else 2852251881Speter { 2853251881Speter /* Inline props are not supported: use bulk updates to avoid 2854251881Speter * PROPFINDs for every added node. */ 2855251881Speter use_bulk_updates = TRUE; 2856251881Speter } 2857251881Speter } 2858251881Speter } 2859251881Speter 2860251881Speter if (use_bulk_updates) 2861251881Speter { 2862251881Speter svn_xml_make_open_tag(&buf, scratch_pool, svn_xml_normal, 2863251881Speter "S:update-report", 2864251881Speter "xmlns:S", SVN_XML_NAMESPACE, "send-all", "true", 2865299742Sdim SVN_VA_NULL); 2866251881Speter } 2867251881Speter else 2868251881Speter { 2869251881Speter svn_xml_make_open_tag(&buf, scratch_pool, svn_xml_normal, 2870251881Speter "S:update-report", 2871251881Speter "xmlns:S", SVN_XML_NAMESPACE, 2872299742Sdim SVN_VA_NULL); 2873251881Speter /* Subversion 1.8+ servers can be told to send properties for newly 2874251881Speter added items inline even when doing a skelta response. */ 2875251881Speter make_simple_xml_tag(&buf, "S:include-props", "yes", scratch_pool); 2876251881Speter } 2877251881Speter 2878251881Speter make_simple_xml_tag(&buf, "S:src-path", report->source, scratch_pool); 2879251881Speter 2880251881Speter if (SVN_IS_VALID_REVNUM(report->target_rev)) 2881251881Speter { 2882251881Speter make_simple_xml_tag(&buf, "S:target-revision", 2883251881Speter apr_ltoa(scratch_pool, report->target_rev), 2884251881Speter scratch_pool); 2885251881Speter } 2886251881Speter 2887251881Speter if (report->destination && *report->destination) 2888251881Speter { 2889251881Speter make_simple_xml_tag(&buf, "S:dst-path", report->destination, 2890251881Speter scratch_pool); 2891251881Speter } 2892251881Speter 2893251881Speter if (report->update_target && *report->update_target) 2894251881Speter { 2895251881Speter make_simple_xml_tag(&buf, "S:update-target", report->update_target, 2896251881Speter scratch_pool); 2897251881Speter } 2898251881Speter 2899251881Speter if (report->ignore_ancestry) 2900251881Speter { 2901251881Speter make_simple_xml_tag(&buf, "S:ignore-ancestry", "yes", scratch_pool); 2902251881Speter } 2903251881Speter 2904251881Speter if (report->send_copyfrom_args) 2905251881Speter { 2906251881Speter make_simple_xml_tag(&buf, "S:send-copyfrom-args", "yes", scratch_pool); 2907251881Speter } 2908251881Speter 2909251881Speter /* Old servers know "recursive" but not "depth"; help them DTRT. */ 2910251881Speter if (depth == svn_depth_files || depth == svn_depth_empty) 2911251881Speter { 2912251881Speter make_simple_xml_tag(&buf, "S:recursive", "no", scratch_pool); 2913251881Speter } 2914251881Speter 2915251881Speter /* When in 'send-all' mode, mod_dav_svn will assume that it should 2916251881Speter calculate and transmit real text-deltas (instead of empty windows 2917251881Speter that merely indicate "text is changed") unless it finds this 2918251881Speter element. 2919251881Speter 2920251881Speter NOTE: Do NOT count on servers actually obeying this, as some exist 2921251881Speter which obey send-all, but do not check for this directive at all! 2922251881Speter 2923251881Speter NOTE 2: When not in 'send-all' mode, mod_dav_svn can still be configured to 2924251881Speter override our request and send text-deltas. */ 2925251881Speter if (! text_deltas) 2926251881Speter { 2927251881Speter make_simple_xml_tag(&buf, "S:text-deltas", "no", scratch_pool); 2928251881Speter } 2929251881Speter 2930251881Speter make_simple_xml_tag(&buf, "S:depth", svn_depth_to_word(depth), scratch_pool); 2931251881Speter 2932299742Sdim SVN_ERR(svn_stream_write(report->body_template, buf->data, &buf->len)); 2933251881Speter 2934251881Speter return SVN_NO_ERROR; 2935251881Speter} 2936251881Speter 2937251881Spetersvn_error_t * 2938251881Spetersvn_ra_serf__do_update(svn_ra_session_t *ra_session, 2939251881Speter const svn_ra_reporter3_t **reporter, 2940251881Speter void **report_baton, 2941251881Speter svn_revnum_t revision_to_update_to, 2942251881Speter const char *update_target, 2943251881Speter svn_depth_t depth, 2944251881Speter svn_boolean_t send_copyfrom_args, 2945251881Speter svn_boolean_t ignore_ancestry, 2946251881Speter const svn_delta_editor_t *update_editor, 2947251881Speter void *update_baton, 2948251881Speter apr_pool_t *result_pool, 2949251881Speter apr_pool_t *scratch_pool) 2950251881Speter{ 2951251881Speter svn_ra_serf__session_t *session = ra_session->priv; 2952251881Speter 2953251881Speter SVN_ERR(make_update_reporter(ra_session, reporter, report_baton, 2954251881Speter revision_to_update_to, 2955251881Speter session->session_url.path, NULL, update_target, 2956251881Speter depth, ignore_ancestry, TRUE /* text_deltas */, 2957251881Speter send_copyfrom_args, 2958251881Speter update_editor, update_baton, 2959251881Speter result_pool, scratch_pool)); 2960251881Speter return SVN_NO_ERROR; 2961251881Speter} 2962251881Speter 2963251881Spetersvn_error_t * 2964251881Spetersvn_ra_serf__do_diff(svn_ra_session_t *ra_session, 2965251881Speter const svn_ra_reporter3_t **reporter, 2966251881Speter void **report_baton, 2967251881Speter svn_revnum_t revision, 2968251881Speter const char *diff_target, 2969251881Speter svn_depth_t depth, 2970251881Speter svn_boolean_t ignore_ancestry, 2971251881Speter svn_boolean_t text_deltas, 2972251881Speter const char *versus_url, 2973251881Speter const svn_delta_editor_t *diff_editor, 2974251881Speter void *diff_baton, 2975251881Speter apr_pool_t *pool) 2976251881Speter{ 2977251881Speter svn_ra_serf__session_t *session = ra_session->priv; 2978251881Speter apr_pool_t *scratch_pool = svn_pool_create(pool); 2979251881Speter 2980251881Speter SVN_ERR(make_update_reporter(ra_session, reporter, report_baton, 2981251881Speter revision, 2982251881Speter session->session_url.path, versus_url, diff_target, 2983299742Sdim depth, ignore_ancestry, text_deltas, 2984299742Sdim FALSE /* send_copyfrom */, 2985251881Speter diff_editor, diff_baton, 2986251881Speter pool, scratch_pool)); 2987251881Speter svn_pool_destroy(scratch_pool); 2988251881Speter return SVN_NO_ERROR; 2989251881Speter} 2990251881Speter 2991251881Spetersvn_error_t * 2992251881Spetersvn_ra_serf__do_status(svn_ra_session_t *ra_session, 2993251881Speter const svn_ra_reporter3_t **reporter, 2994251881Speter void **report_baton, 2995251881Speter const char *status_target, 2996251881Speter svn_revnum_t revision, 2997251881Speter svn_depth_t depth, 2998251881Speter const svn_delta_editor_t *status_editor, 2999251881Speter void *status_baton, 3000251881Speter apr_pool_t *pool) 3001251881Speter{ 3002251881Speter svn_ra_serf__session_t *session = ra_session->priv; 3003251881Speter apr_pool_t *scratch_pool = svn_pool_create(pool); 3004251881Speter 3005251881Speter SVN_ERR(make_update_reporter(ra_session, reporter, report_baton, 3006251881Speter revision, 3007251881Speter session->session_url.path, NULL, status_target, 3008251881Speter depth, FALSE, FALSE, FALSE, 3009251881Speter status_editor, status_baton, 3010251881Speter pool, scratch_pool)); 3011251881Speter svn_pool_destroy(scratch_pool); 3012251881Speter return SVN_NO_ERROR; 3013251881Speter} 3014251881Speter 3015251881Spetersvn_error_t * 3016251881Spetersvn_ra_serf__do_switch(svn_ra_session_t *ra_session, 3017251881Speter const svn_ra_reporter3_t **reporter, 3018251881Speter void **report_baton, 3019251881Speter svn_revnum_t revision_to_switch_to, 3020251881Speter const char *switch_target, 3021251881Speter svn_depth_t depth, 3022251881Speter const char *switch_url, 3023251881Speter svn_boolean_t send_copyfrom_args, 3024251881Speter svn_boolean_t ignore_ancestry, 3025251881Speter const svn_delta_editor_t *switch_editor, 3026251881Speter void *switch_baton, 3027251881Speter apr_pool_t *result_pool, 3028251881Speter apr_pool_t *scratch_pool) 3029251881Speter{ 3030251881Speter svn_ra_serf__session_t *session = ra_session->priv; 3031251881Speter 3032251881Speter return make_update_reporter(ra_session, reporter, report_baton, 3033251881Speter revision_to_switch_to, 3034251881Speter session->session_url.path, 3035251881Speter switch_url, switch_target, 3036251881Speter depth, 3037251881Speter ignore_ancestry, 3038251881Speter TRUE /* text_deltas */, 3039251881Speter send_copyfrom_args, 3040251881Speter switch_editor, switch_baton, 3041251881Speter result_pool, scratch_pool); 3042251881Speter} 3043