2 * serve.c : Functions for serving the Subversion protocol
4 * ====================================================================
5 * Licensed to the Apache Software Foundation (ASF) under one
6 * or more contributor license agreements. See the NOTICE file
7 * distributed with this work for additional information
8 * regarding copyright ownership. The ASF licenses this file
9 * to you under the Apache License, Version 2.0 (the
10 * "License"); you may not use this file except in compliance
11 * with the License. You may obtain a copy of the License at
13 * http://www.apache.org/licenses/LICENSE-2.0
15 * Unless required by applicable law or agreed to in writing,
16 * software distributed under the License is distributed on an
17 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18 * KIND, either express or implied. See the License for the
19 * specific language governing permissions and limitations
21 * ====================================================================
27 #include <limits.h> /* for UINT_MAX */
30 #define APR_WANT_STRFUNC
32 #include <apr_general.h>
34 #include <apr_strings.h>
36 #include "svn_compat.h"
37 #include "svn_private_config.h" /* For SVN_PATH_LOCAL_SEPARATOR */
39 #include "svn_types.h"
40 #include "svn_string.h"
41 #include "svn_pools.h"
42 #include "svn_error.h"
43 #include "svn_ra.h" /* for SVN_RA_CAPABILITY_* */
44 #include "svn_ra_svn.h"
45 #include "svn_repos.h"
46 #include "svn_dirent_uri.h"
49 #include "svn_config.h"
50 #include "svn_props.h"
51 #include "svn_mergeinfo.h"
54 #include "private/svn_log.h"
55 #include "private/svn_mergeinfo_private.h"
56 #include "private/svn_ra_svn_private.h"
57 #include "private/svn_fspath.h"
60 #include <unistd.h> /* For getpid() */
65 typedef struct commit_callback_baton_t {
67 svn_revnum_t *new_rev;
70 const char **post_commit_err;
71 } commit_callback_baton_t;
73 typedef struct report_driver_baton_t {
75 const char *repos_url; /* Decoded repository URL. */
78 /* so update() can distinguish checkout from update in logging */
80 svn_boolean_t only_empty_entries;
81 /* for diff() logging */
82 svn_revnum_t *from_rev;
83 } report_driver_baton_t;
85 typedef struct log_baton_t {
87 svn_ra_svn_conn_t *conn;
91 typedef struct file_revs_baton_t {
92 svn_ra_svn_conn_t *conn;
93 apr_pool_t *pool; /* Pool provided in the handler call. */
96 typedef struct fs_warning_baton_t {
97 server_baton_t *server;
98 svn_ra_svn_conn_t *conn;
100 } fs_warning_baton_t;
102 typedef struct authz_baton_t {
103 server_baton_t *server;
104 svn_ra_svn_conn_t *conn;
107 /* Write LEN bytes of ERRSTR to LOG_FILE with svn_io_file_write(). */
109 log_write(apr_file_t *log_file, const char *errstr, apr_size_t len,
112 return svn_io_file_write(log_file, errstr, &len, pool);
116 log_error(svn_error_t *err, apr_file_t *log_file, const char *remote_host,
117 const char *user, const char *repos, apr_pool_t *pool)
119 const char *timestr, *continuation;
121 /* 8192 from MAX_STRING_LEN in from httpd-2.2.4/include/httpd.h */
124 if (err == SVN_NO_ERROR)
127 if (log_file == NULL)
130 timestr = svn_time_to_cstring(apr_time_now(), pool);
131 remote_host = (remote_host ? remote_host : "-");
132 user = (user ? user : "-");
133 repos = (repos ? repos : "-");
138 const char *message = svn_err_best_message(err, errbuf, sizeof(errbuf));
139 /* based on httpd-2.2.4/server/log.c:log_error_core */
140 apr_size_t len = apr_snprintf(errstr, sizeof(errstr),
142 " %s %s %s %s ERR%s %s %ld %d ",
143 getpid(), timestr, remote_host, user,
145 err->file ? err->file : "-", err->line,
148 len += escape_errorlog_item(errstr + len, message,
149 sizeof(errstr) - len);
150 /* Truncate for the terminator (as apr_snprintf does) */
151 if (len > sizeof(errstr) - sizeof(APR_EOL_STR)) {
152 len = sizeof(errstr) - sizeof(APR_EOL_STR);
154 strcpy(errstr + len, APR_EOL_STR);
155 len += strlen(APR_EOL_STR);
156 svn_error_clear(log_write(log_file, errstr, len, pool));
163 /* Call log_error with log_file, remote_host, user, and repos
164 arguments from SERVER and CONN. */
166 log_server_error(svn_error_t *err, server_baton_t *server,
167 svn_ra_svn_conn_t *conn, apr_pool_t *pool)
169 log_error(err, server->log_file, svn_ra_svn_conn_remote_host(conn),
170 server->user, server->repos_name, pool);
173 /* svn_error_create() a new error, log_server_error() it, and
176 error_create_and_log(apr_status_t apr_err, svn_error_t *child,
177 const char *message, server_baton_t *server,
178 svn_ra_svn_conn_t *conn, apr_pool_t *pool)
180 svn_error_t *err = svn_error_create(apr_err, child, message);
181 log_server_error(err, server, conn, pool);
185 /* Log a failure ERR, transmit ERR back to the client (as part of a
186 "failure" notification), consume ERR, and flush the connection. */
188 log_fail_and_flush(svn_error_t *err, server_baton_t *server,
189 svn_ra_svn_conn_t *conn, apr_pool_t *pool)
193 log_server_error(err, server, conn, pool);
194 io_err = svn_ra_svn__write_cmd_failure(conn, pool, err);
195 svn_error_clear(err);
197 return svn_ra_svn__flush(conn, pool);
200 /* Log a client command. */
201 static svn_error_t *log_command(server_baton_t *b,
202 svn_ra_svn_conn_t *conn,
204 const char *fmt, ...)
206 const char *remote_host, *timestr, *log, *line;
210 if (b->log_file == NULL)
213 remote_host = svn_ra_svn_conn_remote_host(conn);
214 timestr = svn_time_to_cstring(apr_time_now(), pool);
217 log = apr_pvsprintf(pool, fmt, ap);
220 line = apr_psprintf(pool, "%" APR_PID_T_FMT
221 " %s %s %s %s %s" APR_EOL_STR,
223 (remote_host ? remote_host : "-"),
224 (b->user ? b->user : "-"), b->repos_name, log);
225 nbytes = strlen(line);
227 return log_write(b->log_file, line, nbytes, pool);
230 /* Log an authz failure */
232 log_authz_denied(const char *path,
233 svn_repos_authz_access_t required,
235 svn_ra_svn_conn_t *conn,
238 const char *timestr, *remote_host, *line;
240 if (b->log_file == NULL)
246 timestr = svn_time_to_cstring(apr_time_now(), pool);
247 remote_host = svn_ra_svn_conn_remote_host(conn);
249 line = apr_psprintf(pool, "%" APR_PID_T_FMT
250 " %s %s %s %s Authorization Failed %s%s %s" APR_EOL_STR,
252 (remote_host ? remote_host : "-"),
253 (b->user ? b->user : "-"),
255 (required & svn_authz_recursive ? "recursive " : ""),
256 (required & svn_authz_write ? "write" : "read"),
257 (path && path[0] ? path : "/"));
259 return log_write(b->log_file, line, strlen(line), pool);
263 svn_error_t *load_pwdb_config(server_baton_t *server,
264 svn_ra_svn_conn_t *conn,
267 const char *pwdb_path;
270 svn_config_get(server->cfg, &pwdb_path, SVN_CONFIG_SECTION_GENERAL,
271 SVN_CONFIG_OPTION_PASSWORD_DB, NULL);
276 pwdb_path = svn_dirent_internal_style(pwdb_path, pool);
277 pwdb_path = svn_dirent_join(server->base, pwdb_path, pool);
279 err = svn_config_read3(&server->pwdb, pwdb_path, TRUE,
283 log_server_error(err, server, conn, pool);
285 /* Because it may be possible to read the pwdb file with some
286 access methods and not others, ignore errors reading the pwdb
287 file and just don't present password authentication as an
288 option. Also, some authentications (e.g. --tunnel) can
289 proceed without it anyway.
291 ### Not entirely sure why SVN_ERR_BAD_FILENAME is checked
292 ### for here. That seems to have been introduced in r856914,
293 ### and only in r870942 was the APR_EACCES check introduced. */
294 if (err->apr_err != SVN_ERR_BAD_FILENAME
295 && ! APR_STATUS_IS_EACCES(err->apr_err))
297 /* Now that we've logged the error, clear it and return a
298 * nice, generic error to the user:
299 * http://subversion.tigris.org/issues/show_bug.cgi?id=2271 */
300 svn_error_clear(err);
301 return svn_error_create(SVN_ERR_AUTHN_FAILED, NULL, NULL);
304 /* Ignore SVN_ERR_BAD_FILENAME and APR_EACCES and proceed. */
305 svn_error_clear(err);
312 /* Canonicalize *ACCESS_FILE based on the type of argument. Results are
313 * placed in *ACCESS_FILE. SERVER baton is used to convert relative paths to
314 * absolute paths rooted at the server root. REPOS_ROOT is used to calculate
315 * an absolute URL for repos-relative URLs. */
317 canonicalize_access_file(const char **access_file, server_baton_t *server,
318 const char *repos_root, apr_pool_t *pool)
320 if (svn_path_is_url(*access_file))
322 *access_file = svn_uri_canonicalize(*access_file, pool);
324 else if (svn_path_is_repos_relative_url(*access_file))
326 const char *repos_root_url;
328 SVN_ERR(svn_uri_get_file_url_from_dirent(&repos_root_url, repos_root,
330 SVN_ERR(svn_path_resolve_repos_relative_url(access_file, *access_file,
331 repos_root_url, pool));
332 *access_file = svn_uri_canonicalize(*access_file, pool);
336 *access_file = svn_dirent_internal_style(*access_file, pool);
337 *access_file = svn_dirent_join(server->base, *access_file, pool);
343 svn_error_t *load_authz_config(server_baton_t *server,
344 svn_ra_svn_conn_t *conn,
345 const char *repos_root,
348 const char *authzdb_path;
349 const char *groupsdb_path;
352 /* Read authz configuration. */
353 svn_config_get(server->cfg, &authzdb_path, SVN_CONFIG_SECTION_GENERAL,
354 SVN_CONFIG_OPTION_AUTHZ_DB, NULL);
356 svn_config_get(server->cfg, &groupsdb_path, SVN_CONFIG_SECTION_GENERAL,
357 SVN_CONFIG_OPTION_GROUPS_DB, NULL);
361 const char *case_force_val;
363 /* Canonicalize and add the base onto the authzdb_path (if needed). */
364 err = canonicalize_access_file(&authzdb_path, server,
367 /* Same for the groupsdb_path if it is present. */
368 if (groupsdb_path && !err)
369 err = canonicalize_access_file(&groupsdb_path, server,
373 err = svn_repos_authz_read2(&server->authzdb, authzdb_path,
374 groupsdb_path, TRUE, pool);
378 log_server_error(err, server, conn, pool);
379 svn_error_clear(err);
380 return svn_error_create(SVN_ERR_AUTHZ_INVALID_CONFIG, NULL, NULL);
383 /* Are we going to be case-normalizing usernames when we consult
384 * this authz file? */
385 svn_config_get(server->cfg, &case_force_val, SVN_CONFIG_SECTION_GENERAL,
386 SVN_CONFIG_OPTION_FORCE_USERNAME_CASE, NULL);
389 if (strcmp(case_force_val, "upper") == 0)
390 server->username_case = CASE_FORCE_UPPER;
391 else if (strcmp(case_force_val, "lower") == 0)
392 server->username_case = CASE_FORCE_LOWER;
394 server->username_case = CASE_ASIS;
399 server->authzdb = NULL;
400 server->username_case = CASE_ASIS;
406 /* Set *FS_PATH to the portion of URL that is the path within the
407 repository, if URL is inside REPOS_URL (if URL is not inside
408 REPOS_URL, then error, with the effect on *FS_PATH undefined).
410 If the resultant fs path would be the empty string (i.e., URL and
411 REPOS_URL are the same), then set *FS_PATH to "/".
413 Assume that REPOS_URL and URL are already URI-decoded. */
414 static svn_error_t *get_fs_path(const char *repos_url, const char *url,
415 const char **fs_path)
419 len = strlen(repos_url);
420 if (strncmp(url, repos_url, len) != 0)
421 return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL,
422 "'%s' is not the same repository as '%s'",
424 *fs_path = url + len;
431 /* --- AUTHENTICATION AND AUTHORIZATION FUNCTIONS --- */
433 /* Convert TEXT to upper case if TO_UPPERCASE is TRUE, else
434 converts it to lower case. */
435 static void convert_case(char *text, svn_boolean_t to_uppercase)
440 *c = (char)(to_uppercase ? apr_toupper(*c) : apr_tolower(*c));
445 /* Set *ALLOWED to TRUE if PATH is accessible in the REQUIRED mode to
446 the user described in BATON according to the authz rules in BATON.
447 Use POOL for temporary allocations only. If no authz rules are
448 present in BATON, grant access by default. */
449 static svn_error_t *authz_check_access(svn_boolean_t *allowed,
451 svn_repos_authz_access_t required,
453 svn_ra_svn_conn_t *conn,
456 /* If authz cannot be performed, grant access. This is NOT the same
457 as the default policy when authz is performed on a path with no
458 rules. In the latter case, the default is to deny access, and is
459 set by svn_repos_authz_check_access. */
466 /* If the authz request is for the empty path (ie. ""), replace it
467 with the root path. This happens because of stripping done at
468 various levels in svnserve that remove the leading / on an
469 absolute path. Passing such a malformed path to the authz
470 routines throws them into an infinite loop and makes them miss
473 path = svn_fspath__canonicalize(path, pool);
475 /* If we have a username, and we've not yet used it + any username
476 case normalization that might be requested to determine "the
477 username we used for authz purposes", do so now. */
478 if (b->user && (! b->authz_user))
480 char *authz_user = apr_pstrdup(b->pool, b->user);
481 if (b->username_case == CASE_FORCE_UPPER)
482 convert_case(authz_user, TRUE);
483 else if (b->username_case == CASE_FORCE_LOWER)
484 convert_case(authz_user, FALSE);
485 b->authz_user = authz_user;
488 SVN_ERR(svn_repos_authz_check_access(b->authzdb, b->authz_repos_name,
489 path, b->authz_user, required,
492 SVN_ERR(log_authz_denied(path, required, b, conn, pool));
497 /* Set *ALLOWED to TRUE if PATH is readable by the user described in
498 * BATON. Use POOL for temporary allocations only. ROOT is not used.
499 * Implements the svn_repos_authz_func_t interface.
501 static svn_error_t *authz_check_access_cb(svn_boolean_t *allowed,
507 authz_baton_t *sb = baton;
509 return authz_check_access(allowed, path, svn_authz_read,
510 sb->server, sb->conn, pool);
513 /* If authz is enabled in the specified BATON, return a read authorization
514 function. Otherwise, return NULL. */
515 static svn_repos_authz_func_t authz_check_access_cb_func(server_baton_t *baton)
518 return authz_check_access_cb;
522 /* Set *ALLOWED to TRUE if the REQUIRED access to PATH is granted,
523 * according to the state in BATON. Use POOL for temporary
524 * allocations only. ROOT is not used. Implements the
525 * svn_repos_authz_callback_t interface.
527 static svn_error_t *authz_commit_cb(svn_repos_authz_access_t required,
528 svn_boolean_t *allowed,
534 authz_baton_t *sb = baton;
536 return authz_check_access(allowed, path, required,
537 sb->server, sb->conn, pool);
541 enum access_type get_access(server_baton_t *b, enum authn_type auth)
543 const char *var = (auth == AUTHENTICATED) ? SVN_CONFIG_OPTION_AUTH_ACCESS :
544 SVN_CONFIG_OPTION_ANON_ACCESS;
545 const char *val, *def = (auth == AUTHENTICATED) ? "write" : "read";
546 enum access_type result;
548 svn_config_get(b->cfg, &val, SVN_CONFIG_SECTION_GENERAL, var, def);
549 result = (strcmp(val, "write") == 0 ? WRITE_ACCESS :
550 strcmp(val, "read") == 0 ? READ_ACCESS : NO_ACCESS);
551 return (result == WRITE_ACCESS && b->read_only) ? READ_ACCESS : result;
554 static enum access_type current_access(server_baton_t *b)
556 return get_access(b, (b->user) ? AUTHENTICATED : UNAUTHENTICATED);
559 /* Send authentication mechs for ACCESS_TYPE to the client. If NEEDS_USERNAME
560 is true, don't send anonymous mech even if that would give the desired
562 static svn_error_t *send_mechs(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
563 server_baton_t *b, enum access_type required,
564 svn_boolean_t needs_username)
566 if (!needs_username && get_access(b, UNAUTHENTICATED) >= required)
567 SVN_ERR(svn_ra_svn__write_word(conn, pool, "ANONYMOUS"));
568 if (b->tunnel_user && get_access(b, AUTHENTICATED) >= required)
569 SVN_ERR(svn_ra_svn__write_word(conn, pool, "EXTERNAL"));
570 if (b->pwdb && get_access(b, AUTHENTICATED) >= required)
571 SVN_ERR(svn_ra_svn__write_word(conn, pool, "CRAM-MD5"));
575 /* Context for cleanup handler. */
576 struct cleanup_fs_access_baton
582 /* Pool cleanup handler. Make sure fs's access_t points to NULL when
583 the command pool is destroyed. */
584 static apr_status_t cleanup_fs_access(void *data)
587 struct cleanup_fs_access_baton *baton = data;
589 serr = svn_fs_set_access(baton->fs, NULL);
592 apr_status_t apr_err = serr->apr_err;
593 svn_error_clear(serr);
601 /* Create an svn_fs_access_t in POOL for USER and associate it with
602 B's filesystem. Also, register a cleanup handler with POOL which
603 de-associates the svn_fs_access_t from B's filesystem. */
605 create_fs_access(server_baton_t *b, apr_pool_t *pool)
607 svn_fs_access_t *fs_access;
608 struct cleanup_fs_access_baton *cleanup_baton;
613 SVN_ERR(svn_fs_create_access(&fs_access, b->user, pool));
614 SVN_ERR(svn_fs_set_access(b->fs, fs_access));
616 cleanup_baton = apr_pcalloc(pool, sizeof(*cleanup_baton));
617 cleanup_baton->pool = pool;
618 cleanup_baton->fs = b->fs;
619 apr_pool_cleanup_register(pool, cleanup_baton, cleanup_fs_access,
620 apr_pool_cleanup_null);
625 /* Authenticate, once the client has chosen a mechanism and possibly
626 * sent an initial mechanism token. On success, set *success to true
627 * and b->user to the authenticated username (or NULL for anonymous).
628 * On authentication failure, report failure to the client and set
629 * *success to FALSE. On communications failure, return an error.
630 * If NEEDS_USERNAME is TRUE, don't allow anonymous authentication. */
631 static svn_error_t *auth(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
632 const char *mech, const char *mecharg,
633 server_baton_t *b, enum access_type required,
634 svn_boolean_t needs_username,
635 svn_boolean_t *success)
640 if (get_access(b, AUTHENTICATED) >= required
641 && b->tunnel_user && strcmp(mech, "EXTERNAL") == 0)
643 if (*mecharg && strcmp(mecharg, b->tunnel_user) != 0)
644 return svn_ra_svn__write_tuple(conn, pool, "w(c)", "failure",
645 "Requested username does not match");
646 b->user = b->tunnel_user;
647 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w()", "success"));
652 if (get_access(b, UNAUTHENTICATED) >= required
653 && strcmp(mech, "ANONYMOUS") == 0 && ! needs_username)
655 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w()", "success"));
660 if (get_access(b, AUTHENTICATED) >= required
661 && b->pwdb && strcmp(mech, "CRAM-MD5") == 0)
663 SVN_ERR(svn_ra_svn_cram_server(conn, pool, b->pwdb, &user, success));
664 b->user = apr_pstrdup(b->pool, user);
668 return svn_ra_svn__write_tuple(conn, pool, "w(c)", "failure",
669 "Must authenticate with listed mechanism");
672 /* Perform an authentication request using the built-in SASL implementation. */
674 internal_auth_request(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
675 server_baton_t *b, enum access_type required,
676 svn_boolean_t needs_username)
678 svn_boolean_t success;
679 const char *mech, *mecharg;
681 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w((!", "success"));
682 SVN_ERR(send_mechs(conn, pool, b, required, needs_username));
683 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!)c)", b->realm));
686 SVN_ERR(svn_ra_svn__read_tuple(conn, pool, "w(?c)", &mech, &mecharg));
689 SVN_ERR(auth(conn, pool, mech, mecharg, b, required, needs_username,
696 /* Perform an authentication request in order to get an access level of
697 * REQUIRED or higher. Since the client may escape the authentication
698 * exchange, the caller should check current_access(b) to see if
699 * authentication succeeded. */
700 static svn_error_t *auth_request(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
701 server_baton_t *b, enum access_type required,
702 svn_boolean_t needs_username)
706 return cyrus_auth_request(conn, pool, b, required, needs_username);
709 return internal_auth_request(conn, pool, b, required, needs_username);
712 /* Send a trivial auth notification on CONN which lists no mechanisms,
713 * indicating that authentication is unnecessary. Usually called in
714 * response to invocation of a svnserve command.
716 static svn_error_t *trivial_auth_request(svn_ra_svn_conn_t *conn,
717 apr_pool_t *pool, server_baton_t *b)
719 return svn_ra_svn__write_cmd_response(conn, pool, "()c", "");
722 /* Ensure that the client has the REQUIRED access by checking the
723 * access directives (both blanket and per-directory) in BATON. If
724 * PATH is NULL, then only the blanket access configuration will
727 * If NEEDS_USERNAME is TRUE, then a lookup is only successful if the
728 * user described in BATON is authenticated and, well, has a username
731 * Use POOL for temporary allocations only.
733 static svn_boolean_t lookup_access(apr_pool_t *pool,
734 server_baton_t *baton,
735 svn_ra_svn_conn_t *conn,
736 svn_repos_authz_access_t required,
738 svn_boolean_t needs_username)
740 enum access_type req = (required & svn_authz_write) ?
741 WRITE_ACCESS : READ_ACCESS;
742 svn_boolean_t authorized;
745 /* Get authz's opinion on the access. */
746 err = authz_check_access(&authorized, path, required, baton, conn, pool);
748 /* If an error made lookup fail, deny access. */
751 log_server_error(err, baton, conn, pool);
752 svn_error_clear(err);
756 /* If the required access is blanket-granted AND granted by authz
757 AND we already have a username if one is required, then the
758 lookup has succeeded. */
759 if (current_access(baton) >= req
761 && (! needs_username || baton->user))
767 /* Check that the client has the REQUIRED access by consulting the
768 * authentication and authorization states stored in BATON. If the
769 * client does not have the required access credentials, attempt to
770 * authenticate the client to get that access, using CONN for
773 * This function is supposed to be called to handle the authentication
774 * half of a standard svn protocol reply. If an error is returned, it
775 * probably means that the server can terminate the client connection
776 * with an apologetic error, as it implies an authentication failure.
778 * PATH and NEEDS_USERNAME are passed along to lookup_access, their
779 * behaviour is documented there.
781 static svn_error_t *must_have_access(svn_ra_svn_conn_t *conn,
784 svn_repos_authz_access_t required,
786 svn_boolean_t needs_username)
788 enum access_type req = (required & svn_authz_write) ?
789 WRITE_ACCESS : READ_ACCESS;
791 /* See whether the user already has the required access. If so,
792 nothing needs to be done. Create the FS access and send a
793 trivial auth request. */
794 if (lookup_access(pool, b, conn, required, path, needs_username))
796 SVN_ERR(create_fs_access(b, pool));
797 return trivial_auth_request(conn, pool, b);
800 /* If the required blanket access can be obtained by authenticating,
801 try that. Unfortunately, we can't tell until after
802 authentication whether authz will work or not. We force
803 requiring a username because we need one to be able to check
804 authz configuration again with a different user credentials than
805 the first time round. */
807 && get_access(b, AUTHENTICATED) >= req
808 && (b->tunnel_user || b->pwdb || b->use_sasl))
809 SVN_ERR(auth_request(conn, pool, b, req, TRUE));
811 /* Now that an authentication has been done get the new take of
812 authz on the request. */
813 if (! lookup_access(pool, b, conn, required, path, needs_username))
814 return svn_error_create(SVN_ERR_RA_SVN_CMD_ERR,
815 error_create_and_log(SVN_ERR_RA_NOT_AUTHORIZED,
816 NULL, NULL, b, conn, pool),
819 /* Else, access is granted, and there is much rejoicing. */
820 SVN_ERR(create_fs_access(b, pool));
825 /* --- REPORTER COMMAND SET --- */
827 /* To allow for pipelining, reporter commands have no reponses. If we
828 * get an error, we ignore all subsequent reporter commands and return
829 * the error finish_report, to be handled by the calling command.
832 static svn_error_t *set_path(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
833 apr_array_header_t *params, void *baton)
835 report_driver_baton_t *b = baton;
836 const char *path, *lock_token, *depth_word;
838 /* Default to infinity, for old clients that don't send depth. */
839 svn_depth_t depth = svn_depth_infinity;
840 svn_boolean_t start_empty;
842 SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "crb?(?c)?w",
843 &path, &rev, &start_empty, &lock_token,
846 depth = svn_depth_from_word(depth_word);
847 path = svn_relpath_canonicalize(path, pool);
848 if (b->from_rev && strcmp(path, "") == 0)
851 b->err = svn_repos_set_path3(b->report_baton, path, rev, depth,
852 start_empty, lock_token, pool);
855 b->only_empty_entries = FALSE;
859 static svn_error_t *delete_path(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
860 apr_array_header_t *params, void *baton)
862 report_driver_baton_t *b = baton;
865 SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "c", &path));
866 path = svn_relpath_canonicalize(path, pool);
868 b->err = svn_repos_delete_path(b->report_baton, path, pool);
872 static svn_error_t *link_path(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
873 apr_array_header_t *params, void *baton)
875 report_driver_baton_t *b = baton;
876 const char *path, *url, *lock_token, *fs_path, *depth_word;
878 svn_boolean_t start_empty;
879 /* Default to infinity, for old clients that don't send depth. */
880 svn_depth_t depth = svn_depth_infinity;
882 SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "ccrb?(?c)?w",
883 &path, &url, &rev, &start_empty,
884 &lock_token, &depth_word));
886 /* ### WHAT?! The link path is an absolute URL?! Didn't see that
887 coming... -- cmpilato */
888 path = svn_relpath_canonicalize(path, pool);
889 url = svn_uri_canonicalize(url, pool);
891 depth = svn_depth_from_word(depth_word);
893 b->err = get_fs_path(svn_path_uri_decode(b->repos_url, pool),
894 svn_path_uri_decode(url, pool),
897 b->err = svn_repos_link_path3(b->report_baton, path, fs_path, rev,
898 depth, start_empty, lock_token, pool);
903 static svn_error_t *finish_report(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
904 apr_array_header_t *params, void *baton)
906 report_driver_baton_t *b = baton;
908 /* No arguments to parse. */
909 SVN_ERR(trivial_auth_request(conn, pool, b->sb));
911 b->err = svn_repos_finish_report(b->report_baton, pool);
915 static svn_error_t *abort_report(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
916 apr_array_header_t *params, void *baton)
918 report_driver_baton_t *b = baton;
920 /* No arguments to parse. */
921 svn_error_clear(svn_repos_abort_report(b->report_baton, pool));
925 static const svn_ra_svn_cmd_entry_t report_commands[] = {
926 { "set-path", set_path },
927 { "delete-path", delete_path },
928 { "link-path", link_path },
929 { "finish-report", finish_report, TRUE },
930 { "abort-report", abort_report, TRUE },
934 /* Accept a report from the client, drive the network editor with the
935 * result, and then write an empty command response. If there is a
936 * non-protocol failure, accept_report will abort the edit and return
937 * a command error to be reported by handle_commands().
939 * If only_empty_entry is not NULL and the report contains only one
940 * item, and that item is empty, set *only_empty_entry to TRUE, else
943 * If from_rev is not NULL, set *from_rev to the revision number from
944 * the set-path on ""; if somehow set-path "" never happens, set
945 * *from_rev to SVN_INVALID_REVNUM.
947 static svn_error_t *accept_report(svn_boolean_t *only_empty_entry,
948 svn_revnum_t *from_rev,
949 svn_ra_svn_conn_t *conn, apr_pool_t *pool,
950 server_baton_t *b, svn_revnum_t rev,
951 const char *target, const char *tgt_path,
952 svn_boolean_t text_deltas,
954 svn_boolean_t send_copyfrom_args,
955 svn_boolean_t ignore_ancestry)
957 const svn_delta_editor_t *editor;
958 void *edit_baton, *report_baton;
959 report_driver_baton_t rb;
966 /* Make an svn_repos report baton. Tell it to drive the network editor
967 * when the report is complete. */
968 svn_ra_svn_get_editor(&editor, &edit_baton, conn, pool, NULL, NULL);
969 SVN_CMD_ERR(svn_repos_begin_report3(&report_baton, rev, b->repos,
970 b->fs_path->data, target, tgt_path,
971 text_deltas, depth, ignore_ancestry,
974 authz_check_access_cb_func(b),
975 &ab, svn_ra_svn_zero_copy_limit(conn),
979 rb.repos_url = svn_path_uri_decode(b->repos_url, pool);
980 rb.report_baton = report_baton;
982 rb.entry_counter = 0;
983 rb.only_empty_entries = TRUE;
984 rb.from_rev = from_rev;
986 *from_rev = SVN_INVALID_REVNUM;
987 err = svn_ra_svn__handle_commands2(conn, pool, report_commands, &rb, TRUE);
990 /* Network or protocol error while handling commands. */
991 svn_error_clear(rb.err);
996 /* Some failure during the reporting or editing operations. */
999 SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, ""));
1001 if (only_empty_entry)
1002 *only_empty_entry = rb.entry_counter == 1 && rb.only_empty_entries;
1004 return SVN_NO_ERROR;
1007 /* --- MAIN COMMAND SET --- */
1009 /* Write out a list of property diffs. PROPDIFFS is an array of svn_prop_t
1011 static svn_error_t *write_prop_diffs(svn_ra_svn_conn_t *conn,
1013 const apr_array_header_t *propdiffs)
1017 for (i = 0; i < propdiffs->nelts; ++i)
1019 const svn_prop_t *prop = &APR_ARRAY_IDX(propdiffs, i, svn_prop_t);
1021 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "c(?s)",
1022 prop->name, prop->value));
1025 return SVN_NO_ERROR;
1028 /* Write out a lock to the client. */
1029 static svn_error_t *write_lock(svn_ra_svn_conn_t *conn,
1033 const char *cdate, *edate;
1035 cdate = svn_time_to_cstring(lock->creation_date, pool);
1036 edate = lock->expiration_date
1037 ? svn_time_to_cstring(lock->expiration_date, pool) : NULL;
1038 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "ccc(?c)c(?c)", lock->path,
1039 lock->token, lock->owner, lock->comment,
1042 return SVN_NO_ERROR;
1045 /* ### This really belongs in libsvn_repos. */
1046 /* Get the explicit properties and/or inherited properties for a PATH in
1047 ROOT, with hardcoded committed-info values. */
1048 static svn_error_t *
1049 get_props(apr_hash_t **props,
1050 apr_array_header_t **iprops,
1052 svn_fs_root_t *root,
1056 /* Get the explicit properties. */
1061 const char *cdate, *cauthor, *uuid;
1063 SVN_ERR(svn_fs_node_proplist(props, root, path, pool));
1065 /* Hardcode the values for the committed revision, date, and author. */
1066 SVN_ERR(svn_repos_get_committed_info(&crev, &cdate, &cauthor, root,
1068 str = svn_string_create(apr_psprintf(pool, "%ld", crev),
1070 svn_hash_sets(*props, SVN_PROP_ENTRY_COMMITTED_REV, str);
1071 str = (cdate) ? svn_string_create(cdate, pool) : NULL;
1072 svn_hash_sets(*props, SVN_PROP_ENTRY_COMMITTED_DATE, str);
1073 str = (cauthor) ? svn_string_create(cauthor, pool) : NULL;
1074 svn_hash_sets(*props, SVN_PROP_ENTRY_LAST_AUTHOR, str);
1076 /* Hardcode the values for the UUID. */
1077 SVN_ERR(svn_fs_get_uuid(svn_fs_root_fs(root), &uuid, pool));
1078 str = (uuid) ? svn_string_create(uuid, pool) : NULL;
1079 svn_hash_sets(*props, SVN_PROP_ENTRY_UUID, str);
1082 /* Get any inherited properties the user is authorized to. */
1085 SVN_ERR(svn_repos_fs_get_inherited_props(
1086 iprops, root, path, NULL,
1087 authz_check_access_cb_func(b->server),
1091 return SVN_NO_ERROR;
1094 /* Set BATON->FS_PATH for the repository URL found in PARAMS. */
1095 static svn_error_t *reparent(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
1096 apr_array_header_t *params, void *baton)
1098 server_baton_t *b = baton;
1100 const char *fs_path;
1102 SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "c", &url));
1103 url = svn_uri_canonicalize(url, pool);
1104 SVN_ERR(trivial_auth_request(conn, pool, b));
1105 SVN_CMD_ERR(get_fs_path(svn_path_uri_decode(b->repos_url, pool),
1106 svn_path_uri_decode(url, pool),
1108 SVN_ERR(log_command(b, conn, pool, "%s", svn_log__reparent(fs_path, pool)));
1109 svn_stringbuf_set(b->fs_path, fs_path);
1110 SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, ""));
1111 return SVN_NO_ERROR;
1114 static svn_error_t *get_latest_rev(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
1115 apr_array_header_t *params, void *baton)
1117 server_baton_t *b = baton;
1120 SVN_ERR(log_command(b, conn, pool, "get-latest-rev"));
1122 SVN_ERR(trivial_auth_request(conn, pool, b));
1123 SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->fs, pool));
1124 SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, "r", rev));
1125 return SVN_NO_ERROR;
1128 static svn_error_t *get_dated_rev(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
1129 apr_array_header_t *params, void *baton)
1131 server_baton_t *b = baton;
1134 const char *timestr;
1136 SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "c", ×tr));
1137 SVN_ERR(log_command(b, conn, pool, "get-dated-rev %s", timestr));
1139 SVN_ERR(trivial_auth_request(conn, pool, b));
1140 SVN_CMD_ERR(svn_time_from_cstring(&tm, timestr, pool));
1141 SVN_CMD_ERR(svn_repos_dated_revision(&rev, b->repos, tm, pool));
1142 SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, "r", rev));
1143 return SVN_NO_ERROR;
1146 /* Common logic for change_rev_prop() and change_rev_prop2(). */
1147 static svn_error_t *do_change_rev_prop(svn_ra_svn_conn_t *conn,
1151 const svn_string_t *const *old_value_p,
1152 const svn_string_t *value,
1160 SVN_ERR(must_have_access(conn, pool, b, svn_authz_write, NULL, FALSE));
1161 SVN_ERR(log_command(b, conn, pool, "%s",
1162 svn_log__change_rev_prop(rev, name, pool)));
1163 SVN_CMD_ERR(svn_repos_fs_change_rev_prop4(b->repos, rev, b->user,
1164 name, old_value_p, value,
1166 authz_check_access_cb_func(b), &ab,
1168 SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, ""));
1170 return SVN_NO_ERROR;
1173 static svn_error_t *change_rev_prop2(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
1174 apr_array_header_t *params, void *baton)
1176 server_baton_t *b = baton;
1179 svn_string_t *value;
1180 const svn_string_t *const *old_value_p;
1181 svn_string_t *old_value;
1182 svn_boolean_t dont_care;
1184 SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "rc(?s)(b?s)",
1185 &rev, &name, &value,
1186 &dont_care, &old_value));
1188 /* Argument parsing. */
1192 old_value_p = (const svn_string_t *const *)&old_value;
1194 /* Input validation. */
1195 if (dont_care && old_value)
1198 err = svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL,
1199 "'previous-value' and 'dont-care' cannot both be "
1200 "set in 'change-rev-prop2' request");
1201 return log_fail_and_flush(err, b, conn, pool);
1205 SVN_ERR(do_change_rev_prop(conn, b, rev, name, old_value_p, value, pool));
1207 return SVN_NO_ERROR;
1210 static svn_error_t *change_rev_prop(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
1211 apr_array_header_t *params, void *baton)
1213 server_baton_t *b = baton;
1216 svn_string_t *value;
1218 /* Because the revprop value was at one time mandatory, the usual
1219 optional element pattern "(?s)" isn't used. */
1220 SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "rc?s", &rev, &name, &value));
1222 SVN_ERR(do_change_rev_prop(conn, b, rev, name, NULL, value, pool));
1224 return SVN_NO_ERROR;
1227 static svn_error_t *rev_proplist(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
1228 apr_array_header_t *params, void *baton)
1230 server_baton_t *b = baton;
1238 SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "r", &rev));
1239 SVN_ERR(log_command(b, conn, pool, "%s", svn_log__rev_proplist(rev, pool)));
1241 SVN_ERR(trivial_auth_request(conn, pool, b));
1242 SVN_CMD_ERR(svn_repos_fs_revision_proplist(&props, b->repos, rev,
1243 authz_check_access_cb_func(b), &ab,
1245 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w((!", "success"));
1246 SVN_ERR(svn_ra_svn__write_proplist(conn, pool, props));
1247 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!))"));
1248 return SVN_NO_ERROR;
1251 static svn_error_t *rev_prop(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
1252 apr_array_header_t *params, void *baton)
1254 server_baton_t *b = baton;
1257 svn_string_t *value;
1263 SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "rc", &rev, &name));
1264 SVN_ERR(log_command(b, conn, pool, "%s",
1265 svn_log__rev_prop(rev, name, pool)));
1267 SVN_ERR(trivial_auth_request(conn, pool, b));
1268 SVN_CMD_ERR(svn_repos_fs_revision_prop(&value, b->repos, rev, name,
1269 authz_check_access_cb_func(b), &ab,
1271 SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, "(?s)", value));
1272 return SVN_NO_ERROR;
1275 static svn_error_t *commit_done(const svn_commit_info_t *commit_info,
1276 void *baton, apr_pool_t *pool)
1278 commit_callback_baton_t *ccb = baton;
1280 *ccb->new_rev = commit_info->revision;
1281 *ccb->date = commit_info->date
1282 ? apr_pstrdup(ccb->pool, commit_info->date): NULL;
1283 *ccb->author = commit_info->author
1284 ? apr_pstrdup(ccb->pool, commit_info->author) : NULL;
1285 *ccb->post_commit_err = commit_info->post_commit_err
1286 ? apr_pstrdup(ccb->pool, commit_info->post_commit_err) : NULL;
1287 return SVN_NO_ERROR;
1290 /* Add the LOCK_TOKENS (if any) to the filesystem access context,
1291 * checking path authorizations using the state in SB as we go.
1292 * LOCK_TOKENS is an array of svn_ra_svn_item_t structs. Return a
1293 * client error if LOCK_TOKENS is not a list of lists. If a lock
1294 * violates the authz configuration, return SVN_ERR_RA_NOT_AUTHORIZED
1295 * to the client. Use POOL for temporary allocations only.
1297 static svn_error_t *add_lock_tokens(svn_ra_svn_conn_t *conn,
1298 const apr_array_header_t *lock_tokens,
1303 svn_fs_access_t *fs_access;
1305 SVN_ERR(svn_fs_get_access(&fs_access, sb->fs));
1307 /* If there is no access context, nowhere to add the tokens. */
1309 return SVN_NO_ERROR;
1311 for (i = 0; i < lock_tokens->nelts; ++i)
1313 const char *path, *token, *full_path;
1314 svn_ra_svn_item_t *path_item, *token_item;
1315 svn_ra_svn_item_t *item = &APR_ARRAY_IDX(lock_tokens, i,
1317 if (item->kind != SVN_RA_SVN_LIST)
1318 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
1319 "Lock tokens aren't a list of lists");
1321 path_item = &APR_ARRAY_IDX(item->u.list, 0, svn_ra_svn_item_t);
1322 if (path_item->kind != SVN_RA_SVN_STRING)
1323 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
1324 "Lock path isn't a string");
1326 token_item = &APR_ARRAY_IDX(item->u.list, 1, svn_ra_svn_item_t);
1327 if (token_item->kind != SVN_RA_SVN_STRING)
1328 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
1329 "Lock token isn't a string");
1331 path = path_item->u.string->data;
1332 full_path = svn_fspath__join(sb->fs_path->data,
1333 svn_relpath_canonicalize(path, pool),
1336 if (! lookup_access(pool, sb, conn, svn_authz_write,
1338 return error_create_and_log(SVN_ERR_RA_NOT_AUTHORIZED, NULL, NULL,
1341 token = token_item->u.string->data;
1342 SVN_ERR(svn_fs_access_add_lock_token2(fs_access, path, token));
1345 return SVN_NO_ERROR;
1348 /* Unlock the paths with lock tokens in LOCK_TOKENS, ignoring any errors.
1349 LOCK_TOKENS contains svn_ra_svn_item_t elements, assumed to be lists. */
1350 static svn_error_t *unlock_paths(const apr_array_header_t *lock_tokens,
1352 svn_ra_svn_conn_t *conn,
1356 apr_pool_t *iterpool;
1358 iterpool = svn_pool_create(pool);
1360 for (i = 0; i < lock_tokens->nelts; ++i)
1362 svn_ra_svn_item_t *item, *path_item, *token_item;
1363 const char *path, *token, *full_path;
1365 svn_pool_clear(iterpool);
1367 item = &APR_ARRAY_IDX(lock_tokens, i, svn_ra_svn_item_t);
1368 path_item = &APR_ARRAY_IDX(item->u.list, 0, svn_ra_svn_item_t);
1369 token_item = &APR_ARRAY_IDX(item->u.list, 1, svn_ra_svn_item_t);
1371 path = path_item->u.string->data;
1372 token = token_item->u.string->data;
1374 full_path = svn_fspath__join(sb->fs_path->data,
1375 svn_relpath_canonicalize(path, iterpool),
1378 /* The lock may have become defunct after the commit, so ignore such
1380 err = svn_repos_fs_unlock(sb->repos, full_path, token,
1382 log_server_error(err, sb, conn, iterpool);
1383 svn_error_clear(err);
1386 svn_pool_destroy(iterpool);
1388 return SVN_NO_ERROR;
1391 static svn_error_t *commit(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
1392 apr_array_header_t *params, void *baton)
1394 server_baton_t *b = baton;
1395 const char *log_msg = NULL,
1398 *post_commit_err = NULL;
1399 apr_array_header_t *lock_tokens;
1400 svn_boolean_t keep_locks;
1401 apr_array_header_t *revprop_list = NULL;
1402 apr_hash_t *revprop_table;
1403 const svn_delta_editor_t *editor;
1405 svn_boolean_t aborted;
1406 commit_callback_baton_t ccb;
1407 svn_revnum_t new_rev;
1413 if (params->nelts == 1)
1415 /* Clients before 1.2 don't send lock-tokens, keep-locks,
1416 and rev-props fields. */
1417 SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "c", &log_msg));
1420 revprop_list = NULL;
1424 /* Clients before 1.5 don't send the rev-props field. */
1425 SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "clb?l", &log_msg,
1426 &lock_tokens, &keep_locks,
1430 /* The handling for locks is a little problematic, because the
1431 protocol won't let us send several auth requests once one has
1432 succeeded. So we request write access and a username before
1433 adding tokens (if we have any), and subsequently fail if a lock
1435 SVN_ERR(must_have_access(conn, pool, b, svn_authz_write,
1437 (lock_tokens && lock_tokens->nelts)));
1439 /* Authorize the lock tokens and give them to the FS if we got
1441 if (lock_tokens && lock_tokens->nelts)
1442 SVN_CMD_ERR(add_lock_tokens(conn, lock_tokens, b, pool));
1444 /* Ignore LOG_MSG, per the protocol. See ra_svn_commit(). */
1446 SVN_ERR(svn_ra_svn__parse_proplist(revprop_list, pool, &revprop_table));
1449 revprop_table = apr_hash_make(pool);
1450 svn_hash_sets(revprop_table, SVN_PROP_REVISION_LOG,
1451 svn_string_create(log_msg, pool));
1454 /* Get author from the baton, making sure clients can't circumvent
1455 the authentication via the revision props. */
1456 svn_hash_sets(revprop_table, SVN_PROP_REVISION_AUTHOR,
1457 b->user ? svn_string_create(b->user, pool) : NULL);
1460 ccb.new_rev = &new_rev;
1462 ccb.author = &author;
1463 ccb.post_commit_err = &post_commit_err;
1464 /* ### Note that svn_repos_get_commit_editor5 actually wants a decoded URL. */
1465 SVN_CMD_ERR(svn_repos_get_commit_editor5
1466 (&editor, &edit_baton, b->repos, NULL,
1467 svn_path_uri_decode(b->repos_url, pool),
1468 b->fs_path->data, revprop_table,
1470 authz_commit_cb, &ab, pool));
1471 SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, ""));
1472 SVN_ERR(svn_ra_svn_drive_editor2(conn, pool, editor, edit_baton,
1476 SVN_ERR(log_command(b, conn, pool, "%s",
1477 svn_log__commit(new_rev, pool)));
1478 SVN_ERR(trivial_auth_request(conn, pool, b));
1480 /* In tunnel mode, deltify before answering the client, because
1481 answering may cause the client to terminate the connection
1482 and thus kill the server. But otherwise, deltify after
1483 answering the client, to avoid user-visible delay. */
1486 SVN_ERR(svn_fs_deltify_revision(b->fs, new_rev, pool));
1488 /* Unlock the paths. */
1489 if (! keep_locks && lock_tokens && lock_tokens->nelts)
1490 SVN_ERR(unlock_paths(lock_tokens, b, conn, pool));
1492 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "r(?c)(?c)(?c)",
1493 new_rev, date, author, post_commit_err));
1496 SVN_ERR(svn_fs_deltify_revision(b->fs, new_rev, pool));
1498 return SVN_NO_ERROR;
1501 static svn_error_t *get_file(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
1502 apr_array_header_t *params, void *baton)
1504 server_baton_t *b = baton;
1505 const char *path, *full_path, *hex_digest;
1507 svn_fs_root_t *root;
1508 svn_stream_t *contents;
1509 apr_hash_t *props = NULL;
1510 apr_array_header_t *inherited_props;
1511 svn_string_t write_str;
1514 svn_boolean_t want_props, want_contents;
1515 apr_uint64_t wants_inherited_props;
1516 svn_checksum_t *checksum;
1517 svn_error_t *err, *write_err;
1524 /* Parse arguments. */
1525 SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "c(?r)bb?B", &path, &rev,
1526 &want_props, &want_contents,
1527 &wants_inherited_props));
1529 if (wants_inherited_props == SVN_RA_SVN_UNSPECIFIED_NUMBER)
1530 wants_inherited_props = FALSE;
1532 full_path = svn_fspath__join(b->fs_path->data,
1533 svn_relpath_canonicalize(path, pool), pool);
1535 /* Check authorizations */
1536 SVN_ERR(must_have_access(conn, pool, b, svn_authz_read,
1539 if (!SVN_IS_VALID_REVNUM(rev))
1540 SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->fs, pool));
1542 SVN_ERR(log_command(b, conn, pool, "%s",
1543 svn_log__get_file(full_path, rev,
1544 want_contents, want_props, pool)));
1546 /* Fetch the properties and a stream for the contents. */
1547 SVN_CMD_ERR(svn_fs_revision_root(&root, b->fs, rev, pool));
1548 SVN_CMD_ERR(svn_fs_file_checksum(&checksum, svn_checksum_md5, root,
1549 full_path, TRUE, pool));
1550 hex_digest = svn_checksum_to_cstring_display(checksum, pool);
1552 /* Fetch the file's explicit and/or inherited properties if
1553 requested. Although the wants-iprops boolean was added to the
1554 protocol in 1.8 a standard 1.8 client never requests iprops. */
1555 if (want_props || wants_inherited_props)
1556 SVN_CMD_ERR(get_props(want_props ? &props : NULL,
1557 wants_inherited_props ? &inherited_props : NULL,
1558 &ab, root, full_path,
1561 SVN_CMD_ERR(svn_fs_file_contents(&contents, root, full_path, pool));
1563 /* Send successful command response with revision and props. */
1564 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w((?c)r(!", "success",
1566 SVN_ERR(svn_ra_svn__write_proplist(conn, pool, props));
1568 if (wants_inherited_props)
1570 apr_pool_t *iterpool = svn_pool_create(pool);
1572 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!)(?!"));
1573 for (i = 0; i < inherited_props->nelts; i++)
1575 svn_prop_inherited_item_t *iprop =
1576 APR_ARRAY_IDX(inherited_props, i, svn_prop_inherited_item_t *);
1578 svn_pool_clear(iterpool);
1579 SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "!(c(!",
1580 iprop->path_or_url));
1581 SVN_ERR(svn_ra_svn__write_proplist(conn, iterpool, iprop->prop_hash));
1582 SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "!))!",
1583 iprop->path_or_url));
1585 svn_pool_destroy(iterpool);
1588 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!))"));
1590 /* Now send the file's contents. */
1597 err = svn_stream_read(contents, buf, &len);
1602 write_str.data = buf;
1603 write_str.len = len;
1604 SVN_ERR(svn_ra_svn__write_string(conn, pool, &write_str));
1606 if (len < sizeof(buf))
1608 err = svn_stream_close(contents);
1612 write_err = svn_ra_svn__write_cstring(conn, pool, "");
1615 svn_error_clear(err);
1619 SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, ""));
1622 return SVN_NO_ERROR;
1625 static svn_error_t *get_dir(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
1626 apr_array_header_t *params, void *baton)
1628 server_baton_t *b = baton;
1629 const char *path, *full_path;
1631 apr_hash_t *entries, *props = NULL;
1632 apr_array_header_t *inherited_props;
1633 apr_hash_index_t *hi;
1634 svn_fs_root_t *root;
1635 apr_pool_t *subpool;
1636 svn_boolean_t want_props, want_contents;
1637 apr_uint64_t wants_inherited_props;
1638 apr_uint64_t dirent_fields;
1639 apr_array_header_t *dirent_fields_list = NULL;
1640 svn_ra_svn_item_t *elt;
1647 SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "c(?r)bb?l?B", &path, &rev,
1648 &want_props, &want_contents,
1649 &dirent_fields_list,
1650 &wants_inherited_props));
1652 if (wants_inherited_props == SVN_RA_SVN_UNSPECIFIED_NUMBER)
1653 wants_inherited_props = FALSE;
1655 if (! dirent_fields_list)
1657 dirent_fields = SVN_DIRENT_ALL;
1663 for (i = 0; i < dirent_fields_list->nelts; ++i)
1665 elt = &APR_ARRAY_IDX(dirent_fields_list, i, svn_ra_svn_item_t);
1667 if (elt->kind != SVN_RA_SVN_WORD)
1668 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
1669 "Dirent field not a string");
1671 if (strcmp(SVN_RA_SVN_DIRENT_KIND, elt->u.word) == 0)
1672 dirent_fields |= SVN_DIRENT_KIND;
1673 else if (strcmp(SVN_RA_SVN_DIRENT_SIZE, elt->u.word) == 0)
1674 dirent_fields |= SVN_DIRENT_SIZE;
1675 else if (strcmp(SVN_RA_SVN_DIRENT_HAS_PROPS, elt->u.word) == 0)
1676 dirent_fields |= SVN_DIRENT_HAS_PROPS;
1677 else if (strcmp(SVN_RA_SVN_DIRENT_CREATED_REV, elt->u.word) == 0)
1678 dirent_fields |= SVN_DIRENT_CREATED_REV;
1679 else if (strcmp(SVN_RA_SVN_DIRENT_TIME, elt->u.word) == 0)
1680 dirent_fields |= SVN_DIRENT_TIME;
1681 else if (strcmp(SVN_RA_SVN_DIRENT_LAST_AUTHOR, elt->u.word) == 0)
1682 dirent_fields |= SVN_DIRENT_LAST_AUTHOR;
1686 full_path = svn_fspath__join(b->fs_path->data,
1687 svn_relpath_canonicalize(path, pool), pool);
1689 /* Check authorizations */
1690 SVN_ERR(must_have_access(conn, pool, b, svn_authz_read,
1693 if (!SVN_IS_VALID_REVNUM(rev))
1694 SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->fs, pool));
1696 SVN_ERR(log_command(b, conn, pool, "%s",
1697 svn_log__get_dir(full_path, rev,
1698 want_contents, want_props,
1699 dirent_fields, pool)));
1701 /* Fetch the root of the appropriate revision. */
1702 SVN_CMD_ERR(svn_fs_revision_root(&root, b->fs, rev, pool));
1704 /* Fetch the directory's explicit and/or inherited properties if
1705 requested. Although the wants-iprops boolean was added to the
1706 protocol in 1.8 a standard 1.8 client never requests iprops. */
1707 if (want_props || wants_inherited_props)
1708 SVN_CMD_ERR(get_props(want_props ? &props : NULL,
1709 wants_inherited_props ? &inherited_props : NULL,
1710 &ab, root, full_path,
1713 /* Fetch the directories' entries before starting the response, to allow
1714 proper error handling in cases like when FULL_PATH doesn't exist */
1716 SVN_CMD_ERR(svn_fs_dir_entries(&entries, root, full_path, pool));
1718 /* Begin response ... */
1719 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w(r(!", "success", rev));
1720 SVN_ERR(svn_ra_svn__write_proplist(conn, pool, props));
1721 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!)(!"));
1723 /* Fetch the directory entries if requested and send them immediately. */
1726 /* Use epoch for a placeholder for a missing date. */
1727 const char *missing_date = svn_time_to_cstring(0, pool);
1729 /* Transform the hash table's FS entries into dirents. This probably
1730 * belongs in libsvn_repos. */
1731 subpool = svn_pool_create(pool);
1732 for (hi = apr_hash_first(pool, entries); hi; hi = apr_hash_next(hi))
1734 const char *name = svn__apr_hash_index_key(hi);
1735 svn_fs_dirent_t *fsent = svn__apr_hash_index_val(hi);
1736 const char *file_path;
1738 /* The fields in the entry tuple. */
1739 svn_node_kind_t entry_kind = svn_node_none;
1740 svn_filesize_t entry_size = 0;
1741 svn_boolean_t has_props = FALSE;
1742 /* If 'created rev' was not requested, send 0. We can't use
1743 * SVN_INVALID_REVNUM as the tuple field is not optional.
1744 * See the email thread on dev@, 2012-03-28, subject
1745 * "buildbot failure in ASF Buildbot on svn-slik-w2k3-x64-ra",
1746 * <http://svn.haxx.se/dev/archive-2012-03/0655.shtml>. */
1747 svn_revnum_t created_rev = 0;
1748 const char *cdate = NULL;
1749 const char *last_author = NULL;
1751 svn_pool_clear(subpool);
1753 file_path = svn_fspath__join(full_path, name, subpool);
1754 if (! lookup_access(subpool, b, conn, svn_authz_read,
1758 if (dirent_fields & SVN_DIRENT_KIND)
1759 entry_kind = fsent->kind;
1761 if (dirent_fields & SVN_DIRENT_SIZE)
1762 if (entry_kind != svn_node_dir)
1763 SVN_CMD_ERR(svn_fs_file_length(&entry_size, root, file_path,
1766 if (dirent_fields & SVN_DIRENT_HAS_PROPS)
1768 apr_hash_t *file_props;
1771 SVN_CMD_ERR(svn_fs_node_proplist(&file_props, root, file_path,
1773 has_props = (apr_hash_count(file_props) > 0);
1776 if ((dirent_fields & SVN_DIRENT_LAST_AUTHOR)
1777 || (dirent_fields & SVN_DIRENT_TIME)
1778 || (dirent_fields & SVN_DIRENT_CREATED_REV))
1780 /* created_rev, last_author, time */
1781 SVN_CMD_ERR(svn_repos_get_committed_info(&created_rev,
1789 /* The client does not properly handle a missing CDATE. For
1790 interoperability purposes, we must fill in some junk.
1792 See libsvn_ra_svn/client.c:ra_svn_get_dir() */
1794 cdate = missing_date;
1796 /* Send the entry. */
1797 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "cwnbr(?c)(?c)", name,
1798 svn_node_kind_to_word(entry_kind),
1799 (apr_uint64_t) entry_size,
1800 has_props, created_rev,
1801 cdate, last_author));
1803 svn_pool_destroy(subpool);
1806 if (wants_inherited_props)
1808 apr_pool_t *iterpool = svn_pool_create(pool);
1810 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!)(?!"));
1811 for (i = 0; i < inherited_props->nelts; i++)
1813 svn_prop_inherited_item_t *iprop =
1814 APR_ARRAY_IDX(inherited_props, i, svn_prop_inherited_item_t *);
1816 svn_pool_clear(iterpool);
1817 SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "!(c(!",
1818 iprop->path_or_url));
1819 SVN_ERR(svn_ra_svn__write_proplist(conn, iterpool, iprop->prop_hash));
1820 SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "!))!",
1821 iprop->path_or_url));
1823 svn_pool_destroy(iterpool);
1826 /* Finish response. */
1827 return svn_ra_svn__write_tuple(conn, pool, "!))");
1830 static svn_error_t *update(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
1831 apr_array_header_t *params, void *baton)
1833 server_baton_t *b = baton;
1835 const char *target, *full_path, *depth_word;
1836 svn_boolean_t recurse;
1837 apr_uint64_t send_copyfrom_args; /* Optional; default FALSE */
1838 apr_uint64_t ignore_ancestry; /* Optional; default FALSE */
1839 /* Default to unknown. Old clients won't send depth, but we'll
1840 handle that by converting recurse if necessary. */
1841 svn_depth_t depth = svn_depth_unknown;
1842 svn_boolean_t is_checkout;
1844 /* Parse the arguments. */
1845 SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "(?r)cb?wB?B", &rev, &target,
1846 &recurse, &depth_word,
1847 &send_copyfrom_args, &ignore_ancestry));
1848 target = svn_relpath_canonicalize(target, pool);
1851 depth = svn_depth_from_word(depth_word);
1853 depth = SVN_DEPTH_INFINITY_OR_FILES(recurse);
1855 full_path = svn_fspath__join(b->fs_path->data, target, pool);
1856 /* Check authorization and authenticate the user if necessary. */
1857 SVN_ERR(must_have_access(conn, pool, b, svn_authz_read, full_path, FALSE));
1859 if (!SVN_IS_VALID_REVNUM(rev))
1860 SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->fs, pool));
1862 SVN_ERR(accept_report(&is_checkout, NULL,
1863 conn, pool, b, rev, target, NULL, TRUE,
1865 (send_copyfrom_args == TRUE) /* send_copyfrom_args */,
1866 (ignore_ancestry == TRUE) /* ignore_ancestry */));
1869 SVN_ERR(log_command(b, conn, pool, "%s",
1870 svn_log__checkout(full_path, rev,
1875 SVN_ERR(log_command(b, conn, pool, "%s",
1876 svn_log__update(full_path, rev, depth,
1877 send_copyfrom_args, pool)));
1880 return SVN_NO_ERROR;
1883 static svn_error_t *switch_cmd(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
1884 apr_array_header_t *params, void *baton)
1886 server_baton_t *b = baton;
1888 const char *target, *depth_word;
1889 const char *switch_url, *switch_path;
1890 svn_boolean_t recurse;
1891 /* Default to unknown. Old clients won't send depth, but we'll
1892 handle that by converting recurse if necessary. */
1893 svn_depth_t depth = svn_depth_unknown;
1894 apr_uint64_t send_copyfrom_args; /* Optional; default FALSE */
1895 apr_uint64_t ignore_ancestry; /* Optional; default TRUE */
1897 /* Parse the arguments. */
1898 SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "(?r)cbc?w?BB", &rev, &target,
1899 &recurse, &switch_url, &depth_word,
1900 &send_copyfrom_args, &ignore_ancestry));
1901 target = svn_relpath_canonicalize(target, pool);
1902 switch_url = svn_uri_canonicalize(switch_url, pool);
1905 depth = svn_depth_from_word(depth_word);
1907 depth = SVN_DEPTH_INFINITY_OR_FILES(recurse);
1909 SVN_ERR(trivial_auth_request(conn, pool, b));
1910 if (!SVN_IS_VALID_REVNUM(rev))
1911 SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->fs, pool));
1913 SVN_CMD_ERR(get_fs_path(svn_path_uri_decode(b->repos_url, pool),
1914 svn_path_uri_decode(switch_url, pool),
1918 const char *full_path = svn_fspath__join(b->fs_path->data, target, pool);
1919 SVN_ERR(log_command(b, conn, pool, "%s",
1920 svn_log__switch(full_path, switch_path, rev,
1924 return accept_report(NULL, NULL,
1925 conn, pool, b, rev, target, switch_path, TRUE,
1927 (send_copyfrom_args == TRUE) /* send_copyfrom_args */,
1928 (ignore_ancestry != FALSE) /* ignore_ancestry */);
1931 static svn_error_t *status(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
1932 apr_array_header_t *params, void *baton)
1934 server_baton_t *b = baton;
1936 const char *target, *depth_word;
1937 svn_boolean_t recurse;
1938 /* Default to unknown. Old clients won't send depth, but we'll
1939 handle that by converting recurse if necessary. */
1940 svn_depth_t depth = svn_depth_unknown;
1942 /* Parse the arguments. */
1943 SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "cb?(?r)?w",
1944 &target, &recurse, &rev, &depth_word));
1945 target = svn_relpath_canonicalize(target, pool);
1948 depth = svn_depth_from_word(depth_word);
1950 depth = SVN_DEPTH_INFINITY_OR_EMPTY(recurse);
1952 SVN_ERR(trivial_auth_request(conn, pool, b));
1953 if (!SVN_IS_VALID_REVNUM(rev))
1954 SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->fs, pool));
1957 const char *full_path = svn_fspath__join(b->fs_path->data, target, pool);
1958 SVN_ERR(log_command(b, conn, pool, "%s",
1959 svn_log__status(full_path, rev, depth, pool)));
1962 return accept_report(NULL, NULL, conn, pool, b, rev, target, NULL, FALSE,
1963 depth, FALSE, FALSE);
1966 static svn_error_t *diff(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
1967 apr_array_header_t *params, void *baton)
1969 server_baton_t *b = baton;
1971 const char *target, *versus_url, *versus_path, *depth_word;
1972 svn_boolean_t recurse, ignore_ancestry;
1973 svn_boolean_t text_deltas;
1974 /* Default to unknown. Old clients won't send depth, but we'll
1975 handle that by converting recurse if necessary. */
1976 svn_depth_t depth = svn_depth_unknown;
1978 /* Parse the arguments. */
1979 if (params->nelts == 5)
1981 /* Clients before 1.4 don't send the text_deltas boolean or depth. */
1982 SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "(?r)cbbc", &rev, &target,
1983 &recurse, &ignore_ancestry, &versus_url));
1989 SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "(?r)cbbcb?w",
1990 &rev, &target, &recurse,
1991 &ignore_ancestry, &versus_url,
1992 &text_deltas, &depth_word));
1994 target = svn_relpath_canonicalize(target, pool);
1995 versus_url = svn_uri_canonicalize(versus_url, pool);
1998 depth = svn_depth_from_word(depth_word);
2000 depth = SVN_DEPTH_INFINITY_OR_FILES(recurse);
2002 SVN_ERR(trivial_auth_request(conn, pool, b));
2004 if (!SVN_IS_VALID_REVNUM(rev))
2005 SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->fs, pool));
2006 SVN_CMD_ERR(get_fs_path(svn_path_uri_decode(b->repos_url, pool),
2007 svn_path_uri_decode(versus_url, pool),
2011 const char *full_path = svn_fspath__join(b->fs_path->data, target, pool);
2012 svn_revnum_t from_rev;
2013 SVN_ERR(accept_report(NULL, &from_rev,
2014 conn, pool, b, rev, target, versus_path,
2015 text_deltas, depth, FALSE, ignore_ancestry));
2016 SVN_ERR(log_command(b, conn, pool, "%s",
2017 svn_log__diff(full_path, from_rev, versus_path,
2018 rev, depth, ignore_ancestry,
2021 return SVN_NO_ERROR;
2024 /* Regardless of whether a client's capabilities indicate an
2025 understanding of this command (by way of SVN_RA_SVN_CAP_MERGEINFO),
2026 we provide a response.
2028 ASSUMPTION: When performing a 'merge' with two URLs at different
2029 revisions, the client will call this command more than once. */
2030 static svn_error_t *get_mergeinfo(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
2031 apr_array_header_t *params, void *baton)
2033 server_baton_t *b = baton;
2035 apr_array_header_t *paths, *canonical_paths;
2036 svn_mergeinfo_catalog_t mergeinfo;
2038 apr_hash_index_t *hi;
2039 const char *inherit_word;
2040 svn_mergeinfo_inheritance_t inherit;
2041 svn_boolean_t include_descendants;
2042 apr_pool_t *iterpool;
2048 SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "l(?r)wb", &paths, &rev,
2049 &inherit_word, &include_descendants));
2050 inherit = svn_inheritance_from_word(inherit_word);
2052 /* Canonicalize the paths which mergeinfo has been requested for. */
2053 canonical_paths = apr_array_make(pool, paths->nelts, sizeof(const char *));
2054 for (i = 0; i < paths->nelts; i++)
2056 svn_ra_svn_item_t *item = &APR_ARRAY_IDX(paths, i, svn_ra_svn_item_t);
2057 const char *full_path;
2059 if (item->kind != SVN_RA_SVN_STRING)
2060 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2061 _("Path is not a string"));
2062 full_path = svn_relpath_canonicalize(item->u.string->data, pool);
2063 full_path = svn_fspath__join(b->fs_path->data, full_path, pool);
2064 APR_ARRAY_PUSH(canonical_paths, const char *) = full_path;
2067 SVN_ERR(log_command(b, conn, pool, "%s",
2068 svn_log__get_mergeinfo(canonical_paths, inherit,
2069 include_descendants,
2072 SVN_ERR(trivial_auth_request(conn, pool, b));
2073 SVN_CMD_ERR(svn_repos_fs_get_mergeinfo(&mergeinfo, b->repos,
2074 canonical_paths, rev,
2076 include_descendants,
2077 authz_check_access_cb_func(b), &ab,
2079 SVN_ERR(svn_mergeinfo__remove_prefix_from_catalog(&mergeinfo, mergeinfo,
2080 b->fs_path->data, pool));
2081 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w((!", "success"));
2082 iterpool = svn_pool_create(pool);
2083 for (hi = apr_hash_first(pool, mergeinfo); hi; hi = apr_hash_next(hi))
2085 const char *key = svn__apr_hash_index_key(hi);
2086 svn_mergeinfo_t value = svn__apr_hash_index_val(hi);
2087 svn_string_t *mergeinfo_string;
2089 svn_pool_clear(iterpool);
2091 SVN_ERR(svn_mergeinfo_to_string(&mergeinfo_string, value, iterpool));
2092 SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "cs", key,
2095 svn_pool_destroy(iterpool);
2096 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!))"));
2098 return SVN_NO_ERROR;
2101 /* Send a log entry to the client. */
2102 static svn_error_t *log_receiver(void *baton,
2103 svn_log_entry_t *log_entry,
2106 log_baton_t *b = baton;
2107 svn_ra_svn_conn_t *conn = b->conn;
2108 apr_hash_index_t *h;
2109 svn_boolean_t invalid_revnum = FALSE;
2111 const char *author, *date, *message;
2112 apr_uint64_t revprop_count;
2114 if (log_entry->revision == SVN_INVALID_REVNUM)
2116 /* If the stack depth is zero, we've seen the last revision, so don't
2117 send it, just return. */
2118 if (b->stack_depth == 0)
2119 return SVN_NO_ERROR;
2121 /* Because the svn protocol won't let us send an invalid revnum, we have
2122 to fudge here and send an additional flag. */
2123 log_entry->revision = 0;
2124 invalid_revnum = TRUE;
2128 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "(!"));
2129 if (log_entry->changed_paths2)
2131 for (h = apr_hash_first(pool, log_entry->changed_paths2); h;
2132 h = apr_hash_next(h))
2134 const char *path = svn__apr_hash_index_key(h);
2135 svn_log_changed_path2_t *change = svn__apr_hash_index_val(h);
2137 action[0] = change->action;
2139 SVN_ERR(svn_ra_svn__write_tuple(
2140 conn, pool, "cw(?cr)(cbb)",
2143 change->copyfrom_path,
2144 change->copyfrom_rev,
2145 svn_node_kind_to_word(change->node_kind),
2146 /* text_modified and props_modified are never unknown */
2147 change->text_modified == svn_tristate_true,
2148 change->props_modified == svn_tristate_true));
2151 svn_compat_log_revprops_out(&author, &date, &message, log_entry->revprops);
2152 svn_compat_log_revprops_clear(log_entry->revprops);
2153 if (log_entry->revprops)
2154 revprop_count = apr_hash_count(log_entry->revprops);
2157 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!)r(?c)(?c)(?c)bbn(!",
2158 log_entry->revision,
2159 author, date, message,
2160 log_entry->has_children,
2161 invalid_revnum, revprop_count));
2162 SVN_ERR(svn_ra_svn__write_proplist(conn, pool, log_entry->revprops));
2163 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!)b",
2164 log_entry->subtractive_merge));
2166 if (log_entry->has_children)
2169 return SVN_NO_ERROR;
2172 static svn_error_t *log_cmd(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
2173 apr_array_header_t *params, void *baton)
2175 svn_error_t *err, *write_err;
2176 server_baton_t *b = baton;
2177 svn_revnum_t start_rev, end_rev;
2178 const char *full_path;
2179 svn_boolean_t send_changed_paths, strict_node, include_merged_revisions;
2180 apr_array_header_t *paths, *full_paths, *revprop_items, *revprops;
2182 svn_ra_svn_item_t *elt;
2184 apr_uint64_t limit, include_merged_revs_param;
2191 SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "l(?r)(?r)bb?n?Bwl", &paths,
2192 &start_rev, &end_rev, &send_changed_paths,
2193 &strict_node, &limit,
2194 &include_merged_revs_param,
2195 &revprop_word, &revprop_items));
2197 if (include_merged_revs_param == SVN_RA_SVN_UNSPECIFIED_NUMBER)
2198 include_merged_revisions = FALSE;
2200 include_merged_revisions = (svn_boolean_t) include_merged_revs_param;
2202 if (revprop_word == NULL)
2203 /* pre-1.5 client */
2204 revprops = svn_compat_log_revprops_in(pool);
2205 else if (strcmp(revprop_word, "all-revprops") == 0)
2207 else if (strcmp(revprop_word, "revprops") == 0)
2209 SVN_ERR_ASSERT(revprop_items);
2211 revprops = apr_array_make(pool, revprop_items->nelts,
2213 for (i = 0; i < revprop_items->nelts; i++)
2215 elt = &APR_ARRAY_IDX(revprop_items, i, svn_ra_svn_item_t);
2216 if (elt->kind != SVN_RA_SVN_STRING)
2217 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2218 _("Log revprop entry not a string"));
2219 APR_ARRAY_PUSH(revprops, const char *) = elt->u.string->data;
2223 return svn_error_createf(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2224 _("Unknown revprop word '%s' in log command"),
2227 /* If we got an unspecified number then the user didn't send us anything,
2228 so we assume no limit. If it's larger than INT_MAX then someone is
2229 messing with us, since we know the svn client libraries will never send
2230 us anything that big, so play it safe and default to no limit. */
2231 if (limit == SVN_RA_SVN_UNSPECIFIED_NUMBER || limit > INT_MAX)
2234 full_paths = apr_array_make(pool, paths->nelts, sizeof(const char *));
2235 for (i = 0; i < paths->nelts; i++)
2237 elt = &APR_ARRAY_IDX(paths, i, svn_ra_svn_item_t);
2238 if (elt->kind != SVN_RA_SVN_STRING)
2239 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2240 _("Log path entry not a string"));
2241 full_path = svn_relpath_canonicalize(elt->u.string->data, pool),
2242 full_path = svn_fspath__join(b->fs_path->data, full_path, pool);
2243 APR_ARRAY_PUSH(full_paths, const char *) = full_path;
2245 SVN_ERR(trivial_auth_request(conn, pool, b));
2247 SVN_ERR(log_command(b, conn, pool, "%s",
2248 svn_log__log(full_paths, start_rev, end_rev,
2249 (int) limit, send_changed_paths,
2250 strict_node, include_merged_revisions,
2253 /* Get logs. (Can't report errors back to the client at this point.) */
2254 lb.fs_path = b->fs_path->data;
2257 err = svn_repos_get_logs4(b->repos, full_paths, start_rev, end_rev,
2258 (int) limit, send_changed_paths, strict_node,
2259 include_merged_revisions, revprops,
2260 authz_check_access_cb_func(b), &ab, log_receiver,
2263 write_err = svn_ra_svn__write_word(conn, pool, "done");
2266 svn_error_clear(err);
2270 SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, ""));
2271 return SVN_NO_ERROR;
2274 static svn_error_t *check_path(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
2275 apr_array_header_t *params, void *baton)
2277 server_baton_t *b = baton;
2279 const char *path, *full_path;
2280 svn_fs_root_t *root;
2281 svn_node_kind_t kind;
2283 SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "c(?r)", &path, &rev));
2284 full_path = svn_fspath__join(b->fs_path->data,
2285 svn_relpath_canonicalize(path, pool), pool);
2287 /* Check authorizations */
2288 SVN_ERR(must_have_access(conn, pool, b, svn_authz_read,
2291 if (!SVN_IS_VALID_REVNUM(rev))
2292 SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->fs, pool));
2294 SVN_ERR(log_command(b, conn, pool, "check-path %s@%d",
2295 svn_path_uri_encode(full_path, pool), rev));
2297 SVN_CMD_ERR(svn_fs_revision_root(&root, b->fs, rev, pool));
2298 SVN_CMD_ERR(svn_fs_check_path(&kind, root, full_path, pool));
2299 SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, "w",
2300 svn_node_kind_to_word(kind)));
2301 return SVN_NO_ERROR;
2304 static svn_error_t *stat_cmd(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
2305 apr_array_header_t *params, void *baton)
2307 server_baton_t *b = baton;
2309 const char *path, *full_path, *cdate;
2310 svn_fs_root_t *root;
2311 svn_dirent_t *dirent;
2313 SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "c(?r)", &path, &rev));
2314 full_path = svn_fspath__join(b->fs_path->data,
2315 svn_relpath_canonicalize(path, pool), pool);
2317 /* Check authorizations */
2318 SVN_ERR(must_have_access(conn, pool, b, svn_authz_read,
2321 if (!SVN_IS_VALID_REVNUM(rev))
2322 SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->fs, pool));
2324 SVN_ERR(log_command(b, conn, pool, "stat %s@%d",
2325 svn_path_uri_encode(full_path, pool), rev));
2327 SVN_CMD_ERR(svn_fs_revision_root(&root, b->fs, rev, pool));
2328 SVN_CMD_ERR(svn_repos_stat(&dirent, root, full_path, pool));
2330 /* Need to return the equivalent of "(?l)", since that's what the
2331 client is reading. */
2335 SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, "()"));
2336 return SVN_NO_ERROR;
2339 cdate = (dirent->time == (time_t) -1) ? NULL
2340 : svn_time_to_cstring(dirent->time, pool);
2342 SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, "((wnbr(?c)(?c)))",
2343 svn_node_kind_to_word(dirent->kind),
2344 (apr_uint64_t) dirent->size,
2345 dirent->has_props, dirent->created_rev,
2346 cdate, dirent->last_author));
2348 return SVN_NO_ERROR;
2351 static svn_error_t *get_locations(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
2352 apr_array_header_t *params, void *baton)
2354 svn_error_t *err, *write_err;
2355 server_baton_t *b = baton;
2356 svn_revnum_t revision;
2357 apr_array_header_t *location_revisions, *loc_revs_proto;
2358 svn_ra_svn_item_t *elt;
2360 const char *relative_path;
2361 svn_revnum_t peg_revision;
2362 apr_hash_t *fs_locations;
2363 const char *abs_path;
2369 /* Parse the arguments. */
2370 SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "crl", &relative_path,
2373 relative_path = svn_relpath_canonicalize(relative_path, pool);
2375 abs_path = svn_fspath__join(b->fs_path->data, relative_path, pool);
2377 location_revisions = apr_array_make(pool, loc_revs_proto->nelts,
2378 sizeof(svn_revnum_t));
2379 for (i = 0; i < loc_revs_proto->nelts; i++)
2381 elt = &APR_ARRAY_IDX(loc_revs_proto, i, svn_ra_svn_item_t);
2382 if (elt->kind != SVN_RA_SVN_NUMBER)
2383 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2384 "Get-locations location revisions entry "
2385 "not a revision number");
2386 revision = (svn_revnum_t)(elt->u.number);
2387 APR_ARRAY_PUSH(location_revisions, svn_revnum_t) = revision;
2389 SVN_ERR(trivial_auth_request(conn, pool, b));
2390 SVN_ERR(log_command(b, conn, pool, "%s",
2391 svn_log__get_locations(abs_path, peg_revision,
2392 location_revisions, pool)));
2394 /* All the parameters are fine - let's perform the query against the
2397 /* We store both err and write_err here, so the client will get
2398 * the "done" even if there was an error in fetching the results. */
2400 err = svn_repos_trace_node_locations(b->fs, &fs_locations, abs_path,
2401 peg_revision, location_revisions,
2402 authz_check_access_cb_func(b), &ab,
2405 /* Now, write the results to the connection. */
2410 apr_hash_index_t *iter;
2412 for (iter = apr_hash_first(pool, fs_locations); iter;
2413 iter = apr_hash_next(iter))
2415 const svn_revnum_t *iter_key = svn__apr_hash_index_key(iter);
2416 const char *iter_value = svn__apr_hash_index_val(iter);
2418 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "rc",
2419 *iter_key, iter_value));
2424 write_err = svn_ra_svn__write_word(conn, pool, "done");
2427 svn_error_clear(err);
2432 SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, ""));
2434 return SVN_NO_ERROR;
2437 static svn_error_t *gls_receiver(svn_location_segment_t *segment,
2441 svn_ra_svn_conn_t *conn = baton;
2442 return svn_ra_svn__write_tuple(conn, pool, "rr(?c)",
2443 segment->range_start,
2448 static svn_error_t *get_location_segments(svn_ra_svn_conn_t *conn,
2450 apr_array_header_t *params,
2453 svn_error_t *err, *write_err;
2454 server_baton_t *b = baton;
2455 svn_revnum_t peg_revision, start_rev, end_rev;
2456 const char *relative_path;
2457 const char *abs_path;
2463 /* Parse the arguments. */
2464 SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "c(?r)(?r)(?r)",
2465 &relative_path, &peg_revision,
2466 &start_rev, &end_rev));
2467 relative_path = svn_relpath_canonicalize(relative_path, pool);
2469 abs_path = svn_fspath__join(b->fs_path->data, relative_path, pool);
2471 SVN_ERR(trivial_auth_request(conn, pool, b));
2472 SVN_ERR(log_command(baton, conn, pool, "%s",
2473 svn_log__get_location_segments(abs_path, peg_revision,
2477 /* No START_REV or PEG_REVISION? We'll use HEAD. */
2478 if (!SVN_IS_VALID_REVNUM(start_rev) || !SVN_IS_VALID_REVNUM(peg_revision))
2480 svn_revnum_t youngest;
2482 SVN_CMD_ERR(svn_fs_youngest_rev(&youngest, b->fs, pool));
2484 if (!SVN_IS_VALID_REVNUM(start_rev))
2485 start_rev = youngest;
2486 if (!SVN_IS_VALID_REVNUM(peg_revision))
2487 peg_revision = youngest;
2490 /* No END_REV? We'll use 0. */
2491 if (!SVN_IS_VALID_REVNUM(end_rev))
2494 if (end_rev > start_rev)
2496 err = svn_error_createf(SVN_ERR_INCORRECT_PARAMS, NULL,
2497 "Get-location-segments end revision must not be "
2498 "younger than start revision");
2499 return log_fail_and_flush(err, b, conn, pool);
2502 if (start_rev > peg_revision)
2504 err = svn_error_createf(SVN_ERR_INCORRECT_PARAMS, NULL,
2505 "Get-location-segments start revision must not "
2506 "be younger than peg revision");
2507 return log_fail_and_flush(err, b, conn, pool);
2510 /* All the parameters are fine - let's perform the query against the
2513 /* We store both err and write_err here, so the client will get
2514 * the "done" even if there was an error in fetching the results. */
2516 err = svn_repos_node_location_segments(b->repos, abs_path,
2517 peg_revision, start_rev, end_rev,
2518 gls_receiver, (void *)conn,
2519 authz_check_access_cb_func(b), &ab,
2521 write_err = svn_ra_svn__write_word(conn, pool, "done");
2524 svn_error_clear(err);
2529 SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, ""));
2531 return SVN_NO_ERROR;
2534 /* This implements svn_write_fn_t. Write LEN bytes starting at DATA to the
2535 client as a string. */
2536 static svn_error_t *svndiff_handler(void *baton, const char *data,
2539 file_revs_baton_t *b = baton;
2544 return svn_ra_svn__write_string(b->conn, b->pool, &str);
2547 /* This implements svn_close_fn_t. Mark the end of the data by writing an
2548 empty string to the client. */
2549 static svn_error_t *svndiff_close_handler(void *baton)
2551 file_revs_baton_t *b = baton;
2553 SVN_ERR(svn_ra_svn__write_cstring(b->conn, b->pool, ""));
2554 return SVN_NO_ERROR;
2557 /* This implements the svn_repos_file_rev_handler_t interface. */
2558 static svn_error_t *file_rev_handler(void *baton, const char *path,
2559 svn_revnum_t rev, apr_hash_t *rev_props,
2560 svn_boolean_t merged_revision,
2561 svn_txdelta_window_handler_t *d_handler,
2563 apr_array_header_t *prop_diffs,
2566 file_revs_baton_t *frb = baton;
2567 svn_stream_t *stream;
2569 SVN_ERR(svn_ra_svn__write_tuple(frb->conn, pool, "cr(!",
2571 SVN_ERR(svn_ra_svn__write_proplist(frb->conn, pool, rev_props));
2572 SVN_ERR(svn_ra_svn__write_tuple(frb->conn, pool, "!)(!"));
2573 SVN_ERR(write_prop_diffs(frb->conn, pool, prop_diffs));
2574 SVN_ERR(svn_ra_svn__write_tuple(frb->conn, pool, "!)b", merged_revision));
2576 /* Store the pool for the delta stream. */
2579 /* Prepare for the delta or just write an empty string. */
2582 stream = svn_stream_create(baton, pool);
2583 svn_stream_set_write(stream, svndiff_handler);
2584 svn_stream_set_close(stream, svndiff_close_handler);
2586 /* If the connection does not support SVNDIFF1 or if we don't want to use
2587 * compression, use the non-compressing "version 0" implementation */
2588 if ( svn_ra_svn_compression_level(frb->conn) > 0
2589 && svn_ra_svn_has_capability(frb->conn, SVN_RA_SVN_CAP_SVNDIFF1))
2590 svn_txdelta_to_svndiff3(d_handler, d_baton, stream, 1,
2591 svn_ra_svn_compression_level(frb->conn), pool);
2593 svn_txdelta_to_svndiff3(d_handler, d_baton, stream, 0,
2594 svn_ra_svn_compression_level(frb->conn), pool);
2597 SVN_ERR(svn_ra_svn__write_cstring(frb->conn, pool, ""));
2599 return SVN_NO_ERROR;
2602 static svn_error_t *get_file_revs(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
2603 apr_array_header_t *params, void *baton)
2605 server_baton_t *b = baton;
2606 svn_error_t *err, *write_err;
2607 file_revs_baton_t frb;
2608 svn_revnum_t start_rev, end_rev;
2610 const char *full_path;
2611 apr_uint64_t include_merged_revs_param;
2612 svn_boolean_t include_merged_revisions;
2618 /* Parse arguments. */
2619 SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "c(?r)(?r)?B",
2620 &path, &start_rev, &end_rev,
2621 &include_merged_revs_param));
2622 path = svn_relpath_canonicalize(path, pool);
2623 SVN_ERR(trivial_auth_request(conn, pool, b));
2624 full_path = svn_fspath__join(b->fs_path->data, path, pool);
2626 if (include_merged_revs_param == SVN_RA_SVN_UNSPECIFIED_NUMBER)
2627 include_merged_revisions = FALSE;
2629 include_merged_revisions = (svn_boolean_t) include_merged_revs_param;
2631 SVN_ERR(log_command(b, conn, pool, "%s",
2632 svn_log__get_file_revs(full_path, start_rev, end_rev,
2633 include_merged_revisions,
2639 err = svn_repos_get_file_revs2(b->repos, full_path, start_rev, end_rev,
2640 include_merged_revisions,
2641 authz_check_access_cb_func(b), &ab,
2642 file_rev_handler, &frb, pool);
2643 write_err = svn_ra_svn__write_word(conn, pool, "done");
2646 svn_error_clear(err);
2650 SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, ""));
2652 return SVN_NO_ERROR;
2655 static svn_error_t *lock(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
2656 apr_array_header_t *params, void *baton)
2658 server_baton_t *b = baton;
2660 const char *comment;
2661 const char *full_path;
2662 svn_boolean_t steal_lock;
2663 svn_revnum_t current_rev;
2666 SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "c(?c)b(?r)", &path, &comment,
2667 &steal_lock, ¤t_rev));
2668 full_path = svn_fspath__join(b->fs_path->data,
2669 svn_relpath_canonicalize(path, pool), pool);
2671 SVN_ERR(must_have_access(conn, pool, b, svn_authz_write,
2673 SVN_ERR(log_command(b, conn, pool, "%s",
2674 svn_log__lock_one_path(full_path, steal_lock, pool)));
2676 SVN_CMD_ERR(svn_repos_fs_lock(&l, b->repos, full_path, NULL, comment, 0,
2677 0, /* No expiration time. */
2678 current_rev, steal_lock, pool));
2680 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w(!", "success"));
2681 SVN_ERR(write_lock(conn, pool, l));
2682 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!)"));
2684 return SVN_NO_ERROR;
2687 static svn_error_t *lock_many(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
2688 apr_array_header_t *params, void *baton)
2690 server_baton_t *b = baton;
2691 apr_array_header_t *path_revs;
2692 const char *comment;
2693 svn_boolean_t steal_lock;
2695 apr_pool_t *subpool;
2697 const char *full_path;
2698 svn_revnum_t current_rev;
2699 apr_array_header_t *log_paths;
2701 svn_error_t *err = SVN_NO_ERROR, *write_err;
2703 SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "(?c)bl", &comment, &steal_lock,
2706 subpool = svn_pool_create(pool);
2708 /* Because we can only send a single auth reply per request, we send
2709 a reply before parsing the lock commands. This means an authz
2710 access denial will abort the processing of the locks and return
2712 SVN_ERR(must_have_access(conn, pool, b, svn_authz_write, NULL, TRUE));
2714 /* Loop through the lock requests. */
2715 log_paths = apr_array_make(pool, path_revs->nelts, sizeof(full_path));
2716 for (i = 0; i < path_revs->nelts; ++i)
2718 svn_ra_svn_item_t *item = &APR_ARRAY_IDX(path_revs, i,
2721 svn_pool_clear(subpool);
2723 if (item->kind != SVN_RA_SVN_LIST)
2724 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2725 "Lock requests should be list of lists");
2727 SVN_ERR(svn_ra_svn__parse_tuple(item->u.list, pool, "c(?r)", &path,
2730 /* Allocate the full_path out of pool so it will survive for use
2731 * by operational logging, after this loop. */
2732 full_path = svn_fspath__join(b->fs_path->data,
2733 svn_relpath_canonicalize(path, subpool),
2735 APR_ARRAY_PUSH(log_paths, const char *) = full_path;
2737 if (! lookup_access(pool, b, conn, svn_authz_write, full_path, TRUE))
2739 err = error_create_and_log(SVN_ERR_RA_NOT_AUTHORIZED, NULL, NULL,
2744 err = svn_repos_fs_lock(&l, b->repos, full_path,
2745 NULL, comment, FALSE,
2746 0, /* No expiration time. */
2748 steal_lock, subpool);
2752 if (SVN_ERR_IS_LOCK_ERROR(err))
2754 write_err = svn_ra_svn__write_cmd_failure(conn, pool, err);
2755 svn_error_clear(err);
2764 SVN_ERR(svn_ra_svn__write_tuple(conn, subpool, "w!", "success"));
2765 SVN_ERR(write_lock(conn, subpool, l));
2766 SVN_ERR(svn_ra_svn__write_tuple(conn, subpool, "!"));
2770 svn_pool_destroy(subpool);
2772 SVN_ERR(log_command(b, conn, pool, "%s",
2773 svn_log__lock(log_paths, steal_lock, pool)));
2775 /* NOTE: err might contain a fatal locking error from the loop above. */
2776 write_err = svn_ra_svn__write_word(conn, pool, "done");
2779 svn_error_clear(err);
2781 SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, ""));
2783 return SVN_NO_ERROR;
2786 static svn_error_t *unlock(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
2787 apr_array_header_t *params, void *baton)
2789 server_baton_t *b = baton;
2790 const char *path, *token, *full_path;
2791 svn_boolean_t break_lock;
2793 SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "c(?c)b", &path, &token,
2796 full_path = svn_fspath__join(b->fs_path->data,
2797 svn_relpath_canonicalize(path, pool), pool);
2799 /* Username required unless break_lock was specified. */
2800 SVN_ERR(must_have_access(conn, pool, b, svn_authz_write,
2801 full_path, ! break_lock));
2802 SVN_ERR(log_command(b, conn, pool, "%s",
2803 svn_log__unlock_one_path(full_path, break_lock, pool)));
2805 SVN_CMD_ERR(svn_repos_fs_unlock(b->repos, full_path, token, break_lock,
2808 SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, ""));
2810 return SVN_NO_ERROR;
2813 static svn_error_t *unlock_many(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
2814 apr_array_header_t *params, void *baton)
2816 server_baton_t *b = baton;
2817 svn_boolean_t break_lock;
2818 apr_array_header_t *unlock_tokens;
2820 apr_pool_t *subpool;
2822 const char *full_path;
2823 apr_array_header_t *log_paths;
2825 svn_error_t *err = SVN_NO_ERROR, *write_err;
2827 SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "bl", &break_lock,
2830 /* Username required unless break_lock was specified. */
2831 SVN_ERR(must_have_access(conn, pool, b, svn_authz_write, NULL, ! break_lock));
2833 subpool = svn_pool_create(pool);
2835 /* Loop through the unlock requests. */
2836 log_paths = apr_array_make(pool, unlock_tokens->nelts, sizeof(full_path));
2837 for (i = 0; i < unlock_tokens->nelts; i++)
2839 svn_ra_svn_item_t *item = &APR_ARRAY_IDX(unlock_tokens, i,
2842 svn_pool_clear(subpool);
2844 if (item->kind != SVN_RA_SVN_LIST)
2845 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2846 "Unlock request should be a list of lists");
2848 SVN_ERR(svn_ra_svn__parse_tuple(item->u.list, subpool, "c(?c)", &path,
2851 /* Allocate the full_path out of pool so it will survive for use
2852 * by operational logging, after this loop. */
2853 full_path = svn_fspath__join(b->fs_path->data,
2854 svn_relpath_canonicalize(path, subpool),
2856 APR_ARRAY_PUSH(log_paths, const char *) = full_path;
2858 if (! lookup_access(subpool, b, conn, svn_authz_write, full_path,
2860 return svn_error_create(SVN_ERR_RA_SVN_CMD_ERR,
2861 error_create_and_log(SVN_ERR_RA_NOT_AUTHORIZED,
2866 err = svn_repos_fs_unlock(b->repos, full_path, token, break_lock,
2870 if (SVN_ERR_IS_UNLOCK_ERROR(err))
2872 write_err = svn_ra_svn__write_cmd_failure(conn, pool, err);
2873 svn_error_clear(err);
2881 SVN_ERR(svn_ra_svn__write_tuple(conn, subpool, "w(c)", "success",
2885 svn_pool_destroy(subpool);
2887 SVN_ERR(log_command(b, conn, pool, "%s",
2888 svn_log__unlock(log_paths, break_lock, pool)));
2890 /* NOTE: err might contain a fatal unlocking error from the loop above. */
2891 write_err = svn_ra_svn__write_word(conn, pool, "done");
2894 svn_error_clear(err);
2895 SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, ""));
2897 return SVN_NO_ERROR;
2900 static svn_error_t *get_lock(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
2901 apr_array_header_t *params, void *baton)
2903 server_baton_t *b = baton;
2905 const char *full_path;
2908 SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "c", &path));
2910 full_path = svn_fspath__join(b->fs_path->data,
2911 svn_relpath_canonicalize(path, pool), pool);
2913 SVN_ERR(must_have_access(conn, pool, b, svn_authz_read,
2915 SVN_ERR(log_command(b, conn, pool, "get-lock %s",
2916 svn_path_uri_encode(full_path, pool)));
2918 SVN_CMD_ERR(svn_fs_get_lock(&l, b->fs, full_path, pool));
2920 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w((!", "success"));
2922 SVN_ERR(write_lock(conn, pool, l));
2923 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!))"));
2925 return SVN_NO_ERROR;
2928 static svn_error_t *get_locks(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
2929 apr_array_header_t *params, void *baton)
2931 server_baton_t *b = baton;
2933 const char *full_path;
2934 const char *depth_word;
2937 apr_hash_index_t *hi;
2944 SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "c?(?w)", &path, &depth_word));
2946 depth = depth_word ? svn_depth_from_word(depth_word) : svn_depth_infinity;
2947 if ((depth != svn_depth_empty) &&
2948 (depth != svn_depth_files) &&
2949 (depth != svn_depth_immediates) &&
2950 (depth != svn_depth_infinity))
2952 err = svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL,
2953 "Invalid 'depth' specified in get-locks request");
2954 return log_fail_and_flush(err, b, conn, pool);
2957 full_path = svn_fspath__join(b->fs_path->data,
2958 svn_relpath_canonicalize(path, pool), pool);
2960 SVN_ERR(trivial_auth_request(conn, pool, b));
2962 SVN_ERR(log_command(b, conn, pool, "get-locks %s",
2963 svn_path_uri_encode(full_path, pool)));
2964 SVN_CMD_ERR(svn_repos_fs_get_locks2(&locks, b->repos, full_path, depth,
2965 authz_check_access_cb_func(b), &ab,
2968 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w((!", "success"));
2969 for (hi = apr_hash_first(pool, locks); hi; hi = apr_hash_next(hi))
2971 svn_lock_t *l = svn__apr_hash_index_val(hi);
2973 SVN_ERR(write_lock(conn, pool, l));
2975 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!))"));
2977 return SVN_NO_ERROR;
2980 static svn_error_t *replay_one_revision(svn_ra_svn_conn_t *conn,
2983 svn_revnum_t low_water_mark,
2984 svn_boolean_t send_deltas,
2987 const svn_delta_editor_t *editor;
2989 svn_fs_root_t *root;
2996 SVN_ERR(log_command(b, conn, pool,
2997 svn_log__replay(b->fs_path->data, rev, pool)));
2999 svn_ra_svn_get_editor(&editor, &edit_baton, conn, pool, NULL, NULL);
3001 err = svn_fs_revision_root(&root, b->fs, rev, pool);
3004 err = svn_repos_replay2(root, b->fs_path->data, low_water_mark,
3005 send_deltas, editor, edit_baton,
3006 authz_check_access_cb_func(b), &ab, pool);
3009 svn_error_clear(editor->abort_edit(edit_baton, pool));
3012 return svn_ra_svn__write_cmd_finish_replay(conn, pool);
3015 static svn_error_t *replay(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
3016 apr_array_header_t *params, void *baton)
3018 svn_revnum_t rev, low_water_mark;
3019 svn_boolean_t send_deltas;
3020 server_baton_t *b = baton;
3022 SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "rrb", &rev, &low_water_mark,
3025 SVN_ERR(trivial_auth_request(conn, pool, b));
3027 SVN_ERR(replay_one_revision(conn, b, rev, low_water_mark,
3028 send_deltas, pool));
3030 SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, ""));
3032 return SVN_NO_ERROR;
3035 static svn_error_t *replay_range(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
3036 apr_array_header_t *params, void *baton)
3038 svn_revnum_t start_rev, end_rev, rev, low_water_mark;
3039 svn_boolean_t send_deltas;
3040 server_baton_t *b = baton;
3041 apr_pool_t *iterpool;
3047 SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "rrrb", &start_rev,
3048 &end_rev, &low_water_mark,
3051 SVN_ERR(trivial_auth_request(conn, pool, b));
3053 iterpool = svn_pool_create(pool);
3054 for (rev = start_rev; rev <= end_rev; rev++)
3058 svn_pool_clear(iterpool);
3060 SVN_CMD_ERR(svn_repos_fs_revision_proplist(&props, b->repos, rev,
3061 authz_check_access_cb_func(b),
3064 SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "w(!", "revprops"));
3065 SVN_ERR(svn_ra_svn__write_proplist(conn, iterpool, props));
3066 SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "!)"));
3068 SVN_ERR(replay_one_revision(conn, b, rev, low_water_mark,
3069 send_deltas, iterpool));
3072 svn_pool_destroy(iterpool);
3074 SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, ""));
3076 return SVN_NO_ERROR;
3079 static svn_error_t *
3080 get_deleted_rev(svn_ra_svn_conn_t *conn,
3082 apr_array_header_t *params,
3085 server_baton_t *b = baton;
3086 const char *path, *full_path;
3087 svn_revnum_t peg_revision;
3088 svn_revnum_t end_revision;
3089 svn_revnum_t revision_deleted;
3091 SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "crr",
3092 &path, &peg_revision, &end_revision));
3093 full_path = svn_fspath__join(b->fs_path->data,
3094 svn_relpath_canonicalize(path, pool), pool);
3095 SVN_ERR(log_command(b, conn, pool, "get-deleted-rev"));
3096 SVN_ERR(trivial_auth_request(conn, pool, b));
3097 SVN_ERR(svn_repos_deleted_rev(b->fs, full_path, peg_revision, end_revision,
3098 &revision_deleted, pool));
3099 SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, "r", revision_deleted));
3100 return SVN_NO_ERROR;
3103 static svn_error_t *
3104 get_inherited_props(svn_ra_svn_conn_t *conn,
3106 apr_array_header_t *params,
3109 server_baton_t *b = baton;
3110 const char *path, *full_path;
3112 svn_fs_root_t *root;
3113 apr_array_header_t *inherited_props;
3115 apr_pool_t *iterpool = svn_pool_create(pool);
3121 /* Parse arguments. */
3122 SVN_ERR(svn_ra_svn__parse_tuple(params, iterpool, "c(?r)", &path, &rev));
3124 full_path = svn_fspath__join(b->fs_path->data,
3125 svn_relpath_canonicalize(path, iterpool),
3128 /* Check authorizations */
3129 SVN_ERR(must_have_access(conn, iterpool, b, svn_authz_read,
3132 if (!SVN_IS_VALID_REVNUM(rev))
3133 SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->fs, pool));
3135 SVN_ERR(log_command(b, conn, pool, "%s",
3136 svn_log__get_inherited_props(full_path, rev,
3139 /* Fetch the properties and a stream for the contents. */
3140 SVN_CMD_ERR(svn_fs_revision_root(&root, b->fs, rev, iterpool));
3141 SVN_CMD_ERR(get_props(NULL, &inherited_props, &ab, root, full_path, pool));
3143 /* Send successful command response with revision and props. */
3144 SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "w(!", "success"));
3146 SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "!(?!"));
3148 for (i = 0; i < inherited_props->nelts; i++)
3150 svn_prop_inherited_item_t *iprop =
3151 APR_ARRAY_IDX(inherited_props, i, svn_prop_inherited_item_t *);
3153 svn_pool_clear(iterpool);
3154 SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "!(c(!",
3155 iprop->path_or_url));
3156 SVN_ERR(svn_ra_svn__write_proplist(conn, iterpool, iprop->prop_hash));
3157 SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "!))!",
3158 iprop->path_or_url));
3161 SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "!))"));
3162 svn_pool_destroy(iterpool);
3163 return SVN_NO_ERROR;
3166 static const svn_ra_svn_cmd_entry_t main_commands[] = {
3167 { "reparent", reparent },
3168 { "get-latest-rev", get_latest_rev },
3169 { "get-dated-rev", get_dated_rev },
3170 { "change-rev-prop", change_rev_prop },
3171 { "change-rev-prop2",change_rev_prop2 },
3172 { "rev-proplist", rev_proplist },
3173 { "rev-prop", rev_prop },
3174 { "commit", commit },
3175 { "get-file", get_file },
3176 { "get-dir", get_dir },
3177 { "update", update },
3178 { "switch", switch_cmd },
3179 { "status", status },
3181 { "get-mergeinfo", get_mergeinfo },
3183 { "check-path", check_path },
3184 { "stat", stat_cmd },
3185 { "get-locations", get_locations },
3186 { "get-location-segments", get_location_segments },
3187 { "get-file-revs", get_file_revs },
3189 { "lock-many", lock_many },
3190 { "unlock", unlock },
3191 { "unlock-many", unlock_many },
3192 { "get-lock", get_lock },
3193 { "get-locks", get_locks },
3194 { "replay", replay },
3195 { "replay-range", replay_range },
3196 { "get-deleted-rev", get_deleted_rev },
3197 { "get-iprops", get_inherited_props },
3201 /* Skip past the scheme part of a URL, including the tunnel specification
3202 * if present. Return NULL if the scheme part is invalid for ra_svn. */
3203 static const char *skip_scheme_part(const char *url)
3205 if (strncmp(url, "svn", 3) != 0)
3209 url += strcspn(url, ":");
3210 if (strncmp(url, "://", 3) != 0)
3215 /* Check that PATH is a valid repository path, meaning it doesn't contain any
3217 NOTE: This is similar to svn_path_is_backpath_present, but that function
3218 assumes the path separator is '/'. This function also checks for
3219 segments delimited by the local path separator. */
3220 static svn_boolean_t
3221 repos_path_valid(const char *path)
3223 const char *s = path;
3227 /* Scan for the end of the segment. */
3228 while (*path && *path != '/' && *path != SVN_PATH_LOCAL_SEPARATOR)
3231 /* Check for '..'. */
3233 /* On Windows, don't allow sequences of more than one character
3234 consisting of just dots and spaces. Win32 functions treat
3235 paths such as ".. " and "......." inconsistently. Make sure
3236 no one can escape out of the root. */
3237 if (path - s >= 2 && strspn(s, ". ") == (size_t)(path - s))
3240 if (path - s == 2 && s[0] == '.' && s[1] == '.')
3244 /* Skip all separators. */
3245 while (*path && (*path == '/' || *path == SVN_PATH_LOCAL_SEPARATOR))
3253 /* Look for the repository given by URL, using ROOT as the virtual
3254 * repository root. If we find one, fill in the repos, fs, cfg,
3255 * repos_url, and fs_path fields of B. Set B->repos's client
3256 * capabilities to CAPABILITIES, which must be at least as long-lived
3257 * as POOL, and whose elements are SVN_RA_CAPABILITY_*.
3259 static svn_error_t *find_repos(const char *url, const char *root,
3261 svn_ra_svn_conn_t *conn,
3262 const apr_array_header_t *capabilities,
3265 const char *path, *full_path, *repos_root, *fs_path, *hooks_env;
3266 svn_stringbuf_t *url_buf;
3268 /* Skip past the scheme and authority part. */
3269 path = skip_scheme_part(url);
3271 return svn_error_createf(SVN_ERR_BAD_URL, NULL,
3272 "Non-svn URL passed to svn server: '%s'", url);
3276 path = strchr(path, '/');
3280 path = svn_relpath_canonicalize(path, pool);
3281 path = svn_path_uri_decode(path, pool);
3283 /* Ensure that it isn't possible to escape the root by disallowing
3285 if (!repos_path_valid(path))
3286 return svn_error_create(SVN_ERR_BAD_FILENAME, NULL,
3287 "Couldn't determine repository path");
3289 /* Join the server-configured root with the client path. */
3290 full_path = svn_dirent_join(svn_dirent_canonicalize(root, pool),
3293 /* Search for a repository in the full path. */
3294 repos_root = svn_repos_find_root_path(full_path, pool);
3296 return svn_error_createf(SVN_ERR_RA_SVN_REPOS_NOT_FOUND, NULL,
3297 "No repository found in '%s'", url);
3299 /* Open the repository and fill in b with the resulting information. */
3300 SVN_ERR(svn_repos_open2(&b->repos, repos_root, b->fs_config, pool));
3301 SVN_ERR(svn_repos_remember_client_capabilities(b->repos, capabilities));
3302 b->fs = svn_repos_fs(b->repos);
3303 fs_path = full_path + strlen(repos_root);
3304 b->fs_path = svn_stringbuf_create(*fs_path ? fs_path : "/", pool);
3305 url_buf = svn_stringbuf_create(url, pool);
3306 svn_path_remove_components(url_buf,
3307 svn_path_component_count(b->fs_path->data));
3308 b->repos_url = url_buf->data;
3309 b->authz_repos_name = svn_dirent_is_child(root, repos_root, pool);
3310 if (b->authz_repos_name == NULL)
3311 b->repos_name = svn_dirent_basename(repos_root, pool);
3313 b->repos_name = b->authz_repos_name;
3314 b->repos_name = svn_path_uri_encode(b->repos_name, pool);
3316 /* If the svnserve configuration has not been loaded then load it from the
3320 b->base = svn_repos_conf_dir(b->repos, pool);
3322 SVN_ERR(svn_config_read3(&b->cfg, svn_repos_svnserve_conf(b->repos, pool),
3323 FALSE, /* must_exist */
3324 FALSE, /* section_names_case_sensitive */
3325 FALSE, /* option_names_case_sensitive */
3327 SVN_ERR(load_pwdb_config(b, conn, pool));
3328 SVN_ERR(load_authz_config(b, conn, repos_root, pool));
3330 /* svnserve.conf has been loaded via the --config-file option so need
3331 * to load pwdb and authz. */
3334 SVN_ERR(load_pwdb_config(b, conn, pool));
3335 SVN_ERR(load_authz_config(b, conn, repos_root, pool));
3338 #ifdef SVN_HAVE_SASL
3339 /* Should we use Cyrus SASL? */
3340 SVN_ERR(svn_config_get_bool(b->cfg, &b->use_sasl, SVN_CONFIG_SECTION_SASL,
3341 SVN_CONFIG_OPTION_USE_SASL, FALSE));
3344 /* Use the repository UUID as the default realm. */
3345 SVN_ERR(svn_fs_get_uuid(b->fs, &b->realm, pool));
3346 svn_config_get(b->cfg, &b->realm, SVN_CONFIG_SECTION_GENERAL,
3347 SVN_CONFIG_OPTION_REALM, b->realm);
3349 /* Make sure it's possible for the client to authenticate. Note
3350 that this doesn't take into account any authz configuration read
3351 above, because we can't know about access it grants until paths
3352 are given by the client. */
3353 if (get_access(b, UNAUTHENTICATED) == NO_ACCESS
3354 && (get_access(b, AUTHENTICATED) == NO_ACCESS
3355 || (!b->tunnel_user && !b->pwdb && !b->use_sasl)))
3356 return error_create_and_log(SVN_ERR_RA_NOT_AUTHORIZED, NULL,
3357 "No access allowed to this repository",
3360 /* Configure hook script environment variables. */
3361 svn_config_get(b->cfg, &hooks_env, SVN_CONFIG_SECTION_GENERAL,
3362 SVN_CONFIG_OPTION_HOOKS_ENV, NULL);
3364 hooks_env = svn_dirent_internal_style(hooks_env, pool);
3365 SVN_ERR(svn_repos_hooks_setenv(b->repos, hooks_env, pool));
3367 return SVN_NO_ERROR;
3370 /* Compute the authentication name EXTERNAL should be able to get, if any. */
3371 static const char *get_tunnel_user(serve_params_t *params, apr_pool_t *pool)
3373 /* Only offer EXTERNAL for connections tunneled over a login agent. */
3374 if (!params->tunnel)
3377 /* If a tunnel user was provided on the command line, use that. */
3378 if (params->tunnel_user)
3379 return params->tunnel_user;
3381 return svn_user_get_name(pool);
3385 fs_warning_func(void *baton, svn_error_t *err)
3387 fs_warning_baton_t *b = baton;
3388 log_server_error(err, b->server, b->conn, b->pool);
3389 /* TODO: Keep log_pool in the server baton, cleared after every log? */
3390 svn_pool_clear(b->pool);
3393 /* Return the normalized repository-relative path for the given PATH
3394 * (may be a URL, full path or relative path) and fs contained in the
3395 * server baton BATON. Allocate the result in POOL.
3398 get_normalized_repo_rel_path(void *baton,
3402 server_baton_t *sb = baton;
3404 if (svn_path_is_url(path))
3406 /* This is a copyfrom URL. */
3407 path = svn_uri_skip_ancestor(sb->repos_url, path, pool);
3408 path = svn_fspath__canonicalize(path, pool);
3412 /* This is a base-relative path. */
3413 if ((path)[0] != '/')
3414 /* Get an absolute path for use in the FS. */
3415 path = svn_fspath__join(sb->fs_path->data, path, pool);
3421 /* Get the revision root for REVISION in fs given by server baton BATON
3422 * and return it in *FS_ROOT. Use HEAD if REVISION is SVN_INVALID_REVNUM.
3423 * Use POOL for allocations.
3425 static svn_error_t *
3426 get_revision_root(svn_fs_root_t **fs_root,
3428 svn_revnum_t revision,
3431 server_baton_t *sb = baton;
3433 if (!SVN_IS_VALID_REVNUM(revision))
3434 SVN_ERR(svn_fs_youngest_rev(&revision, sb->fs, pool));
3436 SVN_ERR(svn_fs_revision_root(fs_root, sb->fs, revision, pool));
3438 return SVN_NO_ERROR;
3441 static svn_error_t *
3442 fetch_props_func(apr_hash_t **props,
3445 svn_revnum_t base_revision,
3446 apr_pool_t *result_pool,
3447 apr_pool_t *scratch_pool)
3449 svn_fs_root_t *fs_root;
3452 path = get_normalized_repo_rel_path(baton, path, scratch_pool);
3453 SVN_ERR(get_revision_root(&fs_root, baton, base_revision, scratch_pool));
3455 err = svn_fs_node_proplist(props, fs_root, path, result_pool);
3456 if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND)
3458 svn_error_clear(err);
3459 *props = apr_hash_make(result_pool);
3460 return SVN_NO_ERROR;
3463 return svn_error_trace(err);
3465 return SVN_NO_ERROR;
3468 static svn_error_t *
3469 fetch_kind_func(svn_node_kind_t *kind,
3472 svn_revnum_t base_revision,
3473 apr_pool_t *scratch_pool)
3475 svn_fs_root_t *fs_root;
3477 path = get_normalized_repo_rel_path(baton, path, scratch_pool);
3478 SVN_ERR(get_revision_root(&fs_root, baton, base_revision, scratch_pool));
3480 SVN_ERR(svn_fs_check_path(kind, fs_root, path, scratch_pool));
3482 return SVN_NO_ERROR;
3485 static svn_error_t *
3486 fetch_base_func(const char **filename,
3489 svn_revnum_t base_revision,
3490 apr_pool_t *result_pool,
3491 apr_pool_t *scratch_pool)
3493 svn_stream_t *contents;
3494 svn_stream_t *file_stream;
3495 const char *tmp_filename;
3496 svn_fs_root_t *fs_root;
3499 path = get_normalized_repo_rel_path(baton, path, scratch_pool);
3500 SVN_ERR(get_revision_root(&fs_root, baton, base_revision, scratch_pool));
3502 err = svn_fs_file_contents(&contents, fs_root, path, scratch_pool);
3503 if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND)
3505 svn_error_clear(err);
3507 return SVN_NO_ERROR;
3510 return svn_error_trace(err);
3511 SVN_ERR(svn_stream_open_unique(&file_stream, &tmp_filename, NULL,
3512 svn_io_file_del_on_pool_cleanup,
3513 scratch_pool, scratch_pool));
3514 SVN_ERR(svn_stream_copy3(contents, file_stream, NULL, NULL, scratch_pool));
3516 *filename = apr_pstrdup(result_pool, tmp_filename);
3518 return SVN_NO_ERROR;
3521 svn_error_t *serve(svn_ra_svn_conn_t *conn, serve_params_t *params,
3524 svn_error_t *err, *io_err;
3526 const char *uuid, *client_url, *ra_client_string, *client_string;
3527 apr_array_header_t *caplist, *cap_words;
3529 fs_warning_baton_t warn_baton;
3530 svn_stringbuf_t *cap_log = svn_stringbuf_create_empty(pool);
3532 b.tunnel = params->tunnel;
3533 b.tunnel_user = get_tunnel_user(params, pool);
3534 b.read_only = params->read_only;
3536 b.username_case = params->username_case;
3537 b.authz_user = NULL;
3538 b.base = params->base;
3539 b.cfg = params->cfg;
3543 b.log_file = params->log_file;
3546 b.vhost = params->vhost;
3548 /* construct FS configuration parameters */
3549 b.fs_config = apr_hash_make(pool);
3550 svn_hash_sets(b.fs_config, SVN_FS_CONFIG_FSFS_CACHE_DELTAS,
3551 params->cache_txdeltas ? "1" :"0");
3552 svn_hash_sets(b.fs_config, SVN_FS_CONFIG_FSFS_CACHE_FULLTEXTS,
3553 params->cache_fulltexts ? "1" :"0");
3554 svn_hash_sets(b.fs_config, SVN_FS_CONFIG_FSFS_CACHE_REVPROPS,
3555 params->cache_revprops ? "1" :"0");
3557 /* Send greeting. We don't support version 1 any more, so we can
3558 * send an empty mechlist. */
3559 if (params->compression_level > 0)
3560 SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, "nn()(wwwwwwwwwww)",
3561 (apr_uint64_t) 2, (apr_uint64_t) 2,
3562 SVN_RA_SVN_CAP_EDIT_PIPELINE,
3563 SVN_RA_SVN_CAP_SVNDIFF1,
3564 SVN_RA_SVN_CAP_ABSENT_ENTRIES,
3565 SVN_RA_SVN_CAP_COMMIT_REVPROPS,
3566 SVN_RA_SVN_CAP_DEPTH,
3567 SVN_RA_SVN_CAP_LOG_REVPROPS,
3568 SVN_RA_SVN_CAP_ATOMIC_REVPROPS,
3569 SVN_RA_SVN_CAP_PARTIAL_REPLAY,
3570 SVN_RA_SVN_CAP_INHERITED_PROPS,
3571 SVN_RA_SVN_CAP_EPHEMERAL_TXNPROPS,
3572 SVN_RA_SVN_CAP_GET_FILE_REVS_REVERSE
3575 SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, "nn()(wwwwwwwwww)",
3576 (apr_uint64_t) 2, (apr_uint64_t) 2,
3577 SVN_RA_SVN_CAP_EDIT_PIPELINE,
3578 SVN_RA_SVN_CAP_ABSENT_ENTRIES,
3579 SVN_RA_SVN_CAP_COMMIT_REVPROPS,
3580 SVN_RA_SVN_CAP_DEPTH,
3581 SVN_RA_SVN_CAP_LOG_REVPROPS,
3582 SVN_RA_SVN_CAP_ATOMIC_REVPROPS,
3583 SVN_RA_SVN_CAP_PARTIAL_REPLAY,
3584 SVN_RA_SVN_CAP_INHERITED_PROPS,
3585 SVN_RA_SVN_CAP_EPHEMERAL_TXNPROPS,
3586 SVN_RA_SVN_CAP_GET_FILE_REVS_REVERSE
3589 /* Read client response, which we assume to be in version 2 format:
3590 * version, capability list, and client URL; then we do an auth
3592 SVN_ERR(svn_ra_svn__read_tuple(conn, pool, "nlc?c(?c)",
3593 &ver, &caplist, &client_url,
3597 return SVN_NO_ERROR;
3599 client_url = svn_uri_canonicalize(client_url, pool);
3600 SVN_ERR(svn_ra_svn_set_capabilities(conn, caplist));
3602 /* All released versions of Subversion support edit-pipeline,
3603 * so we do not accept connections from clients that do not. */
3604 if (! svn_ra_svn_has_capability(conn, SVN_RA_SVN_CAP_EDIT_PIPELINE))
3605 return SVN_NO_ERROR;
3607 /* find_repos needs the capabilities as a list of words (eventually
3608 they get handed to the start-commit hook). While we could add a
3609 new interface to re-retrieve them from conn and convert the
3610 result to a list, it's simpler to just convert caplist by hand
3611 here, since we already have it and turning 'svn_ra_svn_item_t's
3612 into 'const char *'s is pretty easy.
3614 We only record capabilities we care about. The client may report
3615 more (because it doesn't know what the server cares about). */
3618 svn_ra_svn_item_t *item;
3620 cap_words = apr_array_make(pool, 1, sizeof(const char *));
3621 for (i = 0; i < caplist->nelts; i++)
3623 item = &APR_ARRAY_IDX(caplist, i, svn_ra_svn_item_t);
3624 /* ra_svn_set_capabilities() already type-checked for us */
3625 if (strcmp(item->u.word, SVN_RA_SVN_CAP_MERGEINFO) == 0)
3627 APR_ARRAY_PUSH(cap_words, const char *)
3628 = SVN_RA_CAPABILITY_MERGEINFO;
3630 /* Save for operational log. */
3631 if (cap_log->len > 0)
3632 svn_stringbuf_appendcstr(cap_log, " ");
3633 svn_stringbuf_appendcstr(cap_log, item->u.word);
3637 err = find_repos(client_url, params->root, &b, conn, cap_words, pool);
3640 SVN_ERR(auth_request(conn, pool, &b, READ_ACCESS, FALSE));
3641 if (current_access(&b) == NO_ACCESS)
3642 err = error_create_and_log(SVN_ERR_RA_NOT_AUTHORIZED, NULL,
3643 "Not authorized for access",
3648 log_error(err, b.log_file, svn_ra_svn_conn_remote_host(conn),
3649 b.user, NULL, pool);
3650 io_err = svn_ra_svn__write_cmd_failure(conn, pool, err);
3651 svn_error_clear(err);
3653 return svn_ra_svn__flush(conn, pool);
3657 if (ra_client_string == NULL || ra_client_string[0] == '\0')
3658 ra_client_string = "-";
3660 ra_client_string = svn_path_uri_encode(ra_client_string, pool);
3661 if (client_string == NULL || client_string[0] == '\0')
3662 client_string = "-";
3664 client_string = svn_path_uri_encode(client_string, pool);
3665 SVN_ERR(log_command(&b, conn, pool,
3666 "open %" APR_UINT64_T_FMT " cap=(%s) %s %s %s",
3668 svn_path_uri_encode(b.fs_path->data, pool),
3669 ra_client_string, client_string));
3671 warn_baton.server = &b;
3672 warn_baton.conn = conn;
3673 warn_baton.pool = svn_pool_create(pool);
3674 svn_fs_set_warning_func(b.fs, fs_warning_func, &warn_baton);
3676 SVN_ERR(svn_fs_get_uuid(b.fs, &uuid, pool));
3678 /* We can't claim mergeinfo capability until we know whether the
3679 repository supports mergeinfo (i.e., is not a 1.4 repository),
3680 but we don't get the repository url from the client until after
3681 we've already sent the initial list of server capabilities. So
3682 we list repository capabilities here, in our first response after
3683 the client has sent the url. */
3685 svn_boolean_t supports_mergeinfo;
3686 SVN_ERR(svn_repos_has_capability(b.repos, &supports_mergeinfo,
3687 SVN_REPOS_CAPABILITY_MERGEINFO, pool));
3689 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w(cc(!",
3690 "success", uuid, b.repos_url));
3691 if (supports_mergeinfo)
3692 SVN_ERR(svn_ra_svn__write_word(conn, pool, SVN_RA_SVN_CAP_MERGEINFO));
3693 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!))"));
3696 /* Set up editor shims. */
3698 svn_delta_shim_callbacks_t *callbacks =
3699 svn_delta_shim_callbacks_default(pool);
3701 callbacks->fetch_base_func = fetch_base_func;
3702 callbacks->fetch_props_func = fetch_props_func;
3703 callbacks->fetch_kind_func = fetch_kind_func;
3704 callbacks->fetch_baton = &b;
3706 SVN_ERR(svn_ra_svn__set_shim_callbacks(conn, callbacks));
3709 return svn_ra_svn__handle_commands2(conn, pool, main_commands, &b, FALSE);