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() */
66 typedef struct commit_callback_baton_t {
68 svn_revnum_t *new_rev;
71 const char **post_commit_err;
72 } commit_callback_baton_t;
74 typedef struct report_driver_baton_t {
76 const char *repos_url; /* Decoded repository URL. */
79 /* so update() can distinguish checkout from update in logging */
81 svn_boolean_t only_empty_entries;
82 /* for diff() logging */
83 svn_revnum_t *from_rev;
84 } report_driver_baton_t;
86 typedef struct log_baton_t {
88 svn_ra_svn_conn_t *conn;
91 /* Set to TRUE when at least one changed path has been sent. */
92 svn_boolean_t started;
95 typedef struct file_revs_baton_t {
96 svn_ra_svn_conn_t *conn;
97 apr_pool_t *pool; /* Pool provided in the handler call. */
100 typedef struct fs_warning_baton_t {
101 server_baton_t *server;
102 svn_ra_svn_conn_t *conn;
103 } fs_warning_baton_t;
105 typedef struct authz_baton_t {
106 server_baton_t *server;
107 svn_ra_svn_conn_t *conn;
110 /* svn_error_create() a new error, log_server_error() it, and
113 log_error(svn_error_t *err, server_baton_t *server)
115 logger__log_error(server->logger, err, server->repository,
116 server->client_info);
119 /* svn_error_create() a new error, log_server_error() it, and
122 error_create_and_log(apr_status_t apr_err, svn_error_t *child,
123 const char *message, server_baton_t *server)
125 svn_error_t *err = svn_error_create(apr_err, child, message);
126 log_error(err, server);
130 /* Log a failure ERR, transmit ERR back to the client (as part of a
131 "failure" notification), consume ERR, and flush the connection. */
133 log_fail_and_flush(svn_error_t *err, server_baton_t *server,
134 svn_ra_svn_conn_t *conn, apr_pool_t *pool)
138 log_error(err, server);
139 io_err = svn_ra_svn__write_cmd_failure(conn, pool, err);
140 svn_error_clear(err);
142 return svn_ra_svn__flush(conn, pool);
145 /* Log a client command. */
146 static svn_error_t *log_command(server_baton_t *b,
147 svn_ra_svn_conn_t *conn,
149 const char *fmt, ...)
151 const char *remote_host, *timestr, *log, *line;
155 if (b->logger == NULL)
158 remote_host = svn_ra_svn_conn_remote_host(conn);
159 timestr = svn_time_to_cstring(apr_time_now(), pool);
162 log = apr_pvsprintf(pool, fmt, ap);
165 line = apr_psprintf(pool, "%" APR_PID_T_FMT
166 " %s %s %s %s %s" APR_EOL_STR,
168 (remote_host ? remote_host : "-"),
169 (b->client_info->user ? b->client_info->user : "-"),
170 b->repository->repos_name, log);
171 nbytes = strlen(line);
173 return logger__write(b->logger, line, nbytes);
176 /* Log an authz failure */
178 log_authz_denied(const char *path,
179 svn_repos_authz_access_t required,
183 const char *timestr, *remote_host, *line;
188 if (!b->client_info || !b->client_info->user)
191 timestr = svn_time_to_cstring(apr_time_now(), pool);
192 remote_host = b->client_info->remote_host;
194 line = apr_psprintf(pool, "%" APR_PID_T_FMT
195 " %s %s %s %s Authorization Failed %s%s %s" APR_EOL_STR,
197 (remote_host ? remote_host : "-"),
198 b->client_info->user,
199 b->repository->repos_name,
200 (required & svn_authz_recursive ? "recursive " : ""),
201 (required & svn_authz_write ? "write" : "read"),
202 (path && path[0] ? path : "/"));
204 return logger__write(b->logger, line, strlen(line));
207 /* If CFG specifies a path to the password DB, read that DB through
208 * CONFIG_POOL and store it in REPOSITORY->PWDB.
211 load_pwdb_config(repository_t *repository,
213 svn_repos__config_pool_t *config_pool,
216 const char *pwdb_path;
219 svn_config_get(cfg, &pwdb_path,
220 SVN_CONFIG_SECTION_GENERAL,
221 SVN_CONFIG_OPTION_PASSWORD_DB, NULL);
223 repository->pwdb = NULL;
226 pwdb_path = svn_dirent_internal_style(pwdb_path, pool);
227 pwdb_path = svn_dirent_join(repository->base, pwdb_path, pool);
229 err = svn_repos__config_pool_get(&repository->pwdb, config_pool,
231 repository->repos, pool);
234 /* Because it may be possible to read the pwdb file with some
235 access methods and not others, ignore errors reading the pwdb
236 file and just don't present password authentication as an
237 option. Also, some authentications (e.g. --tunnel) can
238 proceed without it anyway.
240 ### Not entirely sure why SVN_ERR_BAD_FILENAME is checked
241 ### for here. That seems to have been introduced in r856914,
242 ### and only in r870942 was the APR_EACCES check introduced. */
243 if (err->apr_err != SVN_ERR_BAD_FILENAME
244 && ! APR_STATUS_IS_EACCES(err->apr_err))
246 return svn_error_create(SVN_ERR_AUTHN_FAILED, err, NULL);
249 /* Ignore SVN_ERR_BAD_FILENAME and APR_EACCES and proceed. */
250 svn_error_clear(err);
257 /* Canonicalize *ACCESS_FILE based on the type of argument. Results are
258 * placed in *ACCESS_FILE. REPOSITORY is used to convert relative paths to
259 * absolute paths rooted at the server root. REPOS_ROOT is used to calculate
260 * an absolute URL for repos-relative URLs. */
262 canonicalize_access_file(const char **access_file, repository_t *repository,
263 const char *repos_root, apr_pool_t *pool)
265 if (svn_path_is_url(*access_file))
267 *access_file = svn_uri_canonicalize(*access_file, pool);
269 else if (svn_path_is_repos_relative_url(*access_file))
271 const char *repos_root_url;
273 SVN_ERR(svn_uri_get_file_url_from_dirent(&repos_root_url, repos_root,
275 SVN_ERR(svn_path_resolve_repos_relative_url(access_file, *access_file,
276 repos_root_url, pool));
277 *access_file = svn_uri_canonicalize(*access_file, pool);
281 *access_file = svn_dirent_internal_style(*access_file, pool);
282 *access_file = svn_dirent_join(repository->base, *access_file, pool);
288 /* Load the authz database for the listening server based on the entries
289 in the SERVER struct.
291 SERVER and CONN must not be NULL. The real errors will be logged with
292 SERVER and CONN but return generic errors to the client. */
294 load_authz_config(repository_t *repository,
295 const char *repos_root,
299 const char *authzdb_path;
300 const char *groupsdb_path;
303 /* Read authz configuration. */
304 svn_config_get(cfg, &authzdb_path, SVN_CONFIG_SECTION_GENERAL,
305 SVN_CONFIG_OPTION_AUTHZ_DB, NULL);
307 svn_config_get(cfg, &groupsdb_path, SVN_CONFIG_SECTION_GENERAL,
308 SVN_CONFIG_OPTION_GROUPS_DB, NULL);
312 const char *case_force_val;
314 /* Canonicalize and add the base onto the authzdb_path (if needed). */
315 err = canonicalize_access_file(&authzdb_path, repository,
318 /* Same for the groupsdb_path if it is present. */
319 if (groupsdb_path && !err)
320 err = canonicalize_access_file(&groupsdb_path, repository,
324 err = svn_repos_authz_read3(&repository->authzdb, authzdb_path,
325 groupsdb_path, TRUE, repository->repos,
329 return svn_error_create(SVN_ERR_AUTHZ_INVALID_CONFIG, err, NULL);
331 /* Are we going to be case-normalizing usernames when we consult
332 * this authz file? */
333 svn_config_get(cfg, &case_force_val,
334 SVN_CONFIG_SECTION_GENERAL,
335 SVN_CONFIG_OPTION_FORCE_USERNAME_CASE, NULL);
338 if (strcmp(case_force_val, "upper") == 0)
339 repository->username_case = CASE_FORCE_UPPER;
340 else if (strcmp(case_force_val, "lower") == 0)
341 repository->username_case = CASE_FORCE_LOWER;
343 repository->username_case = CASE_ASIS;
348 repository->authzdb = NULL;
349 repository->username_case = CASE_ASIS;
355 /* If ERROR is a AUTH* error as returned by load_pwdb_config or
356 * load_authz_config, write it to SERVER's log file.
357 * Return a sanitized version of ERROR.
360 handle_config_error(svn_error_t *error,
361 server_baton_t *server)
364 && ( error->apr_err == SVN_ERR_AUTHZ_INVALID_CONFIG
365 || error->apr_err == SVN_ERR_AUTHN_FAILED))
367 apr_status_t apr_err = error->apr_err;
368 log_error(error, server);
370 /* Now that we've logged the error, clear it and return a
371 * nice, generic error to the user:
372 * http://subversion.tigris.org/issues/show_bug.cgi?id=2271 */
373 svn_error_clear(error);
374 return svn_error_create(apr_err, NULL, NULL);
380 /* Set *FS_PATH to the portion of URL that is the path within the
381 repository, if URL is inside REPOS_URL (if URL is not inside
382 REPOS_URL, then error, with the effect on *FS_PATH undefined).
384 If the resultant fs path would be the empty string (i.e., URL and
385 REPOS_URL are the same), then set *FS_PATH to "/".
387 Assume that REPOS_URL and URL are already URI-decoded. */
388 static svn_error_t *get_fs_path(const char *repos_url, const char *url,
389 const char **fs_path)
393 len = strlen(repos_url);
394 if (strncmp(url, repos_url, len) != 0)
395 return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL,
396 "'%s' is not the same repository as '%s'",
398 *fs_path = url + len;
405 /* --- AUTHENTICATION AND AUTHORIZATION FUNCTIONS --- */
407 /* Convert TEXT to upper case if TO_UPPERCASE is TRUE, else
408 converts it to lower case. */
409 static void convert_case(char *text, svn_boolean_t to_uppercase)
414 *c = (char)(to_uppercase ? apr_toupper(*c) : apr_tolower(*c));
419 /* Set *ALLOWED to TRUE if PATH is accessible in the REQUIRED mode to
420 the user described in BATON according to the authz rules in BATON.
421 Use POOL for temporary allocations only. If no authz rules are
422 present in BATON, grant access by default. */
423 static svn_error_t *authz_check_access(svn_boolean_t *allowed,
425 svn_repos_authz_access_t required,
429 repository_t *repository = b->repository;
430 client_info_t *client_info = b->client_info;
432 /* If authz cannot be performed, grant access. This is NOT the same
433 as the default policy when authz is performed on a path with no
434 rules. In the latter case, the default is to deny access, and is
435 set by svn_repos_authz_check_access. */
436 if (!repository->authzdb)
442 /* If the authz request is for the empty path (ie. ""), replace it
443 with the root path. This happens because of stripping done at
444 various levels in svnserve that remove the leading / on an
445 absolute path. Passing such a malformed path to the authz
446 routines throws them into an infinite loop and makes them miss
448 if (path && *path != '/')
449 path = svn_fspath__canonicalize(path, pool);
451 /* If we have a username, and we've not yet used it + any username
452 case normalization that might be requested to determine "the
453 username we used for authz purposes", do so now. */
454 if (client_info->user && (! client_info->authz_user))
456 char *authz_user = apr_pstrdup(b->pool, client_info->user);
457 if (repository->username_case == CASE_FORCE_UPPER)
458 convert_case(authz_user, TRUE);
459 else if (repository->username_case == CASE_FORCE_LOWER)
460 convert_case(authz_user, FALSE);
462 client_info->authz_user = authz_user;
465 SVN_ERR(svn_repos_authz_check_access(repository->authzdb,
466 repository->authz_repos_name,
467 path, client_info->authz_user,
468 required, allowed, pool));
470 SVN_ERR(log_authz_denied(path, required, b, pool));
475 /* Set *ALLOWED to TRUE if PATH is readable by the user described in
476 * BATON. Use POOL for temporary allocations only. ROOT is not used.
477 * Implements the svn_repos_authz_func_t interface.
479 static svn_error_t *authz_check_access_cb(svn_boolean_t *allowed,
485 authz_baton_t *sb = baton;
487 return authz_check_access(allowed, path, svn_authz_read,
491 /* If authz is enabled in the specified BATON, return a read authorization
492 function. Otherwise, return NULL. */
493 static svn_repos_authz_func_t authz_check_access_cb_func(server_baton_t *baton)
495 if (baton->repository->authzdb)
496 return authz_check_access_cb;
500 /* Set *ALLOWED to TRUE if the REQUIRED access to PATH is granted,
501 * according to the state in BATON. Use POOL for temporary
502 * allocations only. ROOT is not used. Implements the
503 * svn_repos_authz_callback_t interface.
505 static svn_error_t *authz_commit_cb(svn_repos_authz_access_t required,
506 svn_boolean_t *allowed,
512 authz_baton_t *sb = baton;
514 return authz_check_access(allowed, path, required, sb->server, pool);
517 /* Return the access level specified for OPTION in CFG. If no such
518 * setting exists, use DEF. If READ_ONLY is set, unconditionally disable
521 static enum access_type
522 get_access(svn_config_t *cfg,
525 svn_boolean_t read_only)
527 enum access_type result;
530 svn_config_get(cfg, &val, SVN_CONFIG_SECTION_GENERAL, option, def);
531 result = (strcmp(val, "write") == 0 ? WRITE_ACCESS :
532 strcmp(val, "read") == 0 ? READ_ACCESS : NO_ACCESS);
534 return result == WRITE_ACCESS && read_only ? READ_ACCESS : result;
537 /* Set the *_ACCESS members in REPOSITORY according to the settings in
538 * CFG. If READ_ONLY is set, unconditionally disable write access.
541 set_access(repository_t *repository,
543 svn_boolean_t read_only)
545 repository->auth_access = get_access(cfg, SVN_CONFIG_OPTION_AUTH_ACCESS,
547 repository->anon_access = get_access(cfg, SVN_CONFIG_OPTION_ANON_ACCESS,
551 /* Return the access level for the user in B.
553 static enum access_type
554 current_access(server_baton_t *b)
556 return b->client_info->user ? b->repository->auth_access
557 : b->repository->anon_access;
560 /* Send authentication mechs for ACCESS_TYPE to the client. If NEEDS_USERNAME
561 is true, don't send anonymous mech even if that would give the desired
563 static svn_error_t *send_mechs(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
564 server_baton_t *b, enum access_type required,
565 svn_boolean_t needs_username)
567 if (!needs_username && b->repository->anon_access >= required)
568 SVN_ERR(svn_ra_svn__write_word(conn, pool, "ANONYMOUS"));
569 if (b->client_info->tunnel_user && b->repository->auth_access >= required)
570 SVN_ERR(svn_ra_svn__write_word(conn, pool, "EXTERNAL"));
571 if (b->repository->pwdb && b->repository->auth_access >= required)
572 SVN_ERR(svn_ra_svn__write_word(conn, pool, "CRAM-MD5"));
576 /* Context for cleanup handler. */
577 struct cleanup_fs_access_baton
583 /* Pool cleanup handler. Make sure fs's access_t points to NULL when
584 the command pool is destroyed. */
585 static apr_status_t cleanup_fs_access(void *data)
588 struct cleanup_fs_access_baton *baton = data;
590 serr = svn_fs_set_access(baton->fs, NULL);
593 apr_status_t apr_err = serr->apr_err;
594 svn_error_clear(serr);
602 /* Create an svn_fs_access_t in POOL for USER and associate it with
603 B's filesystem. Also, register a cleanup handler with POOL which
604 de-associates the svn_fs_access_t from B's filesystem. */
606 create_fs_access(server_baton_t *b, apr_pool_t *pool)
608 svn_fs_access_t *fs_access;
609 struct cleanup_fs_access_baton *cleanup_baton;
611 if (!b->client_info->user)
614 SVN_ERR(svn_fs_create_access(&fs_access, b->client_info->user, pool));
615 SVN_ERR(svn_fs_set_access(b->repository->fs, fs_access));
617 cleanup_baton = apr_pcalloc(pool, sizeof(*cleanup_baton));
618 cleanup_baton->pool = pool;
619 cleanup_baton->fs = b->repository->fs;
620 apr_pool_cleanup_register(pool, cleanup_baton, cleanup_fs_access,
621 apr_pool_cleanup_null);
626 /* Authenticate, once the client has chosen a mechanism and possibly
627 * sent an initial mechanism token. On success, set *success to true
628 * and b->user to the authenticated username (or NULL for anonymous).
629 * On authentication failure, report failure to the client and set
630 * *success to FALSE. On communications failure, return an error.
631 * If NEEDS_USERNAME is TRUE, don't allow anonymous authentication. */
632 static svn_error_t *auth(svn_boolean_t *success,
633 svn_ra_svn_conn_t *conn,
634 const char *mech, const char *mecharg,
635 server_baton_t *b, enum access_type required,
636 svn_boolean_t needs_username,
637 apr_pool_t *scratch_pool)
642 if (b->repository->auth_access >= required
643 && b->client_info->tunnel_user && strcmp(mech, "EXTERNAL") == 0)
645 if (*mecharg && strcmp(mecharg, b->client_info->tunnel_user) != 0)
646 return svn_ra_svn__write_tuple(conn, scratch_pool, "w(c)", "failure",
647 "Requested username does not match");
648 b->client_info->user = b->client_info->tunnel_user;
649 SVN_ERR(svn_ra_svn__write_tuple(conn, scratch_pool, "w()", "success"));
654 if (b->repository->anon_access >= required
655 && strcmp(mech, "ANONYMOUS") == 0 && ! needs_username)
657 SVN_ERR(svn_ra_svn__write_tuple(conn, scratch_pool, "w()", "success"));
662 if (b->repository->auth_access >= required
663 && b->repository->pwdb && strcmp(mech, "CRAM-MD5") == 0)
665 SVN_ERR(svn_ra_svn_cram_server(conn, scratch_pool, b->repository->pwdb,
667 b->client_info->user = apr_pstrdup(b->pool, user);
671 return svn_ra_svn__write_tuple(conn, scratch_pool, "w(c)", "failure",
672 "Must authenticate with listed mechanism");
675 /* Perform an authentication request using the built-in SASL implementation. */
677 internal_auth_request(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
678 server_baton_t *b, enum access_type required,
679 svn_boolean_t needs_username)
681 svn_boolean_t success;
682 const char *mech, *mecharg;
683 apr_pool_t *iterpool;
685 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w((!", "success"));
686 SVN_ERR(send_mechs(conn, pool, b, required, needs_username));
687 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!)c)", b->repository->realm));
689 iterpool = svn_pool_create(pool);
692 svn_pool_clear(iterpool);
694 SVN_ERR(svn_ra_svn__read_tuple(conn, pool, "w(?c)", &mech, &mecharg));
697 SVN_ERR(auth(&success, conn, mech, mecharg, b, required,
698 needs_username, iterpool));
701 svn_pool_destroy(iterpool);
706 /* Perform an authentication request in order to get an access level of
707 * REQUIRED or higher. Since the client may escape the authentication
708 * exchange, the caller should check current_access(b) to see if
709 * authentication succeeded. */
710 static svn_error_t *auth_request(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
711 server_baton_t *b, enum access_type required,
712 svn_boolean_t needs_username)
715 if (b->repository->use_sasl)
716 return cyrus_auth_request(conn, pool, b, required, needs_username);
719 return internal_auth_request(conn, pool, b, required, needs_username);
722 /* Send a trivial auth notification on CONN which lists no mechanisms,
723 * indicating that authentication is unnecessary. Usually called in
724 * response to invocation of a svnserve command.
726 static svn_error_t *trivial_auth_request(svn_ra_svn_conn_t *conn,
727 apr_pool_t *pool, server_baton_t *b)
729 return svn_ra_svn__write_cmd_response(conn, pool, "()c", "");
732 /* Ensure that the client has the REQUIRED access by checking the
733 * access directives (both blanket and per-directory) in BATON. If
734 * PATH is NULL, then only the blanket access configuration will
737 * If NEEDS_USERNAME is TRUE, then a lookup is only successful if the
738 * user described in BATON is authenticated and, well, has a username
741 * Use POOL for temporary allocations only.
743 static svn_boolean_t lookup_access(apr_pool_t *pool,
744 server_baton_t *baton,
745 svn_repos_authz_access_t required,
747 svn_boolean_t needs_username)
749 enum access_type req = (required & svn_authz_write) ?
750 WRITE_ACCESS : READ_ACCESS;
751 svn_boolean_t authorized;
754 /* Get authz's opinion on the access. */
755 err = authz_check_access(&authorized, path, required, baton, pool);
757 /* If an error made lookup fail, deny access. */
760 log_error(err, baton);
761 svn_error_clear(err);
765 /* If the required access is blanket-granted AND granted by authz
766 AND we already have a username if one is required, then the
767 lookup has succeeded. */
768 if (current_access(baton) >= req
770 && (! needs_username || baton->client_info->user))
776 /* Check that the client has the REQUIRED access by consulting the
777 * authentication and authorization states stored in BATON. If the
778 * client does not have the required access credentials, attempt to
779 * authenticate the client to get that access, using CONN for
782 * This function is supposed to be called to handle the authentication
783 * half of a standard svn protocol reply. If an error is returned, it
784 * probably means that the server can terminate the client connection
785 * with an apologetic error, as it implies an authentication failure.
787 * PATH and NEEDS_USERNAME are passed along to lookup_access, their
788 * behaviour is documented there.
790 static svn_error_t *must_have_access(svn_ra_svn_conn_t *conn,
793 svn_repos_authz_access_t required,
795 svn_boolean_t needs_username)
797 enum access_type req = (required & svn_authz_write) ?
798 WRITE_ACCESS : READ_ACCESS;
800 /* See whether the user already has the required access. If so,
801 nothing needs to be done. Create the FS access and send a
802 trivial auth request. */
803 if (lookup_access(pool, b, required, path, needs_username))
805 SVN_ERR(create_fs_access(b, pool));
806 return trivial_auth_request(conn, pool, b);
809 /* If the required blanket access can be obtained by authenticating,
810 try that. Unfortunately, we can't tell until after
811 authentication whether authz will work or not. We force
812 requiring a username because we need one to be able to check
813 authz configuration again with a different user credentials than
814 the first time round. */
815 if (b->client_info->user == NULL
816 && b->repository->auth_access >= req
817 && (b->client_info->tunnel_user || b->repository->pwdb
818 || b->repository->use_sasl))
819 SVN_ERR(auth_request(conn, pool, b, req, TRUE));
821 /* Now that an authentication has been done get the new take of
822 authz on the request. */
823 if (! lookup_access(pool, b, required, path, needs_username))
824 return svn_error_create(SVN_ERR_RA_SVN_CMD_ERR,
825 error_create_and_log(SVN_ERR_RA_NOT_AUTHORIZED,
829 /* Else, access is granted, and there is much rejoicing. */
830 SVN_ERR(create_fs_access(b, pool));
835 /* --- REPORTER COMMAND SET --- */
837 /* To allow for pipelining, reporter commands have no reponses. If we
838 * get an error, we ignore all subsequent reporter commands and return
839 * the error finish_report, to be handled by the calling command.
842 static svn_error_t *set_path(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
843 svn_ra_svn__list_t *params, void *baton)
845 report_driver_baton_t *b = baton;
846 const char *path, *lock_token, *depth_word;
848 /* Default to infinity, for old clients that don't send depth. */
849 svn_depth_t depth = svn_depth_infinity;
850 svn_boolean_t start_empty;
852 SVN_ERR(svn_ra_svn__parse_tuple(params, "crb?(?c)?w",
853 &path, &rev, &start_empty, &lock_token,
856 depth = svn_depth_from_word(depth_word);
857 path = svn_relpath_canonicalize(path, pool);
858 if (b->from_rev && strcmp(path, "") == 0)
861 b->err = svn_repos_set_path3(b->report_baton, path, rev, depth,
862 start_empty, lock_token, pool);
865 b->only_empty_entries = FALSE;
869 static svn_error_t *delete_path(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
870 svn_ra_svn__list_t *params, void *baton)
872 report_driver_baton_t *b = baton;
875 SVN_ERR(svn_ra_svn__parse_tuple(params, "c", &path));
876 path = svn_relpath_canonicalize(path, pool);
878 b->err = svn_repos_delete_path(b->report_baton, path, pool);
882 static svn_error_t *link_path(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
883 svn_ra_svn__list_t *params, void *baton)
885 report_driver_baton_t *b = baton;
886 const char *path, *url, *lock_token, *fs_path, *depth_word;
888 svn_boolean_t start_empty;
889 /* Default to infinity, for old clients that don't send depth. */
890 svn_depth_t depth = svn_depth_infinity;
892 SVN_ERR(svn_ra_svn__parse_tuple(params, "ccrb?(?c)?w",
893 &path, &url, &rev, &start_empty,
894 &lock_token, &depth_word));
896 /* ### WHAT?! The link path is an absolute URL?! Didn't see that
897 coming... -- cmpilato */
898 path = svn_relpath_canonicalize(path, pool);
899 url = svn_uri_canonicalize(url, pool);
901 depth = svn_depth_from_word(depth_word);
903 b->err = get_fs_path(svn_path_uri_decode(b->repos_url, pool),
904 svn_path_uri_decode(url, pool),
907 b->err = svn_repos_link_path3(b->report_baton, path, fs_path, rev,
908 depth, start_empty, lock_token, pool);
913 static svn_error_t *finish_report(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
914 svn_ra_svn__list_t *params, void *baton)
916 report_driver_baton_t *b = baton;
918 /* No arguments to parse. */
919 SVN_ERR(trivial_auth_request(conn, pool, b->sb));
921 b->err = svn_repos_finish_report(b->report_baton, pool);
926 abort_report(svn_ra_svn_conn_t *conn,
928 svn_ra_svn__list_t *params,
931 report_driver_baton_t *b = baton;
933 /* No arguments to parse. */
934 svn_error_clear(svn_repos_abort_report(b->report_baton, pool));
938 static const svn_ra_svn__cmd_entry_t report_commands[] = {
939 { "set-path", set_path },
940 { "delete-path", delete_path },
941 { "link-path", link_path },
942 { "finish-report", finish_report, NULL, TRUE },
943 { "abort-report", abort_report, NULL, TRUE },
947 /* Accept a report from the client, drive the network editor with the
948 * result, and then write an empty command response. If there is a
949 * non-protocol failure, accept_report will abort the edit and return
950 * a command error to be reported by handle_commands().
952 * If only_empty_entry is not NULL and the report contains only one
953 * item, and that item is empty, set *only_empty_entry to TRUE, else
956 * If from_rev is not NULL, set *from_rev to the revision number from
957 * the set-path on ""; if somehow set-path "" never happens, set
958 * *from_rev to SVN_INVALID_REVNUM.
960 static svn_error_t *accept_report(svn_boolean_t *only_empty_entry,
961 svn_revnum_t *from_rev,
962 svn_ra_svn_conn_t *conn, apr_pool_t *pool,
963 server_baton_t *b, svn_revnum_t rev,
964 const char *target, const char *tgt_path,
965 svn_boolean_t text_deltas,
967 svn_boolean_t send_copyfrom_args,
968 svn_boolean_t ignore_ancestry)
970 const svn_delta_editor_t *editor;
971 void *edit_baton, *report_baton;
972 report_driver_baton_t rb;
979 /* Make an svn_repos report baton. Tell it to drive the network editor
980 * when the report is complete. */
981 svn_ra_svn_get_editor(&editor, &edit_baton, conn, pool, NULL, NULL);
982 SVN_CMD_ERR(svn_repos_begin_report3(&report_baton, rev,
983 b->repository->repos,
984 b->repository->fs_path->data, target,
985 tgt_path, text_deltas, depth,
986 ignore_ancestry, send_copyfrom_args,
988 authz_check_access_cb_func(b),
989 &ab, svn_ra_svn_zero_copy_limit(conn),
993 rb.repos_url = svn_path_uri_decode(b->repository->repos_url, pool);
994 rb.report_baton = report_baton;
996 rb.entry_counter = 0;
997 rb.only_empty_entries = TRUE;
998 rb.from_rev = from_rev;
1000 *from_rev = SVN_INVALID_REVNUM;
1001 err = svn_ra_svn__handle_commands2(conn, pool, report_commands, &rb, TRUE);
1004 /* Network or protocol error while handling commands. */
1005 svn_error_clear(rb.err);
1010 /* Some failure during the reporting or editing operations. */
1011 SVN_CMD_ERR(rb.err);
1013 SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, ""));
1015 if (only_empty_entry)
1016 *only_empty_entry = rb.entry_counter == 1 && rb.only_empty_entries;
1018 return SVN_NO_ERROR;
1021 /* --- MAIN COMMAND SET --- */
1023 /* Write out a list of property diffs. PROPDIFFS is an array of svn_prop_t
1025 static svn_error_t *write_prop_diffs(svn_ra_svn_conn_t *conn,
1027 const apr_array_header_t *propdiffs)
1031 for (i = 0; i < propdiffs->nelts; ++i)
1033 const svn_prop_t *prop = &APR_ARRAY_IDX(propdiffs, i, svn_prop_t);
1035 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "c(?s)",
1036 prop->name, prop->value));
1039 return SVN_NO_ERROR;
1042 /* Write out a lock to the client. */
1043 static svn_error_t *write_lock(svn_ra_svn_conn_t *conn,
1045 const svn_lock_t *lock)
1047 const char *cdate, *edate;
1049 cdate = svn_time_to_cstring(lock->creation_date, pool);
1050 edate = lock->expiration_date
1051 ? svn_time_to_cstring(lock->expiration_date, pool) : NULL;
1052 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "ccc(?c)c(?c)", lock->path,
1053 lock->token, lock->owner, lock->comment,
1056 return SVN_NO_ERROR;
1059 /* ### This really belongs in libsvn_repos. */
1060 /* Get the explicit properties and/or inherited properties for a PATH in
1061 ROOT, with hardcoded committed-info values. */
1062 static svn_error_t *
1063 get_props(apr_hash_t **props,
1064 apr_array_header_t **iprops,
1066 svn_fs_root_t *root,
1070 /* Get the explicit properties. */
1075 const char *cdate, *cauthor, *uuid;
1077 SVN_ERR(svn_fs_node_proplist(props, root, path, pool));
1079 /* Hardcode the values for the committed revision, date, and author. */
1080 SVN_ERR(svn_repos_get_committed_info(&crev, &cdate, &cauthor, root,
1082 str = svn_string_createf(pool, "%ld", crev);
1083 svn_hash_sets(*props, SVN_PROP_ENTRY_COMMITTED_REV, str);
1084 str = (cdate) ? svn_string_create(cdate, pool) : NULL;
1085 svn_hash_sets(*props, SVN_PROP_ENTRY_COMMITTED_DATE, str);
1086 str = (cauthor) ? svn_string_create(cauthor, pool) : NULL;
1087 svn_hash_sets(*props, SVN_PROP_ENTRY_LAST_AUTHOR, str);
1089 /* Hardcode the values for the UUID. */
1090 SVN_ERR(svn_fs_get_uuid(svn_fs_root_fs(root), &uuid, pool));
1091 str = (uuid) ? svn_string_create(uuid, pool) : NULL;
1092 svn_hash_sets(*props, SVN_PROP_ENTRY_UUID, str);
1095 /* Get any inherited properties the user is authorized to. */
1098 SVN_ERR(svn_repos_fs_get_inherited_props(
1099 iprops, root, path, NULL,
1100 authz_check_access_cb_func(b->server),
1104 return SVN_NO_ERROR;
1107 /* Set BATON->FS_PATH for the repository URL found in PARAMS. */
1108 static svn_error_t *
1109 reparent(svn_ra_svn_conn_t *conn,
1111 svn_ra_svn__list_t *params,
1114 server_baton_t *b = baton;
1116 const char *fs_path;
1118 SVN_ERR(svn_ra_svn__parse_tuple(params, "c", &url));
1119 url = svn_uri_canonicalize(url, pool);
1120 SVN_ERR(trivial_auth_request(conn, pool, b));
1121 SVN_CMD_ERR(get_fs_path(svn_path_uri_decode(b->repository->repos_url, pool),
1122 svn_path_uri_decode(url, pool),
1124 SVN_ERR(log_command(b, conn, pool, "%s", svn_log__reparent(fs_path, pool)));
1125 svn_stringbuf_set(b->repository->fs_path, fs_path);
1126 SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, ""));
1127 return SVN_NO_ERROR;
1130 static svn_error_t *
1131 get_latest_rev(svn_ra_svn_conn_t *conn,
1133 svn_ra_svn__list_t *params,
1136 server_baton_t *b = baton;
1139 SVN_ERR(log_command(b, conn, pool, "get-latest-rev"));
1141 SVN_ERR(trivial_auth_request(conn, pool, b));
1142 SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->repository->fs, pool));
1143 SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, "r", rev));
1144 return SVN_NO_ERROR;
1147 static svn_error_t *
1148 get_dated_rev(svn_ra_svn_conn_t *conn,
1150 svn_ra_svn__list_t *params,
1153 server_baton_t *b = baton;
1156 const char *timestr;
1158 SVN_ERR(svn_ra_svn__parse_tuple(params, "c", ×tr));
1159 SVN_ERR(log_command(b, conn, pool, "get-dated-rev %s", timestr));
1161 SVN_ERR(trivial_auth_request(conn, pool, b));
1162 SVN_CMD_ERR(svn_time_from_cstring(&tm, timestr, pool));
1163 SVN_CMD_ERR(svn_repos_dated_revision(&rev, b->repository->repos, tm, pool));
1164 SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, "r", rev));
1165 return SVN_NO_ERROR;
1168 /* Common logic for change_rev_prop() and change_rev_prop2(). */
1169 static svn_error_t *do_change_rev_prop(svn_ra_svn_conn_t *conn,
1173 const svn_string_t *const *old_value_p,
1174 const svn_string_t *value,
1182 SVN_ERR(must_have_access(conn, pool, b, svn_authz_write, NULL, FALSE));
1183 SVN_ERR(log_command(b, conn, pool, "%s",
1184 svn_log__change_rev_prop(rev, name, pool)));
1185 SVN_CMD_ERR(svn_repos_fs_change_rev_prop4(b->repository->repos, rev,
1186 b->client_info->user,
1187 name, old_value_p, value,
1189 authz_check_access_cb_func(b), &ab,
1191 SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, ""));
1193 return SVN_NO_ERROR;
1196 static svn_error_t *
1197 change_rev_prop2(svn_ra_svn_conn_t *conn,
1199 svn_ra_svn__list_t *params,
1202 server_baton_t *b = baton;
1205 svn_string_t *value;
1206 const svn_string_t *const *old_value_p;
1207 svn_string_t *old_value;
1208 svn_boolean_t dont_care;
1210 SVN_ERR(svn_ra_svn__parse_tuple(params, "rc(?s)(b?s)",
1211 &rev, &name, &value,
1212 &dont_care, &old_value));
1214 /* Argument parsing. */
1218 old_value_p = (const svn_string_t *const *)&old_value;
1220 /* Input validation. */
1221 if (dont_care && old_value)
1224 err = svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL,
1225 "'previous-value' and 'dont-care' cannot both be "
1226 "set in 'change-rev-prop2' request");
1227 return log_fail_and_flush(err, b, conn, pool);
1231 SVN_ERR(do_change_rev_prop(conn, b, rev, name, old_value_p, value, pool));
1233 return SVN_NO_ERROR;
1236 static svn_error_t *
1237 change_rev_prop(svn_ra_svn_conn_t *conn,
1239 svn_ra_svn__list_t *params,
1242 server_baton_t *b = baton;
1245 svn_string_t *value;
1247 /* Because the revprop value was at one time mandatory, the usual
1248 optional element pattern "(?s)" isn't used. */
1249 SVN_ERR(svn_ra_svn__parse_tuple(params, "rc?s", &rev, &name, &value));
1251 SVN_ERR(do_change_rev_prop(conn, b, rev, name, NULL, value, pool));
1253 return SVN_NO_ERROR;
1256 static svn_error_t *
1257 rev_proplist(svn_ra_svn_conn_t *conn,
1259 svn_ra_svn__list_t *params,
1262 server_baton_t *b = baton;
1270 SVN_ERR(svn_ra_svn__parse_tuple(params, "r", &rev));
1271 SVN_ERR(log_command(b, conn, pool, "%s", svn_log__rev_proplist(rev, pool)));
1273 SVN_ERR(trivial_auth_request(conn, pool, b));
1274 SVN_CMD_ERR(svn_repos_fs_revision_proplist(&props, b->repository->repos,
1276 authz_check_access_cb_func(b),
1278 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w((!", "success"));
1279 SVN_ERR(svn_ra_svn__write_proplist(conn, pool, props));
1280 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!))"));
1281 return SVN_NO_ERROR;
1284 static svn_error_t *
1285 rev_prop(svn_ra_svn_conn_t *conn,
1287 svn_ra_svn__list_t *params,
1290 server_baton_t *b = baton;
1293 svn_string_t *value;
1299 SVN_ERR(svn_ra_svn__parse_tuple(params, "rc", &rev, &name));
1300 SVN_ERR(log_command(b, conn, pool, "%s",
1301 svn_log__rev_prop(rev, name, pool)));
1303 SVN_ERR(trivial_auth_request(conn, pool, b));
1304 SVN_CMD_ERR(svn_repos_fs_revision_prop(&value, b->repository->repos, rev,
1305 name, authz_check_access_cb_func(b),
1307 SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, "(?s)", value));
1308 return SVN_NO_ERROR;
1311 static svn_error_t *commit_done(const svn_commit_info_t *commit_info,
1312 void *baton, apr_pool_t *pool)
1314 commit_callback_baton_t *ccb = baton;
1316 *ccb->new_rev = commit_info->revision;
1317 *ccb->date = commit_info->date
1318 ? apr_pstrdup(ccb->pool, commit_info->date): NULL;
1319 *ccb->author = commit_info->author
1320 ? apr_pstrdup(ccb->pool, commit_info->author) : NULL;
1321 *ccb->post_commit_err = commit_info->post_commit_err
1322 ? apr_pstrdup(ccb->pool, commit_info->post_commit_err) : NULL;
1323 return SVN_NO_ERROR;
1326 /* Add the LOCK_TOKENS (if any) to the filesystem access context,
1327 * checking path authorizations using the state in SB as we go.
1328 * LOCK_TOKENS is an array of svn_ra_svn__item_t structs. Return a
1329 * client error if LOCK_TOKENS is not a list of lists. If a lock
1330 * violates the authz configuration, return SVN_ERR_RA_NOT_AUTHORIZED
1331 * to the client. Use POOL for temporary allocations only.
1333 static svn_error_t *
1334 add_lock_tokens(const svn_ra_svn__list_t *lock_tokens,
1339 svn_fs_access_t *fs_access;
1341 SVN_ERR(svn_fs_get_access(&fs_access, sb->repository->fs));
1343 /* If there is no access context, nowhere to add the tokens. */
1345 return SVN_NO_ERROR;
1347 for (i = 0; i < lock_tokens->nelts; ++i)
1349 const char *path, *token, *full_path;
1350 svn_ra_svn__item_t *path_item, *token_item;
1351 svn_ra_svn__item_t *item = &SVN_RA_SVN__LIST_ITEM(lock_tokens, i);
1352 if (item->kind != SVN_RA_SVN_LIST)
1353 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
1354 "Lock tokens aren't a list of lists");
1356 path_item = &SVN_RA_SVN__LIST_ITEM(&item->u.list, 0);
1357 if (path_item->kind != SVN_RA_SVN_STRING)
1358 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
1359 "Lock path isn't a string");
1361 token_item = &SVN_RA_SVN__LIST_ITEM(&item->u.list, 1);
1362 if (token_item->kind != SVN_RA_SVN_STRING)
1363 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
1364 "Lock token isn't a string");
1366 path = path_item->u.string.data;
1367 full_path = svn_fspath__join(sb->repository->fs_path->data,
1368 svn_relpath_canonicalize(path, pool),
1371 if (! lookup_access(pool, sb, svn_authz_write, full_path, TRUE))
1372 return error_create_and_log(SVN_ERR_RA_NOT_AUTHORIZED, NULL, NULL,
1375 token = token_item->u.string.data;
1376 SVN_ERR(svn_fs_access_add_lock_token2(fs_access, path, token));
1379 return SVN_NO_ERROR;
1382 /* Implements svn_fs_lock_callback_t. */
1383 static svn_error_t *
1384 lock_cb(void *baton,
1386 const svn_lock_t *lock,
1387 svn_error_t *fs_err,
1390 server_baton_t *sb = baton;
1392 log_error(fs_err, sb);
1394 return SVN_NO_ERROR;
1397 /* Unlock the paths with lock tokens in LOCK_TOKENS, ignoring any errors.
1398 LOCK_TOKENS contains svn_ra_svn__item_t elements, assumed to be lists. */
1399 static svn_error_t *
1400 unlock_paths(const svn_ra_svn__list_t *lock_tokens,
1405 apr_pool_t *subpool = svn_pool_create(pool);
1406 apr_hash_t *targets = apr_hash_make(subpool);
1409 for (i = 0; i < lock_tokens->nelts; ++i)
1411 svn_ra_svn__item_t *item, *path_item, *token_item;
1412 const char *path, *token, *full_path;
1414 item = &SVN_RA_SVN__LIST_ITEM(lock_tokens, i);
1415 path_item = &SVN_RA_SVN__LIST_ITEM(&item->u.list, 0);
1416 token_item = &SVN_RA_SVN__LIST_ITEM(&item->u.list, 1);
1418 path = path_item->u.string.data;
1419 full_path = svn_fspath__join(sb->repository->fs_path->data,
1420 svn_relpath_canonicalize(path, subpool),
1422 token = token_item->u.string.data;
1423 svn_hash_sets(targets, full_path, token);
1427 /* The lock may have become defunct after the commit, so ignore such
1429 err = svn_repos_fs_unlock_many(sb->repository->repos, targets, FALSE,
1430 lock_cb, sb, subpool, subpool);
1432 svn_error_clear(err);
1434 svn_pool_destroy(subpool);
1436 return SVN_NO_ERROR;
1439 static svn_error_t *
1440 commit(svn_ra_svn_conn_t *conn,
1442 svn_ra_svn__list_t *params,
1445 server_baton_t *b = baton;
1446 const char *log_msg,
1449 *post_commit_err = NULL;
1450 svn_ra_svn__list_t *lock_tokens;
1451 svn_boolean_t keep_locks;
1452 svn_ra_svn__list_t *revprop_list;
1453 apr_hash_t *revprop_table;
1454 const svn_delta_editor_t *editor;
1456 svn_boolean_t aborted;
1457 commit_callback_baton_t ccb;
1458 svn_revnum_t new_rev;
1464 if (params->nelts == 1)
1466 /* Clients before 1.2 don't send lock-tokens, keep-locks,
1467 and rev-props fields. */
1468 SVN_ERR(svn_ra_svn__parse_tuple(params, "c", &log_msg));
1471 revprop_list = NULL;
1475 /* Clients before 1.5 don't send the rev-props field. */
1476 SVN_ERR(svn_ra_svn__parse_tuple(params, "clb?l", &log_msg,
1477 &lock_tokens, &keep_locks,
1481 /* The handling for locks is a little problematic, because the
1482 protocol won't let us send several auth requests once one has
1483 succeeded. So we request write access and a username before
1484 adding tokens (if we have any), and subsequently fail if a lock
1486 SVN_ERR(must_have_access(conn, pool, b, svn_authz_write,
1488 (lock_tokens && lock_tokens->nelts)));
1490 /* Authorize the lock tokens and give them to the FS if we got
1492 if (lock_tokens && lock_tokens->nelts)
1493 SVN_CMD_ERR(add_lock_tokens(lock_tokens, b, pool));
1495 /* Ignore LOG_MSG, per the protocol. See ra_svn_commit(). */
1497 SVN_ERR(svn_ra_svn__parse_proplist(revprop_list, pool, &revprop_table));
1500 revprop_table = apr_hash_make(pool);
1501 svn_hash_sets(revprop_table, SVN_PROP_REVISION_LOG,
1502 svn_string_create(log_msg, pool));
1505 /* Get author from the baton, making sure clients can't circumvent
1506 the authentication via the revision props. */
1507 svn_hash_sets(revprop_table, SVN_PROP_REVISION_AUTHOR,
1508 b->client_info->user
1509 ? svn_string_create(b->client_info->user, pool)
1513 ccb.new_rev = &new_rev;
1515 ccb.author = &author;
1516 ccb.post_commit_err = &post_commit_err;
1517 /* ### Note that svn_repos_get_commit_editor5 actually wants a decoded URL. */
1518 SVN_CMD_ERR(svn_repos_get_commit_editor5
1519 (&editor, &edit_baton, b->repository->repos, NULL,
1520 svn_path_uri_decode(b->repository->repos_url, pool),
1521 b->repository->fs_path->data, revprop_table,
1523 authz_commit_cb, &ab, pool));
1524 SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, ""));
1525 SVN_ERR(svn_ra_svn_drive_editor2(conn, pool, editor, edit_baton,
1529 SVN_ERR(log_command(b, conn, pool, "%s",
1530 svn_log__commit(new_rev, pool)));
1531 SVN_ERR(trivial_auth_request(conn, pool, b));
1533 /* In tunnel mode, deltify before answering the client, because
1534 answering may cause the client to terminate the connection
1535 and thus kill the server. But otherwise, deltify after
1536 answering the client, to avoid user-visible delay. */
1538 if (b->client_info->tunnel)
1539 SVN_ERR(svn_fs_deltify_revision(b->repository->fs, new_rev, pool));
1541 /* Unlock the paths. */
1542 if (! keep_locks && lock_tokens && lock_tokens->nelts)
1543 SVN_ERR(unlock_paths(lock_tokens, b, pool));
1545 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "r(?c)(?c)(?c)",
1546 new_rev, date, author, post_commit_err));
1548 if (! b->client_info->tunnel)
1549 SVN_ERR(svn_fs_deltify_revision(b->repository->fs, new_rev, pool));
1551 return SVN_NO_ERROR;
1554 static svn_error_t *
1555 get_file(svn_ra_svn_conn_t *conn,
1557 svn_ra_svn__list_t *params,
1560 server_baton_t *b = baton;
1561 const char *path, *full_path, *hex_digest;
1563 svn_fs_root_t *root;
1564 svn_stream_t *contents;
1565 apr_hash_t *props = NULL;
1566 apr_array_header_t *inherited_props;
1567 svn_string_t write_str;
1570 svn_boolean_t want_props, want_contents;
1571 apr_uint64_t wants_inherited_props;
1572 svn_checksum_t *checksum;
1573 svn_error_t *err, *write_err;
1580 /* Parse arguments. */
1581 SVN_ERR(svn_ra_svn__parse_tuple(params, "c(?r)bb?B", &path, &rev,
1582 &want_props, &want_contents,
1583 &wants_inherited_props));
1585 if (wants_inherited_props == SVN_RA_SVN_UNSPECIFIED_NUMBER)
1586 wants_inherited_props = FALSE;
1588 full_path = svn_fspath__join(b->repository->fs_path->data,
1589 svn_relpath_canonicalize(path, pool), pool);
1591 /* Check authorizations */
1592 SVN_ERR(must_have_access(conn, pool, b, svn_authz_read,
1595 if (!SVN_IS_VALID_REVNUM(rev))
1596 SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->repository->fs, pool));
1598 SVN_ERR(log_command(b, conn, pool, "%s",
1599 svn_log__get_file(full_path, rev,
1600 want_contents, want_props, pool)));
1602 /* Fetch the properties and a stream for the contents. */
1603 SVN_CMD_ERR(svn_fs_revision_root(&root, b->repository->fs, rev, pool));
1604 SVN_CMD_ERR(svn_fs_file_checksum(&checksum, svn_checksum_md5, root,
1605 full_path, TRUE, pool));
1606 hex_digest = svn_checksum_to_cstring_display(checksum, pool);
1608 /* Fetch the file's explicit and/or inherited properties if
1609 requested. Although the wants-iprops boolean was added to the
1610 protocol in 1.8 a standard 1.8 client never requests iprops. */
1611 if (want_props || wants_inherited_props)
1612 SVN_CMD_ERR(get_props(want_props ? &props : NULL,
1613 wants_inherited_props ? &inherited_props : NULL,
1614 &ab, root, full_path,
1617 SVN_CMD_ERR(svn_fs_file_contents(&contents, root, full_path, pool));
1619 /* Send successful command response with revision and props. */
1620 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w((?c)r(!", "success",
1622 SVN_ERR(svn_ra_svn__write_proplist(conn, pool, props));
1624 if (wants_inherited_props)
1626 apr_pool_t *iterpool = svn_pool_create(pool);
1628 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!)(?!"));
1629 for (i = 0; i < inherited_props->nelts; i++)
1631 svn_prop_inherited_item_t *iprop =
1632 APR_ARRAY_IDX(inherited_props, i, svn_prop_inherited_item_t *);
1634 svn_pool_clear(iterpool);
1635 SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "!(c(!",
1636 iprop->path_or_url));
1637 SVN_ERR(svn_ra_svn__write_proplist(conn, iterpool, iprop->prop_hash));
1638 SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "!))!",
1639 iprop->path_or_url));
1641 svn_pool_destroy(iterpool);
1644 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!))"));
1646 /* Now send the file's contents. */
1653 err = svn_stream_read_full(contents, buf, &len);
1658 write_str.data = buf;
1659 write_str.len = len;
1660 SVN_ERR(svn_ra_svn__write_string(conn, pool, &write_str));
1662 if (len < sizeof(buf))
1664 err = svn_stream_close(contents);
1668 write_err = svn_ra_svn__write_cstring(conn, pool, "");
1671 svn_error_clear(err);
1675 SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, ""));
1678 return SVN_NO_ERROR;
1681 /* Translate all the words in DIRENT_FIELDS_LIST into the flags in
1682 * DIRENT_FIELDS_P. If DIRENT_FIELDS_LIST is NULL, set all flags. */
1683 static svn_error_t *
1684 parse_dirent_fields(apr_uint32_t *dirent_fields_p,
1685 svn_ra_svn__list_t *dirent_fields_list)
1687 static const svn_string_t str_kind
1688 = SVN__STATIC_STRING(SVN_RA_SVN_DIRENT_KIND);
1689 static const svn_string_t str_size
1690 = SVN__STATIC_STRING(SVN_RA_SVN_DIRENT_SIZE);
1691 static const svn_string_t str_has_props
1692 = SVN__STATIC_STRING(SVN_RA_SVN_DIRENT_HAS_PROPS);
1693 static const svn_string_t str_created_rev
1694 = SVN__STATIC_STRING(SVN_RA_SVN_DIRENT_CREATED_REV);
1695 static const svn_string_t str_time
1696 = SVN__STATIC_STRING(SVN_RA_SVN_DIRENT_TIME);
1697 static const svn_string_t str_last_author
1698 = SVN__STATIC_STRING(SVN_RA_SVN_DIRENT_LAST_AUTHOR);
1700 apr_uint32_t dirent_fields;
1702 if (! dirent_fields_list)
1704 dirent_fields = SVN_DIRENT_ALL;
1711 for (i = 0; i < dirent_fields_list->nelts; ++i)
1713 svn_ra_svn__item_t *elt
1714 = &SVN_RA_SVN__LIST_ITEM(dirent_fields_list, i);
1716 if (elt->kind != SVN_RA_SVN_WORD)
1717 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
1718 "Dirent field not a word");
1720 if (svn_string_compare(&str_kind, &elt->u.word))
1721 dirent_fields |= SVN_DIRENT_KIND;
1722 else if (svn_string_compare(&str_size, &elt->u.word))
1723 dirent_fields |= SVN_DIRENT_SIZE;
1724 else if (svn_string_compare(&str_has_props, &elt->u.word))
1725 dirent_fields |= SVN_DIRENT_HAS_PROPS;
1726 else if (svn_string_compare(&str_created_rev, &elt->u.word))
1727 dirent_fields |= SVN_DIRENT_CREATED_REV;
1728 else if (svn_string_compare(&str_time, &elt->u.word))
1729 dirent_fields |= SVN_DIRENT_TIME;
1730 else if (svn_string_compare(&str_last_author, &elt->u.word))
1731 dirent_fields |= SVN_DIRENT_LAST_AUTHOR;
1735 *dirent_fields_p = dirent_fields;
1736 return SVN_NO_ERROR;
1739 static svn_error_t *
1740 get_dir(svn_ra_svn_conn_t *conn,
1742 svn_ra_svn__list_t *params,
1745 server_baton_t *b = baton;
1746 const char *path, *full_path;
1748 apr_hash_t *entries, *props = NULL;
1749 apr_array_header_t *inherited_props;
1750 apr_hash_index_t *hi;
1751 svn_fs_root_t *root;
1752 apr_pool_t *subpool;
1753 svn_boolean_t want_props, want_contents;
1754 apr_uint64_t wants_inherited_props;
1755 apr_uint32_t dirent_fields;
1756 svn_ra_svn__list_t *dirent_fields_list = NULL;
1763 SVN_ERR(svn_ra_svn__parse_tuple(params, "c(?r)bb?l?B", &path, &rev,
1764 &want_props, &want_contents,
1765 &dirent_fields_list,
1766 &wants_inherited_props));
1768 if (wants_inherited_props == SVN_RA_SVN_UNSPECIFIED_NUMBER)
1769 wants_inherited_props = FALSE;
1771 SVN_ERR(parse_dirent_fields(&dirent_fields, dirent_fields_list));
1772 full_path = svn_fspath__join(b->repository->fs_path->data,
1773 svn_relpath_canonicalize(path, pool), pool);
1775 /* Check authorizations */
1776 SVN_ERR(must_have_access(conn, pool, b, svn_authz_read,
1779 if (!SVN_IS_VALID_REVNUM(rev))
1780 SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->repository->fs, pool));
1782 SVN_ERR(log_command(b, conn, pool, "%s",
1783 svn_log__get_dir(full_path, rev,
1784 want_contents, want_props,
1785 dirent_fields, pool)));
1787 /* Fetch the root of the appropriate revision. */
1788 SVN_CMD_ERR(svn_fs_revision_root(&root, b->repository->fs, rev, pool));
1790 /* Fetch the directory's explicit and/or inherited properties if
1791 requested. Although the wants-iprops boolean was added to the
1792 protocol in 1.8 a standard 1.8 client never requests iprops. */
1793 if (want_props || wants_inherited_props)
1794 SVN_CMD_ERR(get_props(want_props ? &props : NULL,
1795 wants_inherited_props ? &inherited_props : NULL,
1796 &ab, root, full_path,
1799 /* Fetch the directories' entries before starting the response, to allow
1800 proper error handling in cases like when FULL_PATH doesn't exist */
1802 SVN_CMD_ERR(svn_fs_dir_entries(&entries, root, full_path, pool));
1804 /* Begin response ... */
1805 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w(r(!", "success", rev));
1806 SVN_ERR(svn_ra_svn__write_proplist(conn, pool, props));
1807 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!)(!"));
1809 /* Fetch the directory entries if requested and send them immediately. */
1812 /* Use epoch for a placeholder for a missing date. */
1813 const char *missing_date = svn_time_to_cstring(0, pool);
1815 /* Transform the hash table's FS entries into dirents. This probably
1816 * belongs in libsvn_repos. */
1817 subpool = svn_pool_create(pool);
1818 for (hi = apr_hash_first(pool, entries); hi; hi = apr_hash_next(hi))
1820 const char *name = apr_hash_this_key(hi);
1821 svn_fs_dirent_t *fsent = apr_hash_this_val(hi);
1822 const char *file_path;
1824 /* The fields in the entry tuple. */
1825 svn_node_kind_t entry_kind = svn_node_none;
1826 svn_filesize_t entry_size = 0;
1827 svn_boolean_t has_props = FALSE;
1828 /* If 'created rev' was not requested, send 0. We can't use
1829 * SVN_INVALID_REVNUM as the tuple field is not optional.
1830 * See the email thread on dev@, 2012-03-28, subject
1831 * "buildbot failure in ASF Buildbot on svn-slik-w2k3-x64-ra",
1832 * <http://svn.haxx.se/dev/archive-2012-03/0655.shtml>. */
1833 svn_revnum_t created_rev = 0;
1834 const char *cdate = NULL;
1835 const char *last_author = NULL;
1837 svn_pool_clear(subpool);
1839 file_path = svn_fspath__join(full_path, name, subpool);
1840 if (! lookup_access(subpool, b, svn_authz_read, file_path, FALSE))
1843 if (dirent_fields & SVN_DIRENT_KIND)
1844 entry_kind = fsent->kind;
1846 if (dirent_fields & SVN_DIRENT_SIZE)
1847 if (fsent->kind != svn_node_dir)
1848 SVN_CMD_ERR(svn_fs_file_length(&entry_size, root, file_path,
1851 if (dirent_fields & SVN_DIRENT_HAS_PROPS)
1854 SVN_CMD_ERR(svn_fs_node_has_props(&has_props, root, file_path,
1858 if ((dirent_fields & SVN_DIRENT_LAST_AUTHOR)
1859 || (dirent_fields & SVN_DIRENT_TIME)
1860 || (dirent_fields & SVN_DIRENT_CREATED_REV))
1862 /* created_rev, last_author, time */
1863 SVN_CMD_ERR(svn_repos_get_committed_info(&created_rev,
1871 /* The client does not properly handle a missing CDATE. For
1872 interoperability purposes, we must fill in some junk.
1874 See libsvn_ra_svn/client.c:ra_svn_get_dir() */
1876 cdate = missing_date;
1878 /* Send the entry. */
1879 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "cwnbr(?c)(?c)", name,
1880 svn_node_kind_to_word(entry_kind),
1881 (apr_uint64_t) entry_size,
1882 has_props, created_rev,
1883 cdate, last_author));
1885 svn_pool_destroy(subpool);
1888 if (wants_inherited_props)
1890 apr_pool_t *iterpool = svn_pool_create(pool);
1892 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!)(?!"));
1893 for (i = 0; i < inherited_props->nelts; i++)
1895 svn_prop_inherited_item_t *iprop =
1896 APR_ARRAY_IDX(inherited_props, i, svn_prop_inherited_item_t *);
1898 svn_pool_clear(iterpool);
1899 SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "!(c(!",
1900 iprop->path_or_url));
1901 SVN_ERR(svn_ra_svn__write_proplist(conn, iterpool, iprop->prop_hash));
1902 SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "!))!",
1903 iprop->path_or_url));
1905 svn_pool_destroy(iterpool);
1908 /* Finish response. */
1909 return svn_ra_svn__write_tuple(conn, pool, "!))");
1912 static svn_error_t *
1913 update(svn_ra_svn_conn_t *conn,
1915 svn_ra_svn__list_t *params,
1918 server_baton_t *b = baton;
1920 const char *target, *full_path, *depth_word;
1921 svn_boolean_t recurse;
1922 svn_tristate_t send_copyfrom_args; /* Optional; default FALSE */
1923 svn_tristate_t ignore_ancestry; /* Optional; default FALSE */
1924 /* Default to unknown. Old clients won't send depth, but we'll
1925 handle that by converting recurse if necessary. */
1926 svn_depth_t depth = svn_depth_unknown;
1927 svn_boolean_t is_checkout;
1929 /* Parse the arguments. */
1930 SVN_ERR(svn_ra_svn__parse_tuple(params, "(?r)cb?w3?3", &rev, &target,
1931 &recurse, &depth_word,
1932 &send_copyfrom_args, &ignore_ancestry));
1933 target = svn_relpath_canonicalize(target, pool);
1936 depth = svn_depth_from_word(depth_word);
1938 depth = SVN_DEPTH_INFINITY_OR_FILES(recurse);
1940 full_path = svn_fspath__join(b->repository->fs_path->data, target, pool);
1941 /* Check authorization and authenticate the user if necessary. */
1942 SVN_ERR(must_have_access(conn, pool, b, svn_authz_read, full_path, FALSE));
1944 if (!SVN_IS_VALID_REVNUM(rev))
1945 SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->repository->fs, pool));
1947 SVN_ERR(accept_report(&is_checkout, NULL,
1948 conn, pool, b, rev, target, NULL, TRUE,
1950 (send_copyfrom_args == svn_tristate_true),
1951 (ignore_ancestry == svn_tristate_true)));
1954 SVN_ERR(log_command(b, conn, pool, "%s",
1955 svn_log__checkout(full_path, rev,
1960 SVN_ERR(log_command(b, conn, pool, "%s",
1961 svn_log__update(full_path, rev, depth,
1963 == svn_tristate_true),
1967 return SVN_NO_ERROR;
1970 static svn_error_t *
1971 switch_cmd(svn_ra_svn_conn_t *conn,
1973 svn_ra_svn__list_t *params,
1976 server_baton_t *b = baton;
1978 const char *target, *depth_word;
1979 const char *switch_url, *switch_path;
1980 svn_boolean_t recurse;
1981 /* Default to unknown. Old clients won't send depth, but we'll
1982 handle that by converting recurse if necessary. */
1983 svn_depth_t depth = svn_depth_unknown;
1984 svn_tristate_t send_copyfrom_args; /* Optional; default FALSE */
1985 svn_tristate_t ignore_ancestry; /* Optional; default TRUE */
1987 /* Parse the arguments. */
1988 SVN_ERR(svn_ra_svn__parse_tuple(params, "(?r)cbc?w?33", &rev, &target,
1989 &recurse, &switch_url, &depth_word,
1990 &send_copyfrom_args, &ignore_ancestry));
1991 target = svn_relpath_canonicalize(target, pool);
1992 switch_url = svn_uri_canonicalize(switch_url, pool);
1995 depth = svn_depth_from_word(depth_word);
1997 depth = SVN_DEPTH_INFINITY_OR_FILES(recurse);
1999 SVN_ERR(trivial_auth_request(conn, pool, b));
2000 if (!SVN_IS_VALID_REVNUM(rev))
2001 SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->repository->fs, pool));
2003 SVN_CMD_ERR(get_fs_path(svn_path_uri_decode(b->repository->repos_url,
2005 svn_path_uri_decode(switch_url, pool),
2009 const char *full_path = svn_fspath__join(b->repository->fs_path->data,
2011 SVN_ERR(log_command(b, conn, pool, "%s",
2012 svn_log__switch(full_path, switch_path, rev,
2016 return accept_report(NULL, NULL,
2017 conn, pool, b, rev, target, switch_path, TRUE,
2019 (send_copyfrom_args == svn_tristate_true),
2020 (ignore_ancestry != svn_tristate_false));
2023 static svn_error_t *
2024 status(svn_ra_svn_conn_t *conn,
2026 svn_ra_svn__list_t *params,
2029 server_baton_t *b = baton;
2031 const char *target, *depth_word;
2032 svn_boolean_t recurse;
2033 /* Default to unknown. Old clients won't send depth, but we'll
2034 handle that by converting recurse if necessary. */
2035 svn_depth_t depth = svn_depth_unknown;
2037 /* Parse the arguments. */
2038 SVN_ERR(svn_ra_svn__parse_tuple(params, "cb?(?r)?w",
2039 &target, &recurse, &rev, &depth_word));
2040 target = svn_relpath_canonicalize(target, pool);
2043 depth = svn_depth_from_word(depth_word);
2045 depth = SVN_DEPTH_INFINITY_OR_EMPTY(recurse);
2047 SVN_ERR(trivial_auth_request(conn, pool, b));
2048 if (!SVN_IS_VALID_REVNUM(rev))
2049 SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->repository->fs, pool));
2052 const char *full_path = svn_fspath__join(b->repository->fs_path->data,
2054 SVN_ERR(log_command(b, conn, pool, "%s",
2055 svn_log__status(full_path, rev, depth, pool)));
2058 return accept_report(NULL, NULL, conn, pool, b, rev, target, NULL, FALSE,
2059 depth, FALSE, FALSE);
2062 static svn_error_t *
2063 diff(svn_ra_svn_conn_t *conn,
2065 svn_ra_svn__list_t *params,
2068 server_baton_t *b = baton;
2070 const char *target, *versus_url, *versus_path, *depth_word;
2071 svn_boolean_t recurse, ignore_ancestry;
2072 svn_boolean_t text_deltas;
2073 /* Default to unknown. Old clients won't send depth, but we'll
2074 handle that by converting recurse if necessary. */
2075 svn_depth_t depth = svn_depth_unknown;
2077 /* Parse the arguments. */
2078 if (params->nelts == 5)
2080 /* Clients before 1.4 don't send the text_deltas boolean or depth. */
2081 SVN_ERR(svn_ra_svn__parse_tuple(params, "(?r)cbbc", &rev, &target,
2082 &recurse, &ignore_ancestry, &versus_url));
2088 SVN_ERR(svn_ra_svn__parse_tuple(params, "(?r)cbbcb?w",
2089 &rev, &target, &recurse,
2090 &ignore_ancestry, &versus_url,
2091 &text_deltas, &depth_word));
2093 target = svn_relpath_canonicalize(target, pool);
2094 versus_url = svn_uri_canonicalize(versus_url, pool);
2097 depth = svn_depth_from_word(depth_word);
2099 depth = SVN_DEPTH_INFINITY_OR_FILES(recurse);
2101 SVN_ERR(trivial_auth_request(conn, pool, b));
2103 if (!SVN_IS_VALID_REVNUM(rev))
2104 SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->repository->fs, pool));
2105 SVN_CMD_ERR(get_fs_path(svn_path_uri_decode(b->repository->repos_url,
2107 svn_path_uri_decode(versus_url, pool),
2111 const char *full_path = svn_fspath__join(b->repository->fs_path->data,
2113 svn_revnum_t from_rev;
2114 SVN_ERR(accept_report(NULL, &from_rev,
2115 conn, pool, b, rev, target, versus_path,
2116 text_deltas, depth, FALSE, ignore_ancestry));
2117 SVN_ERR(log_command(b, conn, pool, "%s",
2118 svn_log__diff(full_path, from_rev, versus_path,
2119 rev, depth, ignore_ancestry,
2122 return SVN_NO_ERROR;
2125 /* Baton type to be used with mergeinfo_receiver. */
2126 typedef struct mergeinfo_receiver_baton_t
2128 /* Send the response over this connection. */
2129 svn_ra_svn_conn_t *conn;
2131 /* Start path of the query; report paths relative to this one. */
2132 const char *fs_path;
2134 /* Did we already send the opening sequence? */
2135 svn_boolean_t starting_tuple_sent;
2136 } mergeinfo_receiver_baton_t;
2138 /* Utility method sending the start of the "get m/i" response once
2139 over BATON->CONN. */
2140 static svn_error_t *
2141 send_mergeinfo_starting_tuple(mergeinfo_receiver_baton_t *baton,
2142 apr_pool_t *scratch_pool)
2144 if (baton->starting_tuple_sent)
2145 return SVN_NO_ERROR;
2147 SVN_ERR(svn_ra_svn__write_tuple(baton->conn, scratch_pool,
2148 "w((!", "success"));
2149 baton->starting_tuple_sent = TRUE;
2151 return SVN_NO_ERROR;
2154 /* Implements svn_repos_mergeinfo_receiver_t, sending the MERGEINFO
2155 * out over the connection in the mergeinfo_receiver_baton_t * BATON. */
2156 static svn_error_t *
2157 mergeinfo_receiver(const char *path,
2158 svn_mergeinfo_t mergeinfo,
2160 apr_pool_t *scratch_pool)
2162 mergeinfo_receiver_baton_t *b = baton;
2163 svn_string_t *mergeinfo_string;
2165 /* Delay starting the response until we checked that the initial
2166 request went through. We are at that point now b/c we've got
2167 the first results in. */
2168 SVN_ERR(send_mergeinfo_starting_tuple(b, scratch_pool));
2170 /* Adjust the path info and send the m/i. */
2171 path = svn_fspath__skip_ancestor(b->fs_path, path);
2172 SVN_ERR(svn_mergeinfo_to_string(&mergeinfo_string, mergeinfo,
2174 SVN_ERR(svn_ra_svn__write_tuple(b->conn, scratch_pool, "cs", path,
2177 return SVN_NO_ERROR;
2180 /* Regardless of whether a client's capabilities indicate an
2181 understanding of this command (by way of SVN_RA_SVN_CAP_MERGEINFO),
2182 we provide a response.
2184 ASSUMPTION: When performing a 'merge' with two URLs at different
2185 revisions, the client will call this command more than once. */
2186 static svn_error_t *
2187 get_mergeinfo(svn_ra_svn_conn_t *conn,
2189 svn_ra_svn__list_t *params,
2192 server_baton_t *b = baton;
2194 svn_ra_svn__list_t *paths;
2195 apr_array_header_t *canonical_paths;
2197 const char *inherit_word;
2198 svn_mergeinfo_inheritance_t inherit;
2199 svn_boolean_t include_descendants;
2201 mergeinfo_receiver_baton_t mergeinfo_baton;
2206 mergeinfo_baton.conn = conn;
2207 mergeinfo_baton.fs_path = b->repository->fs_path->data;
2208 mergeinfo_baton.starting_tuple_sent = FALSE;
2210 SVN_ERR(svn_ra_svn__parse_tuple(params, "l(?r)wb", &paths, &rev,
2211 &inherit_word, &include_descendants));
2212 inherit = svn_inheritance_from_word(inherit_word);
2214 /* Canonicalize the paths which mergeinfo has been requested for. */
2215 canonical_paths = apr_array_make(pool, paths->nelts, sizeof(const char *));
2216 for (i = 0; i < paths->nelts; i++)
2218 svn_ra_svn__item_t *item = &SVN_RA_SVN__LIST_ITEM(paths, i);
2219 const char *full_path;
2221 if (item->kind != SVN_RA_SVN_STRING)
2222 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2223 _("Path is not a string"));
2224 full_path = svn_relpath_canonicalize(item->u.string.data, pool);
2225 full_path = svn_fspath__join(b->repository->fs_path->data, full_path, pool);
2226 APR_ARRAY_PUSH(canonical_paths, const char *) = full_path;
2229 SVN_ERR(log_command(b, conn, pool, "%s",
2230 svn_log__get_mergeinfo(canonical_paths, inherit,
2231 include_descendants,
2234 SVN_ERR(trivial_auth_request(conn, pool, b));
2236 SVN_CMD_ERR(svn_repos_fs_get_mergeinfo2(b->repository->repos,
2237 canonical_paths, rev,
2239 include_descendants,
2240 authz_check_access_cb_func(b), &ab,
2245 /* We might not have sent anything
2246 => ensure to begin the response in any case. */
2247 SVN_ERR(send_mergeinfo_starting_tuple(&mergeinfo_baton, pool));
2248 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!))"));
2250 return SVN_NO_ERROR;
2253 /* Send a changed paths list entry to the client.
2254 This implements svn_repos_path_change_receiver_t. */
2255 static svn_error_t *
2256 path_change_receiver(void *baton,
2257 svn_repos_path_change_t *change,
2258 apr_pool_t *scratch_pool)
2260 const char symbol[] = "MADR";
2262 log_baton_t *b = baton;
2263 svn_ra_svn_conn_t *conn = b->conn;
2265 /* Sanitize and convert change kind to ra-svn level action.
2267 Pushing that conversion down into libsvn_ra_svn would add yet another
2268 API dependency there. */
2269 char action = ( change->change_kind < svn_fs_path_change_modify
2270 || change->change_kind > svn_fs_path_change_replace)
2272 : symbol[change->change_kind];
2274 /* Open lists once: LOG_ENTRY and LOG_ENTRY->CHANGED_PATHS. */
2277 SVN_ERR(svn_ra_svn__start_list(conn, scratch_pool));
2278 SVN_ERR(svn_ra_svn__start_list(conn, scratch_pool));
2282 /* Serialize CHANGE. */
2283 SVN_ERR(svn_ra_svn__write_data_log_changed_path(
2287 change->copyfrom_path,
2288 change->copyfrom_rev,
2293 return SVN_NO_ERROR;
2296 /* Send a the meta data and the revpros for LOG_ENTRY to the client.
2297 This implements svn_log_entry_receiver_t. */
2298 static svn_error_t *
2299 revision_receiver(void *baton,
2300 svn_repos_log_entry_t *log_entry,
2301 apr_pool_t *scratch_pool)
2303 log_baton_t *b = baton;
2304 svn_ra_svn_conn_t *conn = b->conn;
2305 svn_boolean_t invalid_revnum = FALSE;
2306 const svn_string_t *author, *date, *message;
2307 unsigned revprop_count;
2309 if (log_entry->revision == SVN_INVALID_REVNUM)
2311 /* If the stack depth is zero, we've seen the last revision, so don't
2312 send it, just return. */
2313 if (b->stack_depth == 0)
2314 return SVN_NO_ERROR;
2316 /* Because the svn protocol won't let us send an invalid revnum, we have
2317 to fudge here and send an additional flag. */
2318 log_entry->revision = 0;
2319 invalid_revnum = TRUE;
2323 svn_compat_log_revprops_out_string(&author, &date, &message,
2324 log_entry->revprops);
2326 /* Revprops list filtering is somewhat expensive.
2327 Avoid doing that for the 90% case where only the standard revprops
2328 have been requested and delivered. */
2329 if (author && date && message && apr_hash_count(log_entry->revprops) == 3)
2335 svn_compat_log_revprops_clear(log_entry->revprops);
2336 if (log_entry->revprops)
2337 revprop_count = apr_hash_count(log_entry->revprops);
2342 /* Open lists once: LOG_ENTRY and LOG_ENTRY->CHANGED_PATHS. */
2345 SVN_ERR(svn_ra_svn__start_list(conn, scratch_pool));
2346 SVN_ERR(svn_ra_svn__start_list(conn, scratch_pool));
2349 /* Close LOG_ENTRY->CHANGED_PATHS. */
2350 SVN_ERR(svn_ra_svn__end_list(conn, scratch_pool));
2353 /* send LOG_ENTRY main members */
2354 SVN_ERR(svn_ra_svn__write_data_log_entry(conn, scratch_pool,
2355 log_entry->revision,
2356 author, date, message,
2357 log_entry->has_children,
2358 invalid_revnum, revprop_count));
2360 /* send LOG_ENTRY->REVPROPS */
2361 SVN_ERR(svn_ra_svn__start_list(conn, scratch_pool));
2363 SVN_ERR(svn_ra_svn__write_proplist(conn, scratch_pool,
2364 log_entry->revprops));
2365 SVN_ERR(svn_ra_svn__end_list(conn, scratch_pool));
2367 /* send LOG_ENTRY members that were added in later SVN releases */
2368 SVN_ERR(svn_ra_svn__write_boolean(conn, scratch_pool,
2369 log_entry->subtractive_merge));
2370 SVN_ERR(svn_ra_svn__end_list(conn, scratch_pool));
2372 if (log_entry->has_children)
2375 return SVN_NO_ERROR;
2378 static svn_error_t *
2379 log_cmd(svn_ra_svn_conn_t *conn,
2381 svn_ra_svn__list_t *params,
2384 svn_error_t *err, *write_err;
2385 server_baton_t *b = baton;
2386 svn_revnum_t start_rev, end_rev;
2387 const char *full_path;
2388 svn_boolean_t send_changed_paths, strict_node, include_merged_revisions;
2389 apr_array_header_t *full_paths, *revprops;
2390 svn_ra_svn__list_t *paths, *revprop_items;
2392 svn_ra_svn__item_t *elt;
2394 apr_uint64_t limit, include_merged_revs_param;
2401 SVN_ERR(svn_ra_svn__parse_tuple(params, "l(?r)(?r)bb?n?Bwl", &paths,
2402 &start_rev, &end_rev, &send_changed_paths,
2403 &strict_node, &limit,
2404 &include_merged_revs_param,
2405 &revprop_word, &revprop_items));
2407 if (include_merged_revs_param == SVN_RA_SVN_UNSPECIFIED_NUMBER)
2408 include_merged_revisions = FALSE;
2410 include_merged_revisions = (svn_boolean_t) include_merged_revs_param;
2412 if (revprop_word == NULL)
2413 /* pre-1.5 client */
2414 revprops = svn_compat_log_revprops_in(pool);
2415 else if (strcmp(revprop_word, "all-revprops") == 0)
2417 else if (strcmp(revprop_word, "revprops") == 0)
2419 SVN_ERR_ASSERT(revprop_items);
2421 revprops = apr_array_make(pool, revprop_items->nelts,
2423 for (i = 0; i < revprop_items->nelts; i++)
2425 elt = &SVN_RA_SVN__LIST_ITEM(revprop_items, i);
2426 if (elt->kind != SVN_RA_SVN_STRING)
2427 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2428 _("Log revprop entry not a string"));
2429 APR_ARRAY_PUSH(revprops, const char *) = elt->u.string.data;
2433 return svn_error_createf(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2434 _("Unknown revprop word '%s' in log command"),
2437 /* If we got an unspecified number then the user didn't send us anything,
2438 so we assume no limit. If it's larger than INT_MAX then someone is
2439 messing with us, since we know the svn client libraries will never send
2440 us anything that big, so play it safe and default to no limit. */
2441 if (limit == SVN_RA_SVN_UNSPECIFIED_NUMBER || limit > INT_MAX)
2444 full_paths = apr_array_make(pool, paths->nelts, sizeof(const char *));
2445 for (i = 0; i < paths->nelts; i++)
2447 elt = &SVN_RA_SVN__LIST_ITEM(paths, i);
2448 if (elt->kind != SVN_RA_SVN_STRING)
2449 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2450 _("Log path entry not a string"));
2451 full_path = svn_relpath_canonicalize(elt->u.string.data, pool),
2452 full_path = svn_fspath__join(b->repository->fs_path->data, full_path,
2454 APR_ARRAY_PUSH(full_paths, const char *) = full_path;
2456 SVN_ERR(trivial_auth_request(conn, pool, b));
2458 SVN_ERR(log_command(b, conn, pool, "%s",
2459 svn_log__log(full_paths, start_rev, end_rev,
2460 (int) limit, send_changed_paths,
2461 strict_node, include_merged_revisions,
2464 /* Get logs. (Can't report errors back to the client at this point.) */
2465 lb.fs_path = b->repository->fs_path->data;
2469 err = svn_repos_get_logs5(b->repository->repos, full_paths, start_rev,
2470 end_rev, (int) limit,
2471 strict_node, include_merged_revisions,
2472 revprops, authz_check_access_cb_func(b), &ab,
2473 send_changed_paths ? path_change_receiver : NULL,
2474 send_changed_paths ? &lb : NULL,
2475 revision_receiver, &lb, pool);
2477 write_err = svn_ra_svn__write_word(conn, pool, "done");
2480 svn_error_clear(err);
2484 SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, ""));
2485 return SVN_NO_ERROR;
2488 static svn_error_t *
2489 check_path(svn_ra_svn_conn_t *conn,
2491 svn_ra_svn__list_t *params,
2494 server_baton_t *b = baton;
2496 const char *path, *full_path;
2497 svn_fs_root_t *root;
2498 svn_node_kind_t kind;
2500 SVN_ERR(svn_ra_svn__parse_tuple(params, "c(?r)", &path, &rev));
2501 full_path = svn_fspath__join(b->repository->fs_path->data,
2502 svn_relpath_canonicalize(path, pool), pool);
2504 /* Check authorizations */
2505 SVN_ERR(must_have_access(conn, pool, b, svn_authz_read,
2508 if (!SVN_IS_VALID_REVNUM(rev))
2509 SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->repository->fs, pool));
2511 SVN_ERR(log_command(b, conn, pool, "check-path %s@%d",
2512 svn_path_uri_encode(full_path, pool), rev));
2514 SVN_CMD_ERR(svn_fs_revision_root(&root, b->repository->fs, rev, pool));
2515 SVN_CMD_ERR(svn_fs_check_path(&kind, root, full_path, pool));
2516 SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, "w",
2517 svn_node_kind_to_word(kind)));
2518 return SVN_NO_ERROR;
2521 static svn_error_t *
2522 stat_cmd(svn_ra_svn_conn_t *conn,
2524 svn_ra_svn__list_t *params,
2527 server_baton_t *b = baton;
2529 const char *path, *full_path, *cdate;
2530 svn_fs_root_t *root;
2531 svn_dirent_t *dirent;
2533 SVN_ERR(svn_ra_svn__parse_tuple(params, "c(?r)", &path, &rev));
2534 full_path = svn_fspath__join(b->repository->fs_path->data,
2535 svn_relpath_canonicalize(path, pool), pool);
2537 /* Check authorizations */
2538 SVN_ERR(must_have_access(conn, pool, b, svn_authz_read,
2541 if (!SVN_IS_VALID_REVNUM(rev))
2542 SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->repository->fs, pool));
2544 SVN_ERR(log_command(b, conn, pool, "stat %s@%d",
2545 svn_path_uri_encode(full_path, pool), rev));
2547 SVN_CMD_ERR(svn_fs_revision_root(&root, b->repository->fs, rev, pool));
2548 SVN_CMD_ERR(svn_repos_stat(&dirent, root, full_path, pool));
2550 /* Need to return the equivalent of "(?l)", since that's what the
2551 client is reading. */
2555 SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, "()"));
2556 return SVN_NO_ERROR;
2559 cdate = (dirent->time == (time_t) -1) ? NULL
2560 : svn_time_to_cstring(dirent->time, pool);
2562 SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, "((wnbr(?c)(?c)))",
2563 svn_node_kind_to_word(dirent->kind),
2564 (apr_uint64_t) dirent->size,
2565 dirent->has_props, dirent->created_rev,
2566 cdate, dirent->last_author));
2568 return SVN_NO_ERROR;
2571 static svn_error_t *
2572 get_locations(svn_ra_svn_conn_t *conn,
2574 svn_ra_svn__list_t *params,
2577 svn_error_t *err, *write_err;
2578 server_baton_t *b = baton;
2579 svn_revnum_t revision;
2580 apr_array_header_t *location_revisions;
2581 svn_ra_svn__list_t *loc_revs_proto;
2582 svn_ra_svn__item_t *elt;
2584 const char *relative_path;
2585 svn_revnum_t peg_revision;
2586 apr_hash_t *fs_locations;
2587 const char *abs_path;
2593 /* Parse the arguments. */
2594 SVN_ERR(svn_ra_svn__parse_tuple(params, "crl", &relative_path,
2597 relative_path = svn_relpath_canonicalize(relative_path, pool);
2599 abs_path = svn_fspath__join(b->repository->fs_path->data, relative_path,
2602 location_revisions = apr_array_make(pool, loc_revs_proto->nelts,
2603 sizeof(svn_revnum_t));
2604 for (i = 0; i < loc_revs_proto->nelts; i++)
2606 elt = &SVN_RA_SVN__LIST_ITEM(loc_revs_proto, i);
2607 if (elt->kind != SVN_RA_SVN_NUMBER)
2608 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2609 "Get-locations location revisions entry "
2610 "not a revision number");
2611 revision = (svn_revnum_t)(elt->u.number);
2612 APR_ARRAY_PUSH(location_revisions, svn_revnum_t) = revision;
2614 SVN_ERR(trivial_auth_request(conn, pool, b));
2615 SVN_ERR(log_command(b, conn, pool, "%s",
2616 svn_log__get_locations(abs_path, peg_revision,
2617 location_revisions, pool)));
2619 /* All the parameters are fine - let's perform the query against the
2622 /* We store both err and write_err here, so the client will get
2623 * the "done" even if there was an error in fetching the results. */
2625 err = svn_repos_trace_node_locations(b->repository->fs, &fs_locations,
2626 abs_path, peg_revision,
2628 authz_check_access_cb_func(b), &ab,
2631 /* Now, write the results to the connection. */
2636 apr_hash_index_t *iter;
2638 for (iter = apr_hash_first(pool, fs_locations); iter;
2639 iter = apr_hash_next(iter))
2641 const svn_revnum_t *iter_key = apr_hash_this_key(iter);
2642 const char *iter_value = apr_hash_this_val(iter);
2644 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "rc",
2645 *iter_key, iter_value));
2650 write_err = svn_ra_svn__write_word(conn, pool, "done");
2653 svn_error_clear(err);
2658 SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, ""));
2660 return SVN_NO_ERROR;
2663 static svn_error_t *gls_receiver(svn_location_segment_t *segment,
2667 svn_ra_svn_conn_t *conn = baton;
2668 return svn_ra_svn__write_tuple(conn, pool, "rr(?c)",
2669 segment->range_start,
2674 static svn_error_t *
2675 get_location_segments(svn_ra_svn_conn_t *conn,
2677 svn_ra_svn__list_t *params,
2680 svn_error_t *err, *write_err;
2681 server_baton_t *b = baton;
2682 svn_revnum_t peg_revision, start_rev, end_rev;
2683 const char *relative_path;
2684 const char *abs_path;
2690 /* Parse the arguments. */
2691 SVN_ERR(svn_ra_svn__parse_tuple(params, "c(?r)(?r)(?r)",
2692 &relative_path, &peg_revision,
2693 &start_rev, &end_rev));
2694 relative_path = svn_relpath_canonicalize(relative_path, pool);
2696 abs_path = svn_fspath__join(b->repository->fs_path->data, relative_path,
2699 SVN_ERR(trivial_auth_request(conn, pool, b));
2700 SVN_ERR(log_command(baton, conn, pool, "%s",
2701 svn_log__get_location_segments(abs_path, peg_revision,
2705 /* No START_REV or PEG_REVISION? We'll use HEAD. */
2706 if (!SVN_IS_VALID_REVNUM(start_rev) || !SVN_IS_VALID_REVNUM(peg_revision))
2708 svn_revnum_t youngest;
2710 err = svn_fs_youngest_rev(&youngest, b->repository->fs, pool);
2714 err = svn_error_compose_create(
2715 svn_ra_svn__write_word(conn, pool, "done"),
2718 return log_fail_and_flush(err, b, conn, pool);
2721 if (!SVN_IS_VALID_REVNUM(start_rev))
2722 start_rev = youngest;
2723 if (!SVN_IS_VALID_REVNUM(peg_revision))
2724 peg_revision = youngest;
2727 /* No END_REV? We'll use 0. */
2728 if (!SVN_IS_VALID_REVNUM(end_rev))
2731 if (end_rev > start_rev)
2733 err = svn_ra_svn__write_word(conn, pool, "done");
2734 err = svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, err,
2735 "Get-location-segments end revision must not be "
2736 "younger than start revision");
2737 return log_fail_and_flush(err, b, conn, pool);
2740 if (start_rev > peg_revision)
2742 err = svn_ra_svn__write_word(conn, pool, "done");
2743 err = svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, err,
2744 "Get-location-segments start revision must not "
2745 "be younger than peg revision");
2746 return log_fail_and_flush(err, b, conn, pool);
2749 /* All the parameters are fine - let's perform the query against the
2752 /* We store both err and write_err here, so the client will get
2753 * the "done" even if there was an error in fetching the results. */
2755 err = svn_repos_node_location_segments(b->repository->repos, abs_path,
2756 peg_revision, start_rev, end_rev,
2757 gls_receiver, (void *)conn,
2758 authz_check_access_cb_func(b), &ab,
2760 write_err = svn_ra_svn__write_word(conn, pool, "done");
2763 return svn_error_compose_create(write_err, err);
2767 SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, ""));
2769 return SVN_NO_ERROR;
2772 /* This implements svn_write_fn_t. Write LEN bytes starting at DATA to the
2773 client as a string. */
2774 static svn_error_t *svndiff_handler(void *baton, const char *data,
2777 file_revs_baton_t *b = baton;
2782 return svn_ra_svn__write_string(b->conn, b->pool, &str);
2785 /* This implements svn_close_fn_t. Mark the end of the data by writing an
2786 empty string to the client. */
2787 static svn_error_t *svndiff_close_handler(void *baton)
2789 file_revs_baton_t *b = baton;
2791 SVN_ERR(svn_ra_svn__write_cstring(b->conn, b->pool, ""));
2792 return SVN_NO_ERROR;
2795 /* This implements the svn_repos_file_rev_handler_t interface. */
2796 static svn_error_t *file_rev_handler(void *baton, const char *path,
2797 svn_revnum_t rev, apr_hash_t *rev_props,
2798 svn_boolean_t merged_revision,
2799 svn_txdelta_window_handler_t *d_handler,
2801 apr_array_header_t *prop_diffs,
2804 file_revs_baton_t *frb = baton;
2805 svn_stream_t *stream;
2807 SVN_ERR(svn_ra_svn__write_tuple(frb->conn, pool, "cr(!",
2809 SVN_ERR(svn_ra_svn__write_proplist(frb->conn, pool, rev_props));
2810 SVN_ERR(svn_ra_svn__write_tuple(frb->conn, pool, "!)(!"));
2811 SVN_ERR(write_prop_diffs(frb->conn, pool, prop_diffs));
2812 SVN_ERR(svn_ra_svn__write_tuple(frb->conn, pool, "!)b", merged_revision));
2814 /* Store the pool for the delta stream. */
2817 /* Prepare for the delta or just write an empty string. */
2820 stream = svn_stream_create(baton, pool);
2821 svn_stream_set_write(stream, svndiff_handler);
2822 svn_stream_set_close(stream, svndiff_close_handler);
2824 svn_txdelta_to_svndiff3(d_handler, d_baton, stream,
2825 svn_ra_svn__svndiff_version(frb->conn),
2826 svn_ra_svn_compression_level(frb->conn), pool);
2829 SVN_ERR(svn_ra_svn__write_cstring(frb->conn, pool, ""));
2831 return SVN_NO_ERROR;
2834 static svn_error_t *
2835 get_file_revs(svn_ra_svn_conn_t *conn,
2837 svn_ra_svn__list_t *params,
2840 server_baton_t *b = baton;
2841 svn_error_t *err, *write_err;
2842 file_revs_baton_t frb;
2843 svn_revnum_t start_rev, end_rev;
2845 const char *full_path;
2846 apr_uint64_t include_merged_revs_param;
2847 svn_boolean_t include_merged_revisions;
2853 /* Parse arguments. */
2854 SVN_ERR(svn_ra_svn__parse_tuple(params, "c(?r)(?r)?B",
2855 &path, &start_rev, &end_rev,
2856 &include_merged_revs_param));
2857 path = svn_relpath_canonicalize(path, pool);
2858 SVN_ERR(trivial_auth_request(conn, pool, b));
2859 full_path = svn_fspath__join(b->repository->fs_path->data, path, pool);
2861 if (include_merged_revs_param == SVN_RA_SVN_UNSPECIFIED_NUMBER)
2862 include_merged_revisions = FALSE;
2864 include_merged_revisions = (svn_boolean_t) include_merged_revs_param;
2866 SVN_ERR(log_command(b, conn, pool, "%s",
2867 svn_log__get_file_revs(full_path, start_rev, end_rev,
2868 include_merged_revisions,
2874 err = svn_repos_get_file_revs2(b->repository->repos, full_path, start_rev,
2875 end_rev, include_merged_revisions,
2876 authz_check_access_cb_func(b), &ab,
2877 file_rev_handler, &frb, pool);
2878 write_err = svn_ra_svn__write_word(conn, pool, "done");
2881 svn_error_clear(err);
2885 SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, ""));
2887 return SVN_NO_ERROR;
2890 static svn_error_t *
2891 lock(svn_ra_svn_conn_t *conn,
2893 svn_ra_svn__list_t *params,
2896 server_baton_t *b = baton;
2898 const char *comment;
2899 const char *full_path;
2900 svn_boolean_t steal_lock;
2901 svn_revnum_t current_rev;
2904 SVN_ERR(svn_ra_svn__parse_tuple(params, "c(?c)b(?r)", &path, &comment,
2905 &steal_lock, ¤t_rev));
2906 full_path = svn_fspath__join(b->repository->fs_path->data,
2907 svn_relpath_canonicalize(path, pool), pool);
2909 SVN_ERR(must_have_access(conn, pool, b, svn_authz_write,
2911 SVN_ERR(log_command(b, conn, pool, "%s",
2912 svn_log__lock_one_path(full_path, steal_lock, pool)));
2914 SVN_CMD_ERR(svn_repos_fs_lock(&l, b->repository->repos, full_path, NULL,
2915 comment, 0, 0, /* No expiration time. */
2916 current_rev, steal_lock, pool));
2918 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w(!", "success"));
2919 SVN_ERR(write_lock(conn, pool, l));
2920 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!)"));
2922 return SVN_NO_ERROR;
2925 struct lock_result_t {
2926 const svn_lock_t *lock;
2930 struct lock_many_baton_t {
2931 apr_hash_t *results;
2935 /* Implements svn_fs_lock_callback_t. */
2936 static svn_error_t *
2937 lock_many_cb(void *baton,
2939 const svn_lock_t *fs_lock,
2940 svn_error_t *fs_err,
2943 struct lock_many_baton_t *b = baton;
2944 struct lock_result_t *result = apr_palloc(b->pool,
2945 sizeof(struct lock_result_t));
2947 result->lock = fs_lock;
2948 result->err = svn_error_dup(fs_err);
2949 svn_hash_sets(b->results, apr_pstrdup(b->pool, path), result);
2951 return SVN_NO_ERROR;
2955 clear_lock_result_hash(apr_hash_t *results,
2956 apr_pool_t *scratch_pool)
2958 apr_hash_index_t *hi;
2960 for (hi = apr_hash_first(scratch_pool, results); hi; hi = apr_hash_next(hi))
2962 struct lock_result_t *result = apr_hash_this_val(hi);
2963 svn_error_clear(result->err);
2967 static svn_error_t *
2968 lock_many(svn_ra_svn_conn_t *conn,
2970 svn_ra_svn__list_t *params,
2973 server_baton_t *b = baton;
2974 svn_ra_svn__list_t *path_revs;
2975 const char *comment;
2976 svn_boolean_t steal_lock;
2978 apr_pool_t *subpool;
2979 svn_error_t *err, *write_err = SVN_NO_ERROR;
2980 apr_hash_t *targets = apr_hash_make(pool);
2981 apr_hash_t *authz_results = apr_hash_make(pool);
2982 apr_hash_index_t *hi;
2983 struct lock_many_baton_t lmb;
2985 SVN_ERR(svn_ra_svn__parse_tuple(params, "(?c)bl", &comment, &steal_lock,
2988 subpool = svn_pool_create(pool);
2990 /* Because we can only send a single auth reply per request, we send
2991 a reply before parsing the lock commands. This means an authz
2992 access denial will abort the processing of the locks and return
2994 SVN_ERR(must_have_access(conn, pool, b, svn_authz_write, NULL, TRUE));
2996 /* Parse the lock requests from PATH_REVS into TARGETS. */
2997 for (i = 0; i < path_revs->nelts; ++i)
2999 const char *path, *full_path;
3000 svn_revnum_t current_rev;
3001 svn_ra_svn__item_t *item = &SVN_RA_SVN__LIST_ITEM(path_revs, i);
3002 svn_fs_lock_target_t *target;
3004 svn_pool_clear(subpool);
3006 if (item->kind != SVN_RA_SVN_LIST)
3007 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
3008 "Lock requests should be list of lists");
3010 SVN_ERR(svn_ra_svn__parse_tuple(&item->u.list, "c(?r)", &path,
3013 full_path = svn_fspath__join(b->repository->fs_path->data,
3014 svn_relpath_canonicalize(path, subpool),
3016 target = svn_fs_lock_target_create(NULL, current_rev, pool);
3018 /* Any duplicate paths, once canonicalized, get collapsed into a
3019 single path that is processed once. The result is then
3020 returned multiple times. */
3021 svn_hash_sets(targets, full_path, target);
3024 SVN_ERR(log_command(b, conn, subpool, "%s",
3025 svn_log__lock(targets, steal_lock, subpool)));
3029 Note: From here on we need to make sure any errors in authz_results, or
3030 results, are cleared before returning from this function. */
3031 for (hi = apr_hash_first(pool, targets); hi; hi = apr_hash_next(hi))
3033 const char *full_path = apr_hash_this_key(hi);
3035 svn_pool_clear(subpool);
3037 if (! lookup_access(subpool, b, svn_authz_write, full_path, TRUE))
3039 struct lock_result_t *result
3040 = apr_palloc(pool, sizeof(struct lock_result_t));
3042 result->lock = NULL;
3043 result->err = error_create_and_log(SVN_ERR_RA_NOT_AUTHORIZED,
3045 svn_hash_sets(authz_results, full_path, result);
3046 svn_hash_sets(targets, full_path, NULL);
3050 lmb.results = apr_hash_make(pool);
3053 err = svn_repos_fs_lock_many(b->repository->repos, targets,
3055 0, /* No expiration time. */
3056 steal_lock, lock_many_cb, &lmb,
3059 /* Return results in the same order as the paths were supplied. */
3060 for (i = 0; i < path_revs->nelts; ++i)
3062 const char *path, *full_path;
3063 svn_revnum_t current_rev;
3064 svn_ra_svn__item_t *item = &SVN_RA_SVN__LIST_ITEM(path_revs, i);
3065 struct lock_result_t *result;
3067 svn_pool_clear(subpool);
3069 write_err = svn_ra_svn__parse_tuple(&item->u.list, "c(?r)",
3070 &path, ¤t_rev);
3074 full_path = svn_fspath__join(b->repository->fs_path->data,
3075 svn_relpath_canonicalize(path, subpool),
3078 result = svn_hash_gets(lmb.results, full_path);
3080 result = svn_hash_gets(authz_results, full_path);
3083 /* No result? Something really odd happened, create a
3084 placeholder error so that any other results can be
3085 reported in the correct order. */
3086 result = apr_palloc(pool, sizeof(struct lock_result_t));
3087 result->err = svn_error_createf(SVN_ERR_FS_LOCK_OPERATION_FAILED, 0,
3088 _("No result for '%s'."), path);
3089 svn_hash_sets(lmb.results, full_path, result);
3093 write_err = svn_ra_svn__write_cmd_failure(conn, subpool,
3097 write_err = svn_ra_svn__write_tuple(conn, subpool,
3100 write_err = write_lock(conn, subpool, result->lock);
3102 write_err = svn_ra_svn__write_tuple(conn, subpool, "!");
3108 clear_lock_result_hash(authz_results, subpool);
3109 clear_lock_result_hash(lmb.results, subpool);
3111 svn_pool_destroy(subpool);
3114 write_err = svn_ra_svn__write_word(conn, pool, "done");
3117 svn_error_clear(err);
3119 SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, ""));
3121 return SVN_NO_ERROR;
3124 static svn_error_t *
3125 unlock(svn_ra_svn_conn_t *conn,
3127 svn_ra_svn__list_t *params,
3130 server_baton_t *b = baton;
3131 const char *path, *token, *full_path;
3132 svn_boolean_t break_lock;
3134 SVN_ERR(svn_ra_svn__parse_tuple(params, "c(?c)b", &path, &token,
3137 full_path = svn_fspath__join(b->repository->fs_path->data,
3138 svn_relpath_canonicalize(path, pool), pool);
3140 /* Username required unless break_lock was specified. */
3141 SVN_ERR(must_have_access(conn, pool, b, svn_authz_write,
3142 full_path, ! break_lock));
3143 SVN_ERR(log_command(b, conn, pool, "%s",
3144 svn_log__unlock_one_path(full_path, break_lock, pool)));
3146 SVN_CMD_ERR(svn_repos_fs_unlock(b->repository->repos, full_path, token,
3149 SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, ""));
3151 return SVN_NO_ERROR;
3154 static svn_error_t *
3155 unlock_many(svn_ra_svn_conn_t *conn,
3157 svn_ra_svn__list_t *params,
3160 server_baton_t *b = baton;
3161 svn_boolean_t break_lock;
3162 svn_ra_svn__list_t *unlock_tokens;
3164 apr_pool_t *subpool;
3165 svn_error_t *err = SVN_NO_ERROR, *write_err = SVN_NO_ERROR;
3166 apr_hash_t *targets = apr_hash_make(pool);
3167 apr_hash_t *authz_results = apr_hash_make(pool);
3168 apr_hash_index_t *hi;
3169 struct lock_many_baton_t lmb;
3171 SVN_ERR(svn_ra_svn__parse_tuple(params, "bl", &break_lock, &unlock_tokens));
3173 /* Username required unless break_lock was specified. */
3174 SVN_ERR(must_have_access(conn, pool, b, svn_authz_write, NULL, ! break_lock));
3176 subpool = svn_pool_create(pool);
3178 /* Parse the unlock requests from PATH_REVS into TARGETS. */
3179 for (i = 0; i < unlock_tokens->nelts; i++)
3181 svn_ra_svn__item_t *item = &SVN_RA_SVN__LIST_ITEM(unlock_tokens, i);
3182 const char *path, *full_path, *token;
3184 svn_pool_clear(subpool);
3186 if (item->kind != SVN_RA_SVN_LIST)
3187 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
3188 "Unlock request should be a list of lists");
3190 SVN_ERR(svn_ra_svn__parse_tuple(&item->u.list, "c(?c)", &path,
3195 full_path = svn_fspath__join(b->repository->fs_path->data,
3196 svn_relpath_canonicalize(path, subpool),
3199 /* Any duplicate paths, once canonicalized, get collapsed into a
3200 single path that is processed once. The result is then
3201 returned multiple times. */
3202 svn_hash_sets(targets, full_path, token);
3205 SVN_ERR(log_command(b, conn, subpool, "%s",
3206 svn_log__unlock(targets, break_lock, subpool)));
3210 Note: From here on we need to make sure any errors in authz_results, or
3211 results, are cleared before returning from this function. */
3212 for (hi = apr_hash_first(pool, targets); hi; hi = apr_hash_next(hi))
3214 const char *full_path = apr_hash_this_key(hi);
3216 svn_pool_clear(subpool);
3218 if (! lookup_access(subpool, b, svn_authz_write, full_path,
3221 struct lock_result_t *result
3222 = apr_palloc(pool, sizeof(struct lock_result_t));
3224 result->lock = NULL;
3225 result->err = error_create_and_log(SVN_ERR_RA_NOT_AUTHORIZED,
3227 svn_hash_sets(authz_results, full_path, result);
3228 svn_hash_sets(targets, full_path, NULL);
3232 lmb.results = apr_hash_make(pool);
3235 err = svn_repos_fs_unlock_many(b->repository->repos, targets,
3236 break_lock, lock_many_cb, &lmb,
3239 /* Return results in the same order as the paths were supplied. */
3240 for (i = 0; i < unlock_tokens->nelts; ++i)
3242 const char *path, *token, *full_path;
3243 svn_ra_svn__item_t *item = &SVN_RA_SVN__LIST_ITEM(unlock_tokens, i);
3244 struct lock_result_t *result;
3246 svn_pool_clear(subpool);
3248 write_err = svn_ra_svn__parse_tuple(&item->u.list, "c(?c)",
3253 full_path = svn_fspath__join(b->repository->fs_path->data,
3254 svn_relpath_canonicalize(path, subpool),
3257 result = svn_hash_gets(lmb.results, full_path);
3259 result = svn_hash_gets(authz_results, full_path);
3262 /* No result? Something really odd happened, create a
3263 placeholder error so that any other results can be
3264 reported in the correct order. */
3265 result = apr_palloc(pool, sizeof(struct lock_result_t));
3266 result->err = svn_error_createf(SVN_ERR_FS_LOCK_OPERATION_FAILED, 0,
3267 _("No result for '%s'."), path);
3268 svn_hash_sets(lmb.results, full_path, result);
3272 write_err = svn_ra_svn__write_cmd_failure(conn, pool, result->err);
3274 write_err = svn_ra_svn__write_tuple(conn, subpool, "w(c)", "success",
3280 clear_lock_result_hash(authz_results, subpool);
3281 clear_lock_result_hash(lmb.results, subpool);
3283 svn_pool_destroy(subpool);
3286 write_err = svn_ra_svn__write_word(conn, pool, "done");
3289 svn_error_clear(err);
3291 SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, ""));
3293 return SVN_NO_ERROR;
3296 static svn_error_t *
3297 get_lock(svn_ra_svn_conn_t *conn,
3299 svn_ra_svn__list_t *params,
3302 server_baton_t *b = baton;
3304 const char *full_path;
3307 SVN_ERR(svn_ra_svn__parse_tuple(params, "c", &path));
3309 full_path = svn_fspath__join(b->repository->fs_path->data,
3310 svn_relpath_canonicalize(path, pool), pool);
3312 SVN_ERR(must_have_access(conn, pool, b, svn_authz_read,
3314 SVN_ERR(log_command(b, conn, pool, "get-lock %s",
3315 svn_path_uri_encode(full_path, pool)));
3317 SVN_CMD_ERR(svn_fs_get_lock(&l, b->repository->fs, full_path, pool));
3319 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w((!", "success"));
3321 SVN_ERR(write_lock(conn, pool, l));
3322 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!))"));
3324 return SVN_NO_ERROR;
3327 static svn_error_t *
3328 get_locks(svn_ra_svn_conn_t *conn,
3330 svn_ra_svn__list_t *params,
3333 server_baton_t *b = baton;
3335 const char *full_path;
3336 const char *depth_word;
3339 apr_hash_index_t *hi;
3346 SVN_ERR(svn_ra_svn__parse_tuple(params, "c?(?w)", &path, &depth_word));
3348 depth = depth_word ? svn_depth_from_word(depth_word) : svn_depth_infinity;
3349 if ((depth != svn_depth_empty) &&
3350 (depth != svn_depth_files) &&
3351 (depth != svn_depth_immediates) &&
3352 (depth != svn_depth_infinity))
3354 err = svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL,
3355 "Invalid 'depth' specified in get-locks request");
3356 return log_fail_and_flush(err, b, conn, pool);
3359 full_path = svn_fspath__join(b->repository->fs_path->data,
3360 svn_relpath_canonicalize(path, pool), pool);
3362 SVN_ERR(trivial_auth_request(conn, pool, b));
3364 SVN_ERR(log_command(b, conn, pool, "get-locks %s",
3365 svn_path_uri_encode(full_path, pool)));
3366 SVN_CMD_ERR(svn_repos_fs_get_locks2(&locks, b->repository->repos,
3368 authz_check_access_cb_func(b), &ab,
3371 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w((!", "success"));
3372 for (hi = apr_hash_first(pool, locks); hi; hi = apr_hash_next(hi))
3374 svn_lock_t *l = apr_hash_this_val(hi);
3376 SVN_ERR(write_lock(conn, pool, l));
3378 SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!))"));
3380 return SVN_NO_ERROR;
3383 static svn_error_t *replay_one_revision(svn_ra_svn_conn_t *conn,
3386 svn_revnum_t low_water_mark,
3387 svn_boolean_t send_deltas,
3390 const svn_delta_editor_t *editor;
3392 svn_fs_root_t *root;
3399 SVN_ERR(log_command(b, conn, pool,
3400 svn_log__replay(b->repository->fs_path->data, rev,
3403 svn_ra_svn_get_editor(&editor, &edit_baton, conn, pool, NULL, NULL);
3405 err = svn_fs_revision_root(&root, b->repository->fs, rev, pool);
3408 err = svn_repos_replay2(root, b->repository->fs_path->data,
3409 low_water_mark, send_deltas, editor, edit_baton,
3410 authz_check_access_cb_func(b), &ab, pool);
3413 svn_error_clear(editor->abort_edit(edit_baton, pool));
3416 return svn_ra_svn__write_cmd_finish_replay(conn, pool);
3419 static svn_error_t *
3420 replay(svn_ra_svn_conn_t *conn,
3422 svn_ra_svn__list_t *params,
3425 svn_revnum_t rev, low_water_mark;
3426 svn_boolean_t send_deltas;
3427 server_baton_t *b = baton;
3429 SVN_ERR(svn_ra_svn__parse_tuple(params, "rrb", &rev, &low_water_mark,
3432 SVN_ERR(trivial_auth_request(conn, pool, b));
3434 SVN_ERR(replay_one_revision(conn, b, rev, low_water_mark,
3435 send_deltas, pool));
3437 SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, ""));
3439 return SVN_NO_ERROR;
3442 static svn_error_t *
3443 replay_range(svn_ra_svn_conn_t *conn,
3445 svn_ra_svn__list_t *params,
3448 svn_revnum_t start_rev, end_rev, rev, low_water_mark;
3449 svn_boolean_t send_deltas;
3450 server_baton_t *b = baton;
3451 apr_pool_t *iterpool;
3457 SVN_ERR(svn_ra_svn__parse_tuple(params, "rrrb", &start_rev,
3458 &end_rev, &low_water_mark,
3461 SVN_ERR(trivial_auth_request(conn, pool, b));
3463 iterpool = svn_pool_create(pool);
3464 for (rev = start_rev; rev <= end_rev; rev++)
3468 svn_pool_clear(iterpool);
3470 SVN_CMD_ERR(svn_repos_fs_revision_proplist(&props,
3471 b->repository->repos, rev,
3472 authz_check_access_cb_func(b),
3475 SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "w(!", "revprops"));
3476 SVN_ERR(svn_ra_svn__write_proplist(conn, iterpool, props));
3477 SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "!)"));
3479 SVN_ERR(replay_one_revision(conn, b, rev, low_water_mark,
3480 send_deltas, iterpool));
3483 svn_pool_destroy(iterpool);
3485 SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, ""));
3487 return SVN_NO_ERROR;
3490 static svn_error_t *
3491 get_deleted_rev(svn_ra_svn_conn_t *conn,
3493 svn_ra_svn__list_t *params,
3496 server_baton_t *b = baton;
3497 const char *path, *full_path;
3498 svn_revnum_t peg_revision;
3499 svn_revnum_t end_revision;
3500 svn_revnum_t revision_deleted;
3502 SVN_ERR(svn_ra_svn__parse_tuple(params, "crr",
3503 &path, &peg_revision, &end_revision));
3504 full_path = svn_fspath__join(b->repository->fs_path->data,
3505 svn_relpath_canonicalize(path, pool), pool);
3506 SVN_ERR(log_command(b, conn, pool, "get-deleted-rev"));
3507 SVN_ERR(trivial_auth_request(conn, pool, b));
3508 SVN_ERR(svn_repos_deleted_rev(b->repository->fs, full_path, peg_revision,
3509 end_revision, &revision_deleted, pool));
3510 SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, "r", revision_deleted));
3511 return SVN_NO_ERROR;
3514 static svn_error_t *
3515 get_inherited_props(svn_ra_svn_conn_t *conn,
3517 svn_ra_svn__list_t *params,
3520 server_baton_t *b = baton;
3521 const char *path, *full_path;
3523 svn_fs_root_t *root;
3524 apr_array_header_t *inherited_props;
3526 apr_pool_t *iterpool = svn_pool_create(pool);
3528 svn_node_kind_t node_kind;
3533 /* Parse arguments. */
3534 SVN_ERR(svn_ra_svn__parse_tuple(params, "c(?r)", &path, &rev));
3536 full_path = svn_fspath__join(b->repository->fs_path->data,
3537 svn_relpath_canonicalize(path, iterpool),
3540 /* Check authorizations */
3541 SVN_ERR(must_have_access(conn, iterpool, b, svn_authz_read,
3544 SVN_ERR(log_command(b, conn, pool, "%s",
3545 svn_log__get_inherited_props(full_path, rev,
3548 /* Fetch the properties and a stream for the contents. */
3549 SVN_CMD_ERR(svn_fs_revision_root(&root, b->repository->fs, rev, iterpool));
3550 SVN_CMD_ERR(svn_fs_check_path(&node_kind, root, full_path, pool));
3551 if (node_kind == svn_node_none)
3553 SVN_CMD_ERR(svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL,
3554 _("'%s' path not found"), full_path));
3556 SVN_CMD_ERR(get_props(NULL, &inherited_props, &ab, root, full_path, pool));
3558 /* Send successful command response with revision and props. */
3559 SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "w(!", "success"));
3561 SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "!(?!"));
3563 for (i = 0; i < inherited_props->nelts; i++)
3565 svn_prop_inherited_item_t *iprop =
3566 APR_ARRAY_IDX(inherited_props, i, svn_prop_inherited_item_t *);
3568 svn_pool_clear(iterpool);
3569 SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "!(c(!",
3570 iprop->path_or_url));
3571 SVN_ERR(svn_ra_svn__write_proplist(conn, iterpool, iprop->prop_hash));
3572 SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "!))!",
3573 iprop->path_or_url));
3576 SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "!))"));
3577 svn_pool_destroy(iterpool);
3578 return SVN_NO_ERROR;
3581 /* Baton type to be used with list_receiver. */
3582 typedef struct list_receiver_baton_t
3584 /* Send the data through this connection. */
3585 svn_ra_svn_conn_t *conn;
3587 /* Send the field selected by these flags. */
3588 apr_uint32_t dirent_fields;
3589 } list_receiver_baton_t;
3591 /* Implements svn_repos_dirent_receiver_t, sending DIRENT and PATH to the
3592 * client. BATON must be a list_receiver_baton_t. */
3593 static svn_error_t *
3594 list_receiver(const char *path,
3595 svn_dirent_t *dirent,
3599 list_receiver_baton_t *b = baton;
3600 return svn_error_trace(svn_ra_svn__write_dirent(b->conn, pool, path, dirent,
3604 static svn_error_t *
3605 list(svn_ra_svn_conn_t *conn,
3607 svn_ra_svn__list_t *params,
3610 server_baton_t *b = baton;
3611 const char *path, *full_path;
3614 apr_array_header_t *patterns = NULL;
3615 svn_fs_root_t *root;
3616 const char *depth_word;
3617 svn_boolean_t path_info_only;
3618 svn_ra_svn__list_t *dirent_fields_list = NULL;
3619 svn_ra_svn__list_t *patterns_list = NULL;
3621 list_receiver_baton_t rb;
3622 svn_error_t *err, *write_err;
3628 /* Read the command parameters. */
3629 SVN_ERR(svn_ra_svn__parse_tuple(params, "c(?r)w?l?l", &path, &rev,
3630 &depth_word, &dirent_fields_list,
3634 SVN_ERR(parse_dirent_fields(&rb.dirent_fields, dirent_fields_list));
3636 depth = svn_depth_from_word(depth_word);
3637 full_path = svn_fspath__join(b->repository->fs_path->data,
3638 svn_relpath_canonicalize(path, pool), pool);
3640 /* Read the patterns list. */
3643 patterns = apr_array_make(pool, 0, sizeof(const char *));
3644 for (i = 0; i < patterns_list->nelts; ++i)
3646 svn_ra_svn__item_t *elt = &SVN_RA_SVN__LIST_ITEM(patterns_list, i);
3648 if (elt->kind != SVN_RA_SVN_STRING)
3649 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
3650 "Pattern field not a string");
3652 APR_ARRAY_PUSH(patterns, const char *) = elt->u.string.data;
3656 /* Check authorizations */
3657 SVN_ERR(must_have_access(conn, pool, b, svn_authz_read,
3660 if (!SVN_IS_VALID_REVNUM(rev))
3661 SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->repository->fs, pool));
3663 SVN_ERR(log_command(b, conn, pool, "%s",
3664 svn_log__list(full_path, rev, patterns, depth,
3665 rb.dirent_fields, pool)));
3667 /* Fetch the root of the appropriate revision. */
3668 SVN_CMD_ERR(svn_fs_revision_root(&root, b->repository->fs, rev, pool));
3670 /* Fetch the directory entries if requested and send them immediately. */
3671 path_info_only = (rb.dirent_fields & ~SVN_DIRENT_KIND) == 0;
3672 err = svn_repos_list(root, full_path, patterns, depth, path_info_only,
3673 authz_check_access_cb_func(b), &ab, list_receiver,
3674 &rb, NULL, NULL, pool);
3677 /* Finish response. */
3678 write_err = svn_ra_svn__write_word(conn, pool, "done");
3681 svn_error_clear(err);
3686 return svn_error_trace(svn_ra_svn__write_cmd_response(conn, pool, ""));
3689 static const svn_ra_svn__cmd_entry_t main_commands[] = {
3690 { "reparent", reparent },
3691 { "get-latest-rev", get_latest_rev },
3692 { "get-dated-rev", get_dated_rev },
3693 { "change-rev-prop", change_rev_prop },
3694 { "change-rev-prop2",change_rev_prop2 },
3695 { "rev-proplist", rev_proplist },
3696 { "rev-prop", rev_prop },
3697 { "commit", commit },
3698 { "get-file", get_file },
3699 { "get-dir", get_dir },
3700 { "update", update },
3701 { "switch", switch_cmd },
3702 { "status", status },
3704 { "get-mergeinfo", get_mergeinfo },
3706 { "check-path", check_path },
3707 { "stat", stat_cmd },
3708 { "get-locations", get_locations },
3709 { "get-location-segments", get_location_segments },
3710 { "get-file-revs", get_file_revs },
3712 { "lock-many", lock_many },
3713 { "unlock", unlock },
3714 { "unlock-many", unlock_many },
3715 { "get-lock", get_lock },
3716 { "get-locks", get_locks },
3717 { "replay", replay },
3718 { "replay-range", replay_range },
3719 { "get-deleted-rev", get_deleted_rev },
3720 { "get-iprops", get_inherited_props },
3725 /* Skip past the scheme part of a URL, including the tunnel specification
3726 * if present. Return NULL if the scheme part is invalid for ra_svn. */
3727 static const char *skip_scheme_part(const char *url)
3729 if (strncmp(url, "svn", 3) != 0)
3733 url += strcspn(url, ":");
3734 if (strncmp(url, "://", 3) != 0)
3739 /* Check that PATH is a valid repository path, meaning it doesn't contain any
3741 NOTE: This is similar to svn_path_is_backpath_present, but that function
3742 assumes the path separator is '/'. This function also checks for
3743 segments delimited by the local path separator. */
3744 static svn_boolean_t
3745 repos_path_valid(const char *path)
3747 const char *s = path;
3751 /* Scan for the end of the segment. */
3752 while (*path && *path != '/' && *path != SVN_PATH_LOCAL_SEPARATOR)
3755 /* Check for '..'. */
3757 /* On Windows, don't allow sequences of more than one character
3758 consisting of just dots and spaces. Win32 functions treat
3759 paths such as ".. " and "......." inconsistently. Make sure
3760 no one can escape out of the root. */
3761 if (path - s >= 2 && strspn(s, ". ") == (size_t)(path - s))
3764 if (path - s == 2 && s[0] == '.' && s[1] == '.')
3768 /* Skip all separators. */
3769 while (*path && (*path == '/' || *path == SVN_PATH_LOCAL_SEPARATOR))
3777 /* Look for the repository given by URL, using ROOT as the virtual
3778 * repository root. If we find one, fill in the repos, fs, repos_url,
3779 * and fs_path fields of REPOSITORY. VHOST and READ_ONLY flags are the
3780 * same as in the server baton.
3782 * CONFIG_POOL shall be used to load config objects.
3784 * Use SCRATCH_POOL for temporary allocations.
3787 static svn_error_t *
3788 find_repos(const char *url,
3790 svn_boolean_t vhost,
3791 svn_boolean_t read_only,
3793 repository_t *repository,
3794 svn_repos__config_pool_t *config_pool,
3795 apr_hash_t *fs_config,
3796 apr_pool_t *result_pool,
3797 apr_pool_t *scratch_pool)
3799 const char *path, *full_path, *fs_path, *hooks_env;
3800 svn_stringbuf_t *url_buf;
3801 svn_boolean_t sasl_requested;
3803 /* Skip past the scheme and authority part. */
3804 path = skip_scheme_part(url);
3806 return svn_error_createf(SVN_ERR_BAD_URL, NULL,
3807 "Non-svn URL passed to svn server: '%s'", url);
3811 path = strchr(path, '/');
3815 path = svn_relpath_canonicalize(path, scratch_pool);
3816 path = svn_path_uri_decode(path, scratch_pool);
3818 /* Ensure that it isn't possible to escape the root by disallowing
3820 if (!repos_path_valid(path))
3821 return svn_error_create(SVN_ERR_BAD_FILENAME, NULL,
3822 "Couldn't determine repository path");
3824 /* Join the server-configured root with the client path. */
3825 full_path = svn_dirent_join(svn_dirent_canonicalize(root, scratch_pool),
3826 path, scratch_pool);
3828 /* Search for a repository in the full path. */
3829 repository->repos_root = svn_repos_find_root_path(full_path, result_pool);
3830 if (!repository->repos_root)
3831 return svn_error_createf(SVN_ERR_RA_SVN_REPOS_NOT_FOUND, NULL,
3832 "No repository found in '%s'", url);
3834 /* Open the repository and fill in b with the resulting information. */
3835 SVN_ERR(svn_repos_open3(&repository->repos, repository->repos_root,
3836 fs_config, result_pool, scratch_pool));
3837 SVN_ERR(svn_repos_remember_client_capabilities(repository->repos,
3838 repository->capabilities));
3839 repository->fs = svn_repos_fs(repository->repos);
3840 fs_path = full_path + strlen(repository->repos_root);
3841 repository->fs_path = svn_stringbuf_create(*fs_path ? fs_path : "/",
3843 url_buf = svn_stringbuf_create(url, result_pool);
3844 svn_path_remove_components(url_buf,
3845 svn_path_component_count(repository->fs_path->data));
3846 repository->repos_url = url_buf->data;
3847 repository->authz_repos_name = svn_dirent_is_child(root,
3848 repository->repos_root,
3850 if (repository->authz_repos_name == NULL)
3851 repository->repos_name = svn_dirent_basename(repository->repos_root,
3854 repository->repos_name = repository->authz_repos_name;
3855 repository->repos_name = svn_path_uri_encode(repository->repos_name,
3858 /* If the svnserve configuration has not been loaded then load it from the
3862 repository->base = svn_repos_conf_dir(repository->repos, result_pool);
3864 SVN_ERR(svn_repos__config_pool_get(&cfg, config_pool,
3865 svn_repos_svnserve_conf
3866 (repository->repos, result_pool),
3867 FALSE, repository->repos,
3871 SVN_ERR(load_pwdb_config(repository, cfg, config_pool, result_pool));
3872 SVN_ERR(load_authz_config(repository, repository->repos_root, cfg,
3875 /* Should we use Cyrus SASL? */
3876 SVN_ERR(svn_config_get_bool(cfg, &sasl_requested,
3877 SVN_CONFIG_SECTION_SASL,
3878 SVN_CONFIG_OPTION_USE_SASL, FALSE));
3881 #ifdef SVN_HAVE_SASL
3884 repository->use_sasl = sasl_requested;
3886 svn_config_get(cfg, &val, SVN_CONFIG_SECTION_SASL,
3887 SVN_CONFIG_OPTION_MIN_SSF, "0");
3888 SVN_ERR(svn_cstring_atoui(&repository->min_ssf, val));
3890 svn_config_get(cfg, &val, SVN_CONFIG_SECTION_SASL,
3891 SVN_CONFIG_OPTION_MAX_SSF, "256");
3892 SVN_ERR(svn_cstring_atoui(&repository->max_ssf, val));
3893 #else /* !SVN_HAVE_SASL */
3894 return svn_error_createf(SVN_ERR_BAD_CONFIG_VALUE, NULL,
3895 _("SASL requested but not compiled in; "
3896 "set '%s' to 'false' or recompile "
3897 "svnserve with SASL support"),
3898 SVN_CONFIG_OPTION_USE_SASL);
3899 #endif /* SVN_HAVE_SASL */
3903 repository->use_sasl = FALSE;
3906 /* Use the repository UUID as the default realm. */
3907 SVN_ERR(svn_fs_get_uuid(repository->fs, &repository->realm, scratch_pool));
3908 svn_config_get(cfg, &repository->realm, SVN_CONFIG_SECTION_GENERAL,
3909 SVN_CONFIG_OPTION_REALM, repository->realm);
3910 repository->realm = apr_pstrdup(result_pool, repository->realm);
3912 /* Make sure it's possible for the client to authenticate. Note
3913 that this doesn't take into account any authz configuration read
3914 above, because we can't know about access it grants until paths
3915 are given by the client. */
3916 set_access(repository, cfg, read_only);
3918 /* Configure hook script environment variables. */
3919 svn_config_get(cfg, &hooks_env, SVN_CONFIG_SECTION_GENERAL,
3920 SVN_CONFIG_OPTION_HOOKS_ENV, NULL);
3922 hooks_env = svn_dirent_internal_style(hooks_env, scratch_pool);
3924 SVN_ERR(svn_repos_hooks_setenv(repository->repos, hooks_env, scratch_pool));
3925 repository->hooks_env = apr_pstrdup(result_pool, hooks_env);
3927 return SVN_NO_ERROR;
3930 /* Compute the authentication name EXTERNAL should be able to get, if any. */
3931 static const char *get_tunnel_user(serve_params_t *params, apr_pool_t *pool)
3933 /* Only offer EXTERNAL for connections tunneled over a login agent. */
3934 if (!params->tunnel)
3937 /* If a tunnel user was provided on the command line, use that. */
3938 if (params->tunnel_user)
3939 return params->tunnel_user;
3941 return svn_user_get_name(pool);
3945 fs_warning_func(void *baton, svn_error_t *err)
3947 fs_warning_baton_t *b = baton;
3948 log_error(err, b->server);
3951 /* Return the normalized repository-relative path for the given PATH
3952 * (may be a URL, full path or relative path) and fs contained in the
3953 * server baton BATON. Allocate the result in POOL.
3956 get_normalized_repo_rel_path(void *baton,
3960 server_baton_t *sb = baton;
3962 if (svn_path_is_url(path))
3964 /* This is a copyfrom URL. */
3965 path = svn_uri_skip_ancestor(sb->repository->repos_url, path, pool);
3966 path = svn_fspath__canonicalize(path, pool);
3970 /* This is a base-relative path. */
3971 if ((path)[0] != '/')
3972 /* Get an absolute path for use in the FS. */
3973 path = svn_fspath__join(sb->repository->fs_path->data, path, pool);
3979 /* Get the revision root for REVISION in fs given by server baton BATON
3980 * and return it in *FS_ROOT. Use HEAD if REVISION is SVN_INVALID_REVNUM.
3981 * Use POOL for allocations.
3983 static svn_error_t *
3984 get_revision_root(svn_fs_root_t **fs_root,
3986 svn_revnum_t revision,
3989 server_baton_t *sb = baton;
3991 if (!SVN_IS_VALID_REVNUM(revision))
3992 SVN_ERR(svn_fs_youngest_rev(&revision, sb->repository->fs, pool));
3994 SVN_ERR(svn_fs_revision_root(fs_root, sb->repository->fs, revision, pool));
3996 return SVN_NO_ERROR;
3999 static svn_error_t *
4000 fetch_props_func(apr_hash_t **props,
4003 svn_revnum_t base_revision,
4004 apr_pool_t *result_pool,
4005 apr_pool_t *scratch_pool)
4007 svn_fs_root_t *fs_root;
4010 path = get_normalized_repo_rel_path(baton, path, scratch_pool);
4011 SVN_ERR(get_revision_root(&fs_root, baton, base_revision, scratch_pool));
4013 err = svn_fs_node_proplist(props, fs_root, path, result_pool);
4014 if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND)
4016 svn_error_clear(err);
4017 *props = apr_hash_make(result_pool);
4018 return SVN_NO_ERROR;
4021 return svn_error_trace(err);
4023 return SVN_NO_ERROR;
4026 static svn_error_t *
4027 fetch_kind_func(svn_node_kind_t *kind,
4030 svn_revnum_t base_revision,
4031 apr_pool_t *scratch_pool)
4033 svn_fs_root_t *fs_root;
4035 path = get_normalized_repo_rel_path(baton, path, scratch_pool);
4036 SVN_ERR(get_revision_root(&fs_root, baton, base_revision, scratch_pool));
4038 SVN_ERR(svn_fs_check_path(kind, fs_root, path, scratch_pool));
4040 return SVN_NO_ERROR;
4043 static svn_error_t *
4044 fetch_base_func(const char **filename,
4047 svn_revnum_t base_revision,
4048 apr_pool_t *result_pool,
4049 apr_pool_t *scratch_pool)
4051 svn_stream_t *contents;
4052 svn_stream_t *file_stream;
4053 const char *tmp_filename;
4054 svn_fs_root_t *fs_root;
4057 path = get_normalized_repo_rel_path(baton, path, scratch_pool);
4058 SVN_ERR(get_revision_root(&fs_root, baton, base_revision, scratch_pool));
4060 err = svn_fs_file_contents(&contents, fs_root, path, scratch_pool);
4061 if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND)
4063 svn_error_clear(err);
4065 return SVN_NO_ERROR;
4068 return svn_error_trace(err);
4069 SVN_ERR(svn_stream_open_unique(&file_stream, &tmp_filename, NULL,
4070 svn_io_file_del_on_pool_cleanup,
4071 scratch_pool, scratch_pool));
4072 SVN_ERR(svn_stream_copy3(contents, file_stream, NULL, NULL, scratch_pool));
4074 *filename = apr_pstrdup(result_pool, tmp_filename);
4076 return SVN_NO_ERROR;
4080 get_client_info(svn_ra_svn_conn_t *conn,
4081 serve_params_t *params,
4084 client_info_t *client_info = apr_pcalloc(pool, sizeof(*client_info));
4086 client_info->tunnel = params->tunnel;
4087 client_info->tunnel_user = get_tunnel_user(params, pool);
4088 client_info->user = NULL;
4089 client_info->authz_user = NULL;
4090 client_info->remote_host = svn_ra_svn_conn_remote_host(conn);
4095 /* Construct the server baton for CONN using PARAMS and return it in *BATON.
4096 * It's lifetime is the same as that of CONN. SCRATCH_POOL
4098 static svn_error_t *
4099 construct_server_baton(server_baton_t **baton,
4100 svn_ra_svn_conn_t *conn,
4101 serve_params_t *params,
4102 apr_pool_t *scratch_pool)
4104 svn_error_t *err, *io_err;
4106 const char *client_url, *ra_client_string, *client_string;
4107 svn_ra_svn__list_t *caplist;
4108 apr_pool_t *conn_pool = svn_ra_svn__get_pool(conn);
4109 server_baton_t *b = apr_pcalloc(conn_pool, sizeof(*b));
4110 fs_warning_baton_t *warn_baton;
4111 svn_stringbuf_t *cap_log = svn_stringbuf_create_empty(scratch_pool);
4113 b->repository = apr_pcalloc(conn_pool, sizeof(*b->repository));
4114 b->repository->username_case = params->username_case;
4115 b->repository->base = params->base;
4116 b->repository->pwdb = NULL;
4117 b->repository->authzdb = NULL;
4118 b->repository->realm = NULL;
4119 b->repository->use_sasl = FALSE;
4121 b->read_only = params->read_only;
4122 b->pool = conn_pool;
4123 b->vhost = params->vhost;
4125 b->logger = params->logger;
4126 b->client_info = get_client_info(conn, params, conn_pool);
4128 /* Send greeting. We don't support version 1 any more, so we can
4129 * send an empty mechlist. */
4130 if (params->compression_level > 0)
4131 SVN_ERR(svn_ra_svn__write_cmd_response(conn, scratch_pool,
4132 "nn()(wwwwwwwwwwwww)",
4133 (apr_uint64_t) 2, (apr_uint64_t) 2,
4134 SVN_RA_SVN_CAP_EDIT_PIPELINE,
4135 SVN_RA_SVN_CAP_SVNDIFF1,
4136 SVN_RA_SVN_CAP_SVNDIFF2_ACCEPTED,
4137 SVN_RA_SVN_CAP_ABSENT_ENTRIES,
4138 SVN_RA_SVN_CAP_COMMIT_REVPROPS,
4139 SVN_RA_SVN_CAP_DEPTH,
4140 SVN_RA_SVN_CAP_LOG_REVPROPS,
4141 SVN_RA_SVN_CAP_ATOMIC_REVPROPS,
4142 SVN_RA_SVN_CAP_PARTIAL_REPLAY,
4143 SVN_RA_SVN_CAP_INHERITED_PROPS,
4144 SVN_RA_SVN_CAP_EPHEMERAL_TXNPROPS,
4145 SVN_RA_SVN_CAP_GET_FILE_REVS_REVERSE,
4149 SVN_ERR(svn_ra_svn__write_cmd_response(conn, scratch_pool,
4150 "nn()(wwwwwwwwwww)",
4151 (apr_uint64_t) 2, (apr_uint64_t) 2,
4152 SVN_RA_SVN_CAP_EDIT_PIPELINE,
4153 SVN_RA_SVN_CAP_ABSENT_ENTRIES,
4154 SVN_RA_SVN_CAP_COMMIT_REVPROPS,
4155 SVN_RA_SVN_CAP_DEPTH,
4156 SVN_RA_SVN_CAP_LOG_REVPROPS,
4157 SVN_RA_SVN_CAP_ATOMIC_REVPROPS,
4158 SVN_RA_SVN_CAP_PARTIAL_REPLAY,
4159 SVN_RA_SVN_CAP_INHERITED_PROPS,
4160 SVN_RA_SVN_CAP_EPHEMERAL_TXNPROPS,
4161 SVN_RA_SVN_CAP_GET_FILE_REVS_REVERSE,
4165 /* Read client response, which we assume to be in version 2 format:
4166 * version, capability list, and client URL; then we do an auth
4168 SVN_ERR(svn_ra_svn__read_tuple(conn, scratch_pool, "nlc?c(?c)",
4169 &ver, &caplist, &client_url,
4173 return SVN_NO_ERROR;
4175 client_url = svn_uri_canonicalize(client_url, conn_pool);
4176 SVN_ERR(svn_ra_svn__set_capabilities(conn, caplist));
4178 /* All released versions of Subversion support edit-pipeline,
4179 * so we do not accept connections from clients that do not. */
4180 if (! svn_ra_svn_has_capability(conn, SVN_RA_SVN_CAP_EDIT_PIPELINE))
4181 return SVN_NO_ERROR;
4183 /* find_repos needs the capabilities as a list of words (eventually
4184 they get handed to the start-commit hook). While we could add a
4185 new interface to re-retrieve them from conn and convert the
4186 result to a list, it's simpler to just convert caplist by hand
4187 here, since we already have it and turning 'svn_ra_svn__item_t's
4188 into 'const char *'s is pretty easy.
4190 We only record capabilities we care about. The client may report
4191 more (because it doesn't know what the server cares about). */
4194 svn_ra_svn__item_t *item;
4196 b->repository->capabilities = apr_array_make(conn_pool, 1,
4197 sizeof(const char *));
4198 for (i = 0; i < caplist->nelts; i++)
4200 static const svn_string_t str_cap_mergeinfo
4201 = SVN__STATIC_STRING(SVN_RA_SVN_CAP_MERGEINFO);
4203 item = &SVN_RA_SVN__LIST_ITEM(caplist, i);
4204 /* ra_svn_set_capabilities() already type-checked for us */
4205 if (svn_string_compare(&item->u.word, &str_cap_mergeinfo))
4207 APR_ARRAY_PUSH(b->repository->capabilities, const char *)
4208 = SVN_RA_CAPABILITY_MERGEINFO;
4210 /* Save for operational log. */
4211 if (cap_log->len > 0)
4212 svn_stringbuf_appendcstr(cap_log, " ");
4213 svn_stringbuf_appendcstr(cap_log, item->u.word.data);
4217 err = handle_config_error(find_repos(client_url, params->root, b->vhost,
4218 b->read_only, params->cfg,
4219 b->repository, params->config_pool,
4221 conn_pool, scratch_pool),
4225 if (b->repository->anon_access == NO_ACCESS
4226 && (b->repository->auth_access == NO_ACCESS
4227 || (!b->client_info->tunnel_user && !b->repository->pwdb
4228 && !b->repository->use_sasl)))
4229 err = error_create_and_log(SVN_ERR_RA_NOT_AUTHORIZED, NULL,
4230 "No access allowed to this repository",
4235 SVN_ERR(auth_request(conn, scratch_pool, b, READ_ACCESS, FALSE));
4236 if (current_access(b) == NO_ACCESS)
4237 err = error_create_and_log(SVN_ERR_RA_NOT_AUTHORIZED, NULL,
4238 "Not authorized for access", b);
4243 io_err = svn_ra_svn__write_cmd_failure(conn, scratch_pool, err);
4244 svn_error_clear(err);
4246 return svn_ra_svn__flush(conn, scratch_pool);
4249 SVN_ERR(svn_fs_get_uuid(b->repository->fs, &b->repository->uuid,
4252 /* We can't claim mergeinfo capability until we know whether the
4253 repository supports mergeinfo (i.e., is not a 1.4 repository),
4254 but we don't get the repository url from the client until after
4255 we've already sent the initial list of server capabilities. So
4256 we list repository capabilities here, in our first response after
4257 the client has sent the url. */
4259 svn_boolean_t supports_mergeinfo;
4260 SVN_ERR(svn_repos_has_capability(b->repository->repos,
4261 &supports_mergeinfo,
4262 SVN_REPOS_CAPABILITY_MERGEINFO,
4265 SVN_ERR(svn_ra_svn__write_tuple(conn, scratch_pool, "w(cc(!",
4266 "success", b->repository->uuid,
4267 b->repository->repos_url));
4268 if (supports_mergeinfo)
4269 SVN_ERR(svn_ra_svn__write_word(conn, scratch_pool,
4270 SVN_RA_SVN_CAP_MERGEINFO));
4271 SVN_ERR(svn_ra_svn__write_tuple(conn, scratch_pool, "!))"));
4272 SVN_ERR(svn_ra_svn__flush(conn, scratch_pool));
4276 if (ra_client_string == NULL || ra_client_string[0] == '\0')
4277 ra_client_string = "-";
4279 ra_client_string = svn_path_uri_encode(ra_client_string, scratch_pool);
4280 if (client_string == NULL || client_string[0] == '\0')
4281 client_string = "-";
4283 client_string = svn_path_uri_encode(client_string, scratch_pool);
4284 SVN_ERR(log_command(b, conn, scratch_pool,
4285 "open %" APR_UINT64_T_FMT " cap=(%s) %s %s %s",
4287 svn_path_uri_encode(b->repository->fs_path->data,
4289 ra_client_string, client_string));
4291 warn_baton = apr_pcalloc(conn_pool, sizeof(*warn_baton));
4292 warn_baton->server = b;
4293 warn_baton->conn = conn;
4294 svn_fs_set_warning_func(b->repository->fs, fs_warning_func, warn_baton);
4296 /* Set up editor shims. */
4298 svn_delta_shim_callbacks_t *callbacks =
4299 svn_delta_shim_callbacks_default(conn_pool);
4301 callbacks->fetch_base_func = fetch_base_func;
4302 callbacks->fetch_props_func = fetch_props_func;
4303 callbacks->fetch_kind_func = fetch_kind_func;
4304 callbacks->fetch_baton = b;
4306 SVN_ERR(svn_ra_svn__set_shim_callbacks(conn, callbacks));
4311 return SVN_NO_ERROR;
4315 serve_interruptable(svn_boolean_t *terminate_p,
4316 connection_t *connection,
4317 svn_boolean_t (* is_busy)(connection_t *),
4320 svn_boolean_t terminate = FALSE;
4321 svn_error_t *err = NULL;
4322 const svn_ra_svn__cmd_entry_t *command;
4323 apr_pool_t *iterpool = svn_pool_create(pool);
4325 /* Prepare command parser. */
4326 apr_hash_t *cmd_hash = apr_hash_make(pool);
4327 for (command = main_commands; command->cmdname; command++)
4328 svn_hash_sets(cmd_hash, command->cmdname, command);
4330 /* Auto-initialize connection */
4331 if (! connection->conn)
4335 /* Enable TCP keep-alives on the socket so we time out when
4336 * the connection breaks due to network-layer problems.
4337 * If the peer has dropped the connection due to a network partition
4338 * or a crash, or if the peer no longer considers the connection
4339 * valid because we are behind a NAT and our public IP has changed,
4340 * it will respond to the keep-alive probe with a RST instead of an
4341 * acknowledgment segment, which will cause svn to abort the session
4342 * even while it is currently blocked waiting for data from the peer. */
4343 ar = apr_socket_opt_set(connection->usock, APR_SO_KEEPALIVE, 1);
4346 /* It's not a fatal error if we cannot enable keep-alives. */
4349 /* create the connection, configure ports etc. */
4351 = svn_ra_svn_create_conn5(connection->usock, NULL, NULL,
4352 connection->params->compression_level,
4353 connection->params->zero_copy_limit,
4354 connection->params->error_check_interval,
4355 connection->params->max_request_size,
4356 connection->params->max_response_size,
4359 /* Construct server baton and open the repository for the first time. */
4360 err = construct_server_baton(&connection->baton, connection->conn,
4361 connection->params, pool);
4364 /* If we can't access the repo for some reason, end this connection. */
4368 /* Process incoming commands. */
4369 while (!terminate && !err)
4371 svn_pool_clear(iterpool);
4372 if (is_busy && is_busy(connection))
4374 svn_boolean_t has_command;
4376 /* If the server is busy, execute just one command and only if
4377 * there is one currently waiting in our receive buffers.
4379 err = svn_ra_svn__has_command(&has_command, &terminate,
4380 connection->conn, iterpool);
4381 if (!err && has_command)
4382 err = svn_ra_svn__handle_command(&terminate, cmd_hash,
4391 /* The server is not busy, thus let's serve whichever command
4392 * comes in next and whenever it comes in. This requires the
4393 * busy() callback test to return TRUE while there are still some
4396 err = svn_ra_svn__handle_command(&terminate, cmd_hash,
4403 /* error or normal end of session. Close the connection */
4404 svn_pool_destroy(iterpool);
4406 *terminate_p = terminate;
4408 return svn_error_trace(err);
4411 svn_error_t *serve(svn_ra_svn_conn_t *conn,
4412 serve_params_t *params,
4415 server_baton_t *baton = NULL;
4417 SVN_ERR(construct_server_baton(&baton, conn, params, pool));
4418 return svn_ra_svn__handle_commands2(conn, pool, main_commands, baton, FALSE);