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