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