/* * client.c : Functions for repository access via the Subversion protocol * * ==================================================================== * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. * ==================================================================== */ #include "svn_private_config.h" #define APR_WANT_STRFUNC #include #include #include #include #include #include "svn_hash.h" #include "svn_types.h" #include "svn_string.h" #include "svn_dirent_uri.h" #include "svn_error.h" #include "svn_time.h" #include "svn_path.h" #include "svn_pools.h" #include "svn_config.h" #include "svn_ra.h" #include "svn_ra_svn.h" #include "svn_props.h" #include "svn_mergeinfo.h" #include "svn_version.h" #include "svn_private_config.h" #include "private/svn_fspath.h" #include "../libsvn_ra/ra_loader.h" #include "ra_svn.h" #ifdef SVN_HAVE_SASL #define DO_AUTH svn_ra_svn__do_cyrus_auth #else #define DO_AUTH svn_ra_svn__do_internal_auth #endif /* We aren't using SVN_DEPTH_IS_RECURSIVE here because that macro (for whatever reason) deems svn_depth_immediates as non-recursive, which is ... kinda true, but not true enough for our purposes. We need our requested recursion level to be *at least* as recursive as the real depth we're looking for. */ #define DEPTH_TO_RECURSE(d) \ ((d) == svn_depth_unknown || (d) > svn_depth_files) typedef struct ra_svn_commit_callback_baton_t { svn_ra_svn__session_baton_t *sess_baton; apr_pool_t *pool; svn_revnum_t *new_rev; svn_commit_callback2_t callback; void *callback_baton; } ra_svn_commit_callback_baton_t; typedef struct ra_svn_reporter_baton_t { svn_ra_svn__session_baton_t *sess_baton; svn_ra_svn_conn_t *conn; apr_pool_t *pool; const svn_delta_editor_t *editor; void *edit_baton; } ra_svn_reporter_baton_t; /* Parse an svn URL's tunnel portion into tunnel, if there is a tunnel portion. */ static void parse_tunnel(const char *url, const char **tunnel, apr_pool_t *pool) { *tunnel = NULL; if (strncasecmp(url, "svn", 3) != 0) return; url += 3; /* Get the tunnel specification, if any. */ if (*url == '+') { const char *p; url++; p = strchr(url, ':'); if (!p) return; *tunnel = apr_pstrmemdup(pool, url, p - url); } } static svn_error_t *make_connection(const char *hostname, unsigned short port, apr_socket_t **sock, apr_pool_t *pool) { apr_sockaddr_t *sa; apr_status_t status; int family = APR_INET; /* Make sure we have IPV6 support first before giving apr_sockaddr_info_get APR_UNSPEC, because it may give us back an IPV6 address even if we can't create IPV6 sockets. */ #if APR_HAVE_IPV6 #ifdef MAX_SECS_TO_LINGER status = apr_socket_create(sock, APR_INET6, SOCK_STREAM, pool); #else status = apr_socket_create(sock, APR_INET6, SOCK_STREAM, APR_PROTO_TCP, pool); #endif if (status == 0) { apr_socket_close(*sock); family = APR_UNSPEC; } #endif /* Resolve the hostname. */ status = apr_sockaddr_info_get(&sa, hostname, family, port, 0, pool); if (status) return svn_error_createf(status, NULL, _("Unknown hostname '%s'"), hostname); /* Iterate through the returned list of addresses attempting to * connect to each in turn. */ do { /* Create the socket. */ #ifdef MAX_SECS_TO_LINGER /* ### old APR interface */ status = apr_socket_create(sock, sa->family, SOCK_STREAM, pool); #else status = apr_socket_create(sock, sa->family, SOCK_STREAM, APR_PROTO_TCP, pool); #endif if (status == APR_SUCCESS) { status = apr_socket_connect(*sock, sa); if (status != APR_SUCCESS) apr_socket_close(*sock); } sa = sa->next; } while (status != APR_SUCCESS && sa); if (status) return svn_error_wrap_apr(status, _("Can't connect to host '%s'"), hostname); /* Enable TCP keep-alives on the socket so we time out when * the connection breaks due to network-layer problems. * If the peer has dropped the connection due to a network partition * or a crash, or if the peer no longer considers the connection * valid because we are behind a NAT and our public IP has changed, * it will respond to the keep-alive probe with a RST instead of an * acknowledgment segment, which will cause svn to abort the session * even while it is currently blocked waiting for data from the peer. * See issue #3347. */ status = apr_socket_opt_set(*sock, APR_SO_KEEPALIVE, 1); if (status) { /* It's not a fatal error if we cannot enable keep-alives. */ } return SVN_NO_ERROR; } /* Set *DIFFS to an array of svn_prop_t, allocated in POOL, based on the property diffs in LIST, received from the server. */ static svn_error_t *parse_prop_diffs(const apr_array_header_t *list, apr_pool_t *pool, apr_array_header_t **diffs) { int i; *diffs = apr_array_make(pool, list->nelts, sizeof(svn_prop_t)); for (i = 0; i < list->nelts; i++) { svn_prop_t *prop; svn_ra_svn_item_t *elt = &APR_ARRAY_IDX(list, i, svn_ra_svn_item_t); if (elt->kind != SVN_RA_SVN_LIST) return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, _("Prop diffs element not a list")); prop = apr_array_push(*diffs); SVN_ERR(svn_ra_svn__parse_tuple(elt->u.list, pool, "c(?s)", &prop->name, &prop->value)); } return SVN_NO_ERROR; } /* Parse a lockdesc, provided in LIST as specified by the protocol into LOCK, allocated in POOL. */ static svn_error_t *parse_lock(const apr_array_header_t *list, apr_pool_t *pool, svn_lock_t **lock) { const char *cdate, *edate; *lock = svn_lock_create(pool); SVN_ERR(svn_ra_svn__parse_tuple(list, pool, "ccc(?c)c(?c)", &(*lock)->path, &(*lock)->token, &(*lock)->owner, &(*lock)->comment, &cdate, &edate)); (*lock)->path = svn_fspath__canonicalize((*lock)->path, pool); SVN_ERR(svn_time_from_cstring(&(*lock)->creation_date, cdate, pool)); if (edate) SVN_ERR(svn_time_from_cstring(&(*lock)->expiration_date, edate, pool)); return SVN_NO_ERROR; } /* --- AUTHENTICATION ROUTINES --- */ svn_error_t *svn_ra_svn__auth_response(svn_ra_svn_conn_t *conn, apr_pool_t *pool, const char *mech, const char *mech_arg) { return svn_ra_svn__write_tuple(conn, pool, "w(?c)", mech, mech_arg); } static svn_error_t *handle_auth_request(svn_ra_svn__session_baton_t *sess, apr_pool_t *pool) { svn_ra_svn_conn_t *conn = sess->conn; apr_array_header_t *mechlist; const char *realm; SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "lc", &mechlist, &realm)); if (mechlist->nelts == 0) return SVN_NO_ERROR; return DO_AUTH(sess, mechlist, realm, pool); } /* --- REPORTER IMPLEMENTATION --- */ static svn_error_t *ra_svn_set_path(void *baton, const char *path, svn_revnum_t rev, svn_depth_t depth, svn_boolean_t start_empty, const char *lock_token, apr_pool_t *pool) { ra_svn_reporter_baton_t *b = baton; SVN_ERR(svn_ra_svn__write_cmd_set_path(b->conn, pool, path, rev, start_empty, lock_token, depth)); return SVN_NO_ERROR; } static svn_error_t *ra_svn_delete_path(void *baton, const char *path, apr_pool_t *pool) { ra_svn_reporter_baton_t *b = baton; SVN_ERR(svn_ra_svn__write_cmd_delete_path(b->conn, pool, path)); return SVN_NO_ERROR; } static svn_error_t *ra_svn_link_path(void *baton, const char *path, const char *url, svn_revnum_t rev, svn_depth_t depth, svn_boolean_t start_empty, const char *lock_token, apr_pool_t *pool) { ra_svn_reporter_baton_t *b = baton; SVN_ERR(svn_ra_svn__write_cmd_link_path(b->conn, pool, path, url, rev, start_empty, lock_token, depth)); return SVN_NO_ERROR; } static svn_error_t *ra_svn_finish_report(void *baton, apr_pool_t *pool) { ra_svn_reporter_baton_t *b = baton; SVN_ERR(svn_ra_svn__write_cmd_finish_report(b->conn, b->pool)); SVN_ERR(handle_auth_request(b->sess_baton, b->pool)); SVN_ERR(svn_ra_svn_drive_editor2(b->conn, b->pool, b->editor, b->edit_baton, NULL, FALSE)); SVN_ERR(svn_ra_svn__read_cmd_response(b->conn, b->pool, "")); return SVN_NO_ERROR; } static svn_error_t *ra_svn_abort_report(void *baton, apr_pool_t *pool) { ra_svn_reporter_baton_t *b = baton; SVN_ERR(svn_ra_svn__write_cmd_abort_report(b->conn, b->pool)); return SVN_NO_ERROR; } static svn_ra_reporter3_t ra_svn_reporter = { ra_svn_set_path, ra_svn_delete_path, ra_svn_link_path, ra_svn_finish_report, ra_svn_abort_report }; /* Set *REPORTER and *REPORT_BATON to a new reporter which will drive * EDITOR/EDIT_BATON when it gets the finish_report() call. * * Allocate the new reporter in POOL. */ static svn_error_t * ra_svn_get_reporter(svn_ra_svn__session_baton_t *sess_baton, apr_pool_t *pool, const svn_delta_editor_t *editor, void *edit_baton, const char *target, svn_depth_t depth, const svn_ra_reporter3_t **reporter, void **report_baton) { ra_svn_reporter_baton_t *b; const svn_delta_editor_t *filter_editor; void *filter_baton; /* We can skip the depth filtering when the user requested depth_files or depth_infinity because the server will transmit the right stuff anyway. */ if ((depth != svn_depth_files) && (depth != svn_depth_infinity) && ! svn_ra_svn_has_capability(sess_baton->conn, SVN_RA_SVN_CAP_DEPTH)) { SVN_ERR(svn_delta_depth_filter_editor(&filter_editor, &filter_baton, editor, edit_baton, depth, *target != '\0', pool)); editor = filter_editor; edit_baton = filter_baton; } b = apr_palloc(pool, sizeof(*b)); b->sess_baton = sess_baton; b->conn = sess_baton->conn; b->pool = pool; b->editor = editor; b->edit_baton = edit_baton; *reporter = &ra_svn_reporter; *report_baton = b; return SVN_NO_ERROR; } /* --- RA LAYER IMPLEMENTATION --- */ /* (Note: *ARGV is an output parameter.) */ static svn_error_t *find_tunnel_agent(const char *tunnel, const char *hostinfo, const char ***argv, apr_hash_t *config, apr_pool_t *pool) { svn_config_t *cfg; const char *val, *var, *cmd; char **cmd_argv; apr_size_t len; apr_status_t status; int n; /* Look up the tunnel specification in config. */ cfg = config ? svn_hash_gets(config, SVN_CONFIG_CATEGORY_CONFIG) : NULL; svn_config_get(cfg, &val, SVN_CONFIG_SECTION_TUNNELS, tunnel, NULL); /* We have one predefined tunnel scheme, if it isn't overridden by config. */ if (!val && strcmp(tunnel, "ssh") == 0) { /* Killing the tunnel agent with SIGTERM leads to unsightly * stderr output from ssh, unless we pass -q. * The "-q" option to ssh is widely supported: all versions of * OpenSSH have it, the old ssh-1.x and the 2.x, 3.x ssh.com * versions have it too. If the user is using some other ssh * implementation that doesn't accept it, they can override it * in the [tunnels] section of the config. */ val = "$SVN_SSH ssh -q"; } if (!val || !*val) return svn_error_createf(SVN_ERR_BAD_URL, NULL, _("Undefined tunnel scheme '%s'"), tunnel); /* If the scheme definition begins with "$varname", it means there * is an environment variable which can override the command. */ if (*val == '$') { val++; len = strcspn(val, " "); var = apr_pstrmemdup(pool, val, len); cmd = getenv(var); if (!cmd) { cmd = val + len; while (*cmd == ' ') cmd++; if (!*cmd) return svn_error_createf(SVN_ERR_BAD_URL, NULL, _("Tunnel scheme %s requires environment " "variable %s to be defined"), tunnel, var); } } else cmd = val; /* Tokenize the command into a list of arguments. */ status = apr_tokenize_to_argv(cmd, &cmd_argv, pool); if (status != APR_SUCCESS) return svn_error_wrap_apr(status, _("Can't tokenize command '%s'"), cmd); /* Append the fixed arguments to the result. */ for (n = 0; cmd_argv[n] != NULL; n++) ; *argv = apr_palloc(pool, (n + 4) * sizeof(char *)); memcpy((void *) *argv, cmd_argv, n * sizeof(char *)); (*argv)[n++] = svn_path_uri_decode(hostinfo, pool); (*argv)[n++] = "svnserve"; (*argv)[n++] = "-t"; (*argv)[n] = NULL; return SVN_NO_ERROR; } /* This function handles any errors which occur in the child process * created for a tunnel agent. We write the error out as a command * failure; the code in ra_svn_open() to read the server's greeting * will see the error and return it to the caller. */ static void handle_child_process_error(apr_pool_t *pool, apr_status_t status, const char *desc) { svn_ra_svn_conn_t *conn; apr_file_t *in_file, *out_file; svn_error_t *err; if (apr_file_open_stdin(&in_file, pool) || apr_file_open_stdout(&out_file, pool)) return; conn = svn_ra_svn_create_conn3(NULL, in_file, out_file, SVN_DELTA_COMPRESSION_LEVEL_DEFAULT, 0, 0, pool); err = svn_error_wrap_apr(status, _("Error in child process: %s"), desc); svn_error_clear(svn_ra_svn__write_cmd_failure(conn, pool, err)); svn_error_clear(err); svn_error_clear(svn_ra_svn__flush(conn, pool)); } /* (Note: *CONN is an output parameter.) */ static svn_error_t *make_tunnel(const char **args, svn_ra_svn_conn_t **conn, apr_pool_t *pool) { apr_status_t status; apr_proc_t *proc; apr_procattr_t *attr; svn_error_t *err; status = apr_procattr_create(&attr, pool); if (status == APR_SUCCESS) status = apr_procattr_io_set(attr, 1, 1, 0); if (status == APR_SUCCESS) status = apr_procattr_cmdtype_set(attr, APR_PROGRAM_PATH); if (status == APR_SUCCESS) status = apr_procattr_child_errfn_set(attr, handle_child_process_error); proc = apr_palloc(pool, sizeof(*proc)); if (status == APR_SUCCESS) status = apr_proc_create(proc, *args, args, NULL, attr, pool); if (status != APR_SUCCESS) return svn_error_create(SVN_ERR_RA_CANNOT_CREATE_TUNNEL, svn_error_wrap_apr(status, _("Can't create tunnel")), NULL); /* Arrange for the tunnel agent to get a SIGTERM on pool * cleanup. This is a little extreme, but the alternatives * weren't working out. * * Closing the pipes and waiting for the process to die * was prone to mysterious hangs which are difficult to * diagnose (e.g. svnserve dumps core due to unrelated bug; * sshd goes into zombie state; ssh connection is never * closed; ssh never terminates). * See also the long dicussion in issue #2580 if you really * want to know various reasons for these problems and * the different opinions on this issue. * * On Win32, APR does not support KILL_ONLY_ONCE. It only has * KILL_ALWAYS and KILL_NEVER. Other modes are converted to * KILL_ALWAYS, which immediately calls TerminateProcess(). * This instantly kills the tunnel, leaving sshd and svnserve * on a remote machine running indefinitely. These processes * accumulate. The problem is most often seen with a fast client * machine and a modest internet connection, as the tunnel * is killed before being able to gracefully complete the * session. In that case, svn is unusable 100% of the time on * the windows machine. Thus, on Win32, we use KILL_NEVER and * take the lesser of two evils. */ #ifdef WIN32 apr_pool_note_subprocess(pool, proc, APR_KILL_NEVER); #else apr_pool_note_subprocess(pool, proc, APR_KILL_ONLY_ONCE); #endif /* APR pipe objects inherit by default. But we don't want the * tunnel agent's pipes held open by future child processes * (such as other ra_svn sessions), so turn that off. */ apr_file_inherit_unset(proc->in); apr_file_inherit_unset(proc->out); /* Guard against dotfile output to stdout on the server. */ *conn = svn_ra_svn_create_conn3(NULL, proc->out, proc->in, SVN_DELTA_COMPRESSION_LEVEL_DEFAULT, 0, 0, pool); err = svn_ra_svn__skip_leading_garbage(*conn, pool); if (err) return svn_error_quick_wrap( err, _("To better debug SSH connection problems, remove the -q " "option from 'ssh' in the [tunnels] section of your " "Subversion configuration file.")); return SVN_NO_ERROR; } /* Parse URL inot URI, validating it and setting the default port if none was given. Allocate the URI fileds out of POOL. */ static svn_error_t *parse_url(const char *url, apr_uri_t *uri, apr_pool_t *pool) { apr_status_t apr_err; apr_err = apr_uri_parse(pool, url, uri); if (apr_err != 0) return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL, _("Illegal svn repository URL '%s'"), url); if (! uri->port) uri->port = SVN_RA_SVN_PORT; return SVN_NO_ERROR; } /* Open a session to URL, returning it in *SESS_P, allocating it in POOL. URI is a parsed version of URL. CALLBACKS and CALLBACKS_BATON are provided by the caller of ra_svn_open. If tunnel_argv is non-null, it points to a program argument list to use when invoking the tunnel agent. */ static svn_error_t *open_session(svn_ra_svn__session_baton_t **sess_p, const char *url, const apr_uri_t *uri, const char **tunnel_argv, const svn_ra_callbacks2_t *callbacks, void *callbacks_baton, apr_pool_t *pool) { svn_ra_svn__session_baton_t *sess; svn_ra_svn_conn_t *conn; apr_socket_t *sock; apr_uint64_t minver, maxver; apr_array_header_t *mechlist, *server_caplist, *repos_caplist; const char *client_string = NULL; sess = apr_palloc(pool, sizeof(*sess)); sess->pool = pool; sess->is_tunneled = (tunnel_argv != NULL); sess->url = apr_pstrdup(pool, url); sess->user = uri->user; sess->hostname = uri->hostname; sess->realm_prefix = apr_psprintf(pool, "", uri->hostname, uri->port); sess->tunnel_argv = tunnel_argv; sess->callbacks = callbacks; sess->callbacks_baton = callbacks_baton; sess->bytes_read = sess->bytes_written = 0; if (tunnel_argv) SVN_ERR(make_tunnel(tunnel_argv, &conn, pool)); else { SVN_ERR(make_connection(uri->hostname, uri->port, &sock, pool)); conn = svn_ra_svn_create_conn3(sock, NULL, NULL, SVN_DELTA_COMPRESSION_LEVEL_DEFAULT, 0, 0, pool); } /* Build the useragent string, querying the client for any customizations it wishes to note. For historical reasons, we still deliver the hard-coded client version info (SVN_RA_SVN__DEFAULT_USERAGENT) and the customized client string separately in the protocol/capabilities handshake below. But the commit logic wants the combined form for use with the SVN_PROP_TXN_USER_AGENT ephemeral property because that's consistent with our DAV approach. */ if (sess->callbacks->get_client_string != NULL) SVN_ERR(sess->callbacks->get_client_string(sess->callbacks_baton, &client_string, pool)); if (client_string) sess->useragent = apr_pstrcat(pool, SVN_RA_SVN__DEFAULT_USERAGENT " ", client_string, (char *)NULL); else sess->useragent = SVN_RA_SVN__DEFAULT_USERAGENT; /* Make sure we set conn->session before reading from it, * because the reader and writer functions expect a non-NULL value. */ sess->conn = conn; conn->session = sess; /* Read server's greeting. */ SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "nnll", &minver, &maxver, &mechlist, &server_caplist)); /* We support protocol version 2. */ if (minver > 2) return svn_error_createf(SVN_ERR_RA_SVN_BAD_VERSION, NULL, _("Server requires minimum version %d"), (int) minver); if (maxver < 2) return svn_error_createf(SVN_ERR_RA_SVN_BAD_VERSION, NULL, _("Server only supports versions up to %d"), (int) maxver); SVN_ERR(svn_ra_svn_set_capabilities(conn, server_caplist)); /* All released versions of Subversion support edit-pipeline, * so we do not support servers that do not. */ if (! svn_ra_svn_has_capability(conn, SVN_RA_SVN_CAP_EDIT_PIPELINE)) return svn_error_create(SVN_ERR_RA_SVN_BAD_VERSION, NULL, _("Server does not support edit pipelining")); /* In protocol version 2, we send back our protocol version, our * capability list, and the URL, and subsequently there is an auth * request. */ /* Client-side capabilities list: */ SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "n(wwwwww)cc(?c)", (apr_uint64_t) 2, SVN_RA_SVN_CAP_EDIT_PIPELINE, SVN_RA_SVN_CAP_SVNDIFF1, SVN_RA_SVN_CAP_ABSENT_ENTRIES, SVN_RA_SVN_CAP_DEPTH, SVN_RA_SVN_CAP_MERGEINFO, SVN_RA_SVN_CAP_LOG_REVPROPS, url, SVN_RA_SVN__DEFAULT_USERAGENT, client_string)); SVN_ERR(handle_auth_request(sess, pool)); /* This is where the security layer would go into effect if we * supported security layers, which is a ways off. */ /* Read the repository's uuid and root URL, and perhaps learn more capabilities that weren't available before now. */ SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "c?c?l", &conn->uuid, &conn->repos_root, &repos_caplist)); if (repos_caplist) SVN_ERR(svn_ra_svn_set_capabilities(conn, repos_caplist)); if (conn->repos_root) { conn->repos_root = svn_uri_canonicalize(conn->repos_root, pool); /* We should check that the returned string is a prefix of url, since that's the API guarantee, but this isn't true for 1.0 servers. Checking the length prevents client crashes. */ if (strlen(conn->repos_root) > strlen(url)) return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, _("Impossibly long repository root from " "server")); } *sess_p = sess; return SVN_NO_ERROR; } #ifdef SVN_HAVE_SASL #define RA_SVN_DESCRIPTION \ N_("Module for accessing a repository using the svn network protocol.\n" \ " - with Cyrus SASL authentication") #else #define RA_SVN_DESCRIPTION \ N_("Module for accessing a repository using the svn network protocol.") #endif static const char *ra_svn_get_description(void) { return _(RA_SVN_DESCRIPTION); } static const char * const * ra_svn_get_schemes(apr_pool_t *pool) { static const char *schemes[] = { "svn", NULL }; return schemes; } static svn_error_t *ra_svn_open(svn_ra_session_t *session, const char **corrected_url, const char *url, const svn_ra_callbacks2_t *callbacks, void *callback_baton, apr_hash_t *config, apr_pool_t *pool) { apr_pool_t *sess_pool = svn_pool_create(pool); svn_ra_svn__session_baton_t *sess; const char *tunnel, **tunnel_argv; apr_uri_t uri; svn_config_t *cfg, *cfg_client; /* We don't support server-prescribed redirections in ra-svn. */ if (corrected_url) *corrected_url = NULL; SVN_ERR(parse_url(url, &uri, sess_pool)); parse_tunnel(url, &tunnel, pool); if (tunnel) SVN_ERR(find_tunnel_agent(tunnel, uri.hostinfo, &tunnel_argv, config, pool)); else tunnel_argv = NULL; cfg_client = config ? svn_hash_gets(config, SVN_CONFIG_CATEGORY_CONFIG) : NULL; cfg = config ? svn_hash_gets(config, SVN_CONFIG_CATEGORY_SERVERS) : NULL; svn_auth_set_parameter(callbacks->auth_baton, SVN_AUTH_PARAM_CONFIG_CATEGORY_CONFIG, cfg_client); svn_auth_set_parameter(callbacks->auth_baton, SVN_AUTH_PARAM_CONFIG_CATEGORY_SERVERS, cfg); /* We open the session in a subpool so we can get rid of it if we reparent with a server that doesn't support reparenting. */ SVN_ERR(open_session(&sess, url, &uri, tunnel_argv, callbacks, callback_baton, sess_pool)); session->priv = sess; return SVN_NO_ERROR; } static svn_error_t *ra_svn_reparent(svn_ra_session_t *ra_session, const char *url, apr_pool_t *pool) { svn_ra_svn__session_baton_t *sess = ra_session->priv; svn_ra_svn_conn_t *conn = sess->conn; svn_error_t *err; apr_pool_t *sess_pool; svn_ra_svn__session_baton_t *new_sess; apr_uri_t uri; SVN_ERR(svn_ra_svn__write_cmd_reparent(conn, pool, url)); err = handle_auth_request(sess, pool); if (! err) { SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "")); sess->url = apr_pstrdup(sess->pool, url); return SVN_NO_ERROR; } else if (err->apr_err != SVN_ERR_RA_SVN_UNKNOWN_CMD) return err; /* Servers before 1.4 doesn't support this command; try to reconnect instead. */ svn_error_clear(err); /* Create a new subpool of the RA session pool. */ sess_pool = svn_pool_create(ra_session->pool); err = parse_url(url, &uri, sess_pool); if (! err) err = open_session(&new_sess, url, &uri, sess->tunnel_argv, sess->callbacks, sess->callbacks_baton, sess_pool); /* We destroy the new session pool on error, since it is allocated in the main session pool. */ if (err) { svn_pool_destroy(sess_pool); return err; } /* We have a new connection, assign it and destroy the old. */ ra_session->priv = new_sess; svn_pool_destroy(sess->pool); return SVN_NO_ERROR; } static svn_error_t *ra_svn_get_session_url(svn_ra_session_t *session, const char **url, apr_pool_t *pool) { svn_ra_svn__session_baton_t *sess = session->priv; *url = apr_pstrdup(pool, sess->url); return SVN_NO_ERROR; } static svn_error_t *ra_svn_get_latest_rev(svn_ra_session_t *session, svn_revnum_t *rev, apr_pool_t *pool) { svn_ra_svn__session_baton_t *sess_baton = session->priv; svn_ra_svn_conn_t *conn = sess_baton->conn; SVN_ERR(svn_ra_svn__write_cmd_get_latest_rev(conn, pool)); SVN_ERR(handle_auth_request(sess_baton, pool)); SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "r", rev)); return SVN_NO_ERROR; } static svn_error_t *ra_svn_get_dated_rev(svn_ra_session_t *session, svn_revnum_t *rev, apr_time_t tm, apr_pool_t *pool) { svn_ra_svn__session_baton_t *sess_baton = session->priv; svn_ra_svn_conn_t *conn = sess_baton->conn; SVN_ERR(svn_ra_svn__write_cmd_get_dated_rev(conn, pool, tm)); SVN_ERR(handle_auth_request(sess_baton, pool)); SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "r", rev)); return SVN_NO_ERROR; } /* Forward declaration. */ static svn_error_t *ra_svn_has_capability(svn_ra_session_t *session, svn_boolean_t *has, const char *capability, apr_pool_t *pool); static svn_error_t *ra_svn_change_rev_prop(svn_ra_session_t *session, svn_revnum_t rev, const char *name, const svn_string_t *const *old_value_p, const svn_string_t *value, apr_pool_t *pool) { svn_ra_svn__session_baton_t *sess_baton = session->priv; svn_ra_svn_conn_t *conn = sess_baton->conn; svn_boolean_t dont_care; const svn_string_t *old_value; svn_boolean_t has_atomic_revprops; SVN_ERR(ra_svn_has_capability(session, &has_atomic_revprops, SVN_RA_SVN_CAP_ATOMIC_REVPROPS, pool)); if (old_value_p) { /* How did you get past the same check in svn_ra_change_rev_prop2()? */ SVN_ERR_ASSERT(has_atomic_revprops); dont_care = FALSE; old_value = *old_value_p; } else { dont_care = TRUE; old_value = NULL; } if (has_atomic_revprops) SVN_ERR(svn_ra_svn__write_cmd_change_rev_prop2(conn, pool, rev, name, value, dont_care, old_value)); else SVN_ERR(svn_ra_svn__write_cmd_change_rev_prop(conn, pool, rev, name, value)); SVN_ERR(handle_auth_request(sess_baton, pool)); SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "")); return SVN_NO_ERROR; } static svn_error_t *ra_svn_get_uuid(svn_ra_session_t *session, const char **uuid, apr_pool_t *pool) { svn_ra_svn__session_baton_t *sess_baton = session->priv; svn_ra_svn_conn_t *conn = sess_baton->conn; *uuid = conn->uuid; return SVN_NO_ERROR; } static svn_error_t *ra_svn_get_repos_root(svn_ra_session_t *session, const char **url, apr_pool_t *pool) { svn_ra_svn__session_baton_t *sess_baton = session->priv; svn_ra_svn_conn_t *conn = sess_baton->conn; if (!conn->repos_root) return svn_error_create(SVN_ERR_RA_SVN_BAD_VERSION, NULL, _("Server did not send repository root")); *url = conn->repos_root; return SVN_NO_ERROR; } static svn_error_t *ra_svn_rev_proplist(svn_ra_session_t *session, svn_revnum_t rev, apr_hash_t **props, apr_pool_t *pool) { svn_ra_svn__session_baton_t *sess_baton = session->priv; svn_ra_svn_conn_t *conn = sess_baton->conn; apr_array_header_t *proplist; SVN_ERR(svn_ra_svn__write_cmd_rev_proplist(conn, pool, rev)); SVN_ERR(handle_auth_request(sess_baton, pool)); SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "l", &proplist)); SVN_ERR(svn_ra_svn__parse_proplist(proplist, pool, props)); return SVN_NO_ERROR; } static svn_error_t *ra_svn_rev_prop(svn_ra_session_t *session, svn_revnum_t rev, const char *name, svn_string_t **value, apr_pool_t *pool) { svn_ra_svn__session_baton_t *sess_baton = session->priv; svn_ra_svn_conn_t *conn = sess_baton->conn; SVN_ERR(svn_ra_svn__write_cmd_rev_prop(conn, pool, rev, name)); SVN_ERR(handle_auth_request(sess_baton, pool)); SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "(?s)", value)); return SVN_NO_ERROR; } static svn_error_t *ra_svn_end_commit(void *baton) { ra_svn_commit_callback_baton_t *ccb = baton; svn_commit_info_t *commit_info = svn_create_commit_info(ccb->pool); SVN_ERR(handle_auth_request(ccb->sess_baton, ccb->pool)); SVN_ERR(svn_ra_svn__read_tuple(ccb->sess_baton->conn, ccb->pool, "r(?c)(?c)?(?c)", &(commit_info->revision), &(commit_info->date), &(commit_info->author), &(commit_info->post_commit_err))); if (ccb->callback) SVN_ERR(ccb->callback(commit_info, ccb->callback_baton, ccb->pool)); return SVN_NO_ERROR; } static svn_error_t *ra_svn_commit(svn_ra_session_t *session, const svn_delta_editor_t **editor, void **edit_baton, apr_hash_t *revprop_table, svn_commit_callback2_t callback, void *callback_baton, apr_hash_t *lock_tokens, svn_boolean_t keep_locks, apr_pool_t *pool) { svn_ra_svn__session_baton_t *sess_baton = session->priv; svn_ra_svn_conn_t *conn = sess_baton->conn; ra_svn_commit_callback_baton_t *ccb; apr_hash_index_t *hi; apr_pool_t *iterpool; const svn_string_t *log_msg = svn_hash_gets(revprop_table, SVN_PROP_REVISION_LOG); if (log_msg == NULL && ! svn_ra_svn_has_capability(conn, SVN_RA_SVN_CAP_COMMIT_REVPROPS)) { return svn_error_createf(SVN_ERR_BAD_PROPERTY_VALUE, NULL, _("ra_svn does not support not specifying " "a log message with pre-1.5 servers; " "consider passing an empty one, or upgrading " "the server")); } else if (log_msg == NULL) /* 1.5+ server. Set LOG_MSG to something, since the 'logmsg' argument to the 'commit' protocol command is non-optional; on the server side, only REVPROP_TABLE will be used, and LOG_MSG will be ignored. The "svn:log" member of REVPROP_TABLE table is NULL, therefore the commit will have a NULL log message (not just "", really NULL). svnserve 1.5.x+ has always ignored LOG_MSG when REVPROP_TABLE was present; this was elevated to a protocol promise in r1498550 (and later documented in this comment) in order to fix the segmentation fault bug described in the log message of r1498550.*/ log_msg = svn_string_create("", pool); /* If we're sending revprops other than svn:log, make sure the server won't silently ignore them. */ if (apr_hash_count(revprop_table) > 1 && ! svn_ra_svn_has_capability(conn, SVN_RA_SVN_CAP_COMMIT_REVPROPS)) return svn_error_create(SVN_ERR_RA_NOT_IMPLEMENTED, NULL, _("Server doesn't support setting arbitrary " "revision properties during commit")); /* If the server supports ephemeral txnprops, add the one that reports the client's version level string. */ if (svn_ra_svn_has_capability(conn, SVN_RA_SVN_CAP_COMMIT_REVPROPS) && svn_ra_svn_has_capability(conn, SVN_RA_SVN_CAP_EPHEMERAL_TXNPROPS)) { svn_hash_sets(revprop_table, SVN_PROP_TXN_CLIENT_COMPAT_VERSION, svn_string_create(SVN_VER_NUMBER, pool)); svn_hash_sets(revprop_table, SVN_PROP_TXN_USER_AGENT, svn_string_create(sess_baton->useragent, pool)); } /* Tell the server we're starting the commit. Send log message here for backwards compatibility with servers before 1.5. */ SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w(c(!", "commit", log_msg->data)); if (lock_tokens) { iterpool = svn_pool_create(pool); for (hi = apr_hash_first(pool, lock_tokens); hi; hi = apr_hash_next(hi)) { const void *key; void *val; const char *path, *token; svn_pool_clear(iterpool); apr_hash_this(hi, &key, NULL, &val); path = key; token = val; SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "cc", path, token)); } svn_pool_destroy(iterpool); } SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!)b(!", keep_locks)); SVN_ERR(svn_ra_svn__write_proplist(conn, pool, revprop_table)); SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!))")); SVN_ERR(handle_auth_request(sess_baton, pool)); SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "")); /* Remember a few arguments for when the commit is over. */ ccb = apr_palloc(pool, sizeof(*ccb)); ccb->sess_baton = sess_baton; ccb->pool = pool; ccb->new_rev = NULL; ccb->callback = callback; ccb->callback_baton = callback_baton; /* Fetch an editor for the caller to drive. The editor will call * ra_svn_end_commit() upon close_edit(), at which point we'll fill * in the new_rev, committed_date, and committed_author values. */ svn_ra_svn_get_editor(editor, edit_baton, conn, pool, ra_svn_end_commit, ccb); return SVN_NO_ERROR; } /* Parse IPROPLIST, an array of svn_ra_svn_item_t structures, as a list of const char * repos relative paths and properties for those paths, storing the result as an array of svn_prop_inherited_item_t *items. */ static svn_error_t * parse_iproplist(apr_array_header_t **inherited_props, const apr_array_header_t *iproplist, svn_ra_session_t *session, apr_pool_t *result_pool, apr_pool_t *scratch_pool) { int i; const char *repos_root_url; apr_pool_t *iterpool; if (iproplist == NULL) { /* If the server doesn't have the SVN_RA_CAPABILITY_INHERITED_PROPS capability we shouldn't be asking for inherited props, but if we did and the server sent back nothing then we'll want to handle that. */ *inherited_props = NULL; return SVN_NO_ERROR; } SVN_ERR(ra_svn_get_repos_root(session, &repos_root_url, scratch_pool)); *inherited_props = apr_array_make( result_pool, iproplist->nelts, sizeof(svn_prop_inherited_item_t *)); iterpool = svn_pool_create(scratch_pool); for (i = 0; i < iproplist->nelts; i++) { apr_array_header_t *iprop_list; char *parent_rel_path; apr_hash_t *iprops; apr_hash_index_t *hi; svn_prop_inherited_item_t *new_iprop = apr_palloc(result_pool, sizeof(*new_iprop)); svn_ra_svn_item_t *elt = &APR_ARRAY_IDX(iproplist, i, svn_ra_svn_item_t); if (elt->kind != SVN_RA_SVN_LIST) return svn_error_create( SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, _("Inherited proplist element not a list")); svn_pool_clear(iterpool); SVN_ERR(svn_ra_svn__parse_tuple(elt->u.list, iterpool, "cl", &parent_rel_path, &iprop_list)); SVN_ERR(svn_ra_svn__parse_proplist(iprop_list, iterpool, &iprops)); new_iprop->path_or_url = svn_path_url_add_component2(repos_root_url, parent_rel_path, result_pool); new_iprop->prop_hash = apr_hash_make(result_pool); for (hi = apr_hash_first(iterpool, iprops); hi; hi = apr_hash_next(hi)) { const char *name = svn__apr_hash_index_key(hi); svn_string_t *value = svn__apr_hash_index_val(hi); svn_hash_sets(new_iprop->prop_hash, apr_pstrdup(result_pool, name), svn_string_dup(value, result_pool)); } APR_ARRAY_PUSH(*inherited_props, svn_prop_inherited_item_t *) = new_iprop; } svn_pool_destroy(iterpool); return SVN_NO_ERROR; } static svn_error_t *ra_svn_get_file(svn_ra_session_t *session, const char *path, svn_revnum_t rev, svn_stream_t *stream, svn_revnum_t *fetched_rev, apr_hash_t **props, apr_pool_t *pool) { svn_ra_svn__session_baton_t *sess_baton = session->priv; svn_ra_svn_conn_t *conn = sess_baton->conn; apr_array_header_t *proplist; const char *expected_digest; svn_checksum_t *expected_checksum = NULL; svn_checksum_ctx_t *checksum_ctx; apr_pool_t *iterpool; SVN_ERR(svn_ra_svn__write_cmd_get_file(conn, pool, path, rev, (props != NULL), (stream != NULL))); SVN_ERR(handle_auth_request(sess_baton, pool)); SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "(?c)rl", &expected_digest, &rev, &proplist)); if (fetched_rev) *fetched_rev = rev; if (props) SVN_ERR(svn_ra_svn__parse_proplist(proplist, pool, props)); /* We're done if the contents weren't wanted. */ if (!stream) return SVN_NO_ERROR; if (expected_digest) { SVN_ERR(svn_checksum_parse_hex(&expected_checksum, svn_checksum_md5, expected_digest, pool)); checksum_ctx = svn_checksum_ctx_create(svn_checksum_md5, pool); } /* Read the file's contents. */ iterpool = svn_pool_create(pool); while (1) { svn_ra_svn_item_t *item; svn_pool_clear(iterpool); SVN_ERR(svn_ra_svn__read_item(conn, iterpool, &item)); if (item->kind != SVN_RA_SVN_STRING) return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, _("Non-string as part of file contents")); if (item->u.string->len == 0) break; if (expected_checksum) SVN_ERR(svn_checksum_update(checksum_ctx, item->u.string->data, item->u.string->len)); SVN_ERR(svn_stream_write(stream, item->u.string->data, &item->u.string->len)); } svn_pool_destroy(iterpool); SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "")); if (expected_checksum) { svn_checksum_t *checksum; SVN_ERR(svn_checksum_final(&checksum, checksum_ctx, pool)); if (!svn_checksum_match(checksum, expected_checksum)) return svn_checksum_mismatch_err(expected_checksum, checksum, pool, _("Checksum mismatch for '%s'"), path); } return SVN_NO_ERROR; } static svn_error_t *ra_svn_get_dir(svn_ra_session_t *session, apr_hash_t **dirents, svn_revnum_t *fetched_rev, apr_hash_t **props, const char *path, svn_revnum_t rev, apr_uint32_t dirent_fields, apr_pool_t *pool) { svn_ra_svn__session_baton_t *sess_baton = session->priv; svn_ra_svn_conn_t *conn = sess_baton->conn; apr_array_header_t *proplist, *dirlist; int i; SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w(c(?r)bb(!", "get-dir", path, rev, (props != NULL), (dirents != NULL))); if (dirent_fields & SVN_DIRENT_KIND) SVN_ERR(svn_ra_svn__write_word(conn, pool, SVN_RA_SVN_DIRENT_KIND)); if (dirent_fields & SVN_DIRENT_SIZE) SVN_ERR(svn_ra_svn__write_word(conn, pool, SVN_RA_SVN_DIRENT_SIZE)); if (dirent_fields & SVN_DIRENT_HAS_PROPS) SVN_ERR(svn_ra_svn__write_word(conn, pool, SVN_RA_SVN_DIRENT_HAS_PROPS)); if (dirent_fields & SVN_DIRENT_CREATED_REV) SVN_ERR(svn_ra_svn__write_word(conn, pool, SVN_RA_SVN_DIRENT_CREATED_REV)); if (dirent_fields & SVN_DIRENT_TIME) SVN_ERR(svn_ra_svn__write_word(conn, pool, SVN_RA_SVN_DIRENT_TIME)); if (dirent_fields & SVN_DIRENT_LAST_AUTHOR) SVN_ERR(svn_ra_svn__write_word(conn, pool, SVN_RA_SVN_DIRENT_LAST_AUTHOR)); SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!))")); SVN_ERR(handle_auth_request(sess_baton, pool)); SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "rll", &rev, &proplist, &dirlist)); if (fetched_rev) *fetched_rev = rev; if (props) SVN_ERR(svn_ra_svn__parse_proplist(proplist, pool, props)); /* We're done if dirents aren't wanted. */ if (!dirents) return SVN_NO_ERROR; /* Interpret the directory list. */ *dirents = apr_hash_make(pool); for (i = 0; i < dirlist->nelts; i++) { const char *name, *kind, *cdate, *cauthor; svn_boolean_t has_props; svn_dirent_t *dirent; apr_uint64_t size; svn_revnum_t crev; svn_ra_svn_item_t *elt = &APR_ARRAY_IDX(dirlist, i, svn_ra_svn_item_t); if (elt->kind != SVN_RA_SVN_LIST) return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, _("Dirlist element not a list")); SVN_ERR(svn_ra_svn__parse_tuple(elt->u.list, pool, "cwnbr(?c)(?c)", &name, &kind, &size, &has_props, &crev, &cdate, &cauthor)); name = svn_relpath_canonicalize(name, pool); dirent = svn_dirent_create(pool); dirent->kind = svn_node_kind_from_word(kind); dirent->size = size;/* FIXME: svn_filesize_t */ dirent->has_props = has_props; dirent->created_rev = crev; /* NOTE: the tuple's format string says CDATE may be NULL. But this function does not allow that. The server has always sent us some random date, however, so this just happens to work. But let's be wary of servers that are (improperly) fixed to send NULL. Note: they should NOT be "fixed" to send NULL, as that would break any older clients which received that NULL. But we may as well be defensive against a malicous server. */ if (cdate == NULL) dirent->time = 0; else SVN_ERR(svn_time_from_cstring(&dirent->time, cdate, pool)); dirent->last_author = cauthor; svn_hash_sets(*dirents, name, dirent); } return SVN_NO_ERROR; } /* Converts a apr_uint64_t with values TRUE, FALSE or SVN_RA_SVN_UNSPECIFIED_NUMBER as provided by svn_ra_svn__parse_tuple to a svn_tristate_t */ static svn_tristate_t optbool_to_tristate(apr_uint64_t v) { if (v == TRUE) /* not just non-zero but exactly equal to 'TRUE' */ return svn_tristate_true; if (v == FALSE) return svn_tristate_false; return svn_tristate_unknown; /* Contains SVN_RA_SVN_UNSPECIFIED_NUMBER */ } /* If REVISION is SVN_INVALID_REVNUM, no value is sent to the server, which defaults to youngest. */ static svn_error_t *ra_svn_get_mergeinfo(svn_ra_session_t *session, svn_mergeinfo_catalog_t *catalog, const apr_array_header_t *paths, svn_revnum_t revision, svn_mergeinfo_inheritance_t inherit, svn_boolean_t include_descendants, apr_pool_t *pool) { svn_ra_svn__session_baton_t *sess_baton = session->priv; svn_ra_svn_conn_t *conn = sess_baton->conn; int i; apr_array_header_t *mergeinfo_tuple; svn_ra_svn_item_t *elt; const char *path; SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w((!", "get-mergeinfo")); for (i = 0; i < paths->nelts; i++) { path = APR_ARRAY_IDX(paths, i, const char *); SVN_ERR(svn_ra_svn__write_cstring(conn, pool, path)); } SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!)(?r)wb)", revision, svn_inheritance_to_word(inherit), include_descendants)); SVN_ERR(handle_auth_request(sess_baton, pool)); SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "l", &mergeinfo_tuple)); *catalog = NULL; if (mergeinfo_tuple->nelts > 0) { *catalog = apr_hash_make(pool); for (i = 0; i < mergeinfo_tuple->nelts; i++) { svn_mergeinfo_t for_path; const char *to_parse; elt = &((svn_ra_svn_item_t *) mergeinfo_tuple->elts)[i]; if (elt->kind != SVN_RA_SVN_LIST) return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, _("Mergeinfo element is not a list")); SVN_ERR(svn_ra_svn__parse_tuple(elt->u.list, pool, "cc", &path, &to_parse)); SVN_ERR(svn_mergeinfo_parse(&for_path, to_parse, pool)); /* Correct for naughty servers that send "relative" paths with leading slashes! */ svn_hash_sets(*catalog, path[0] == '/' ? path + 1 :path, for_path); } } return SVN_NO_ERROR; } static svn_error_t *ra_svn_update(svn_ra_session_t *session, const svn_ra_reporter3_t **reporter, void **report_baton, svn_revnum_t rev, const char *target, svn_depth_t depth, svn_boolean_t send_copyfrom_args, svn_boolean_t ignore_ancestry, const svn_delta_editor_t *update_editor, void *update_baton, apr_pool_t *pool, apr_pool_t *scratch_pool) { svn_ra_svn__session_baton_t *sess_baton = session->priv; svn_ra_svn_conn_t *conn = sess_baton->conn; svn_boolean_t recurse = DEPTH_TO_RECURSE(depth); /* Tell the server we want to start an update. */ SVN_ERR(svn_ra_svn__write_cmd_update(conn, pool, rev, target, recurse, depth, send_copyfrom_args, ignore_ancestry)); SVN_ERR(handle_auth_request(sess_baton, pool)); /* Fetch a reporter for the caller to drive. The reporter will drive * update_editor upon finish_report(). */ SVN_ERR(ra_svn_get_reporter(sess_baton, pool, update_editor, update_baton, target, depth, reporter, report_baton)); return SVN_NO_ERROR; } static svn_error_t * ra_svn_switch(svn_ra_session_t *session, const svn_ra_reporter3_t **reporter, void **report_baton, svn_revnum_t rev, const char *target, svn_depth_t depth, const char *switch_url, svn_boolean_t send_copyfrom_args, svn_boolean_t ignore_ancestry, const svn_delta_editor_t *update_editor, void *update_baton, apr_pool_t *result_pool, apr_pool_t *scratch_pool) { apr_pool_t *pool = result_pool; svn_ra_svn__session_baton_t *sess_baton = session->priv; svn_ra_svn_conn_t *conn = sess_baton->conn; svn_boolean_t recurse = DEPTH_TO_RECURSE(depth); /* Tell the server we want to start a switch. */ SVN_ERR(svn_ra_svn__write_cmd_switch(conn, pool, rev, target, recurse, switch_url, depth, send_copyfrom_args, ignore_ancestry)); SVN_ERR(handle_auth_request(sess_baton, pool)); /* Fetch a reporter for the caller to drive. The reporter will drive * update_editor upon finish_report(). */ SVN_ERR(ra_svn_get_reporter(sess_baton, pool, update_editor, update_baton, target, depth, reporter, report_baton)); return SVN_NO_ERROR; } static svn_error_t *ra_svn_status(svn_ra_session_t *session, const svn_ra_reporter3_t **reporter, void **report_baton, const char *target, svn_revnum_t rev, svn_depth_t depth, const svn_delta_editor_t *status_editor, void *status_baton, apr_pool_t *pool) { svn_ra_svn__session_baton_t *sess_baton = session->priv; svn_ra_svn_conn_t *conn = sess_baton->conn; svn_boolean_t recurse = DEPTH_TO_RECURSE(depth); /* Tell the server we want to start a status operation. */ SVN_ERR(svn_ra_svn__write_cmd_status(conn, pool, target, recurse, rev, depth)); SVN_ERR(handle_auth_request(sess_baton, pool)); /* Fetch a reporter for the caller to drive. The reporter will drive * status_editor upon finish_report(). */ SVN_ERR(ra_svn_get_reporter(sess_baton, pool, status_editor, status_baton, target, depth, reporter, report_baton)); return SVN_NO_ERROR; } static svn_error_t *ra_svn_diff(svn_ra_session_t *session, const svn_ra_reporter3_t **reporter, void **report_baton, svn_revnum_t rev, const char *target, svn_depth_t depth, svn_boolean_t ignore_ancestry, svn_boolean_t text_deltas, const char *versus_url, const svn_delta_editor_t *diff_editor, void *diff_baton, apr_pool_t *pool) { svn_ra_svn__session_baton_t *sess_baton = session->priv; svn_ra_svn_conn_t *conn = sess_baton->conn; svn_boolean_t recurse = DEPTH_TO_RECURSE(depth); /* Tell the server we want to start a diff. */ SVN_ERR(svn_ra_svn__write_cmd_diff(conn, pool, rev, target, recurse, ignore_ancestry, versus_url, text_deltas, depth)); SVN_ERR(handle_auth_request(sess_baton, pool)); /* Fetch a reporter for the caller to drive. The reporter will drive * diff_editor upon finish_report(). */ SVN_ERR(ra_svn_get_reporter(sess_baton, pool, diff_editor, diff_baton, target, depth, reporter, report_baton)); return SVN_NO_ERROR; } static svn_error_t * perform_ra_svn_log(svn_error_t **outer_error, svn_ra_session_t *session, const apr_array_header_t *paths, svn_revnum_t start, svn_revnum_t end, int limit, svn_boolean_t discover_changed_paths, svn_boolean_t strict_node_history, svn_boolean_t include_merged_revisions, const apr_array_header_t *revprops, svn_log_entry_receiver_t receiver, void *receiver_baton, apr_pool_t *pool) { svn_ra_svn__session_baton_t *sess_baton = session->priv; svn_ra_svn_conn_t *conn = sess_baton->conn; apr_pool_t *iterpool; int i; int nest_level = 0; const char *path; char *name; svn_boolean_t want_custom_revprops; SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w((!", "log")); if (paths) { for (i = 0; i < paths->nelts; i++) { path = APR_ARRAY_IDX(paths, i, const char *); SVN_ERR(svn_ra_svn__write_cstring(conn, pool, path)); } } SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!)(?r)(?r)bbnb!", start, end, discover_changed_paths, strict_node_history, (apr_uint64_t) limit, include_merged_revisions)); if (revprops) { want_custom_revprops = FALSE; SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!w(!", "revprops")); for (i = 0; i < revprops->nelts; i++) { name = APR_ARRAY_IDX(revprops, i, char *); SVN_ERR(svn_ra_svn__write_cstring(conn, pool, name)); if (!want_custom_revprops && strcmp(name, SVN_PROP_REVISION_AUTHOR) != 0 && strcmp(name, SVN_PROP_REVISION_DATE) != 0 && strcmp(name, SVN_PROP_REVISION_LOG) != 0) want_custom_revprops = TRUE; } SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!))")); } else { SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!w())", "all-revprops")); want_custom_revprops = TRUE; } SVN_ERR(handle_auth_request(sess_baton, pool)); /* Read the log messages. */ iterpool = svn_pool_create(pool); while (1) { apr_uint64_t has_children_param, invalid_revnum_param; apr_uint64_t has_subtractive_merge_param; svn_string_t *author, *date, *message; apr_array_header_t *cplist, *rplist; svn_log_entry_t *log_entry; svn_boolean_t has_children; svn_boolean_t subtractive_merge = FALSE; apr_uint64_t revprop_count; svn_ra_svn_item_t *item; apr_hash_t *cphash; svn_revnum_t rev; int nreceived; svn_pool_clear(iterpool); SVN_ERR(svn_ra_svn__read_item(conn, iterpool, &item)); if (item->kind == SVN_RA_SVN_WORD && strcmp(item->u.word, "done") == 0) break; if (item->kind != SVN_RA_SVN_LIST) return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, _("Log entry not a list")); SVN_ERR(svn_ra_svn__parse_tuple(item->u.list, iterpool, "lr(?s)(?s)(?s)?BBnl?B", &cplist, &rev, &author, &date, &message, &has_children_param, &invalid_revnum_param, &revprop_count, &rplist, &has_subtractive_merge_param)); if (want_custom_revprops && rplist == NULL) { /* Caller asked for custom revprops, but server is too old. */ return svn_error_create(SVN_ERR_RA_NOT_IMPLEMENTED, NULL, _("Server does not support custom revprops" " via log")); } if (has_children_param == SVN_RA_SVN_UNSPECIFIED_NUMBER) has_children = FALSE; else has_children = (svn_boolean_t) has_children_param; if (has_subtractive_merge_param == SVN_RA_SVN_UNSPECIFIED_NUMBER) subtractive_merge = FALSE; else subtractive_merge = (svn_boolean_t) has_subtractive_merge_param; /* Because the svn protocol won't let us send an invalid revnum, we have to recover that fact using the extra parameter. */ if (invalid_revnum_param != SVN_RA_SVN_UNSPECIFIED_NUMBER && invalid_revnum_param) rev = SVN_INVALID_REVNUM; if (cplist->nelts > 0) { /* Interpret the changed-paths list. */ cphash = apr_hash_make(iterpool); for (i = 0; i < cplist->nelts; i++) { svn_log_changed_path2_t *change; const char *copy_path, *action, *cpath, *kind_str; apr_uint64_t text_mods, prop_mods; svn_revnum_t copy_rev; svn_ra_svn_item_t *elt = &APR_ARRAY_IDX(cplist, i, svn_ra_svn_item_t); if (elt->kind != SVN_RA_SVN_LIST) return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, _("Changed-path entry not a list")); SVN_ERR(svn_ra_svn__parse_tuple(elt->u.list, iterpool, "cw(?cr)?(?c?BB)", &cpath, &action, ©_path, ©_rev, &kind_str, &text_mods, &prop_mods)); cpath = svn_fspath__canonicalize(cpath, iterpool); if (copy_path) copy_path = svn_fspath__canonicalize(copy_path, iterpool); change = svn_log_changed_path2_create(iterpool); change->action = *action; change->copyfrom_path = copy_path; change->copyfrom_rev = copy_rev; change->node_kind = svn_node_kind_from_word(kind_str); change->text_modified = optbool_to_tristate(text_mods); change->props_modified = optbool_to_tristate(prop_mods); svn_hash_sets(cphash, cpath, change); } } else cphash = NULL; nreceived = 0; if (! (limit && (nest_level == 0) && (++nreceived > limit)) && ! *outer_error) { svn_error_t *err; log_entry = svn_log_entry_create(iterpool); log_entry->changed_paths = cphash; log_entry->changed_paths2 = cphash; log_entry->revision = rev; log_entry->has_children = has_children; log_entry->subtractive_merge = subtractive_merge; if (rplist) SVN_ERR(svn_ra_svn__parse_proplist(rplist, iterpool, &log_entry->revprops)); if (log_entry->revprops == NULL) log_entry->revprops = apr_hash_make(iterpool); if (revprops == NULL) { /* Caller requested all revprops; set author/date/log. */ if (author) svn_hash_sets(log_entry->revprops, SVN_PROP_REVISION_AUTHOR, author); if (date) svn_hash_sets(log_entry->revprops, SVN_PROP_REVISION_DATE, date); if (message) svn_hash_sets(log_entry->revprops, SVN_PROP_REVISION_LOG, message); } else { /* Caller requested some; maybe set author/date/log. */ for (i = 0; i < revprops->nelts; i++) { name = APR_ARRAY_IDX(revprops, i, char *); if (author && strcmp(name, SVN_PROP_REVISION_AUTHOR) == 0) svn_hash_sets(log_entry->revprops, SVN_PROP_REVISION_AUTHOR, author); if (date && strcmp(name, SVN_PROP_REVISION_DATE) == 0) svn_hash_sets(log_entry->revprops, SVN_PROP_REVISION_DATE, date); if (message && strcmp(name, SVN_PROP_REVISION_LOG) == 0) svn_hash_sets(log_entry->revprops, SVN_PROP_REVISION_LOG, message); } } err = receiver(receiver_baton, log_entry, iterpool); if (err && err->apr_err == SVN_ERR_CEASE_INVOCATION) { *outer_error = svn_error_trace( svn_error_compose_create(*outer_error, err)); } else SVN_ERR(err); if (log_entry->has_children) { nest_level++; } if (! SVN_IS_VALID_REVNUM(log_entry->revision)) { SVN_ERR_ASSERT(nest_level); nest_level--; } } } svn_pool_destroy(iterpool); /* Read the response. */ return svn_error_trace(svn_ra_svn__read_cmd_response(conn, pool, "")); } static svn_error_t * ra_svn_log(svn_ra_session_t *session, const apr_array_header_t *paths, svn_revnum_t start, svn_revnum_t end, int limit, svn_boolean_t discover_changed_paths, svn_boolean_t strict_node_history, svn_boolean_t include_merged_revisions, const apr_array_header_t *revprops, svn_log_entry_receiver_t receiver, void *receiver_baton, apr_pool_t *pool) { svn_error_t *outer_error = NULL; svn_error_t *err; err = svn_error_trace(perform_ra_svn_log(&outer_error, session, paths, start, end, limit, discover_changed_paths, strict_node_history, include_merged_revisions, revprops, receiver, receiver_baton, pool)); return svn_error_trace( svn_error_compose_create(outer_error, err)); } static svn_error_t *ra_svn_check_path(svn_ra_session_t *session, const char *path, svn_revnum_t rev, svn_node_kind_t *kind, apr_pool_t *pool) { svn_ra_svn__session_baton_t *sess_baton = session->priv; svn_ra_svn_conn_t *conn = sess_baton->conn; const char *kind_word; SVN_ERR(svn_ra_svn__write_cmd_check_path(conn, pool, path, rev)); SVN_ERR(handle_auth_request(sess_baton, pool)); SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "w", &kind_word)); *kind = svn_node_kind_from_word(kind_word); return SVN_NO_ERROR; } /* If ERR is a command not supported error, wrap it in a SVN_ERR_RA_NOT_IMPLEMENTED with error message MSG. Else, return err. */ static svn_error_t *handle_unsupported_cmd(svn_error_t *err, const char *msg) { if (err && err->apr_err == SVN_ERR_RA_SVN_UNKNOWN_CMD) return svn_error_create(SVN_ERR_RA_NOT_IMPLEMENTED, err, _(msg)); return err; } static svn_error_t *ra_svn_stat(svn_ra_session_t *session, const char *path, svn_revnum_t rev, svn_dirent_t **dirent, apr_pool_t *pool) { svn_ra_svn__session_baton_t *sess_baton = session->priv; svn_ra_svn_conn_t *conn = sess_baton->conn; apr_array_header_t *list = NULL; svn_dirent_t *the_dirent; SVN_ERR(svn_ra_svn__write_cmd_stat(conn, pool, path, rev)); SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess_baton, pool), N_("'stat' not implemented"))); SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "(?l)", &list)); if (! list) { *dirent = NULL; } else { const char *kind, *cdate, *cauthor; svn_boolean_t has_props; svn_revnum_t crev; apr_uint64_t size; SVN_ERR(svn_ra_svn__parse_tuple(list, pool, "wnbr(?c)(?c)", &kind, &size, &has_props, &crev, &cdate, &cauthor)); the_dirent = svn_dirent_create(pool); the_dirent->kind = svn_node_kind_from_word(kind); the_dirent->size = size;/* FIXME: svn_filesize_t */ the_dirent->has_props = has_props; the_dirent->created_rev = crev; SVN_ERR(svn_time_from_cstring(&the_dirent->time, cdate, pool)); the_dirent->last_author = cauthor; *dirent = the_dirent; } return SVN_NO_ERROR; } static svn_error_t *ra_svn_get_locations(svn_ra_session_t *session, apr_hash_t **locations, const char *path, svn_revnum_t peg_revision, const apr_array_header_t *location_revisions, apr_pool_t *pool) { svn_ra_svn__session_baton_t *sess_baton = session->priv; svn_ra_svn_conn_t *conn = sess_baton->conn; svn_revnum_t revision; svn_boolean_t is_done; int i; /* Transmit the parameters. */ SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w(cr(!", "get-locations", path, peg_revision)); for (i = 0; i < location_revisions->nelts; i++) { revision = APR_ARRAY_IDX(location_revisions, i, svn_revnum_t); SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!r!", revision)); } SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!))")); /* Servers before 1.1 don't support this command. Check for this here. */ SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess_baton, pool), N_("'get-locations' not implemented"))); /* Read the hash items. */ is_done = FALSE; *locations = apr_hash_make(pool); while (!is_done) { svn_ra_svn_item_t *item; const char *ret_path; SVN_ERR(svn_ra_svn__read_item(conn, pool, &item)); if (item->kind == SVN_RA_SVN_WORD && strcmp(item->u.word, "done") == 0) is_done = 1; else if (item->kind != SVN_RA_SVN_LIST) return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, _("Location entry not a list")); else { SVN_ERR(svn_ra_svn__parse_tuple(item->u.list, pool, "rc", &revision, &ret_path)); ret_path = svn_fspath__canonicalize(ret_path, pool); apr_hash_set(*locations, apr_pmemdup(pool, &revision, sizeof(revision)), sizeof(revision), ret_path); } } /* Read the response. This is so the server would have a chance to * report an error. */ return svn_ra_svn__read_cmd_response(conn, pool, ""); } static svn_error_t * ra_svn_get_location_segments(svn_ra_session_t *session, const char *path, svn_revnum_t peg_revision, svn_revnum_t start_rev, svn_revnum_t end_rev, svn_location_segment_receiver_t receiver, void *receiver_baton, apr_pool_t *pool) { svn_ra_svn__session_baton_t *sess_baton = session->priv; svn_ra_svn_conn_t *conn = sess_baton->conn; svn_boolean_t is_done; apr_pool_t *iterpool = svn_pool_create(pool); /* Transmit the parameters. */ SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w(c(?r)(?r)(?r))", "get-location-segments", path, peg_revision, start_rev, end_rev)); /* Servers before 1.5 don't support this command. Check for this here. */ SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess_baton, pool), N_("'get-location-segments'" " not implemented"))); /* Parse the response. */ is_done = FALSE; while (!is_done) { svn_revnum_t range_start, range_end; svn_ra_svn_item_t *item; const char *ret_path; svn_pool_clear(iterpool); SVN_ERR(svn_ra_svn__read_item(conn, iterpool, &item)); if (item->kind == SVN_RA_SVN_WORD && strcmp(item->u.word, "done") == 0) is_done = 1; else if (item->kind != SVN_RA_SVN_LIST) return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, _("Location segment entry not a list")); else { svn_location_segment_t *segment = apr_pcalloc(iterpool, sizeof(*segment)); SVN_ERR(svn_ra_svn__parse_tuple(item->u.list, iterpool, "rr(?c)", &range_start, &range_end, &ret_path)); if (! (SVN_IS_VALID_REVNUM(range_start) && SVN_IS_VALID_REVNUM(range_end))) return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, _("Expected valid revision range")); if (ret_path) ret_path = svn_relpath_canonicalize(ret_path, iterpool); segment->path = ret_path; segment->range_start = range_start; segment->range_end = range_end; SVN_ERR(receiver(segment, receiver_baton, iterpool)); } } svn_pool_destroy(iterpool); /* Read the response. This is so the server would have a chance to * report an error. */ SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "")); return SVN_NO_ERROR; } static svn_error_t *ra_svn_get_file_revs(svn_ra_session_t *session, const char *path, svn_revnum_t start, svn_revnum_t end, svn_boolean_t include_merged_revisions, svn_file_rev_handler_t handler, void *handler_baton, apr_pool_t *pool) { svn_ra_svn__session_baton_t *sess_baton = session->priv; apr_pool_t *rev_pool, *chunk_pool; svn_boolean_t has_txdelta; svn_boolean_t had_revision = FALSE; /* One sub-pool for each revision and one for each txdelta chunk. Note that the rev_pool must live during the following txdelta. */ rev_pool = svn_pool_create(pool); chunk_pool = svn_pool_create(pool); SVN_ERR(svn_ra_svn__write_cmd_get_file_revs(sess_baton->conn, pool, path, start, end, include_merged_revisions)); /* Servers before 1.1 don't support this command. Check for this here. */ SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess_baton, pool), N_("'get-file-revs' not implemented"))); while (1) { apr_array_header_t *rev_proplist, *proplist; apr_uint64_t merged_rev_param; apr_array_header_t *props; svn_ra_svn_item_t *item; apr_hash_t *rev_props; svn_revnum_t rev; const char *p; svn_boolean_t merged_rev; svn_txdelta_window_handler_t d_handler; void *d_baton; svn_pool_clear(rev_pool); svn_pool_clear(chunk_pool); SVN_ERR(svn_ra_svn__read_item(sess_baton->conn, rev_pool, &item)); if (item->kind == SVN_RA_SVN_WORD && strcmp(item->u.word, "done") == 0) break; /* Either we've got a correct revision or we will error out below. */ had_revision = TRUE; if (item->kind != SVN_RA_SVN_LIST) return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, _("Revision entry not a list")); SVN_ERR(svn_ra_svn__parse_tuple(item->u.list, rev_pool, "crll?B", &p, &rev, &rev_proplist, &proplist, &merged_rev_param)); p = svn_fspath__canonicalize(p, rev_pool); SVN_ERR(svn_ra_svn__parse_proplist(rev_proplist, rev_pool, &rev_props)); SVN_ERR(parse_prop_diffs(proplist, rev_pool, &props)); if (merged_rev_param == SVN_RA_SVN_UNSPECIFIED_NUMBER) merged_rev = FALSE; else merged_rev = (svn_boolean_t) merged_rev_param; /* Get the first delta chunk so we know if there is a delta. */ SVN_ERR(svn_ra_svn__read_item(sess_baton->conn, chunk_pool, &item)); if (item->kind != SVN_RA_SVN_STRING) return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, _("Text delta chunk not a string")); has_txdelta = item->u.string->len > 0; SVN_ERR(handler(handler_baton, p, rev, rev_props, merged_rev, has_txdelta ? &d_handler : NULL, &d_baton, props, rev_pool)); /* Process the text delta if any. */ if (has_txdelta) { svn_stream_t *stream; if (d_handler) stream = svn_txdelta_parse_svndiff(d_handler, d_baton, TRUE, rev_pool); else stream = NULL; while (item->u.string->len > 0) { apr_size_t size; size = item->u.string->len; if (stream) SVN_ERR(svn_stream_write(stream, item->u.string->data, &size)); svn_pool_clear(chunk_pool); SVN_ERR(svn_ra_svn__read_item(sess_baton->conn, chunk_pool, &item)); if (item->kind != SVN_RA_SVN_STRING) return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, _("Text delta chunk not a string")); } if (stream) SVN_ERR(svn_stream_close(stream)); } } SVN_ERR(svn_ra_svn__read_cmd_response(sess_baton->conn, pool, "")); /* Return error if we didn't get any revisions. */ if (!had_revision) return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, _("The get-file-revs command didn't return " "any revisions")); svn_pool_destroy(chunk_pool); svn_pool_destroy(rev_pool); return SVN_NO_ERROR; } /* For each path in PATH_REVS, send a 'lock' command to the server. Used with 1.2.x series servers which support locking, but of only one path at a time. ra_svn_lock(), which supports 'lock-many' is now the default. See svn_ra_lock() docstring for interface details. */ static svn_error_t *ra_svn_lock_compat(svn_ra_session_t *session, apr_hash_t *path_revs, const char *comment, svn_boolean_t steal_lock, svn_ra_lock_callback_t lock_func, void *lock_baton, apr_pool_t *pool) { svn_ra_svn__session_baton_t *sess = session->priv; svn_ra_svn_conn_t* conn = sess->conn; apr_array_header_t *list; apr_hash_index_t *hi; apr_pool_t *iterpool = svn_pool_create(pool); for (hi = apr_hash_first(pool, path_revs); hi; hi = apr_hash_next(hi)) { svn_lock_t *lock; const void *key; const char *path; void *val; svn_revnum_t *revnum; svn_error_t *err, *callback_err = NULL; svn_pool_clear(iterpool); apr_hash_this(hi, &key, NULL, &val); path = key; revnum = val; SVN_ERR(svn_ra_svn__write_cmd_lock(conn, iterpool, path, comment, steal_lock, *revnum)); /* Servers before 1.2 doesn't support locking. Check this here. */ SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess, pool), N_("Server doesn't support " "the lock command"))); err = svn_ra_svn__read_cmd_response(conn, iterpool, "l", &list); if (!err) SVN_ERR(parse_lock(list, iterpool, &lock)); if (err && !SVN_ERR_IS_LOCK_ERROR(err)) return err; if (lock_func) callback_err = lock_func(lock_baton, path, TRUE, err ? NULL : lock, err, iterpool); svn_error_clear(err); if (callback_err) return callback_err; } svn_pool_destroy(iterpool); return SVN_NO_ERROR; } /* For each path in PATH_TOKENS, send an 'unlock' command to the server. Used with 1.2.x series servers which support unlocking, but of only one path at a time. ra_svn_unlock(), which supports 'unlock-many' is now the default. See svn_ra_unlock() docstring for interface details. */ static svn_error_t *ra_svn_unlock_compat(svn_ra_session_t *session, apr_hash_t *path_tokens, svn_boolean_t break_lock, svn_ra_lock_callback_t lock_func, void *lock_baton, apr_pool_t *pool) { svn_ra_svn__session_baton_t *sess = session->priv; svn_ra_svn_conn_t* conn = sess->conn; apr_hash_index_t *hi; apr_pool_t *iterpool = svn_pool_create(pool); for (hi = apr_hash_first(pool, path_tokens); hi; hi = apr_hash_next(hi)) { const void *key; const char *path; void *val; const char *token; svn_error_t *err, *callback_err = NULL; svn_pool_clear(iterpool); apr_hash_this(hi, &key, NULL, &val); path = key; if (strcmp(val, "") != 0) token = val; else token = NULL; SVN_ERR(svn_ra_svn__write_cmd_unlock(conn, iterpool, path, token, break_lock)); /* Servers before 1.2 don't support locking. Check this here. */ SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess, iterpool), N_("Server doesn't support the unlock " "command"))); err = svn_ra_svn__read_cmd_response(conn, iterpool, ""); if (err && !SVN_ERR_IS_UNLOCK_ERROR(err)) return err; if (lock_func) callback_err = lock_func(lock_baton, path, FALSE, NULL, err, pool); svn_error_clear(err); if (callback_err) return callback_err; } svn_pool_destroy(iterpool); return SVN_NO_ERROR; } /* Tell the server to lock all paths in PATH_REVS. See svn_ra_lock() for interface details. */ static svn_error_t *ra_svn_lock(svn_ra_session_t *session, apr_hash_t *path_revs, const char *comment, svn_boolean_t steal_lock, svn_ra_lock_callback_t lock_func, void *lock_baton, apr_pool_t *pool) { svn_ra_svn__session_baton_t *sess = session->priv; svn_ra_svn_conn_t *conn = sess->conn; apr_hash_index_t *hi; svn_error_t *err; apr_pool_t *iterpool = svn_pool_create(pool); SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w((?c)b(!", "lock-many", comment, steal_lock)); for (hi = apr_hash_first(pool, path_revs); hi; hi = apr_hash_next(hi)) { const void *key; const char *path; void *val; svn_revnum_t *revnum; svn_pool_clear(iterpool); apr_hash_this(hi, &key, NULL, &val); path = key; revnum = val; SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "c(?r)", path, *revnum)); } SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!))")); err = handle_auth_request(sess, pool); /* Pre-1.3 servers don't support 'lock-many'. If that fails, fall back * to 'lock'. */ if (err && err->apr_err == SVN_ERR_RA_SVN_UNKNOWN_CMD) { svn_error_clear(err); return ra_svn_lock_compat(session, path_revs, comment, steal_lock, lock_func, lock_baton, pool); } if (err) return err; /* Loop over responses to get lock information. */ for (hi = apr_hash_first(pool, path_revs); hi; hi = apr_hash_next(hi)) { svn_ra_svn_item_t *elt; const void *key; const char *path; svn_error_t *callback_err; const char *status; svn_lock_t *lock; apr_array_header_t *list; apr_hash_this(hi, &key, NULL, NULL); path = key; svn_pool_clear(iterpool); SVN_ERR(svn_ra_svn__read_item(conn, iterpool, &elt)); /* The server might have encountered some sort of fatal error in the middle of the request list. If this happens, it will transmit "done" to end the lock-info early, and then the overall command response will talk about the fatal error. */ if (elt->kind == SVN_RA_SVN_WORD && strcmp(elt->u.word, "done") == 0) break; if (elt->kind != SVN_RA_SVN_LIST) return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, _("Lock response not a list")); SVN_ERR(svn_ra_svn__parse_tuple(elt->u.list, iterpool, "wl", &status, &list)); if (strcmp(status, "failure") == 0) err = svn_ra_svn__handle_failure_status(list, iterpool); else if (strcmp(status, "success") == 0) { SVN_ERR(parse_lock(list, iterpool, &lock)); err = NULL; } else return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, _("Unknown status for lock command")); if (lock_func) callback_err = lock_func(lock_baton, path, TRUE, err ? NULL : lock, err, iterpool); else callback_err = SVN_NO_ERROR; svn_error_clear(err); if (callback_err) return callback_err; } /* If we didn't break early above, and the whole hash was traversed, read the final "done" from the server. */ if (!hi) { svn_ra_svn_item_t *elt; SVN_ERR(svn_ra_svn__read_item(conn, pool, &elt)); if (elt->kind != SVN_RA_SVN_WORD || strcmp(elt->u.word, "done") != 0) return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, _("Didn't receive end marker for lock " "responses")); } SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "")); svn_pool_destroy(iterpool); return SVN_NO_ERROR; } /* Tell the server to unlock all paths in PATH_TOKENS. See svn_ra_unlock() for interface details. */ static svn_error_t *ra_svn_unlock(svn_ra_session_t *session, apr_hash_t *path_tokens, svn_boolean_t break_lock, svn_ra_lock_callback_t lock_func, void *lock_baton, apr_pool_t *pool) { svn_ra_svn__session_baton_t *sess = session->priv; svn_ra_svn_conn_t *conn = sess->conn; apr_hash_index_t *hi; apr_pool_t *iterpool = svn_pool_create(pool); svn_error_t *err; const char *path; SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w(b(!", "unlock-many", break_lock)); for (hi = apr_hash_first(pool, path_tokens); hi; hi = apr_hash_next(hi)) { void *val; const void *key; const char *token; svn_pool_clear(iterpool); apr_hash_this(hi, &key, NULL, &val); path = key; if (strcmp(val, "") != 0) token = val; else token = NULL; SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "c(?c)", path, token)); } SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!))")); err = handle_auth_request(sess, pool); /* Pre-1.3 servers don't support 'unlock-many'. If unknown, fall back * to 'unlock'. */ if (err && err->apr_err == SVN_ERR_RA_SVN_UNKNOWN_CMD) { svn_error_clear(err); return ra_svn_unlock_compat(session, path_tokens, break_lock, lock_func, lock_baton, pool); } if (err) return err; /* Loop over responses to unlock files. */ for (hi = apr_hash_first(pool, path_tokens); hi; hi = apr_hash_next(hi)) { svn_ra_svn_item_t *elt; const void *key; svn_error_t *callback_err; const char *status; apr_array_header_t *list; svn_pool_clear(iterpool); SVN_ERR(svn_ra_svn__read_item(conn, iterpool, &elt)); /* The server might have encountered some sort of fatal error in the middle of the request list. If this happens, it will transmit "done" to end the lock-info early, and then the overall command response will talk about the fatal error. */ if (elt->kind == SVN_RA_SVN_WORD && (strcmp(elt->u.word, "done") == 0)) break; apr_hash_this(hi, &key, NULL, NULL); path = key; if (elt->kind != SVN_RA_SVN_LIST) return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, _("Unlock response not a list")); SVN_ERR(svn_ra_svn__parse_tuple(elt->u.list, iterpool, "wl", &status, &list)); if (strcmp(status, "failure") == 0) err = svn_ra_svn__handle_failure_status(list, iterpool); else if (strcmp(status, "success") == 0) { SVN_ERR(svn_ra_svn__parse_tuple(list, iterpool, "c", &path)); err = SVN_NO_ERROR; } else return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, _("Unknown status for unlock command")); if (lock_func) callback_err = lock_func(lock_baton, path, FALSE, NULL, err, iterpool); else callback_err = SVN_NO_ERROR; svn_error_clear(err); if (callback_err) return callback_err; } /* If we didn't break early above, and the whole hash was traversed, read the final "done" from the server. */ if (!hi) { svn_ra_svn_item_t *elt; SVN_ERR(svn_ra_svn__read_item(conn, pool, &elt)); if (elt->kind != SVN_RA_SVN_WORD || strcmp(elt->u.word, "done") != 0) return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, _("Didn't receive end marker for unlock " "responses")); } SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "")); svn_pool_destroy(iterpool); return SVN_NO_ERROR; } static svn_error_t *ra_svn_get_lock(svn_ra_session_t *session, svn_lock_t **lock, const char *path, apr_pool_t *pool) { svn_ra_svn__session_baton_t *sess = session->priv; svn_ra_svn_conn_t* conn = sess->conn; apr_array_header_t *list; SVN_ERR(svn_ra_svn__write_cmd_get_lock(conn, pool, path)); /* Servers before 1.2 doesn't support locking. Check this here. */ SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess, pool), N_("Server doesn't support the get-lock " "command"))); SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "(?l)", &list)); if (list) SVN_ERR(parse_lock(list, pool, lock)); else *lock = NULL; return SVN_NO_ERROR; } /* Copied from svn_ra_get_path_relative_to_root() and de-vtable-ized to prevent a dependency cycle. */ static svn_error_t *path_relative_to_root(svn_ra_session_t *session, const char **rel_path, const char *url, apr_pool_t *pool) { const char *root_url; SVN_ERR(ra_svn_get_repos_root(session, &root_url, pool)); *rel_path = svn_uri_skip_ancestor(root_url, url, pool); if (! *rel_path) return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL, _("'%s' isn't a child of repository root " "URL '%s'"), url, root_url); return SVN_NO_ERROR; } static svn_error_t *ra_svn_get_locks(svn_ra_session_t *session, apr_hash_t **locks, const char *path, svn_depth_t depth, apr_pool_t *pool) { svn_ra_svn__session_baton_t *sess = session->priv; svn_ra_svn_conn_t* conn = sess->conn; apr_array_header_t *list; const char *full_url, *abs_path; int i; /* Figure out the repository abspath from PATH. */ full_url = svn_path_url_add_component2(sess->url, path, pool); SVN_ERR(path_relative_to_root(session, &abs_path, full_url, pool)); abs_path = svn_fspath__canonicalize(abs_path, pool); SVN_ERR(svn_ra_svn__write_cmd_get_locks(conn, pool, path, depth)); /* Servers before 1.2 doesn't support locking. Check this here. */ SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess, pool), N_("Server doesn't support the get-lock " "command"))); SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "l", &list)); *locks = apr_hash_make(pool); for (i = 0; i < list->nelts; ++i) { svn_lock_t *lock; svn_ra_svn_item_t *elt = &APR_ARRAY_IDX(list, i, svn_ra_svn_item_t); if (elt->kind != SVN_RA_SVN_LIST) return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, _("Lock element not a list")); SVN_ERR(parse_lock(elt->u.list, pool, &lock)); /* Filter out unwanted paths. Since Subversion only allows locks on files, we can treat depth=immediates the same as depth=files for filtering purposes. Meaning, we'll keep this lock if: a) its path is the very path we queried, or b) we've asked for a fully recursive answer, or c) we've asked for depth=files or depth=immediates, and this lock is on an immediate child of our query path. */ if ((strcmp(abs_path, lock->path) == 0) || (depth == svn_depth_infinity)) { svn_hash_sets(*locks, lock->path, lock); } else if ((depth == svn_depth_files) || (depth == svn_depth_immediates)) { const char *relpath = svn_fspath__skip_ancestor(abs_path, lock->path); if (relpath && (svn_path_component_count(relpath) == 1)) svn_hash_sets(*locks, lock->path, lock); } } return SVN_NO_ERROR; } static svn_error_t *ra_svn_replay(svn_ra_session_t *session, svn_revnum_t revision, svn_revnum_t low_water_mark, svn_boolean_t send_deltas, const svn_delta_editor_t *editor, void *edit_baton, apr_pool_t *pool) { svn_ra_svn__session_baton_t *sess = session->priv; SVN_ERR(svn_ra_svn__write_cmd_replay(sess->conn, pool, revision, low_water_mark, send_deltas)); SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess, pool), N_("Server doesn't support the replay " "command"))); SVN_ERR(svn_ra_svn_drive_editor2(sess->conn, pool, editor, edit_baton, NULL, TRUE)); return svn_ra_svn__read_cmd_response(sess->conn, pool, ""); } static svn_error_t * ra_svn_replay_range(svn_ra_session_t *session, svn_revnum_t start_revision, svn_revnum_t end_revision, svn_revnum_t low_water_mark, svn_boolean_t send_deltas, svn_ra_replay_revstart_callback_t revstart_func, svn_ra_replay_revfinish_callback_t revfinish_func, void *replay_baton, apr_pool_t *pool) { svn_ra_svn__session_baton_t *sess = session->priv; apr_pool_t *iterpool; svn_revnum_t rev; svn_boolean_t drive_aborted = FALSE; SVN_ERR(svn_ra_svn__write_cmd_replay_range(sess->conn, pool, start_revision, end_revision, low_water_mark, send_deltas)); SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess, pool), N_("Server doesn't support the " "replay-range command"))); iterpool = svn_pool_create(pool); for (rev = start_revision; rev <= end_revision; rev++) { const svn_delta_editor_t *editor; void *edit_baton; apr_hash_t *rev_props; const char *word; apr_array_header_t *list; svn_pool_clear(iterpool); SVN_ERR(svn_ra_svn__read_tuple(sess->conn, iterpool, "wl", &word, &list)); if (strcmp(word, "revprops") != 0) return svn_error_createf(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, _("Expected 'revprops', found '%s'"), word); SVN_ERR(svn_ra_svn__parse_proplist(list, iterpool, &rev_props)); SVN_ERR(revstart_func(rev, replay_baton, &editor, &edit_baton, rev_props, iterpool)); SVN_ERR(svn_ra_svn_drive_editor2(sess->conn, iterpool, editor, edit_baton, &drive_aborted, TRUE)); /* If drive_editor2() aborted the commit, do NOT try to call revfinish_func and commit the transaction! */ if (drive_aborted) { svn_pool_destroy(iterpool); return svn_error_create(SVN_ERR_RA_SVN_EDIT_ABORTED, NULL, _("Error while replaying commit")); } SVN_ERR(revfinish_func(rev, replay_baton, editor, edit_baton, rev_props, iterpool)); } svn_pool_destroy(iterpool); return svn_ra_svn__read_cmd_response(sess->conn, pool, ""); } static svn_error_t * ra_svn_has_capability(svn_ra_session_t *session, svn_boolean_t *has, const char *capability, apr_pool_t *pool) { svn_ra_svn__session_baton_t *sess = session->priv; static const char* capabilities[][2] = { /* { ra capability string, svn:// wire capability string} */ {SVN_RA_CAPABILITY_DEPTH, SVN_RA_SVN_CAP_DEPTH}, {SVN_RA_CAPABILITY_MERGEINFO, SVN_RA_SVN_CAP_MERGEINFO}, {SVN_RA_CAPABILITY_LOG_REVPROPS, SVN_RA_SVN_CAP_LOG_REVPROPS}, {SVN_RA_CAPABILITY_PARTIAL_REPLAY, SVN_RA_SVN_CAP_PARTIAL_REPLAY}, {SVN_RA_CAPABILITY_COMMIT_REVPROPS, SVN_RA_SVN_CAP_COMMIT_REVPROPS}, {SVN_RA_CAPABILITY_ATOMIC_REVPROPS, SVN_RA_SVN_CAP_ATOMIC_REVPROPS}, {SVN_RA_CAPABILITY_INHERITED_PROPS, SVN_RA_SVN_CAP_INHERITED_PROPS}, {SVN_RA_CAPABILITY_EPHEMERAL_TXNPROPS, SVN_RA_SVN_CAP_EPHEMERAL_TXNPROPS}, {SVN_RA_CAPABILITY_GET_FILE_REVS_REVERSE, SVN_RA_SVN_CAP_GET_FILE_REVS_REVERSE}, {NULL, NULL} /* End of list marker */ }; int i; *has = FALSE; for (i = 0; capabilities[i][0]; i++) { if (strcmp(capability, capabilities[i][0]) == 0) { *has = svn_ra_svn_has_capability(sess->conn, capabilities[i][1]); return SVN_NO_ERROR; } } return svn_error_createf(SVN_ERR_UNKNOWN_CAPABILITY, NULL, _("Don't know anything about capability '%s'"), capability); } static svn_error_t * ra_svn_get_deleted_rev(svn_ra_session_t *session, const char *path, svn_revnum_t peg_revision, svn_revnum_t end_revision, svn_revnum_t *revision_deleted, apr_pool_t *pool) { svn_ra_svn__session_baton_t *sess_baton = session->priv; svn_ra_svn_conn_t *conn = sess_baton->conn; /* Transmit the parameters. */ SVN_ERR(svn_ra_svn__write_cmd_get_deleted_rev(conn, pool, path, peg_revision, end_revision)); /* Servers before 1.6 don't support this command. Check for this here. */ SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess_baton, pool), N_("'get-deleted-rev' not implemented"))); return svn_ra_svn__read_cmd_response(conn, pool, "r", revision_deleted); } static svn_error_t * ra_svn_register_editor_shim_callbacks(svn_ra_session_t *session, svn_delta_shim_callbacks_t *callbacks) { svn_ra_svn__session_baton_t *sess_baton = session->priv; svn_ra_svn_conn_t *conn = sess_baton->conn; conn->shim_callbacks = callbacks; return SVN_NO_ERROR; } static svn_error_t * ra_svn_get_inherited_props(svn_ra_session_t *session, apr_array_header_t **iprops, const char *path, svn_revnum_t revision, apr_pool_t *result_pool, apr_pool_t *scratch_pool) { svn_ra_svn__session_baton_t *sess_baton = session->priv; svn_ra_svn_conn_t *conn = sess_baton->conn; apr_array_header_t *iproplist; SVN_ERR(svn_ra_svn__write_cmd_get_iprops(conn, scratch_pool, path, revision)); SVN_ERR(handle_auth_request(sess_baton, scratch_pool)); SVN_ERR(svn_ra_svn__read_cmd_response(conn, scratch_pool, "l", &iproplist)); SVN_ERR(parse_iproplist(iprops, iproplist, session, result_pool, scratch_pool)); return SVN_NO_ERROR; } static const svn_ra__vtable_t ra_svn_vtable = { svn_ra_svn_version, ra_svn_get_description, ra_svn_get_schemes, ra_svn_open, ra_svn_reparent, ra_svn_get_session_url, ra_svn_get_latest_rev, ra_svn_get_dated_rev, ra_svn_change_rev_prop, ra_svn_rev_proplist, ra_svn_rev_prop, ra_svn_commit, ra_svn_get_file, ra_svn_get_dir, ra_svn_get_mergeinfo, ra_svn_update, ra_svn_switch, ra_svn_status, ra_svn_diff, ra_svn_log, ra_svn_check_path, ra_svn_stat, ra_svn_get_uuid, ra_svn_get_repos_root, ra_svn_get_locations, ra_svn_get_location_segments, ra_svn_get_file_revs, ra_svn_lock, ra_svn_unlock, ra_svn_get_lock, ra_svn_get_locks, ra_svn_replay, ra_svn_has_capability, ra_svn_replay_range, ra_svn_get_deleted_rev, ra_svn_register_editor_shim_callbacks, ra_svn_get_inherited_props }; svn_error_t * svn_ra_svn__init(const svn_version_t *loader_version, const svn_ra__vtable_t **vtable, apr_pool_t *pool) { static const svn_version_checklist_t checklist[] = { { "svn_subr", svn_subr_version }, { "svn_delta", svn_delta_version }, { NULL, NULL } }; SVN_ERR(svn_ver_check_list(svn_ra_svn_version(), checklist)); /* Simplified version check to make sure we can safely use the VTABLE parameter. The RA loader does a more exhaustive check. */ if (loader_version->major != SVN_VER_MAJOR) { return svn_error_createf (SVN_ERR_VERSION_MISMATCH, NULL, _("Unsupported RA loader version (%d) for ra_svn"), loader_version->major); } *vtable = &ra_svn_vtable; #ifdef SVN_HAVE_SASL SVN_ERR(svn_ra_svn__sasl_init()); #endif return SVN_NO_ERROR; } /* Compatibility wrapper for the 1.1 and before API. */ #define NAME "ra_svn" #define DESCRIPTION RA_SVN_DESCRIPTION #define VTBL ra_svn_vtable #define INITFUNC svn_ra_svn__init #define COMPAT_INITFUNC svn_ra_svn_init #include "../libsvn_ra/wrapper_template.h"