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{ 236299742Sdim return svn_error_trace(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 371299742Sdim/* (Note: *ARGV_P is an output parameter.) */ 372251881Speterstatic svn_error_t *find_tunnel_agent(const char *tunnel, 373251881Speter const char *hostinfo, 374299742Sdim const char ***argv_p, 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; 380299742Sdim const char **argv; 381251881Speter apr_size_t len; 382251881Speter apr_status_t status; 383251881Speter int n; 384251881Speter 385251881Speter /* Look up the tunnel specification in config. */ 386251881Speter cfg = config ? svn_hash_gets(config, SVN_CONFIG_CATEGORY_CONFIG) : NULL; 387251881Speter svn_config_get(cfg, &val, SVN_CONFIG_SECTION_TUNNELS, tunnel, NULL); 388251881Speter 389251881Speter /* We have one predefined tunnel scheme, if it isn't overridden by config. */ 390251881Speter if (!val && strcmp(tunnel, "ssh") == 0) 391251881Speter { 392251881Speter /* Killing the tunnel agent with SIGTERM leads to unsightly 393251881Speter * stderr output from ssh, unless we pass -q. 394251881Speter * The "-q" option to ssh is widely supported: all versions of 395251881Speter * OpenSSH have it, the old ssh-1.x and the 2.x, 3.x ssh.com 396251881Speter * versions have it too. If the user is using some other ssh 397251881Speter * implementation that doesn't accept it, they can override it 398251881Speter * in the [tunnels] section of the config. */ 399251881Speter val = "$SVN_SSH ssh -q"; 400251881Speter } 401251881Speter 402251881Speter if (!val || !*val) 403251881Speter return svn_error_createf(SVN_ERR_BAD_URL, NULL, 404251881Speter _("Undefined tunnel scheme '%s'"), tunnel); 405251881Speter 406251881Speter /* If the scheme definition begins with "$varname", it means there 407251881Speter * is an environment variable which can override the command. */ 408251881Speter if (*val == '$') 409251881Speter { 410251881Speter val++; 411251881Speter len = strcspn(val, " "); 412251881Speter var = apr_pstrmemdup(pool, val, len); 413251881Speter cmd = getenv(var); 414251881Speter if (!cmd) 415251881Speter { 416251881Speter cmd = val + len; 417251881Speter while (*cmd == ' ') 418251881Speter cmd++; 419251881Speter if (!*cmd) 420251881Speter return svn_error_createf(SVN_ERR_BAD_URL, NULL, 421251881Speter _("Tunnel scheme %s requires environment " 422251881Speter "variable %s to be defined"), tunnel, 423251881Speter var); 424251881Speter } 425251881Speter } 426251881Speter else 427251881Speter cmd = val; 428251881Speter 429251881Speter /* Tokenize the command into a list of arguments. */ 430251881Speter status = apr_tokenize_to_argv(cmd, &cmd_argv, pool); 431251881Speter if (status != APR_SUCCESS) 432251881Speter return svn_error_wrap_apr(status, _("Can't tokenize command '%s'"), cmd); 433251881Speter 434299742Sdim /* Calc number of the fixed arguments. */ 435251881Speter for (n = 0; cmd_argv[n] != NULL; n++) 436251881Speter ; 437251881Speter 438299742Sdim argv = apr_palloc(pool, (n + 4) * sizeof(char *)); 439299742Sdim 440299742Sdim /* Append the fixed arguments to the result. */ 441299742Sdim for (n = 0; cmd_argv[n] != NULL; n++) 442299742Sdim argv[n] = cmd_argv[n]; 443299742Sdim 444299742Sdim argv[n++] = svn_path_uri_decode(hostinfo, pool); 445299742Sdim argv[n++] = "svnserve"; 446299742Sdim argv[n++] = "-t"; 447299742Sdim argv[n] = NULL; 448299742Sdim 449299742Sdim *argv_p = argv; 450251881Speter return SVN_NO_ERROR; 451251881Speter} 452251881Speter 453251881Speter/* This function handles any errors which occur in the child process 454251881Speter * created for a tunnel agent. We write the error out as a command 455251881Speter * failure; the code in ra_svn_open() to read the server's greeting 456251881Speter * will see the error and return it to the caller. */ 457251881Speterstatic void handle_child_process_error(apr_pool_t *pool, apr_status_t status, 458251881Speter const char *desc) 459251881Speter{ 460251881Speter svn_ra_svn_conn_t *conn; 461251881Speter apr_file_t *in_file, *out_file; 462299742Sdim svn_stream_t *in_stream, *out_stream; 463251881Speter svn_error_t *err; 464251881Speter 465251881Speter if (apr_file_open_stdin(&in_file, pool) 466251881Speter || apr_file_open_stdout(&out_file, pool)) 467251881Speter return; 468251881Speter 469299742Sdim in_stream = svn_stream_from_aprfile2(in_file, FALSE, pool); 470299742Sdim out_stream = svn_stream_from_aprfile2(out_file, FALSE, pool); 471299742Sdim 472299742Sdim conn = svn_ra_svn_create_conn4(NULL, in_stream, out_stream, 473251881Speter SVN_DELTA_COMPRESSION_LEVEL_DEFAULT, 0, 474251881Speter 0, pool); 475251881Speter err = svn_error_wrap_apr(status, _("Error in child process: %s"), desc); 476251881Speter svn_error_clear(svn_ra_svn__write_cmd_failure(conn, pool, err)); 477251881Speter svn_error_clear(err); 478251881Speter svn_error_clear(svn_ra_svn__flush(conn, pool)); 479251881Speter} 480251881Speter 481251881Speter/* (Note: *CONN is an output parameter.) */ 482251881Speterstatic svn_error_t *make_tunnel(const char **args, svn_ra_svn_conn_t **conn, 483251881Speter apr_pool_t *pool) 484251881Speter{ 485251881Speter apr_status_t status; 486251881Speter apr_proc_t *proc; 487251881Speter apr_procattr_t *attr; 488251881Speter svn_error_t *err; 489251881Speter 490251881Speter status = apr_procattr_create(&attr, pool); 491251881Speter if (status == APR_SUCCESS) 492251881Speter status = apr_procattr_io_set(attr, 1, 1, 0); 493251881Speter if (status == APR_SUCCESS) 494251881Speter status = apr_procattr_cmdtype_set(attr, APR_PROGRAM_PATH); 495251881Speter if (status == APR_SUCCESS) 496251881Speter status = apr_procattr_child_errfn_set(attr, handle_child_process_error); 497251881Speter proc = apr_palloc(pool, sizeof(*proc)); 498251881Speter if (status == APR_SUCCESS) 499251881Speter status = apr_proc_create(proc, *args, args, NULL, attr, pool); 500251881Speter if (status != APR_SUCCESS) 501251881Speter return svn_error_create(SVN_ERR_RA_CANNOT_CREATE_TUNNEL, 502251881Speter svn_error_wrap_apr(status, 503251881Speter _("Can't create tunnel")), NULL); 504251881Speter 505251881Speter /* Arrange for the tunnel agent to get a SIGTERM on pool 506251881Speter * cleanup. This is a little extreme, but the alternatives 507251881Speter * weren't working out. 508251881Speter * 509251881Speter * Closing the pipes and waiting for the process to die 510251881Speter * was prone to mysterious hangs which are difficult to 511251881Speter * diagnose (e.g. svnserve dumps core due to unrelated bug; 512251881Speter * sshd goes into zombie state; ssh connection is never 513251881Speter * closed; ssh never terminates). 514251881Speter * See also the long dicussion in issue #2580 if you really 515251881Speter * want to know various reasons for these problems and 516251881Speter * the different opinions on this issue. 517251881Speter * 518251881Speter * On Win32, APR does not support KILL_ONLY_ONCE. It only has 519251881Speter * KILL_ALWAYS and KILL_NEVER. Other modes are converted to 520251881Speter * KILL_ALWAYS, which immediately calls TerminateProcess(). 521251881Speter * This instantly kills the tunnel, leaving sshd and svnserve 522251881Speter * on a remote machine running indefinitely. These processes 523251881Speter * accumulate. The problem is most often seen with a fast client 524251881Speter * machine and a modest internet connection, as the tunnel 525251881Speter * is killed before being able to gracefully complete the 526251881Speter * session. In that case, svn is unusable 100% of the time on 527251881Speter * the windows machine. Thus, on Win32, we use KILL_NEVER and 528251881Speter * take the lesser of two evils. 529251881Speter */ 530251881Speter#ifdef WIN32 531251881Speter apr_pool_note_subprocess(pool, proc, APR_KILL_NEVER); 532251881Speter#else 533251881Speter apr_pool_note_subprocess(pool, proc, APR_KILL_ONLY_ONCE); 534251881Speter#endif 535251881Speter 536251881Speter /* APR pipe objects inherit by default. But we don't want the 537251881Speter * tunnel agent's pipes held open by future child processes 538251881Speter * (such as other ra_svn sessions), so turn that off. */ 539251881Speter apr_file_inherit_unset(proc->in); 540251881Speter apr_file_inherit_unset(proc->out); 541251881Speter 542251881Speter /* Guard against dotfile output to stdout on the server. */ 543299742Sdim *conn = svn_ra_svn_create_conn4(NULL, 544299742Sdim svn_stream_from_aprfile2(proc->out, FALSE, 545299742Sdim pool), 546299742Sdim svn_stream_from_aprfile2(proc->in, FALSE, 547299742Sdim pool), 548251881Speter SVN_DELTA_COMPRESSION_LEVEL_DEFAULT, 549251881Speter 0, 0, pool); 550251881Speter err = svn_ra_svn__skip_leading_garbage(*conn, pool); 551251881Speter if (err) 552251881Speter return svn_error_quick_wrap( 553251881Speter err, 554251881Speter _("To better debug SSH connection problems, remove the -q " 555251881Speter "option from 'ssh' in the [tunnels] section of your " 556251881Speter "Subversion configuration file.")); 557251881Speter 558251881Speter return SVN_NO_ERROR; 559251881Speter} 560251881Speter 561251881Speter/* Parse URL inot URI, validating it and setting the default port if none 562251881Speter was given. Allocate the URI fileds out of POOL. */ 563251881Speterstatic svn_error_t *parse_url(const char *url, apr_uri_t *uri, 564251881Speter apr_pool_t *pool) 565251881Speter{ 566251881Speter apr_status_t apr_err; 567251881Speter 568251881Speter apr_err = apr_uri_parse(pool, url, uri); 569251881Speter 570251881Speter if (apr_err != 0) 571251881Speter return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL, 572251881Speter _("Illegal svn repository URL '%s'"), url); 573251881Speter 574251881Speter return SVN_NO_ERROR; 575251881Speter} 576251881Speter 577299742Sdim/* This structure is used as a baton for the pool cleanup function to 578299742Sdim store tunnel parameters used by the close-tunnel callback. */ 579299742Sdimstruct tunnel_data_t { 580299742Sdim void *tunnel_context; 581299742Sdim void *tunnel_baton; 582299742Sdim svn_ra_close_tunnel_func_t close_tunnel; 583299742Sdim svn_stream_t *request; 584299742Sdim svn_stream_t *response; 585299742Sdim}; 586299742Sdim 587299742Sdim/* Pool cleanup function that invokes the close-tunnel callback. */ 588299742Sdimstatic apr_status_t close_tunnel_cleanup(void *baton) 589299742Sdim{ 590299742Sdim const struct tunnel_data_t *const td = baton; 591299742Sdim 592299742Sdim if (td->close_tunnel) 593299742Sdim td->close_tunnel(td->tunnel_context, td->tunnel_baton); 594299742Sdim 595299742Sdim svn_error_clear(svn_stream_close(td->request)); 596299742Sdim 597299742Sdim /* We might have one stream to use for both request and response! */ 598299742Sdim if (td->request != td->response) 599299742Sdim svn_error_clear(svn_stream_close(td->response)); 600299742Sdim 601299742Sdim return APR_SUCCESS; /* ignored */ 602299742Sdim} 603299742Sdim 604251881Speter/* Open a session to URL, returning it in *SESS_P, allocating it in POOL. 605251881Speter URI is a parsed version of URL. CALLBACKS and CALLBACKS_BATON 606299742Sdim are provided by the caller of ra_svn_open. If TUNNEL_NAME is not NULL, 607299742Sdim it is the name of the tunnel type parsed from the URL scheme. 608299742Sdim If TUNNEL_ARGV is not NULL, it points to a program argument list to use 609299742Sdim when invoking the tunnel agent. 610251881Speter*/ 611251881Speterstatic svn_error_t *open_session(svn_ra_svn__session_baton_t **sess_p, 612251881Speter const char *url, 613251881Speter const apr_uri_t *uri, 614299742Sdim const char *tunnel_name, 615251881Speter const char **tunnel_argv, 616299742Sdim apr_hash_t *config, 617251881Speter const svn_ra_callbacks2_t *callbacks, 618251881Speter void *callbacks_baton, 619299742Sdim svn_auth_baton_t *auth_baton, 620299742Sdim apr_pool_t *result_pool, 621299742Sdim apr_pool_t *scratch_pool) 622251881Speter{ 623251881Speter svn_ra_svn__session_baton_t *sess; 624251881Speter svn_ra_svn_conn_t *conn; 625251881Speter apr_socket_t *sock; 626251881Speter apr_uint64_t minver, maxver; 627251881Speter apr_array_header_t *mechlist, *server_caplist, *repos_caplist; 628251881Speter const char *client_string = NULL; 629299742Sdim apr_pool_t *pool = result_pool; 630251881Speter 631251881Speter sess = apr_palloc(pool, sizeof(*sess)); 632251881Speter sess->pool = pool; 633299742Sdim sess->is_tunneled = (tunnel_name != NULL); 634251881Speter sess->url = apr_pstrdup(pool, url); 635251881Speter sess->user = uri->user; 636251881Speter sess->hostname = uri->hostname; 637299742Sdim sess->tunnel_name = tunnel_name; 638251881Speter sess->tunnel_argv = tunnel_argv; 639251881Speter sess->callbacks = callbacks; 640251881Speter sess->callbacks_baton = callbacks_baton; 641251881Speter sess->bytes_read = sess->bytes_written = 0; 642299742Sdim sess->auth_baton = auth_baton; 643251881Speter 644299742Sdim if (config) 645299742Sdim SVN_ERR(svn_config_copy_config(&sess->config, config, pool)); 646251881Speter else 647299742Sdim sess->config = NULL; 648299742Sdim 649299742Sdim if (tunnel_name) 650251881Speter { 651299742Sdim sess->realm_prefix = apr_psprintf(pool, "<svn+%s://%s:%d>", 652299742Sdim tunnel_name, 653299742Sdim uri->hostname, uri->port); 654299742Sdim 655299742Sdim if (tunnel_argv) 656299742Sdim SVN_ERR(make_tunnel(tunnel_argv, &conn, pool)); 657299742Sdim else 658299742Sdim { 659299742Sdim struct tunnel_data_t *const td = apr_palloc(pool, sizeof(*td)); 660299742Sdim 661299742Sdim td->tunnel_baton = callbacks->tunnel_baton; 662299742Sdim td->close_tunnel = NULL; 663299742Sdim 664299742Sdim SVN_ERR(callbacks->open_tunnel_func( 665299742Sdim &td->request, &td->response, 666299742Sdim &td->close_tunnel, &td->tunnel_context, 667299742Sdim callbacks->tunnel_baton, tunnel_name, 668299742Sdim uri->user, uri->hostname, uri->port, 669299742Sdim callbacks->cancel_func, callbacks_baton, 670299742Sdim pool)); 671299742Sdim 672299742Sdim apr_pool_cleanup_register(pool, td, close_tunnel_cleanup, 673299742Sdim apr_pool_cleanup_null); 674299742Sdim 675299742Sdim conn = svn_ra_svn_create_conn4(NULL, td->response, td->request, 676299742Sdim SVN_DELTA_COMPRESSION_LEVEL_DEFAULT, 677299742Sdim 0, 0, pool); 678299742Sdim SVN_ERR(svn_ra_svn__skip_leading_garbage(conn, pool)); 679299742Sdim } 680299742Sdim } 681299742Sdim else 682299742Sdim { 683299742Sdim sess->realm_prefix = apr_psprintf(pool, "<svn://%s:%d>", uri->hostname, 684299742Sdim uri->port ? uri->port : SVN_RA_SVN_PORT); 685299742Sdim 686299742Sdim SVN_ERR(make_connection(uri->hostname, 687299742Sdim uri->port ? uri->port : SVN_RA_SVN_PORT, 688299742Sdim &sock, pool)); 689299742Sdim conn = svn_ra_svn_create_conn4(sock, NULL, NULL, 690251881Speter SVN_DELTA_COMPRESSION_LEVEL_DEFAULT, 691251881Speter 0, 0, pool); 692251881Speter } 693251881Speter 694251881Speter /* Build the useragent string, querying the client for any 695251881Speter customizations it wishes to note. For historical reasons, we 696251881Speter still deliver the hard-coded client version info 697251881Speter (SVN_RA_SVN__DEFAULT_USERAGENT) and the customized client string 698251881Speter separately in the protocol/capabilities handshake below. But the 699251881Speter commit logic wants the combined form for use with the 700251881Speter SVN_PROP_TXN_USER_AGENT ephemeral property because that's 701251881Speter consistent with our DAV approach. */ 702251881Speter if (sess->callbacks->get_client_string != NULL) 703251881Speter SVN_ERR(sess->callbacks->get_client_string(sess->callbacks_baton, 704251881Speter &client_string, pool)); 705251881Speter if (client_string) 706251881Speter sess->useragent = apr_pstrcat(pool, SVN_RA_SVN__DEFAULT_USERAGENT " ", 707299742Sdim client_string, SVN_VA_NULL); 708251881Speter else 709251881Speter sess->useragent = SVN_RA_SVN__DEFAULT_USERAGENT; 710251881Speter 711251881Speter /* Make sure we set conn->session before reading from it, 712251881Speter * because the reader and writer functions expect a non-NULL value. */ 713251881Speter sess->conn = conn; 714251881Speter conn->session = sess; 715251881Speter 716251881Speter /* Read server's greeting. */ 717251881Speter SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "nnll", &minver, &maxver, 718251881Speter &mechlist, &server_caplist)); 719251881Speter 720251881Speter /* We support protocol version 2. */ 721251881Speter if (minver > 2) 722251881Speter return svn_error_createf(SVN_ERR_RA_SVN_BAD_VERSION, NULL, 723251881Speter _("Server requires minimum version %d"), 724251881Speter (int) minver); 725251881Speter if (maxver < 2) 726251881Speter return svn_error_createf(SVN_ERR_RA_SVN_BAD_VERSION, NULL, 727251881Speter _("Server only supports versions up to %d"), 728251881Speter (int) maxver); 729251881Speter SVN_ERR(svn_ra_svn_set_capabilities(conn, server_caplist)); 730251881Speter 731251881Speter /* All released versions of Subversion support edit-pipeline, 732251881Speter * so we do not support servers that do not. */ 733251881Speter if (! svn_ra_svn_has_capability(conn, SVN_RA_SVN_CAP_EDIT_PIPELINE)) 734251881Speter return svn_error_create(SVN_ERR_RA_SVN_BAD_VERSION, NULL, 735251881Speter _("Server does not support edit pipelining")); 736251881Speter 737251881Speter /* In protocol version 2, we send back our protocol version, our 738251881Speter * capability list, and the URL, and subsequently there is an auth 739251881Speter * request. */ 740251881Speter /* Client-side capabilities list: */ 741251881Speter SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "n(wwwwww)cc(?c)", 742251881Speter (apr_uint64_t) 2, 743251881Speter SVN_RA_SVN_CAP_EDIT_PIPELINE, 744251881Speter SVN_RA_SVN_CAP_SVNDIFF1, 745251881Speter SVN_RA_SVN_CAP_ABSENT_ENTRIES, 746251881Speter SVN_RA_SVN_CAP_DEPTH, 747251881Speter SVN_RA_SVN_CAP_MERGEINFO, 748251881Speter SVN_RA_SVN_CAP_LOG_REVPROPS, 749251881Speter url, 750251881Speter SVN_RA_SVN__DEFAULT_USERAGENT, 751251881Speter client_string)); 752251881Speter SVN_ERR(handle_auth_request(sess, pool)); 753251881Speter 754251881Speter /* This is where the security layer would go into effect if we 755251881Speter * supported security layers, which is a ways off. */ 756251881Speter 757251881Speter /* Read the repository's uuid and root URL, and perhaps learn more 758251881Speter capabilities that weren't available before now. */ 759251881Speter SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "c?c?l", &conn->uuid, 760251881Speter &conn->repos_root, &repos_caplist)); 761251881Speter if (repos_caplist) 762251881Speter SVN_ERR(svn_ra_svn_set_capabilities(conn, repos_caplist)); 763251881Speter 764251881Speter if (conn->repos_root) 765251881Speter { 766251881Speter conn->repos_root = svn_uri_canonicalize(conn->repos_root, pool); 767251881Speter /* We should check that the returned string is a prefix of url, since 768251881Speter that's the API guarantee, but this isn't true for 1.0 servers. 769251881Speter Checking the length prevents client crashes. */ 770251881Speter if (strlen(conn->repos_root) > strlen(url)) 771251881Speter return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, 772251881Speter _("Impossibly long repository root from " 773251881Speter "server")); 774251881Speter } 775251881Speter 776251881Speter *sess_p = sess; 777251881Speter 778251881Speter return SVN_NO_ERROR; 779251881Speter} 780251881Speter 781251881Speter 782251881Speter#ifdef SVN_HAVE_SASL 783251881Speter#define RA_SVN_DESCRIPTION \ 784251881Speter N_("Module for accessing a repository using the svn network protocol.\n" \ 785251881Speter " - with Cyrus SASL authentication") 786251881Speter#else 787251881Speter#define RA_SVN_DESCRIPTION \ 788251881Speter N_("Module for accessing a repository using the svn network protocol.") 789251881Speter#endif 790251881Speter 791262253Speterstatic const char *ra_svn_get_description(apr_pool_t *pool) 792251881Speter{ 793251881Speter return _(RA_SVN_DESCRIPTION); 794251881Speter} 795251881Speter 796251881Speterstatic const char * const * 797251881Speterra_svn_get_schemes(apr_pool_t *pool) 798251881Speter{ 799251881Speter static const char *schemes[] = { "svn", NULL }; 800251881Speter 801251881Speter return schemes; 802251881Speter} 803251881Speter 804251881Speter 805251881Speter 806251881Speterstatic svn_error_t *ra_svn_open(svn_ra_session_t *session, 807251881Speter const char **corrected_url, 808251881Speter const char *url, 809251881Speter const svn_ra_callbacks2_t *callbacks, 810251881Speter void *callback_baton, 811299742Sdim svn_auth_baton_t *auth_baton, 812251881Speter apr_hash_t *config, 813299742Sdim apr_pool_t *result_pool, 814299742Sdim apr_pool_t *scratch_pool) 815251881Speter{ 816299742Sdim apr_pool_t *sess_pool = svn_pool_create(result_pool); 817251881Speter svn_ra_svn__session_baton_t *sess; 818251881Speter const char *tunnel, **tunnel_argv; 819251881Speter apr_uri_t uri; 820251881Speter svn_config_t *cfg, *cfg_client; 821251881Speter 822251881Speter /* We don't support server-prescribed redirections in ra-svn. */ 823251881Speter if (corrected_url) 824251881Speter *corrected_url = NULL; 825251881Speter 826251881Speter SVN_ERR(parse_url(url, &uri, sess_pool)); 827251881Speter 828299742Sdim parse_tunnel(url, &tunnel, result_pool); 829251881Speter 830299742Sdim /* Use the default tunnel implementation if we got a tunnel name, 831299742Sdim but either do not have tunnel handler callbacks installed, or 832299742Sdim the handlers don't like the tunnel name. */ 833299742Sdim if (tunnel 834299742Sdim && (!callbacks->open_tunnel_func 835299742Sdim || (callbacks->check_tunnel_func && callbacks->open_tunnel_func 836299742Sdim && !callbacks->check_tunnel_func(callbacks->tunnel_baton, 837299742Sdim tunnel)))) 838251881Speter SVN_ERR(find_tunnel_agent(tunnel, uri.hostinfo, &tunnel_argv, config, 839299742Sdim result_pool)); 840251881Speter else 841251881Speter tunnel_argv = NULL; 842251881Speter 843251881Speter cfg_client = config 844251881Speter ? svn_hash_gets(config, SVN_CONFIG_CATEGORY_CONFIG) 845251881Speter : NULL; 846251881Speter cfg = config ? svn_hash_gets(config, SVN_CONFIG_CATEGORY_SERVERS) : NULL; 847299742Sdim svn_auth_set_parameter(auth_baton, 848251881Speter SVN_AUTH_PARAM_CONFIG_CATEGORY_CONFIG, cfg_client); 849299742Sdim svn_auth_set_parameter(auth_baton, 850251881Speter SVN_AUTH_PARAM_CONFIG_CATEGORY_SERVERS, cfg); 851251881Speter 852251881Speter /* We open the session in a subpool so we can get rid of it if we 853251881Speter reparent with a server that doesn't support reparenting. */ 854299742Sdim SVN_ERR(open_session(&sess, url, &uri, tunnel, tunnel_argv, config, 855299742Sdim callbacks, callback_baton, 856299742Sdim auth_baton, sess_pool, scratch_pool)); 857251881Speter session->priv = sess; 858251881Speter 859251881Speter return SVN_NO_ERROR; 860251881Speter} 861251881Speter 862299742Sdimstatic svn_error_t *ra_svn_dup_session(svn_ra_session_t *new_session, 863299742Sdim svn_ra_session_t *old_session, 864299742Sdim const char *new_session_url, 865299742Sdim apr_pool_t *result_pool, 866299742Sdim apr_pool_t *scratch_pool) 867299742Sdim{ 868299742Sdim svn_ra_svn__session_baton_t *old_sess = old_session->priv; 869299742Sdim 870299742Sdim SVN_ERR(ra_svn_open(new_session, NULL, new_session_url, 871299742Sdim old_sess->callbacks, old_sess->callbacks_baton, 872299742Sdim old_sess->auth_baton, old_sess->config, 873299742Sdim result_pool, scratch_pool)); 874299742Sdim 875299742Sdim return SVN_NO_ERROR; 876299742Sdim} 877299742Sdim 878251881Speterstatic svn_error_t *ra_svn_reparent(svn_ra_session_t *ra_session, 879251881Speter const char *url, 880251881Speter apr_pool_t *pool) 881251881Speter{ 882251881Speter svn_ra_svn__session_baton_t *sess = ra_session->priv; 883251881Speter svn_ra_svn_conn_t *conn = sess->conn; 884251881Speter svn_error_t *err; 885251881Speter apr_pool_t *sess_pool; 886251881Speter svn_ra_svn__session_baton_t *new_sess; 887251881Speter apr_uri_t uri; 888251881Speter 889251881Speter SVN_ERR(svn_ra_svn__write_cmd_reparent(conn, pool, url)); 890251881Speter err = handle_auth_request(sess, pool); 891251881Speter if (! err) 892251881Speter { 893251881Speter SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "")); 894251881Speter sess->url = apr_pstrdup(sess->pool, url); 895251881Speter return SVN_NO_ERROR; 896251881Speter } 897251881Speter else if (err->apr_err != SVN_ERR_RA_SVN_UNKNOWN_CMD) 898251881Speter return err; 899251881Speter 900251881Speter /* Servers before 1.4 doesn't support this command; try to reconnect 901251881Speter instead. */ 902251881Speter svn_error_clear(err); 903251881Speter /* Create a new subpool of the RA session pool. */ 904251881Speter sess_pool = svn_pool_create(ra_session->pool); 905251881Speter err = parse_url(url, &uri, sess_pool); 906251881Speter if (! err) 907299742Sdim err = open_session(&new_sess, url, &uri, sess->tunnel_name, sess->tunnel_argv, 908299742Sdim sess->config, sess->callbacks, sess->callbacks_baton, 909299742Sdim sess->auth_baton, sess_pool, sess_pool); 910251881Speter /* We destroy the new session pool on error, since it is allocated in 911251881Speter the main session pool. */ 912251881Speter if (err) 913251881Speter { 914251881Speter svn_pool_destroy(sess_pool); 915251881Speter return err; 916251881Speter } 917251881Speter 918251881Speter /* We have a new connection, assign it and destroy the old. */ 919251881Speter ra_session->priv = new_sess; 920251881Speter svn_pool_destroy(sess->pool); 921251881Speter 922251881Speter return SVN_NO_ERROR; 923251881Speter} 924251881Speter 925251881Speterstatic svn_error_t *ra_svn_get_session_url(svn_ra_session_t *session, 926251881Speter const char **url, apr_pool_t *pool) 927251881Speter{ 928251881Speter svn_ra_svn__session_baton_t *sess = session->priv; 929251881Speter *url = apr_pstrdup(pool, sess->url); 930251881Speter return SVN_NO_ERROR; 931251881Speter} 932251881Speter 933251881Speterstatic svn_error_t *ra_svn_get_latest_rev(svn_ra_session_t *session, 934251881Speter svn_revnum_t *rev, apr_pool_t *pool) 935251881Speter{ 936251881Speter svn_ra_svn__session_baton_t *sess_baton = session->priv; 937251881Speter svn_ra_svn_conn_t *conn = sess_baton->conn; 938251881Speter 939251881Speter SVN_ERR(svn_ra_svn__write_cmd_get_latest_rev(conn, pool)); 940251881Speter SVN_ERR(handle_auth_request(sess_baton, pool)); 941251881Speter SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "r", rev)); 942251881Speter return SVN_NO_ERROR; 943251881Speter} 944251881Speter 945251881Speterstatic svn_error_t *ra_svn_get_dated_rev(svn_ra_session_t *session, 946251881Speter svn_revnum_t *rev, apr_time_t tm, 947251881Speter apr_pool_t *pool) 948251881Speter{ 949251881Speter svn_ra_svn__session_baton_t *sess_baton = session->priv; 950251881Speter svn_ra_svn_conn_t *conn = sess_baton->conn; 951251881Speter 952251881Speter SVN_ERR(svn_ra_svn__write_cmd_get_dated_rev(conn, pool, tm)); 953251881Speter SVN_ERR(handle_auth_request(sess_baton, pool)); 954251881Speter SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "r", rev)); 955251881Speter return SVN_NO_ERROR; 956251881Speter} 957251881Speter 958251881Speter/* Forward declaration. */ 959251881Speterstatic svn_error_t *ra_svn_has_capability(svn_ra_session_t *session, 960251881Speter svn_boolean_t *has, 961251881Speter const char *capability, 962251881Speter apr_pool_t *pool); 963251881Speter 964251881Speterstatic svn_error_t *ra_svn_change_rev_prop(svn_ra_session_t *session, svn_revnum_t rev, 965251881Speter const char *name, 966251881Speter const svn_string_t *const *old_value_p, 967251881Speter const svn_string_t *value, 968251881Speter apr_pool_t *pool) 969251881Speter{ 970251881Speter svn_ra_svn__session_baton_t *sess_baton = session->priv; 971251881Speter svn_ra_svn_conn_t *conn = sess_baton->conn; 972251881Speter svn_boolean_t dont_care; 973251881Speter const svn_string_t *old_value; 974251881Speter svn_boolean_t has_atomic_revprops; 975251881Speter 976251881Speter SVN_ERR(ra_svn_has_capability(session, &has_atomic_revprops, 977251881Speter SVN_RA_SVN_CAP_ATOMIC_REVPROPS, 978251881Speter pool)); 979251881Speter 980251881Speter if (old_value_p) 981251881Speter { 982251881Speter /* How did you get past the same check in svn_ra_change_rev_prop2()? */ 983251881Speter SVN_ERR_ASSERT(has_atomic_revprops); 984251881Speter 985251881Speter dont_care = FALSE; 986251881Speter old_value = *old_value_p; 987251881Speter } 988251881Speter else 989251881Speter { 990251881Speter dont_care = TRUE; 991251881Speter old_value = NULL; 992251881Speter } 993251881Speter 994251881Speter if (has_atomic_revprops) 995251881Speter SVN_ERR(svn_ra_svn__write_cmd_change_rev_prop2(conn, pool, rev, name, 996251881Speter value, dont_care, 997251881Speter old_value)); 998251881Speter else 999251881Speter SVN_ERR(svn_ra_svn__write_cmd_change_rev_prop(conn, pool, rev, name, 1000251881Speter value)); 1001251881Speter 1002251881Speter SVN_ERR(handle_auth_request(sess_baton, pool)); 1003251881Speter SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "")); 1004251881Speter return SVN_NO_ERROR; 1005251881Speter} 1006251881Speter 1007251881Speterstatic svn_error_t *ra_svn_get_uuid(svn_ra_session_t *session, const char **uuid, 1008251881Speter apr_pool_t *pool) 1009251881Speter{ 1010251881Speter svn_ra_svn__session_baton_t *sess_baton = session->priv; 1011251881Speter svn_ra_svn_conn_t *conn = sess_baton->conn; 1012251881Speter 1013251881Speter *uuid = conn->uuid; 1014251881Speter return SVN_NO_ERROR; 1015251881Speter} 1016251881Speter 1017251881Speterstatic svn_error_t *ra_svn_get_repos_root(svn_ra_session_t *session, const char **url, 1018251881Speter apr_pool_t *pool) 1019251881Speter{ 1020251881Speter svn_ra_svn__session_baton_t *sess_baton = session->priv; 1021251881Speter svn_ra_svn_conn_t *conn = sess_baton->conn; 1022251881Speter 1023251881Speter if (!conn->repos_root) 1024251881Speter return svn_error_create(SVN_ERR_RA_SVN_BAD_VERSION, NULL, 1025251881Speter _("Server did not send repository root")); 1026251881Speter *url = conn->repos_root; 1027251881Speter return SVN_NO_ERROR; 1028251881Speter} 1029251881Speter 1030251881Speterstatic svn_error_t *ra_svn_rev_proplist(svn_ra_session_t *session, svn_revnum_t rev, 1031251881Speter apr_hash_t **props, apr_pool_t *pool) 1032251881Speter{ 1033251881Speter svn_ra_svn__session_baton_t *sess_baton = session->priv; 1034251881Speter svn_ra_svn_conn_t *conn = sess_baton->conn; 1035251881Speter apr_array_header_t *proplist; 1036251881Speter 1037251881Speter SVN_ERR(svn_ra_svn__write_cmd_rev_proplist(conn, pool, rev)); 1038251881Speter SVN_ERR(handle_auth_request(sess_baton, pool)); 1039251881Speter SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "l", &proplist)); 1040251881Speter SVN_ERR(svn_ra_svn__parse_proplist(proplist, pool, props)); 1041251881Speter return SVN_NO_ERROR; 1042251881Speter} 1043251881Speter 1044251881Speterstatic svn_error_t *ra_svn_rev_prop(svn_ra_session_t *session, svn_revnum_t rev, 1045251881Speter const char *name, 1046251881Speter svn_string_t **value, apr_pool_t *pool) 1047251881Speter{ 1048251881Speter svn_ra_svn__session_baton_t *sess_baton = session->priv; 1049251881Speter svn_ra_svn_conn_t *conn = sess_baton->conn; 1050251881Speter 1051251881Speter SVN_ERR(svn_ra_svn__write_cmd_rev_prop(conn, pool, rev, name)); 1052251881Speter SVN_ERR(handle_auth_request(sess_baton, pool)); 1053251881Speter SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "(?s)", value)); 1054251881Speter return SVN_NO_ERROR; 1055251881Speter} 1056251881Speter 1057251881Speterstatic svn_error_t *ra_svn_end_commit(void *baton) 1058251881Speter{ 1059251881Speter ra_svn_commit_callback_baton_t *ccb = baton; 1060251881Speter svn_commit_info_t *commit_info = svn_create_commit_info(ccb->pool); 1061251881Speter 1062251881Speter SVN_ERR(handle_auth_request(ccb->sess_baton, ccb->pool)); 1063251881Speter SVN_ERR(svn_ra_svn__read_tuple(ccb->sess_baton->conn, ccb->pool, 1064251881Speter "r(?c)(?c)?(?c)", 1065251881Speter &(commit_info->revision), 1066251881Speter &(commit_info->date), 1067251881Speter &(commit_info->author), 1068251881Speter &(commit_info->post_commit_err))); 1069251881Speter 1070299742Sdim commit_info->repos_root = apr_pstrdup(ccb->pool, 1071299742Sdim ccb->sess_baton->conn->repos_root); 1072299742Sdim 1073251881Speter if (ccb->callback) 1074251881Speter SVN_ERR(ccb->callback(commit_info, ccb->callback_baton, ccb->pool)); 1075251881Speter 1076251881Speter return SVN_NO_ERROR; 1077251881Speter} 1078251881Speter 1079251881Speterstatic svn_error_t *ra_svn_commit(svn_ra_session_t *session, 1080251881Speter const svn_delta_editor_t **editor, 1081251881Speter void **edit_baton, 1082251881Speter apr_hash_t *revprop_table, 1083251881Speter svn_commit_callback2_t callback, 1084251881Speter void *callback_baton, 1085251881Speter apr_hash_t *lock_tokens, 1086251881Speter svn_boolean_t keep_locks, 1087251881Speter apr_pool_t *pool) 1088251881Speter{ 1089251881Speter svn_ra_svn__session_baton_t *sess_baton = session->priv; 1090251881Speter svn_ra_svn_conn_t *conn = sess_baton->conn; 1091251881Speter ra_svn_commit_callback_baton_t *ccb; 1092251881Speter apr_hash_index_t *hi; 1093251881Speter apr_pool_t *iterpool; 1094251881Speter const svn_string_t *log_msg = svn_hash_gets(revprop_table, 1095251881Speter SVN_PROP_REVISION_LOG); 1096251881Speter 1097253734Speter if (log_msg == NULL && 1098253734Speter ! svn_ra_svn_has_capability(conn, SVN_RA_SVN_CAP_COMMIT_REVPROPS)) 1099253734Speter { 1100253734Speter return svn_error_createf(SVN_ERR_BAD_PROPERTY_VALUE, NULL, 1101253734Speter _("ra_svn does not support not specifying " 1102253734Speter "a log message with pre-1.5 servers; " 1103253734Speter "consider passing an empty one, or upgrading " 1104253734Speter "the server")); 1105299742Sdim } 1106253734Speter else if (log_msg == NULL) 1107253734Speter /* 1.5+ server. Set LOG_MSG to something, since the 'logmsg' argument 1108253734Speter to the 'commit' protocol command is non-optional; on the server side, 1109299742Sdim only REVPROP_TABLE will be used, and LOG_MSG will be ignored. The 1110253734Speter "svn:log" member of REVPROP_TABLE table is NULL, therefore the commit 1111253734Speter will have a NULL log message (not just "", really NULL). 1112253734Speter 1113253734Speter svnserve 1.5.x+ has always ignored LOG_MSG when REVPROP_TABLE was 1114253734Speter present; this was elevated to a protocol promise in r1498550 (and 1115253734Speter later documented in this comment) in order to fix the segmentation 1116253734Speter fault bug described in the log message of r1498550.*/ 1117253734Speter log_msg = svn_string_create("", pool); 1118253734Speter 1119251881Speter /* If we're sending revprops other than svn:log, make sure the server won't 1120251881Speter silently ignore them. */ 1121251881Speter if (apr_hash_count(revprop_table) > 1 && 1122251881Speter ! svn_ra_svn_has_capability(conn, SVN_RA_SVN_CAP_COMMIT_REVPROPS)) 1123251881Speter return svn_error_create(SVN_ERR_RA_NOT_IMPLEMENTED, NULL, 1124251881Speter _("Server doesn't support setting arbitrary " 1125251881Speter "revision properties during commit")); 1126251881Speter 1127251881Speter /* If the server supports ephemeral txnprops, add the one that 1128251881Speter reports the client's version level string. */ 1129251881Speter if (svn_ra_svn_has_capability(conn, SVN_RA_SVN_CAP_COMMIT_REVPROPS) && 1130251881Speter svn_ra_svn_has_capability(conn, SVN_RA_SVN_CAP_EPHEMERAL_TXNPROPS)) 1131251881Speter { 1132251881Speter svn_hash_sets(revprop_table, SVN_PROP_TXN_CLIENT_COMPAT_VERSION, 1133251881Speter svn_string_create(SVN_VER_NUMBER, pool)); 1134251881Speter svn_hash_sets(revprop_table, SVN_PROP_TXN_USER_AGENT, 1135251881Speter svn_string_create(sess_baton->useragent, pool)); 1136251881Speter } 1137251881Speter 1138251881Speter /* Tell the server we're starting the commit. 1139251881Speter Send log message here for backwards compatibility with servers 1140251881Speter before 1.5. */ 1141251881Speter SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w(c(!", "commit", 1142251881Speter log_msg->data)); 1143251881Speter if (lock_tokens) 1144251881Speter { 1145251881Speter iterpool = svn_pool_create(pool); 1146251881Speter for (hi = apr_hash_first(pool, lock_tokens); hi; hi = apr_hash_next(hi)) 1147251881Speter { 1148251881Speter const void *key; 1149251881Speter void *val; 1150251881Speter const char *path, *token; 1151251881Speter 1152251881Speter svn_pool_clear(iterpool); 1153251881Speter apr_hash_this(hi, &key, NULL, &val); 1154251881Speter path = key; 1155251881Speter token = val; 1156251881Speter SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "cc", path, token)); 1157251881Speter } 1158251881Speter svn_pool_destroy(iterpool); 1159251881Speter } 1160251881Speter SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!)b(!", keep_locks)); 1161251881Speter SVN_ERR(svn_ra_svn__write_proplist(conn, pool, revprop_table)); 1162251881Speter SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!))")); 1163251881Speter SVN_ERR(handle_auth_request(sess_baton, pool)); 1164251881Speter SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "")); 1165251881Speter 1166251881Speter /* Remember a few arguments for when the commit is over. */ 1167251881Speter ccb = apr_palloc(pool, sizeof(*ccb)); 1168251881Speter ccb->sess_baton = sess_baton; 1169251881Speter ccb->pool = pool; 1170251881Speter ccb->new_rev = NULL; 1171251881Speter ccb->callback = callback; 1172251881Speter ccb->callback_baton = callback_baton; 1173251881Speter 1174251881Speter /* Fetch an editor for the caller to drive. The editor will call 1175251881Speter * ra_svn_end_commit() upon close_edit(), at which point we'll fill 1176251881Speter * in the new_rev, committed_date, and committed_author values. */ 1177251881Speter svn_ra_svn_get_editor(editor, edit_baton, conn, pool, 1178251881Speter ra_svn_end_commit, ccb); 1179251881Speter return SVN_NO_ERROR; 1180251881Speter} 1181251881Speter 1182251881Speter/* Parse IPROPLIST, an array of svn_ra_svn_item_t structures, as a list of 1183251881Speter const char * repos relative paths and properties for those paths, storing 1184251881Speter the result as an array of svn_prop_inherited_item_t *items. */ 1185251881Speterstatic svn_error_t * 1186251881Speterparse_iproplist(apr_array_header_t **inherited_props, 1187251881Speter const apr_array_header_t *iproplist, 1188251881Speter svn_ra_session_t *session, 1189251881Speter apr_pool_t *result_pool, 1190251881Speter apr_pool_t *scratch_pool) 1191251881Speter 1192251881Speter{ 1193251881Speter int i; 1194251881Speter apr_pool_t *iterpool; 1195251881Speter 1196251881Speter if (iproplist == NULL) 1197251881Speter { 1198251881Speter /* If the server doesn't have the SVN_RA_CAPABILITY_INHERITED_PROPS 1199251881Speter capability we shouldn't be asking for inherited props, but if we 1200251881Speter did and the server sent back nothing then we'll want to handle 1201251881Speter that. */ 1202251881Speter *inherited_props = NULL; 1203251881Speter return SVN_NO_ERROR; 1204251881Speter } 1205251881Speter 1206251881Speter *inherited_props = apr_array_make( 1207251881Speter result_pool, iproplist->nelts, sizeof(svn_prop_inherited_item_t *)); 1208251881Speter 1209251881Speter iterpool = svn_pool_create(scratch_pool); 1210251881Speter 1211251881Speter for (i = 0; i < iproplist->nelts; i++) 1212251881Speter { 1213251881Speter apr_array_header_t *iprop_list; 1214251881Speter char *parent_rel_path; 1215251881Speter apr_hash_t *iprops; 1216251881Speter apr_hash_index_t *hi; 1217251881Speter svn_prop_inherited_item_t *new_iprop = 1218251881Speter apr_palloc(result_pool, sizeof(*new_iprop)); 1219251881Speter svn_ra_svn_item_t *elt = &APR_ARRAY_IDX(iproplist, i, 1220251881Speter svn_ra_svn_item_t); 1221251881Speter if (elt->kind != SVN_RA_SVN_LIST) 1222251881Speter return svn_error_create( 1223251881Speter SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, 1224251881Speter _("Inherited proplist element not a list")); 1225251881Speter 1226251881Speter svn_pool_clear(iterpool); 1227251881Speter 1228251881Speter SVN_ERR(svn_ra_svn__parse_tuple(elt->u.list, iterpool, "cl", 1229251881Speter &parent_rel_path, &iprop_list)); 1230251881Speter SVN_ERR(svn_ra_svn__parse_proplist(iprop_list, iterpool, &iprops)); 1231299742Sdim new_iprop->path_or_url = apr_pstrdup(result_pool, parent_rel_path); 1232299742Sdim new_iprop->prop_hash = svn_hash__make(result_pool); 1233251881Speter for (hi = apr_hash_first(iterpool, iprops); 1234251881Speter hi; 1235251881Speter hi = apr_hash_next(hi)) 1236251881Speter { 1237299742Sdim const char *name = apr_hash_this_key(hi); 1238299742Sdim svn_string_t *value = apr_hash_this_val(hi); 1239251881Speter svn_hash_sets(new_iprop->prop_hash, 1240251881Speter apr_pstrdup(result_pool, name), 1241251881Speter svn_string_dup(value, result_pool)); 1242251881Speter } 1243251881Speter APR_ARRAY_PUSH(*inherited_props, svn_prop_inherited_item_t *) = 1244251881Speter new_iprop; 1245251881Speter } 1246251881Speter svn_pool_destroy(iterpool); 1247251881Speter return SVN_NO_ERROR; 1248251881Speter} 1249251881Speter 1250251881Speterstatic svn_error_t *ra_svn_get_file(svn_ra_session_t *session, const char *path, 1251251881Speter svn_revnum_t rev, svn_stream_t *stream, 1252251881Speter svn_revnum_t *fetched_rev, 1253251881Speter apr_hash_t **props, 1254251881Speter apr_pool_t *pool) 1255251881Speter{ 1256251881Speter svn_ra_svn__session_baton_t *sess_baton = session->priv; 1257251881Speter svn_ra_svn_conn_t *conn = sess_baton->conn; 1258251881Speter apr_array_header_t *proplist; 1259251881Speter const char *expected_digest; 1260251881Speter svn_checksum_t *expected_checksum = NULL; 1261251881Speter svn_checksum_ctx_t *checksum_ctx; 1262251881Speter apr_pool_t *iterpool; 1263251881Speter 1264251881Speter SVN_ERR(svn_ra_svn__write_cmd_get_file(conn, pool, path, rev, 1265251881Speter (props != NULL), (stream != NULL))); 1266251881Speter SVN_ERR(handle_auth_request(sess_baton, pool)); 1267251881Speter SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "(?c)rl", 1268251881Speter &expected_digest, 1269251881Speter &rev, &proplist)); 1270251881Speter 1271251881Speter if (fetched_rev) 1272251881Speter *fetched_rev = rev; 1273251881Speter if (props) 1274251881Speter SVN_ERR(svn_ra_svn__parse_proplist(proplist, pool, props)); 1275251881Speter 1276251881Speter /* We're done if the contents weren't wanted. */ 1277251881Speter if (!stream) 1278251881Speter return SVN_NO_ERROR; 1279251881Speter 1280251881Speter if (expected_digest) 1281251881Speter { 1282251881Speter SVN_ERR(svn_checksum_parse_hex(&expected_checksum, svn_checksum_md5, 1283251881Speter expected_digest, pool)); 1284251881Speter checksum_ctx = svn_checksum_ctx_create(svn_checksum_md5, pool); 1285251881Speter } 1286251881Speter 1287251881Speter /* Read the file's contents. */ 1288251881Speter iterpool = svn_pool_create(pool); 1289251881Speter while (1) 1290251881Speter { 1291251881Speter svn_ra_svn_item_t *item; 1292251881Speter 1293251881Speter svn_pool_clear(iterpool); 1294251881Speter SVN_ERR(svn_ra_svn__read_item(conn, iterpool, &item)); 1295251881Speter if (item->kind != SVN_RA_SVN_STRING) 1296251881Speter return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, 1297251881Speter _("Non-string as part of file contents")); 1298251881Speter if (item->u.string->len == 0) 1299251881Speter break; 1300251881Speter 1301251881Speter if (expected_checksum) 1302251881Speter SVN_ERR(svn_checksum_update(checksum_ctx, item->u.string->data, 1303251881Speter item->u.string->len)); 1304251881Speter 1305251881Speter SVN_ERR(svn_stream_write(stream, item->u.string->data, 1306251881Speter &item->u.string->len)); 1307251881Speter } 1308251881Speter svn_pool_destroy(iterpool); 1309251881Speter 1310251881Speter SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "")); 1311251881Speter 1312251881Speter if (expected_checksum) 1313251881Speter { 1314251881Speter svn_checksum_t *checksum; 1315251881Speter 1316251881Speter SVN_ERR(svn_checksum_final(&checksum, checksum_ctx, pool)); 1317251881Speter if (!svn_checksum_match(checksum, expected_checksum)) 1318251881Speter return svn_checksum_mismatch_err(expected_checksum, checksum, pool, 1319251881Speter _("Checksum mismatch for '%s'"), 1320251881Speter path); 1321251881Speter } 1322251881Speter 1323251881Speter return SVN_NO_ERROR; 1324251881Speter} 1325251881Speter 1326251881Speterstatic svn_error_t *ra_svn_get_dir(svn_ra_session_t *session, 1327251881Speter apr_hash_t **dirents, 1328251881Speter svn_revnum_t *fetched_rev, 1329251881Speter apr_hash_t **props, 1330251881Speter const char *path, 1331251881Speter svn_revnum_t rev, 1332251881Speter apr_uint32_t dirent_fields, 1333251881Speter apr_pool_t *pool) 1334251881Speter{ 1335251881Speter svn_ra_svn__session_baton_t *sess_baton = session->priv; 1336251881Speter svn_ra_svn_conn_t *conn = sess_baton->conn; 1337251881Speter apr_array_header_t *proplist, *dirlist; 1338251881Speter int i; 1339251881Speter 1340251881Speter SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w(c(?r)bb(!", "get-dir", path, 1341251881Speter rev, (props != NULL), (dirents != NULL))); 1342251881Speter if (dirent_fields & SVN_DIRENT_KIND) 1343251881Speter SVN_ERR(svn_ra_svn__write_word(conn, pool, SVN_RA_SVN_DIRENT_KIND)); 1344251881Speter if (dirent_fields & SVN_DIRENT_SIZE) 1345251881Speter SVN_ERR(svn_ra_svn__write_word(conn, pool, SVN_RA_SVN_DIRENT_SIZE)); 1346251881Speter if (dirent_fields & SVN_DIRENT_HAS_PROPS) 1347251881Speter SVN_ERR(svn_ra_svn__write_word(conn, pool, SVN_RA_SVN_DIRENT_HAS_PROPS)); 1348251881Speter if (dirent_fields & SVN_DIRENT_CREATED_REV) 1349251881Speter SVN_ERR(svn_ra_svn__write_word(conn, pool, SVN_RA_SVN_DIRENT_CREATED_REV)); 1350251881Speter if (dirent_fields & SVN_DIRENT_TIME) 1351251881Speter SVN_ERR(svn_ra_svn__write_word(conn, pool, SVN_RA_SVN_DIRENT_TIME)); 1352251881Speter if (dirent_fields & SVN_DIRENT_LAST_AUTHOR) 1353251881Speter SVN_ERR(svn_ra_svn__write_word(conn, pool, SVN_RA_SVN_DIRENT_LAST_AUTHOR)); 1354251881Speter 1355299742Sdim /* Always send the, nominally optional, want-iprops as "false" to 1356299742Sdim workaround a bug in svnserve 1.8.0-1.8.8 that causes the server 1357299742Sdim to see "true" if it is omitted. */ 1358299742Sdim SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!)b)", FALSE)); 1359251881Speter 1360251881Speter SVN_ERR(handle_auth_request(sess_baton, pool)); 1361251881Speter SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "rll", &rev, &proplist, 1362251881Speter &dirlist)); 1363251881Speter 1364251881Speter if (fetched_rev) 1365251881Speter *fetched_rev = rev; 1366251881Speter if (props) 1367251881Speter SVN_ERR(svn_ra_svn__parse_proplist(proplist, pool, props)); 1368251881Speter 1369251881Speter /* We're done if dirents aren't wanted. */ 1370251881Speter if (!dirents) 1371251881Speter return SVN_NO_ERROR; 1372251881Speter 1373251881Speter /* Interpret the directory list. */ 1374299742Sdim *dirents = svn_hash__make(pool); 1375251881Speter for (i = 0; i < dirlist->nelts; i++) 1376251881Speter { 1377251881Speter const char *name, *kind, *cdate, *cauthor; 1378251881Speter svn_boolean_t has_props; 1379251881Speter svn_dirent_t *dirent; 1380251881Speter apr_uint64_t size; 1381251881Speter svn_revnum_t crev; 1382251881Speter svn_ra_svn_item_t *elt = &APR_ARRAY_IDX(dirlist, i, svn_ra_svn_item_t); 1383251881Speter 1384251881Speter if (elt->kind != SVN_RA_SVN_LIST) 1385251881Speter return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, 1386251881Speter _("Dirlist element not a list")); 1387251881Speter SVN_ERR(svn_ra_svn__parse_tuple(elt->u.list, pool, "cwnbr(?c)(?c)", 1388251881Speter &name, &kind, &size, &has_props, 1389251881Speter &crev, &cdate, &cauthor)); 1390299742Sdim 1391299742Sdim /* Nothing to sanitize here. Any multi-segment path is simply 1392299742Sdim illegal in the hash returned by svn_ra_get_dir2. */ 1393299742Sdim if (strchr(name, '/')) 1394299742Sdim return svn_error_createf(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, 1395299742Sdim _("Invalid directory entry name '%s'"), 1396299742Sdim name); 1397299742Sdim 1398251881Speter dirent = svn_dirent_create(pool); 1399251881Speter dirent->kind = svn_node_kind_from_word(kind); 1400251881Speter dirent->size = size;/* FIXME: svn_filesize_t */ 1401251881Speter dirent->has_props = has_props; 1402251881Speter dirent->created_rev = crev; 1403251881Speter /* NOTE: the tuple's format string says CDATE may be NULL. But this 1404251881Speter function does not allow that. The server has always sent us some 1405251881Speter random date, however, so this just happens to work. But let's 1406251881Speter be wary of servers that are (improperly) fixed to send NULL. 1407251881Speter 1408251881Speter Note: they should NOT be "fixed" to send NULL, as that would break 1409251881Speter any older clients which received that NULL. But we may as well 1410251881Speter be defensive against a malicous server. */ 1411251881Speter if (cdate == NULL) 1412251881Speter dirent->time = 0; 1413251881Speter else 1414251881Speter SVN_ERR(svn_time_from_cstring(&dirent->time, cdate, pool)); 1415251881Speter dirent->last_author = cauthor; 1416251881Speter svn_hash_sets(*dirents, name, dirent); 1417251881Speter } 1418251881Speter 1419251881Speter return SVN_NO_ERROR; 1420251881Speter} 1421251881Speter 1422251881Speter/* Converts a apr_uint64_t with values TRUE, FALSE or 1423251881Speter SVN_RA_SVN_UNSPECIFIED_NUMBER as provided by svn_ra_svn__parse_tuple 1424251881Speter to a svn_tristate_t */ 1425251881Speterstatic svn_tristate_t 1426251881Speteroptbool_to_tristate(apr_uint64_t v) 1427251881Speter{ 1428251881Speter if (v == TRUE) /* not just non-zero but exactly equal to 'TRUE' */ 1429251881Speter return svn_tristate_true; 1430251881Speter if (v == FALSE) 1431251881Speter return svn_tristate_false; 1432251881Speter 1433251881Speter return svn_tristate_unknown; /* Contains SVN_RA_SVN_UNSPECIFIED_NUMBER */ 1434251881Speter} 1435251881Speter 1436251881Speter/* If REVISION is SVN_INVALID_REVNUM, no value is sent to the 1437251881Speter server, which defaults to youngest. */ 1438251881Speterstatic svn_error_t *ra_svn_get_mergeinfo(svn_ra_session_t *session, 1439251881Speter svn_mergeinfo_catalog_t *catalog, 1440251881Speter const apr_array_header_t *paths, 1441251881Speter svn_revnum_t revision, 1442251881Speter svn_mergeinfo_inheritance_t inherit, 1443251881Speter svn_boolean_t include_descendants, 1444251881Speter apr_pool_t *pool) 1445251881Speter{ 1446251881Speter svn_ra_svn__session_baton_t *sess_baton = session->priv; 1447251881Speter svn_ra_svn_conn_t *conn = sess_baton->conn; 1448251881Speter int i; 1449251881Speter apr_array_header_t *mergeinfo_tuple; 1450251881Speter svn_ra_svn_item_t *elt; 1451251881Speter const char *path; 1452251881Speter 1453251881Speter SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w((!", "get-mergeinfo")); 1454251881Speter for (i = 0; i < paths->nelts; i++) 1455251881Speter { 1456251881Speter path = APR_ARRAY_IDX(paths, i, const char *); 1457251881Speter SVN_ERR(svn_ra_svn__write_cstring(conn, pool, path)); 1458251881Speter } 1459251881Speter SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!)(?r)wb)", revision, 1460251881Speter svn_inheritance_to_word(inherit), 1461251881Speter include_descendants)); 1462251881Speter 1463251881Speter SVN_ERR(handle_auth_request(sess_baton, pool)); 1464251881Speter SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "l", &mergeinfo_tuple)); 1465251881Speter 1466251881Speter *catalog = NULL; 1467251881Speter if (mergeinfo_tuple->nelts > 0) 1468251881Speter { 1469299742Sdim *catalog = svn_hash__make(pool); 1470251881Speter for (i = 0; i < mergeinfo_tuple->nelts; i++) 1471251881Speter { 1472251881Speter svn_mergeinfo_t for_path; 1473251881Speter const char *to_parse; 1474251881Speter 1475251881Speter elt = &((svn_ra_svn_item_t *) mergeinfo_tuple->elts)[i]; 1476251881Speter if (elt->kind != SVN_RA_SVN_LIST) 1477251881Speter return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, 1478251881Speter _("Mergeinfo element is not a list")); 1479251881Speter SVN_ERR(svn_ra_svn__parse_tuple(elt->u.list, pool, "cc", 1480251881Speter &path, &to_parse)); 1481251881Speter SVN_ERR(svn_mergeinfo_parse(&for_path, to_parse, pool)); 1482251881Speter /* Correct for naughty servers that send "relative" paths 1483251881Speter with leading slashes! */ 1484251881Speter svn_hash_sets(*catalog, path[0] == '/' ? path + 1 :path, for_path); 1485251881Speter } 1486251881Speter } 1487251881Speter 1488251881Speter return SVN_NO_ERROR; 1489251881Speter} 1490251881Speter 1491251881Speterstatic svn_error_t *ra_svn_update(svn_ra_session_t *session, 1492251881Speter const svn_ra_reporter3_t **reporter, 1493251881Speter void **report_baton, svn_revnum_t rev, 1494251881Speter const char *target, svn_depth_t depth, 1495251881Speter svn_boolean_t send_copyfrom_args, 1496251881Speter svn_boolean_t ignore_ancestry, 1497251881Speter const svn_delta_editor_t *update_editor, 1498251881Speter void *update_baton, 1499251881Speter apr_pool_t *pool, 1500251881Speter apr_pool_t *scratch_pool) 1501251881Speter{ 1502251881Speter svn_ra_svn__session_baton_t *sess_baton = session->priv; 1503251881Speter svn_ra_svn_conn_t *conn = sess_baton->conn; 1504251881Speter svn_boolean_t recurse = DEPTH_TO_RECURSE(depth); 1505251881Speter 1506251881Speter /* Tell the server we want to start an update. */ 1507251881Speter SVN_ERR(svn_ra_svn__write_cmd_update(conn, pool, rev, target, recurse, 1508251881Speter depth, send_copyfrom_args, 1509251881Speter ignore_ancestry)); 1510251881Speter SVN_ERR(handle_auth_request(sess_baton, pool)); 1511251881Speter 1512251881Speter /* Fetch a reporter for the caller to drive. The reporter will drive 1513251881Speter * update_editor upon finish_report(). */ 1514251881Speter SVN_ERR(ra_svn_get_reporter(sess_baton, pool, update_editor, update_baton, 1515251881Speter target, depth, reporter, report_baton)); 1516251881Speter return SVN_NO_ERROR; 1517251881Speter} 1518251881Speter 1519251881Speterstatic svn_error_t * 1520251881Speterra_svn_switch(svn_ra_session_t *session, 1521251881Speter const svn_ra_reporter3_t **reporter, 1522251881Speter void **report_baton, svn_revnum_t rev, 1523251881Speter const char *target, svn_depth_t depth, 1524251881Speter const char *switch_url, 1525251881Speter svn_boolean_t send_copyfrom_args, 1526251881Speter svn_boolean_t ignore_ancestry, 1527251881Speter const svn_delta_editor_t *update_editor, 1528251881Speter void *update_baton, 1529251881Speter apr_pool_t *result_pool, 1530251881Speter apr_pool_t *scratch_pool) 1531251881Speter{ 1532251881Speter apr_pool_t *pool = result_pool; 1533251881Speter svn_ra_svn__session_baton_t *sess_baton = session->priv; 1534251881Speter svn_ra_svn_conn_t *conn = sess_baton->conn; 1535251881Speter svn_boolean_t recurse = DEPTH_TO_RECURSE(depth); 1536251881Speter 1537251881Speter /* Tell the server we want to start a switch. */ 1538251881Speter SVN_ERR(svn_ra_svn__write_cmd_switch(conn, pool, rev, target, recurse, 1539251881Speter switch_url, depth, 1540251881Speter send_copyfrom_args, ignore_ancestry)); 1541251881Speter SVN_ERR(handle_auth_request(sess_baton, pool)); 1542251881Speter 1543251881Speter /* Fetch a reporter for the caller to drive. The reporter will drive 1544251881Speter * update_editor upon finish_report(). */ 1545251881Speter SVN_ERR(ra_svn_get_reporter(sess_baton, pool, update_editor, update_baton, 1546251881Speter target, depth, reporter, report_baton)); 1547251881Speter return SVN_NO_ERROR; 1548251881Speter} 1549251881Speter 1550251881Speterstatic svn_error_t *ra_svn_status(svn_ra_session_t *session, 1551251881Speter const svn_ra_reporter3_t **reporter, 1552251881Speter void **report_baton, 1553251881Speter const char *target, svn_revnum_t rev, 1554251881Speter svn_depth_t depth, 1555251881Speter const svn_delta_editor_t *status_editor, 1556251881Speter void *status_baton, apr_pool_t *pool) 1557251881Speter{ 1558251881Speter svn_ra_svn__session_baton_t *sess_baton = session->priv; 1559251881Speter svn_ra_svn_conn_t *conn = sess_baton->conn; 1560251881Speter svn_boolean_t recurse = DEPTH_TO_RECURSE(depth); 1561251881Speter 1562251881Speter /* Tell the server we want to start a status operation. */ 1563251881Speter SVN_ERR(svn_ra_svn__write_cmd_status(conn, pool, target, recurse, rev, 1564251881Speter depth)); 1565251881Speter SVN_ERR(handle_auth_request(sess_baton, pool)); 1566251881Speter 1567251881Speter /* Fetch a reporter for the caller to drive. The reporter will drive 1568251881Speter * status_editor upon finish_report(). */ 1569251881Speter SVN_ERR(ra_svn_get_reporter(sess_baton, pool, status_editor, status_baton, 1570251881Speter target, depth, reporter, report_baton)); 1571251881Speter return SVN_NO_ERROR; 1572251881Speter} 1573251881Speter 1574251881Speterstatic svn_error_t *ra_svn_diff(svn_ra_session_t *session, 1575251881Speter const svn_ra_reporter3_t **reporter, 1576251881Speter void **report_baton, 1577251881Speter svn_revnum_t rev, const char *target, 1578251881Speter svn_depth_t depth, 1579251881Speter svn_boolean_t ignore_ancestry, 1580251881Speter svn_boolean_t text_deltas, 1581251881Speter const char *versus_url, 1582251881Speter const svn_delta_editor_t *diff_editor, 1583251881Speter void *diff_baton, apr_pool_t *pool) 1584251881Speter{ 1585251881Speter svn_ra_svn__session_baton_t *sess_baton = session->priv; 1586251881Speter svn_ra_svn_conn_t *conn = sess_baton->conn; 1587251881Speter svn_boolean_t recurse = DEPTH_TO_RECURSE(depth); 1588251881Speter 1589251881Speter /* Tell the server we want to start a diff. */ 1590251881Speter SVN_ERR(svn_ra_svn__write_cmd_diff(conn, pool, rev, target, recurse, 1591251881Speter ignore_ancestry, versus_url, 1592251881Speter text_deltas, depth)); 1593251881Speter SVN_ERR(handle_auth_request(sess_baton, pool)); 1594251881Speter 1595251881Speter /* Fetch a reporter for the caller to drive. The reporter will drive 1596251881Speter * diff_editor upon finish_report(). */ 1597251881Speter SVN_ERR(ra_svn_get_reporter(sess_baton, pool, diff_editor, diff_baton, 1598251881Speter target, depth, reporter, report_baton)); 1599251881Speter return SVN_NO_ERROR; 1600251881Speter} 1601251881Speter 1602251881Speter 1603253734Speterstatic svn_error_t * 1604253734Speterperform_ra_svn_log(svn_error_t **outer_error, 1605253734Speter svn_ra_session_t *session, 1606253734Speter const apr_array_header_t *paths, 1607253734Speter svn_revnum_t start, svn_revnum_t end, 1608253734Speter int limit, 1609253734Speter svn_boolean_t discover_changed_paths, 1610253734Speter svn_boolean_t strict_node_history, 1611253734Speter svn_boolean_t include_merged_revisions, 1612253734Speter const apr_array_header_t *revprops, 1613253734Speter svn_log_entry_receiver_t receiver, 1614253734Speter void *receiver_baton, 1615253734Speter apr_pool_t *pool) 1616251881Speter{ 1617251881Speter svn_ra_svn__session_baton_t *sess_baton = session->priv; 1618251881Speter svn_ra_svn_conn_t *conn = sess_baton->conn; 1619251881Speter apr_pool_t *iterpool; 1620251881Speter int i; 1621251881Speter int nest_level = 0; 1622251881Speter const char *path; 1623251881Speter char *name; 1624251881Speter svn_boolean_t want_custom_revprops; 1625299742Sdim svn_boolean_t want_author = FALSE; 1626299742Sdim svn_boolean_t want_message = FALSE; 1627299742Sdim svn_boolean_t want_date = FALSE; 1628299742Sdim int nreceived = 0; 1629251881Speter 1630251881Speter SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w((!", "log")); 1631251881Speter if (paths) 1632251881Speter { 1633251881Speter for (i = 0; i < paths->nelts; i++) 1634251881Speter { 1635251881Speter path = APR_ARRAY_IDX(paths, i, const char *); 1636251881Speter SVN_ERR(svn_ra_svn__write_cstring(conn, pool, path)); 1637251881Speter } 1638251881Speter } 1639251881Speter SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!)(?r)(?r)bbnb!", start, end, 1640251881Speter discover_changed_paths, strict_node_history, 1641251881Speter (apr_uint64_t) limit, 1642251881Speter include_merged_revisions)); 1643251881Speter if (revprops) 1644251881Speter { 1645251881Speter want_custom_revprops = FALSE; 1646251881Speter SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!w(!", "revprops")); 1647251881Speter for (i = 0; i < revprops->nelts; i++) 1648251881Speter { 1649251881Speter name = APR_ARRAY_IDX(revprops, i, char *); 1650251881Speter SVN_ERR(svn_ra_svn__write_cstring(conn, pool, name)); 1651299742Sdim 1652299742Sdim if (strcmp(name, SVN_PROP_REVISION_AUTHOR) == 0) 1653299742Sdim want_author = TRUE; 1654299742Sdim else if (strcmp(name, SVN_PROP_REVISION_DATE) == 0) 1655299742Sdim want_date = TRUE; 1656299742Sdim else if (strcmp(name, SVN_PROP_REVISION_LOG) == 0) 1657299742Sdim want_message = TRUE; 1658299742Sdim else 1659251881Speter want_custom_revprops = TRUE; 1660251881Speter } 1661251881Speter SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!))")); 1662251881Speter } 1663251881Speter else 1664251881Speter { 1665251881Speter SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!w())", "all-revprops")); 1666299742Sdim 1667299742Sdim want_author = TRUE; 1668299742Sdim want_date = TRUE; 1669299742Sdim want_message = TRUE; 1670251881Speter want_custom_revprops = TRUE; 1671251881Speter } 1672251881Speter 1673251881Speter SVN_ERR(handle_auth_request(sess_baton, pool)); 1674251881Speter 1675251881Speter /* Read the log messages. */ 1676251881Speter iterpool = svn_pool_create(pool); 1677251881Speter while (1) 1678251881Speter { 1679251881Speter apr_uint64_t has_children_param, invalid_revnum_param; 1680251881Speter apr_uint64_t has_subtractive_merge_param; 1681251881Speter svn_string_t *author, *date, *message; 1682251881Speter apr_array_header_t *cplist, *rplist; 1683251881Speter svn_log_entry_t *log_entry; 1684251881Speter svn_boolean_t has_children; 1685251881Speter svn_boolean_t subtractive_merge = FALSE; 1686251881Speter apr_uint64_t revprop_count; 1687251881Speter svn_ra_svn_item_t *item; 1688251881Speter apr_hash_t *cphash; 1689251881Speter svn_revnum_t rev; 1690251881Speter 1691251881Speter svn_pool_clear(iterpool); 1692251881Speter SVN_ERR(svn_ra_svn__read_item(conn, iterpool, &item)); 1693251881Speter if (item->kind == SVN_RA_SVN_WORD && strcmp(item->u.word, "done") == 0) 1694251881Speter break; 1695251881Speter if (item->kind != SVN_RA_SVN_LIST) 1696251881Speter return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, 1697251881Speter _("Log entry not a list")); 1698251881Speter SVN_ERR(svn_ra_svn__parse_tuple(item->u.list, iterpool, 1699251881Speter "lr(?s)(?s)(?s)?BBnl?B", 1700251881Speter &cplist, &rev, &author, &date, 1701251881Speter &message, &has_children_param, 1702251881Speter &invalid_revnum_param, 1703251881Speter &revprop_count, &rplist, 1704251881Speter &has_subtractive_merge_param)); 1705251881Speter if (want_custom_revprops && rplist == NULL) 1706251881Speter { 1707251881Speter /* Caller asked for custom revprops, but server is too old. */ 1708251881Speter return svn_error_create(SVN_ERR_RA_NOT_IMPLEMENTED, NULL, 1709251881Speter _("Server does not support custom revprops" 1710251881Speter " via log")); 1711251881Speter } 1712251881Speter 1713251881Speter if (has_children_param == SVN_RA_SVN_UNSPECIFIED_NUMBER) 1714251881Speter has_children = FALSE; 1715251881Speter else 1716251881Speter has_children = (svn_boolean_t) has_children_param; 1717251881Speter 1718251881Speter if (has_subtractive_merge_param == SVN_RA_SVN_UNSPECIFIED_NUMBER) 1719251881Speter subtractive_merge = FALSE; 1720251881Speter else 1721251881Speter subtractive_merge = (svn_boolean_t) has_subtractive_merge_param; 1722251881Speter 1723251881Speter /* Because the svn protocol won't let us send an invalid revnum, we have 1724251881Speter to recover that fact using the extra parameter. */ 1725251881Speter if (invalid_revnum_param != SVN_RA_SVN_UNSPECIFIED_NUMBER 1726251881Speter && invalid_revnum_param) 1727251881Speter rev = SVN_INVALID_REVNUM; 1728251881Speter 1729251881Speter if (cplist->nelts > 0) 1730251881Speter { 1731251881Speter /* Interpret the changed-paths list. */ 1732299742Sdim cphash = svn_hash__make(iterpool); 1733251881Speter for (i = 0; i < cplist->nelts; i++) 1734251881Speter { 1735251881Speter svn_log_changed_path2_t *change; 1736299742Sdim svn_string_t *cpath; 1737299742Sdim const char *copy_path, *action, *kind_str; 1738251881Speter apr_uint64_t text_mods, prop_mods; 1739251881Speter svn_revnum_t copy_rev; 1740251881Speter svn_ra_svn_item_t *elt = &APR_ARRAY_IDX(cplist, i, 1741251881Speter svn_ra_svn_item_t); 1742251881Speter 1743251881Speter if (elt->kind != SVN_RA_SVN_LIST) 1744251881Speter return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, 1745251881Speter _("Changed-path entry not a list")); 1746299742Sdim SVN_ERR(svn_ra_svn__read_data_log_changed_entry(elt->u.list, 1747251881Speter &cpath, &action, ©_path, 1748251881Speter ©_rev, &kind_str, 1749251881Speter &text_mods, &prop_mods)); 1750299742Sdim 1751299742Sdim if (!svn_fspath__is_canonical(cpath->data)) 1752299742Sdim { 1753299742Sdim cpath->data = svn_fspath__canonicalize(cpath->data, iterpool); 1754299742Sdim cpath->len = strlen(cpath->data); 1755299742Sdim } 1756299742Sdim if (copy_path && !svn_fspath__is_canonical(copy_path)) 1757251881Speter copy_path = svn_fspath__canonicalize(copy_path, iterpool); 1758299742Sdim 1759251881Speter change = svn_log_changed_path2_create(iterpool); 1760251881Speter change->action = *action; 1761251881Speter change->copyfrom_path = copy_path; 1762251881Speter change->copyfrom_rev = copy_rev; 1763251881Speter change->node_kind = svn_node_kind_from_word(kind_str); 1764251881Speter change->text_modified = optbool_to_tristate(text_mods); 1765251881Speter change->props_modified = optbool_to_tristate(prop_mods); 1766299742Sdim apr_hash_set(cphash, cpath->data, cpath->len, change); 1767251881Speter } 1768251881Speter } 1769251881Speter else 1770251881Speter cphash = NULL; 1771251881Speter 1772299742Sdim /* Invoke RECEIVER 1773299742Sdim - Except if the server sends more than a >= 1 limit top level items 1774299742Sdim - Or when the callback reported a SVN_ERR_CEASE_INVOCATION 1775299742Sdim in an earlier invocation. */ 1776253734Speter if (! (limit && (nest_level == 0) && (++nreceived > limit)) 1777253734Speter && ! *outer_error) 1778251881Speter { 1779253734Speter svn_error_t *err; 1780251881Speter log_entry = svn_log_entry_create(iterpool); 1781251881Speter 1782251881Speter log_entry->changed_paths = cphash; 1783251881Speter log_entry->changed_paths2 = cphash; 1784251881Speter log_entry->revision = rev; 1785251881Speter log_entry->has_children = has_children; 1786251881Speter log_entry->subtractive_merge = subtractive_merge; 1787251881Speter if (rplist) 1788251881Speter SVN_ERR(svn_ra_svn__parse_proplist(rplist, iterpool, 1789251881Speter &log_entry->revprops)); 1790251881Speter if (log_entry->revprops == NULL) 1791299742Sdim log_entry->revprops = svn_hash__make(iterpool); 1792299742Sdim 1793299742Sdim if (author && want_author) 1794299742Sdim svn_hash_sets(log_entry->revprops, 1795299742Sdim SVN_PROP_REVISION_AUTHOR, author); 1796299742Sdim if (date && want_date) 1797299742Sdim svn_hash_sets(log_entry->revprops, 1798299742Sdim SVN_PROP_REVISION_DATE, date); 1799299742Sdim if (message && want_message) 1800299742Sdim svn_hash_sets(log_entry->revprops, 1801299742Sdim SVN_PROP_REVISION_LOG, message); 1802299742Sdim 1803253734Speter err = receiver(receiver_baton, log_entry, iterpool); 1804253734Speter if (err && err->apr_err == SVN_ERR_CEASE_INVOCATION) 1805253734Speter { 1806253734Speter *outer_error = svn_error_trace( 1807253734Speter svn_error_compose_create(*outer_error, err)); 1808253734Speter } 1809253734Speter else 1810253734Speter SVN_ERR(err); 1811253734Speter 1812251881Speter if (log_entry->has_children) 1813251881Speter { 1814251881Speter nest_level++; 1815251881Speter } 1816251881Speter if (! SVN_IS_VALID_REVNUM(log_entry->revision)) 1817251881Speter { 1818251881Speter SVN_ERR_ASSERT(nest_level); 1819251881Speter nest_level--; 1820251881Speter } 1821251881Speter } 1822251881Speter } 1823251881Speter svn_pool_destroy(iterpool); 1824251881Speter 1825251881Speter /* Read the response. */ 1826253734Speter return svn_error_trace(svn_ra_svn__read_cmd_response(conn, pool, "")); 1827251881Speter} 1828251881Speter 1829253734Speterstatic svn_error_t * 1830253734Speterra_svn_log(svn_ra_session_t *session, 1831253734Speter const apr_array_header_t *paths, 1832253734Speter svn_revnum_t start, svn_revnum_t end, 1833253734Speter int limit, 1834253734Speter svn_boolean_t discover_changed_paths, 1835253734Speter svn_boolean_t strict_node_history, 1836253734Speter svn_boolean_t include_merged_revisions, 1837253734Speter const apr_array_header_t *revprops, 1838253734Speter svn_log_entry_receiver_t receiver, 1839253734Speter void *receiver_baton, apr_pool_t *pool) 1840253734Speter{ 1841253734Speter svn_error_t *outer_error = NULL; 1842253734Speter svn_error_t *err; 1843251881Speter 1844253734Speter err = svn_error_trace(perform_ra_svn_log(&outer_error, 1845253734Speter session, paths, 1846253734Speter start, end, 1847253734Speter limit, 1848253734Speter discover_changed_paths, 1849253734Speter strict_node_history, 1850253734Speter include_merged_revisions, 1851253734Speter revprops, 1852253734Speter receiver, receiver_baton, 1853253734Speter pool)); 1854253734Speter return svn_error_trace( 1855253734Speter svn_error_compose_create(outer_error, 1856253734Speter err)); 1857253734Speter} 1858253734Speter 1859253734Speter 1860253734Speter 1861251881Speterstatic svn_error_t *ra_svn_check_path(svn_ra_session_t *session, 1862251881Speter const char *path, svn_revnum_t rev, 1863251881Speter svn_node_kind_t *kind, apr_pool_t *pool) 1864251881Speter{ 1865251881Speter svn_ra_svn__session_baton_t *sess_baton = session->priv; 1866251881Speter svn_ra_svn_conn_t *conn = sess_baton->conn; 1867251881Speter const char *kind_word; 1868251881Speter 1869251881Speter SVN_ERR(svn_ra_svn__write_cmd_check_path(conn, pool, path, rev)); 1870251881Speter SVN_ERR(handle_auth_request(sess_baton, pool)); 1871251881Speter SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "w", &kind_word)); 1872251881Speter *kind = svn_node_kind_from_word(kind_word); 1873251881Speter return SVN_NO_ERROR; 1874251881Speter} 1875251881Speter 1876251881Speter 1877251881Speter/* If ERR is a command not supported error, wrap it in a 1878251881Speter SVN_ERR_RA_NOT_IMPLEMENTED with error message MSG. Else, return err. */ 1879251881Speterstatic svn_error_t *handle_unsupported_cmd(svn_error_t *err, 1880251881Speter const char *msg) 1881251881Speter{ 1882251881Speter if (err && err->apr_err == SVN_ERR_RA_SVN_UNKNOWN_CMD) 1883251881Speter return svn_error_create(SVN_ERR_RA_NOT_IMPLEMENTED, err, 1884251881Speter _(msg)); 1885251881Speter return err; 1886251881Speter} 1887251881Speter 1888251881Speter 1889251881Speterstatic svn_error_t *ra_svn_stat(svn_ra_session_t *session, 1890251881Speter const char *path, svn_revnum_t rev, 1891251881Speter svn_dirent_t **dirent, apr_pool_t *pool) 1892251881Speter{ 1893251881Speter svn_ra_svn__session_baton_t *sess_baton = session->priv; 1894251881Speter svn_ra_svn_conn_t *conn = sess_baton->conn; 1895251881Speter apr_array_header_t *list = NULL; 1896251881Speter svn_dirent_t *the_dirent; 1897251881Speter 1898251881Speter SVN_ERR(svn_ra_svn__write_cmd_stat(conn, pool, path, rev)); 1899251881Speter SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess_baton, pool), 1900251881Speter N_("'stat' not implemented"))); 1901251881Speter SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "(?l)", &list)); 1902251881Speter 1903251881Speter if (! list) 1904251881Speter { 1905251881Speter *dirent = NULL; 1906251881Speter } 1907251881Speter else 1908251881Speter { 1909251881Speter const char *kind, *cdate, *cauthor; 1910251881Speter svn_boolean_t has_props; 1911251881Speter svn_revnum_t crev; 1912251881Speter apr_uint64_t size; 1913251881Speter 1914251881Speter SVN_ERR(svn_ra_svn__parse_tuple(list, pool, "wnbr(?c)(?c)", 1915251881Speter &kind, &size, &has_props, 1916251881Speter &crev, &cdate, &cauthor)); 1917251881Speter 1918251881Speter the_dirent = svn_dirent_create(pool); 1919251881Speter the_dirent->kind = svn_node_kind_from_word(kind); 1920251881Speter the_dirent->size = size;/* FIXME: svn_filesize_t */ 1921251881Speter the_dirent->has_props = has_props; 1922251881Speter the_dirent->created_rev = crev; 1923251881Speter SVN_ERR(svn_time_from_cstring(&the_dirent->time, cdate, pool)); 1924251881Speter the_dirent->last_author = cauthor; 1925251881Speter 1926251881Speter *dirent = the_dirent; 1927251881Speter } 1928251881Speter 1929251881Speter return SVN_NO_ERROR; 1930251881Speter} 1931251881Speter 1932251881Speter 1933251881Speterstatic svn_error_t *ra_svn_get_locations(svn_ra_session_t *session, 1934251881Speter apr_hash_t **locations, 1935251881Speter const char *path, 1936251881Speter svn_revnum_t peg_revision, 1937251881Speter const apr_array_header_t *location_revisions, 1938251881Speter apr_pool_t *pool) 1939251881Speter{ 1940251881Speter svn_ra_svn__session_baton_t *sess_baton = session->priv; 1941251881Speter svn_ra_svn_conn_t *conn = sess_baton->conn; 1942251881Speter svn_revnum_t revision; 1943251881Speter svn_boolean_t is_done; 1944251881Speter int i; 1945251881Speter 1946251881Speter /* Transmit the parameters. */ 1947251881Speter SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w(cr(!", 1948251881Speter "get-locations", path, peg_revision)); 1949251881Speter for (i = 0; i < location_revisions->nelts; i++) 1950251881Speter { 1951251881Speter revision = APR_ARRAY_IDX(location_revisions, i, svn_revnum_t); 1952251881Speter SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!r!", revision)); 1953251881Speter } 1954251881Speter 1955251881Speter SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!))")); 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-locations' not implemented"))); 1960251881Speter 1961251881Speter /* Read the hash items. */ 1962251881Speter is_done = FALSE; 1963251881Speter *locations = apr_hash_make(pool); 1964251881Speter while (!is_done) 1965251881Speter { 1966251881Speter svn_ra_svn_item_t *item; 1967251881Speter const char *ret_path; 1968251881Speter 1969251881Speter SVN_ERR(svn_ra_svn__read_item(conn, pool, &item)); 1970251881Speter if (item->kind == SVN_RA_SVN_WORD && strcmp(item->u.word, "done") == 0) 1971251881Speter is_done = 1; 1972251881Speter else if (item->kind != SVN_RA_SVN_LIST) 1973251881Speter return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, 1974251881Speter _("Location entry not a list")); 1975251881Speter else 1976251881Speter { 1977251881Speter SVN_ERR(svn_ra_svn__parse_tuple(item->u.list, pool, "rc", 1978251881Speter &revision, &ret_path)); 1979251881Speter ret_path = svn_fspath__canonicalize(ret_path, pool); 1980251881Speter apr_hash_set(*locations, apr_pmemdup(pool, &revision, 1981251881Speter sizeof(revision)), 1982251881Speter sizeof(revision), ret_path); 1983251881Speter } 1984251881Speter } 1985251881Speter 1986251881Speter /* Read the response. This is so the server would have a chance to 1987251881Speter * report an error. */ 1988299742Sdim return svn_error_trace(svn_ra_svn__read_cmd_response(conn, pool, "")); 1989251881Speter} 1990251881Speter 1991251881Speterstatic svn_error_t * 1992251881Speterra_svn_get_location_segments(svn_ra_session_t *session, 1993251881Speter const char *path, 1994251881Speter svn_revnum_t peg_revision, 1995251881Speter svn_revnum_t start_rev, 1996251881Speter svn_revnum_t end_rev, 1997251881Speter svn_location_segment_receiver_t receiver, 1998251881Speter void *receiver_baton, 1999251881Speter apr_pool_t *pool) 2000251881Speter{ 2001251881Speter svn_ra_svn__session_baton_t *sess_baton = session->priv; 2002251881Speter svn_ra_svn_conn_t *conn = sess_baton->conn; 2003251881Speter svn_boolean_t is_done; 2004251881Speter apr_pool_t *iterpool = svn_pool_create(pool); 2005251881Speter 2006251881Speter /* Transmit the parameters. */ 2007251881Speter SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w(c(?r)(?r)(?r))", 2008251881Speter "get-location-segments", 2009251881Speter path, peg_revision, start_rev, end_rev)); 2010251881Speter 2011251881Speter /* Servers before 1.5 don't support this command. Check for this here. */ 2012251881Speter SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess_baton, pool), 2013251881Speter N_("'get-location-segments'" 2014251881Speter " not implemented"))); 2015251881Speter 2016251881Speter /* Parse the response. */ 2017251881Speter is_done = FALSE; 2018251881Speter while (!is_done) 2019251881Speter { 2020251881Speter svn_revnum_t range_start, range_end; 2021251881Speter svn_ra_svn_item_t *item; 2022251881Speter const char *ret_path; 2023251881Speter 2024251881Speter svn_pool_clear(iterpool); 2025251881Speter SVN_ERR(svn_ra_svn__read_item(conn, iterpool, &item)); 2026251881Speter if (item->kind == SVN_RA_SVN_WORD && strcmp(item->u.word, "done") == 0) 2027251881Speter is_done = 1; 2028251881Speter else if (item->kind != SVN_RA_SVN_LIST) 2029251881Speter return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, 2030251881Speter _("Location segment entry not a list")); 2031251881Speter else 2032251881Speter { 2033251881Speter svn_location_segment_t *segment = apr_pcalloc(iterpool, 2034251881Speter sizeof(*segment)); 2035251881Speter SVN_ERR(svn_ra_svn__parse_tuple(item->u.list, iterpool, "rr(?c)", 2036251881Speter &range_start, &range_end, &ret_path)); 2037251881Speter if (! (SVN_IS_VALID_REVNUM(range_start) 2038251881Speter && SVN_IS_VALID_REVNUM(range_end))) 2039251881Speter return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, 2040251881Speter _("Expected valid revision range")); 2041251881Speter if (ret_path) 2042251881Speter ret_path = svn_relpath_canonicalize(ret_path, iterpool); 2043251881Speter segment->path = ret_path; 2044251881Speter segment->range_start = range_start; 2045251881Speter segment->range_end = range_end; 2046251881Speter SVN_ERR(receiver(segment, receiver_baton, iterpool)); 2047251881Speter } 2048251881Speter } 2049251881Speter svn_pool_destroy(iterpool); 2050251881Speter 2051251881Speter /* Read the response. This is so the server would have a chance to 2052251881Speter * report an error. */ 2053251881Speter SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "")); 2054251881Speter 2055251881Speter return SVN_NO_ERROR; 2056251881Speter} 2057251881Speter 2058251881Speterstatic svn_error_t *ra_svn_get_file_revs(svn_ra_session_t *session, 2059251881Speter const char *path, 2060251881Speter svn_revnum_t start, svn_revnum_t end, 2061251881Speter svn_boolean_t include_merged_revisions, 2062251881Speter svn_file_rev_handler_t handler, 2063251881Speter void *handler_baton, apr_pool_t *pool) 2064251881Speter{ 2065251881Speter svn_ra_svn__session_baton_t *sess_baton = session->priv; 2066251881Speter apr_pool_t *rev_pool, *chunk_pool; 2067251881Speter svn_boolean_t has_txdelta; 2068251881Speter svn_boolean_t had_revision = FALSE; 2069251881Speter 2070251881Speter /* One sub-pool for each revision and one for each txdelta chunk. 2071251881Speter Note that the rev_pool must live during the following txdelta. */ 2072251881Speter rev_pool = svn_pool_create(pool); 2073251881Speter chunk_pool = svn_pool_create(pool); 2074251881Speter 2075251881Speter SVN_ERR(svn_ra_svn__write_cmd_get_file_revs(sess_baton->conn, pool, 2076251881Speter path, start, end, 2077251881Speter include_merged_revisions)); 2078251881Speter 2079251881Speter /* Servers before 1.1 don't support this command. Check for this here. */ 2080251881Speter SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess_baton, pool), 2081251881Speter N_("'get-file-revs' not implemented"))); 2082251881Speter 2083251881Speter while (1) 2084251881Speter { 2085251881Speter apr_array_header_t *rev_proplist, *proplist; 2086251881Speter apr_uint64_t merged_rev_param; 2087251881Speter apr_array_header_t *props; 2088251881Speter svn_ra_svn_item_t *item; 2089251881Speter apr_hash_t *rev_props; 2090251881Speter svn_revnum_t rev; 2091251881Speter const char *p; 2092251881Speter svn_boolean_t merged_rev; 2093251881Speter svn_txdelta_window_handler_t d_handler; 2094251881Speter void *d_baton; 2095251881Speter 2096251881Speter svn_pool_clear(rev_pool); 2097251881Speter svn_pool_clear(chunk_pool); 2098251881Speter SVN_ERR(svn_ra_svn__read_item(sess_baton->conn, rev_pool, &item)); 2099251881Speter if (item->kind == SVN_RA_SVN_WORD && strcmp(item->u.word, "done") == 0) 2100251881Speter break; 2101251881Speter /* Either we've got a correct revision or we will error out below. */ 2102251881Speter had_revision = TRUE; 2103251881Speter if (item->kind != SVN_RA_SVN_LIST) 2104251881Speter return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, 2105251881Speter _("Revision entry not a list")); 2106251881Speter 2107251881Speter SVN_ERR(svn_ra_svn__parse_tuple(item->u.list, rev_pool, 2108251881Speter "crll?B", &p, &rev, &rev_proplist, 2109251881Speter &proplist, &merged_rev_param)); 2110251881Speter p = svn_fspath__canonicalize(p, rev_pool); 2111251881Speter SVN_ERR(svn_ra_svn__parse_proplist(rev_proplist, rev_pool, &rev_props)); 2112251881Speter SVN_ERR(parse_prop_diffs(proplist, rev_pool, &props)); 2113251881Speter if (merged_rev_param == SVN_RA_SVN_UNSPECIFIED_NUMBER) 2114251881Speter merged_rev = FALSE; 2115251881Speter else 2116251881Speter merged_rev = (svn_boolean_t) merged_rev_param; 2117251881Speter 2118251881Speter /* Get the first delta chunk so we know if there is a delta. */ 2119251881Speter SVN_ERR(svn_ra_svn__read_item(sess_baton->conn, chunk_pool, &item)); 2120251881Speter if (item->kind != SVN_RA_SVN_STRING) 2121251881Speter return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, 2122251881Speter _("Text delta chunk not a string")); 2123251881Speter has_txdelta = item->u.string->len > 0; 2124251881Speter 2125251881Speter SVN_ERR(handler(handler_baton, p, rev, rev_props, merged_rev, 2126251881Speter has_txdelta ? &d_handler : NULL, &d_baton, 2127251881Speter props, rev_pool)); 2128251881Speter 2129251881Speter /* Process the text delta if any. */ 2130251881Speter if (has_txdelta) 2131251881Speter { 2132251881Speter svn_stream_t *stream; 2133251881Speter 2134299742Sdim if (d_handler && d_handler != svn_delta_noop_window_handler) 2135251881Speter stream = svn_txdelta_parse_svndiff(d_handler, d_baton, TRUE, 2136251881Speter rev_pool); 2137251881Speter else 2138251881Speter stream = NULL; 2139251881Speter while (item->u.string->len > 0) 2140251881Speter { 2141251881Speter apr_size_t size; 2142251881Speter 2143251881Speter size = item->u.string->len; 2144251881Speter if (stream) 2145251881Speter SVN_ERR(svn_stream_write(stream, item->u.string->data, &size)); 2146251881Speter svn_pool_clear(chunk_pool); 2147251881Speter 2148251881Speter SVN_ERR(svn_ra_svn__read_item(sess_baton->conn, chunk_pool, 2149251881Speter &item)); 2150251881Speter if (item->kind != SVN_RA_SVN_STRING) 2151251881Speter return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, 2152251881Speter _("Text delta chunk not a string")); 2153251881Speter } 2154251881Speter if (stream) 2155251881Speter SVN_ERR(svn_stream_close(stream)); 2156251881Speter } 2157251881Speter } 2158251881Speter 2159251881Speter SVN_ERR(svn_ra_svn__read_cmd_response(sess_baton->conn, pool, "")); 2160251881Speter 2161251881Speter /* Return error if we didn't get any revisions. */ 2162251881Speter if (!had_revision) 2163251881Speter return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, 2164251881Speter _("The get-file-revs command didn't return " 2165251881Speter "any revisions")); 2166251881Speter 2167251881Speter svn_pool_destroy(chunk_pool); 2168251881Speter svn_pool_destroy(rev_pool); 2169251881Speter 2170251881Speter return SVN_NO_ERROR; 2171251881Speter} 2172251881Speter 2173251881Speter/* For each path in PATH_REVS, send a 'lock' command to the server. 2174251881Speter Used with 1.2.x series servers which support locking, but of only 2175251881Speter one path at a time. ra_svn_lock(), which supports 'lock-many' 2176251881Speter is now the default. See svn_ra_lock() docstring for interface details. */ 2177251881Speterstatic svn_error_t *ra_svn_lock_compat(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_array_header_t *list; 2188251881Speter apr_hash_index_t *hi; 2189251881Speter apr_pool_t *iterpool = svn_pool_create(pool); 2190251881Speter 2191251881Speter for (hi = apr_hash_first(pool, path_revs); hi; hi = apr_hash_next(hi)) 2192251881Speter { 2193251881Speter svn_lock_t *lock; 2194251881Speter const void *key; 2195251881Speter const char *path; 2196251881Speter void *val; 2197251881Speter svn_revnum_t *revnum; 2198251881Speter svn_error_t *err, *callback_err = NULL; 2199251881Speter 2200251881Speter svn_pool_clear(iterpool); 2201251881Speter 2202251881Speter apr_hash_this(hi, &key, NULL, &val); 2203251881Speter path = key; 2204251881Speter revnum = val; 2205251881Speter 2206251881Speter SVN_ERR(svn_ra_svn__write_cmd_lock(conn, iterpool, path, comment, 2207251881Speter steal_lock, *revnum)); 2208251881Speter 2209251881Speter /* Servers before 1.2 doesn't support locking. Check this here. */ 2210251881Speter SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess, pool), 2211251881Speter N_("Server doesn't support " 2212251881Speter "the lock command"))); 2213251881Speter 2214251881Speter err = svn_ra_svn__read_cmd_response(conn, iterpool, "l", &list); 2215251881Speter 2216251881Speter if (!err) 2217251881Speter SVN_ERR(parse_lock(list, iterpool, &lock)); 2218251881Speter 2219251881Speter if (err && !SVN_ERR_IS_LOCK_ERROR(err)) 2220251881Speter return err; 2221251881Speter 2222251881Speter if (lock_func) 2223251881Speter callback_err = lock_func(lock_baton, path, TRUE, err ? NULL : lock, 2224251881Speter err, iterpool); 2225251881Speter 2226251881Speter svn_error_clear(err); 2227251881Speter 2228251881Speter if (callback_err) 2229251881Speter return callback_err; 2230251881Speter } 2231251881Speter 2232251881Speter svn_pool_destroy(iterpool); 2233251881Speter 2234251881Speter return SVN_NO_ERROR; 2235251881Speter} 2236251881Speter 2237251881Speter/* For each path in PATH_TOKENS, send an 'unlock' command to the server. 2238251881Speter Used with 1.2.x series servers which support unlocking, but of only 2239251881Speter one path at a time. ra_svn_unlock(), which supports 'unlock-many' is 2240251881Speter now the default. See svn_ra_unlock() docstring for interface details. */ 2241251881Speterstatic svn_error_t *ra_svn_unlock_compat(svn_ra_session_t *session, 2242251881Speter apr_hash_t *path_tokens, 2243251881Speter svn_boolean_t break_lock, 2244251881Speter svn_ra_lock_callback_t lock_func, 2245251881Speter void *lock_baton, 2246251881Speter apr_pool_t *pool) 2247251881Speter{ 2248251881Speter svn_ra_svn__session_baton_t *sess = session->priv; 2249251881Speter svn_ra_svn_conn_t* conn = sess->conn; 2250251881Speter apr_hash_index_t *hi; 2251251881Speter apr_pool_t *iterpool = svn_pool_create(pool); 2252251881Speter 2253251881Speter for (hi = apr_hash_first(pool, path_tokens); hi; hi = apr_hash_next(hi)) 2254251881Speter { 2255251881Speter const void *key; 2256251881Speter const char *path; 2257251881Speter void *val; 2258251881Speter const char *token; 2259251881Speter svn_error_t *err, *callback_err = NULL; 2260251881Speter 2261251881Speter svn_pool_clear(iterpool); 2262251881Speter 2263251881Speter apr_hash_this(hi, &key, NULL, &val); 2264251881Speter path = key; 2265251881Speter if (strcmp(val, "") != 0) 2266251881Speter token = val; 2267251881Speter else 2268251881Speter token = NULL; 2269251881Speter 2270251881Speter SVN_ERR(svn_ra_svn__write_cmd_unlock(conn, iterpool, path, token, 2271251881Speter break_lock)); 2272251881Speter 2273251881Speter /* Servers before 1.2 don't support locking. Check this here. */ 2274251881Speter SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess, iterpool), 2275251881Speter N_("Server doesn't support the unlock " 2276251881Speter "command"))); 2277251881Speter 2278251881Speter err = svn_ra_svn__read_cmd_response(conn, iterpool, ""); 2279251881Speter 2280251881Speter if (err && !SVN_ERR_IS_UNLOCK_ERROR(err)) 2281251881Speter return err; 2282251881Speter 2283251881Speter if (lock_func) 2284251881Speter callback_err = lock_func(lock_baton, path, FALSE, NULL, err, pool); 2285251881Speter 2286251881Speter svn_error_clear(err); 2287251881Speter 2288251881Speter if (callback_err) 2289251881Speter return callback_err; 2290251881Speter } 2291251881Speter 2292251881Speter svn_pool_destroy(iterpool); 2293251881Speter 2294251881Speter return SVN_NO_ERROR; 2295251881Speter} 2296251881Speter 2297251881Speter/* Tell the server to lock all paths in PATH_REVS. 2298251881Speter See svn_ra_lock() for interface details. */ 2299251881Speterstatic svn_error_t *ra_svn_lock(svn_ra_session_t *session, 2300251881Speter apr_hash_t *path_revs, 2301251881Speter const char *comment, 2302251881Speter svn_boolean_t steal_lock, 2303251881Speter svn_ra_lock_callback_t lock_func, 2304251881Speter void *lock_baton, 2305251881Speter apr_pool_t *pool) 2306251881Speter{ 2307251881Speter svn_ra_svn__session_baton_t *sess = session->priv; 2308251881Speter svn_ra_svn_conn_t *conn = sess->conn; 2309251881Speter apr_hash_index_t *hi; 2310251881Speter svn_error_t *err; 2311251881Speter apr_pool_t *iterpool = svn_pool_create(pool); 2312251881Speter 2313251881Speter SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w((?c)b(!", "lock-many", 2314251881Speter comment, steal_lock)); 2315251881Speter 2316251881Speter for (hi = apr_hash_first(pool, path_revs); hi; hi = apr_hash_next(hi)) 2317251881Speter { 2318251881Speter const void *key; 2319251881Speter const char *path; 2320251881Speter void *val; 2321251881Speter svn_revnum_t *revnum; 2322251881Speter 2323251881Speter svn_pool_clear(iterpool); 2324251881Speter apr_hash_this(hi, &key, NULL, &val); 2325251881Speter path = key; 2326251881Speter revnum = val; 2327251881Speter 2328251881Speter SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "c(?r)", path, *revnum)); 2329251881Speter } 2330251881Speter 2331251881Speter SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!))")); 2332251881Speter 2333251881Speter err = handle_auth_request(sess, pool); 2334251881Speter 2335251881Speter /* Pre-1.3 servers don't support 'lock-many'. If that fails, fall back 2336251881Speter * to 'lock'. */ 2337251881Speter if (err && err->apr_err == SVN_ERR_RA_SVN_UNKNOWN_CMD) 2338251881Speter { 2339251881Speter svn_error_clear(err); 2340251881Speter return ra_svn_lock_compat(session, path_revs, comment, steal_lock, 2341251881Speter lock_func, lock_baton, pool); 2342251881Speter } 2343251881Speter 2344251881Speter if (err) 2345251881Speter return err; 2346251881Speter 2347251881Speter /* Loop over responses to get lock information. */ 2348251881Speter for (hi = apr_hash_first(pool, path_revs); hi; hi = apr_hash_next(hi)) 2349251881Speter { 2350251881Speter svn_ra_svn_item_t *elt; 2351251881Speter const void *key; 2352251881Speter const char *path; 2353251881Speter svn_error_t *callback_err; 2354251881Speter const char *status; 2355251881Speter svn_lock_t *lock; 2356251881Speter apr_array_header_t *list; 2357251881Speter 2358251881Speter apr_hash_this(hi, &key, NULL, NULL); 2359251881Speter path = key; 2360251881Speter 2361251881Speter svn_pool_clear(iterpool); 2362251881Speter SVN_ERR(svn_ra_svn__read_item(conn, iterpool, &elt)); 2363251881Speter 2364251881Speter /* The server might have encountered some sort of fatal error in 2365251881Speter the middle of the request list. If this happens, it will 2366251881Speter transmit "done" to end the lock-info early, and then the 2367251881Speter overall command response will talk about the fatal error. */ 2368251881Speter if (elt->kind == SVN_RA_SVN_WORD && strcmp(elt->u.word, "done") == 0) 2369251881Speter break; 2370251881Speter 2371251881Speter if (elt->kind != SVN_RA_SVN_LIST) 2372251881Speter return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, 2373251881Speter _("Lock response not a list")); 2374251881Speter 2375251881Speter SVN_ERR(svn_ra_svn__parse_tuple(elt->u.list, iterpool, "wl", &status, 2376251881Speter &list)); 2377251881Speter 2378251881Speter if (strcmp(status, "failure") == 0) 2379251881Speter err = svn_ra_svn__handle_failure_status(list, iterpool); 2380251881Speter else if (strcmp(status, "success") == 0) 2381251881Speter { 2382251881Speter SVN_ERR(parse_lock(list, iterpool, &lock)); 2383251881Speter err = NULL; 2384251881Speter } 2385251881Speter else 2386251881Speter return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, 2387251881Speter _("Unknown status for lock command")); 2388251881Speter 2389251881Speter if (lock_func) 2390251881Speter callback_err = lock_func(lock_baton, path, TRUE, 2391251881Speter err ? NULL : lock, 2392251881Speter err, iterpool); 2393251881Speter else 2394251881Speter callback_err = SVN_NO_ERROR; 2395251881Speter 2396251881Speter svn_error_clear(err); 2397251881Speter 2398251881Speter if (callback_err) 2399251881Speter return callback_err; 2400251881Speter } 2401251881Speter 2402251881Speter /* If we didn't break early above, and the whole hash was traversed, 2403251881Speter read the final "done" from the server. */ 2404251881Speter if (!hi) 2405251881Speter { 2406251881Speter svn_ra_svn_item_t *elt; 2407251881Speter 2408251881Speter SVN_ERR(svn_ra_svn__read_item(conn, pool, &elt)); 2409251881Speter if (elt->kind != SVN_RA_SVN_WORD || strcmp(elt->u.word, "done") != 0) 2410251881Speter return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, 2411251881Speter _("Didn't receive end marker for lock " 2412251881Speter "responses")); 2413251881Speter } 2414251881Speter 2415251881Speter SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "")); 2416251881Speter 2417251881Speter svn_pool_destroy(iterpool); 2418251881Speter 2419251881Speter return SVN_NO_ERROR; 2420251881Speter} 2421251881Speter 2422251881Speter/* Tell the server to unlock all paths in PATH_TOKENS. 2423251881Speter See svn_ra_unlock() for interface details. */ 2424251881Speterstatic svn_error_t *ra_svn_unlock(svn_ra_session_t *session, 2425251881Speter apr_hash_t *path_tokens, 2426251881Speter svn_boolean_t break_lock, 2427251881Speter svn_ra_lock_callback_t lock_func, 2428251881Speter void *lock_baton, 2429251881Speter apr_pool_t *pool) 2430251881Speter{ 2431251881Speter svn_ra_svn__session_baton_t *sess = session->priv; 2432251881Speter svn_ra_svn_conn_t *conn = sess->conn; 2433251881Speter apr_hash_index_t *hi; 2434251881Speter apr_pool_t *iterpool = svn_pool_create(pool); 2435251881Speter svn_error_t *err; 2436251881Speter const char *path; 2437251881Speter 2438251881Speter SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w(b(!", "unlock-many", 2439251881Speter break_lock)); 2440251881Speter 2441251881Speter for (hi = apr_hash_first(pool, path_tokens); hi; hi = apr_hash_next(hi)) 2442251881Speter { 2443251881Speter void *val; 2444251881Speter const void *key; 2445251881Speter const char *token; 2446251881Speter 2447251881Speter svn_pool_clear(iterpool); 2448251881Speter apr_hash_this(hi, &key, NULL, &val); 2449251881Speter path = key; 2450251881Speter 2451251881Speter if (strcmp(val, "") != 0) 2452251881Speter token = val; 2453251881Speter else 2454251881Speter token = NULL; 2455251881Speter 2456251881Speter SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "c(?c)", path, token)); 2457251881Speter } 2458251881Speter 2459251881Speter SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!))")); 2460251881Speter 2461251881Speter err = handle_auth_request(sess, pool); 2462251881Speter 2463251881Speter /* Pre-1.3 servers don't support 'unlock-many'. If unknown, fall back 2464251881Speter * to 'unlock'. 2465251881Speter */ 2466251881Speter if (err && err->apr_err == SVN_ERR_RA_SVN_UNKNOWN_CMD) 2467251881Speter { 2468251881Speter svn_error_clear(err); 2469251881Speter return ra_svn_unlock_compat(session, path_tokens, break_lock, lock_func, 2470251881Speter lock_baton, pool); 2471251881Speter } 2472251881Speter 2473251881Speter if (err) 2474251881Speter return err; 2475251881Speter 2476251881Speter /* Loop over responses to unlock files. */ 2477251881Speter for (hi = apr_hash_first(pool, path_tokens); hi; hi = apr_hash_next(hi)) 2478251881Speter { 2479251881Speter svn_ra_svn_item_t *elt; 2480251881Speter const void *key; 2481251881Speter svn_error_t *callback_err; 2482251881Speter const char *status; 2483251881Speter apr_array_header_t *list; 2484251881Speter 2485251881Speter svn_pool_clear(iterpool); 2486251881Speter 2487251881Speter SVN_ERR(svn_ra_svn__read_item(conn, iterpool, &elt)); 2488251881Speter 2489251881Speter /* The server might have encountered some sort of fatal error in 2490251881Speter the middle of the request list. If this happens, it will 2491251881Speter transmit "done" to end the lock-info early, and then the 2492251881Speter overall command response will talk about the fatal error. */ 2493251881Speter if (elt->kind == SVN_RA_SVN_WORD && (strcmp(elt->u.word, "done") == 0)) 2494251881Speter break; 2495251881Speter 2496251881Speter apr_hash_this(hi, &key, NULL, NULL); 2497251881Speter path = key; 2498251881Speter 2499251881Speter if (elt->kind != SVN_RA_SVN_LIST) 2500251881Speter return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, 2501251881Speter _("Unlock response not a list")); 2502251881Speter 2503251881Speter SVN_ERR(svn_ra_svn__parse_tuple(elt->u.list, iterpool, "wl", &status, 2504251881Speter &list)); 2505251881Speter 2506251881Speter if (strcmp(status, "failure") == 0) 2507251881Speter err = svn_ra_svn__handle_failure_status(list, iterpool); 2508251881Speter else if (strcmp(status, "success") == 0) 2509251881Speter { 2510251881Speter SVN_ERR(svn_ra_svn__parse_tuple(list, iterpool, "c", &path)); 2511251881Speter err = SVN_NO_ERROR; 2512251881Speter } 2513251881Speter else 2514251881Speter return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, 2515251881Speter _("Unknown status for unlock command")); 2516251881Speter 2517251881Speter if (lock_func) 2518251881Speter callback_err = lock_func(lock_baton, path, FALSE, NULL, err, 2519251881Speter iterpool); 2520251881Speter else 2521251881Speter callback_err = SVN_NO_ERROR; 2522251881Speter 2523251881Speter svn_error_clear(err); 2524251881Speter 2525251881Speter if (callback_err) 2526251881Speter return callback_err; 2527251881Speter } 2528251881Speter 2529251881Speter /* If we didn't break early above, and the whole hash was traversed, 2530251881Speter read the final "done" from the server. */ 2531251881Speter if (!hi) 2532251881Speter { 2533251881Speter svn_ra_svn_item_t *elt; 2534251881Speter 2535251881Speter SVN_ERR(svn_ra_svn__read_item(conn, pool, &elt)); 2536251881Speter if (elt->kind != SVN_RA_SVN_WORD || strcmp(elt->u.word, "done") != 0) 2537251881Speter return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, 2538251881Speter _("Didn't receive end marker for unlock " 2539251881Speter "responses")); 2540251881Speter } 2541251881Speter 2542251881Speter SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "")); 2543251881Speter 2544251881Speter svn_pool_destroy(iterpool); 2545251881Speter 2546251881Speter return SVN_NO_ERROR; 2547251881Speter} 2548251881Speter 2549251881Speterstatic svn_error_t *ra_svn_get_lock(svn_ra_session_t *session, 2550251881Speter svn_lock_t **lock, 2551251881Speter const char *path, 2552251881Speter apr_pool_t *pool) 2553251881Speter{ 2554251881Speter svn_ra_svn__session_baton_t *sess = session->priv; 2555251881Speter svn_ra_svn_conn_t* conn = sess->conn; 2556251881Speter apr_array_header_t *list; 2557251881Speter 2558251881Speter SVN_ERR(svn_ra_svn__write_cmd_get_lock(conn, pool, path)); 2559251881Speter 2560251881Speter /* Servers before 1.2 doesn't support locking. Check this here. */ 2561251881Speter SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess, pool), 2562251881Speter N_("Server doesn't support the get-lock " 2563251881Speter "command"))); 2564251881Speter 2565251881Speter SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "(?l)", &list)); 2566251881Speter if (list) 2567251881Speter SVN_ERR(parse_lock(list, pool, lock)); 2568251881Speter else 2569251881Speter *lock = NULL; 2570251881Speter 2571251881Speter return SVN_NO_ERROR; 2572251881Speter} 2573251881Speter 2574251881Speter/* Copied from svn_ra_get_path_relative_to_root() and de-vtable-ized 2575251881Speter to prevent a dependency cycle. */ 2576251881Speterstatic svn_error_t *path_relative_to_root(svn_ra_session_t *session, 2577251881Speter const char **rel_path, 2578251881Speter const char *url, 2579251881Speter apr_pool_t *pool) 2580251881Speter{ 2581251881Speter const char *root_url; 2582251881Speter 2583251881Speter SVN_ERR(ra_svn_get_repos_root(session, &root_url, pool)); 2584251881Speter *rel_path = svn_uri_skip_ancestor(root_url, url, pool); 2585251881Speter if (! *rel_path) 2586251881Speter return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL, 2587251881Speter _("'%s' isn't a child of repository root " 2588251881Speter "URL '%s'"), 2589251881Speter url, root_url); 2590251881Speter return SVN_NO_ERROR; 2591251881Speter} 2592251881Speter 2593251881Speterstatic svn_error_t *ra_svn_get_locks(svn_ra_session_t *session, 2594251881Speter apr_hash_t **locks, 2595251881Speter const char *path, 2596251881Speter svn_depth_t depth, 2597251881Speter apr_pool_t *pool) 2598251881Speter{ 2599251881Speter svn_ra_svn__session_baton_t *sess = session->priv; 2600251881Speter svn_ra_svn_conn_t* conn = sess->conn; 2601251881Speter apr_array_header_t *list; 2602251881Speter const char *full_url, *abs_path; 2603251881Speter int i; 2604251881Speter 2605251881Speter /* Figure out the repository abspath from PATH. */ 2606251881Speter full_url = svn_path_url_add_component2(sess->url, path, pool); 2607251881Speter SVN_ERR(path_relative_to_root(session, &abs_path, full_url, pool)); 2608251881Speter abs_path = svn_fspath__canonicalize(abs_path, pool); 2609251881Speter 2610251881Speter SVN_ERR(svn_ra_svn__write_cmd_get_locks(conn, pool, path, depth)); 2611251881Speter 2612251881Speter /* Servers before 1.2 doesn't support locking. Check this here. */ 2613251881Speter SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess, pool), 2614251881Speter N_("Server doesn't support the get-lock " 2615251881Speter "command"))); 2616251881Speter 2617251881Speter SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "l", &list)); 2618251881Speter 2619251881Speter *locks = apr_hash_make(pool); 2620251881Speter 2621251881Speter for (i = 0; i < list->nelts; ++i) 2622251881Speter { 2623251881Speter svn_lock_t *lock; 2624251881Speter svn_ra_svn_item_t *elt = &APR_ARRAY_IDX(list, i, svn_ra_svn_item_t); 2625251881Speter 2626251881Speter if (elt->kind != SVN_RA_SVN_LIST) 2627251881Speter return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, 2628251881Speter _("Lock element not a list")); 2629251881Speter SVN_ERR(parse_lock(elt->u.list, pool, &lock)); 2630251881Speter 2631251881Speter /* Filter out unwanted paths. Since Subversion only allows 2632251881Speter locks on files, we can treat depth=immediates the same as 2633251881Speter depth=files for filtering purposes. Meaning, we'll keep 2634251881Speter this lock if: 2635251881Speter 2636251881Speter a) its path is the very path we queried, or 2637251881Speter b) we've asked for a fully recursive answer, or 2638251881Speter c) we've asked for depth=files or depth=immediates, and this 2639251881Speter lock is on an immediate child of our query path. 2640251881Speter */ 2641251881Speter if ((strcmp(abs_path, lock->path) == 0) || (depth == svn_depth_infinity)) 2642251881Speter { 2643251881Speter svn_hash_sets(*locks, lock->path, lock); 2644251881Speter } 2645251881Speter else if ((depth == svn_depth_files) || (depth == svn_depth_immediates)) 2646251881Speter { 2647251881Speter const char *relpath = svn_fspath__skip_ancestor(abs_path, lock->path); 2648251881Speter if (relpath && (svn_path_component_count(relpath) == 1)) 2649251881Speter svn_hash_sets(*locks, lock->path, lock); 2650251881Speter } 2651251881Speter } 2652251881Speter 2653251881Speter return SVN_NO_ERROR; 2654251881Speter} 2655251881Speter 2656251881Speter 2657251881Speterstatic svn_error_t *ra_svn_replay(svn_ra_session_t *session, 2658251881Speter svn_revnum_t revision, 2659251881Speter svn_revnum_t low_water_mark, 2660251881Speter svn_boolean_t send_deltas, 2661251881Speter const svn_delta_editor_t *editor, 2662251881Speter void *edit_baton, 2663251881Speter apr_pool_t *pool) 2664251881Speter{ 2665251881Speter svn_ra_svn__session_baton_t *sess = session->priv; 2666251881Speter 2667251881Speter SVN_ERR(svn_ra_svn__write_cmd_replay(sess->conn, pool, revision, 2668251881Speter low_water_mark, send_deltas)); 2669251881Speter 2670251881Speter SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess, pool), 2671251881Speter N_("Server doesn't support the replay " 2672251881Speter "command"))); 2673251881Speter 2674251881Speter SVN_ERR(svn_ra_svn_drive_editor2(sess->conn, pool, editor, edit_baton, 2675251881Speter NULL, TRUE)); 2676251881Speter 2677299742Sdim return svn_error_trace(svn_ra_svn__read_cmd_response(sess->conn, pool, "")); 2678251881Speter} 2679251881Speter 2680251881Speter 2681251881Speterstatic svn_error_t * 2682251881Speterra_svn_replay_range(svn_ra_session_t *session, 2683251881Speter svn_revnum_t start_revision, 2684251881Speter svn_revnum_t end_revision, 2685251881Speter svn_revnum_t low_water_mark, 2686251881Speter svn_boolean_t send_deltas, 2687251881Speter svn_ra_replay_revstart_callback_t revstart_func, 2688251881Speter svn_ra_replay_revfinish_callback_t revfinish_func, 2689251881Speter void *replay_baton, 2690251881Speter apr_pool_t *pool) 2691251881Speter{ 2692251881Speter svn_ra_svn__session_baton_t *sess = session->priv; 2693251881Speter apr_pool_t *iterpool; 2694251881Speter svn_revnum_t rev; 2695251881Speter svn_boolean_t drive_aborted = FALSE; 2696251881Speter 2697251881Speter SVN_ERR(svn_ra_svn__write_cmd_replay_range(sess->conn, pool, 2698251881Speter start_revision, end_revision, 2699251881Speter low_water_mark, send_deltas)); 2700251881Speter 2701251881Speter SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess, pool), 2702251881Speter N_("Server doesn't support the " 2703251881Speter "replay-range command"))); 2704251881Speter 2705251881Speter iterpool = svn_pool_create(pool); 2706251881Speter for (rev = start_revision; rev <= end_revision; rev++) 2707251881Speter { 2708251881Speter const svn_delta_editor_t *editor; 2709251881Speter void *edit_baton; 2710251881Speter apr_hash_t *rev_props; 2711251881Speter const char *word; 2712251881Speter apr_array_header_t *list; 2713251881Speter 2714251881Speter svn_pool_clear(iterpool); 2715251881Speter 2716251881Speter SVN_ERR(svn_ra_svn__read_tuple(sess->conn, iterpool, 2717251881Speter "wl", &word, &list)); 2718251881Speter if (strcmp(word, "revprops") != 0) 2719251881Speter return svn_error_createf(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, 2720251881Speter _("Expected 'revprops', found '%s'"), 2721251881Speter word); 2722251881Speter 2723251881Speter SVN_ERR(svn_ra_svn__parse_proplist(list, iterpool, &rev_props)); 2724251881Speter 2725251881Speter SVN_ERR(revstart_func(rev, replay_baton, 2726251881Speter &editor, &edit_baton, 2727251881Speter rev_props, 2728251881Speter iterpool)); 2729251881Speter SVN_ERR(svn_ra_svn_drive_editor2(sess->conn, iterpool, 2730251881Speter editor, edit_baton, 2731251881Speter &drive_aborted, TRUE)); 2732251881Speter /* If drive_editor2() aborted the commit, do NOT try to call 2733251881Speter revfinish_func and commit the transaction! */ 2734251881Speter if (drive_aborted) { 2735251881Speter svn_pool_destroy(iterpool); 2736251881Speter return svn_error_create(SVN_ERR_RA_SVN_EDIT_ABORTED, NULL, 2737251881Speter _("Error while replaying commit")); 2738251881Speter } 2739251881Speter SVN_ERR(revfinish_func(rev, replay_baton, 2740251881Speter editor, edit_baton, 2741251881Speter rev_props, 2742251881Speter iterpool)); 2743251881Speter } 2744251881Speter svn_pool_destroy(iterpool); 2745251881Speter 2746299742Sdim return svn_error_trace(svn_ra_svn__read_cmd_response(sess->conn, pool, "")); 2747251881Speter} 2748251881Speter 2749251881Speter 2750251881Speterstatic svn_error_t * 2751251881Speterra_svn_has_capability(svn_ra_session_t *session, 2752251881Speter svn_boolean_t *has, 2753251881Speter const char *capability, 2754251881Speter apr_pool_t *pool) 2755251881Speter{ 2756251881Speter svn_ra_svn__session_baton_t *sess = session->priv; 2757251881Speter static const char* capabilities[][2] = 2758251881Speter { 2759251881Speter /* { ra capability string, svn:// wire capability string} */ 2760251881Speter {SVN_RA_CAPABILITY_DEPTH, SVN_RA_SVN_CAP_DEPTH}, 2761251881Speter {SVN_RA_CAPABILITY_MERGEINFO, SVN_RA_SVN_CAP_MERGEINFO}, 2762251881Speter {SVN_RA_CAPABILITY_LOG_REVPROPS, SVN_RA_SVN_CAP_LOG_REVPROPS}, 2763251881Speter {SVN_RA_CAPABILITY_PARTIAL_REPLAY, SVN_RA_SVN_CAP_PARTIAL_REPLAY}, 2764251881Speter {SVN_RA_CAPABILITY_COMMIT_REVPROPS, SVN_RA_SVN_CAP_COMMIT_REVPROPS}, 2765251881Speter {SVN_RA_CAPABILITY_ATOMIC_REVPROPS, SVN_RA_SVN_CAP_ATOMIC_REVPROPS}, 2766251881Speter {SVN_RA_CAPABILITY_INHERITED_PROPS, SVN_RA_SVN_CAP_INHERITED_PROPS}, 2767251881Speter {SVN_RA_CAPABILITY_EPHEMERAL_TXNPROPS, 2768251881Speter SVN_RA_SVN_CAP_EPHEMERAL_TXNPROPS}, 2769251881Speter {SVN_RA_CAPABILITY_GET_FILE_REVS_REVERSE, 2770251881Speter SVN_RA_SVN_CAP_GET_FILE_REVS_REVERSE}, 2771251881Speter 2772251881Speter {NULL, NULL} /* End of list marker */ 2773251881Speter }; 2774251881Speter int i; 2775251881Speter 2776251881Speter *has = FALSE; 2777251881Speter 2778251881Speter for (i = 0; capabilities[i][0]; i++) 2779251881Speter { 2780251881Speter if (strcmp(capability, capabilities[i][0]) == 0) 2781251881Speter { 2782251881Speter *has = svn_ra_svn_has_capability(sess->conn, capabilities[i][1]); 2783251881Speter return SVN_NO_ERROR; 2784251881Speter } 2785251881Speter } 2786251881Speter 2787251881Speter return svn_error_createf(SVN_ERR_UNKNOWN_CAPABILITY, NULL, 2788251881Speter _("Don't know anything about capability '%s'"), 2789251881Speter capability); 2790251881Speter} 2791251881Speter 2792251881Speterstatic svn_error_t * 2793251881Speterra_svn_get_deleted_rev(svn_ra_session_t *session, 2794251881Speter const char *path, 2795251881Speter svn_revnum_t peg_revision, 2796251881Speter svn_revnum_t end_revision, 2797251881Speter svn_revnum_t *revision_deleted, 2798251881Speter apr_pool_t *pool) 2799251881Speter 2800251881Speter{ 2801251881Speter svn_ra_svn__session_baton_t *sess_baton = session->priv; 2802251881Speter svn_ra_svn_conn_t *conn = sess_baton->conn; 2803251881Speter 2804251881Speter /* Transmit the parameters. */ 2805251881Speter SVN_ERR(svn_ra_svn__write_cmd_get_deleted_rev(conn, pool, path, 2806251881Speter peg_revision, end_revision)); 2807251881Speter 2808251881Speter /* Servers before 1.6 don't support this command. Check for this here. */ 2809251881Speter SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess_baton, pool), 2810251881Speter N_("'get-deleted-rev' not implemented"))); 2811251881Speter 2812299742Sdim return svn_error_trace(svn_ra_svn__read_cmd_response(conn, pool, "r", 2813299742Sdim revision_deleted)); 2814251881Speter} 2815251881Speter 2816251881Speterstatic svn_error_t * 2817251881Speterra_svn_register_editor_shim_callbacks(svn_ra_session_t *session, 2818251881Speter svn_delta_shim_callbacks_t *callbacks) 2819251881Speter{ 2820251881Speter svn_ra_svn__session_baton_t *sess_baton = session->priv; 2821251881Speter svn_ra_svn_conn_t *conn = sess_baton->conn; 2822251881Speter 2823251881Speter conn->shim_callbacks = callbacks; 2824251881Speter 2825251881Speter return SVN_NO_ERROR; 2826251881Speter} 2827251881Speter 2828251881Speterstatic svn_error_t * 2829251881Speterra_svn_get_inherited_props(svn_ra_session_t *session, 2830251881Speter apr_array_header_t **iprops, 2831251881Speter const char *path, 2832251881Speter svn_revnum_t revision, 2833251881Speter apr_pool_t *result_pool, 2834251881Speter apr_pool_t *scratch_pool) 2835251881Speter{ 2836251881Speter svn_ra_svn__session_baton_t *sess_baton = session->priv; 2837251881Speter svn_ra_svn_conn_t *conn = sess_baton->conn; 2838251881Speter apr_array_header_t *iproplist; 2839299742Sdim svn_boolean_t iprop_capable; 2840251881Speter 2841299742Sdim SVN_ERR(ra_svn_has_capability(session, &iprop_capable, 2842299742Sdim SVN_RA_CAPABILITY_INHERITED_PROPS, 2843299742Sdim scratch_pool)); 2844299742Sdim 2845299742Sdim /* If we don't support native iprop handling, use the implementation 2846299742Sdim in libsvn_ra */ 2847299742Sdim if (!iprop_capable) 2848299742Sdim return svn_error_create(SVN_ERR_RA_NOT_IMPLEMENTED, NULL, NULL); 2849299742Sdim 2850251881Speter SVN_ERR(svn_ra_svn__write_cmd_get_iprops(conn, scratch_pool, 2851251881Speter path, revision)); 2852251881Speter SVN_ERR(handle_auth_request(sess_baton, scratch_pool)); 2853251881Speter SVN_ERR(svn_ra_svn__read_cmd_response(conn, scratch_pool, "l", &iproplist)); 2854251881Speter SVN_ERR(parse_iproplist(iprops, iproplist, session, result_pool, 2855251881Speter scratch_pool)); 2856251881Speter 2857251881Speter return SVN_NO_ERROR; 2858251881Speter} 2859251881Speter 2860251881Speterstatic const svn_ra__vtable_t ra_svn_vtable = { 2861251881Speter svn_ra_svn_version, 2862251881Speter ra_svn_get_description, 2863251881Speter ra_svn_get_schemes, 2864251881Speter ra_svn_open, 2865299742Sdim ra_svn_dup_session, 2866251881Speter ra_svn_reparent, 2867251881Speter ra_svn_get_session_url, 2868251881Speter ra_svn_get_latest_rev, 2869251881Speter ra_svn_get_dated_rev, 2870251881Speter ra_svn_change_rev_prop, 2871251881Speter ra_svn_rev_proplist, 2872251881Speter ra_svn_rev_prop, 2873251881Speter ra_svn_commit, 2874251881Speter ra_svn_get_file, 2875251881Speter ra_svn_get_dir, 2876251881Speter ra_svn_get_mergeinfo, 2877251881Speter ra_svn_update, 2878251881Speter ra_svn_switch, 2879251881Speter ra_svn_status, 2880251881Speter ra_svn_diff, 2881251881Speter ra_svn_log, 2882251881Speter ra_svn_check_path, 2883251881Speter ra_svn_stat, 2884251881Speter ra_svn_get_uuid, 2885251881Speter ra_svn_get_repos_root, 2886251881Speter ra_svn_get_locations, 2887251881Speter ra_svn_get_location_segments, 2888251881Speter ra_svn_get_file_revs, 2889251881Speter ra_svn_lock, 2890251881Speter ra_svn_unlock, 2891251881Speter ra_svn_get_lock, 2892251881Speter ra_svn_get_locks, 2893251881Speter ra_svn_replay, 2894251881Speter ra_svn_has_capability, 2895251881Speter ra_svn_replay_range, 2896251881Speter ra_svn_get_deleted_rev, 2897251881Speter ra_svn_register_editor_shim_callbacks, 2898251881Speter ra_svn_get_inherited_props 2899251881Speter}; 2900251881Speter 2901251881Spetersvn_error_t * 2902251881Spetersvn_ra_svn__init(const svn_version_t *loader_version, 2903251881Speter const svn_ra__vtable_t **vtable, 2904251881Speter apr_pool_t *pool) 2905251881Speter{ 2906251881Speter static const svn_version_checklist_t checklist[] = 2907251881Speter { 2908251881Speter { "svn_subr", svn_subr_version }, 2909251881Speter { "svn_delta", svn_delta_version }, 2910251881Speter { NULL, NULL } 2911251881Speter }; 2912251881Speter 2913262253Speter SVN_ERR(svn_ver_check_list2(svn_ra_svn_version(), checklist, svn_ver_equal)); 2914251881Speter 2915251881Speter /* Simplified version check to make sure we can safely use the 2916251881Speter VTABLE parameter. The RA loader does a more exhaustive check. */ 2917251881Speter if (loader_version->major != SVN_VER_MAJOR) 2918251881Speter { 2919251881Speter return svn_error_createf 2920251881Speter (SVN_ERR_VERSION_MISMATCH, NULL, 2921251881Speter _("Unsupported RA loader version (%d) for ra_svn"), 2922251881Speter loader_version->major); 2923251881Speter } 2924251881Speter 2925251881Speter *vtable = &ra_svn_vtable; 2926251881Speter 2927251881Speter#ifdef SVN_HAVE_SASL 2928251881Speter SVN_ERR(svn_ra_svn__sasl_init()); 2929251881Speter#endif 2930251881Speter 2931251881Speter return SVN_NO_ERROR; 2932251881Speter} 2933251881Speter 2934251881Speter/* Compatibility wrapper for the 1.1 and before API. */ 2935251881Speter#define NAME "ra_svn" 2936251881Speter#define DESCRIPTION RA_SVN_DESCRIPTION 2937251881Speter#define VTBL ra_svn_vtable 2938251881Speter#define INITFUNC svn_ra_svn__init 2939251881Speter#define COMPAT_INITFUNC svn_ra_svn_init 2940251881Speter#include "../libsvn_ra/wrapper_template.h" 2941