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