| /* |
| * client.c : Functions for repository access via the Subversion protocol |
| * |
| * ==================================================================== |
| * Copyright (c) 2000-2006 CollabNet. All rights reserved. |
| * |
| * This software is licensed as described in the file COPYING, which |
| * you should have received as part of this distribution. The terms |
| * are also available at http://subversion.tigris.org/license-1.html. |
| * If newer versions of this license are posted there, you may use a |
| * newer version instead, at your option. |
| * |
| * This software consists of voluntary contributions made by many |
| * individuals. For exact contribution history, see the revision |
| * history and logs, available at http://subversion.tigris.org/. |
| * ==================================================================== |
| */ |
| |
| |
| |
| #define APR_WANT_STRFUNC |
| #include <apr_want.h> |
| #include <apr_general.h> |
| #include <apr_strings.h> |
| #include <apr_network_io.h> |
| #include <apr_md5.h> |
| #include <apr_uri.h> |
| #include <assert.h> |
| |
| #include "svn_types.h" |
| #include "svn_string.h" |
| #include "svn_error.h" |
| #include "svn_time.h" |
| #include "svn_path.h" |
| #include "svn_pools.h" |
| #include "svn_config.h" |
| #include "svn_private_config.h" |
| #include "svn_ra.h" |
| #include "../libsvn_ra/ra_loader.h" |
| #include "svn_ra_svn.h" |
| #include "svn_md5.h" |
| #include "svn_props.h" |
| |
| #include "ra_svn.h" |
| |
| typedef struct { |
| apr_pool_t *pool; |
| svn_ra_svn_conn_t *conn; |
| int protocol_version; |
| svn_boolean_t is_tunneled; |
| svn_auth_baton_t *auth_baton; |
| const char *user; |
| const char *realm_prefix; |
| const char **tunnel_argv; |
| } ra_svn_session_baton_t; |
| |
| typedef struct { |
| 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_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) |
| { |
| const char *p; |
| |
| *tunnel = NULL; |
| |
| if (strncasecmp(url, "svn", 3) != 0) |
| return; |
| url += 3; |
| |
| /* Get the tunnel specification, if any. */ |
| if (*url == '+') |
| { |
| url++; |
| p = strchr(url, ':'); |
| if (!p) |
| return; |
| *tunnel = apr_pstrmemdup(pool, url, p - url); |
| url = p; |
| } |
| } |
| |
| 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); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| /* Convert a property list received from the server into a hash table. */ |
| static svn_error_t *parse_proplist(apr_array_header_t *list, apr_pool_t *pool, |
| apr_hash_t **props) |
| { |
| char *name; |
| svn_string_t *value; |
| svn_ra_svn_item_t *elt; |
| int i; |
| |
| *props = apr_hash_make(pool); |
| for (i = 0; i < list->nelts; i++) |
| { |
| elt = &((svn_ra_svn_item_t *) list->elts)[i]; |
| if (elt->kind != SVN_RA_SVN_LIST) |
| return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, |
| _("Proplist element not a list")); |
| SVN_ERR(svn_ra_svn_parse_tuple(elt->u.list, pool, "cs", &name, &value)); |
| apr_hash_set(*props, name, APR_HASH_KEY_STRING, value); |
| } |
| 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(apr_array_header_t *list, |
| apr_pool_t *pool, |
| apr_array_header_t **diffs) |
| { |
| svn_ra_svn_item_t *elt; |
| svn_prop_t *prop; |
| int i; |
| |
| *diffs = apr_array_make(pool, list->nelts, sizeof(svn_prop_t)); |
| |
| for (i = 0; i < list->nelts; i++) |
| { |
| 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(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_path_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; |
| } |
| |
| static svn_error_t *interpret_kind(const char *str, apr_pool_t *pool, |
| svn_node_kind_t *kind) |
| { |
| if (strcmp(str, "none") == 0) |
| *kind = svn_node_none; |
| else if (strcmp(str, "file") == 0) |
| *kind = svn_node_file; |
| else if (strcmp(str, "dir") == 0) |
| *kind = svn_node_dir; |
| else if (strcmp(str, "unknown") == 0) |
| *kind = svn_node_unknown; |
| else |
| return svn_error_createf(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, |
| _("Unrecognized node kind '%s' from server"), |
| str); |
| return SVN_NO_ERROR; |
| } |
| |
| /* --- AUTHENTICATION ROUTINES --- */ |
| |
| static svn_boolean_t find_mech(apr_array_header_t *mechlist, const char *mech) |
| { |
| int i; |
| svn_ra_svn_item_t *elt; |
| |
| for (i = 0; i < mechlist->nelts; i++) |
| { |
| elt = &((svn_ra_svn_item_t *) mechlist->elts)[i]; |
| if (elt->kind == SVN_RA_SVN_WORD && strcmp(elt->u.word, mech) == 0) |
| return TRUE; |
| } |
| return FALSE; |
| } |
| |
| /* Having picked a mechanism, start authentication by writing out an |
| * auth response. If COMPAT is true, also write out a version number |
| * and capability list. MECH_ARG may be NULL for mechanisms with no |
| * initial client response. */ |
| static svn_error_t *auth_response(svn_ra_svn_conn_t *conn, apr_pool_t *pool, |
| const char *mech, const char *mech_arg, |
| svn_boolean_t compat) |
| { |
| if (compat) |
| return svn_ra_svn_write_tuple(conn, pool, "nw(?c)(www)", (apr_uint64_t) 1, |
| mech, mech_arg, |
| SVN_RA_SVN_CAP_EDIT_PIPELINE, |
| SVN_RA_SVN_CAP_SVNDIFF1, |
| SVN_RA_SVN_CAP_ABSENT_ENTRIES); |
| else |
| return svn_ra_svn_write_tuple(conn, pool, "w(?c)", mech, mech_arg); |
| } |
| |
| /* Read the "success" response to ANONYMOUS or EXTERNAL authentication. */ |
| static svn_error_t *read_success(svn_ra_svn_conn_t *conn, apr_pool_t *pool) |
| { |
| const char *status, *arg; |
| |
| SVN_ERR(svn_ra_svn_read_tuple(conn, pool, "w(?c)", &status, &arg)); |
| if (strcmp(status, "failure") == 0 && arg) |
| return svn_error_createf(SVN_ERR_RA_NOT_AUTHORIZED, NULL, |
| _("Authentication error from server: %s"), arg); |
| else if (strcmp(status, "success") != 0 || arg) |
| return svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL, |
| _("Unexpected server response to authentication")); |
| return SVN_NO_ERROR; |
| } |
| |
| /* Respond to an auth request and perform authentication. REALM may |
| * be NULL for the initial authentication exchange of protocol version |
| * 1. */ |
| static svn_error_t *do_auth(ra_svn_session_baton_t *sess, |
| apr_array_header_t *mechlist, |
| const char *realm, apr_pool_t *pool) |
| { |
| svn_ra_svn_conn_t *conn = sess->conn; |
| const char *realmstring, *user, *password, *msg; |
| svn_auth_iterstate_t *iterstate; |
| void *creds; |
| svn_boolean_t compat = (realm == NULL); |
| |
| realmstring = realm ? apr_psprintf(pool, "%s %s", sess->realm_prefix, realm) |
| : sess->realm_prefix; |
| if (sess->is_tunneled && find_mech(mechlist, "EXTERNAL")) |
| { |
| /* Ask the server to use the tunnel connection environment (on |
| * Unix, that means uid) to determine the authentication name. */ |
| SVN_ERR(auth_response(conn, pool, "EXTERNAL", "", compat)); |
| return read_success(conn, pool); |
| } |
| else if (find_mech(mechlist, "ANONYMOUS")) |
| { |
| SVN_ERR(auth_response(conn, pool, "ANONYMOUS", "", compat)); |
| return read_success(conn, pool); |
| } |
| else if (find_mech(mechlist, "CRAM-MD5")) |
| { |
| SVN_ERR(svn_auth_first_credentials(&creds, &iterstate, |
| SVN_AUTH_CRED_SIMPLE, realmstring, |
| sess->auth_baton, pool)); |
| if (!creds) |
| return svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL, |
| _("Can't get password")); |
| while (creds) |
| { |
| user = ((svn_auth_cred_simple_t *) creds)->username; |
| password = ((svn_auth_cred_simple_t *) creds)->password; |
| SVN_ERR(auth_response(conn, pool, "CRAM-MD5", NULL, compat)); |
| SVN_ERR(svn_ra_svn__cram_client(conn, pool, user, password, &msg)); |
| if (!msg) |
| break; |
| SVN_ERR(svn_auth_next_credentials(&creds, iterstate, pool)); |
| } |
| if (!creds) |
| return svn_error_createf(SVN_ERR_RA_NOT_AUTHORIZED, NULL, |
| _("Authentication error from server: %s"), |
| msg); |
| SVN_ERR(svn_auth_save_credentials(iterstate, pool)); |
| return SVN_NO_ERROR; |
| } |
| else |
| return svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL, |
| _("Cannot negotiate authentication mechanism")); |
| } |
| |
| static svn_error_t *handle_auth_request(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; |
| |
| if (sess->protocol_version < 2) |
| return SVN_NO_ERROR; |
| 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_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(b->conn, pool, "set-path", "crb(?c)", path, rev, |
| start_empty, lock_token)); |
| 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(b->conn, pool, "delete-path", "c", 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_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(b->conn, pool, "link-path", "ccrb(?c)", |
| path, url, rev, start_empty, lock_token)); |
| 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(b->conn, b->pool, "finish-report", "")); |
| SVN_ERR(handle_auth_request(b->sess_baton, b->pool)); |
| SVN_ERR(svn_ra_svn_drive_editor(b->conn, b->pool, b->editor, b->edit_baton, |
| NULL)); |
| 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(b->conn, b->pool, "abort-report", "")); |
| return SVN_NO_ERROR; |
| } |
| |
| static svn_ra_reporter2_t ra_svn_reporter = { |
| ra_svn_set_path, |
| ra_svn_delete_path, |
| ra_svn_link_path, |
| ra_svn_finish_report, |
| ra_svn_abort_report |
| }; |
| |
| static void ra_svn_get_reporter(ra_svn_session_baton_t *sess_baton, |
| apr_pool_t *pool, |
| const svn_delta_editor_t *editor, |
| void *edit_baton, |
| const svn_ra_reporter2_t **reporter, |
| void **report_baton) |
| { |
| ra_svn_reporter_baton_t *b; |
| |
| 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; |
| } |
| |
| /* --- 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 ? apr_hash_get(config, SVN_CONFIG_CATEGORY_CONFIG, |
| APR_HASH_KEY_STRING) : 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) |
| val = "$SVN_SSH ssh"; |
| |
| 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(*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_conn(NULL, in_file, out_file, 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(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; |
| |
| 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_wrap_apr(status, _("Can't create tunnel")); |
| |
| /* Arrange for the tunnel agent to get a SIGKILL 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). |
| * - Killing the tunnel agent with SIGTERM leads to unsightly |
| * stderr output from ssh. |
| */ |
| apr_pool_note_subprocess(pool, proc, APR_KILL_ALWAYS); |
| |
| /* 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_conn(NULL, proc->out, proc->in, pool); |
| (*conn)->proc = proc; |
| SVN_ERR(svn_ra_svn_skip_leading_garbage(*conn, pool)); |
| 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. AUTH_BATON is 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(ra_svn_session_baton_t **sess_p, |
| const char *url, |
| const apr_uri_t *uri, |
| svn_auth_baton_t *auth_baton, |
| const char **tunnel_argv, |
| apr_pool_t *pool) |
| { |
| 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, *caplist; |
| |
| 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_conn(sock, NULL, NULL, pool); |
| } |
| |
| /* Read server's greeting. */ |
| SVN_ERR(svn_ra_svn_read_cmd_response(conn, pool, "nnll", &minver, &maxver, |
| &mechlist, &caplist)); |
| /* We support protocol versions 1 and 2. */ |
| if (minver > 2) |
| return svn_error_createf(SVN_ERR_RA_SVN_BAD_VERSION, NULL, |
| _("Server requires minimum version %d"), |
| (int) minver); |
| SVN_ERR(svn_ra_svn_set_capabilities(conn, caplist)); |
| |
| sess = apr_palloc(pool, sizeof(*sess)); |
| sess->pool = pool; |
| sess->conn = conn; |
| sess->protocol_version = (maxver > 2) ? 2 : maxver; |
| sess->is_tunneled = (tunnel_argv != NULL); |
| sess->auth_baton = auth_baton; |
| sess->user = uri->user; |
| sess->realm_prefix = apr_psprintf(pool, "<svn://%s:%d>", uri->hostname, |
| uri->port); |
| sess->tunnel_argv = tunnel_argv; |
| |
| /* In protocol version 2, we send back our protocol version, our |
| * capability list, and the URL, and subsequently there is an auth |
| * request. In version 1, we send back the protocol version, auth |
| * mechanism, mechanism initial response, and capability list, and; |
| * then send the URL after authentication. do_auth temporarily has |
| * support for the mixed-style response. */ |
| /* When we punt support for protocol version 1, we should: |
| * - Eliminate this conditional and the similar one below |
| * - Remove v1 support from auth_response and inline it into do_auth |
| * - Remove the (realm == NULL) support from do_auth |
| * - Inline do_auth into handle_auth_request |
| * - Remove the protocol version check from handle_auth_request */ |
| if (sess->protocol_version == 1) |
| { |
| SVN_ERR(do_auth(sess, mechlist, NULL, pool)); |
| SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "c", url)); |
| } |
| else |
| { |
| SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "n(www)c", (apr_uint64_t) 2, |
| SVN_RA_SVN_CAP_EDIT_PIPELINE, |
| SVN_RA_SVN_CAP_SVNDIFF1, |
| SVN_RA_SVN_CAP_ABSENT_ENTRIES, url)); |
| 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. */ |
| SVN_ERR(svn_ra_svn_read_cmd_response(conn, pool, "c?c", &conn->uuid, |
| &conn->repos_root)); |
| if (conn->repos_root) |
| { |
| conn->repos_root = svn_path_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; |
| } |
| |
| |
| #define RA_SVN_DESCRIPTION \ |
| N_("Module for accessing a repository using the svn network protocol.") |
| |
| 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 *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); |
| ra_svn_session_baton_t *sess; |
| const char *tunnel, **tunnel_argv; |
| apr_uri_t uri; |
| |
| 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; |
| |
| /* 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, callbacks->auth_baton, tunnel_argv, |
| 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) |
| { |
| 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; |
| ra_svn_session_baton_t *new_sess; |
| apr_uri_t uri; |
| |
| SVN_ERR(svn_ra_svn_write_cmd(conn, pool, "reparent", "c", url)); |
| err = handle_auth_request(sess, pool); |
| if (! err) |
| return svn_ra_svn_read_cmd_response(conn, pool, ""); |
| 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->auth_baton, |
| sess->tunnel_argv, 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_latest_rev(svn_ra_session_t *session, |
| svn_revnum_t *rev, apr_pool_t *pool) |
| { |
| 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(conn, pool, "get-latest-rev", "")); |
| 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) |
| { |
| 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(conn, pool, "get-dated-rev", "c", |
| svn_time_to_cstring(tm, 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_change_rev_prop(svn_ra_session_t *session, svn_revnum_t rev, |
| const char *name, |
| const svn_string_t *value, |
| apr_pool_t *pool) |
| { |
| 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(conn, pool, "change-rev-prop", "rc?s", |
| 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) |
| { |
| 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) |
| { |
| 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) |
| { |
| 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(conn, pool, "rev-proplist", "r", rev)); |
| SVN_ERR(handle_auth_request(sess_baton, pool)); |
| SVN_ERR(svn_ra_svn_read_cmd_response(conn, pool, "l", &proplist)); |
| SVN_ERR(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) |
| { |
| 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(conn, pool, "rev-prop", "rc", 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))); |
| |
| return ccb->callback(commit_info, ccb->callback_baton, ccb->pool); |
| |
| } |
| |
| static svn_error_t *ra_svn_commit(svn_ra_session_t *session, |
| const svn_delta_editor_t **editor, |
| void **edit_baton, |
| const char *log_msg, |
| svn_commit_callback2_t callback, |
| void *callback_baton, |
| apr_hash_t *lock_tokens, |
| svn_boolean_t keep_locks, |
| apr_pool_t *pool) |
| { |
| 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; |
| |
| /* Tell the server we're starting the commit. */ |
| SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "w(c(!", "commit", log_msg)); |
| 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(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->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; |
| } |
| |
| 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) |
| { |
| ra_svn_session_baton_t *sess_baton = session->priv; |
| svn_ra_svn_conn_t *conn = sess_baton->conn; |
| apr_array_header_t *proplist; |
| unsigned char digest[APR_MD5_DIGESTSIZE]; |
| const char *expected_checksum, *hex_digest; |
| apr_md5_ctx_t md5_context; |
| apr_pool_t *iterpool; |
| |
| SVN_ERR(svn_ra_svn_write_cmd(conn, pool, "get-file", "c(?r)bb", 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_checksum, |
| &rev, &proplist)); |
| |
| if (fetched_rev) |
| *fetched_rev = rev; |
| if (props) |
| SVN_ERR(parse_proplist(proplist, pool, props)); |
| |
| /* We're done if the contents weren't wanted. */ |
| if (!stream) |
| return SVN_NO_ERROR; |
| |
| if (expected_checksum) |
| apr_md5_init(&md5_context); |
| |
| /* 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) |
| apr_md5_update(&md5_context, 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) |
| { |
| apr_md5_final(digest, &md5_context); |
| hex_digest = svn_md5_digest_to_cstring_display(digest, pool); |
| if (strcmp(hex_digest, expected_checksum) != 0) |
| return svn_error_createf |
| (SVN_ERR_CHECKSUM_MISMATCH, NULL, |
| _("Checksum mismatch for '%s':\n" |
| " expected checksum: %s\n" |
| " actual checksum: %s\n"), |
| path, expected_checksum, hex_digest); |
| } |
| |
| 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) |
| { |
| ra_svn_session_baton_t *sess_baton = session->priv; |
| svn_ra_svn_conn_t *conn = sess_baton->conn; |
| svn_revnum_t crev; |
| apr_array_header_t *proplist, *dirlist; |
| int i; |
| svn_ra_svn_item_t *elt; |
| const char *name, *kind, *cdate, *cauthor; |
| svn_boolean_t has_props; |
| apr_uint64_t size; |
| svn_dirent_t *dirent; |
| |
| 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(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++) |
| { |
| elt = &((svn_ra_svn_item_t *) dirlist->elts)[i]; |
| 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_path_canonicalize(name, pool); |
| dirent = apr_palloc(pool, sizeof(*dirent)); |
| SVN_ERR(interpret_kind(kind, pool, &dirent->kind)); |
| dirent->size = size;/* FIXME: svn_filesize_t */ |
| dirent->has_props = has_props; |
| dirent->created_rev = crev; |
| SVN_ERR(svn_time_from_cstring(&dirent->time, cdate, pool)); |
| dirent->last_author = cauthor; |
| apr_hash_set(*dirents, name, APR_HASH_KEY_STRING, dirent); |
| } |
| |
| return SVN_NO_ERROR; |
| } |
| |
| static svn_error_t *ra_svn_update(svn_ra_session_t *session, |
| const svn_ra_reporter2_t **reporter, |
| void **report_baton, svn_revnum_t rev, |
| const char *target, svn_boolean_t recurse, |
| const svn_delta_editor_t *update_editor, |
| void *update_baton, apr_pool_t *pool) |
| { |
| ra_svn_session_baton_t *sess_baton = session->priv; |
| svn_ra_svn_conn_t *conn = sess_baton->conn; |
| |
| /* Tell the server we want to start an update. */ |
| SVN_ERR(svn_ra_svn_write_cmd(conn, pool, "update", "(?r)cb", rev, target, |
| recurse)); |
| 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(). */ |
| ra_svn_get_reporter(sess_baton, pool, update_editor, update_baton, |
| reporter, report_baton); |
| return SVN_NO_ERROR; |
| } |
| |
| static svn_error_t *ra_svn_switch(svn_ra_session_t *session, |
| const svn_ra_reporter2_t **reporter, |
| void **report_baton, svn_revnum_t rev, |
| const char *target, svn_boolean_t recurse, |
| const char *switch_url, |
| const svn_delta_editor_t *update_editor, |
| void *update_baton, apr_pool_t *pool) |
| { |
| ra_svn_session_baton_t *sess_baton = session->priv; |
| svn_ra_svn_conn_t *conn = sess_baton->conn; |
| |
| /* Tell the server we want to start a switch. */ |
| SVN_ERR(svn_ra_svn_write_cmd(conn, pool, "switch", "(?r)cbc", rev, target, |
| recurse, switch_url)); |
| 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(). */ |
| ra_svn_get_reporter(sess_baton, pool, update_editor, update_baton, |
| reporter, report_baton); |
| return SVN_NO_ERROR; |
| } |
| |
| static svn_error_t *ra_svn_status(svn_ra_session_t *session, |
| const svn_ra_reporter2_t **reporter, |
| void **report_baton, |
| const char *target, svn_revnum_t rev, |
| svn_boolean_t recurse, |
| const svn_delta_editor_t *status_editor, |
| void *status_baton, apr_pool_t *pool) |
| { |
| ra_svn_session_baton_t *sess_baton = session->priv; |
| svn_ra_svn_conn_t *conn = sess_baton->conn; |
| |
| /* Tell the server we want to start a status operation. */ |
| SVN_ERR(svn_ra_svn_write_cmd(conn, pool, "status", "cb(?r)", |
| target, recurse, rev)); |
| 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(). */ |
| ra_svn_get_reporter(sess_baton, pool, status_editor, status_baton, |
| reporter, report_baton); |
| return SVN_NO_ERROR; |
| } |
| |
| static svn_error_t *ra_svn_diff(svn_ra_session_t *session, |
| const svn_ra_reporter2_t **reporter, |
| void **report_baton, |
| svn_revnum_t rev, const char *target, |
| svn_boolean_t recurse, |
| 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) |
| { |
| ra_svn_session_baton_t *sess_baton = session->priv; |
| svn_ra_svn_conn_t *conn = sess_baton->conn; |
| |
| /* Tell the server we want to start a diff. */ |
| SVN_ERR(svn_ra_svn_write_cmd(conn, pool, "diff", "(?r)cbbcb", rev, |
| target, recurse, ignore_ancestry, |
| versus_url, text_deltas)); |
| 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(). */ |
| ra_svn_get_reporter(sess_baton, pool, diff_editor, diff_baton, |
| reporter, report_baton); |
| return SVN_NO_ERROR; |
| } |
| |
| 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_log_message_receiver_t receiver, |
| void *receiver_baton, apr_pool_t *pool) |
| { |
| ra_svn_session_baton_t *sess_baton = session->priv; |
| svn_ra_svn_conn_t *conn = sess_baton->conn; |
| apr_pool_t *subpool; |
| int i; |
| const char *path, *author, *date, *message, *cpath, *action, *copy_path; |
| svn_ra_svn_item_t *item, *elt; |
| apr_array_header_t *cplist; |
| apr_hash_t *cphash; |
| svn_revnum_t rev, copy_rev; |
| svn_log_changed_path_t *change; |
| int nreceived = 0; |
| |
| SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "w((!", "log")); |
| if (paths) |
| { |
| for (i = 0; i < paths->nelts; i++) |
| { |
| path = ((const char **) paths->elts)[i]; |
| SVN_ERR(svn_ra_svn_write_cstring(conn, pool, path)); |
| } |
| } |
| SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "!)(?r)(?r)bbn)", start, end, |
| discover_changed_paths, strict_node_history, |
| (apr_uint64_t) limit)); |
| SVN_ERR(handle_auth_request(sess_baton, pool)); |
| |
| /* Read the log messages. */ |
| subpool = svn_pool_create(pool); |
| while (1) |
| { |
| SVN_ERR(svn_ra_svn_read_item(conn, subpool, &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, subpool, "lr(?c)(?c)(?c)", |
| &cplist, &rev, &author, &date, |
| &message)); |
| if (cplist->nelts > 0) |
| { |
| /* Interpret the changed-paths list. */ |
| cphash = apr_hash_make(subpool); |
| for (i = 0; i < cplist->nelts; i++) |
| { |
| elt = &((svn_ra_svn_item_t *) cplist->elts)[i]; |
| 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, subpool, "cw(?cr)", |
| &cpath, &action, ©_path, |
| ©_rev)); |
| cpath = svn_path_canonicalize(cpath, subpool); |
| if (copy_path) |
| copy_path = svn_path_canonicalize(copy_path, subpool); |
| change = apr_palloc(subpool, sizeof(*change)); |
| change->action = *action; |
| change->copyfrom_path = copy_path; |
| change->copyfrom_rev = copy_rev; |
| apr_hash_set(cphash, cpath, APR_HASH_KEY_STRING, change); |
| } |
| } |
| else |
| cphash = NULL; |
| |
| if (! (limit && ++nreceived > limit)) |
| SVN_ERR(receiver(receiver_baton, cphash, rev, author, date, message, |
| subpool)); |
| |
| apr_pool_clear(subpool); |
| } |
| apr_pool_destroy(subpool); |
| |
| /* Read the response. */ |
| SVN_ERR(svn_ra_svn_read_cmd_response(conn, pool, "")); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| |
| 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) |
| { |
| 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(conn, pool, "check-path", "c(?r)", path, rev)); |
| SVN_ERR(handle_auth_request(sess_baton, pool)); |
| SVN_ERR(svn_ra_svn_read_cmd_response(conn, pool, "w", &kind_word)); |
| SVN_ERR(interpret_kind(kind_word, pool, kind)); |
| 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) |
| { |
| ra_svn_session_baton_t *sess_baton = session->priv; |
| svn_ra_svn_conn_t *conn = sess_baton->conn; |
| apr_array_header_t *list = NULL; |
| const char *kind, *cdate, *cauthor; |
| svn_revnum_t crev; |
| svn_boolean_t has_props; |
| apr_uint64_t size; |
| svn_dirent_t *the_dirent; |
| |
| SVN_ERR(svn_ra_svn_write_cmd(conn, pool, "stat", "c(?r)", path, rev)); |
| |
| SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess_baton, pool), |
| _("'stat' not implemented"))); |
| |
| SVN_ERR(svn_ra_svn_read_cmd_response(conn, pool, "(?l)", &list)); |
| |
| if (! list) |
| { |
| *dirent = NULL; |
| } |
| else |
| { |
| SVN_ERR(svn_ra_svn_parse_tuple(list, pool, "wnbr(?c)(?c)", |
| &kind, &size, &has_props, |
| &crev, &cdate, &cauthor)); |
| |
| the_dirent = apr_palloc(pool, sizeof(*the_dirent)); |
| SVN_ERR(interpret_kind(kind, pool, &the_dirent->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, |
| apr_array_header_t *location_revisions, |
| apr_pool_t *pool) |
| { |
| ra_svn_session_baton_t *sess_baton = session->priv; |
| svn_ra_svn_conn_t *conn = sess_baton->conn; |
| svn_revnum_t revision; |
| svn_ra_svn_item_t *item; |
| svn_boolean_t is_done; |
| int i; |
| const char *ret_path; |
| |
| /* 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 = ((svn_revnum_t *)location_revisions->elts)[i]; |
| 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), |
| _("'get-locations' not implemented"))); |
| |
| /* Read the hash items. */ |
| is_done = FALSE; |
| *locations = apr_hash_make(pool); |
| while (!is_done) |
| { |
| 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_path_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. */ |
| 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_ra_file_rev_handler_t handler, |
| void *handler_baton, apr_pool_t *pool) |
| { |
| ra_svn_session_baton_t *sess_baton = session->priv; |
| apr_pool_t *rev_pool, *chunk_pool; |
| svn_ra_svn_item_t *item; |
| const char *p; |
| svn_revnum_t rev; |
| apr_array_header_t *rev_proplist, *proplist; |
| apr_hash_t *rev_props; |
| apr_array_header_t *props; |
| svn_boolean_t has_txdelta; |
| svn_boolean_t had_revision = FALSE; |
| svn_stream_t *stream; |
| svn_txdelta_window_handler_t d_handler; |
| void *d_baton; |
| apr_size_t size; |
| |
| /* 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(sess_baton->conn, pool, "get-file-revs", |
| "c(?r)(?r)", path, start, end)); |
| |
| /* Servers before 1.1 don't support this command. Check for this here. */ |
| SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess_baton, pool), |
| _("'get-file-revs' not implemented"))); |
| |
| while (1) |
| { |
| 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", &p, &rev, &rev_proplist, |
| &proplist)); |
| p = svn_path_canonicalize(p, rev_pool); |
| SVN_ERR(parse_proplist(rev_proplist, rev_pool, &rev_props)); |
| SVN_ERR(parse_prop_diffs(proplist, rev_pool, &props)); |
| |
| /* 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, |
| has_txdelta ? &d_handler : NULL, &d_baton, |
| props, rev_pool)); |
| |
| /* Process the text delta if any. */ |
| if (has_txdelta) |
| { |
| if (d_handler) |
| stream = svn_txdelta_parse_svndiff(d_handler, d_baton, TRUE, |
| rev_pool); |
| else |
| stream = NULL; |
| while (item->u.string->len > 0) |
| { |
| 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) |
| { |
| 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(conn, iterpool, "lock", "c(?c)b(?r)", |
| 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), |
| _("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) |
| { |
| 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(conn, iterpool, "unlock", "c(?c)b", |
| 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), |
| _("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) |
| { |
| ra_svn_session_baton_t *sess = session->priv; |
| svn_ra_svn_conn_t *conn = sess->conn; |
| apr_hash_index_t *hi; |
| svn_ra_svn_item_t *elt; |
| svn_error_t *err, *callback_err = SVN_NO_ERROR; |
| apr_pool_t *subpool = svn_pool_create(pool); |
| const char *status; |
| svn_lock_t *lock; |
| apr_array_header_t *list = NULL; |
| |
| 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(subpool); |
| apr_hash_this(hi, &key, NULL, &val); |
| path = key; |
| revnum = val; |
| |
| SVN_ERR(svn_ra_svn_write_tuple(conn, subpool, "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)) |
| { |
| const void *key; |
| const char *path; |
| |
| apr_hash_this(hi, &key, NULL, NULL); |
| path = key; |
| |
| svn_pool_clear(subpool); |
| SVN_ERR(svn_ra_svn_read_item(conn, subpool, &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, subpool, "wl", &status, |
| &list)); |
| |
| if (strcmp(status, "failure") == 0) |
| err = svn_ra_svn__handle_failure_status(list, subpool); |
| else if (strcmp(status, "success") == 0) |
| { |
| SVN_ERR(parse_lock(list, subpool, &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, subpool); |
| 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_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(subpool); |
| |
| 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) |
| { |
| ra_svn_session_baton_t *sess = session->priv; |
| svn_ra_svn_conn_t *conn = sess->conn; |
| apr_hash_index_t *hi; |
| apr_pool_t *subpool = svn_pool_create(pool); |
| svn_error_t *err, *callback_err = NULL; |
| svn_ra_svn_item_t *elt; |
| const char *status = NULL; |
| apr_array_header_t *list = NULL; |
| const void *key; |
| 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 char *token; |
| |
| svn_pool_clear(subpool); |
| 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, subpool, "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_pool_clear(subpool); |
| |
| SVN_ERR(svn_ra_svn_read_item(conn, subpool, &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, subpool, "wl", &status, |
| &list)); |
| |
| if (strcmp(status, "failure") == 0) |
| err = svn_ra_svn__handle_failure_status(list, subpool); |
| else if (strcmp(status, "success") == 0) |
| { |
| SVN_ERR(svn_ra_svn_parse_tuple(list, subpool, "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, |
| subpool); |
| 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_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(subpool); |
| |
| 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) |
| { |
| 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(conn, pool, "get-lock", "c", path)); |
| |
| /* Servers before 1.2 doesn't support locking. Check this here. */ |
| SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess, pool), |
| _("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; |
| } |
| |
| static svn_error_t *ra_svn_get_locks(svn_ra_session_t *session, |
| apr_hash_t **locks, |
| const char *path, |
| apr_pool_t *pool) |
| { |
| ra_svn_session_baton_t *sess = session->priv; |
| svn_ra_svn_conn_t* conn = sess->conn; |
| apr_array_header_t *list; |
| int i; |
| svn_ra_svn_item_t *elt; |
| svn_lock_t *lock; |
| |
| SVN_ERR(svn_ra_svn_write_cmd(conn, pool, "get-locks", "c", path)); |
| |
| /* Servers before 1.2 doesn't support locking. Check this here. */ |
| SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess, pool), |
| _("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) |
| { |
| 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)); |
| apr_hash_set(*locks, lock->path, APR_HASH_KEY_STRING, 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) |
| { |
| ra_svn_session_baton_t *sess = session->priv; |
| |
| SVN_ERR(svn_ra_svn_write_cmd(sess->conn, pool, "replay", "rrb", revision, |
| low_water_mark, send_deltas)); |
| |
| SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess, pool), |
| _("Server doesn't support the replay " |
| "command"))); |
| |
| SVN_ERR(svn_ra_svn_drive_editor2(sess->conn, pool, editor, edit_baton, |
| NULL, TRUE)); |
| |
| SVN_ERR(svn_ra_svn_read_cmd_response(sess->conn, 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_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_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_file_revs, |
| ra_svn_lock, |
| ra_svn_unlock, |
| ra_svn_get_lock, |
| ra_svn_get_locks, |
| ra_svn_replay, |
| }; |
| |
| 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; |
| |
| 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" |