client.c revision 362181
1/* 2 * client.c : Functions for repository access via the Subversion protocol 3 * 4 * ==================================================================== 5 * Licensed to the Apache Software Foundation (ASF) under one 6 * or more contributor license agreements. See the NOTICE file 7 * distributed with this work for additional information 8 * regarding copyright ownership. The ASF licenses this file 9 * to you under the Apache License, Version 2.0 (the 10 * "License"); you may not use this file except in compliance 11 * with the License. You may obtain a copy of the License at 12 * 13 * http://www.apache.org/licenses/LICENSE-2.0 14 * 15 * Unless required by applicable law or agreed to in writing, 16 * software distributed under the License is distributed on an 17 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 18 * KIND, either express or implied. See the License for the 19 * specific language governing permissions and limitations 20 * under the License. 21 * ==================================================================== 22 */ 23 24 25 26#include "svn_private_config.h" 27 28#define APR_WANT_STRFUNC 29#include <apr_want.h> 30#include <apr_general.h> 31#include <apr_strings.h> 32#include <apr_network_io.h> 33#include <apr_uri.h> 34 35#include "svn_hash.h" 36#include "svn_types.h" 37#include "svn_string.h" 38#include "svn_dirent_uri.h" 39#include "svn_error.h" 40#include "svn_time.h" 41#include "svn_path.h" 42#include "svn_pools.h" 43#include "svn_config.h" 44#include "svn_ra.h" 45#include "svn_ra_svn.h" 46#include "svn_props.h" 47#include "svn_mergeinfo.h" 48#include "svn_version.h" 49#include "svn_ctype.h" 50 51#include "svn_private_config.h" 52 53#include "private/svn_fspath.h" 54#include "private/svn_string_private.h" 55#include "private/svn_subr_private.h" 56 57#include "../libsvn_ra/ra_loader.h" 58 59#include "ra_svn.h" 60 61#ifdef SVN_HAVE_SASL 62#define DO_AUTH svn_ra_svn__do_cyrus_auth 63#else 64#define DO_AUTH svn_ra_svn__do_internal_auth 65#endif 66 67/* We aren't using SVN_DEPTH_IS_RECURSIVE here because that macro (for 68 whatever reason) deems svn_depth_immediates as non-recursive, which 69 is ... kinda true, but not true enough for our purposes. We need 70 our requested recursion level to be *at least* as recursive as the 71 real depth we're looking for. 72 */ 73#define DEPTH_TO_RECURSE(d) \ 74 ((d) == svn_depth_unknown || (d) > svn_depth_files) 75 76typedef struct ra_svn_commit_callback_baton_t { 77 svn_ra_svn__session_baton_t *sess_baton; 78 apr_pool_t *pool; 79 svn_revnum_t *new_rev; 80 svn_commit_callback2_t callback; 81 void *callback_baton; 82} ra_svn_commit_callback_baton_t; 83 84typedef struct ra_svn_reporter_baton_t { 85 svn_ra_svn__session_baton_t *sess_baton; 86 svn_ra_svn_conn_t *conn; 87 apr_pool_t *pool; 88 const svn_delta_editor_t *editor; 89 void *edit_baton; 90} ra_svn_reporter_baton_t; 91 92/* Parse an svn URL's tunnel portion into tunnel, if there is a tunnel 93 portion. */ 94static void parse_tunnel(const char *url, const char **tunnel, 95 apr_pool_t *pool) 96{ 97 *tunnel = NULL; 98 99 if (strncasecmp(url, "svn", 3) != 0) 100 return; 101 url += 3; 102 103 /* Get the tunnel specification, if any. */ 104 if (*url == '+') 105 { 106 const char *p; 107 108 url++; 109 p = strchr(url, ':'); 110 if (!p) 111 return; 112 *tunnel = apr_pstrmemdup(pool, url, p - url); 113 } 114} 115 116static svn_error_t *make_connection(const char *hostname, unsigned short port, 117 apr_socket_t **sock, apr_pool_t *pool) 118{ 119 apr_sockaddr_t *sa; 120 apr_status_t status; 121 int family = APR_INET; 122 123 /* Make sure we have IPV6 support first before giving apr_sockaddr_info_get 124 APR_UNSPEC, because it may give us back an IPV6 address even if we can't 125 create IPV6 sockets. */ 126 127#if APR_HAVE_IPV6 128#ifdef MAX_SECS_TO_LINGER 129 status = apr_socket_create(sock, APR_INET6, SOCK_STREAM, pool); 130#else 131 status = apr_socket_create(sock, APR_INET6, SOCK_STREAM, 132 APR_PROTO_TCP, pool); 133#endif 134 if (status == 0) 135 { 136 apr_socket_close(*sock); 137 family = APR_UNSPEC; 138 } 139#endif 140 141 /* Resolve the hostname. */ 142 status = apr_sockaddr_info_get(&sa, hostname, family, port, 0, pool); 143 if (status) 144 return svn_error_createf(status, NULL, _("Unknown hostname '%s'"), 145 hostname); 146 /* Iterate through the returned list of addresses attempting to 147 * connect to each in turn. */ 148 do 149 { 150 /* Create the socket. */ 151#ifdef MAX_SECS_TO_LINGER 152 /* ### old APR interface */ 153 status = apr_socket_create(sock, sa->family, SOCK_STREAM, pool); 154#else 155 status = apr_socket_create(sock, sa->family, SOCK_STREAM, APR_PROTO_TCP, 156 pool); 157#endif 158 if (status == APR_SUCCESS) 159 { 160 status = apr_socket_connect(*sock, sa); 161 if (status != APR_SUCCESS) 162 apr_socket_close(*sock); 163 } 164 sa = sa->next; 165 } 166 while (status != APR_SUCCESS && sa); 167 168 if (status) 169 return svn_error_wrap_apr(status, _("Can't connect to host '%s'"), 170 hostname); 171 172 /* Enable TCP keep-alives on the socket so we time out when 173 * the connection breaks due to network-layer problems. 174 * If the peer has dropped the connection due to a network partition 175 * or a crash, or if the peer no longer considers the connection 176 * valid because we are behind a NAT and our public IP has changed, 177 * it will respond to the keep-alive probe with a RST instead of an 178 * acknowledgment segment, which will cause svn to abort the session 179 * even while it is currently blocked waiting for data from the peer. 180 * See issue #3347. */ 181 status = apr_socket_opt_set(*sock, APR_SO_KEEPALIVE, 1); 182 if (status) 183 { 184 /* It's not a fatal error if we cannot enable keep-alives. */ 185 } 186 187 return SVN_NO_ERROR; 188} 189 190/* Set *DIFFS to an array of svn_prop_t, allocated in POOL, based on the 191 property diffs in LIST, received from the server. */ 192static svn_error_t *parse_prop_diffs(const svn_ra_svn__list_t *list, 193 apr_pool_t *pool, 194 apr_array_header_t **diffs) 195{ 196 int i; 197 198 *diffs = apr_array_make(pool, list->nelts, sizeof(svn_prop_t)); 199 200 for (i = 0; i < list->nelts; i++) 201 { 202 svn_prop_t *prop; 203 svn_ra_svn__item_t *elt = &SVN_RA_SVN__LIST_ITEM(list, i); 204 205 if (elt->kind != SVN_RA_SVN_LIST) 206 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, 207 _("Prop diffs element not a list")); 208 prop = apr_array_push(*diffs); 209 SVN_ERR(svn_ra_svn__parse_tuple(&elt->u.list, "c(?s)", 210 &prop->name, &prop->value)); 211 } 212 return SVN_NO_ERROR; 213} 214 215/* Parse a lockdesc, provided in LIST as specified by the protocol into 216 LOCK, allocated in POOL. */ 217static svn_error_t *parse_lock(const svn_ra_svn__list_t *list, 218 apr_pool_t *pool, 219 svn_lock_t **lock) 220{ 221 const char *cdate, *edate; 222 *lock = svn_lock_create(pool); 223 SVN_ERR(svn_ra_svn__parse_tuple(list, "ccc(?c)c(?c)", &(*lock)->path, 224 &(*lock)->token, &(*lock)->owner, 225 &(*lock)->comment, &cdate, &edate)); 226 (*lock)->path = svn_fspath__canonicalize((*lock)->path, pool); 227 SVN_ERR(svn_time_from_cstring(&(*lock)->creation_date, cdate, pool)); 228 if (edate) 229 SVN_ERR(svn_time_from_cstring(&(*lock)->expiration_date, edate, pool)); 230 return SVN_NO_ERROR; 231} 232 233/* --- AUTHENTICATION ROUTINES --- */ 234 235svn_error_t *svn_ra_svn__auth_response(svn_ra_svn_conn_t *conn, 236 apr_pool_t *pool, 237 const char *mech, const char *mech_arg) 238{ 239 return svn_error_trace(svn_ra_svn__write_tuple(conn, pool, "w(?c)", mech, mech_arg)); 240} 241 242static svn_error_t *handle_auth_request(svn_ra_svn__session_baton_t *sess, 243 apr_pool_t *pool) 244{ 245 svn_ra_svn_conn_t *conn = sess->conn; 246 svn_ra_svn__list_t *mechlist; 247 const char *realm; 248 249 SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "lc", &mechlist, &realm)); 250 if (mechlist->nelts == 0) 251 return SVN_NO_ERROR; 252 return DO_AUTH(sess, mechlist, realm, pool); 253} 254 255/* --- REPORTER IMPLEMENTATION --- */ 256 257static svn_error_t *ra_svn_set_path(void *baton, const char *path, 258 svn_revnum_t rev, 259 svn_depth_t depth, 260 svn_boolean_t start_empty, 261 const char *lock_token, 262 apr_pool_t *pool) 263{ 264 ra_svn_reporter_baton_t *b = baton; 265 266 SVN_ERR(svn_ra_svn__write_cmd_set_path(b->conn, pool, path, rev, 267 start_empty, lock_token, depth)); 268 return SVN_NO_ERROR; 269} 270 271static svn_error_t *ra_svn_delete_path(void *baton, const char *path, 272 apr_pool_t *pool) 273{ 274 ra_svn_reporter_baton_t *b = baton; 275 276 SVN_ERR(svn_ra_svn__write_cmd_delete_path(b->conn, pool, path)); 277 return SVN_NO_ERROR; 278} 279 280static svn_error_t *ra_svn_link_path(void *baton, const char *path, 281 const char *url, 282 svn_revnum_t rev, 283 svn_depth_t depth, 284 svn_boolean_t start_empty, 285 const char *lock_token, 286 apr_pool_t *pool) 287{ 288 ra_svn_reporter_baton_t *b = baton; 289 290 SVN_ERR(svn_ra_svn__write_cmd_link_path(b->conn, pool, path, url, rev, 291 start_empty, lock_token, depth)); 292 return SVN_NO_ERROR; 293} 294 295static svn_error_t *ra_svn_finish_report(void *baton, 296 apr_pool_t *pool) 297{ 298 ra_svn_reporter_baton_t *b = baton; 299 300 SVN_ERR(svn_ra_svn__write_cmd_finish_report(b->conn, b->pool)); 301 SVN_ERR(handle_auth_request(b->sess_baton, b->pool)); 302 SVN_ERR(svn_ra_svn_drive_editor2(b->conn, b->pool, b->editor, b->edit_baton, 303 NULL, FALSE)); 304 SVN_ERR(svn_ra_svn__read_cmd_response(b->conn, b->pool, "")); 305 return SVN_NO_ERROR; 306} 307 308static svn_error_t *ra_svn_abort_report(void *baton, 309 apr_pool_t *pool) 310{ 311 ra_svn_reporter_baton_t *b = baton; 312 313 SVN_ERR(svn_ra_svn__write_cmd_abort_report(b->conn, b->pool)); 314 return SVN_NO_ERROR; 315} 316 317static svn_ra_reporter3_t ra_svn_reporter = { 318 ra_svn_set_path, 319 ra_svn_delete_path, 320 ra_svn_link_path, 321 ra_svn_finish_report, 322 ra_svn_abort_report 323}; 324 325/* Set *REPORTER and *REPORT_BATON to a new reporter which will drive 326 * EDITOR/EDIT_BATON when it gets the finish_report() call. 327 * 328 * Allocate the new reporter in POOL. 329 */ 330static svn_error_t * 331ra_svn_get_reporter(svn_ra_svn__session_baton_t *sess_baton, 332 apr_pool_t *pool, 333 const svn_delta_editor_t *editor, 334 void *edit_baton, 335 const char *target, 336 svn_depth_t depth, 337 const svn_ra_reporter3_t **reporter, 338 void **report_baton) 339{ 340 ra_svn_reporter_baton_t *b; 341 const svn_delta_editor_t *filter_editor; 342 void *filter_baton; 343 344 /* We can skip the depth filtering when the user requested 345 depth_files or depth_infinity because the server will 346 transmit the right stuff anyway. */ 347 if ((depth != svn_depth_files) && (depth != svn_depth_infinity) 348 && ! svn_ra_svn_has_capability(sess_baton->conn, SVN_RA_SVN_CAP_DEPTH)) 349 { 350 SVN_ERR(svn_delta_depth_filter_editor(&filter_editor, 351 &filter_baton, 352 editor, edit_baton, depth, 353 *target != '\0', 354 pool)); 355 editor = filter_editor; 356 edit_baton = filter_baton; 357 } 358 359 b = apr_palloc(pool, sizeof(*b)); 360 b->sess_baton = sess_baton; 361 b->conn = sess_baton->conn; 362 b->pool = pool; 363 b->editor = editor; 364 b->edit_baton = edit_baton; 365 366 *reporter = &ra_svn_reporter; 367 *report_baton = b; 368 369 return SVN_NO_ERROR; 370} 371 372/* --- RA LAYER IMPLEMENTATION --- */ 373 374/* (Note: *ARGV_P is an output parameter.) */ 375static svn_error_t *find_tunnel_agent(const char *tunnel, 376 const char *hostinfo, 377 const char ***argv_p, 378 apr_hash_t *config, apr_pool_t *pool) 379{ 380 svn_config_t *cfg; 381 const char *val, *var, *cmd; 382 char **cmd_argv; 383 const char **argv; 384 apr_size_t len; 385 apr_status_t status; 386 int n; 387 388 /* Look up the tunnel specification in config. */ 389 cfg = config ? svn_hash_gets(config, SVN_CONFIG_CATEGORY_CONFIG) : NULL; 390 svn_config_get(cfg, &val, SVN_CONFIG_SECTION_TUNNELS, tunnel, NULL); 391 392 /* We have one predefined tunnel scheme, if it isn't overridden by config. */ 393 if (!val && strcmp(tunnel, "ssh") == 0) 394 { 395 /* Killing the tunnel agent with SIGTERM leads to unsightly 396 * stderr output from ssh, unless we pass -q. 397 * The "-q" option to ssh is widely supported: all versions of 398 * OpenSSH have it, the old ssh-1.x and the 2.x, 3.x ssh.com 399 * versions have it too. If the user is using some other ssh 400 * implementation that doesn't accept it, they can override it 401 * in the [tunnels] section of the config. */ 402 val = "$SVN_SSH ssh -q --"; 403 } 404 405 if (!val || !*val) 406 return svn_error_createf(SVN_ERR_BAD_URL, NULL, 407 _("Undefined tunnel scheme '%s'"), tunnel); 408 409 /* If the scheme definition begins with "$varname", it means there 410 * is an environment variable which can override the command. */ 411 if (*val == '$') 412 { 413 val++; 414 len = strcspn(val, " "); 415 var = apr_pstrmemdup(pool, val, len); 416 cmd = getenv(var); 417 if (!cmd) 418 { 419 cmd = val + len; 420 while (*cmd == ' ') 421 cmd++; 422 if (!*cmd) 423 return svn_error_createf(SVN_ERR_BAD_URL, NULL, 424 _("Tunnel scheme %s requires environment " 425 "variable %s to be defined"), tunnel, 426 var); 427 } 428 } 429 else 430 cmd = val; 431 432 /* Tokenize the command into a list of arguments. */ 433 status = apr_tokenize_to_argv(cmd, &cmd_argv, pool); 434 if (status != APR_SUCCESS) 435 return svn_error_wrap_apr(status, _("Can't tokenize command '%s'"), cmd); 436 437 /* Calc number of the fixed arguments. */ 438 for (n = 0; cmd_argv[n] != NULL; n++) 439 ; 440 441 argv = apr_palloc(pool, (n + 4) * sizeof(char *)); 442 443 /* Append the fixed arguments to the result. */ 444 for (n = 0; cmd_argv[n] != NULL; n++) 445 argv[n] = cmd_argv[n]; 446 447 argv[n++] = hostinfo; 448 argv[n++] = "svnserve"; 449 argv[n++] = "-t"; 450 argv[n] = NULL; 451 452 *argv_p = argv; 453 return SVN_NO_ERROR; 454} 455 456/* This function handles any errors which occur in the child process 457 * created for a tunnel agent. We write the error out as a command 458 * failure; the code in ra_svn_open() to read the server's greeting 459 * will see the error and return it to the caller. */ 460static void handle_child_process_error(apr_pool_t *pool, apr_status_t status, 461 const char *desc) 462{ 463 svn_ra_svn_conn_t *conn; 464 apr_file_t *in_file, *out_file; 465 svn_stream_t *in_stream, *out_stream; 466 svn_error_t *err; 467 468 if (apr_file_open_stdin(&in_file, pool) 469 || apr_file_open_stdout(&out_file, pool)) 470 return; 471 472 in_stream = svn_stream_from_aprfile2(in_file, FALSE, pool); 473 out_stream = svn_stream_from_aprfile2(out_file, FALSE, pool); 474 475 conn = svn_ra_svn_create_conn5(NULL, in_stream, out_stream, 476 SVN_DELTA_COMPRESSION_LEVEL_DEFAULT, 0, 477 0, 0, 0, pool); 478 err = svn_error_wrap_apr(status, _("Error in child process: %s"), desc); 479 svn_error_clear(svn_ra_svn__write_cmd_failure(conn, pool, err)); 480 svn_error_clear(err); 481 svn_error_clear(svn_ra_svn__flush(conn, pool)); 482} 483 484/* (Note: *CONN is an output parameter.) */ 485static svn_error_t *make_tunnel(const char **args, svn_ra_svn_conn_t **conn, 486 apr_pool_t *pool) 487{ 488 apr_status_t status; 489 apr_proc_t *proc; 490 apr_procattr_t *attr; 491 svn_error_t *err; 492 493 status = apr_procattr_create(&attr, pool); 494 if (status == APR_SUCCESS) 495 status = apr_procattr_io_set(attr, 1, 1, 0); 496 if (status == APR_SUCCESS) 497 status = apr_procattr_cmdtype_set(attr, APR_PROGRAM_PATH); 498 if (status == APR_SUCCESS) 499 status = apr_procattr_child_errfn_set(attr, handle_child_process_error); 500 proc = apr_palloc(pool, sizeof(*proc)); 501 if (status == APR_SUCCESS) 502 status = apr_proc_create(proc, *args, args, NULL, attr, pool); 503 if (status != APR_SUCCESS) 504 return svn_error_create(SVN_ERR_RA_CANNOT_CREATE_TUNNEL, 505 svn_error_wrap_apr(status, 506 _("Can't create tunnel")), NULL); 507 508 /* Arrange for the tunnel agent to get a SIGTERM on pool 509 * cleanup. This is a little extreme, but the alternatives 510 * weren't working out. 511 * 512 * Closing the pipes and waiting for the process to die 513 * was prone to mysterious hangs which are difficult to 514 * diagnose (e.g. svnserve dumps core due to unrelated bug; 515 * sshd goes into zombie state; ssh connection is never 516 * closed; ssh never terminates). 517 * See also the long dicussion in issue #2580 if you really 518 * want to know various reasons for these problems and 519 * the different opinions on this issue. 520 * 521 * On Win32, APR does not support KILL_ONLY_ONCE. It only has 522 * KILL_ALWAYS and KILL_NEVER. Other modes are converted to 523 * KILL_ALWAYS, which immediately calls TerminateProcess(). 524 * This instantly kills the tunnel, leaving sshd and svnserve 525 * on a remote machine running indefinitely. These processes 526 * accumulate. The problem is most often seen with a fast client 527 * machine and a modest internet connection, as the tunnel 528 * is killed before being able to gracefully complete the 529 * session. In that case, svn is unusable 100% of the time on 530 * the windows machine. Thus, on Win32, we use KILL_NEVER and 531 * take the lesser of two evils. 532 */ 533#ifdef WIN32 534 apr_pool_note_subprocess(pool, proc, APR_KILL_NEVER); 535#else 536 apr_pool_note_subprocess(pool, proc, APR_KILL_ONLY_ONCE); 537#endif 538 539 /* APR pipe objects inherit by default. But we don't want the 540 * tunnel agent's pipes held open by future child processes 541 * (such as other ra_svn sessions), so turn that off. */ 542 apr_file_inherit_unset(proc->in); 543 apr_file_inherit_unset(proc->out); 544 545 /* Guard against dotfile output to stdout on the server. */ 546 *conn = svn_ra_svn_create_conn5(NULL, 547 svn_stream_from_aprfile2(proc->out, FALSE, 548 pool), 549 svn_stream_from_aprfile2(proc->in, FALSE, 550 pool), 551 SVN_DELTA_COMPRESSION_LEVEL_DEFAULT, 552 0, 0, 0, 0, pool); 553 err = svn_ra_svn__skip_leading_garbage(*conn, pool); 554 if (err) 555 return svn_error_quick_wrap( 556 err, 557 _("To better debug SSH connection problems, remove the -q " 558 "option from 'ssh' in the [tunnels] section of your " 559 "Subversion configuration file.")); 560 561 return SVN_NO_ERROR; 562} 563 564/* Parse URL inot URI, validating it and setting the default port if none 565 was given. Allocate the URI fileds out of POOL. */ 566static svn_error_t *parse_url(const char *url, apr_uri_t *uri, 567 apr_pool_t *pool) 568{ 569 apr_status_t apr_err; 570 571 apr_err = apr_uri_parse(pool, url, uri); 572 573 if (apr_err != 0) 574 return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL, 575 _("Illegal svn repository URL '%s'"), url); 576 577 return SVN_NO_ERROR; 578} 579 580/* This structure is used as a baton for the pool cleanup function to 581 store tunnel parameters used by the close-tunnel callback. */ 582struct tunnel_data_t { 583 void *tunnel_context; 584 void *tunnel_baton; 585 svn_ra_close_tunnel_func_t close_tunnel; 586 svn_stream_t *request; 587 svn_stream_t *response; 588}; 589 590/* Pool cleanup function that invokes the close-tunnel callback. */ 591static apr_status_t close_tunnel_cleanup(void *baton) 592{ 593 const struct tunnel_data_t *const td = baton; 594 595 if (td->close_tunnel) 596 td->close_tunnel(td->tunnel_context, td->tunnel_baton); 597 598 svn_error_clear(svn_stream_close(td->request)); 599 600 /* We might have one stream to use for both request and response! */ 601 if (td->request != td->response) 602 svn_error_clear(svn_stream_close(td->response)); 603 604 return APR_SUCCESS; /* ignored */ 605} 606 607/* Open a session to URL, returning it in *SESS_P, allocating it in POOL. 608 URI is a parsed version of URL. CALLBACKS and CALLBACKS_BATON 609 are provided by the caller of ra_svn_open. If TUNNEL_NAME is not NULL, 610 it is the name of the tunnel type parsed from the URL scheme. 611 If TUNNEL_ARGV is not NULL, it points to a program argument list to use 612 when invoking the tunnel agent. 613*/ 614static svn_error_t *open_session(svn_ra_svn__session_baton_t **sess_p, 615 const char *url, 616 const apr_uri_t *uri, 617 const char *tunnel_name, 618 const char **tunnel_argv, 619 apr_hash_t *config, 620 const svn_ra_callbacks2_t *callbacks, 621 void *callbacks_baton, 622 svn_auth_baton_t *auth_baton, 623 apr_pool_t *result_pool, 624 apr_pool_t *scratch_pool) 625{ 626 svn_ra_svn__session_baton_t *sess; 627 svn_ra_svn_conn_t *conn; 628 apr_socket_t *sock; 629 apr_uint64_t minver, maxver; 630 svn_ra_svn__list_t *mechlist, *server_caplist, *repos_caplist; 631 const char *client_string = NULL; 632 apr_pool_t *pool = result_pool; 633 svn_ra_svn__parent_t *parent; 634 635 parent = apr_pcalloc(pool, sizeof(*parent)); 636 parent->client_url = svn_stringbuf_create(url, pool); 637 parent->server_url = svn_stringbuf_create(url, pool); 638 parent->path = svn_stringbuf_create_empty(pool); 639 640 sess = apr_palloc(pool, sizeof(*sess)); 641 sess->pool = pool; 642 sess->is_tunneled = (tunnel_name != NULL); 643 sess->parent = parent; 644 sess->user = uri->user; 645 sess->hostname = uri->hostname; 646 sess->tunnel_name = tunnel_name; 647 sess->tunnel_argv = tunnel_argv; 648 sess->callbacks = callbacks; 649 sess->callbacks_baton = callbacks_baton; 650 sess->bytes_read = sess->bytes_written = 0; 651 sess->auth_baton = auth_baton; 652 653 if (config) 654 SVN_ERR(svn_config_copy_config(&sess->config, config, pool)); 655 else 656 sess->config = NULL; 657 658 if (tunnel_name) 659 { 660 sess->realm_prefix = apr_psprintf(pool, "<svn+%s://%s:%d>", 661 tunnel_name, 662 uri->hostname, uri->port); 663 664 if (tunnel_argv) 665 SVN_ERR(make_tunnel(tunnel_argv, &conn, pool)); 666 else 667 { 668 struct tunnel_data_t *const td = apr_palloc(pool, sizeof(*td)); 669 670 td->tunnel_baton = callbacks->tunnel_baton; 671 td->close_tunnel = NULL; 672 673 SVN_ERR(callbacks->open_tunnel_func( 674 &td->request, &td->response, 675 &td->close_tunnel, &td->tunnel_context, 676 callbacks->tunnel_baton, tunnel_name, 677 uri->user, uri->hostname, uri->port, 678 callbacks->cancel_func, callbacks_baton, 679 pool)); 680 681 apr_pool_cleanup_register(pool, td, close_tunnel_cleanup, 682 apr_pool_cleanup_null); 683 684 conn = svn_ra_svn_create_conn5(NULL, td->response, td->request, 685 SVN_DELTA_COMPRESSION_LEVEL_DEFAULT, 686 0, 0, 0, 0, pool); 687 SVN_ERR(svn_ra_svn__skip_leading_garbage(conn, pool)); 688 } 689 } 690 else 691 { 692 sess->realm_prefix = apr_psprintf(pool, "<svn://%s:%d>", uri->hostname, 693 uri->port ? uri->port : SVN_RA_SVN_PORT); 694 695 SVN_ERR(make_connection(uri->hostname, 696 uri->port ? uri->port : SVN_RA_SVN_PORT, 697 &sock, pool)); 698 conn = svn_ra_svn_create_conn5(sock, NULL, NULL, 699 SVN_DELTA_COMPRESSION_LEVEL_DEFAULT, 700 0, 0, 0, 0, pool); 701 } 702 703 /* Build the useragent string, querying the client for any 704 customizations it wishes to note. For historical reasons, we 705 still deliver the hard-coded client version info 706 (SVN_RA_SVN__DEFAULT_USERAGENT) and the customized client string 707 separately in the protocol/capabilities handshake below. But the 708 commit logic wants the combined form for use with the 709 SVN_PROP_TXN_USER_AGENT ephemeral property because that's 710 consistent with our DAV approach. */ 711 if (sess->callbacks->get_client_string != NULL) 712 SVN_ERR(sess->callbacks->get_client_string(sess->callbacks_baton, 713 &client_string, pool)); 714 if (client_string) 715 sess->useragent = apr_pstrcat(pool, SVN_RA_SVN__DEFAULT_USERAGENT " ", 716 client_string, SVN_VA_NULL); 717 else 718 sess->useragent = SVN_RA_SVN__DEFAULT_USERAGENT; 719 720 /* Make sure we set conn->session before reading from it, 721 * because the reader and writer functions expect a non-NULL value. */ 722 sess->conn = conn; 723 conn->session = sess; 724 725 /* Read server's greeting. */ 726 SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "nnll", &minver, &maxver, 727 &mechlist, &server_caplist)); 728 729 /* We support protocol version 2. */ 730 if (minver > 2) 731 return svn_error_createf(SVN_ERR_RA_SVN_BAD_VERSION, NULL, 732 _("Server requires minimum version %d"), 733 (int) minver); 734 if (maxver < 2) 735 return svn_error_createf(SVN_ERR_RA_SVN_BAD_VERSION, NULL, 736 _("Server only supports versions up to %d"), 737 (int) maxver); 738 SVN_ERR(svn_ra_svn__set_capabilities(conn, server_caplist)); 739 740 /* All released versions of Subversion support edit-pipeline, 741 * so we do not support servers that do not. */ 742 if (! svn_ra_svn_has_capability(conn, SVN_RA_SVN_CAP_EDIT_PIPELINE)) 743 return svn_error_create(SVN_ERR_RA_SVN_BAD_VERSION, NULL, 744 _("Server does not support edit pipelining")); 745 746 /* In protocol version 2, we send back our protocol version, our 747 * capability list, and the URL, and subsequently there is an auth 748 * request. */ 749 /* Client-side capabilities list: */ 750 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "n(wwwwwww)cc(?c)", 751 (apr_uint64_t) 2, 752 SVN_RA_SVN_CAP_EDIT_PIPELINE, 753 SVN_RA_SVN_CAP_SVNDIFF1, 754 SVN_RA_SVN_CAP_SVNDIFF2_ACCEPTED, 755 SVN_RA_SVN_CAP_ABSENT_ENTRIES, 756 SVN_RA_SVN_CAP_DEPTH, 757 SVN_RA_SVN_CAP_MERGEINFO, 758 SVN_RA_SVN_CAP_LOG_REVPROPS, 759 url, 760 SVN_RA_SVN__DEFAULT_USERAGENT, 761 client_string)); 762 SVN_ERR(handle_auth_request(sess, pool)); 763 764 /* This is where the security layer would go into effect if we 765 * supported security layers, which is a ways off. */ 766 767 /* Read the repository's uuid and root URL, and perhaps learn more 768 capabilities that weren't available before now. */ 769 SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "c?c?l", &conn->uuid, 770 &conn->repos_root, &repos_caplist)); 771 if (repos_caplist) 772 SVN_ERR(svn_ra_svn__set_capabilities(conn, repos_caplist)); 773 774 if (conn->repos_root) 775 { 776 conn->repos_root = svn_uri_canonicalize(conn->repos_root, pool); 777 /* We should check that the returned string is a prefix of url, since 778 that's the API guarantee, but this isn't true for 1.0 servers. 779 Checking the length prevents client crashes. */ 780 if (strlen(conn->repos_root) > strlen(url)) 781 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, 782 _("Impossibly long repository root from " 783 "server")); 784 } 785 786 *sess_p = sess; 787 788 return SVN_NO_ERROR; 789} 790 791 792#ifdef SVN_HAVE_SASL 793#define RA_SVN_DESCRIPTION \ 794 N_("Module for accessing a repository using the svn network protocol.\n" \ 795 " - with Cyrus SASL authentication") 796#else 797#define RA_SVN_DESCRIPTION \ 798 N_("Module for accessing a repository using the svn network protocol.") 799#endif 800 801static const char *ra_svn_get_description(apr_pool_t *pool) 802{ 803 return _(RA_SVN_DESCRIPTION); 804} 805 806static const char * const * 807ra_svn_get_schemes(apr_pool_t *pool) 808{ 809 static const char *schemes[] = { "svn", NULL }; 810 811 return schemes; 812} 813 814 815/* A simple whitelist to ensure the following are valid: 816 * user@server 817 * [::1]:22 818 * server-name 819 * server_name 820 * 127.0.0.1 821 * with an extra restriction that a leading '-' is invalid. 822 */ 823static svn_boolean_t 824is_valid_hostinfo(const char *hostinfo) 825{ 826 const char *p = hostinfo; 827 828 if (p[0] == '-') 829 return FALSE; 830 831 while (*p) 832 { 833 if (!svn_ctype_isalnum(*p) && !strchr(":.-_[]@", *p)) 834 return FALSE; 835 836 ++p; 837 } 838 839 return TRUE; 840} 841 842static svn_error_t *ra_svn_open(svn_ra_session_t *session, 843 const char **corrected_url, 844 const char **redirect_url, 845 const char *url, 846 const svn_ra_callbacks2_t *callbacks, 847 void *callback_baton, 848 svn_auth_baton_t *auth_baton, 849 apr_hash_t *config, 850 apr_pool_t *result_pool, 851 apr_pool_t *scratch_pool) 852{ 853 apr_pool_t *sess_pool = svn_pool_create(result_pool); 854 svn_ra_svn__session_baton_t *sess; 855 const char *tunnel, **tunnel_argv; 856 apr_uri_t uri; 857 svn_config_t *cfg, *cfg_client; 858 859 /* We don't support server-prescribed redirections in ra-svn. */ 860 if (corrected_url) 861 *corrected_url = NULL; 862 if (redirect_url) 863 *redirect_url = NULL; 864 865 SVN_ERR(parse_url(url, &uri, sess_pool)); 866 867 parse_tunnel(url, &tunnel, result_pool); 868 869 /* Use the default tunnel implementation if we got a tunnel name, 870 but either do not have tunnel handler callbacks installed, or 871 the handlers don't like the tunnel name. */ 872 if (tunnel 873 && (!callbacks->open_tunnel_func 874 || (callbacks->check_tunnel_func && callbacks->open_tunnel_func 875 && !callbacks->check_tunnel_func(callbacks->tunnel_baton, 876 tunnel)))) 877 { 878 const char *decoded_hostinfo; 879 880 decoded_hostinfo = svn_path_uri_decode(uri.hostinfo, result_pool); 881 882 if (!is_valid_hostinfo(decoded_hostinfo)) 883 return svn_error_createf(SVN_ERR_BAD_URL, NULL, _("Invalid host '%s'"), 884 uri.hostinfo); 885 886 SVN_ERR(find_tunnel_agent(tunnel, decoded_hostinfo, &tunnel_argv, 887 config, result_pool)); 888 } 889 else 890 tunnel_argv = NULL; 891 892 cfg_client = config 893 ? svn_hash_gets(config, SVN_CONFIG_CATEGORY_CONFIG) 894 : NULL; 895 cfg = config ? svn_hash_gets(config, SVN_CONFIG_CATEGORY_SERVERS) : NULL; 896 svn_auth_set_parameter(auth_baton, 897 SVN_AUTH_PARAM_CONFIG_CATEGORY_CONFIG, cfg_client); 898 svn_auth_set_parameter(auth_baton, 899 SVN_AUTH_PARAM_CONFIG_CATEGORY_SERVERS, cfg); 900 901 /* We open the session in a subpool so we can get rid of it if we 902 reparent with a server that doesn't support reparenting. */ 903 SVN_ERR(open_session(&sess, url, &uri, tunnel, tunnel_argv, config, 904 callbacks, callback_baton, 905 auth_baton, sess_pool, scratch_pool)); 906 session->priv = sess; 907 908 return SVN_NO_ERROR; 909} 910 911static svn_error_t *ra_svn_dup_session(svn_ra_session_t *new_session, 912 svn_ra_session_t *old_session, 913 const char *new_session_url, 914 apr_pool_t *result_pool, 915 apr_pool_t *scratch_pool) 916{ 917 svn_ra_svn__session_baton_t *old_sess = old_session->priv; 918 919 SVN_ERR(ra_svn_open(new_session, NULL, NULL, new_session_url, 920 old_sess->callbacks, old_sess->callbacks_baton, 921 old_sess->auth_baton, old_sess->config, 922 result_pool, scratch_pool)); 923 924 return SVN_NO_ERROR; 925} 926 927/* Send the "reparent to URL" command to the server for RA_SESSION and 928 update the session state. Use SCRATCH_POOL for tempoaries. 929 */ 930static svn_error_t * 931reparent_server(svn_ra_session_t *ra_session, 932 const char *url, 933 apr_pool_t *scratch_pool) 934{ 935 svn_ra_svn__session_baton_t *sess = ra_session->priv; 936 svn_ra_svn__parent_t *parent = sess->parent; 937 svn_ra_svn_conn_t *conn = sess->conn; 938 svn_error_t *err; 939 apr_pool_t *sess_pool; 940 svn_ra_svn__session_baton_t *new_sess; 941 apr_uri_t uri; 942 943 /* Send the request to the server. */ 944 SVN_ERR(svn_ra_svn__write_cmd_reparent(conn, scratch_pool, url)); 945 err = handle_auth_request(sess, scratch_pool); 946 if (! err) 947 { 948 SVN_ERR(svn_ra_svn__read_cmd_response(conn, scratch_pool, "")); 949 svn_stringbuf_set(parent->server_url, url); 950 return SVN_NO_ERROR; 951 } 952 else if (err->apr_err != SVN_ERR_RA_SVN_UNKNOWN_CMD) 953 return err; 954 955 /* Servers before 1.4 doesn't support this command; try to reconnect 956 instead. */ 957 svn_error_clear(err); 958 /* Create a new subpool of the RA session pool. */ 959 sess_pool = svn_pool_create(ra_session->pool); 960 err = parse_url(url, &uri, sess_pool); 961 if (! err) 962 err = open_session(&new_sess, url, &uri, sess->tunnel_name, sess->tunnel_argv, 963 sess->config, sess->callbacks, sess->callbacks_baton, 964 sess->auth_baton, sess_pool, sess_pool); 965 /* We destroy the new session pool on error, since it is allocated in 966 the main session pool. */ 967 if (err) 968 { 969 svn_pool_destroy(sess_pool); 970 return err; 971 } 972 973 /* We have a new connection, assign it and destroy the old. */ 974 ra_session->priv = new_sess; 975 svn_pool_destroy(sess->pool); 976 977 return SVN_NO_ERROR; 978} 979 980/* Make sure that RA_SESSION's client and server-side parent infp are in 981 sync. Use SCRATCH_POOL for temporary allocations. */ 982static svn_error_t * 983ensure_exact_server_parent(svn_ra_session_t *ra_session, 984 apr_pool_t *scratch_pool) 985{ 986 svn_ra_svn__session_baton_t *sess = ra_session->priv; 987 svn_ra_svn__parent_t *parent = sess->parent; 988 989 /* During e.g. a checkout operation, many requests will be sent for the 990 same URL that was used to create the session. So, both sides are 991 often already in sync. */ 992 if (svn_stringbuf_compare(parent->client_url, parent->server_url)) 993 return SVN_NO_ERROR; 994 995 /* Actually reparent the server to the session URL. */ 996 SVN_ERR(reparent_server(ra_session, parent->client_url->data, 997 scratch_pool)); 998 svn_stringbuf_setempty(parent->path); 999 1000 return SVN_NO_ERROR; 1001} 1002 1003/* Return a copy of PATH, adjusted to the RA_SESSION's server parent URL. 1004 Allocate the result in RESULT_POOL. */ 1005static const char * 1006reparent_path(svn_ra_session_t *ra_session, 1007 const char *path, 1008 apr_pool_t *result_pool) 1009{ 1010 svn_ra_svn__session_baton_t *sess = ra_session->priv; 1011 svn_ra_svn__parent_t *parent = sess->parent; 1012 1013 return svn_relpath_join(parent->path->data, path, result_pool); 1014} 1015 1016/* Return a copy of PATHS, containing the same const char * paths but 1017 adjusted to the RA_SESSION's server parent URL. Returns NULL if 1018 PATHS is NULL. Allocate the result in RESULT_POOL. */ 1019static apr_array_header_t * 1020reparent_path_array(svn_ra_session_t *ra_session, 1021 const apr_array_header_t *paths, 1022 apr_pool_t *result_pool) 1023{ 1024 int i; 1025 apr_array_header_t *result; 1026 1027 if (!paths) 1028 return NULL; 1029 1030 result = apr_array_copy(result_pool, paths); 1031 for (i = 0; i < result->nelts; ++i) 1032 { 1033 const char **path = &APR_ARRAY_IDX(result, i, const char *); 1034 *path = reparent_path(ra_session, *path, result_pool); 1035 } 1036 1037 return result; 1038} 1039 1040/* Return a copy of PATHS, containing the same paths for keys but adjusted 1041 to the RA_SESSION's server parent URL. Keeps the values as-are and 1042 returns NULL if PATHS is NULL. Allocate the result in RESULT_POOL. */ 1043static apr_hash_t * 1044reparent_path_hash(svn_ra_session_t *ra_session, 1045 apr_hash_t *paths, 1046 apr_pool_t *result_pool, 1047 apr_pool_t *scratch_pool) 1048{ 1049 apr_hash_t *result; 1050 apr_hash_index_t *hi; 1051 1052 if (!paths) 1053 return NULL; 1054 1055 result = svn_hash__make(result_pool); 1056 for (hi = apr_hash_first(scratch_pool, paths); hi; hi = apr_hash_next(hi)) 1057 { 1058 const char *path = apr_hash_this_key(hi); 1059 svn_hash_sets(result, 1060 reparent_path(ra_session, path, result_pool), 1061 apr_hash_this_val(hi)); 1062 } 1063 1064 return result; 1065} 1066 1067static svn_error_t *ra_svn_reparent(svn_ra_session_t *ra_session, 1068 const char *url, 1069 apr_pool_t *pool) 1070{ 1071 svn_ra_svn__session_baton_t *sess = ra_session->priv; 1072 svn_ra_svn__parent_t *parent = sess->parent; 1073 svn_ra_svn_conn_t *conn = sess->conn; 1074 const char *path; 1075 1076 /* Eliminate reparent requests if they are to a sub-path of the 1077 server's current parent path. */ 1078 path = svn_uri_skip_ancestor(parent->server_url->data, url, pool); 1079 if (!path) 1080 { 1081 /* Send the request to the server. 1082 1083 If within the same repository, reparent to the repo root 1084 because this will maximize the chance to turn future reparent 1085 requests into a client-side update of the rel path. */ 1086 path = conn->repos_root 1087 ? svn_uri_skip_ancestor(conn->repos_root, url, pool) 1088 : NULL; 1089 1090 if (path) 1091 SVN_ERR(reparent_server(ra_session, conn->repos_root, pool)); 1092 else 1093 SVN_ERR(reparent_server(ra_session, url, pool)); 1094 } 1095 1096 /* Update the local PARENT information. 1097 PARENT.SERVER_BASE_URL is already up-to-date. */ 1098 svn_stringbuf_set(parent->client_url, url); 1099 if (path) 1100 svn_stringbuf_set(parent->path, path); 1101 else 1102 svn_stringbuf_setempty(parent->path); 1103 1104 return SVN_NO_ERROR; 1105} 1106 1107static svn_error_t *ra_svn_get_session_url(svn_ra_session_t *session, 1108 const char **url, 1109 apr_pool_t *pool) 1110{ 1111 svn_ra_svn__session_baton_t *sess = session->priv; 1112 svn_ra_svn__parent_t *parent = sess->parent; 1113 1114 *url = apr_pstrmemdup(pool, parent->client_url->data, 1115 parent->client_url->len); 1116 1117 return SVN_NO_ERROR; 1118} 1119 1120static svn_error_t *ra_svn_get_latest_rev(svn_ra_session_t *session, 1121 svn_revnum_t *rev, apr_pool_t *pool) 1122{ 1123 svn_ra_svn__session_baton_t *sess_baton = session->priv; 1124 svn_ra_svn_conn_t *conn = sess_baton->conn; 1125 1126 SVN_ERR(svn_ra_svn__write_cmd_get_latest_rev(conn, pool)); 1127 SVN_ERR(handle_auth_request(sess_baton, pool)); 1128 SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "r", rev)); 1129 return SVN_NO_ERROR; 1130} 1131 1132static svn_error_t *ra_svn_get_dated_rev(svn_ra_session_t *session, 1133 svn_revnum_t *rev, apr_time_t tm, 1134 apr_pool_t *pool) 1135{ 1136 svn_ra_svn__session_baton_t *sess_baton = session->priv; 1137 svn_ra_svn_conn_t *conn = sess_baton->conn; 1138 1139 SVN_ERR(svn_ra_svn__write_cmd_get_dated_rev(conn, pool, tm)); 1140 SVN_ERR(handle_auth_request(sess_baton, pool)); 1141 SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "r", rev)); 1142 return SVN_NO_ERROR; 1143} 1144 1145/* Forward declaration. */ 1146static svn_error_t *ra_svn_has_capability(svn_ra_session_t *session, 1147 svn_boolean_t *has, 1148 const char *capability, 1149 apr_pool_t *pool); 1150 1151static svn_error_t *ra_svn_change_rev_prop(svn_ra_session_t *session, svn_revnum_t rev, 1152 const char *name, 1153 const svn_string_t *const *old_value_p, 1154 const svn_string_t *value, 1155 apr_pool_t *pool) 1156{ 1157 svn_ra_svn__session_baton_t *sess_baton = session->priv; 1158 svn_ra_svn_conn_t *conn = sess_baton->conn; 1159 svn_boolean_t dont_care; 1160 const svn_string_t *old_value; 1161 svn_boolean_t has_atomic_revprops; 1162 1163 SVN_ERR(ra_svn_has_capability(session, &has_atomic_revprops, 1164 SVN_RA_SVN_CAP_ATOMIC_REVPROPS, 1165 pool)); 1166 1167 if (old_value_p) 1168 { 1169 /* How did you get past the same check in svn_ra_change_rev_prop2()? */ 1170 SVN_ERR_ASSERT(has_atomic_revprops); 1171 1172 dont_care = FALSE; 1173 old_value = *old_value_p; 1174 } 1175 else 1176 { 1177 dont_care = TRUE; 1178 old_value = NULL; 1179 } 1180 1181 if (has_atomic_revprops) 1182 SVN_ERR(svn_ra_svn__write_cmd_change_rev_prop2(conn, pool, rev, name, 1183 value, dont_care, 1184 old_value)); 1185 else 1186 SVN_ERR(svn_ra_svn__write_cmd_change_rev_prop(conn, pool, rev, name, 1187 value)); 1188 1189 SVN_ERR(handle_auth_request(sess_baton, pool)); 1190 SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "")); 1191 return SVN_NO_ERROR; 1192} 1193 1194static svn_error_t *ra_svn_get_uuid(svn_ra_session_t *session, const char **uuid, 1195 apr_pool_t *pool) 1196{ 1197 svn_ra_svn__session_baton_t *sess_baton = session->priv; 1198 svn_ra_svn_conn_t *conn = sess_baton->conn; 1199 1200 *uuid = conn->uuid; 1201 return SVN_NO_ERROR; 1202} 1203 1204static svn_error_t *ra_svn_get_repos_root(svn_ra_session_t *session, const char **url, 1205 apr_pool_t *pool) 1206{ 1207 svn_ra_svn__session_baton_t *sess_baton = session->priv; 1208 svn_ra_svn_conn_t *conn = sess_baton->conn; 1209 1210 if (!conn->repos_root) 1211 return svn_error_create(SVN_ERR_RA_SVN_BAD_VERSION, NULL, 1212 _("Server did not send repository root")); 1213 *url = conn->repos_root; 1214 return SVN_NO_ERROR; 1215} 1216 1217static svn_error_t *ra_svn_rev_proplist(svn_ra_session_t *session, svn_revnum_t rev, 1218 apr_hash_t **props, apr_pool_t *pool) 1219{ 1220 svn_ra_svn__session_baton_t *sess_baton = session->priv; 1221 svn_ra_svn_conn_t *conn = sess_baton->conn; 1222 svn_ra_svn__list_t *proplist; 1223 1224 SVN_ERR(svn_ra_svn__write_cmd_rev_proplist(conn, pool, rev)); 1225 SVN_ERR(handle_auth_request(sess_baton, pool)); 1226 SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "l", &proplist)); 1227 SVN_ERR(svn_ra_svn__parse_proplist(proplist, pool, props)); 1228 return SVN_NO_ERROR; 1229} 1230 1231static svn_error_t *ra_svn_rev_prop(svn_ra_session_t *session, svn_revnum_t rev, 1232 const char *name, 1233 svn_string_t **value, apr_pool_t *pool) 1234{ 1235 svn_ra_svn__session_baton_t *sess_baton = session->priv; 1236 svn_ra_svn_conn_t *conn = sess_baton->conn; 1237 1238 SVN_ERR(svn_ra_svn__write_cmd_rev_prop(conn, pool, rev, name)); 1239 SVN_ERR(handle_auth_request(sess_baton, pool)); 1240 SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "(?s)", value)); 1241 return SVN_NO_ERROR; 1242} 1243 1244static svn_error_t *ra_svn_end_commit(void *baton) 1245{ 1246 ra_svn_commit_callback_baton_t *ccb = baton; 1247 svn_commit_info_t *commit_info = svn_create_commit_info(ccb->pool); 1248 1249 SVN_ERR(handle_auth_request(ccb->sess_baton, ccb->pool)); 1250 SVN_ERR(svn_ra_svn__read_tuple(ccb->sess_baton->conn, ccb->pool, 1251 "r(?c)(?c)?(?c)", 1252 &(commit_info->revision), 1253 &(commit_info->date), 1254 &(commit_info->author), 1255 &(commit_info->post_commit_err))); 1256 1257 commit_info->repos_root = apr_pstrdup(ccb->pool, 1258 ccb->sess_baton->conn->repos_root); 1259 1260 if (ccb->callback) 1261 SVN_ERR(ccb->callback(commit_info, ccb->callback_baton, ccb->pool)); 1262 1263 return SVN_NO_ERROR; 1264} 1265 1266static svn_error_t *ra_svn_commit(svn_ra_session_t *session, 1267 const svn_delta_editor_t **editor, 1268 void **edit_baton, 1269 apr_hash_t *revprop_table, 1270 svn_commit_callback2_t callback, 1271 void *callback_baton, 1272 apr_hash_t *lock_tokens, 1273 svn_boolean_t keep_locks, 1274 apr_pool_t *pool) 1275{ 1276 svn_ra_svn__session_baton_t *sess_baton = session->priv; 1277 svn_ra_svn_conn_t *conn = sess_baton->conn; 1278 ra_svn_commit_callback_baton_t *ccb; 1279 apr_hash_index_t *hi; 1280 apr_pool_t *iterpool; 1281 const svn_string_t *log_msg = svn_hash_gets(revprop_table, 1282 SVN_PROP_REVISION_LOG); 1283 1284 if (log_msg == NULL && 1285 ! svn_ra_svn_has_capability(conn, SVN_RA_SVN_CAP_COMMIT_REVPROPS)) 1286 { 1287 return svn_error_createf(SVN_ERR_BAD_PROPERTY_VALUE, NULL, 1288 _("ra_svn does not support not specifying " 1289 "a log message with pre-1.5 servers; " 1290 "consider passing an empty one, or upgrading " 1291 "the server")); 1292 } 1293 else if (log_msg == NULL) 1294 /* 1.5+ server. Set LOG_MSG to something, since the 'logmsg' argument 1295 to the 'commit' protocol command is non-optional; on the server side, 1296 only REVPROP_TABLE will be used, and LOG_MSG will be ignored. The 1297 "svn:log" member of REVPROP_TABLE table is NULL, therefore the commit 1298 will have a NULL log message (not just "", really NULL). 1299 1300 svnserve 1.5.x+ has always ignored LOG_MSG when REVPROP_TABLE was 1301 present; this was elevated to a protocol promise in r1498550 (and 1302 later documented in this comment) in order to fix the segmentation 1303 fault bug described in the log message of r1498550.*/ 1304 log_msg = svn_string_create("", pool); 1305 1306 /* If we're sending revprops other than svn:log, make sure the server won't 1307 silently ignore them. */ 1308 if (apr_hash_count(revprop_table) > 1 && 1309 ! svn_ra_svn_has_capability(conn, SVN_RA_SVN_CAP_COMMIT_REVPROPS)) 1310 return svn_error_create(SVN_ERR_RA_NOT_IMPLEMENTED, NULL, 1311 _("Server doesn't support setting arbitrary " 1312 "revision properties during commit")); 1313 1314 /* If the server supports ephemeral txnprops, add the one that 1315 reports the client's version level string. */ 1316 if (svn_ra_svn_has_capability(conn, SVN_RA_SVN_CAP_COMMIT_REVPROPS) && 1317 svn_ra_svn_has_capability(conn, SVN_RA_SVN_CAP_EPHEMERAL_TXNPROPS)) 1318 { 1319 svn_hash_sets(revprop_table, SVN_PROP_TXN_CLIENT_COMPAT_VERSION, 1320 svn_string_create(SVN_VER_NUMBER, pool)); 1321 svn_hash_sets(revprop_table, SVN_PROP_TXN_USER_AGENT, 1322 svn_string_create(sess_baton->useragent, pool)); 1323 } 1324 1325 /* Callbacks may assume that all data is relative the sessions's URL. */ 1326 SVN_ERR(ensure_exact_server_parent(session, pool)); 1327 1328 /* Tell the server we're starting the commit. 1329 Send log message here for backwards compatibility with servers 1330 before 1.5. */ 1331 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w(c(!", "commit", 1332 log_msg->data)); 1333 if (lock_tokens) 1334 { 1335 iterpool = svn_pool_create(pool); 1336 for (hi = apr_hash_first(pool, lock_tokens); hi; hi = apr_hash_next(hi)) 1337 { 1338 const void *key; 1339 void *val; 1340 const char *path, *token; 1341 1342 svn_pool_clear(iterpool); 1343 apr_hash_this(hi, &key, NULL, &val); 1344 path = key; 1345 token = val; 1346 SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "cc", path, token)); 1347 } 1348 svn_pool_destroy(iterpool); 1349 } 1350 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!)b(!", keep_locks)); 1351 SVN_ERR(svn_ra_svn__write_proplist(conn, pool, revprop_table)); 1352 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!))")); 1353 SVN_ERR(handle_auth_request(sess_baton, pool)); 1354 SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "")); 1355 1356 /* Remember a few arguments for when the commit is over. */ 1357 ccb = apr_palloc(pool, sizeof(*ccb)); 1358 ccb->sess_baton = sess_baton; 1359 ccb->pool = pool; 1360 ccb->new_rev = NULL; 1361 ccb->callback = callback; 1362 ccb->callback_baton = callback_baton; 1363 1364 /* Fetch an editor for the caller to drive. The editor will call 1365 * ra_svn_end_commit() upon close_edit(), at which point we'll fill 1366 * in the new_rev, committed_date, and committed_author values. */ 1367 svn_ra_svn_get_editor(editor, edit_baton, conn, pool, 1368 ra_svn_end_commit, ccb); 1369 return SVN_NO_ERROR; 1370} 1371 1372/* Parse IPROPLIST, an array of svn_ra_svn__item_t structures, as a list of 1373 const char * repos relative paths and properties for those paths, storing 1374 the result as an array of svn_prop_inherited_item_t *items. */ 1375static svn_error_t * 1376parse_iproplist(apr_array_header_t **inherited_props, 1377 const svn_ra_svn__list_t *iproplist, 1378 svn_ra_session_t *session, 1379 apr_pool_t *result_pool, 1380 apr_pool_t *scratch_pool) 1381 1382{ 1383 int i; 1384 apr_pool_t *iterpool; 1385 1386 if (iproplist == NULL) 1387 { 1388 /* If the server doesn't have the SVN_RA_CAPABILITY_INHERITED_PROPS 1389 capability we shouldn't be asking for inherited props, but if we 1390 did and the server sent back nothing then we'll want to handle 1391 that. */ 1392 *inherited_props = NULL; 1393 return SVN_NO_ERROR; 1394 } 1395 1396 *inherited_props = apr_array_make( 1397 result_pool, iproplist->nelts, sizeof(svn_prop_inherited_item_t *)); 1398 1399 iterpool = svn_pool_create(scratch_pool); 1400 1401 for (i = 0; i < iproplist->nelts; i++) 1402 { 1403 svn_ra_svn__list_t *iprop_list; 1404 char *parent_rel_path; 1405 apr_hash_t *iprops; 1406 apr_hash_index_t *hi; 1407 svn_prop_inherited_item_t *new_iprop = 1408 apr_palloc(result_pool, sizeof(*new_iprop)); 1409 svn_ra_svn__item_t *elt = &SVN_RA_SVN__LIST_ITEM(iproplist, i); 1410 if (elt->kind != SVN_RA_SVN_LIST) 1411 return svn_error_create( 1412 SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, 1413 _("Inherited proplist element not a list")); 1414 1415 svn_pool_clear(iterpool); 1416 1417 SVN_ERR(svn_ra_svn__parse_tuple(&elt->u.list, "cl", 1418 &parent_rel_path, &iprop_list)); 1419 SVN_ERR(svn_ra_svn__parse_proplist(iprop_list, iterpool, &iprops)); 1420 new_iprop->path_or_url = apr_pstrdup(result_pool, parent_rel_path); 1421 new_iprop->prop_hash = svn_hash__make(result_pool); 1422 for (hi = apr_hash_first(iterpool, iprops); 1423 hi; 1424 hi = apr_hash_next(hi)) 1425 { 1426 const char *name = apr_hash_this_key(hi); 1427 svn_string_t *value = apr_hash_this_val(hi); 1428 svn_hash_sets(new_iprop->prop_hash, 1429 apr_pstrdup(result_pool, name), 1430 svn_string_dup(value, result_pool)); 1431 } 1432 APR_ARRAY_PUSH(*inherited_props, svn_prop_inherited_item_t *) = 1433 new_iprop; 1434 } 1435 svn_pool_destroy(iterpool); 1436 return SVN_NO_ERROR; 1437} 1438 1439static svn_error_t *ra_svn_get_file(svn_ra_session_t *session, const char *path, 1440 svn_revnum_t rev, svn_stream_t *stream, 1441 svn_revnum_t *fetched_rev, 1442 apr_hash_t **props, 1443 apr_pool_t *pool) 1444{ 1445 svn_ra_svn__session_baton_t *sess_baton = session->priv; 1446 svn_ra_svn_conn_t *conn = sess_baton->conn; 1447 svn_ra_svn__list_t *proplist; 1448 const char *expected_digest; 1449 svn_checksum_t *expected_checksum = NULL; 1450 svn_checksum_ctx_t *checksum_ctx; 1451 apr_pool_t *iterpool; 1452 1453 path = reparent_path(session, path, pool); 1454 SVN_ERR(svn_ra_svn__write_cmd_get_file(conn, pool, path, rev, 1455 (props != NULL), (stream != NULL))); 1456 SVN_ERR(handle_auth_request(sess_baton, pool)); 1457 SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "(?c)rl", 1458 &expected_digest, 1459 &rev, &proplist)); 1460 1461 if (fetched_rev) 1462 *fetched_rev = rev; 1463 if (props) 1464 SVN_ERR(svn_ra_svn__parse_proplist(proplist, pool, props)); 1465 1466 /* We're done if the contents weren't wanted. */ 1467 if (!stream) 1468 return SVN_NO_ERROR; 1469 1470 if (expected_digest) 1471 { 1472 SVN_ERR(svn_checksum_parse_hex(&expected_checksum, svn_checksum_md5, 1473 expected_digest, pool)); 1474 checksum_ctx = svn_checksum_ctx_create(svn_checksum_md5, pool); 1475 } 1476 1477 /* Read the file's contents. */ 1478 iterpool = svn_pool_create(pool); 1479 while (1) 1480 { 1481 svn_ra_svn__item_t *item; 1482 1483 svn_pool_clear(iterpool); 1484 SVN_ERR(svn_ra_svn__read_item(conn, iterpool, &item)); 1485 if (item->kind != SVN_RA_SVN_STRING) 1486 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, 1487 _("Non-string as part of file contents")); 1488 if (item->u.string.len == 0) 1489 break; 1490 1491 if (expected_checksum) 1492 SVN_ERR(svn_checksum_update(checksum_ctx, item->u.string.data, 1493 item->u.string.len)); 1494 1495 SVN_ERR(svn_stream_write(stream, item->u.string.data, 1496 &item->u.string.len)); 1497 } 1498 svn_pool_destroy(iterpool); 1499 1500 SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "")); 1501 1502 if (expected_checksum) 1503 { 1504 svn_checksum_t *checksum; 1505 1506 SVN_ERR(svn_checksum_final(&checksum, checksum_ctx, pool)); 1507 if (!svn_checksum_match(checksum, expected_checksum)) 1508 return svn_checksum_mismatch_err(expected_checksum, checksum, pool, 1509 _("Checksum mismatch for '%s'"), 1510 path); 1511 } 1512 1513 return SVN_NO_ERROR; 1514} 1515 1516/* Write the protocol words that correspond to DIRENT_FIELDS to CONN 1517 * and use SCRATCH_POOL for temporary allocations. */ 1518static svn_error_t * 1519send_dirent_fields(svn_ra_svn_conn_t *conn, 1520 apr_uint32_t dirent_fields, 1521 apr_pool_t *scratch_pool) 1522{ 1523 if (dirent_fields & SVN_DIRENT_KIND) 1524 SVN_ERR(svn_ra_svn__write_word(conn, scratch_pool, 1525 SVN_RA_SVN_DIRENT_KIND)); 1526 if (dirent_fields & SVN_DIRENT_SIZE) 1527 SVN_ERR(svn_ra_svn__write_word(conn, scratch_pool, 1528 SVN_RA_SVN_DIRENT_SIZE)); 1529 if (dirent_fields & SVN_DIRENT_HAS_PROPS) 1530 SVN_ERR(svn_ra_svn__write_word(conn, scratch_pool, 1531 SVN_RA_SVN_DIRENT_HAS_PROPS)); 1532 if (dirent_fields & SVN_DIRENT_CREATED_REV) 1533 SVN_ERR(svn_ra_svn__write_word(conn, scratch_pool, 1534 SVN_RA_SVN_DIRENT_CREATED_REV)); 1535 if (dirent_fields & SVN_DIRENT_TIME) 1536 SVN_ERR(svn_ra_svn__write_word(conn, scratch_pool, 1537 SVN_RA_SVN_DIRENT_TIME)); 1538 if (dirent_fields & SVN_DIRENT_LAST_AUTHOR) 1539 SVN_ERR(svn_ra_svn__write_word(conn, scratch_pool, 1540 SVN_RA_SVN_DIRENT_LAST_AUTHOR)); 1541 1542 return SVN_NO_ERROR; 1543} 1544 1545static svn_error_t *ra_svn_get_dir(svn_ra_session_t *session, 1546 apr_hash_t **dirents, 1547 svn_revnum_t *fetched_rev, 1548 apr_hash_t **props, 1549 const char *path, 1550 svn_revnum_t rev, 1551 apr_uint32_t dirent_fields, 1552 apr_pool_t *pool) 1553{ 1554 svn_ra_svn__session_baton_t *sess_baton = session->priv; 1555 svn_ra_svn_conn_t *conn = sess_baton->conn; 1556 svn_ra_svn__list_t *proplist, *dirlist; 1557 int i; 1558 1559 path = reparent_path(session, path, pool); 1560 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w(c(?r)bb(!", "get-dir", path, 1561 rev, (props != NULL), (dirents != NULL))); 1562 SVN_ERR(send_dirent_fields(conn, dirent_fields, pool)); 1563 1564 /* Always send the, nominally optional, want-iprops as "false" to 1565 workaround a bug in svnserve 1.8.0-1.8.8 that causes the server 1566 to see "true" if it is omitted. */ 1567 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!)b)", FALSE)); 1568 1569 SVN_ERR(handle_auth_request(sess_baton, pool)); 1570 SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "rll", &rev, &proplist, 1571 &dirlist)); 1572 1573 if (fetched_rev) 1574 *fetched_rev = rev; 1575 if (props) 1576 SVN_ERR(svn_ra_svn__parse_proplist(proplist, pool, props)); 1577 1578 /* We're done if dirents aren't wanted. */ 1579 if (!dirents) 1580 return SVN_NO_ERROR; 1581 1582 /* Interpret the directory list. */ 1583 *dirents = svn_hash__make(pool); 1584 for (i = 0; i < dirlist->nelts; i++) 1585 { 1586 const char *name, *kind, *cdate, *cauthor; 1587 svn_boolean_t has_props; 1588 svn_dirent_t *dirent; 1589 apr_uint64_t size; 1590 svn_revnum_t crev; 1591 svn_ra_svn__item_t *elt = &SVN_RA_SVN__LIST_ITEM(dirlist, i); 1592 1593 if (elt->kind != SVN_RA_SVN_LIST) 1594 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, 1595 _("Dirlist element not a list")); 1596 SVN_ERR(svn_ra_svn__parse_tuple(&elt->u.list, "cwnbr(?c)(?c)", 1597 &name, &kind, &size, &has_props, 1598 &crev, &cdate, &cauthor)); 1599 1600 /* Nothing to sanitize here. Any multi-segment path is simply 1601 illegal in the hash returned by svn_ra_get_dir2. */ 1602 if (strchr(name, '/')) 1603 return svn_error_createf(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, 1604 _("Invalid directory entry name '%s'"), 1605 name); 1606 1607 dirent = svn_dirent_create(pool); 1608 dirent->kind = svn_node_kind_from_word(kind); 1609 dirent->size = size;/* FIXME: svn_filesize_t */ 1610 dirent->has_props = has_props; 1611 dirent->created_rev = crev; 1612 /* NOTE: the tuple's format string says CDATE may be NULL. But this 1613 function does not allow that. The server has always sent us some 1614 random date, however, so this just happens to work. But let's 1615 be wary of servers that are (improperly) fixed to send NULL. 1616 1617 Note: they should NOT be "fixed" to send NULL, as that would break 1618 any older clients which received that NULL. But we may as well 1619 be defensive against a malicous server. */ 1620 if (cdate == NULL) 1621 dirent->time = 0; 1622 else 1623 SVN_ERR(svn_time_from_cstring(&dirent->time, cdate, pool)); 1624 dirent->last_author = cauthor; 1625 svn_hash_sets(*dirents, name, dirent); 1626 } 1627 1628 return SVN_NO_ERROR; 1629} 1630 1631/* Converts a apr_uint64_t with values TRUE, FALSE or 1632 SVN_RA_SVN_UNSPECIFIED_NUMBER as provided by svn_ra_svn__parse_tuple 1633 to a svn_tristate_t */ 1634static svn_tristate_t 1635optbool_to_tristate(apr_uint64_t v) 1636{ 1637 if (v == TRUE) /* not just non-zero but exactly equal to 'TRUE' */ 1638 return svn_tristate_true; 1639 if (v == FALSE) 1640 return svn_tristate_false; 1641 1642 return svn_tristate_unknown; /* Contains SVN_RA_SVN_UNSPECIFIED_NUMBER */ 1643} 1644 1645/* If REVISION is SVN_INVALID_REVNUM, no value is sent to the 1646 server, which defaults to youngest. */ 1647static svn_error_t *ra_svn_get_mergeinfo(svn_ra_session_t *session, 1648 svn_mergeinfo_catalog_t *catalog, 1649 const apr_array_header_t *paths, 1650 svn_revnum_t revision, 1651 svn_mergeinfo_inheritance_t inherit, 1652 svn_boolean_t include_descendants, 1653 apr_pool_t *pool) 1654{ 1655 svn_ra_svn__session_baton_t *sess_baton = session->priv; 1656 svn_ra_svn__parent_t *parent = sess_baton->parent; 1657 svn_ra_svn_conn_t *conn = sess_baton->conn; 1658 int i; 1659 svn_ra_svn__list_t *mergeinfo_tuple; 1660 svn_ra_svn__item_t *elt; 1661 const char *path; 1662 1663 paths = reparent_path_array(session, paths, pool); 1664 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w((!", "get-mergeinfo")); 1665 for (i = 0; i < paths->nelts; i++) 1666 { 1667 path = APR_ARRAY_IDX(paths, i, const char *); 1668 SVN_ERR(svn_ra_svn__write_cstring(conn, pool, path)); 1669 } 1670 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!)(?r)wb)", revision, 1671 svn_inheritance_to_word(inherit), 1672 include_descendants)); 1673 1674 SVN_ERR(handle_auth_request(sess_baton, pool)); 1675 SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "l", &mergeinfo_tuple)); 1676 1677 *catalog = NULL; 1678 if (mergeinfo_tuple->nelts > 0) 1679 { 1680 *catalog = svn_hash__make(pool); 1681 for (i = 0; i < mergeinfo_tuple->nelts; i++) 1682 { 1683 svn_mergeinfo_t for_path; 1684 const char *to_parse; 1685 1686 elt = &SVN_RA_SVN__LIST_ITEM(mergeinfo_tuple, i); 1687 if (elt->kind != SVN_RA_SVN_LIST) 1688 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, 1689 _("Mergeinfo element is not a list")); 1690 SVN_ERR(svn_ra_svn__parse_tuple(&elt->u.list, "cc", 1691 &path, &to_parse)); 1692 SVN_ERR(svn_mergeinfo_parse(&for_path, to_parse, pool)); 1693 1694 /* Correct for naughty servers that send "relative" paths 1695 with leading slashes! */ 1696 if (path[0] == '/') 1697 ++path; 1698 1699 /* Correct for the (potential) difference between client and 1700 server-side session parent paths. */ 1701 path = svn_relpath_skip_ancestor(parent->path->data, path); 1702 svn_hash_sets(*catalog, path, for_path); 1703 } 1704 } 1705 1706 return SVN_NO_ERROR; 1707} 1708 1709static svn_error_t *ra_svn_update(svn_ra_session_t *session, 1710 const svn_ra_reporter3_t **reporter, 1711 void **report_baton, svn_revnum_t rev, 1712 const char *target, svn_depth_t depth, 1713 svn_boolean_t send_copyfrom_args, 1714 svn_boolean_t ignore_ancestry, 1715 const svn_delta_editor_t *update_editor, 1716 void *update_baton, 1717 apr_pool_t *pool, 1718 apr_pool_t *scratch_pool) 1719{ 1720 svn_ra_svn__session_baton_t *sess_baton = session->priv; 1721 svn_ra_svn_conn_t *conn = sess_baton->conn; 1722 svn_boolean_t recurse = DEPTH_TO_RECURSE(depth); 1723 1724 /* Callbacks may assume that all data is relative the sessions's URL. */ 1725 SVN_ERR(ensure_exact_server_parent(session, scratch_pool)); 1726 1727 /* Tell the server we want to start an update. */ 1728 SVN_ERR(svn_ra_svn__write_cmd_update(conn, pool, rev, target, recurse, 1729 depth, send_copyfrom_args, 1730 ignore_ancestry)); 1731 SVN_ERR(handle_auth_request(sess_baton, pool)); 1732 1733 /* Fetch a reporter for the caller to drive. The reporter will drive 1734 * update_editor upon finish_report(). */ 1735 SVN_ERR(ra_svn_get_reporter(sess_baton, pool, update_editor, update_baton, 1736 target, depth, reporter, report_baton)); 1737 return SVN_NO_ERROR; 1738} 1739 1740static svn_error_t * 1741ra_svn_switch(svn_ra_session_t *session, 1742 const svn_ra_reporter3_t **reporter, 1743 void **report_baton, svn_revnum_t rev, 1744 const char *target, svn_depth_t depth, 1745 const char *switch_url, 1746 svn_boolean_t send_copyfrom_args, 1747 svn_boolean_t ignore_ancestry, 1748 const svn_delta_editor_t *update_editor, 1749 void *update_baton, 1750 apr_pool_t *result_pool, 1751 apr_pool_t *scratch_pool) 1752{ 1753 apr_pool_t *pool = result_pool; 1754 svn_ra_svn__session_baton_t *sess_baton = session->priv; 1755 svn_ra_svn_conn_t *conn = sess_baton->conn; 1756 svn_boolean_t recurse = DEPTH_TO_RECURSE(depth); 1757 1758 /* Callbacks may assume that all data is relative the sessions's URL. */ 1759 SVN_ERR(ensure_exact_server_parent(session, scratch_pool)); 1760 1761 /* Tell the server we want to start a switch. */ 1762 SVN_ERR(svn_ra_svn__write_cmd_switch(conn, pool, rev, target, recurse, 1763 switch_url, depth, 1764 send_copyfrom_args, ignore_ancestry)); 1765 SVN_ERR(handle_auth_request(sess_baton, pool)); 1766 1767 /* Fetch a reporter for the caller to drive. The reporter will drive 1768 * update_editor upon finish_report(). */ 1769 SVN_ERR(ra_svn_get_reporter(sess_baton, pool, update_editor, update_baton, 1770 target, depth, reporter, report_baton)); 1771 return SVN_NO_ERROR; 1772} 1773 1774static svn_error_t *ra_svn_status(svn_ra_session_t *session, 1775 const svn_ra_reporter3_t **reporter, 1776 void **report_baton, 1777 const char *target, svn_revnum_t rev, 1778 svn_depth_t depth, 1779 const svn_delta_editor_t *status_editor, 1780 void *status_baton, apr_pool_t *pool) 1781{ 1782 svn_ra_svn__session_baton_t *sess_baton = session->priv; 1783 svn_ra_svn_conn_t *conn = sess_baton->conn; 1784 svn_boolean_t recurse = DEPTH_TO_RECURSE(depth); 1785 1786 /* Callbacks may assume that all data is relative the sessions's URL. */ 1787 SVN_ERR(ensure_exact_server_parent(session, pool)); 1788 1789 /* Tell the server we want to start a status operation. */ 1790 SVN_ERR(svn_ra_svn__write_cmd_status(conn, pool, target, recurse, rev, 1791 depth)); 1792 SVN_ERR(handle_auth_request(sess_baton, pool)); 1793 1794 /* Fetch a reporter for the caller to drive. The reporter will drive 1795 * status_editor upon finish_report(). */ 1796 SVN_ERR(ra_svn_get_reporter(sess_baton, pool, status_editor, status_baton, 1797 target, depth, reporter, report_baton)); 1798 return SVN_NO_ERROR; 1799} 1800 1801static svn_error_t *ra_svn_diff(svn_ra_session_t *session, 1802 const svn_ra_reporter3_t **reporter, 1803 void **report_baton, 1804 svn_revnum_t rev, const char *target, 1805 svn_depth_t depth, 1806 svn_boolean_t ignore_ancestry, 1807 svn_boolean_t text_deltas, 1808 const char *versus_url, 1809 const svn_delta_editor_t *diff_editor, 1810 void *diff_baton, apr_pool_t *pool) 1811{ 1812 svn_ra_svn__session_baton_t *sess_baton = session->priv; 1813 svn_ra_svn_conn_t *conn = sess_baton->conn; 1814 svn_boolean_t recurse = DEPTH_TO_RECURSE(depth); 1815 1816 /* Callbacks may assume that all data is relative the sessions's URL. */ 1817 SVN_ERR(ensure_exact_server_parent(session, pool)); 1818 1819 /* Tell the server we want to start a diff. */ 1820 SVN_ERR(svn_ra_svn__write_cmd_diff(conn, pool, rev, target, recurse, 1821 ignore_ancestry, versus_url, 1822 text_deltas, depth)); 1823 SVN_ERR(handle_auth_request(sess_baton, pool)); 1824 1825 /* Fetch a reporter for the caller to drive. The reporter will drive 1826 * diff_editor upon finish_report(). */ 1827 SVN_ERR(ra_svn_get_reporter(sess_baton, pool, diff_editor, diff_baton, 1828 target, depth, reporter, report_baton)); 1829 return SVN_NO_ERROR; 1830} 1831 1832/* Return TRUE if ITEM matches the word "done". */ 1833static svn_boolean_t 1834is_done_response(const svn_ra_svn__item_t *item) 1835{ 1836 static const svn_string_t str_done = SVN__STATIC_STRING("done"); 1837 1838 return ( item->kind == SVN_RA_SVN_WORD 1839 && svn_string_compare(&item->u.word, &str_done)); 1840} 1841 1842 1843static svn_error_t * 1844perform_ra_svn_log(svn_error_t **outer_error, 1845 svn_ra_session_t *session, 1846 const apr_array_header_t *paths, 1847 svn_revnum_t start, svn_revnum_t end, 1848 int limit, 1849 svn_boolean_t discover_changed_paths, 1850 svn_boolean_t strict_node_history, 1851 svn_boolean_t include_merged_revisions, 1852 const apr_array_header_t *revprops, 1853 svn_log_entry_receiver_t receiver, 1854 void *receiver_baton, 1855 apr_pool_t *pool) 1856{ 1857 svn_ra_svn__session_baton_t *sess_baton = session->priv; 1858 svn_ra_svn_conn_t *conn = sess_baton->conn; 1859 apr_pool_t *iterpool; 1860 int i; 1861 int nest_level = 0; 1862 const char *path; 1863 char *name; 1864 svn_boolean_t want_custom_revprops; 1865 svn_boolean_t want_author = FALSE; 1866 svn_boolean_t want_message = FALSE; 1867 svn_boolean_t want_date = FALSE; 1868 int nreceived = 0; 1869 1870 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w((!", "log")); 1871 if (paths) 1872 { 1873 for (i = 0; i < paths->nelts; i++) 1874 { 1875 path = APR_ARRAY_IDX(paths, i, const char *); 1876 SVN_ERR(svn_ra_svn__write_cstring(conn, pool, path)); 1877 } 1878 } 1879 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!)(?r)(?r)bbnb!", start, end, 1880 discover_changed_paths, strict_node_history, 1881 (apr_uint64_t) limit, 1882 include_merged_revisions)); 1883 if (revprops) 1884 { 1885 want_custom_revprops = FALSE; 1886 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!w(!", "revprops")); 1887 for (i = 0; i < revprops->nelts; i++) 1888 { 1889 name = APR_ARRAY_IDX(revprops, i, char *); 1890 SVN_ERR(svn_ra_svn__write_cstring(conn, pool, name)); 1891 1892 if (strcmp(name, SVN_PROP_REVISION_AUTHOR) == 0) 1893 want_author = TRUE; 1894 else if (strcmp(name, SVN_PROP_REVISION_DATE) == 0) 1895 want_date = TRUE; 1896 else if (strcmp(name, SVN_PROP_REVISION_LOG) == 0) 1897 want_message = TRUE; 1898 else 1899 want_custom_revprops = TRUE; 1900 } 1901 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!))")); 1902 } 1903 else 1904 { 1905 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!w())", "all-revprops")); 1906 1907 want_author = TRUE; 1908 want_date = TRUE; 1909 want_message = TRUE; 1910 want_custom_revprops = TRUE; 1911 } 1912 1913 SVN_ERR(handle_auth_request(sess_baton, pool)); 1914 1915 /* Read the log messages. */ 1916 iterpool = svn_pool_create(pool); 1917 while (1) 1918 { 1919 apr_uint64_t has_children_param, invalid_revnum_param; 1920 apr_uint64_t has_subtractive_merge_param; 1921 svn_string_t *author, *date, *message; 1922 svn_ra_svn__list_t *cplist, *rplist; 1923 svn_log_entry_t *log_entry; 1924 svn_boolean_t has_children; 1925 svn_boolean_t subtractive_merge = FALSE; 1926 apr_uint64_t revprop_count; 1927 svn_ra_svn__item_t *item; 1928 apr_hash_t *cphash; 1929 svn_revnum_t rev; 1930 1931 svn_pool_clear(iterpool); 1932 SVN_ERR(svn_ra_svn__read_item(conn, iterpool, &item)); 1933 if (is_done_response(item)) 1934 break; 1935 if (item->kind != SVN_RA_SVN_LIST) 1936 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, 1937 _("Log entry not a list")); 1938 SVN_ERR(svn_ra_svn__parse_tuple(&item->u.list, 1939 "lr(?s)(?s)(?s)?BBnl?B", 1940 &cplist, &rev, &author, &date, 1941 &message, &has_children_param, 1942 &invalid_revnum_param, 1943 &revprop_count, &rplist, 1944 &has_subtractive_merge_param)); 1945 if (want_custom_revprops && rplist == NULL) 1946 { 1947 /* Caller asked for custom revprops, but server is too old. */ 1948 return svn_error_create(SVN_ERR_RA_NOT_IMPLEMENTED, NULL, 1949 _("Server does not support custom revprops" 1950 " via log")); 1951 } 1952 1953 if (has_children_param == SVN_RA_SVN_UNSPECIFIED_NUMBER) 1954 has_children = FALSE; 1955 else 1956 has_children = (svn_boolean_t) has_children_param; 1957 1958 if (has_subtractive_merge_param == SVN_RA_SVN_UNSPECIFIED_NUMBER) 1959 subtractive_merge = FALSE; 1960 else 1961 subtractive_merge = (svn_boolean_t) has_subtractive_merge_param; 1962 1963 /* Because the svn protocol won't let us send an invalid revnum, we have 1964 to recover that fact using the extra parameter. */ 1965 if (invalid_revnum_param != SVN_RA_SVN_UNSPECIFIED_NUMBER 1966 && invalid_revnum_param) 1967 rev = SVN_INVALID_REVNUM; 1968 1969 if (cplist->nelts > 0) 1970 { 1971 /* Interpret the changed-paths list. */ 1972 cphash = svn_hash__make(iterpool); 1973 for (i = 0; i < cplist->nelts; i++) 1974 { 1975 svn_log_changed_path2_t *change; 1976 svn_string_t *cpath; 1977 const char *copy_path, *action, *kind_str; 1978 apr_uint64_t text_mods, prop_mods; 1979 svn_revnum_t copy_rev; 1980 svn_ra_svn__item_t *elt = &SVN_RA_SVN__LIST_ITEM(cplist, i); 1981 1982 if (elt->kind != SVN_RA_SVN_LIST) 1983 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, 1984 _("Changed-path entry not a list")); 1985 SVN_ERR(svn_ra_svn__read_data_log_changed_entry(&elt->u.list, 1986 &cpath, &action, ©_path, 1987 ©_rev, &kind_str, 1988 &text_mods, &prop_mods)); 1989 1990 if (!svn_fspath__is_canonical(cpath->data)) 1991 { 1992 cpath->data = svn_fspath__canonicalize(cpath->data, iterpool); 1993 cpath->len = strlen(cpath->data); 1994 } 1995 if (copy_path && !svn_fspath__is_canonical(copy_path)) 1996 copy_path = svn_fspath__canonicalize(copy_path, iterpool); 1997 1998 change = svn_log_changed_path2_create(iterpool); 1999 change->action = *action; 2000 change->copyfrom_path = copy_path; 2001 change->copyfrom_rev = copy_rev; 2002 change->node_kind = svn_node_kind_from_word(kind_str); 2003 change->text_modified = optbool_to_tristate(text_mods); 2004 change->props_modified = optbool_to_tristate(prop_mods); 2005 apr_hash_set(cphash, cpath->data, cpath->len, change); 2006 } 2007 } 2008 else 2009 cphash = NULL; 2010 2011 /* Invoke RECEIVER 2012 - Except if the server sends more than a >= 1 limit top level items 2013 - Or when the callback reported a SVN_ERR_CEASE_INVOCATION 2014 in an earlier invocation. */ 2015 if (! ((limit > 0) && (nest_level == 0) && (++nreceived > limit)) 2016 && ! *outer_error) 2017 { 2018 svn_error_t *err; 2019 log_entry = svn_log_entry_create(iterpool); 2020 2021 log_entry->changed_paths = cphash; 2022 log_entry->changed_paths2 = cphash; 2023 log_entry->revision = rev; 2024 log_entry->has_children = has_children; 2025 log_entry->subtractive_merge = subtractive_merge; 2026 if (rplist) 2027 SVN_ERR(svn_ra_svn__parse_proplist(rplist, iterpool, 2028 &log_entry->revprops)); 2029 if (log_entry->revprops == NULL) 2030 log_entry->revprops = svn_hash__make(iterpool); 2031 2032 if (author && want_author) 2033 svn_hash_sets(log_entry->revprops, 2034 SVN_PROP_REVISION_AUTHOR, author); 2035 if (date && want_date) 2036 svn_hash_sets(log_entry->revprops, 2037 SVN_PROP_REVISION_DATE, date); 2038 if (message && want_message) 2039 svn_hash_sets(log_entry->revprops, 2040 SVN_PROP_REVISION_LOG, message); 2041 2042 err = receiver(receiver_baton, log_entry, iterpool); 2043 if (svn_error_find_cause(err, SVN_ERR_CEASE_INVOCATION)) 2044 { 2045 *outer_error = svn_error_trace( 2046 svn_error_compose_create(*outer_error, err)); 2047 } 2048 else 2049 SVN_ERR(err); 2050 2051 if (log_entry->has_children) 2052 { 2053 nest_level++; 2054 } 2055 if (! SVN_IS_VALID_REVNUM(log_entry->revision)) 2056 { 2057 SVN_ERR_ASSERT(nest_level); 2058 nest_level--; 2059 } 2060 } 2061 } 2062 svn_pool_destroy(iterpool); 2063 2064 /* Read the response. */ 2065 return svn_error_trace(svn_ra_svn__read_cmd_response(conn, pool, "")); 2066} 2067 2068static svn_error_t * 2069ra_svn_log(svn_ra_session_t *session, 2070 const apr_array_header_t *paths, 2071 svn_revnum_t start, svn_revnum_t end, 2072 int limit, 2073 svn_boolean_t discover_changed_paths, 2074 svn_boolean_t strict_node_history, 2075 svn_boolean_t include_merged_revisions, 2076 const apr_array_header_t *revprops, 2077 svn_log_entry_receiver_t receiver, 2078 void *receiver_baton, apr_pool_t *pool) 2079{ 2080 svn_error_t *outer_error = NULL; 2081 svn_error_t *err; 2082 2083 /* If we don't specify paths, the session's URL is implied. 2084 2085 Because the paths passed to callbacks are always relative the repos 2086 root, there is no need *always* sync the parent URLs despite invoking 2087 user-provided callbacks. */ 2088 if (paths) 2089 paths = reparent_path_array(session, paths, pool); 2090 else 2091 SVN_ERR(ensure_exact_server_parent(session, pool)); 2092 2093 err = svn_error_trace(perform_ra_svn_log(&outer_error, 2094 session, paths, 2095 start, end, 2096 limit, 2097 discover_changed_paths, 2098 strict_node_history, 2099 include_merged_revisions, 2100 revprops, 2101 receiver, receiver_baton, 2102 pool)); 2103 return svn_error_trace( 2104 svn_error_compose_create(outer_error, 2105 err)); 2106} 2107 2108 2109 2110static svn_error_t *ra_svn_check_path(svn_ra_session_t *session, 2111 const char *path, svn_revnum_t rev, 2112 svn_node_kind_t *kind, apr_pool_t *pool) 2113{ 2114 svn_ra_svn__session_baton_t *sess_baton = session->priv; 2115 svn_ra_svn_conn_t *conn = sess_baton->conn; 2116 const char *kind_word; 2117 2118 path = reparent_path(session, path, pool); 2119 SVN_ERR(svn_ra_svn__write_cmd_check_path(conn, pool, path, rev)); 2120 SVN_ERR(handle_auth_request(sess_baton, pool)); 2121 SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "w", &kind_word)); 2122 *kind = svn_node_kind_from_word(kind_word); 2123 return SVN_NO_ERROR; 2124} 2125 2126 2127/* If ERR is a command not supported error, wrap it in a 2128 SVN_ERR_RA_NOT_IMPLEMENTED with error message MSG. Else, return err. */ 2129static svn_error_t *handle_unsupported_cmd(svn_error_t *err, 2130 const char *msg) 2131{ 2132 if (err && err->apr_err == SVN_ERR_RA_SVN_UNKNOWN_CMD) 2133 return svn_error_create(SVN_ERR_RA_NOT_IMPLEMENTED, err, 2134 _(msg)); 2135 return err; 2136} 2137 2138 2139static svn_error_t *ra_svn_stat(svn_ra_session_t *session, 2140 const char *path, svn_revnum_t rev, 2141 svn_dirent_t **dirent, apr_pool_t *pool) 2142{ 2143 svn_ra_svn__session_baton_t *sess_baton = session->priv; 2144 svn_ra_svn_conn_t *conn = sess_baton->conn; 2145 svn_ra_svn__list_t *list = NULL; 2146 svn_dirent_t *the_dirent; 2147 2148 path = reparent_path(session, path, pool); 2149 SVN_ERR(svn_ra_svn__write_cmd_stat(conn, pool, path, rev)); 2150 SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess_baton, pool), 2151 N_("'stat' not implemented"))); 2152 SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "(?l)", &list)); 2153 2154 if (! list) 2155 { 2156 *dirent = NULL; 2157 } 2158 else 2159 { 2160 const char *kind, *cdate, *cauthor; 2161 svn_boolean_t has_props; 2162 svn_revnum_t crev; 2163 apr_uint64_t size; 2164 2165 SVN_ERR(svn_ra_svn__parse_tuple(list, "wnbr(?c)(?c)", 2166 &kind, &size, &has_props, 2167 &crev, &cdate, &cauthor)); 2168 2169 the_dirent = svn_dirent_create(pool); 2170 the_dirent->kind = svn_node_kind_from_word(kind); 2171 the_dirent->size = size;/* FIXME: svn_filesize_t */ 2172 the_dirent->has_props = has_props; 2173 the_dirent->created_rev = crev; 2174 SVN_ERR(svn_time_from_cstring(&the_dirent->time, cdate, pool)); 2175 the_dirent->last_author = cauthor; 2176 2177 *dirent = the_dirent; 2178 } 2179 2180 return SVN_NO_ERROR; 2181} 2182 2183 2184static svn_error_t *ra_svn_get_locations(svn_ra_session_t *session, 2185 apr_hash_t **locations, 2186 const char *path, 2187 svn_revnum_t peg_revision, 2188 const apr_array_header_t *location_revisions, 2189 apr_pool_t *pool) 2190{ 2191 svn_ra_svn__session_baton_t *sess_baton = session->priv; 2192 svn_ra_svn_conn_t *conn = sess_baton->conn; 2193 svn_revnum_t revision; 2194 svn_boolean_t is_done; 2195 apr_pool_t *iterpool; 2196 int i; 2197 2198 path = reparent_path(session, path, pool); 2199 2200 /* Transmit the parameters. */ 2201 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w(cr(!", 2202 "get-locations", path, peg_revision)); 2203 for (i = 0; i < location_revisions->nelts; i++) 2204 { 2205 revision = APR_ARRAY_IDX(location_revisions, i, svn_revnum_t); 2206 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!r!", revision)); 2207 } 2208 2209 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!))")); 2210 2211 /* Servers before 1.1 don't support this command. Check for this here. */ 2212 SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess_baton, pool), 2213 N_("'get-locations' not implemented"))); 2214 2215 /* Read the hash items. */ 2216 is_done = FALSE; 2217 *locations = apr_hash_make(pool); 2218 iterpool = svn_pool_create(pool); 2219 while (!is_done) 2220 { 2221 svn_ra_svn__item_t *item; 2222 const char *ret_path; 2223 2224 svn_pool_clear(iterpool); 2225 SVN_ERR(svn_ra_svn__read_item(conn, iterpool, &item)); 2226 if (is_done_response(item)) 2227 is_done = 1; 2228 else if (item->kind != SVN_RA_SVN_LIST) 2229 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, 2230 _("Location entry not a list")); 2231 else 2232 { 2233 SVN_ERR(svn_ra_svn__parse_tuple(&item->u.list, "rc", 2234 &revision, &ret_path)); 2235 2236 /* This also makes RET_PATH live in POOL rather than ITERPOOL. */ 2237 ret_path = svn_fspath__canonicalize(ret_path, pool); 2238 apr_hash_set(*locations, apr_pmemdup(pool, &revision, 2239 sizeof(revision)), 2240 sizeof(revision), ret_path); 2241 } 2242 } 2243 2244 svn_pool_destroy(iterpool); 2245 2246 /* Read the response. This is so the server would have a chance to 2247 * report an error. */ 2248 return svn_error_trace(svn_ra_svn__read_cmd_response(conn, pool, "")); 2249} 2250 2251static svn_error_t * 2252perform_get_location_segments(svn_error_t **outer_error, 2253 svn_ra_session_t *session, 2254 const char *path, 2255 svn_revnum_t peg_revision, 2256 svn_revnum_t start_rev, 2257 svn_revnum_t end_rev, 2258 svn_location_segment_receiver_t receiver, 2259 void *receiver_baton, 2260 apr_pool_t *pool) 2261{ 2262 svn_ra_svn__session_baton_t *sess_baton = session->priv; 2263 svn_ra_svn_conn_t *conn = sess_baton->conn; 2264 svn_boolean_t is_done; 2265 apr_pool_t *iterpool = svn_pool_create(pool); 2266 2267 /* Transmit the parameters. */ 2268 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w(c(?r)(?r)(?r))", 2269 "get-location-segments", 2270 path, peg_revision, start_rev, end_rev)); 2271 2272 /* Servers before 1.5 don't support this command. Check for this here. */ 2273 SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess_baton, pool), 2274 N_("'get-location-segments'" 2275 " not implemented"))); 2276 2277 /* Parse the response. */ 2278 is_done = FALSE; 2279 while (!is_done) 2280 { 2281 svn_revnum_t range_start, range_end; 2282 svn_ra_svn__item_t *item; 2283 const char *ret_path; 2284 2285 svn_pool_clear(iterpool); 2286 SVN_ERR(svn_ra_svn__read_item(conn, iterpool, &item)); 2287 if (is_done_response(item)) 2288 is_done = 1; 2289 else if (item->kind != SVN_RA_SVN_LIST) 2290 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, 2291 _("Location segment entry not a list")); 2292 else 2293 { 2294 svn_location_segment_t *segment = apr_pcalloc(iterpool, 2295 sizeof(*segment)); 2296 SVN_ERR(svn_ra_svn__parse_tuple(&item->u.list, "rr(?c)", 2297 &range_start, &range_end, &ret_path)); 2298 if (! (SVN_IS_VALID_REVNUM(range_start) 2299 && SVN_IS_VALID_REVNUM(range_end))) 2300 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, 2301 _("Expected valid revision range")); 2302 if (ret_path) 2303 ret_path = svn_relpath_canonicalize(ret_path, iterpool); 2304 segment->path = ret_path; 2305 segment->range_start = range_start; 2306 segment->range_end = range_end; 2307 2308 if (!*outer_error) 2309 { 2310 svn_error_t *err = svn_error_trace(receiver(segment, receiver_baton, 2311 iterpool)); 2312 2313 if (svn_error_find_cause(err, SVN_ERR_CEASE_INVOCATION)) 2314 *outer_error = err; 2315 else 2316 SVN_ERR(err); 2317 } 2318 } 2319 } 2320 svn_pool_destroy(iterpool); 2321 2322 /* Read the response. This is so the server would have a chance to 2323 * report an error. */ 2324 SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "")); 2325 2326 return SVN_NO_ERROR; 2327} 2328 2329static svn_error_t * 2330ra_svn_get_location_segments(svn_ra_session_t *session, 2331 const char *path, 2332 svn_revnum_t peg_revision, 2333 svn_revnum_t start_rev, 2334 svn_revnum_t end_rev, 2335 svn_location_segment_receiver_t receiver, 2336 void *receiver_baton, 2337 apr_pool_t *pool) 2338{ 2339 svn_error_t *outer_err = SVN_NO_ERROR; 2340 svn_error_t *err; 2341 2342 path = reparent_path(session, path, pool); 2343 err = svn_error_trace( 2344 perform_get_location_segments(&outer_err, session, path, 2345 peg_revision, start_rev, end_rev, 2346 receiver, receiver_baton, pool)); 2347 return svn_error_compose_create(outer_err, err); 2348} 2349 2350static svn_error_t *ra_svn_get_file_revs(svn_ra_session_t *session, 2351 const char *path, 2352 svn_revnum_t start, svn_revnum_t end, 2353 svn_boolean_t include_merged_revisions, 2354 svn_file_rev_handler_t handler, 2355 void *handler_baton, apr_pool_t *pool) 2356{ 2357 svn_ra_svn__session_baton_t *sess_baton = session->priv; 2358 apr_pool_t *rev_pool, *chunk_pool; 2359 svn_boolean_t has_txdelta; 2360 svn_boolean_t had_revision = FALSE; 2361 2362 /* One sub-pool for each revision and one for each txdelta chunk. 2363 Note that the rev_pool must live during the following txdelta. */ 2364 rev_pool = svn_pool_create(pool); 2365 chunk_pool = svn_pool_create(pool); 2366 2367 path = reparent_path(session, path, pool); 2368 SVN_ERR(svn_ra_svn__write_cmd_get_file_revs(sess_baton->conn, pool, 2369 path, start, end, 2370 include_merged_revisions)); 2371 2372 /* Servers before 1.1 don't support this command. Check for this here. */ 2373 SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess_baton, pool), 2374 N_("'get-file-revs' not implemented"))); 2375 2376 while (1) 2377 { 2378 svn_ra_svn__list_t *rev_proplist, *proplist; 2379 apr_uint64_t merged_rev_param; 2380 apr_array_header_t *props; 2381 svn_ra_svn__item_t *item; 2382 apr_hash_t *rev_props; 2383 svn_revnum_t rev; 2384 const char *p; 2385 svn_boolean_t merged_rev; 2386 svn_txdelta_window_handler_t d_handler; 2387 void *d_baton; 2388 2389 svn_pool_clear(rev_pool); 2390 svn_pool_clear(chunk_pool); 2391 SVN_ERR(svn_ra_svn__read_item(sess_baton->conn, rev_pool, &item)); 2392 if (is_done_response(item)) 2393 break; 2394 /* Either we've got a correct revision or we will error out below. */ 2395 had_revision = TRUE; 2396 if (item->kind != SVN_RA_SVN_LIST) 2397 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, 2398 _("Revision entry not a list")); 2399 2400 SVN_ERR(svn_ra_svn__parse_tuple(&item->u.list, 2401 "crll?B", &p, &rev, &rev_proplist, 2402 &proplist, &merged_rev_param)); 2403 p = svn_fspath__canonicalize(p, rev_pool); 2404 SVN_ERR(svn_ra_svn__parse_proplist(rev_proplist, rev_pool, &rev_props)); 2405 SVN_ERR(parse_prop_diffs(proplist, rev_pool, &props)); 2406 if (merged_rev_param == SVN_RA_SVN_UNSPECIFIED_NUMBER) 2407 merged_rev = FALSE; 2408 else 2409 merged_rev = (svn_boolean_t) merged_rev_param; 2410 2411 /* Get the first delta chunk so we know if there is a delta. */ 2412 SVN_ERR(svn_ra_svn__read_item(sess_baton->conn, chunk_pool, &item)); 2413 if (item->kind != SVN_RA_SVN_STRING) 2414 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, 2415 _("Text delta chunk not a string")); 2416 has_txdelta = item->u.string.len > 0; 2417 2418 SVN_ERR(handler(handler_baton, p, rev, rev_props, merged_rev, 2419 has_txdelta ? &d_handler : NULL, &d_baton, 2420 props, rev_pool)); 2421 2422 /* Process the text delta if any. */ 2423 if (has_txdelta) 2424 { 2425 svn_stream_t *stream; 2426 2427 if (d_handler && d_handler != svn_delta_noop_window_handler) 2428 stream = svn_txdelta_parse_svndiff(d_handler, d_baton, TRUE, 2429 rev_pool); 2430 else 2431 stream = NULL; 2432 while (item->u.string.len > 0) 2433 { 2434 apr_size_t size; 2435 2436 size = item->u.string.len; 2437 if (stream) 2438 SVN_ERR(svn_stream_write(stream, item->u.string.data, &size)); 2439 svn_pool_clear(chunk_pool); 2440 2441 SVN_ERR(svn_ra_svn__read_item(sess_baton->conn, chunk_pool, 2442 &item)); 2443 if (item->kind != SVN_RA_SVN_STRING) 2444 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, 2445 _("Text delta chunk not a string")); 2446 } 2447 if (stream) 2448 SVN_ERR(svn_stream_close(stream)); 2449 } 2450 } 2451 2452 SVN_ERR(svn_ra_svn__read_cmd_response(sess_baton->conn, pool, "")); 2453 2454 /* Return error if we didn't get any revisions. */ 2455 if (!had_revision) 2456 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, 2457 _("The get-file-revs command didn't return " 2458 "any revisions")); 2459 2460 svn_pool_destroy(chunk_pool); 2461 svn_pool_destroy(rev_pool); 2462 2463 return SVN_NO_ERROR; 2464} 2465 2466/* For each path in PATH_REVS, send a 'lock' command to the server. 2467 Used with 1.2.x series servers which support locking, but of only 2468 one path at a time. ra_svn_lock(), which supports 'lock-many' 2469 is now the default. See svn_ra_lock() docstring for interface details. */ 2470static svn_error_t *ra_svn_lock_compat(svn_ra_session_t *session, 2471 apr_hash_t *path_revs, 2472 const char *comment, 2473 svn_boolean_t steal_lock, 2474 svn_ra_lock_callback_t lock_func, 2475 void *lock_baton, 2476 apr_pool_t *pool) 2477{ 2478 svn_ra_svn__session_baton_t *sess = session->priv; 2479 svn_ra_svn_conn_t* conn = sess->conn; 2480 svn_ra_svn__list_t *list; 2481 apr_hash_index_t *hi; 2482 apr_pool_t *iterpool = svn_pool_create(pool); 2483 2484 for (hi = apr_hash_first(pool, path_revs); hi; hi = apr_hash_next(hi)) 2485 { 2486 svn_lock_t *lock; 2487 const void *key; 2488 const char *path; 2489 void *val; 2490 svn_revnum_t *revnum; 2491 svn_error_t *err, *callback_err = NULL; 2492 2493 svn_pool_clear(iterpool); 2494 2495 apr_hash_this(hi, &key, NULL, &val); 2496 path = key; 2497 revnum = val; 2498 2499 SVN_ERR(svn_ra_svn__write_cmd_lock(conn, iterpool, path, comment, 2500 steal_lock, *revnum)); 2501 2502 /* Servers before 1.2 doesn't support locking. Check this here. */ 2503 SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess, pool), 2504 N_("Server doesn't support " 2505 "the lock command"))); 2506 2507 err = svn_ra_svn__read_cmd_response(conn, iterpool, "l", &list); 2508 2509 if (!err) 2510 SVN_ERR(parse_lock(list, iterpool, &lock)); 2511 2512 if (err && !SVN_ERR_IS_LOCK_ERROR(err)) 2513 return err; 2514 2515 if (lock_func) 2516 callback_err = lock_func(lock_baton, path, TRUE, err ? NULL : lock, 2517 err, iterpool); 2518 2519 svn_error_clear(err); 2520 2521 if (callback_err) 2522 return callback_err; 2523 } 2524 2525 svn_pool_destroy(iterpool); 2526 2527 return SVN_NO_ERROR; 2528} 2529 2530/* For each path in PATH_TOKENS, send an 'unlock' command to the server. 2531 Used with 1.2.x series servers which support unlocking, but of only 2532 one path at a time. ra_svn_unlock(), which supports 'unlock-many' is 2533 now the default. See svn_ra_unlock() docstring for interface details. */ 2534static svn_error_t *ra_svn_unlock_compat(svn_ra_session_t *session, 2535 apr_hash_t *path_tokens, 2536 svn_boolean_t break_lock, 2537 svn_ra_lock_callback_t lock_func, 2538 void *lock_baton, 2539 apr_pool_t *pool) 2540{ 2541 svn_ra_svn__session_baton_t *sess = session->priv; 2542 svn_ra_svn_conn_t* conn = sess->conn; 2543 apr_hash_index_t *hi; 2544 apr_pool_t *iterpool = svn_pool_create(pool); 2545 2546 for (hi = apr_hash_first(pool, path_tokens); hi; hi = apr_hash_next(hi)) 2547 { 2548 const void *key; 2549 const char *path; 2550 void *val; 2551 const svn_string_t *token; 2552 svn_error_t *err, *callback_err = NULL; 2553 2554 svn_pool_clear(iterpool); 2555 2556 apr_hash_this(hi, &key, NULL, &val); 2557 path = key; 2558 if (strcmp(val, "") != 0) 2559 token = svn_string_create(val, iterpool); 2560 else 2561 token = NULL; 2562 2563 SVN_ERR(svn_ra_svn__write_cmd_unlock(conn, iterpool, path, token, 2564 break_lock)); 2565 2566 /* Servers before 1.2 don't support locking. Check this here. */ 2567 SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess, iterpool), 2568 N_("Server doesn't support the unlock " 2569 "command"))); 2570 2571 err = svn_ra_svn__read_cmd_response(conn, iterpool, ""); 2572 2573 if (err && !SVN_ERR_IS_UNLOCK_ERROR(err)) 2574 return err; 2575 2576 if (lock_func) 2577 callback_err = lock_func(lock_baton, path, FALSE, NULL, err, pool); 2578 2579 svn_error_clear(err); 2580 2581 if (callback_err) 2582 return callback_err; 2583 } 2584 2585 svn_pool_destroy(iterpool); 2586 2587 return SVN_NO_ERROR; 2588} 2589 2590/* Tell the server to lock all paths in PATH_REVS. 2591 See svn_ra_lock() for interface details. */ 2592static svn_error_t *ra_svn_lock(svn_ra_session_t *session, 2593 apr_hash_t *path_revs, 2594 const char *comment, 2595 svn_boolean_t steal_lock, 2596 svn_ra_lock_callback_t lock_func, 2597 void *lock_baton, 2598 apr_pool_t *pool) 2599{ 2600 svn_ra_svn__session_baton_t *sess = session->priv; 2601 svn_ra_svn_conn_t *conn = sess->conn; 2602 apr_hash_index_t *hi; 2603 svn_error_t *err; 2604 apr_pool_t *iterpool = svn_pool_create(pool); 2605 2606 path_revs = reparent_path_hash(session, path_revs, pool, pool); 2607 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w((?c)b(!", "lock-many", 2608 comment, steal_lock)); 2609 2610 for (hi = apr_hash_first(pool, path_revs); hi; hi = apr_hash_next(hi)) 2611 { 2612 const void *key; 2613 const char *path; 2614 void *val; 2615 svn_revnum_t *revnum; 2616 2617 svn_pool_clear(iterpool); 2618 apr_hash_this(hi, &key, NULL, &val); 2619 path = key; 2620 revnum = val; 2621 2622 SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "c(?r)", path, *revnum)); 2623 } 2624 2625 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!))")); 2626 2627 err = handle_auth_request(sess, pool); 2628 2629 /* Pre-1.3 servers don't support 'lock-many'. If that fails, fall back 2630 * to 'lock'. */ 2631 if (err && err->apr_err == SVN_ERR_RA_SVN_UNKNOWN_CMD) 2632 { 2633 svn_error_clear(err); 2634 return ra_svn_lock_compat(session, path_revs, comment, steal_lock, 2635 lock_func, lock_baton, pool); 2636 } 2637 2638 if (err) 2639 return err; 2640 2641 /* Loop over responses to get lock information. */ 2642 for (hi = apr_hash_first(pool, path_revs); hi; hi = apr_hash_next(hi)) 2643 { 2644 svn_ra_svn__item_t *elt; 2645 const void *key; 2646 const char *path; 2647 svn_error_t *callback_err; 2648 const char *status; 2649 svn_lock_t *lock; 2650 svn_ra_svn__list_t *list; 2651 2652 apr_hash_this(hi, &key, NULL, NULL); 2653 path = key; 2654 2655 svn_pool_clear(iterpool); 2656 SVN_ERR(svn_ra_svn__read_item(conn, iterpool, &elt)); 2657 2658 /* The server might have encountered some sort of fatal error in 2659 the middle of the request list. If this happens, it will 2660 transmit "done" to end the lock-info early, and then the 2661 overall command response will talk about the fatal error. */ 2662 if (is_done_response(elt)) 2663 break; 2664 2665 if (elt->kind != SVN_RA_SVN_LIST) 2666 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, 2667 _("Lock response not a list")); 2668 2669 SVN_ERR(svn_ra_svn__parse_tuple(&elt->u.list, "wl", &status, &list)); 2670 2671 if (strcmp(status, "failure") == 0) 2672 err = svn_ra_svn__handle_failure_status(list); 2673 else if (strcmp(status, "success") == 0) 2674 { 2675 SVN_ERR(parse_lock(list, iterpool, &lock)); 2676 err = NULL; 2677 } 2678 else 2679 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, 2680 _("Unknown status for lock command")); 2681 2682 if (lock_func) 2683 callback_err = lock_func(lock_baton, path, TRUE, 2684 err ? NULL : lock, 2685 err, iterpool); 2686 else 2687 callback_err = SVN_NO_ERROR; 2688 2689 svn_error_clear(err); 2690 2691 if (callback_err) 2692 return callback_err; 2693 } 2694 2695 /* If we didn't break early above, and the whole hash was traversed, 2696 read the final "done" from the server. */ 2697 if (!hi) 2698 { 2699 svn_ra_svn__item_t *elt; 2700 2701 SVN_ERR(svn_ra_svn__read_item(conn, pool, &elt)); 2702 if (!is_done_response(elt)) 2703 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, 2704 _("Didn't receive end marker for lock " 2705 "responses")); 2706 } 2707 2708 SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "")); 2709 2710 svn_pool_destroy(iterpool); 2711 2712 return SVN_NO_ERROR; 2713} 2714 2715/* Tell the server to unlock all paths in PATH_TOKENS. 2716 See svn_ra_unlock() for interface details. */ 2717static svn_error_t *ra_svn_unlock(svn_ra_session_t *session, 2718 apr_hash_t *path_tokens, 2719 svn_boolean_t break_lock, 2720 svn_ra_lock_callback_t lock_func, 2721 void *lock_baton, 2722 apr_pool_t *pool) 2723{ 2724 svn_ra_svn__session_baton_t *sess = session->priv; 2725 svn_ra_svn_conn_t *conn = sess->conn; 2726 apr_hash_index_t *hi; 2727 apr_pool_t *iterpool = svn_pool_create(pool); 2728 svn_error_t *err; 2729 const char *path; 2730 2731 path_tokens = reparent_path_hash(session, path_tokens, pool, pool); 2732 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w(b(!", "unlock-many", 2733 break_lock)); 2734 2735 for (hi = apr_hash_first(pool, path_tokens); hi; hi = apr_hash_next(hi)) 2736 { 2737 void *val; 2738 const void *key; 2739 const char *token; 2740 2741 svn_pool_clear(iterpool); 2742 apr_hash_this(hi, &key, NULL, &val); 2743 path = key; 2744 2745 if (strcmp(val, "") != 0) 2746 token = val; 2747 else 2748 token = NULL; 2749 2750 SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "c(?c)", path, token)); 2751 } 2752 2753 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!))")); 2754 2755 err = handle_auth_request(sess, pool); 2756 2757 /* Pre-1.3 servers don't support 'unlock-many'. If unknown, fall back 2758 * to 'unlock'. 2759 */ 2760 if (err && err->apr_err == SVN_ERR_RA_SVN_UNKNOWN_CMD) 2761 { 2762 svn_error_clear(err); 2763 return ra_svn_unlock_compat(session, path_tokens, break_lock, lock_func, 2764 lock_baton, pool); 2765 } 2766 2767 if (err) 2768 return err; 2769 2770 /* Loop over responses to unlock files. */ 2771 for (hi = apr_hash_first(pool, path_tokens); hi; hi = apr_hash_next(hi)) 2772 { 2773 svn_ra_svn__item_t *elt; 2774 const void *key; 2775 svn_error_t *callback_err; 2776 const char *status; 2777 svn_ra_svn__list_t *list; 2778 2779 svn_pool_clear(iterpool); 2780 2781 SVN_ERR(svn_ra_svn__read_item(conn, iterpool, &elt)); 2782 2783 /* The server might have encountered some sort of fatal error in 2784 the middle of the request list. If this happens, it will 2785 transmit "done" to end the lock-info early, and then the 2786 overall command response will talk about the fatal error. */ 2787 if (is_done_response(elt)) 2788 break; 2789 2790 apr_hash_this(hi, &key, NULL, NULL); 2791 path = key; 2792 2793 if (elt->kind != SVN_RA_SVN_LIST) 2794 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, 2795 _("Unlock response not a list")); 2796 2797 SVN_ERR(svn_ra_svn__parse_tuple(&elt->u.list, "wl", &status, &list)); 2798 2799 if (strcmp(status, "failure") == 0) 2800 err = svn_ra_svn__handle_failure_status(list); 2801 else if (strcmp(status, "success") == 0) 2802 { 2803 SVN_ERR(svn_ra_svn__parse_tuple(list, "c", &path)); 2804 err = SVN_NO_ERROR; 2805 } 2806 else 2807 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, 2808 _("Unknown status for unlock command")); 2809 2810 if (lock_func) 2811 callback_err = lock_func(lock_baton, path, FALSE, NULL, err, 2812 iterpool); 2813 else 2814 callback_err = SVN_NO_ERROR; 2815 2816 svn_error_clear(err); 2817 2818 if (callback_err) 2819 return callback_err; 2820 } 2821 2822 /* If we didn't break early above, and the whole hash was traversed, 2823 read the final "done" from the server. */ 2824 if (!hi) 2825 { 2826 svn_ra_svn__item_t *elt; 2827 2828 SVN_ERR(svn_ra_svn__read_item(conn, pool, &elt)); 2829 if (! is_done_response(elt)) 2830 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, 2831 _("Didn't receive end marker for unlock " 2832 "responses")); 2833 } 2834 2835 SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "")); 2836 2837 svn_pool_destroy(iterpool); 2838 2839 return SVN_NO_ERROR; 2840} 2841 2842static svn_error_t *ra_svn_get_lock(svn_ra_session_t *session, 2843 svn_lock_t **lock, 2844 const char *path, 2845 apr_pool_t *pool) 2846{ 2847 svn_ra_svn__session_baton_t *sess = session->priv; 2848 svn_ra_svn_conn_t* conn = sess->conn; 2849 svn_ra_svn__list_t *list; 2850 2851 path = reparent_path(session, path, pool); 2852 SVN_ERR(svn_ra_svn__write_cmd_get_lock(conn, pool, path)); 2853 2854 /* Servers before 1.2 doesn't support locking. Check this here. */ 2855 SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess, pool), 2856 N_("Server doesn't support the get-lock " 2857 "command"))); 2858 2859 SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "(?l)", &list)); 2860 if (list) 2861 SVN_ERR(parse_lock(list, pool, lock)); 2862 else 2863 *lock = NULL; 2864 2865 return SVN_NO_ERROR; 2866} 2867 2868/* Copied from svn_ra_get_path_relative_to_root() and de-vtable-ized 2869 to prevent a dependency cycle. */ 2870static svn_error_t *path_relative_to_root(svn_ra_session_t *session, 2871 const char **rel_path, 2872 const char *url, 2873 apr_pool_t *pool) 2874{ 2875 const char *root_url; 2876 2877 SVN_ERR(ra_svn_get_repos_root(session, &root_url, pool)); 2878 *rel_path = svn_uri_skip_ancestor(root_url, url, pool); 2879 if (! *rel_path) 2880 return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL, 2881 _("'%s' isn't a child of repository root " 2882 "URL '%s'"), 2883 url, root_url); 2884 return SVN_NO_ERROR; 2885} 2886 2887static svn_error_t *ra_svn_get_locks(svn_ra_session_t *session, 2888 apr_hash_t **locks, 2889 const char *path, 2890 svn_depth_t depth, 2891 apr_pool_t *pool) 2892{ 2893 svn_ra_svn__session_baton_t *sess = session->priv; 2894 svn_ra_svn_conn_t* conn = sess->conn; 2895 svn_ra_svn__list_t *list; 2896 const char *full_url, *abs_path; 2897 int i; 2898 2899 /* Figure out the repository abspath from PATH. */ 2900 full_url = svn_path_url_add_component2(sess->parent->client_url->data, 2901 path, pool); 2902 SVN_ERR(path_relative_to_root(session, &abs_path, full_url, pool)); 2903 abs_path = svn_fspath__canonicalize(abs_path, pool); 2904 2905 SVN_ERR(svn_ra_svn__write_cmd_get_locks(conn, pool, path, depth)); 2906 2907 /* Servers before 1.2 doesn't support locking. Check this here. */ 2908 SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess, pool), 2909 N_("Server doesn't support the get-lock " 2910 "command"))); 2911 2912 SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "l", &list)); 2913 2914 *locks = apr_hash_make(pool); 2915 2916 for (i = 0; i < list->nelts; ++i) 2917 { 2918 svn_lock_t *lock; 2919 svn_ra_svn__item_t *elt = &SVN_RA_SVN__LIST_ITEM(list, i); 2920 2921 if (elt->kind != SVN_RA_SVN_LIST) 2922 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, 2923 _("Lock element not a list")); 2924 SVN_ERR(parse_lock(&elt->u.list, pool, &lock)); 2925 2926 /* Filter out unwanted paths. Since Subversion only allows 2927 locks on files, we can treat depth=immediates the same as 2928 depth=files for filtering purposes. Meaning, we'll keep 2929 this lock if: 2930 2931 a) its path is the very path we queried, or 2932 b) we've asked for a fully recursive answer, or 2933 c) we've asked for depth=files or depth=immediates, and this 2934 lock is on an immediate child of our query path. 2935 */ 2936 if ((strcmp(abs_path, lock->path) == 0) || (depth == svn_depth_infinity)) 2937 { 2938 svn_hash_sets(*locks, lock->path, lock); 2939 } 2940 else if ((depth == svn_depth_files) || (depth == svn_depth_immediates)) 2941 { 2942 const char *relpath = svn_fspath__skip_ancestor(abs_path, lock->path); 2943 if (relpath && (svn_path_component_count(relpath) == 1)) 2944 svn_hash_sets(*locks, lock->path, lock); 2945 } 2946 } 2947 2948 return SVN_NO_ERROR; 2949} 2950 2951 2952static svn_error_t *ra_svn_replay(svn_ra_session_t *session, 2953 svn_revnum_t revision, 2954 svn_revnum_t low_water_mark, 2955 svn_boolean_t send_deltas, 2956 const svn_delta_editor_t *editor, 2957 void *edit_baton, 2958 apr_pool_t *pool) 2959{ 2960 svn_ra_svn__session_baton_t *sess = session->priv; 2961 2962 /* Complex EDITOR callbacks may rely on client and server parent path 2963 being in sync. */ 2964 SVN_ERR(ensure_exact_server_parent(session, pool)); 2965 SVN_ERR(svn_ra_svn__write_cmd_replay(sess->conn, pool, revision, 2966 low_water_mark, send_deltas)); 2967 2968 SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess, pool), 2969 N_("Server doesn't support the replay " 2970 "command"))); 2971 2972 SVN_ERR(svn_ra_svn_drive_editor2(sess->conn, pool, editor, edit_baton, 2973 NULL, TRUE)); 2974 2975 return svn_error_trace(svn_ra_svn__read_cmd_response(sess->conn, pool, "")); 2976} 2977 2978 2979static svn_error_t * 2980ra_svn_replay_range(svn_ra_session_t *session, 2981 svn_revnum_t start_revision, 2982 svn_revnum_t end_revision, 2983 svn_revnum_t low_water_mark, 2984 svn_boolean_t send_deltas, 2985 svn_ra_replay_revstart_callback_t revstart_func, 2986 svn_ra_replay_revfinish_callback_t revfinish_func, 2987 void *replay_baton, 2988 apr_pool_t *pool) 2989{ 2990 svn_ra_svn__session_baton_t *sess = session->priv; 2991 apr_pool_t *iterpool; 2992 svn_revnum_t rev; 2993 svn_boolean_t drive_aborted = FALSE; 2994 2995 /* Complex EDITOR callbacks may rely on client and server parent path 2996 being in sync. */ 2997 SVN_ERR(ensure_exact_server_parent(session, pool)); 2998 SVN_ERR(svn_ra_svn__write_cmd_replay_range(sess->conn, pool, 2999 start_revision, end_revision, 3000 low_water_mark, send_deltas)); 3001 3002 SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess, pool), 3003 N_("Server doesn't support the " 3004 "replay-range command"))); 3005 3006 iterpool = svn_pool_create(pool); 3007 for (rev = start_revision; rev <= end_revision; rev++) 3008 { 3009 const svn_delta_editor_t *editor; 3010 void *edit_baton; 3011 apr_hash_t *rev_props; 3012 const char *word; 3013 svn_ra_svn__list_t *list; 3014 3015 svn_pool_clear(iterpool); 3016 3017 SVN_ERR(svn_ra_svn__read_tuple(sess->conn, iterpool, 3018 "wl", &word, &list)); 3019 3020 if (strcmp(word, "revprops") != 0) 3021 { 3022 if (strcmp(word, "failure") == 0) 3023 SVN_ERR(svn_ra_svn__handle_failure_status(list)); 3024 3025 return svn_error_createf(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, 3026 _("Expected 'revprops', found '%s'"), 3027 word); 3028 } 3029 3030 SVN_ERR(svn_ra_svn__parse_proplist(list, iterpool, &rev_props)); 3031 3032 SVN_ERR(revstart_func(rev, replay_baton, 3033 &editor, &edit_baton, 3034 rev_props, 3035 iterpool)); 3036 SVN_ERR(svn_ra_svn_drive_editor2(sess->conn, iterpool, 3037 editor, edit_baton, 3038 &drive_aborted, TRUE)); 3039 /* If drive_editor2() aborted the commit, do NOT try to call 3040 revfinish_func and commit the transaction! */ 3041 if (drive_aborted) { 3042 svn_pool_destroy(iterpool); 3043 return svn_error_create(SVN_ERR_RA_SVN_EDIT_ABORTED, NULL, 3044 _("Error while replaying commit")); 3045 } 3046 SVN_ERR(revfinish_func(rev, replay_baton, 3047 editor, edit_baton, 3048 rev_props, 3049 iterpool)); 3050 } 3051 svn_pool_destroy(iterpool); 3052 3053 return svn_error_trace(svn_ra_svn__read_cmd_response(sess->conn, pool, "")); 3054} 3055 3056 3057static svn_error_t * 3058ra_svn_has_capability(svn_ra_session_t *session, 3059 svn_boolean_t *has, 3060 const char *capability, 3061 apr_pool_t *pool) 3062{ 3063 svn_ra_svn__session_baton_t *sess = session->priv; 3064 static const char* capabilities[][2] = 3065 { 3066 /* { ra capability string, svn:// wire capability string} */ 3067 {SVN_RA_CAPABILITY_DEPTH, SVN_RA_SVN_CAP_DEPTH}, 3068 {SVN_RA_CAPABILITY_MERGEINFO, SVN_RA_SVN_CAP_MERGEINFO}, 3069 {SVN_RA_CAPABILITY_LOG_REVPROPS, SVN_RA_SVN_CAP_LOG_REVPROPS}, 3070 {SVN_RA_CAPABILITY_PARTIAL_REPLAY, SVN_RA_SVN_CAP_PARTIAL_REPLAY}, 3071 {SVN_RA_CAPABILITY_COMMIT_REVPROPS, SVN_RA_SVN_CAP_COMMIT_REVPROPS}, 3072 {SVN_RA_CAPABILITY_ATOMIC_REVPROPS, SVN_RA_SVN_CAP_ATOMIC_REVPROPS}, 3073 {SVN_RA_CAPABILITY_INHERITED_PROPS, SVN_RA_SVN_CAP_INHERITED_PROPS}, 3074 {SVN_RA_CAPABILITY_EPHEMERAL_TXNPROPS, 3075 SVN_RA_SVN_CAP_EPHEMERAL_TXNPROPS}, 3076 {SVN_RA_CAPABILITY_GET_FILE_REVS_REVERSE, 3077 SVN_RA_SVN_CAP_GET_FILE_REVS_REVERSE}, 3078 {SVN_RA_CAPABILITY_LIST, SVN_RA_SVN_CAP_LIST}, 3079 3080 {NULL, NULL} /* End of list marker */ 3081 }; 3082 int i; 3083 3084 *has = FALSE; 3085 3086 for (i = 0; capabilities[i][0]; i++) 3087 { 3088 if (strcmp(capability, capabilities[i][0]) == 0) 3089 { 3090 *has = svn_ra_svn_has_capability(sess->conn, capabilities[i][1]); 3091 return SVN_NO_ERROR; 3092 } 3093 } 3094 3095 return svn_error_createf(SVN_ERR_UNKNOWN_CAPABILITY, NULL, 3096 _("Don't know anything about capability '%s'"), 3097 capability); 3098} 3099 3100static svn_error_t * 3101ra_svn_get_deleted_rev(svn_ra_session_t *session, 3102 const char *path, 3103 svn_revnum_t peg_revision, 3104 svn_revnum_t end_revision, 3105 svn_revnum_t *revision_deleted, 3106 apr_pool_t *pool) 3107 3108{ 3109 svn_ra_svn__session_baton_t *sess_baton = session->priv; 3110 svn_ra_svn_conn_t *conn = sess_baton->conn; 3111 svn_error_t *err; 3112 3113 path = reparent_path(session, path, pool); 3114 3115 /* Transmit the parameters. */ 3116 SVN_ERR(svn_ra_svn__write_cmd_get_deleted_rev(conn, pool, path, 3117 peg_revision, end_revision)); 3118 3119 /* Servers before 1.6 don't support this command. Check for this here. */ 3120 SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess_baton, pool), 3121 N_("'get-deleted-rev' not implemented"))); 3122 3123 err = svn_error_trace(svn_ra_svn__read_cmd_response(conn, pool, "r", 3124 revision_deleted)); 3125 /* The protocol does not allow for a reply of SVN_INVALID_REVNUM directly. 3126 Instead, a new enough server returns SVN_ERR_ENTRY_MISSING_REVISION to 3127 indicate the answer to the query is SVN_INVALID_REVNUM. (An older server 3128 closes the connection and returns SVN_ERR_RA_SVN_CONNECTION_CLOSED.) */ 3129 if (err && err->apr_err == SVN_ERR_ENTRY_MISSING_REVISION) 3130 { 3131 *revision_deleted = SVN_INVALID_REVNUM; 3132 svn_error_clear(err); 3133 } 3134 else 3135 SVN_ERR(err); 3136 return SVN_NO_ERROR; 3137} 3138 3139static svn_error_t * 3140ra_svn_register_editor_shim_callbacks(svn_ra_session_t *session, 3141 svn_delta_shim_callbacks_t *callbacks) 3142{ 3143 svn_ra_svn__session_baton_t *sess_baton = session->priv; 3144 svn_ra_svn_conn_t *conn = sess_baton->conn; 3145 3146 conn->shim_callbacks = callbacks; 3147 3148 return SVN_NO_ERROR; 3149} 3150 3151static svn_error_t * 3152ra_svn_get_inherited_props(svn_ra_session_t *session, 3153 apr_array_header_t **iprops, 3154 const char *path, 3155 svn_revnum_t revision, 3156 apr_pool_t *result_pool, 3157 apr_pool_t *scratch_pool) 3158{ 3159 svn_ra_svn__session_baton_t *sess_baton = session->priv; 3160 svn_ra_svn_conn_t *conn = sess_baton->conn; 3161 svn_ra_svn__list_t *iproplist; 3162 svn_boolean_t iprop_capable; 3163 3164 path = reparent_path(session, path, scratch_pool); 3165 SVN_ERR(ra_svn_has_capability(session, &iprop_capable, 3166 SVN_RA_CAPABILITY_INHERITED_PROPS, 3167 scratch_pool)); 3168 3169 /* If we don't support native iprop handling, use the implementation 3170 in libsvn_ra */ 3171 if (!iprop_capable) 3172 return svn_error_create(SVN_ERR_RA_NOT_IMPLEMENTED, NULL, NULL); 3173 3174 SVN_ERR(svn_ra_svn__write_cmd_get_iprops(conn, scratch_pool, 3175 path, revision)); 3176 SVN_ERR(handle_auth_request(sess_baton, scratch_pool)); 3177 SVN_ERR(svn_ra_svn__read_cmd_response(conn, scratch_pool, "l", &iproplist)); 3178 SVN_ERR(parse_iproplist(iprops, iproplist, session, result_pool, 3179 scratch_pool)); 3180 3181 return SVN_NO_ERROR; 3182} 3183 3184static svn_error_t * 3185ra_svn_list(svn_ra_session_t *session, 3186 const char *path, 3187 svn_revnum_t revision, 3188 const apr_array_header_t *patterns, 3189 svn_depth_t depth, 3190 apr_uint32_t dirent_fields, 3191 svn_ra_dirent_receiver_t receiver, 3192 void *receiver_baton, 3193 apr_pool_t *scratch_pool) 3194{ 3195 svn_ra_svn__session_baton_t *sess_baton = session->priv; 3196 svn_ra_svn_conn_t *conn = sess_baton->conn; 3197 int i; 3198 apr_pool_t *iterpool = svn_pool_create(scratch_pool); 3199 3200 path = reparent_path(session, path, scratch_pool); 3201 3202 /* Send the list request. */ 3203 SVN_ERR(svn_ra_svn__write_tuple(conn, scratch_pool, "w(c(?r)w(!", "list", 3204 path, revision, svn_depth_to_word(depth))); 3205 SVN_ERR(send_dirent_fields(conn, dirent_fields, scratch_pool)); 3206 3207 if (patterns) 3208 { 3209 SVN_ERR(svn_ra_svn__write_tuple(conn, scratch_pool, "!)(!")); 3210 3211 for (i = 0; i < patterns->nelts; ++i) 3212 { 3213 const char *pattern = APR_ARRAY_IDX(patterns, i, const char *); 3214 SVN_ERR(svn_ra_svn__write_cstring(conn, scratch_pool, pattern)); 3215 } 3216 } 3217 3218 SVN_ERR(svn_ra_svn__write_tuple(conn, scratch_pool, "!))")); 3219 3220 /* Handle auth request by server */ 3221 SVN_ERR(handle_auth_request(sess_baton, scratch_pool)); 3222 3223 /* Read and process list response. */ 3224 while (1) 3225 { 3226 svn_ra_svn__item_t *item; 3227 const char *dirent_path; 3228 const char *kind_word, *date; 3229 svn_dirent_t dirent = { 0 }; 3230 3231 svn_pool_clear(iterpool); 3232 3233 /* Read the next dirent or bail out on "done", respectively */ 3234 SVN_ERR(svn_ra_svn__read_item(conn, iterpool, &item)); 3235 if (is_done_response(item)) 3236 break; 3237 if (item->kind != SVN_RA_SVN_LIST) 3238 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, 3239 _("List entry not a list")); 3240 SVN_ERR(svn_ra_svn__parse_tuple(&item->u.list, 3241 "cw?(?n)(?b)(?r)(?c)(?c)", 3242 &dirent_path, &kind_word, &dirent.size, 3243 &dirent.has_props, &dirent.created_rev, 3244 &date, &dirent.last_author)); 3245 3246 /* Convert data. */ 3247 dirent.kind = svn_node_kind_from_word(kind_word); 3248 if (date) 3249 SVN_ERR(svn_time_from_cstring(&dirent.time, date, iterpool)); 3250 3251 /* Invoke RECEIVER */ 3252 SVN_ERR(receiver(dirent_path, &dirent, receiver_baton, iterpool)); 3253 } 3254 svn_pool_destroy(iterpool); 3255 3256 /* Read the actual command response. */ 3257 SVN_ERR(svn_ra_svn__read_cmd_response(conn, scratch_pool, "")); 3258 return SVN_NO_ERROR; 3259} 3260 3261static const svn_ra__vtable_t ra_svn_vtable = { 3262 svn_ra_svn_version, 3263 ra_svn_get_description, 3264 ra_svn_get_schemes, 3265 ra_svn_open, 3266 ra_svn_dup_session, 3267 ra_svn_reparent, 3268 ra_svn_get_session_url, 3269 ra_svn_get_latest_rev, 3270 ra_svn_get_dated_rev, 3271 ra_svn_change_rev_prop, 3272 ra_svn_rev_proplist, 3273 ra_svn_rev_prop, 3274 ra_svn_commit, 3275 ra_svn_get_file, 3276 ra_svn_get_dir, 3277 ra_svn_get_mergeinfo, 3278 ra_svn_update, 3279 ra_svn_switch, 3280 ra_svn_status, 3281 ra_svn_diff, 3282 ra_svn_log, 3283 ra_svn_check_path, 3284 ra_svn_stat, 3285 ra_svn_get_uuid, 3286 ra_svn_get_repos_root, 3287 ra_svn_get_locations, 3288 ra_svn_get_location_segments, 3289 ra_svn_get_file_revs, 3290 ra_svn_lock, 3291 ra_svn_unlock, 3292 ra_svn_get_lock, 3293 ra_svn_get_locks, 3294 ra_svn_replay, 3295 ra_svn_has_capability, 3296 ra_svn_replay_range, 3297 ra_svn_get_deleted_rev, 3298 ra_svn_get_inherited_props, 3299 NULL /* ra_set_svn_ra_open */, 3300 ra_svn_list, 3301 ra_svn_register_editor_shim_callbacks, 3302 NULL /* commit_ev2 */, 3303 NULL /* replay_range_ev2 */ 3304}; 3305 3306svn_error_t * 3307svn_ra_svn__init(const svn_version_t *loader_version, 3308 const svn_ra__vtable_t **vtable, 3309 apr_pool_t *pool) 3310{ 3311 static const svn_version_checklist_t checklist[] = 3312 { 3313 { "svn_subr", svn_subr_version }, 3314 { "svn_delta", svn_delta_version }, 3315 { NULL, NULL } 3316 }; 3317 3318 SVN_ERR(svn_ver_check_list2(svn_ra_svn_version(), checklist, svn_ver_equal)); 3319 3320 /* Simplified version check to make sure we can safely use the 3321 VTABLE parameter. The RA loader does a more exhaustive check. */ 3322 if (loader_version->major != SVN_VER_MAJOR) 3323 { 3324 return svn_error_createf 3325 (SVN_ERR_VERSION_MISMATCH, NULL, 3326 _("Unsupported RA loader version (%d) for ra_svn"), 3327 loader_version->major); 3328 } 3329 3330 *vtable = &ra_svn_vtable; 3331 3332#ifdef SVN_HAVE_SASL 3333 SVN_ERR(svn_ra_svn__sasl_init()); 3334#endif 3335 3336 return SVN_NO_ERROR; 3337} 3338 3339/* Compatibility wrapper for the 1.1 and before API. */ 3340#define NAME "ra_svn" 3341#define DESCRIPTION RA_SVN_DESCRIPTION 3342#define VTBL ra_svn_vtable 3343#define INITFUNC svn_ra_svn__init 3344#define COMPAT_INITFUNC svn_ra_svn_init 3345#include "../libsvn_ra/wrapper_template.h" 3346