1251881Speter/* 2251881Speter * client.c : Functions for repository access via the Subversion protocol 3251881Speter * 4251881Speter * ==================================================================== 5251881Speter * Licensed to the Apache Software Foundation (ASF) under one 6251881Speter * or more contributor license agreements. See the NOTICE file 7251881Speter * distributed with this work for additional information 8251881Speter * regarding copyright ownership. The ASF licenses this file 9251881Speter * to you under the Apache License, Version 2.0 (the 10251881Speter * "License"); you may not use this file except in compliance 11251881Speter * with the License. You may obtain a copy of the License at 12251881Speter * 13251881Speter * http://www.apache.org/licenses/LICENSE-2.0 14251881Speter * 15251881Speter * Unless required by applicable law or agreed to in writing, 16251881Speter * software distributed under the License is distributed on an 17251881Speter * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 18251881Speter * KIND, either express or implied. See the License for the 19251881Speter * specific language governing permissions and limitations 20251881Speter * under the License. 21251881Speter * ==================================================================== 22251881Speter */ 23251881Speter 24251881Speter 25251881Speter 26251881Speter#include "svn_private_config.h" 27251881Speter 28251881Speter#define APR_WANT_STRFUNC 29251881Speter#include <apr_want.h> 30251881Speter#include <apr_general.h> 31251881Speter#include <apr_strings.h> 32251881Speter#include <apr_network_io.h> 33251881Speter#include <apr_uri.h> 34251881Speter 35251881Speter#include "svn_hash.h" 36251881Speter#include "svn_types.h" 37251881Speter#include "svn_string.h" 38251881Speter#include "svn_dirent_uri.h" 39251881Speter#include "svn_error.h" 40251881Speter#include "svn_time.h" 41251881Speter#include "svn_path.h" 42251881Speter#include "svn_pools.h" 43251881Speter#include "svn_config.h" 44251881Speter#include "svn_ra.h" 45251881Speter#include "svn_ra_svn.h" 46251881Speter#include "svn_props.h" 47251881Speter#include "svn_mergeinfo.h" 48251881Speter#include "svn_version.h" 49251881Speter 50251881Speter#include "svn_private_config.h" 51251881Speter 52251881Speter#include "private/svn_fspath.h" 53262253Speter#include "private/svn_subr_private.h" 54251881Speter 55251881Speter#include "../libsvn_ra/ra_loader.h" 56251881Speter 57251881Speter#include "ra_svn.h" 58251881Speter 59251881Speter#ifdef SVN_HAVE_SASL 60251881Speter#define DO_AUTH svn_ra_svn__do_cyrus_auth 61251881Speter#else 62251881Speter#define DO_AUTH svn_ra_svn__do_internal_auth 63251881Speter#endif 64251881Speter 65251881Speter/* We aren't using SVN_DEPTH_IS_RECURSIVE here because that macro (for 66251881Speter whatever reason) deems svn_depth_immediates as non-recursive, which 67251881Speter is ... kinda true, but not true enough for our purposes. We need 68251881Speter our requested recursion level to be *at least* as recursive as the 69251881Speter real depth we're looking for. 70251881Speter */ 71251881Speter#define DEPTH_TO_RECURSE(d) \ 72251881Speter ((d) == svn_depth_unknown || (d) > svn_depth_files) 73251881Speter 74251881Spetertypedef struct ra_svn_commit_callback_baton_t { 75251881Speter svn_ra_svn__session_baton_t *sess_baton; 76251881Speter apr_pool_t *pool; 77251881Speter svn_revnum_t *new_rev; 78251881Speter svn_commit_callback2_t callback; 79251881Speter void *callback_baton; 80251881Speter} ra_svn_commit_callback_baton_t; 81251881Speter 82251881Spetertypedef struct ra_svn_reporter_baton_t { 83251881Speter svn_ra_svn__session_baton_t *sess_baton; 84251881Speter svn_ra_svn_conn_t *conn; 85251881Speter apr_pool_t *pool; 86251881Speter const svn_delta_editor_t *editor; 87251881Speter void *edit_baton; 88251881Speter} ra_svn_reporter_baton_t; 89251881Speter 90251881Speter/* Parse an svn URL's tunnel portion into tunnel, if there is a tunnel 91251881Speter portion. */ 92251881Speterstatic void parse_tunnel(const char *url, const char **tunnel, 93251881Speter apr_pool_t *pool) 94251881Speter{ 95251881Speter *tunnel = NULL; 96251881Speter 97251881Speter if (strncasecmp(url, "svn", 3) != 0) 98251881Speter return; 99251881Speter url += 3; 100251881Speter 101251881Speter /* Get the tunnel specification, if any. */ 102251881Speter if (*url == '+') 103251881Speter { 104251881Speter const char *p; 105251881Speter 106251881Speter url++; 107251881Speter p = strchr(url, ':'); 108251881Speter if (!p) 109251881Speter return; 110251881Speter *tunnel = apr_pstrmemdup(pool, url, p - url); 111251881Speter } 112251881Speter} 113251881Speter 114251881Speterstatic svn_error_t *make_connection(const char *hostname, unsigned short port, 115251881Speter apr_socket_t **sock, apr_pool_t *pool) 116251881Speter{ 117251881Speter apr_sockaddr_t *sa; 118251881Speter apr_status_t status; 119251881Speter int family = APR_INET; 120251881Speter 121251881Speter /* Make sure we have IPV6 support first before giving apr_sockaddr_info_get 122251881Speter APR_UNSPEC, because it may give us back an IPV6 address even if we can't 123251881Speter create IPV6 sockets. */ 124251881Speter 125251881Speter#if APR_HAVE_IPV6 126251881Speter#ifdef MAX_SECS_TO_LINGER 127251881Speter status = apr_socket_create(sock, APR_INET6, SOCK_STREAM, pool); 128251881Speter#else 129251881Speter status = apr_socket_create(sock, APR_INET6, SOCK_STREAM, 130251881Speter APR_PROTO_TCP, pool); 131251881Speter#endif 132251881Speter if (status == 0) 133251881Speter { 134251881Speter apr_socket_close(*sock); 135251881Speter family = APR_UNSPEC; 136251881Speter } 137251881Speter#endif 138251881Speter 139251881Speter /* Resolve the hostname. */ 140251881Speter status = apr_sockaddr_info_get(&sa, hostname, family, port, 0, pool); 141251881Speter if (status) 142251881Speter return svn_error_createf(status, NULL, _("Unknown hostname '%s'"), 143251881Speter hostname); 144251881Speter /* Iterate through the returned list of addresses attempting to 145251881Speter * connect to each in turn. */ 146251881Speter do 147251881Speter { 148251881Speter /* Create the socket. */ 149251881Speter#ifdef MAX_SECS_TO_LINGER 150251881Speter /* ### old APR interface */ 151251881Speter status = apr_socket_create(sock, sa->family, SOCK_STREAM, pool); 152251881Speter#else 153251881Speter status = apr_socket_create(sock, sa->family, SOCK_STREAM, APR_PROTO_TCP, 154251881Speter pool); 155251881Speter#endif 156251881Speter if (status == APR_SUCCESS) 157251881Speter { 158251881Speter status = apr_socket_connect(*sock, sa); 159251881Speter if (status != APR_SUCCESS) 160251881Speter apr_socket_close(*sock); 161251881Speter } 162251881Speter sa = sa->next; 163251881Speter } 164251881Speter while (status != APR_SUCCESS && sa); 165251881Speter 166251881Speter if (status) 167251881Speter return svn_error_wrap_apr(status, _("Can't connect to host '%s'"), 168251881Speter hostname); 169251881Speter 170251881Speter /* Enable TCP keep-alives on the socket so we time out when 171251881Speter * the connection breaks due to network-layer problems. 172251881Speter * If the peer has dropped the connection due to a network partition 173251881Speter * or a crash, or if the peer no longer considers the connection 174251881Speter * valid because we are behind a NAT and our public IP has changed, 175251881Speter * it will respond to the keep-alive probe with a RST instead of an 176251881Speter * acknowledgment segment, which will cause svn to abort the session 177251881Speter * even while it is currently blocked waiting for data from the peer. 178251881Speter * See issue #3347. */ 179251881Speter status = apr_socket_opt_set(*sock, APR_SO_KEEPALIVE, 1); 180251881Speter if (status) 181251881Speter { 182251881Speter /* It's not a fatal error if we cannot enable keep-alives. */ 183251881Speter } 184251881Speter 185251881Speter return SVN_NO_ERROR; 186251881Speter} 187251881Speter 188251881Speter/* Set *DIFFS to an array of svn_prop_t, allocated in POOL, based on the 189251881Speter property diffs in LIST, received from the server. */ 190251881Speterstatic svn_error_t *parse_prop_diffs(const apr_array_header_t *list, 191251881Speter apr_pool_t *pool, 192251881Speter apr_array_header_t **diffs) 193251881Speter{ 194251881Speter int i; 195251881Speter 196251881Speter *diffs = apr_array_make(pool, list->nelts, sizeof(svn_prop_t)); 197251881Speter 198251881Speter for (i = 0; i < list->nelts; i++) 199251881Speter { 200251881Speter svn_prop_t *prop; 201251881Speter svn_ra_svn_item_t *elt = &APR_ARRAY_IDX(list, i, svn_ra_svn_item_t); 202251881Speter 203251881Speter if (elt->kind != SVN_RA_SVN_LIST) 204251881Speter return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, 205251881Speter _("Prop diffs element not a list")); 206251881Speter prop = apr_array_push(*diffs); 207251881Speter SVN_ERR(svn_ra_svn__parse_tuple(elt->u.list, pool, "c(?s)", &prop->name, 208251881Speter &prop->value)); 209251881Speter } 210251881Speter return SVN_NO_ERROR; 211251881Speter} 212251881Speter 213251881Speter/* Parse a lockdesc, provided in LIST as specified by the protocol into 214251881Speter LOCK, allocated in POOL. */ 215251881Speterstatic svn_error_t *parse_lock(const apr_array_header_t *list, apr_pool_t *pool, 216251881Speter svn_lock_t **lock) 217251881Speter{ 218251881Speter const char *cdate, *edate; 219251881Speter *lock = svn_lock_create(pool); 220251881Speter SVN_ERR(svn_ra_svn__parse_tuple(list, pool, "ccc(?c)c(?c)", &(*lock)->path, 221251881Speter &(*lock)->token, &(*lock)->owner, 222251881Speter &(*lock)->comment, &cdate, &edate)); 223251881Speter (*lock)->path = svn_fspath__canonicalize((*lock)->path, pool); 224251881Speter SVN_ERR(svn_time_from_cstring(&(*lock)->creation_date, cdate, pool)); 225251881Speter if (edate) 226251881Speter SVN_ERR(svn_time_from_cstring(&(*lock)->expiration_date, edate, pool)); 227251881Speter return SVN_NO_ERROR; 228251881Speter} 229251881Speter 230251881Speter/* --- AUTHENTICATION ROUTINES --- */ 231251881Speter 232251881Spetersvn_error_t *svn_ra_svn__auth_response(svn_ra_svn_conn_t *conn, 233251881Speter apr_pool_t *pool, 234251881Speter const char *mech, const char *mech_arg) 235251881Speter{ 236251881Speter return svn_ra_svn__write_tuple(conn, pool, "w(?c)", mech, mech_arg); 237251881Speter} 238251881Speter 239251881Speterstatic svn_error_t *handle_auth_request(svn_ra_svn__session_baton_t *sess, 240251881Speter apr_pool_t *pool) 241251881Speter{ 242251881Speter svn_ra_svn_conn_t *conn = sess->conn; 243251881Speter apr_array_header_t *mechlist; 244251881Speter const char *realm; 245251881Speter 246251881Speter SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "lc", &mechlist, &realm)); 247251881Speter if (mechlist->nelts == 0) 248251881Speter return SVN_NO_ERROR; 249251881Speter return DO_AUTH(sess, mechlist, realm, pool); 250251881Speter} 251251881Speter 252251881Speter/* --- REPORTER IMPLEMENTATION --- */ 253251881Speter 254251881Speterstatic svn_error_t *ra_svn_set_path(void *baton, const char *path, 255251881Speter svn_revnum_t rev, 256251881Speter svn_depth_t depth, 257251881Speter svn_boolean_t start_empty, 258251881Speter const char *lock_token, 259251881Speter apr_pool_t *pool) 260251881Speter{ 261251881Speter ra_svn_reporter_baton_t *b = baton; 262251881Speter 263251881Speter SVN_ERR(svn_ra_svn__write_cmd_set_path(b->conn, pool, path, rev, 264251881Speter start_empty, lock_token, depth)); 265251881Speter return SVN_NO_ERROR; 266251881Speter} 267251881Speter 268251881Speterstatic svn_error_t *ra_svn_delete_path(void *baton, const char *path, 269251881Speter apr_pool_t *pool) 270251881Speter{ 271251881Speter ra_svn_reporter_baton_t *b = baton; 272251881Speter 273251881Speter SVN_ERR(svn_ra_svn__write_cmd_delete_path(b->conn, pool, path)); 274251881Speter return SVN_NO_ERROR; 275251881Speter} 276251881Speter 277251881Speterstatic svn_error_t *ra_svn_link_path(void *baton, const char *path, 278251881Speter const char *url, 279251881Speter svn_revnum_t rev, 280251881Speter svn_depth_t depth, 281251881Speter svn_boolean_t start_empty, 282251881Speter const char *lock_token, 283251881Speter apr_pool_t *pool) 284251881Speter{ 285251881Speter ra_svn_reporter_baton_t *b = baton; 286251881Speter 287251881Speter SVN_ERR(svn_ra_svn__write_cmd_link_path(b->conn, pool, path, url, rev, 288251881Speter start_empty, lock_token, depth)); 289251881Speter return SVN_NO_ERROR; 290251881Speter} 291251881Speter 292251881Speterstatic svn_error_t *ra_svn_finish_report(void *baton, 293251881Speter apr_pool_t *pool) 294251881Speter{ 295251881Speter ra_svn_reporter_baton_t *b = baton; 296251881Speter 297251881Speter SVN_ERR(svn_ra_svn__write_cmd_finish_report(b->conn, b->pool)); 298251881Speter SVN_ERR(handle_auth_request(b->sess_baton, b->pool)); 299251881Speter SVN_ERR(svn_ra_svn_drive_editor2(b->conn, b->pool, b->editor, b->edit_baton, 300251881Speter NULL, FALSE)); 301251881Speter SVN_ERR(svn_ra_svn__read_cmd_response(b->conn, b->pool, "")); 302251881Speter return SVN_NO_ERROR; 303251881Speter} 304251881Speter 305251881Speterstatic svn_error_t *ra_svn_abort_report(void *baton, 306251881Speter apr_pool_t *pool) 307251881Speter{ 308251881Speter ra_svn_reporter_baton_t *b = baton; 309251881Speter 310251881Speter SVN_ERR(svn_ra_svn__write_cmd_abort_report(b->conn, b->pool)); 311251881Speter return SVN_NO_ERROR; 312251881Speter} 313251881Speter 314251881Speterstatic svn_ra_reporter3_t ra_svn_reporter = { 315251881Speter ra_svn_set_path, 316251881Speter ra_svn_delete_path, 317251881Speter ra_svn_link_path, 318251881Speter ra_svn_finish_report, 319251881Speter ra_svn_abort_report 320251881Speter}; 321251881Speter 322251881Speter/* Set *REPORTER and *REPORT_BATON to a new reporter which will drive 323251881Speter * EDITOR/EDIT_BATON when it gets the finish_report() call. 324251881Speter * 325251881Speter * Allocate the new reporter in POOL. 326251881Speter */ 327251881Speterstatic svn_error_t * 328251881Speterra_svn_get_reporter(svn_ra_svn__session_baton_t *sess_baton, 329251881Speter apr_pool_t *pool, 330251881Speter const svn_delta_editor_t *editor, 331251881Speter void *edit_baton, 332251881Speter const char *target, 333251881Speter svn_depth_t depth, 334251881Speter const svn_ra_reporter3_t **reporter, 335251881Speter void **report_baton) 336251881Speter{ 337251881Speter ra_svn_reporter_baton_t *b; 338251881Speter const svn_delta_editor_t *filter_editor; 339251881Speter void *filter_baton; 340251881Speter 341251881Speter /* We can skip the depth filtering when the user requested 342251881Speter depth_files or depth_infinity because the server will 343251881Speter transmit the right stuff anyway. */ 344251881Speter if ((depth != svn_depth_files) && (depth != svn_depth_infinity) 345251881Speter && ! svn_ra_svn_has_capability(sess_baton->conn, SVN_RA_SVN_CAP_DEPTH)) 346251881Speter { 347251881Speter SVN_ERR(svn_delta_depth_filter_editor(&filter_editor, 348251881Speter &filter_baton, 349251881Speter editor, edit_baton, depth, 350251881Speter *target != '\0', 351251881Speter pool)); 352251881Speter editor = filter_editor; 353251881Speter edit_baton = filter_baton; 354251881Speter } 355251881Speter 356251881Speter b = apr_palloc(pool, sizeof(*b)); 357251881Speter b->sess_baton = sess_baton; 358251881Speter b->conn = sess_baton->conn; 359251881Speter b->pool = pool; 360251881Speter b->editor = editor; 361251881Speter b->edit_baton = edit_baton; 362251881Speter 363251881Speter *reporter = &ra_svn_reporter; 364251881Speter *report_baton = b; 365251881Speter 366251881Speter return SVN_NO_ERROR; 367251881Speter} 368251881Speter 369251881Speter/* --- RA LAYER IMPLEMENTATION --- */ 370251881Speter 371251881Speter/* (Note: *ARGV is an output parameter.) */ 372251881Speterstatic svn_error_t *find_tunnel_agent(const char *tunnel, 373251881Speter const char *hostinfo, 374251881Speter const char ***argv, 375251881Speter apr_hash_t *config, apr_pool_t *pool) 376251881Speter{ 377251881Speter svn_config_t *cfg; 378251881Speter const char *val, *var, *cmd; 379251881Speter char **cmd_argv; 380251881Speter apr_size_t len; 381251881Speter apr_status_t status; 382251881Speter int n; 383251881Speter 384251881Speter /* Look up the tunnel specification in config. */ 385251881Speter cfg = config ? svn_hash_gets(config, SVN_CONFIG_CATEGORY_CONFIG) : NULL; 386251881Speter svn_config_get(cfg, &val, SVN_CONFIG_SECTION_TUNNELS, tunnel, NULL); 387251881Speter 388251881Speter /* We have one predefined tunnel scheme, if it isn't overridden by config. */ 389251881Speter if (!val && strcmp(tunnel, "ssh") == 0) 390251881Speter { 391251881Speter /* Killing the tunnel agent with SIGTERM leads to unsightly 392251881Speter * stderr output from ssh, unless we pass -q. 393251881Speter * The "-q" option to ssh is widely supported: all versions of 394251881Speter * OpenSSH have it, the old ssh-1.x and the 2.x, 3.x ssh.com 395251881Speter * versions have it too. If the user is using some other ssh 396251881Speter * implementation that doesn't accept it, they can override it 397251881Speter * in the [tunnels] section of the config. */ 398251881Speter val = "$SVN_SSH ssh -q"; 399251881Speter } 400251881Speter 401251881Speter if (!val || !*val) 402251881Speter return svn_error_createf(SVN_ERR_BAD_URL, NULL, 403251881Speter _("Undefined tunnel scheme '%s'"), tunnel); 404251881Speter 405251881Speter /* If the scheme definition begins with "$varname", it means there 406251881Speter * is an environment variable which can override the command. */ 407251881Speter if (*val == '$') 408251881Speter { 409251881Speter val++; 410251881Speter len = strcspn(val, " "); 411251881Speter var = apr_pstrmemdup(pool, val, len); 412251881Speter cmd = getenv(var); 413251881Speter if (!cmd) 414251881Speter { 415251881Speter cmd = val + len; 416251881Speter while (*cmd == ' ') 417251881Speter cmd++; 418251881Speter if (!*cmd) 419251881Speter return svn_error_createf(SVN_ERR_BAD_URL, NULL, 420251881Speter _("Tunnel scheme %s requires environment " 421251881Speter "variable %s to be defined"), tunnel, 422251881Speter var); 423251881Speter } 424251881Speter } 425251881Speter else 426251881Speter cmd = val; 427251881Speter 428251881Speter /* Tokenize the command into a list of arguments. */ 429251881Speter status = apr_tokenize_to_argv(cmd, &cmd_argv, pool); 430251881Speter if (status != APR_SUCCESS) 431251881Speter return svn_error_wrap_apr(status, _("Can't tokenize command '%s'"), cmd); 432251881Speter 433251881Speter /* Append the fixed arguments to the result. */ 434251881Speter for (n = 0; cmd_argv[n] != NULL; n++) 435251881Speter ; 436251881Speter *argv = apr_palloc(pool, (n + 4) * sizeof(char *)); 437251881Speter memcpy((void *) *argv, cmd_argv, n * sizeof(char *)); 438251881Speter (*argv)[n++] = svn_path_uri_decode(hostinfo, pool); 439251881Speter (*argv)[n++] = "svnserve"; 440251881Speter (*argv)[n++] = "-t"; 441251881Speter (*argv)[n] = NULL; 442251881Speter 443251881Speter return SVN_NO_ERROR; 444251881Speter} 445251881Speter 446251881Speter/* This function handles any errors which occur in the child process 447251881Speter * created for a tunnel agent. We write the error out as a command 448251881Speter * failure; the code in ra_svn_open() to read the server's greeting 449251881Speter * will see the error and return it to the caller. */ 450251881Speterstatic void handle_child_process_error(apr_pool_t *pool, apr_status_t status, 451251881Speter const char *desc) 452251881Speter{ 453251881Speter svn_ra_svn_conn_t *conn; 454251881Speter apr_file_t *in_file, *out_file; 455251881Speter svn_error_t *err; 456251881Speter 457251881Speter if (apr_file_open_stdin(&in_file, pool) 458251881Speter || apr_file_open_stdout(&out_file, pool)) 459251881Speter return; 460251881Speter 461251881Speter conn = svn_ra_svn_create_conn3(NULL, in_file, out_file, 462251881Speter SVN_DELTA_COMPRESSION_LEVEL_DEFAULT, 0, 463251881Speter 0, pool); 464251881Speter err = svn_error_wrap_apr(status, _("Error in child process: %s"), desc); 465251881Speter svn_error_clear(svn_ra_svn__write_cmd_failure(conn, pool, err)); 466251881Speter svn_error_clear(err); 467251881Speter svn_error_clear(svn_ra_svn__flush(conn, pool)); 468251881Speter} 469251881Speter 470251881Speter/* (Note: *CONN is an output parameter.) */ 471251881Speterstatic svn_error_t *make_tunnel(const char **args, svn_ra_svn_conn_t **conn, 472251881Speter apr_pool_t *pool) 473251881Speter{ 474251881Speter apr_status_t status; 475251881Speter apr_proc_t *proc; 476251881Speter apr_procattr_t *attr; 477251881Speter svn_error_t *err; 478251881Speter 479251881Speter status = apr_procattr_create(&attr, pool); 480251881Speter if (status == APR_SUCCESS) 481251881Speter status = apr_procattr_io_set(attr, 1, 1, 0); 482251881Speter if (status == APR_SUCCESS) 483251881Speter status = apr_procattr_cmdtype_set(attr, APR_PROGRAM_PATH); 484251881Speter if (status == APR_SUCCESS) 485251881Speter status = apr_procattr_child_errfn_set(attr, handle_child_process_error); 486251881Speter proc = apr_palloc(pool, sizeof(*proc)); 487251881Speter if (status == APR_SUCCESS) 488251881Speter status = apr_proc_create(proc, *args, args, NULL, attr, pool); 489251881Speter if (status != APR_SUCCESS) 490251881Speter return svn_error_create(SVN_ERR_RA_CANNOT_CREATE_TUNNEL, 491251881Speter svn_error_wrap_apr(status, 492251881Speter _("Can't create tunnel")), NULL); 493251881Speter 494251881Speter /* Arrange for the tunnel agent to get a SIGTERM on pool 495251881Speter * cleanup. This is a little extreme, but the alternatives 496251881Speter * weren't working out. 497251881Speter * 498251881Speter * Closing the pipes and waiting for the process to die 499251881Speter * was prone to mysterious hangs which are difficult to 500251881Speter * diagnose (e.g. svnserve dumps core due to unrelated bug; 501251881Speter * sshd goes into zombie state; ssh connection is never 502251881Speter * closed; ssh never terminates). 503251881Speter * See also the long dicussion in issue #2580 if you really 504251881Speter * want to know various reasons for these problems and 505251881Speter * the different opinions on this issue. 506251881Speter * 507251881Speter * On Win32, APR does not support KILL_ONLY_ONCE. It only has 508251881Speter * KILL_ALWAYS and KILL_NEVER. Other modes are converted to 509251881Speter * KILL_ALWAYS, which immediately calls TerminateProcess(). 510251881Speter * This instantly kills the tunnel, leaving sshd and svnserve 511251881Speter * on a remote machine running indefinitely. These processes 512251881Speter * accumulate. The problem is most often seen with a fast client 513251881Speter * machine and a modest internet connection, as the tunnel 514251881Speter * is killed before being able to gracefully complete the 515251881Speter * session. In that case, svn is unusable 100% of the time on 516251881Speter * the windows machine. Thus, on Win32, we use KILL_NEVER and 517251881Speter * take the lesser of two evils. 518251881Speter */ 519251881Speter#ifdef WIN32 520251881Speter apr_pool_note_subprocess(pool, proc, APR_KILL_NEVER); 521251881Speter#else 522251881Speter apr_pool_note_subprocess(pool, proc, APR_KILL_ONLY_ONCE); 523251881Speter#endif 524251881Speter 525251881Speter /* APR pipe objects inherit by default. But we don't want the 526251881Speter * tunnel agent's pipes held open by future child processes 527251881Speter * (such as other ra_svn sessions), so turn that off. */ 528251881Speter apr_file_inherit_unset(proc->in); 529251881Speter apr_file_inherit_unset(proc->out); 530251881Speter 531251881Speter /* Guard against dotfile output to stdout on the server. */ 532251881Speter *conn = svn_ra_svn_create_conn3(NULL, proc->out, proc->in, 533251881Speter SVN_DELTA_COMPRESSION_LEVEL_DEFAULT, 534251881Speter 0, 0, pool); 535251881Speter err = svn_ra_svn__skip_leading_garbage(*conn, pool); 536251881Speter if (err) 537251881Speter return svn_error_quick_wrap( 538251881Speter err, 539251881Speter _("To better debug SSH connection problems, remove the -q " 540251881Speter "option from 'ssh' in the [tunnels] section of your " 541251881Speter "Subversion configuration file.")); 542251881Speter 543251881Speter return SVN_NO_ERROR; 544251881Speter} 545251881Speter 546251881Speter/* Parse URL inot URI, validating it and setting the default port if none 547251881Speter was given. Allocate the URI fileds out of POOL. */ 548251881Speterstatic svn_error_t *parse_url(const char *url, apr_uri_t *uri, 549251881Speter apr_pool_t *pool) 550251881Speter{ 551251881Speter apr_status_t apr_err; 552251881Speter 553251881Speter apr_err = apr_uri_parse(pool, url, uri); 554251881Speter 555251881Speter if (apr_err != 0) 556251881Speter return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL, 557251881Speter _("Illegal svn repository URL '%s'"), url); 558251881Speter 559251881Speter if (! uri->port) 560251881Speter uri->port = SVN_RA_SVN_PORT; 561251881Speter 562251881Speter return SVN_NO_ERROR; 563251881Speter} 564251881Speter 565251881Speter/* Open a session to URL, returning it in *SESS_P, allocating it in POOL. 566251881Speter URI is a parsed version of URL. CALLBACKS and CALLBACKS_BATON 567251881Speter are provided by the caller of ra_svn_open. If tunnel_argv is non-null, 568251881Speter it points to a program argument list to use when invoking the tunnel agent. 569251881Speter*/ 570251881Speterstatic svn_error_t *open_session(svn_ra_svn__session_baton_t **sess_p, 571251881Speter const char *url, 572251881Speter const apr_uri_t *uri, 573251881Speter const char **tunnel_argv, 574251881Speter const svn_ra_callbacks2_t *callbacks, 575251881Speter void *callbacks_baton, 576251881Speter apr_pool_t *pool) 577251881Speter{ 578251881Speter svn_ra_svn__session_baton_t *sess; 579251881Speter svn_ra_svn_conn_t *conn; 580251881Speter apr_socket_t *sock; 581251881Speter apr_uint64_t minver, maxver; 582251881Speter apr_array_header_t *mechlist, *server_caplist, *repos_caplist; 583251881Speter const char *client_string = NULL; 584251881Speter 585251881Speter sess = apr_palloc(pool, sizeof(*sess)); 586251881Speter sess->pool = pool; 587251881Speter sess->is_tunneled = (tunnel_argv != NULL); 588251881Speter sess->url = apr_pstrdup(pool, url); 589251881Speter sess->user = uri->user; 590251881Speter sess->hostname = uri->hostname; 591251881Speter sess->realm_prefix = apr_psprintf(pool, "<svn://%s:%d>", uri->hostname, 592251881Speter uri->port); 593251881Speter sess->tunnel_argv = tunnel_argv; 594251881Speter sess->callbacks = callbacks; 595251881Speter sess->callbacks_baton = callbacks_baton; 596251881Speter sess->bytes_read = sess->bytes_written = 0; 597251881Speter 598251881Speter if (tunnel_argv) 599251881Speter SVN_ERR(make_tunnel(tunnel_argv, &conn, pool)); 600251881Speter else 601251881Speter { 602251881Speter SVN_ERR(make_connection(uri->hostname, uri->port, &sock, pool)); 603251881Speter conn = svn_ra_svn_create_conn3(sock, NULL, NULL, 604251881Speter SVN_DELTA_COMPRESSION_LEVEL_DEFAULT, 605251881Speter 0, 0, pool); 606251881Speter } 607251881Speter 608251881Speter /* Build the useragent string, querying the client for any 609251881Speter customizations it wishes to note. For historical reasons, we 610251881Speter still deliver the hard-coded client version info 611251881Speter (SVN_RA_SVN__DEFAULT_USERAGENT) and the customized client string 612251881Speter separately in the protocol/capabilities handshake below. But the 613251881Speter commit logic wants the combined form for use with the 614251881Speter SVN_PROP_TXN_USER_AGENT ephemeral property because that's 615251881Speter consistent with our DAV approach. */ 616251881Speter if (sess->callbacks->get_client_string != NULL) 617251881Speter SVN_ERR(sess->callbacks->get_client_string(sess->callbacks_baton, 618251881Speter &client_string, pool)); 619251881Speter if (client_string) 620251881Speter sess->useragent = apr_pstrcat(pool, SVN_RA_SVN__DEFAULT_USERAGENT " ", 621251881Speter client_string, (char *)NULL); 622251881Speter else 623251881Speter sess->useragent = SVN_RA_SVN__DEFAULT_USERAGENT; 624251881Speter 625251881Speter /* Make sure we set conn->session before reading from it, 626251881Speter * because the reader and writer functions expect a non-NULL value. */ 627251881Speter sess->conn = conn; 628251881Speter conn->session = sess; 629251881Speter 630251881Speter /* Read server's greeting. */ 631251881Speter SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "nnll", &minver, &maxver, 632251881Speter &mechlist, &server_caplist)); 633251881Speter 634251881Speter /* We support protocol version 2. */ 635251881Speter if (minver > 2) 636251881Speter return svn_error_createf(SVN_ERR_RA_SVN_BAD_VERSION, NULL, 637251881Speter _("Server requires minimum version %d"), 638251881Speter (int) minver); 639251881Speter if (maxver < 2) 640251881Speter return svn_error_createf(SVN_ERR_RA_SVN_BAD_VERSION, NULL, 641251881Speter _("Server only supports versions up to %d"), 642251881Speter (int) maxver); 643251881Speter SVN_ERR(svn_ra_svn_set_capabilities(conn, server_caplist)); 644251881Speter 645251881Speter /* All released versions of Subversion support edit-pipeline, 646251881Speter * so we do not support servers that do not. */ 647251881Speter if (! svn_ra_svn_has_capability(conn, SVN_RA_SVN_CAP_EDIT_PIPELINE)) 648251881Speter return svn_error_create(SVN_ERR_RA_SVN_BAD_VERSION, NULL, 649251881Speter _("Server does not support edit pipelining")); 650251881Speter 651251881Speter /* In protocol version 2, we send back our protocol version, our 652251881Speter * capability list, and the URL, and subsequently there is an auth 653251881Speter * request. */ 654251881Speter /* Client-side capabilities list: */ 655251881Speter SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "n(wwwwww)cc(?c)", 656251881Speter (apr_uint64_t) 2, 657251881Speter SVN_RA_SVN_CAP_EDIT_PIPELINE, 658251881Speter SVN_RA_SVN_CAP_SVNDIFF1, 659251881Speter SVN_RA_SVN_CAP_ABSENT_ENTRIES, 660251881Speter SVN_RA_SVN_CAP_DEPTH, 661251881Speter SVN_RA_SVN_CAP_MERGEINFO, 662251881Speter SVN_RA_SVN_CAP_LOG_REVPROPS, 663251881Speter url, 664251881Speter SVN_RA_SVN__DEFAULT_USERAGENT, 665251881Speter client_string)); 666251881Speter SVN_ERR(handle_auth_request(sess, pool)); 667251881Speter 668251881Speter /* This is where the security layer would go into effect if we 669251881Speter * supported security layers, which is a ways off. */ 670251881Speter 671251881Speter /* Read the repository's uuid and root URL, and perhaps learn more 672251881Speter capabilities that weren't available before now. */ 673251881Speter SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "c?c?l", &conn->uuid, 674251881Speter &conn->repos_root, &repos_caplist)); 675251881Speter if (repos_caplist) 676251881Speter SVN_ERR(svn_ra_svn_set_capabilities(conn, repos_caplist)); 677251881Speter 678251881Speter if (conn->repos_root) 679251881Speter { 680251881Speter conn->repos_root = svn_uri_canonicalize(conn->repos_root, pool); 681251881Speter /* We should check that the returned string is a prefix of url, since 682251881Speter that's the API guarantee, but this isn't true for 1.0 servers. 683251881Speter Checking the length prevents client crashes. */ 684251881Speter if (strlen(conn->repos_root) > strlen(url)) 685251881Speter return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, 686251881Speter _("Impossibly long repository root from " 687251881Speter "server")); 688251881Speter } 689251881Speter 690251881Speter *sess_p = sess; 691251881Speter 692251881Speter return SVN_NO_ERROR; 693251881Speter} 694251881Speter 695251881Speter 696251881Speter#ifdef SVN_HAVE_SASL 697251881Speter#define RA_SVN_DESCRIPTION \ 698251881Speter N_("Module for accessing a repository using the svn network protocol.\n" \ 699251881Speter " - with Cyrus SASL authentication") 700251881Speter#else 701251881Speter#define RA_SVN_DESCRIPTION \ 702251881Speter N_("Module for accessing a repository using the svn network protocol.") 703251881Speter#endif 704251881Speter 705262253Speterstatic const char *ra_svn_get_description(apr_pool_t *pool) 706251881Speter{ 707251881Speter return _(RA_SVN_DESCRIPTION); 708251881Speter} 709251881Speter 710251881Speterstatic const char * const * 711251881Speterra_svn_get_schemes(apr_pool_t *pool) 712251881Speter{ 713251881Speter static const char *schemes[] = { "svn", NULL }; 714251881Speter 715251881Speter return schemes; 716251881Speter} 717251881Speter 718251881Speter 719251881Speter 720251881Speterstatic svn_error_t *ra_svn_open(svn_ra_session_t *session, 721251881Speter const char **corrected_url, 722251881Speter const char *url, 723251881Speter const svn_ra_callbacks2_t *callbacks, 724251881Speter void *callback_baton, 725251881Speter apr_hash_t *config, 726251881Speter apr_pool_t *pool) 727251881Speter{ 728251881Speter apr_pool_t *sess_pool = svn_pool_create(pool); 729251881Speter svn_ra_svn__session_baton_t *sess; 730251881Speter const char *tunnel, **tunnel_argv; 731251881Speter apr_uri_t uri; 732251881Speter svn_config_t *cfg, *cfg_client; 733251881Speter 734251881Speter /* We don't support server-prescribed redirections in ra-svn. */ 735251881Speter if (corrected_url) 736251881Speter *corrected_url = NULL; 737251881Speter 738251881Speter SVN_ERR(parse_url(url, &uri, sess_pool)); 739251881Speter 740251881Speter parse_tunnel(url, &tunnel, pool); 741251881Speter 742251881Speter if (tunnel) 743251881Speter SVN_ERR(find_tunnel_agent(tunnel, uri.hostinfo, &tunnel_argv, config, 744251881Speter pool)); 745251881Speter else 746251881Speter tunnel_argv = NULL; 747251881Speter 748251881Speter cfg_client = config 749251881Speter ? svn_hash_gets(config, SVN_CONFIG_CATEGORY_CONFIG) 750251881Speter : NULL; 751251881Speter cfg = config ? svn_hash_gets(config, SVN_CONFIG_CATEGORY_SERVERS) : NULL; 752251881Speter svn_auth_set_parameter(callbacks->auth_baton, 753251881Speter SVN_AUTH_PARAM_CONFIG_CATEGORY_CONFIG, cfg_client); 754251881Speter svn_auth_set_parameter(callbacks->auth_baton, 755251881Speter SVN_AUTH_PARAM_CONFIG_CATEGORY_SERVERS, cfg); 756251881Speter 757251881Speter /* We open the session in a subpool so we can get rid of it if we 758251881Speter reparent with a server that doesn't support reparenting. */ 759251881Speter SVN_ERR(open_session(&sess, url, &uri, tunnel_argv, 760251881Speter callbacks, callback_baton, sess_pool)); 761251881Speter session->priv = sess; 762251881Speter 763251881Speter return SVN_NO_ERROR; 764251881Speter} 765251881Speter 766251881Speterstatic svn_error_t *ra_svn_reparent(svn_ra_session_t *ra_session, 767251881Speter const char *url, 768251881Speter apr_pool_t *pool) 769251881Speter{ 770251881Speter svn_ra_svn__session_baton_t *sess = ra_session->priv; 771251881Speter svn_ra_svn_conn_t *conn = sess->conn; 772251881Speter svn_error_t *err; 773251881Speter apr_pool_t *sess_pool; 774251881Speter svn_ra_svn__session_baton_t *new_sess; 775251881Speter apr_uri_t uri; 776251881Speter 777251881Speter SVN_ERR(svn_ra_svn__write_cmd_reparent(conn, pool, url)); 778251881Speter err = handle_auth_request(sess, pool); 779251881Speter if (! err) 780251881Speter { 781251881Speter SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "")); 782251881Speter sess->url = apr_pstrdup(sess->pool, url); 783251881Speter return SVN_NO_ERROR; 784251881Speter } 785251881Speter else if (err->apr_err != SVN_ERR_RA_SVN_UNKNOWN_CMD) 786251881Speter return err; 787251881Speter 788251881Speter /* Servers before 1.4 doesn't support this command; try to reconnect 789251881Speter instead. */ 790251881Speter svn_error_clear(err); 791251881Speter /* Create a new subpool of the RA session pool. */ 792251881Speter sess_pool = svn_pool_create(ra_session->pool); 793251881Speter err = parse_url(url, &uri, sess_pool); 794251881Speter if (! err) 795251881Speter err = open_session(&new_sess, url, &uri, sess->tunnel_argv, 796251881Speter sess->callbacks, sess->callbacks_baton, sess_pool); 797251881Speter /* We destroy the new session pool on error, since it is allocated in 798251881Speter the main session pool. */ 799251881Speter if (err) 800251881Speter { 801251881Speter svn_pool_destroy(sess_pool); 802251881Speter return err; 803251881Speter } 804251881Speter 805251881Speter /* We have a new connection, assign it and destroy the old. */ 806251881Speter ra_session->priv = new_sess; 807251881Speter svn_pool_destroy(sess->pool); 808251881Speter 809251881Speter return SVN_NO_ERROR; 810251881Speter} 811251881Speter 812251881Speterstatic svn_error_t *ra_svn_get_session_url(svn_ra_session_t *session, 813251881Speter const char **url, apr_pool_t *pool) 814251881Speter{ 815251881Speter svn_ra_svn__session_baton_t *sess = session->priv; 816251881Speter *url = apr_pstrdup(pool, sess->url); 817251881Speter return SVN_NO_ERROR; 818251881Speter} 819251881Speter 820251881Speterstatic svn_error_t *ra_svn_get_latest_rev(svn_ra_session_t *session, 821251881Speter svn_revnum_t *rev, apr_pool_t *pool) 822251881Speter{ 823251881Speter svn_ra_svn__session_baton_t *sess_baton = session->priv; 824251881Speter svn_ra_svn_conn_t *conn = sess_baton->conn; 825251881Speter 826251881Speter SVN_ERR(svn_ra_svn__write_cmd_get_latest_rev(conn, pool)); 827251881Speter SVN_ERR(handle_auth_request(sess_baton, pool)); 828251881Speter SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "r", rev)); 829251881Speter return SVN_NO_ERROR; 830251881Speter} 831251881Speter 832251881Speterstatic svn_error_t *ra_svn_get_dated_rev(svn_ra_session_t *session, 833251881Speter svn_revnum_t *rev, apr_time_t tm, 834251881Speter apr_pool_t *pool) 835251881Speter{ 836251881Speter svn_ra_svn__session_baton_t *sess_baton = session->priv; 837251881Speter svn_ra_svn_conn_t *conn = sess_baton->conn; 838251881Speter 839251881Speter SVN_ERR(svn_ra_svn__write_cmd_get_dated_rev(conn, pool, tm)); 840251881Speter SVN_ERR(handle_auth_request(sess_baton, pool)); 841251881Speter SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "r", rev)); 842251881Speter return SVN_NO_ERROR; 843251881Speter} 844251881Speter 845251881Speter/* Forward declaration. */ 846251881Speterstatic svn_error_t *ra_svn_has_capability(svn_ra_session_t *session, 847251881Speter svn_boolean_t *has, 848251881Speter const char *capability, 849251881Speter apr_pool_t *pool); 850251881Speter 851251881Speterstatic svn_error_t *ra_svn_change_rev_prop(svn_ra_session_t *session, svn_revnum_t rev, 852251881Speter const char *name, 853251881Speter const svn_string_t *const *old_value_p, 854251881Speter const svn_string_t *value, 855251881Speter apr_pool_t *pool) 856251881Speter{ 857251881Speter svn_ra_svn__session_baton_t *sess_baton = session->priv; 858251881Speter svn_ra_svn_conn_t *conn = sess_baton->conn; 859251881Speter svn_boolean_t dont_care; 860251881Speter const svn_string_t *old_value; 861251881Speter svn_boolean_t has_atomic_revprops; 862251881Speter 863251881Speter SVN_ERR(ra_svn_has_capability(session, &has_atomic_revprops, 864251881Speter SVN_RA_SVN_CAP_ATOMIC_REVPROPS, 865251881Speter pool)); 866251881Speter 867251881Speter if (old_value_p) 868251881Speter { 869251881Speter /* How did you get past the same check in svn_ra_change_rev_prop2()? */ 870251881Speter SVN_ERR_ASSERT(has_atomic_revprops); 871251881Speter 872251881Speter dont_care = FALSE; 873251881Speter old_value = *old_value_p; 874251881Speter } 875251881Speter else 876251881Speter { 877251881Speter dont_care = TRUE; 878251881Speter old_value = NULL; 879251881Speter } 880251881Speter 881251881Speter if (has_atomic_revprops) 882251881Speter SVN_ERR(svn_ra_svn__write_cmd_change_rev_prop2(conn, pool, rev, name, 883251881Speter value, dont_care, 884251881Speter old_value)); 885251881Speter else 886251881Speter SVN_ERR(svn_ra_svn__write_cmd_change_rev_prop(conn, pool, rev, name, 887251881Speter value)); 888251881Speter 889251881Speter SVN_ERR(handle_auth_request(sess_baton, pool)); 890251881Speter SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "")); 891251881Speter return SVN_NO_ERROR; 892251881Speter} 893251881Speter 894251881Speterstatic svn_error_t *ra_svn_get_uuid(svn_ra_session_t *session, const char **uuid, 895251881Speter apr_pool_t *pool) 896251881Speter{ 897251881Speter svn_ra_svn__session_baton_t *sess_baton = session->priv; 898251881Speter svn_ra_svn_conn_t *conn = sess_baton->conn; 899251881Speter 900251881Speter *uuid = conn->uuid; 901251881Speter return SVN_NO_ERROR; 902251881Speter} 903251881Speter 904251881Speterstatic svn_error_t *ra_svn_get_repos_root(svn_ra_session_t *session, const char **url, 905251881Speter apr_pool_t *pool) 906251881Speter{ 907251881Speter svn_ra_svn__session_baton_t *sess_baton = session->priv; 908251881Speter svn_ra_svn_conn_t *conn = sess_baton->conn; 909251881Speter 910251881Speter if (!conn->repos_root) 911251881Speter return svn_error_create(SVN_ERR_RA_SVN_BAD_VERSION, NULL, 912251881Speter _("Server did not send repository root")); 913251881Speter *url = conn->repos_root; 914251881Speter return SVN_NO_ERROR; 915251881Speter} 916251881Speter 917251881Speterstatic svn_error_t *ra_svn_rev_proplist(svn_ra_session_t *session, svn_revnum_t rev, 918251881Speter apr_hash_t **props, apr_pool_t *pool) 919251881Speter{ 920251881Speter svn_ra_svn__session_baton_t *sess_baton = session->priv; 921251881Speter svn_ra_svn_conn_t *conn = sess_baton->conn; 922251881Speter apr_array_header_t *proplist; 923251881Speter 924251881Speter SVN_ERR(svn_ra_svn__write_cmd_rev_proplist(conn, pool, rev)); 925251881Speter SVN_ERR(handle_auth_request(sess_baton, pool)); 926251881Speter SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "l", &proplist)); 927251881Speter SVN_ERR(svn_ra_svn__parse_proplist(proplist, pool, props)); 928251881Speter return SVN_NO_ERROR; 929251881Speter} 930251881Speter 931251881Speterstatic svn_error_t *ra_svn_rev_prop(svn_ra_session_t *session, svn_revnum_t rev, 932251881Speter const char *name, 933251881Speter svn_string_t **value, apr_pool_t *pool) 934251881Speter{ 935251881Speter svn_ra_svn__session_baton_t *sess_baton = session->priv; 936251881Speter svn_ra_svn_conn_t *conn = sess_baton->conn; 937251881Speter 938251881Speter SVN_ERR(svn_ra_svn__write_cmd_rev_prop(conn, pool, rev, name)); 939251881Speter SVN_ERR(handle_auth_request(sess_baton, pool)); 940251881Speter SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "(?s)", value)); 941251881Speter return SVN_NO_ERROR; 942251881Speter} 943251881Speter 944251881Speterstatic svn_error_t *ra_svn_end_commit(void *baton) 945251881Speter{ 946251881Speter ra_svn_commit_callback_baton_t *ccb = baton; 947251881Speter svn_commit_info_t *commit_info = svn_create_commit_info(ccb->pool); 948251881Speter 949251881Speter SVN_ERR(handle_auth_request(ccb->sess_baton, ccb->pool)); 950251881Speter SVN_ERR(svn_ra_svn__read_tuple(ccb->sess_baton->conn, ccb->pool, 951251881Speter "r(?c)(?c)?(?c)", 952251881Speter &(commit_info->revision), 953251881Speter &(commit_info->date), 954251881Speter &(commit_info->author), 955251881Speter &(commit_info->post_commit_err))); 956251881Speter 957251881Speter if (ccb->callback) 958251881Speter SVN_ERR(ccb->callback(commit_info, ccb->callback_baton, ccb->pool)); 959251881Speter 960251881Speter return SVN_NO_ERROR; 961251881Speter} 962251881Speter 963251881Speterstatic svn_error_t *ra_svn_commit(svn_ra_session_t *session, 964251881Speter const svn_delta_editor_t **editor, 965251881Speter void **edit_baton, 966251881Speter apr_hash_t *revprop_table, 967251881Speter svn_commit_callback2_t callback, 968251881Speter void *callback_baton, 969251881Speter apr_hash_t *lock_tokens, 970251881Speter svn_boolean_t keep_locks, 971251881Speter apr_pool_t *pool) 972251881Speter{ 973251881Speter svn_ra_svn__session_baton_t *sess_baton = session->priv; 974251881Speter svn_ra_svn_conn_t *conn = sess_baton->conn; 975251881Speter ra_svn_commit_callback_baton_t *ccb; 976251881Speter apr_hash_index_t *hi; 977251881Speter apr_pool_t *iterpool; 978251881Speter const svn_string_t *log_msg = svn_hash_gets(revprop_table, 979251881Speter SVN_PROP_REVISION_LOG); 980251881Speter 981253734Speter if (log_msg == NULL && 982253734Speter ! svn_ra_svn_has_capability(conn, SVN_RA_SVN_CAP_COMMIT_REVPROPS)) 983253734Speter { 984253734Speter return svn_error_createf(SVN_ERR_BAD_PROPERTY_VALUE, NULL, 985253734Speter _("ra_svn does not support not specifying " 986253734Speter "a log message with pre-1.5 servers; " 987253734Speter "consider passing an empty one, or upgrading " 988253734Speter "the server")); 989253734Speter } 990253734Speter else if (log_msg == NULL) 991253734Speter /* 1.5+ server. Set LOG_MSG to something, since the 'logmsg' argument 992253734Speter to the 'commit' protocol command is non-optional; on the server side, 993253734Speter only REVPROP_TABLE will be used, and LOG_MSG will be ignored. The 994253734Speter "svn:log" member of REVPROP_TABLE table is NULL, therefore the commit 995253734Speter will have a NULL log message (not just "", really NULL). 996253734Speter 997253734Speter svnserve 1.5.x+ has always ignored LOG_MSG when REVPROP_TABLE was 998253734Speter present; this was elevated to a protocol promise in r1498550 (and 999253734Speter later documented in this comment) in order to fix the segmentation 1000253734Speter fault bug described in the log message of r1498550.*/ 1001253734Speter log_msg = svn_string_create("", pool); 1002253734Speter 1003251881Speter /* If we're sending revprops other than svn:log, make sure the server won't 1004251881Speter silently ignore them. */ 1005251881Speter if (apr_hash_count(revprop_table) > 1 && 1006251881Speter ! svn_ra_svn_has_capability(conn, SVN_RA_SVN_CAP_COMMIT_REVPROPS)) 1007251881Speter return svn_error_create(SVN_ERR_RA_NOT_IMPLEMENTED, NULL, 1008251881Speter _("Server doesn't support setting arbitrary " 1009251881Speter "revision properties during commit")); 1010251881Speter 1011251881Speter /* If the server supports ephemeral txnprops, add the one that 1012251881Speter reports the client's version level string. */ 1013251881Speter if (svn_ra_svn_has_capability(conn, SVN_RA_SVN_CAP_COMMIT_REVPROPS) && 1014251881Speter svn_ra_svn_has_capability(conn, SVN_RA_SVN_CAP_EPHEMERAL_TXNPROPS)) 1015251881Speter { 1016251881Speter svn_hash_sets(revprop_table, SVN_PROP_TXN_CLIENT_COMPAT_VERSION, 1017251881Speter svn_string_create(SVN_VER_NUMBER, pool)); 1018251881Speter svn_hash_sets(revprop_table, SVN_PROP_TXN_USER_AGENT, 1019251881Speter svn_string_create(sess_baton->useragent, pool)); 1020251881Speter } 1021251881Speter 1022251881Speter /* Tell the server we're starting the commit. 1023251881Speter Send log message here for backwards compatibility with servers 1024251881Speter before 1.5. */ 1025251881Speter SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w(c(!", "commit", 1026251881Speter log_msg->data)); 1027251881Speter if (lock_tokens) 1028251881Speter { 1029251881Speter iterpool = svn_pool_create(pool); 1030251881Speter for (hi = apr_hash_first(pool, lock_tokens); hi; hi = apr_hash_next(hi)) 1031251881Speter { 1032251881Speter const void *key; 1033251881Speter void *val; 1034251881Speter const char *path, *token; 1035251881Speter 1036251881Speter svn_pool_clear(iterpool); 1037251881Speter apr_hash_this(hi, &key, NULL, &val); 1038251881Speter path = key; 1039251881Speter token = val; 1040251881Speter SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "cc", path, token)); 1041251881Speter } 1042251881Speter svn_pool_destroy(iterpool); 1043251881Speter } 1044251881Speter SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!)b(!", keep_locks)); 1045251881Speter SVN_ERR(svn_ra_svn__write_proplist(conn, pool, revprop_table)); 1046251881Speter SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!))")); 1047251881Speter SVN_ERR(handle_auth_request(sess_baton, pool)); 1048251881Speter SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "")); 1049251881Speter 1050251881Speter /* Remember a few arguments for when the commit is over. */ 1051251881Speter ccb = apr_palloc(pool, sizeof(*ccb)); 1052251881Speter ccb->sess_baton = sess_baton; 1053251881Speter ccb->pool = pool; 1054251881Speter ccb->new_rev = NULL; 1055251881Speter ccb->callback = callback; 1056251881Speter ccb->callback_baton = callback_baton; 1057251881Speter 1058251881Speter /* Fetch an editor for the caller to drive. The editor will call 1059251881Speter * ra_svn_end_commit() upon close_edit(), at which point we'll fill 1060251881Speter * in the new_rev, committed_date, and committed_author values. */ 1061251881Speter svn_ra_svn_get_editor(editor, edit_baton, conn, pool, 1062251881Speter ra_svn_end_commit, ccb); 1063251881Speter return SVN_NO_ERROR; 1064251881Speter} 1065251881Speter 1066251881Speter/* Parse IPROPLIST, an array of svn_ra_svn_item_t structures, as a list of 1067251881Speter const char * repos relative paths and properties for those paths, storing 1068251881Speter the result as an array of svn_prop_inherited_item_t *items. */ 1069251881Speterstatic svn_error_t * 1070251881Speterparse_iproplist(apr_array_header_t **inherited_props, 1071251881Speter const apr_array_header_t *iproplist, 1072251881Speter svn_ra_session_t *session, 1073251881Speter apr_pool_t *result_pool, 1074251881Speter apr_pool_t *scratch_pool) 1075251881Speter 1076251881Speter{ 1077251881Speter int i; 1078251881Speter const char *repos_root_url; 1079251881Speter apr_pool_t *iterpool; 1080251881Speter 1081251881Speter if (iproplist == NULL) 1082251881Speter { 1083251881Speter /* If the server doesn't have the SVN_RA_CAPABILITY_INHERITED_PROPS 1084251881Speter capability we shouldn't be asking for inherited props, but if we 1085251881Speter did and the server sent back nothing then we'll want to handle 1086251881Speter that. */ 1087251881Speter *inherited_props = NULL; 1088251881Speter return SVN_NO_ERROR; 1089251881Speter } 1090251881Speter 1091251881Speter SVN_ERR(ra_svn_get_repos_root(session, &repos_root_url, scratch_pool)); 1092251881Speter 1093251881Speter *inherited_props = apr_array_make( 1094251881Speter result_pool, iproplist->nelts, sizeof(svn_prop_inherited_item_t *)); 1095251881Speter 1096251881Speter iterpool = svn_pool_create(scratch_pool); 1097251881Speter 1098251881Speter for (i = 0; i < iproplist->nelts; i++) 1099251881Speter { 1100251881Speter apr_array_header_t *iprop_list; 1101251881Speter char *parent_rel_path; 1102251881Speter apr_hash_t *iprops; 1103251881Speter apr_hash_index_t *hi; 1104251881Speter svn_prop_inherited_item_t *new_iprop = 1105251881Speter apr_palloc(result_pool, sizeof(*new_iprop)); 1106251881Speter svn_ra_svn_item_t *elt = &APR_ARRAY_IDX(iproplist, i, 1107251881Speter svn_ra_svn_item_t); 1108251881Speter if (elt->kind != SVN_RA_SVN_LIST) 1109251881Speter return svn_error_create( 1110251881Speter SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, 1111251881Speter _("Inherited proplist element not a list")); 1112251881Speter 1113251881Speter svn_pool_clear(iterpool); 1114251881Speter 1115251881Speter SVN_ERR(svn_ra_svn__parse_tuple(elt->u.list, iterpool, "cl", 1116251881Speter &parent_rel_path, &iprop_list)); 1117251881Speter SVN_ERR(svn_ra_svn__parse_proplist(iprop_list, iterpool, &iprops)); 1118251881Speter new_iprop->path_or_url = svn_path_url_add_component2(repos_root_url, 1119251881Speter parent_rel_path, 1120251881Speter result_pool); 1121251881Speter new_iprop->prop_hash = apr_hash_make(result_pool); 1122251881Speter for (hi = apr_hash_first(iterpool, iprops); 1123251881Speter hi; 1124251881Speter hi = apr_hash_next(hi)) 1125251881Speter { 1126251881Speter const char *name = svn__apr_hash_index_key(hi); 1127251881Speter svn_string_t *value = svn__apr_hash_index_val(hi); 1128251881Speter svn_hash_sets(new_iprop->prop_hash, 1129251881Speter apr_pstrdup(result_pool, name), 1130251881Speter svn_string_dup(value, result_pool)); 1131251881Speter } 1132251881Speter APR_ARRAY_PUSH(*inherited_props, svn_prop_inherited_item_t *) = 1133251881Speter new_iprop; 1134251881Speter } 1135251881Speter svn_pool_destroy(iterpool); 1136251881Speter return SVN_NO_ERROR; 1137251881Speter} 1138251881Speter 1139251881Speterstatic svn_error_t *ra_svn_get_file(svn_ra_session_t *session, const char *path, 1140251881Speter svn_revnum_t rev, svn_stream_t *stream, 1141251881Speter svn_revnum_t *fetched_rev, 1142251881Speter apr_hash_t **props, 1143251881Speter apr_pool_t *pool) 1144251881Speter{ 1145251881Speter svn_ra_svn__session_baton_t *sess_baton = session->priv; 1146251881Speter svn_ra_svn_conn_t *conn = sess_baton->conn; 1147251881Speter apr_array_header_t *proplist; 1148251881Speter const char *expected_digest; 1149251881Speter svn_checksum_t *expected_checksum = NULL; 1150251881Speter svn_checksum_ctx_t *checksum_ctx; 1151251881Speter apr_pool_t *iterpool; 1152251881Speter 1153251881Speter SVN_ERR(svn_ra_svn__write_cmd_get_file(conn, pool, path, rev, 1154251881Speter (props != NULL), (stream != NULL))); 1155251881Speter SVN_ERR(handle_auth_request(sess_baton, pool)); 1156251881Speter SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "(?c)rl", 1157251881Speter &expected_digest, 1158251881Speter &rev, &proplist)); 1159251881Speter 1160251881Speter if (fetched_rev) 1161251881Speter *fetched_rev = rev; 1162251881Speter if (props) 1163251881Speter SVN_ERR(svn_ra_svn__parse_proplist(proplist, pool, props)); 1164251881Speter 1165251881Speter /* We're done if the contents weren't wanted. */ 1166251881Speter if (!stream) 1167251881Speter return SVN_NO_ERROR; 1168251881Speter 1169251881Speter if (expected_digest) 1170251881Speter { 1171251881Speter SVN_ERR(svn_checksum_parse_hex(&expected_checksum, svn_checksum_md5, 1172251881Speter expected_digest, pool)); 1173251881Speter checksum_ctx = svn_checksum_ctx_create(svn_checksum_md5, pool); 1174251881Speter } 1175251881Speter 1176251881Speter /* Read the file's contents. */ 1177251881Speter iterpool = svn_pool_create(pool); 1178251881Speter while (1) 1179251881Speter { 1180251881Speter svn_ra_svn_item_t *item; 1181251881Speter 1182251881Speter svn_pool_clear(iterpool); 1183251881Speter SVN_ERR(svn_ra_svn__read_item(conn, iterpool, &item)); 1184251881Speter if (item->kind != SVN_RA_SVN_STRING) 1185251881Speter return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, 1186251881Speter _("Non-string as part of file contents")); 1187251881Speter if (item->u.string->len == 0) 1188251881Speter break; 1189251881Speter 1190251881Speter if (expected_checksum) 1191251881Speter SVN_ERR(svn_checksum_update(checksum_ctx, item->u.string->data, 1192251881Speter item->u.string->len)); 1193251881Speter 1194251881Speter SVN_ERR(svn_stream_write(stream, item->u.string->data, 1195251881Speter &item->u.string->len)); 1196251881Speter } 1197251881Speter svn_pool_destroy(iterpool); 1198251881Speter 1199251881Speter SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "")); 1200251881Speter 1201251881Speter if (expected_checksum) 1202251881Speter { 1203251881Speter svn_checksum_t *checksum; 1204251881Speter 1205251881Speter SVN_ERR(svn_checksum_final(&checksum, checksum_ctx, pool)); 1206251881Speter if (!svn_checksum_match(checksum, expected_checksum)) 1207251881Speter return svn_checksum_mismatch_err(expected_checksum, checksum, pool, 1208251881Speter _("Checksum mismatch for '%s'"), 1209251881Speter path); 1210251881Speter } 1211251881Speter 1212251881Speter return SVN_NO_ERROR; 1213251881Speter} 1214251881Speter 1215251881Speterstatic svn_error_t *ra_svn_get_dir(svn_ra_session_t *session, 1216251881Speter apr_hash_t **dirents, 1217251881Speter svn_revnum_t *fetched_rev, 1218251881Speter apr_hash_t **props, 1219251881Speter const char *path, 1220251881Speter svn_revnum_t rev, 1221251881Speter apr_uint32_t dirent_fields, 1222251881Speter apr_pool_t *pool) 1223251881Speter{ 1224251881Speter svn_ra_svn__session_baton_t *sess_baton = session->priv; 1225251881Speter svn_ra_svn_conn_t *conn = sess_baton->conn; 1226251881Speter apr_array_header_t *proplist, *dirlist; 1227251881Speter int i; 1228251881Speter 1229251881Speter SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w(c(?r)bb(!", "get-dir", path, 1230251881Speter rev, (props != NULL), (dirents != NULL))); 1231251881Speter if (dirent_fields & SVN_DIRENT_KIND) 1232251881Speter SVN_ERR(svn_ra_svn__write_word(conn, pool, SVN_RA_SVN_DIRENT_KIND)); 1233251881Speter if (dirent_fields & SVN_DIRENT_SIZE) 1234251881Speter SVN_ERR(svn_ra_svn__write_word(conn, pool, SVN_RA_SVN_DIRENT_SIZE)); 1235251881Speter if (dirent_fields & SVN_DIRENT_HAS_PROPS) 1236251881Speter SVN_ERR(svn_ra_svn__write_word(conn, pool, SVN_RA_SVN_DIRENT_HAS_PROPS)); 1237251881Speter if (dirent_fields & SVN_DIRENT_CREATED_REV) 1238251881Speter SVN_ERR(svn_ra_svn__write_word(conn, pool, SVN_RA_SVN_DIRENT_CREATED_REV)); 1239251881Speter if (dirent_fields & SVN_DIRENT_TIME) 1240251881Speter SVN_ERR(svn_ra_svn__write_word(conn, pool, SVN_RA_SVN_DIRENT_TIME)); 1241251881Speter if (dirent_fields & SVN_DIRENT_LAST_AUTHOR) 1242251881Speter SVN_ERR(svn_ra_svn__write_word(conn, pool, SVN_RA_SVN_DIRENT_LAST_AUTHOR)); 1243251881Speter 1244251881Speter SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!))")); 1245251881Speter 1246251881Speter SVN_ERR(handle_auth_request(sess_baton, pool)); 1247251881Speter SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "rll", &rev, &proplist, 1248251881Speter &dirlist)); 1249251881Speter 1250251881Speter if (fetched_rev) 1251251881Speter *fetched_rev = rev; 1252251881Speter if (props) 1253251881Speter SVN_ERR(svn_ra_svn__parse_proplist(proplist, pool, props)); 1254251881Speter 1255251881Speter /* We're done if dirents aren't wanted. */ 1256251881Speter if (!dirents) 1257251881Speter return SVN_NO_ERROR; 1258251881Speter 1259251881Speter /* Interpret the directory list. */ 1260251881Speter *dirents = apr_hash_make(pool); 1261251881Speter for (i = 0; i < dirlist->nelts; i++) 1262251881Speter { 1263251881Speter const char *name, *kind, *cdate, *cauthor; 1264251881Speter svn_boolean_t has_props; 1265251881Speter svn_dirent_t *dirent; 1266251881Speter apr_uint64_t size; 1267251881Speter svn_revnum_t crev; 1268251881Speter svn_ra_svn_item_t *elt = &APR_ARRAY_IDX(dirlist, i, svn_ra_svn_item_t); 1269251881Speter 1270251881Speter if (elt->kind != SVN_RA_SVN_LIST) 1271251881Speter return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, 1272251881Speter _("Dirlist element not a list")); 1273251881Speter SVN_ERR(svn_ra_svn__parse_tuple(elt->u.list, pool, "cwnbr(?c)(?c)", 1274251881Speter &name, &kind, &size, &has_props, 1275251881Speter &crev, &cdate, &cauthor)); 1276251881Speter name = svn_relpath_canonicalize(name, pool); 1277251881Speter dirent = svn_dirent_create(pool); 1278251881Speter dirent->kind = svn_node_kind_from_word(kind); 1279251881Speter dirent->size = size;/* FIXME: svn_filesize_t */ 1280251881Speter dirent->has_props = has_props; 1281251881Speter dirent->created_rev = crev; 1282251881Speter /* NOTE: the tuple's format string says CDATE may be NULL. But this 1283251881Speter function does not allow that. The server has always sent us some 1284251881Speter random date, however, so this just happens to work. But let's 1285251881Speter be wary of servers that are (improperly) fixed to send NULL. 1286251881Speter 1287251881Speter Note: they should NOT be "fixed" to send NULL, as that would break 1288251881Speter any older clients which received that NULL. But we may as well 1289251881Speter be defensive against a malicous server. */ 1290251881Speter if (cdate == NULL) 1291251881Speter dirent->time = 0; 1292251881Speter else 1293251881Speter SVN_ERR(svn_time_from_cstring(&dirent->time, cdate, pool)); 1294251881Speter dirent->last_author = cauthor; 1295251881Speter svn_hash_sets(*dirents, name, dirent); 1296251881Speter } 1297251881Speter 1298251881Speter return SVN_NO_ERROR; 1299251881Speter} 1300251881Speter 1301251881Speter/* Converts a apr_uint64_t with values TRUE, FALSE or 1302251881Speter SVN_RA_SVN_UNSPECIFIED_NUMBER as provided by svn_ra_svn__parse_tuple 1303251881Speter to a svn_tristate_t */ 1304251881Speterstatic svn_tristate_t 1305251881Speteroptbool_to_tristate(apr_uint64_t v) 1306251881Speter{ 1307251881Speter if (v == TRUE) /* not just non-zero but exactly equal to 'TRUE' */ 1308251881Speter return svn_tristate_true; 1309251881Speter if (v == FALSE) 1310251881Speter return svn_tristate_false; 1311251881Speter 1312251881Speter return svn_tristate_unknown; /* Contains SVN_RA_SVN_UNSPECIFIED_NUMBER */ 1313251881Speter} 1314251881Speter 1315251881Speter/* If REVISION is SVN_INVALID_REVNUM, no value is sent to the 1316251881Speter server, which defaults to youngest. */ 1317251881Speterstatic svn_error_t *ra_svn_get_mergeinfo(svn_ra_session_t *session, 1318251881Speter svn_mergeinfo_catalog_t *catalog, 1319251881Speter const apr_array_header_t *paths, 1320251881Speter svn_revnum_t revision, 1321251881Speter svn_mergeinfo_inheritance_t inherit, 1322251881Speter svn_boolean_t include_descendants, 1323251881Speter apr_pool_t *pool) 1324251881Speter{ 1325251881Speter svn_ra_svn__session_baton_t *sess_baton = session->priv; 1326251881Speter svn_ra_svn_conn_t *conn = sess_baton->conn; 1327251881Speter int i; 1328251881Speter apr_array_header_t *mergeinfo_tuple; 1329251881Speter svn_ra_svn_item_t *elt; 1330251881Speter const char *path; 1331251881Speter 1332251881Speter SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w((!", "get-mergeinfo")); 1333251881Speter for (i = 0; i < paths->nelts; i++) 1334251881Speter { 1335251881Speter path = APR_ARRAY_IDX(paths, i, const char *); 1336251881Speter SVN_ERR(svn_ra_svn__write_cstring(conn, pool, path)); 1337251881Speter } 1338251881Speter SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!)(?r)wb)", revision, 1339251881Speter svn_inheritance_to_word(inherit), 1340251881Speter include_descendants)); 1341251881Speter 1342251881Speter SVN_ERR(handle_auth_request(sess_baton, pool)); 1343251881Speter SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "l", &mergeinfo_tuple)); 1344251881Speter 1345251881Speter *catalog = NULL; 1346251881Speter if (mergeinfo_tuple->nelts > 0) 1347251881Speter { 1348251881Speter *catalog = apr_hash_make(pool); 1349251881Speter for (i = 0; i < mergeinfo_tuple->nelts; i++) 1350251881Speter { 1351251881Speter svn_mergeinfo_t for_path; 1352251881Speter const char *to_parse; 1353251881Speter 1354251881Speter elt = &((svn_ra_svn_item_t *) mergeinfo_tuple->elts)[i]; 1355251881Speter if (elt->kind != SVN_RA_SVN_LIST) 1356251881Speter return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, 1357251881Speter _("Mergeinfo element is not a list")); 1358251881Speter SVN_ERR(svn_ra_svn__parse_tuple(elt->u.list, pool, "cc", 1359251881Speter &path, &to_parse)); 1360251881Speter SVN_ERR(svn_mergeinfo_parse(&for_path, to_parse, pool)); 1361251881Speter /* Correct for naughty servers that send "relative" paths 1362251881Speter with leading slashes! */ 1363251881Speter svn_hash_sets(*catalog, path[0] == '/' ? path + 1 :path, for_path); 1364251881Speter } 1365251881Speter } 1366251881Speter 1367251881Speter return SVN_NO_ERROR; 1368251881Speter} 1369251881Speter 1370251881Speterstatic svn_error_t *ra_svn_update(svn_ra_session_t *session, 1371251881Speter const svn_ra_reporter3_t **reporter, 1372251881Speter void **report_baton, svn_revnum_t rev, 1373251881Speter const char *target, svn_depth_t depth, 1374251881Speter svn_boolean_t send_copyfrom_args, 1375251881Speter svn_boolean_t ignore_ancestry, 1376251881Speter const svn_delta_editor_t *update_editor, 1377251881Speter void *update_baton, 1378251881Speter apr_pool_t *pool, 1379251881Speter apr_pool_t *scratch_pool) 1380251881Speter{ 1381251881Speter svn_ra_svn__session_baton_t *sess_baton = session->priv; 1382251881Speter svn_ra_svn_conn_t *conn = sess_baton->conn; 1383251881Speter svn_boolean_t recurse = DEPTH_TO_RECURSE(depth); 1384251881Speter 1385251881Speter /* Tell the server we want to start an update. */ 1386251881Speter SVN_ERR(svn_ra_svn__write_cmd_update(conn, pool, rev, target, recurse, 1387251881Speter depth, send_copyfrom_args, 1388251881Speter ignore_ancestry)); 1389251881Speter SVN_ERR(handle_auth_request(sess_baton, pool)); 1390251881Speter 1391251881Speter /* Fetch a reporter for the caller to drive. The reporter will drive 1392251881Speter * update_editor upon finish_report(). */ 1393251881Speter SVN_ERR(ra_svn_get_reporter(sess_baton, pool, update_editor, update_baton, 1394251881Speter target, depth, reporter, report_baton)); 1395251881Speter return SVN_NO_ERROR; 1396251881Speter} 1397251881Speter 1398251881Speterstatic svn_error_t * 1399251881Speterra_svn_switch(svn_ra_session_t *session, 1400251881Speter const svn_ra_reporter3_t **reporter, 1401251881Speter void **report_baton, svn_revnum_t rev, 1402251881Speter const char *target, svn_depth_t depth, 1403251881Speter const char *switch_url, 1404251881Speter svn_boolean_t send_copyfrom_args, 1405251881Speter svn_boolean_t ignore_ancestry, 1406251881Speter const svn_delta_editor_t *update_editor, 1407251881Speter void *update_baton, 1408251881Speter apr_pool_t *result_pool, 1409251881Speter apr_pool_t *scratch_pool) 1410251881Speter{ 1411251881Speter apr_pool_t *pool = result_pool; 1412251881Speter svn_ra_svn__session_baton_t *sess_baton = session->priv; 1413251881Speter svn_ra_svn_conn_t *conn = sess_baton->conn; 1414251881Speter svn_boolean_t recurse = DEPTH_TO_RECURSE(depth); 1415251881Speter 1416251881Speter /* Tell the server we want to start a switch. */ 1417251881Speter SVN_ERR(svn_ra_svn__write_cmd_switch(conn, pool, rev, target, recurse, 1418251881Speter switch_url, depth, 1419251881Speter send_copyfrom_args, ignore_ancestry)); 1420251881Speter SVN_ERR(handle_auth_request(sess_baton, pool)); 1421251881Speter 1422251881Speter /* Fetch a reporter for the caller to drive. The reporter will drive 1423251881Speter * update_editor upon finish_report(). */ 1424251881Speter SVN_ERR(ra_svn_get_reporter(sess_baton, pool, update_editor, update_baton, 1425251881Speter target, depth, reporter, report_baton)); 1426251881Speter return SVN_NO_ERROR; 1427251881Speter} 1428251881Speter 1429251881Speterstatic svn_error_t *ra_svn_status(svn_ra_session_t *session, 1430251881Speter const svn_ra_reporter3_t **reporter, 1431251881Speter void **report_baton, 1432251881Speter const char *target, svn_revnum_t rev, 1433251881Speter svn_depth_t depth, 1434251881Speter const svn_delta_editor_t *status_editor, 1435251881Speter void *status_baton, apr_pool_t *pool) 1436251881Speter{ 1437251881Speter svn_ra_svn__session_baton_t *sess_baton = session->priv; 1438251881Speter svn_ra_svn_conn_t *conn = sess_baton->conn; 1439251881Speter svn_boolean_t recurse = DEPTH_TO_RECURSE(depth); 1440251881Speter 1441251881Speter /* Tell the server we want to start a status operation. */ 1442251881Speter SVN_ERR(svn_ra_svn__write_cmd_status(conn, pool, target, recurse, rev, 1443251881Speter depth)); 1444251881Speter SVN_ERR(handle_auth_request(sess_baton, pool)); 1445251881Speter 1446251881Speter /* Fetch a reporter for the caller to drive. The reporter will drive 1447251881Speter * status_editor upon finish_report(). */ 1448251881Speter SVN_ERR(ra_svn_get_reporter(sess_baton, pool, status_editor, status_baton, 1449251881Speter target, depth, reporter, report_baton)); 1450251881Speter return SVN_NO_ERROR; 1451251881Speter} 1452251881Speter 1453251881Speterstatic svn_error_t *ra_svn_diff(svn_ra_session_t *session, 1454251881Speter const svn_ra_reporter3_t **reporter, 1455251881Speter void **report_baton, 1456251881Speter svn_revnum_t rev, const char *target, 1457251881Speter svn_depth_t depth, 1458251881Speter svn_boolean_t ignore_ancestry, 1459251881Speter svn_boolean_t text_deltas, 1460251881Speter const char *versus_url, 1461251881Speter const svn_delta_editor_t *diff_editor, 1462251881Speter void *diff_baton, apr_pool_t *pool) 1463251881Speter{ 1464251881Speter svn_ra_svn__session_baton_t *sess_baton = session->priv; 1465251881Speter svn_ra_svn_conn_t *conn = sess_baton->conn; 1466251881Speter svn_boolean_t recurse = DEPTH_TO_RECURSE(depth); 1467251881Speter 1468251881Speter /* Tell the server we want to start a diff. */ 1469251881Speter SVN_ERR(svn_ra_svn__write_cmd_diff(conn, pool, rev, target, recurse, 1470251881Speter ignore_ancestry, versus_url, 1471251881Speter text_deltas, depth)); 1472251881Speter SVN_ERR(handle_auth_request(sess_baton, pool)); 1473251881Speter 1474251881Speter /* Fetch a reporter for the caller to drive. The reporter will drive 1475251881Speter * diff_editor upon finish_report(). */ 1476251881Speter SVN_ERR(ra_svn_get_reporter(sess_baton, pool, diff_editor, diff_baton, 1477251881Speter target, depth, reporter, report_baton)); 1478251881Speter return SVN_NO_ERROR; 1479251881Speter} 1480251881Speter 1481251881Speter 1482253734Speterstatic svn_error_t * 1483253734Speterperform_ra_svn_log(svn_error_t **outer_error, 1484253734Speter svn_ra_session_t *session, 1485253734Speter const apr_array_header_t *paths, 1486253734Speter svn_revnum_t start, svn_revnum_t end, 1487253734Speter int limit, 1488253734Speter svn_boolean_t discover_changed_paths, 1489253734Speter svn_boolean_t strict_node_history, 1490253734Speter svn_boolean_t include_merged_revisions, 1491253734Speter const apr_array_header_t *revprops, 1492253734Speter svn_log_entry_receiver_t receiver, 1493253734Speter void *receiver_baton, 1494253734Speter apr_pool_t *pool) 1495251881Speter{ 1496251881Speter svn_ra_svn__session_baton_t *sess_baton = session->priv; 1497251881Speter svn_ra_svn_conn_t *conn = sess_baton->conn; 1498251881Speter apr_pool_t *iterpool; 1499251881Speter int i; 1500251881Speter int nest_level = 0; 1501251881Speter const char *path; 1502251881Speter char *name; 1503251881Speter svn_boolean_t want_custom_revprops; 1504251881Speter 1505251881Speter SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w((!", "log")); 1506251881Speter if (paths) 1507251881Speter { 1508251881Speter for (i = 0; i < paths->nelts; i++) 1509251881Speter { 1510251881Speter path = APR_ARRAY_IDX(paths, i, const char *); 1511251881Speter SVN_ERR(svn_ra_svn__write_cstring(conn, pool, path)); 1512251881Speter } 1513251881Speter } 1514251881Speter SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!)(?r)(?r)bbnb!", start, end, 1515251881Speter discover_changed_paths, strict_node_history, 1516251881Speter (apr_uint64_t) limit, 1517251881Speter include_merged_revisions)); 1518251881Speter if (revprops) 1519251881Speter { 1520251881Speter want_custom_revprops = FALSE; 1521251881Speter SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!w(!", "revprops")); 1522251881Speter for (i = 0; i < revprops->nelts; i++) 1523251881Speter { 1524251881Speter name = APR_ARRAY_IDX(revprops, i, char *); 1525251881Speter SVN_ERR(svn_ra_svn__write_cstring(conn, pool, name)); 1526251881Speter if (!want_custom_revprops 1527251881Speter && strcmp(name, SVN_PROP_REVISION_AUTHOR) != 0 1528251881Speter && strcmp(name, SVN_PROP_REVISION_DATE) != 0 1529251881Speter && strcmp(name, SVN_PROP_REVISION_LOG) != 0) 1530251881Speter want_custom_revprops = TRUE; 1531251881Speter } 1532251881Speter SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!))")); 1533251881Speter } 1534251881Speter else 1535251881Speter { 1536251881Speter SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!w())", "all-revprops")); 1537251881Speter want_custom_revprops = TRUE; 1538251881Speter } 1539251881Speter 1540251881Speter SVN_ERR(handle_auth_request(sess_baton, pool)); 1541251881Speter 1542251881Speter /* Read the log messages. */ 1543251881Speter iterpool = svn_pool_create(pool); 1544251881Speter while (1) 1545251881Speter { 1546251881Speter apr_uint64_t has_children_param, invalid_revnum_param; 1547251881Speter apr_uint64_t has_subtractive_merge_param; 1548251881Speter svn_string_t *author, *date, *message; 1549251881Speter apr_array_header_t *cplist, *rplist; 1550251881Speter svn_log_entry_t *log_entry; 1551251881Speter svn_boolean_t has_children; 1552251881Speter svn_boolean_t subtractive_merge = FALSE; 1553251881Speter apr_uint64_t revprop_count; 1554251881Speter svn_ra_svn_item_t *item; 1555251881Speter apr_hash_t *cphash; 1556251881Speter svn_revnum_t rev; 1557251881Speter int nreceived; 1558251881Speter 1559251881Speter svn_pool_clear(iterpool); 1560251881Speter SVN_ERR(svn_ra_svn__read_item(conn, iterpool, &item)); 1561251881Speter if (item->kind == SVN_RA_SVN_WORD && strcmp(item->u.word, "done") == 0) 1562251881Speter break; 1563251881Speter if (item->kind != SVN_RA_SVN_LIST) 1564251881Speter return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, 1565251881Speter _("Log entry not a list")); 1566251881Speter SVN_ERR(svn_ra_svn__parse_tuple(item->u.list, iterpool, 1567251881Speter "lr(?s)(?s)(?s)?BBnl?B", 1568251881Speter &cplist, &rev, &author, &date, 1569251881Speter &message, &has_children_param, 1570251881Speter &invalid_revnum_param, 1571251881Speter &revprop_count, &rplist, 1572251881Speter &has_subtractive_merge_param)); 1573251881Speter if (want_custom_revprops && rplist == NULL) 1574251881Speter { 1575251881Speter /* Caller asked for custom revprops, but server is too old. */ 1576251881Speter return svn_error_create(SVN_ERR_RA_NOT_IMPLEMENTED, NULL, 1577251881Speter _("Server does not support custom revprops" 1578251881Speter " via log")); 1579251881Speter } 1580251881Speter 1581251881Speter if (has_children_param == SVN_RA_SVN_UNSPECIFIED_NUMBER) 1582251881Speter has_children = FALSE; 1583251881Speter else 1584251881Speter has_children = (svn_boolean_t) has_children_param; 1585251881Speter 1586251881Speter if (has_subtractive_merge_param == SVN_RA_SVN_UNSPECIFIED_NUMBER) 1587251881Speter subtractive_merge = FALSE; 1588251881Speter else 1589251881Speter subtractive_merge = (svn_boolean_t) has_subtractive_merge_param; 1590251881Speter 1591251881Speter /* Because the svn protocol won't let us send an invalid revnum, we have 1592251881Speter to recover that fact using the extra parameter. */ 1593251881Speter if (invalid_revnum_param != SVN_RA_SVN_UNSPECIFIED_NUMBER 1594251881Speter && invalid_revnum_param) 1595251881Speter rev = SVN_INVALID_REVNUM; 1596251881Speter 1597251881Speter if (cplist->nelts > 0) 1598251881Speter { 1599251881Speter /* Interpret the changed-paths list. */ 1600251881Speter cphash = apr_hash_make(iterpool); 1601251881Speter for (i = 0; i < cplist->nelts; i++) 1602251881Speter { 1603251881Speter svn_log_changed_path2_t *change; 1604251881Speter const char *copy_path, *action, *cpath, *kind_str; 1605251881Speter apr_uint64_t text_mods, prop_mods; 1606251881Speter svn_revnum_t copy_rev; 1607251881Speter svn_ra_svn_item_t *elt = &APR_ARRAY_IDX(cplist, i, 1608251881Speter svn_ra_svn_item_t); 1609251881Speter 1610251881Speter if (elt->kind != SVN_RA_SVN_LIST) 1611251881Speter return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, 1612251881Speter _("Changed-path entry not a list")); 1613251881Speter SVN_ERR(svn_ra_svn__parse_tuple(elt->u.list, iterpool, 1614251881Speter "cw(?cr)?(?c?BB)", 1615251881Speter &cpath, &action, ©_path, 1616251881Speter ©_rev, &kind_str, 1617251881Speter &text_mods, &prop_mods)); 1618251881Speter cpath = svn_fspath__canonicalize(cpath, iterpool); 1619251881Speter if (copy_path) 1620251881Speter copy_path = svn_fspath__canonicalize(copy_path, iterpool); 1621251881Speter change = svn_log_changed_path2_create(iterpool); 1622251881Speter change->action = *action; 1623251881Speter change->copyfrom_path = copy_path; 1624251881Speter change->copyfrom_rev = copy_rev; 1625251881Speter change->node_kind = svn_node_kind_from_word(kind_str); 1626251881Speter change->text_modified = optbool_to_tristate(text_mods); 1627251881Speter change->props_modified = optbool_to_tristate(prop_mods); 1628251881Speter svn_hash_sets(cphash, cpath, change); 1629251881Speter } 1630251881Speter } 1631251881Speter else 1632251881Speter cphash = NULL; 1633251881Speter 1634251881Speter nreceived = 0; 1635253734Speter if (! (limit && (nest_level == 0) && (++nreceived > limit)) 1636253734Speter && ! *outer_error) 1637251881Speter { 1638253734Speter svn_error_t *err; 1639251881Speter log_entry = svn_log_entry_create(iterpool); 1640251881Speter 1641251881Speter log_entry->changed_paths = cphash; 1642251881Speter log_entry->changed_paths2 = cphash; 1643251881Speter log_entry->revision = rev; 1644251881Speter log_entry->has_children = has_children; 1645251881Speter log_entry->subtractive_merge = subtractive_merge; 1646251881Speter if (rplist) 1647251881Speter SVN_ERR(svn_ra_svn__parse_proplist(rplist, iterpool, 1648251881Speter &log_entry->revprops)); 1649251881Speter if (log_entry->revprops == NULL) 1650251881Speter log_entry->revprops = apr_hash_make(iterpool); 1651251881Speter if (revprops == NULL) 1652251881Speter { 1653251881Speter /* Caller requested all revprops; set author/date/log. */ 1654251881Speter if (author) 1655251881Speter svn_hash_sets(log_entry->revprops, SVN_PROP_REVISION_AUTHOR, 1656251881Speter author); 1657251881Speter if (date) 1658251881Speter svn_hash_sets(log_entry->revprops, SVN_PROP_REVISION_DATE, 1659251881Speter date); 1660251881Speter if (message) 1661251881Speter svn_hash_sets(log_entry->revprops, SVN_PROP_REVISION_LOG, 1662251881Speter message); 1663251881Speter } 1664251881Speter else 1665251881Speter { 1666251881Speter /* Caller requested some; maybe set author/date/log. */ 1667251881Speter for (i = 0; i < revprops->nelts; i++) 1668251881Speter { 1669251881Speter name = APR_ARRAY_IDX(revprops, i, char *); 1670251881Speter if (author && strcmp(name, SVN_PROP_REVISION_AUTHOR) == 0) 1671251881Speter svn_hash_sets(log_entry->revprops, 1672251881Speter SVN_PROP_REVISION_AUTHOR, author); 1673251881Speter if (date && strcmp(name, SVN_PROP_REVISION_DATE) == 0) 1674251881Speter svn_hash_sets(log_entry->revprops, 1675251881Speter SVN_PROP_REVISION_DATE, date); 1676251881Speter if (message && strcmp(name, SVN_PROP_REVISION_LOG) == 0) 1677251881Speter svn_hash_sets(log_entry->revprops, 1678251881Speter SVN_PROP_REVISION_LOG, message); 1679251881Speter } 1680251881Speter } 1681253734Speter err = receiver(receiver_baton, log_entry, iterpool); 1682253734Speter if (err && err->apr_err == SVN_ERR_CEASE_INVOCATION) 1683253734Speter { 1684253734Speter *outer_error = svn_error_trace( 1685253734Speter svn_error_compose_create(*outer_error, err)); 1686253734Speter } 1687253734Speter else 1688253734Speter SVN_ERR(err); 1689253734Speter 1690251881Speter if (log_entry->has_children) 1691251881Speter { 1692251881Speter nest_level++; 1693251881Speter } 1694251881Speter if (! SVN_IS_VALID_REVNUM(log_entry->revision)) 1695251881Speter { 1696251881Speter SVN_ERR_ASSERT(nest_level); 1697251881Speter nest_level--; 1698251881Speter } 1699251881Speter } 1700251881Speter } 1701251881Speter svn_pool_destroy(iterpool); 1702251881Speter 1703251881Speter /* Read the response. */ 1704253734Speter return svn_error_trace(svn_ra_svn__read_cmd_response(conn, pool, "")); 1705251881Speter} 1706251881Speter 1707253734Speterstatic svn_error_t * 1708253734Speterra_svn_log(svn_ra_session_t *session, 1709253734Speter const apr_array_header_t *paths, 1710253734Speter svn_revnum_t start, svn_revnum_t end, 1711253734Speter int limit, 1712253734Speter svn_boolean_t discover_changed_paths, 1713253734Speter svn_boolean_t strict_node_history, 1714253734Speter svn_boolean_t include_merged_revisions, 1715253734Speter const apr_array_header_t *revprops, 1716253734Speter svn_log_entry_receiver_t receiver, 1717253734Speter void *receiver_baton, apr_pool_t *pool) 1718253734Speter{ 1719253734Speter svn_error_t *outer_error = NULL; 1720253734Speter svn_error_t *err; 1721251881Speter 1722253734Speter err = svn_error_trace(perform_ra_svn_log(&outer_error, 1723253734Speter session, paths, 1724253734Speter start, end, 1725253734Speter limit, 1726253734Speter discover_changed_paths, 1727253734Speter strict_node_history, 1728253734Speter include_merged_revisions, 1729253734Speter revprops, 1730253734Speter receiver, receiver_baton, 1731253734Speter pool)); 1732253734Speter return svn_error_trace( 1733253734Speter svn_error_compose_create(outer_error, 1734253734Speter err)); 1735253734Speter} 1736253734Speter 1737253734Speter 1738253734Speter 1739251881Speterstatic svn_error_t *ra_svn_check_path(svn_ra_session_t *session, 1740251881Speter const char *path, svn_revnum_t rev, 1741251881Speter svn_node_kind_t *kind, apr_pool_t *pool) 1742251881Speter{ 1743251881Speter svn_ra_svn__session_baton_t *sess_baton = session->priv; 1744251881Speter svn_ra_svn_conn_t *conn = sess_baton->conn; 1745251881Speter const char *kind_word; 1746251881Speter 1747251881Speter SVN_ERR(svn_ra_svn__write_cmd_check_path(conn, pool, path, rev)); 1748251881Speter SVN_ERR(handle_auth_request(sess_baton, pool)); 1749251881Speter SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "w", &kind_word)); 1750251881Speter *kind = svn_node_kind_from_word(kind_word); 1751251881Speter return SVN_NO_ERROR; 1752251881Speter} 1753251881Speter 1754251881Speter 1755251881Speter/* If ERR is a command not supported error, wrap it in a 1756251881Speter SVN_ERR_RA_NOT_IMPLEMENTED with error message MSG. Else, return err. */ 1757251881Speterstatic svn_error_t *handle_unsupported_cmd(svn_error_t *err, 1758251881Speter const char *msg) 1759251881Speter{ 1760251881Speter if (err && err->apr_err == SVN_ERR_RA_SVN_UNKNOWN_CMD) 1761251881Speter return svn_error_create(SVN_ERR_RA_NOT_IMPLEMENTED, err, 1762251881Speter _(msg)); 1763251881Speter return err; 1764251881Speter} 1765251881Speter 1766251881Speter 1767251881Speterstatic svn_error_t *ra_svn_stat(svn_ra_session_t *session, 1768251881Speter const char *path, svn_revnum_t rev, 1769251881Speter svn_dirent_t **dirent, apr_pool_t *pool) 1770251881Speter{ 1771251881Speter svn_ra_svn__session_baton_t *sess_baton = session->priv; 1772251881Speter svn_ra_svn_conn_t *conn = sess_baton->conn; 1773251881Speter apr_array_header_t *list = NULL; 1774251881Speter svn_dirent_t *the_dirent; 1775251881Speter 1776251881Speter SVN_ERR(svn_ra_svn__write_cmd_stat(conn, pool, path, rev)); 1777251881Speter SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess_baton, pool), 1778251881Speter N_("'stat' not implemented"))); 1779251881Speter SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "(?l)", &list)); 1780251881Speter 1781251881Speter if (! list) 1782251881Speter { 1783251881Speter *dirent = NULL; 1784251881Speter } 1785251881Speter else 1786251881Speter { 1787251881Speter const char *kind, *cdate, *cauthor; 1788251881Speter svn_boolean_t has_props; 1789251881Speter svn_revnum_t crev; 1790251881Speter apr_uint64_t size; 1791251881Speter 1792251881Speter SVN_ERR(svn_ra_svn__parse_tuple(list, pool, "wnbr(?c)(?c)", 1793251881Speter &kind, &size, &has_props, 1794251881Speter &crev, &cdate, &cauthor)); 1795251881Speter 1796251881Speter the_dirent = svn_dirent_create(pool); 1797251881Speter the_dirent->kind = svn_node_kind_from_word(kind); 1798251881Speter the_dirent->size = size;/* FIXME: svn_filesize_t */ 1799251881Speter the_dirent->has_props = has_props; 1800251881Speter the_dirent->created_rev = crev; 1801251881Speter SVN_ERR(svn_time_from_cstring(&the_dirent->time, cdate, pool)); 1802251881Speter the_dirent->last_author = cauthor; 1803251881Speter 1804251881Speter *dirent = the_dirent; 1805251881Speter } 1806251881Speter 1807251881Speter return SVN_NO_ERROR; 1808251881Speter} 1809251881Speter 1810251881Speter 1811251881Speterstatic svn_error_t *ra_svn_get_locations(svn_ra_session_t *session, 1812251881Speter apr_hash_t **locations, 1813251881Speter const char *path, 1814251881Speter svn_revnum_t peg_revision, 1815251881Speter const apr_array_header_t *location_revisions, 1816251881Speter apr_pool_t *pool) 1817251881Speter{ 1818251881Speter svn_ra_svn__session_baton_t *sess_baton = session->priv; 1819251881Speter svn_ra_svn_conn_t *conn = sess_baton->conn; 1820251881Speter svn_revnum_t revision; 1821251881Speter svn_boolean_t is_done; 1822251881Speter int i; 1823251881Speter 1824251881Speter /* Transmit the parameters. */ 1825251881Speter SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w(cr(!", 1826251881Speter "get-locations", path, peg_revision)); 1827251881Speter for (i = 0; i < location_revisions->nelts; i++) 1828251881Speter { 1829251881Speter revision = APR_ARRAY_IDX(location_revisions, i, svn_revnum_t); 1830251881Speter SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!r!", revision)); 1831251881Speter } 1832251881Speter 1833251881Speter SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!))")); 1834251881Speter 1835251881Speter /* Servers before 1.1 don't support this command. Check for this here. */ 1836251881Speter SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess_baton, pool), 1837251881Speter N_("'get-locations' not implemented"))); 1838251881Speter 1839251881Speter /* Read the hash items. */ 1840251881Speter is_done = FALSE; 1841251881Speter *locations = apr_hash_make(pool); 1842251881Speter while (!is_done) 1843251881Speter { 1844251881Speter svn_ra_svn_item_t *item; 1845251881Speter const char *ret_path; 1846251881Speter 1847251881Speter SVN_ERR(svn_ra_svn__read_item(conn, pool, &item)); 1848251881Speter if (item->kind == SVN_RA_SVN_WORD && strcmp(item->u.word, "done") == 0) 1849251881Speter is_done = 1; 1850251881Speter else if (item->kind != SVN_RA_SVN_LIST) 1851251881Speter return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, 1852251881Speter _("Location entry not a list")); 1853251881Speter else 1854251881Speter { 1855251881Speter SVN_ERR(svn_ra_svn__parse_tuple(item->u.list, pool, "rc", 1856251881Speter &revision, &ret_path)); 1857251881Speter ret_path = svn_fspath__canonicalize(ret_path, pool); 1858251881Speter apr_hash_set(*locations, apr_pmemdup(pool, &revision, 1859251881Speter sizeof(revision)), 1860251881Speter sizeof(revision), ret_path); 1861251881Speter } 1862251881Speter } 1863251881Speter 1864251881Speter /* Read the response. This is so the server would have a chance to 1865251881Speter * report an error. */ 1866251881Speter return svn_ra_svn__read_cmd_response(conn, pool, ""); 1867251881Speter} 1868251881Speter 1869251881Speterstatic svn_error_t * 1870251881Speterra_svn_get_location_segments(svn_ra_session_t *session, 1871251881Speter const char *path, 1872251881Speter svn_revnum_t peg_revision, 1873251881Speter svn_revnum_t start_rev, 1874251881Speter svn_revnum_t end_rev, 1875251881Speter svn_location_segment_receiver_t receiver, 1876251881Speter void *receiver_baton, 1877251881Speter apr_pool_t *pool) 1878251881Speter{ 1879251881Speter svn_ra_svn__session_baton_t *sess_baton = session->priv; 1880251881Speter svn_ra_svn_conn_t *conn = sess_baton->conn; 1881251881Speter svn_boolean_t is_done; 1882251881Speter apr_pool_t *iterpool = svn_pool_create(pool); 1883251881Speter 1884251881Speter /* Transmit the parameters. */ 1885251881Speter SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w(c(?r)(?r)(?r))", 1886251881Speter "get-location-segments", 1887251881Speter path, peg_revision, start_rev, end_rev)); 1888251881Speter 1889251881Speter /* Servers before 1.5 don't support this command. Check for this here. */ 1890251881Speter SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess_baton, pool), 1891251881Speter N_("'get-location-segments'" 1892251881Speter " not implemented"))); 1893251881Speter 1894251881Speter /* Parse the response. */ 1895251881Speter is_done = FALSE; 1896251881Speter while (!is_done) 1897251881Speter { 1898251881Speter svn_revnum_t range_start, range_end; 1899251881Speter svn_ra_svn_item_t *item; 1900251881Speter const char *ret_path; 1901251881Speter 1902251881Speter svn_pool_clear(iterpool); 1903251881Speter SVN_ERR(svn_ra_svn__read_item(conn, iterpool, &item)); 1904251881Speter if (item->kind == SVN_RA_SVN_WORD && strcmp(item->u.word, "done") == 0) 1905251881Speter is_done = 1; 1906251881Speter else if (item->kind != SVN_RA_SVN_LIST) 1907251881Speter return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, 1908251881Speter _("Location segment entry not a list")); 1909251881Speter else 1910251881Speter { 1911251881Speter svn_location_segment_t *segment = apr_pcalloc(iterpool, 1912251881Speter sizeof(*segment)); 1913251881Speter SVN_ERR(svn_ra_svn__parse_tuple(item->u.list, iterpool, "rr(?c)", 1914251881Speter &range_start, &range_end, &ret_path)); 1915251881Speter if (! (SVN_IS_VALID_REVNUM(range_start) 1916251881Speter && SVN_IS_VALID_REVNUM(range_end))) 1917251881Speter return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, 1918251881Speter _("Expected valid revision range")); 1919251881Speter if (ret_path) 1920251881Speter ret_path = svn_relpath_canonicalize(ret_path, iterpool); 1921251881Speter segment->path = ret_path; 1922251881Speter segment->range_start = range_start; 1923251881Speter segment->range_end = range_end; 1924251881Speter SVN_ERR(receiver(segment, receiver_baton, iterpool)); 1925251881Speter } 1926251881Speter } 1927251881Speter svn_pool_destroy(iterpool); 1928251881Speter 1929251881Speter /* Read the response. This is so the server would have a chance to 1930251881Speter * report an error. */ 1931251881Speter SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "")); 1932251881Speter 1933251881Speter return SVN_NO_ERROR; 1934251881Speter} 1935251881Speter 1936251881Speterstatic svn_error_t *ra_svn_get_file_revs(svn_ra_session_t *session, 1937251881Speter const char *path, 1938251881Speter svn_revnum_t start, svn_revnum_t end, 1939251881Speter svn_boolean_t include_merged_revisions, 1940251881Speter svn_file_rev_handler_t handler, 1941251881Speter void *handler_baton, apr_pool_t *pool) 1942251881Speter{ 1943251881Speter svn_ra_svn__session_baton_t *sess_baton = session->priv; 1944251881Speter apr_pool_t *rev_pool, *chunk_pool; 1945251881Speter svn_boolean_t has_txdelta; 1946251881Speter svn_boolean_t had_revision = FALSE; 1947251881Speter 1948251881Speter /* One sub-pool for each revision and one for each txdelta chunk. 1949251881Speter Note that the rev_pool must live during the following txdelta. */ 1950251881Speter rev_pool = svn_pool_create(pool); 1951251881Speter chunk_pool = svn_pool_create(pool); 1952251881Speter 1953251881Speter SVN_ERR(svn_ra_svn__write_cmd_get_file_revs(sess_baton->conn, pool, 1954251881Speter path, start, end, 1955251881Speter include_merged_revisions)); 1956251881Speter 1957251881Speter /* Servers before 1.1 don't support this command. Check for this here. */ 1958251881Speter SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess_baton, pool), 1959251881Speter N_("'get-file-revs' not implemented"))); 1960251881Speter 1961251881Speter while (1) 1962251881Speter { 1963251881Speter apr_array_header_t *rev_proplist, *proplist; 1964251881Speter apr_uint64_t merged_rev_param; 1965251881Speter apr_array_header_t *props; 1966251881Speter svn_ra_svn_item_t *item; 1967251881Speter apr_hash_t *rev_props; 1968251881Speter svn_revnum_t rev; 1969251881Speter const char *p; 1970251881Speter svn_boolean_t merged_rev; 1971251881Speter svn_txdelta_window_handler_t d_handler; 1972251881Speter void *d_baton; 1973251881Speter 1974251881Speter svn_pool_clear(rev_pool); 1975251881Speter svn_pool_clear(chunk_pool); 1976251881Speter SVN_ERR(svn_ra_svn__read_item(sess_baton->conn, rev_pool, &item)); 1977251881Speter if (item->kind == SVN_RA_SVN_WORD && strcmp(item->u.word, "done") == 0) 1978251881Speter break; 1979251881Speter /* Either we've got a correct revision or we will error out below. */ 1980251881Speter had_revision = TRUE; 1981251881Speter if (item->kind != SVN_RA_SVN_LIST) 1982251881Speter return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, 1983251881Speter _("Revision entry not a list")); 1984251881Speter 1985251881Speter SVN_ERR(svn_ra_svn__parse_tuple(item->u.list, rev_pool, 1986251881Speter "crll?B", &p, &rev, &rev_proplist, 1987251881Speter &proplist, &merged_rev_param)); 1988251881Speter p = svn_fspath__canonicalize(p, rev_pool); 1989251881Speter SVN_ERR(svn_ra_svn__parse_proplist(rev_proplist, rev_pool, &rev_props)); 1990251881Speter SVN_ERR(parse_prop_diffs(proplist, rev_pool, &props)); 1991251881Speter if (merged_rev_param == SVN_RA_SVN_UNSPECIFIED_NUMBER) 1992251881Speter merged_rev = FALSE; 1993251881Speter else 1994251881Speter merged_rev = (svn_boolean_t) merged_rev_param; 1995251881Speter 1996251881Speter /* Get the first delta chunk so we know if there is a delta. */ 1997251881Speter SVN_ERR(svn_ra_svn__read_item(sess_baton->conn, chunk_pool, &item)); 1998251881Speter if (item->kind != SVN_RA_SVN_STRING) 1999251881Speter return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, 2000251881Speter _("Text delta chunk not a string")); 2001251881Speter has_txdelta = item->u.string->len > 0; 2002251881Speter 2003251881Speter SVN_ERR(handler(handler_baton, p, rev, rev_props, merged_rev, 2004251881Speter has_txdelta ? &d_handler : NULL, &d_baton, 2005251881Speter props, rev_pool)); 2006251881Speter 2007251881Speter /* Process the text delta if any. */ 2008251881Speter if (has_txdelta) 2009251881Speter { 2010251881Speter svn_stream_t *stream; 2011251881Speter 2012251881Speter if (d_handler) 2013251881Speter stream = svn_txdelta_parse_svndiff(d_handler, d_baton, TRUE, 2014251881Speter rev_pool); 2015251881Speter else 2016251881Speter stream = NULL; 2017251881Speter while (item->u.string->len > 0) 2018251881Speter { 2019251881Speter apr_size_t size; 2020251881Speter 2021251881Speter size = item->u.string->len; 2022251881Speter if (stream) 2023251881Speter SVN_ERR(svn_stream_write(stream, item->u.string->data, &size)); 2024251881Speter svn_pool_clear(chunk_pool); 2025251881Speter 2026251881Speter SVN_ERR(svn_ra_svn__read_item(sess_baton->conn, chunk_pool, 2027251881Speter &item)); 2028251881Speter if (item->kind != SVN_RA_SVN_STRING) 2029251881Speter return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, 2030251881Speter _("Text delta chunk not a string")); 2031251881Speter } 2032251881Speter if (stream) 2033251881Speter SVN_ERR(svn_stream_close(stream)); 2034251881Speter } 2035251881Speter } 2036251881Speter 2037251881Speter SVN_ERR(svn_ra_svn__read_cmd_response(sess_baton->conn, pool, "")); 2038251881Speter 2039251881Speter /* Return error if we didn't get any revisions. */ 2040251881Speter if (!had_revision) 2041251881Speter return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, 2042251881Speter _("The get-file-revs command didn't return " 2043251881Speter "any revisions")); 2044251881Speter 2045251881Speter svn_pool_destroy(chunk_pool); 2046251881Speter svn_pool_destroy(rev_pool); 2047251881Speter 2048251881Speter return SVN_NO_ERROR; 2049251881Speter} 2050251881Speter 2051251881Speter/* For each path in PATH_REVS, send a 'lock' command to the server. 2052251881Speter Used with 1.2.x series servers which support locking, but of only 2053251881Speter one path at a time. ra_svn_lock(), which supports 'lock-many' 2054251881Speter is now the default. See svn_ra_lock() docstring for interface details. */ 2055251881Speterstatic svn_error_t *ra_svn_lock_compat(svn_ra_session_t *session, 2056251881Speter apr_hash_t *path_revs, 2057251881Speter const char *comment, 2058251881Speter svn_boolean_t steal_lock, 2059251881Speter svn_ra_lock_callback_t lock_func, 2060251881Speter void *lock_baton, 2061251881Speter apr_pool_t *pool) 2062251881Speter{ 2063251881Speter svn_ra_svn__session_baton_t *sess = session->priv; 2064251881Speter svn_ra_svn_conn_t* conn = sess->conn; 2065251881Speter apr_array_header_t *list; 2066251881Speter apr_hash_index_t *hi; 2067251881Speter apr_pool_t *iterpool = svn_pool_create(pool); 2068251881Speter 2069251881Speter for (hi = apr_hash_first(pool, path_revs); hi; hi = apr_hash_next(hi)) 2070251881Speter { 2071251881Speter svn_lock_t *lock; 2072251881Speter const void *key; 2073251881Speter const char *path; 2074251881Speter void *val; 2075251881Speter svn_revnum_t *revnum; 2076251881Speter svn_error_t *err, *callback_err = NULL; 2077251881Speter 2078251881Speter svn_pool_clear(iterpool); 2079251881Speter 2080251881Speter apr_hash_this(hi, &key, NULL, &val); 2081251881Speter path = key; 2082251881Speter revnum = val; 2083251881Speter 2084251881Speter SVN_ERR(svn_ra_svn__write_cmd_lock(conn, iterpool, path, comment, 2085251881Speter steal_lock, *revnum)); 2086251881Speter 2087251881Speter /* Servers before 1.2 doesn't support locking. Check this here. */ 2088251881Speter SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess, pool), 2089251881Speter N_("Server doesn't support " 2090251881Speter "the lock command"))); 2091251881Speter 2092251881Speter err = svn_ra_svn__read_cmd_response(conn, iterpool, "l", &list); 2093251881Speter 2094251881Speter if (!err) 2095251881Speter SVN_ERR(parse_lock(list, iterpool, &lock)); 2096251881Speter 2097251881Speter if (err && !SVN_ERR_IS_LOCK_ERROR(err)) 2098251881Speter return err; 2099251881Speter 2100251881Speter if (lock_func) 2101251881Speter callback_err = lock_func(lock_baton, path, TRUE, err ? NULL : lock, 2102251881Speter err, iterpool); 2103251881Speter 2104251881Speter svn_error_clear(err); 2105251881Speter 2106251881Speter if (callback_err) 2107251881Speter return callback_err; 2108251881Speter } 2109251881Speter 2110251881Speter svn_pool_destroy(iterpool); 2111251881Speter 2112251881Speter return SVN_NO_ERROR; 2113251881Speter} 2114251881Speter 2115251881Speter/* For each path in PATH_TOKENS, send an 'unlock' command to the server. 2116251881Speter Used with 1.2.x series servers which support unlocking, but of only 2117251881Speter one path at a time. ra_svn_unlock(), which supports 'unlock-many' is 2118251881Speter now the default. See svn_ra_unlock() docstring for interface details. */ 2119251881Speterstatic svn_error_t *ra_svn_unlock_compat(svn_ra_session_t *session, 2120251881Speter apr_hash_t *path_tokens, 2121251881Speter svn_boolean_t break_lock, 2122251881Speter svn_ra_lock_callback_t lock_func, 2123251881Speter void *lock_baton, 2124251881Speter apr_pool_t *pool) 2125251881Speter{ 2126251881Speter svn_ra_svn__session_baton_t *sess = session->priv; 2127251881Speter svn_ra_svn_conn_t* conn = sess->conn; 2128251881Speter apr_hash_index_t *hi; 2129251881Speter apr_pool_t *iterpool = svn_pool_create(pool); 2130251881Speter 2131251881Speter for (hi = apr_hash_first(pool, path_tokens); hi; hi = apr_hash_next(hi)) 2132251881Speter { 2133251881Speter const void *key; 2134251881Speter const char *path; 2135251881Speter void *val; 2136251881Speter const char *token; 2137251881Speter svn_error_t *err, *callback_err = NULL; 2138251881Speter 2139251881Speter svn_pool_clear(iterpool); 2140251881Speter 2141251881Speter apr_hash_this(hi, &key, NULL, &val); 2142251881Speter path = key; 2143251881Speter if (strcmp(val, "") != 0) 2144251881Speter token = val; 2145251881Speter else 2146251881Speter token = NULL; 2147251881Speter 2148251881Speter SVN_ERR(svn_ra_svn__write_cmd_unlock(conn, iterpool, path, token, 2149251881Speter break_lock)); 2150251881Speter 2151251881Speter /* Servers before 1.2 don't support locking. Check this here. */ 2152251881Speter SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess, iterpool), 2153251881Speter N_("Server doesn't support the unlock " 2154251881Speter "command"))); 2155251881Speter 2156251881Speter err = svn_ra_svn__read_cmd_response(conn, iterpool, ""); 2157251881Speter 2158251881Speter if (err && !SVN_ERR_IS_UNLOCK_ERROR(err)) 2159251881Speter return err; 2160251881Speter 2161251881Speter if (lock_func) 2162251881Speter callback_err = lock_func(lock_baton, path, FALSE, NULL, err, pool); 2163251881Speter 2164251881Speter svn_error_clear(err); 2165251881Speter 2166251881Speter if (callback_err) 2167251881Speter return callback_err; 2168251881Speter } 2169251881Speter 2170251881Speter svn_pool_destroy(iterpool); 2171251881Speter 2172251881Speter return SVN_NO_ERROR; 2173251881Speter} 2174251881Speter 2175251881Speter/* Tell the server to lock all paths in PATH_REVS. 2176251881Speter See svn_ra_lock() for interface details. */ 2177251881Speterstatic svn_error_t *ra_svn_lock(svn_ra_session_t *session, 2178251881Speter apr_hash_t *path_revs, 2179251881Speter const char *comment, 2180251881Speter svn_boolean_t steal_lock, 2181251881Speter svn_ra_lock_callback_t lock_func, 2182251881Speter void *lock_baton, 2183251881Speter apr_pool_t *pool) 2184251881Speter{ 2185251881Speter svn_ra_svn__session_baton_t *sess = session->priv; 2186251881Speter svn_ra_svn_conn_t *conn = sess->conn; 2187251881Speter apr_hash_index_t *hi; 2188251881Speter svn_error_t *err; 2189251881Speter apr_pool_t *iterpool = svn_pool_create(pool); 2190251881Speter 2191251881Speter SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w((?c)b(!", "lock-many", 2192251881Speter comment, steal_lock)); 2193251881Speter 2194251881Speter for (hi = apr_hash_first(pool, path_revs); hi; hi = apr_hash_next(hi)) 2195251881Speter { 2196251881Speter const void *key; 2197251881Speter const char *path; 2198251881Speter void *val; 2199251881Speter svn_revnum_t *revnum; 2200251881Speter 2201251881Speter svn_pool_clear(iterpool); 2202251881Speter apr_hash_this(hi, &key, NULL, &val); 2203251881Speter path = key; 2204251881Speter revnum = val; 2205251881Speter 2206251881Speter SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "c(?r)", path, *revnum)); 2207251881Speter } 2208251881Speter 2209251881Speter SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!))")); 2210251881Speter 2211251881Speter err = handle_auth_request(sess, pool); 2212251881Speter 2213251881Speter /* Pre-1.3 servers don't support 'lock-many'. If that fails, fall back 2214251881Speter * to 'lock'. */ 2215251881Speter if (err && err->apr_err == SVN_ERR_RA_SVN_UNKNOWN_CMD) 2216251881Speter { 2217251881Speter svn_error_clear(err); 2218251881Speter return ra_svn_lock_compat(session, path_revs, comment, steal_lock, 2219251881Speter lock_func, lock_baton, pool); 2220251881Speter } 2221251881Speter 2222251881Speter if (err) 2223251881Speter return err; 2224251881Speter 2225251881Speter /* Loop over responses to get lock information. */ 2226251881Speter for (hi = apr_hash_first(pool, path_revs); hi; hi = apr_hash_next(hi)) 2227251881Speter { 2228251881Speter svn_ra_svn_item_t *elt; 2229251881Speter const void *key; 2230251881Speter const char *path; 2231251881Speter svn_error_t *callback_err; 2232251881Speter const char *status; 2233251881Speter svn_lock_t *lock; 2234251881Speter apr_array_header_t *list; 2235251881Speter 2236251881Speter apr_hash_this(hi, &key, NULL, NULL); 2237251881Speter path = key; 2238251881Speter 2239251881Speter svn_pool_clear(iterpool); 2240251881Speter SVN_ERR(svn_ra_svn__read_item(conn, iterpool, &elt)); 2241251881Speter 2242251881Speter /* The server might have encountered some sort of fatal error in 2243251881Speter the middle of the request list. If this happens, it will 2244251881Speter transmit "done" to end the lock-info early, and then the 2245251881Speter overall command response will talk about the fatal error. */ 2246251881Speter if (elt->kind == SVN_RA_SVN_WORD && strcmp(elt->u.word, "done") == 0) 2247251881Speter break; 2248251881Speter 2249251881Speter if (elt->kind != SVN_RA_SVN_LIST) 2250251881Speter return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, 2251251881Speter _("Lock response not a list")); 2252251881Speter 2253251881Speter SVN_ERR(svn_ra_svn__parse_tuple(elt->u.list, iterpool, "wl", &status, 2254251881Speter &list)); 2255251881Speter 2256251881Speter if (strcmp(status, "failure") == 0) 2257251881Speter err = svn_ra_svn__handle_failure_status(list, iterpool); 2258251881Speter else if (strcmp(status, "success") == 0) 2259251881Speter { 2260251881Speter SVN_ERR(parse_lock(list, iterpool, &lock)); 2261251881Speter err = NULL; 2262251881Speter } 2263251881Speter else 2264251881Speter return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, 2265251881Speter _("Unknown status for lock command")); 2266251881Speter 2267251881Speter if (lock_func) 2268251881Speter callback_err = lock_func(lock_baton, path, TRUE, 2269251881Speter err ? NULL : lock, 2270251881Speter err, iterpool); 2271251881Speter else 2272251881Speter callback_err = SVN_NO_ERROR; 2273251881Speter 2274251881Speter svn_error_clear(err); 2275251881Speter 2276251881Speter if (callback_err) 2277251881Speter return callback_err; 2278251881Speter } 2279251881Speter 2280251881Speter /* If we didn't break early above, and the whole hash was traversed, 2281251881Speter read the final "done" from the server. */ 2282251881Speter if (!hi) 2283251881Speter { 2284251881Speter svn_ra_svn_item_t *elt; 2285251881Speter 2286251881Speter SVN_ERR(svn_ra_svn__read_item(conn, pool, &elt)); 2287251881Speter if (elt->kind != SVN_RA_SVN_WORD || strcmp(elt->u.word, "done") != 0) 2288251881Speter return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, 2289251881Speter _("Didn't receive end marker for lock " 2290251881Speter "responses")); 2291251881Speter } 2292251881Speter 2293251881Speter SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "")); 2294251881Speter 2295251881Speter svn_pool_destroy(iterpool); 2296251881Speter 2297251881Speter return SVN_NO_ERROR; 2298251881Speter} 2299251881Speter 2300251881Speter/* Tell the server to unlock all paths in PATH_TOKENS. 2301251881Speter See svn_ra_unlock() for interface details. */ 2302251881Speterstatic svn_error_t *ra_svn_unlock(svn_ra_session_t *session, 2303251881Speter apr_hash_t *path_tokens, 2304251881Speter svn_boolean_t break_lock, 2305251881Speter svn_ra_lock_callback_t lock_func, 2306251881Speter void *lock_baton, 2307251881Speter apr_pool_t *pool) 2308251881Speter{ 2309251881Speter svn_ra_svn__session_baton_t *sess = session->priv; 2310251881Speter svn_ra_svn_conn_t *conn = sess->conn; 2311251881Speter apr_hash_index_t *hi; 2312251881Speter apr_pool_t *iterpool = svn_pool_create(pool); 2313251881Speter svn_error_t *err; 2314251881Speter const char *path; 2315251881Speter 2316251881Speter SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w(b(!", "unlock-many", 2317251881Speter break_lock)); 2318251881Speter 2319251881Speter for (hi = apr_hash_first(pool, path_tokens); hi; hi = apr_hash_next(hi)) 2320251881Speter { 2321251881Speter void *val; 2322251881Speter const void *key; 2323251881Speter const char *token; 2324251881Speter 2325251881Speter svn_pool_clear(iterpool); 2326251881Speter apr_hash_this(hi, &key, NULL, &val); 2327251881Speter path = key; 2328251881Speter 2329251881Speter if (strcmp(val, "") != 0) 2330251881Speter token = val; 2331251881Speter else 2332251881Speter token = NULL; 2333251881Speter 2334251881Speter SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "c(?c)", path, token)); 2335251881Speter } 2336251881Speter 2337251881Speter SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!))")); 2338251881Speter 2339251881Speter err = handle_auth_request(sess, pool); 2340251881Speter 2341251881Speter /* Pre-1.3 servers don't support 'unlock-many'. If unknown, fall back 2342251881Speter * to 'unlock'. 2343251881Speter */ 2344251881Speter if (err && err->apr_err == SVN_ERR_RA_SVN_UNKNOWN_CMD) 2345251881Speter { 2346251881Speter svn_error_clear(err); 2347251881Speter return ra_svn_unlock_compat(session, path_tokens, break_lock, lock_func, 2348251881Speter lock_baton, pool); 2349251881Speter } 2350251881Speter 2351251881Speter if (err) 2352251881Speter return err; 2353251881Speter 2354251881Speter /* Loop over responses to unlock files. */ 2355251881Speter for (hi = apr_hash_first(pool, path_tokens); hi; hi = apr_hash_next(hi)) 2356251881Speter { 2357251881Speter svn_ra_svn_item_t *elt; 2358251881Speter const void *key; 2359251881Speter svn_error_t *callback_err; 2360251881Speter const char *status; 2361251881Speter apr_array_header_t *list; 2362251881Speter 2363251881Speter svn_pool_clear(iterpool); 2364251881Speter 2365251881Speter SVN_ERR(svn_ra_svn__read_item(conn, iterpool, &elt)); 2366251881Speter 2367251881Speter /* The server might have encountered some sort of fatal error in 2368251881Speter the middle of the request list. If this happens, it will 2369251881Speter transmit "done" to end the lock-info early, and then the 2370251881Speter overall command response will talk about the fatal error. */ 2371251881Speter if (elt->kind == SVN_RA_SVN_WORD && (strcmp(elt->u.word, "done") == 0)) 2372251881Speter break; 2373251881Speter 2374251881Speter apr_hash_this(hi, &key, NULL, NULL); 2375251881Speter path = key; 2376251881Speter 2377251881Speter if (elt->kind != SVN_RA_SVN_LIST) 2378251881Speter return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, 2379251881Speter _("Unlock response not a list")); 2380251881Speter 2381251881Speter SVN_ERR(svn_ra_svn__parse_tuple(elt->u.list, iterpool, "wl", &status, 2382251881Speter &list)); 2383251881Speter 2384251881Speter if (strcmp(status, "failure") == 0) 2385251881Speter err = svn_ra_svn__handle_failure_status(list, iterpool); 2386251881Speter else if (strcmp(status, "success") == 0) 2387251881Speter { 2388251881Speter SVN_ERR(svn_ra_svn__parse_tuple(list, iterpool, "c", &path)); 2389251881Speter err = SVN_NO_ERROR; 2390251881Speter } 2391251881Speter else 2392251881Speter return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, 2393251881Speter _("Unknown status for unlock command")); 2394251881Speter 2395251881Speter if (lock_func) 2396251881Speter callback_err = lock_func(lock_baton, path, FALSE, NULL, err, 2397251881Speter iterpool); 2398251881Speter else 2399251881Speter callback_err = SVN_NO_ERROR; 2400251881Speter 2401251881Speter svn_error_clear(err); 2402251881Speter 2403251881Speter if (callback_err) 2404251881Speter return callback_err; 2405251881Speter } 2406251881Speter 2407251881Speter /* If we didn't break early above, and the whole hash was traversed, 2408251881Speter read the final "done" from the server. */ 2409251881Speter if (!hi) 2410251881Speter { 2411251881Speter svn_ra_svn_item_t *elt; 2412251881Speter 2413251881Speter SVN_ERR(svn_ra_svn__read_item(conn, pool, &elt)); 2414251881Speter if (elt->kind != SVN_RA_SVN_WORD || strcmp(elt->u.word, "done") != 0) 2415251881Speter return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, 2416251881Speter _("Didn't receive end marker for unlock " 2417251881Speter "responses")); 2418251881Speter } 2419251881Speter 2420251881Speter SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "")); 2421251881Speter 2422251881Speter svn_pool_destroy(iterpool); 2423251881Speter 2424251881Speter return SVN_NO_ERROR; 2425251881Speter} 2426251881Speter 2427251881Speterstatic svn_error_t *ra_svn_get_lock(svn_ra_session_t *session, 2428251881Speter svn_lock_t **lock, 2429251881Speter const char *path, 2430251881Speter apr_pool_t *pool) 2431251881Speter{ 2432251881Speter svn_ra_svn__session_baton_t *sess = session->priv; 2433251881Speter svn_ra_svn_conn_t* conn = sess->conn; 2434251881Speter apr_array_header_t *list; 2435251881Speter 2436251881Speter SVN_ERR(svn_ra_svn__write_cmd_get_lock(conn, pool, path)); 2437251881Speter 2438251881Speter /* Servers before 1.2 doesn't support locking. Check this here. */ 2439251881Speter SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess, pool), 2440251881Speter N_("Server doesn't support the get-lock " 2441251881Speter "command"))); 2442251881Speter 2443251881Speter SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "(?l)", &list)); 2444251881Speter if (list) 2445251881Speter SVN_ERR(parse_lock(list, pool, lock)); 2446251881Speter else 2447251881Speter *lock = NULL; 2448251881Speter 2449251881Speter return SVN_NO_ERROR; 2450251881Speter} 2451251881Speter 2452251881Speter/* Copied from svn_ra_get_path_relative_to_root() and de-vtable-ized 2453251881Speter to prevent a dependency cycle. */ 2454251881Speterstatic svn_error_t *path_relative_to_root(svn_ra_session_t *session, 2455251881Speter const char **rel_path, 2456251881Speter const char *url, 2457251881Speter apr_pool_t *pool) 2458251881Speter{ 2459251881Speter const char *root_url; 2460251881Speter 2461251881Speter SVN_ERR(ra_svn_get_repos_root(session, &root_url, pool)); 2462251881Speter *rel_path = svn_uri_skip_ancestor(root_url, url, pool); 2463251881Speter if (! *rel_path) 2464251881Speter return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL, 2465251881Speter _("'%s' isn't a child of repository root " 2466251881Speter "URL '%s'"), 2467251881Speter url, root_url); 2468251881Speter return SVN_NO_ERROR; 2469251881Speter} 2470251881Speter 2471251881Speterstatic svn_error_t *ra_svn_get_locks(svn_ra_session_t *session, 2472251881Speter apr_hash_t **locks, 2473251881Speter const char *path, 2474251881Speter svn_depth_t depth, 2475251881Speter apr_pool_t *pool) 2476251881Speter{ 2477251881Speter svn_ra_svn__session_baton_t *sess = session->priv; 2478251881Speter svn_ra_svn_conn_t* conn = sess->conn; 2479251881Speter apr_array_header_t *list; 2480251881Speter const char *full_url, *abs_path; 2481251881Speter int i; 2482251881Speter 2483251881Speter /* Figure out the repository abspath from PATH. */ 2484251881Speter full_url = svn_path_url_add_component2(sess->url, path, pool); 2485251881Speter SVN_ERR(path_relative_to_root(session, &abs_path, full_url, pool)); 2486251881Speter abs_path = svn_fspath__canonicalize(abs_path, pool); 2487251881Speter 2488251881Speter SVN_ERR(svn_ra_svn__write_cmd_get_locks(conn, pool, path, depth)); 2489251881Speter 2490251881Speter /* Servers before 1.2 doesn't support locking. Check this here. */ 2491251881Speter SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess, pool), 2492251881Speter N_("Server doesn't support the get-lock " 2493251881Speter "command"))); 2494251881Speter 2495251881Speter SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "l", &list)); 2496251881Speter 2497251881Speter *locks = apr_hash_make(pool); 2498251881Speter 2499251881Speter for (i = 0; i < list->nelts; ++i) 2500251881Speter { 2501251881Speter svn_lock_t *lock; 2502251881Speter svn_ra_svn_item_t *elt = &APR_ARRAY_IDX(list, i, svn_ra_svn_item_t); 2503251881Speter 2504251881Speter if (elt->kind != SVN_RA_SVN_LIST) 2505251881Speter return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, 2506251881Speter _("Lock element not a list")); 2507251881Speter SVN_ERR(parse_lock(elt->u.list, pool, &lock)); 2508251881Speter 2509251881Speter /* Filter out unwanted paths. Since Subversion only allows 2510251881Speter locks on files, we can treat depth=immediates the same as 2511251881Speter depth=files for filtering purposes. Meaning, we'll keep 2512251881Speter this lock if: 2513251881Speter 2514251881Speter a) its path is the very path we queried, or 2515251881Speter b) we've asked for a fully recursive answer, or 2516251881Speter c) we've asked for depth=files or depth=immediates, and this 2517251881Speter lock is on an immediate child of our query path. 2518251881Speter */ 2519251881Speter if ((strcmp(abs_path, lock->path) == 0) || (depth == svn_depth_infinity)) 2520251881Speter { 2521251881Speter svn_hash_sets(*locks, lock->path, lock); 2522251881Speter } 2523251881Speter else if ((depth == svn_depth_files) || (depth == svn_depth_immediates)) 2524251881Speter { 2525251881Speter const char *relpath = svn_fspath__skip_ancestor(abs_path, lock->path); 2526251881Speter if (relpath && (svn_path_component_count(relpath) == 1)) 2527251881Speter svn_hash_sets(*locks, lock->path, lock); 2528251881Speter } 2529251881Speter } 2530251881Speter 2531251881Speter return SVN_NO_ERROR; 2532251881Speter} 2533251881Speter 2534251881Speter 2535251881Speterstatic svn_error_t *ra_svn_replay(svn_ra_session_t *session, 2536251881Speter svn_revnum_t revision, 2537251881Speter svn_revnum_t low_water_mark, 2538251881Speter svn_boolean_t send_deltas, 2539251881Speter const svn_delta_editor_t *editor, 2540251881Speter void *edit_baton, 2541251881Speter apr_pool_t *pool) 2542251881Speter{ 2543251881Speter svn_ra_svn__session_baton_t *sess = session->priv; 2544251881Speter 2545251881Speter SVN_ERR(svn_ra_svn__write_cmd_replay(sess->conn, pool, revision, 2546251881Speter low_water_mark, send_deltas)); 2547251881Speter 2548251881Speter SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess, pool), 2549251881Speter N_("Server doesn't support the replay " 2550251881Speter "command"))); 2551251881Speter 2552251881Speter SVN_ERR(svn_ra_svn_drive_editor2(sess->conn, pool, editor, edit_baton, 2553251881Speter NULL, TRUE)); 2554251881Speter 2555251881Speter return svn_ra_svn__read_cmd_response(sess->conn, pool, ""); 2556251881Speter} 2557251881Speter 2558251881Speter 2559251881Speterstatic svn_error_t * 2560251881Speterra_svn_replay_range(svn_ra_session_t *session, 2561251881Speter svn_revnum_t start_revision, 2562251881Speter svn_revnum_t end_revision, 2563251881Speter svn_revnum_t low_water_mark, 2564251881Speter svn_boolean_t send_deltas, 2565251881Speter svn_ra_replay_revstart_callback_t revstart_func, 2566251881Speter svn_ra_replay_revfinish_callback_t revfinish_func, 2567251881Speter void *replay_baton, 2568251881Speter apr_pool_t *pool) 2569251881Speter{ 2570251881Speter svn_ra_svn__session_baton_t *sess = session->priv; 2571251881Speter apr_pool_t *iterpool; 2572251881Speter svn_revnum_t rev; 2573251881Speter svn_boolean_t drive_aborted = FALSE; 2574251881Speter 2575251881Speter SVN_ERR(svn_ra_svn__write_cmd_replay_range(sess->conn, pool, 2576251881Speter start_revision, end_revision, 2577251881Speter low_water_mark, send_deltas)); 2578251881Speter 2579251881Speter SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess, pool), 2580251881Speter N_("Server doesn't support the " 2581251881Speter "replay-range command"))); 2582251881Speter 2583251881Speter iterpool = svn_pool_create(pool); 2584251881Speter for (rev = start_revision; rev <= end_revision; rev++) 2585251881Speter { 2586251881Speter const svn_delta_editor_t *editor; 2587251881Speter void *edit_baton; 2588251881Speter apr_hash_t *rev_props; 2589251881Speter const char *word; 2590251881Speter apr_array_header_t *list; 2591251881Speter 2592251881Speter svn_pool_clear(iterpool); 2593251881Speter 2594251881Speter SVN_ERR(svn_ra_svn__read_tuple(sess->conn, iterpool, 2595251881Speter "wl", &word, &list)); 2596251881Speter if (strcmp(word, "revprops") != 0) 2597251881Speter return svn_error_createf(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, 2598251881Speter _("Expected 'revprops', found '%s'"), 2599251881Speter word); 2600251881Speter 2601251881Speter SVN_ERR(svn_ra_svn__parse_proplist(list, iterpool, &rev_props)); 2602251881Speter 2603251881Speter SVN_ERR(revstart_func(rev, replay_baton, 2604251881Speter &editor, &edit_baton, 2605251881Speter rev_props, 2606251881Speter iterpool)); 2607251881Speter SVN_ERR(svn_ra_svn_drive_editor2(sess->conn, iterpool, 2608251881Speter editor, edit_baton, 2609251881Speter &drive_aborted, TRUE)); 2610251881Speter /* If drive_editor2() aborted the commit, do NOT try to call 2611251881Speter revfinish_func and commit the transaction! */ 2612251881Speter if (drive_aborted) { 2613251881Speter svn_pool_destroy(iterpool); 2614251881Speter return svn_error_create(SVN_ERR_RA_SVN_EDIT_ABORTED, NULL, 2615251881Speter _("Error while replaying commit")); 2616251881Speter } 2617251881Speter SVN_ERR(revfinish_func(rev, replay_baton, 2618251881Speter editor, edit_baton, 2619251881Speter rev_props, 2620251881Speter iterpool)); 2621251881Speter } 2622251881Speter svn_pool_destroy(iterpool); 2623251881Speter 2624251881Speter return svn_ra_svn__read_cmd_response(sess->conn, pool, ""); 2625251881Speter} 2626251881Speter 2627251881Speter 2628251881Speterstatic svn_error_t * 2629251881Speterra_svn_has_capability(svn_ra_session_t *session, 2630251881Speter svn_boolean_t *has, 2631251881Speter const char *capability, 2632251881Speter apr_pool_t *pool) 2633251881Speter{ 2634251881Speter svn_ra_svn__session_baton_t *sess = session->priv; 2635251881Speter static const char* capabilities[][2] = 2636251881Speter { 2637251881Speter /* { ra capability string, svn:// wire capability string} */ 2638251881Speter {SVN_RA_CAPABILITY_DEPTH, SVN_RA_SVN_CAP_DEPTH}, 2639251881Speter {SVN_RA_CAPABILITY_MERGEINFO, SVN_RA_SVN_CAP_MERGEINFO}, 2640251881Speter {SVN_RA_CAPABILITY_LOG_REVPROPS, SVN_RA_SVN_CAP_LOG_REVPROPS}, 2641251881Speter {SVN_RA_CAPABILITY_PARTIAL_REPLAY, SVN_RA_SVN_CAP_PARTIAL_REPLAY}, 2642251881Speter {SVN_RA_CAPABILITY_COMMIT_REVPROPS, SVN_RA_SVN_CAP_COMMIT_REVPROPS}, 2643251881Speter {SVN_RA_CAPABILITY_ATOMIC_REVPROPS, SVN_RA_SVN_CAP_ATOMIC_REVPROPS}, 2644251881Speter {SVN_RA_CAPABILITY_INHERITED_PROPS, SVN_RA_SVN_CAP_INHERITED_PROPS}, 2645251881Speter {SVN_RA_CAPABILITY_EPHEMERAL_TXNPROPS, 2646251881Speter SVN_RA_SVN_CAP_EPHEMERAL_TXNPROPS}, 2647251881Speter {SVN_RA_CAPABILITY_GET_FILE_REVS_REVERSE, 2648251881Speter SVN_RA_SVN_CAP_GET_FILE_REVS_REVERSE}, 2649251881Speter 2650251881Speter {NULL, NULL} /* End of list marker */ 2651251881Speter }; 2652251881Speter int i; 2653251881Speter 2654251881Speter *has = FALSE; 2655251881Speter 2656251881Speter for (i = 0; capabilities[i][0]; i++) 2657251881Speter { 2658251881Speter if (strcmp(capability, capabilities[i][0]) == 0) 2659251881Speter { 2660251881Speter *has = svn_ra_svn_has_capability(sess->conn, capabilities[i][1]); 2661251881Speter return SVN_NO_ERROR; 2662251881Speter } 2663251881Speter } 2664251881Speter 2665251881Speter return svn_error_createf(SVN_ERR_UNKNOWN_CAPABILITY, NULL, 2666251881Speter _("Don't know anything about capability '%s'"), 2667251881Speter capability); 2668251881Speter} 2669251881Speter 2670251881Speterstatic svn_error_t * 2671251881Speterra_svn_get_deleted_rev(svn_ra_session_t *session, 2672251881Speter const char *path, 2673251881Speter svn_revnum_t peg_revision, 2674251881Speter svn_revnum_t end_revision, 2675251881Speter svn_revnum_t *revision_deleted, 2676251881Speter apr_pool_t *pool) 2677251881Speter 2678251881Speter{ 2679251881Speter svn_ra_svn__session_baton_t *sess_baton = session->priv; 2680251881Speter svn_ra_svn_conn_t *conn = sess_baton->conn; 2681251881Speter 2682251881Speter /* Transmit the parameters. */ 2683251881Speter SVN_ERR(svn_ra_svn__write_cmd_get_deleted_rev(conn, pool, path, 2684251881Speter peg_revision, end_revision)); 2685251881Speter 2686251881Speter /* Servers before 1.6 don't support this command. Check for this here. */ 2687251881Speter SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess_baton, pool), 2688251881Speter N_("'get-deleted-rev' not implemented"))); 2689251881Speter 2690251881Speter return svn_ra_svn__read_cmd_response(conn, pool, "r", revision_deleted); 2691251881Speter} 2692251881Speter 2693251881Speterstatic svn_error_t * 2694251881Speterra_svn_register_editor_shim_callbacks(svn_ra_session_t *session, 2695251881Speter svn_delta_shim_callbacks_t *callbacks) 2696251881Speter{ 2697251881Speter svn_ra_svn__session_baton_t *sess_baton = session->priv; 2698251881Speter svn_ra_svn_conn_t *conn = sess_baton->conn; 2699251881Speter 2700251881Speter conn->shim_callbacks = callbacks; 2701251881Speter 2702251881Speter return SVN_NO_ERROR; 2703251881Speter} 2704251881Speter 2705251881Speterstatic svn_error_t * 2706251881Speterra_svn_get_inherited_props(svn_ra_session_t *session, 2707251881Speter apr_array_header_t **iprops, 2708251881Speter const char *path, 2709251881Speter svn_revnum_t revision, 2710251881Speter apr_pool_t *result_pool, 2711251881Speter apr_pool_t *scratch_pool) 2712251881Speter{ 2713251881Speter svn_ra_svn__session_baton_t *sess_baton = session->priv; 2714251881Speter svn_ra_svn_conn_t *conn = sess_baton->conn; 2715251881Speter apr_array_header_t *iproplist; 2716251881Speter 2717251881Speter SVN_ERR(svn_ra_svn__write_cmd_get_iprops(conn, scratch_pool, 2718251881Speter path, revision)); 2719251881Speter SVN_ERR(handle_auth_request(sess_baton, scratch_pool)); 2720251881Speter SVN_ERR(svn_ra_svn__read_cmd_response(conn, scratch_pool, "l", &iproplist)); 2721251881Speter SVN_ERR(parse_iproplist(iprops, iproplist, session, result_pool, 2722251881Speter scratch_pool)); 2723251881Speter 2724251881Speter return SVN_NO_ERROR; 2725251881Speter} 2726251881Speter 2727251881Speterstatic const svn_ra__vtable_t ra_svn_vtable = { 2728251881Speter svn_ra_svn_version, 2729251881Speter ra_svn_get_description, 2730251881Speter ra_svn_get_schemes, 2731251881Speter ra_svn_open, 2732251881Speter ra_svn_reparent, 2733251881Speter ra_svn_get_session_url, 2734251881Speter ra_svn_get_latest_rev, 2735251881Speter ra_svn_get_dated_rev, 2736251881Speter ra_svn_change_rev_prop, 2737251881Speter ra_svn_rev_proplist, 2738251881Speter ra_svn_rev_prop, 2739251881Speter ra_svn_commit, 2740251881Speter ra_svn_get_file, 2741251881Speter ra_svn_get_dir, 2742251881Speter ra_svn_get_mergeinfo, 2743251881Speter ra_svn_update, 2744251881Speter ra_svn_switch, 2745251881Speter ra_svn_status, 2746251881Speter ra_svn_diff, 2747251881Speter ra_svn_log, 2748251881Speter ra_svn_check_path, 2749251881Speter ra_svn_stat, 2750251881Speter ra_svn_get_uuid, 2751251881Speter ra_svn_get_repos_root, 2752251881Speter ra_svn_get_locations, 2753251881Speter ra_svn_get_location_segments, 2754251881Speter ra_svn_get_file_revs, 2755251881Speter ra_svn_lock, 2756251881Speter ra_svn_unlock, 2757251881Speter ra_svn_get_lock, 2758251881Speter ra_svn_get_locks, 2759251881Speter ra_svn_replay, 2760251881Speter ra_svn_has_capability, 2761251881Speter ra_svn_replay_range, 2762251881Speter ra_svn_get_deleted_rev, 2763251881Speter ra_svn_register_editor_shim_callbacks, 2764251881Speter ra_svn_get_inherited_props 2765251881Speter}; 2766251881Speter 2767251881Spetersvn_error_t * 2768251881Spetersvn_ra_svn__init(const svn_version_t *loader_version, 2769251881Speter const svn_ra__vtable_t **vtable, 2770251881Speter apr_pool_t *pool) 2771251881Speter{ 2772251881Speter static const svn_version_checklist_t checklist[] = 2773251881Speter { 2774251881Speter { "svn_subr", svn_subr_version }, 2775251881Speter { "svn_delta", svn_delta_version }, 2776251881Speter { NULL, NULL } 2777251881Speter }; 2778251881Speter 2779262253Speter SVN_ERR(svn_ver_check_list2(svn_ra_svn_version(), checklist, svn_ver_equal)); 2780251881Speter 2781251881Speter /* Simplified version check to make sure we can safely use the 2782251881Speter VTABLE parameter. The RA loader does a more exhaustive check. */ 2783251881Speter if (loader_version->major != SVN_VER_MAJOR) 2784251881Speter { 2785251881Speter return svn_error_createf 2786251881Speter (SVN_ERR_VERSION_MISMATCH, NULL, 2787251881Speter _("Unsupported RA loader version (%d) for ra_svn"), 2788251881Speter loader_version->major); 2789251881Speter } 2790251881Speter 2791251881Speter *vtable = &ra_svn_vtable; 2792251881Speter 2793251881Speter#ifdef SVN_HAVE_SASL 2794251881Speter SVN_ERR(svn_ra_svn__sasl_init()); 2795251881Speter#endif 2796251881Speter 2797251881Speter return SVN_NO_ERROR; 2798251881Speter} 2799251881Speter 2800251881Speter/* Compatibility wrapper for the 1.1 and before API. */ 2801251881Speter#define NAME "ra_svn" 2802251881Speter#define DESCRIPTION RA_SVN_DESCRIPTION 2803251881Speter#define VTBL ra_svn_vtable 2804251881Speter#define INITFUNC svn_ra_svn__init 2805251881Speter#define COMPAT_INITFUNC svn_ra_svn_init 2806251881Speter#include "../libsvn_ra/wrapper_template.h" 2807