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