Merge the svnserve-logging branch, in its entirety, to trunk, using the
[svn.git] / subversion / svnserve / serve.c
blob0bfd033b75e7a60eb9130590dc12962c80b009d7
1 /*
2 * serve.c : Functions for serving the Subversion protocol
4 * ====================================================================
5 * Copyright (c) 2000-2007 CollabNet. All rights reserved.
7 * This software is licensed as described in the file COPYING, which
8 * you should have received as part of this distribution. The terms
9 * are also available at http://subversion.tigris.org/license-1.html.
10 * If newer versions of this license are posted there, you may use a
11 * newer version instead, at your option.
13 * This software consists of voluntary contributions made by many
14 * individuals. For exact contribution history, see the revision
15 * history and logs, available at http://subversion.tigris.org/.
16 * ====================================================================
22 #include <limits.h> /* for UINT_MAX */
23 #include <stdarg.h>
25 #define APR_WANT_STRFUNC
26 #include <apr_want.h>
27 #include <apr_general.h>
28 #include <apr_lib.h>
29 #include <apr_strings.h>
30 #include <apr_md5.h>
32 #include "svn_compat.h"
33 #include "svn_private_config.h" /* For SVN_PATH_LOCAL_SEPARATOR */
34 #include "svn_types.h"
35 #include "svn_string.h"
36 #include "svn_pools.h"
37 #include "svn_error.h"
38 #include "svn_ra.h" /* for SVN_RA_CAPABILITY_* */
39 #include "svn_ra_svn.h"
40 #include "svn_repos.h"
41 #include "svn_path.h"
42 #include "svn_time.h"
43 #include "svn_md5.h"
44 #include "svn_config.h"
45 #include "svn_props.h"
46 #include "svn_mergeinfo.h"
47 #include "svn_user.h"
49 #include "private/svn_log.h"
50 #include "private/svn_mergeinfo_private.h"
52 #ifdef HAVE_UNISTD_H
53 #include <unistd.h> /* For getpid() */
54 #endif
56 #include "server.h"
58 typedef struct {
59 apr_pool_t *pool;
60 svn_revnum_t *new_rev;
61 const char **date;
62 const char **author;
63 const char **post_commit_err;
64 } commit_callback_baton_t;
66 typedef struct {
67 server_baton_t *sb;
68 const char *repos_url; /* Decoded repository URL. */
69 void *report_baton;
70 svn_error_t *err;
71 /* so update() can distinguish checkout from update in logging */
72 int entry_counter;
73 svn_boolean_t only_empty_entries;
74 /* for diff() logging */
75 svn_revnum_t *from_rev;
76 } report_driver_baton_t;
78 typedef struct {
79 const char *fs_path;
80 svn_ra_svn_conn_t *conn;
81 int stack_depth;
82 } log_baton_t;
84 typedef struct {
85 svn_ra_svn_conn_t *conn;
86 apr_pool_t *pool; /* Pool provided in the handler call. */
87 } file_revs_baton_t;
89 typedef struct {
90 server_baton_t *server;
91 svn_ra_svn_conn_t *conn;
92 apr_pool_t *pool;
93 } fs_warning_baton_t;
95 svn_error_t *load_configs(svn_config_t **cfg,
96 svn_config_t **pwdb,
97 svn_authz_t **authzdb,
98 const char *filename,
99 svn_boolean_t must_exist,
100 const char *base,
101 apr_pool_t *pool)
103 const char *pwdb_path, *authzdb_path;
104 svn_error_t *err;
106 SVN_ERR(svn_config_read(cfg, filename, must_exist, pool));
108 svn_config_get(*cfg, &pwdb_path, SVN_CONFIG_SECTION_GENERAL,
109 SVN_CONFIG_OPTION_PASSWORD_DB, NULL);
111 *pwdb = NULL;
112 if (pwdb_path)
114 pwdb_path = svn_path_join(base, pwdb_path, pool);
116 /* Because it may be possible to read the pwdb file with some
117 * access methods and not others, ignore errors reading the pwdb
118 * file and just don't present password authentication as an
119 * option. TODO: Log a warning in this case, when we have a way
120 * of doing logging. */
121 err = svn_config_read(pwdb, pwdb_path, TRUE, pool);
122 if (err && err->apr_err == SVN_ERR_BAD_FILENAME)
123 svn_error_clear(err);
124 else if (err)
125 return err;
128 /* Read authz configuration. */
129 svn_config_get(*cfg, &authzdb_path, SVN_CONFIG_SECTION_GENERAL,
130 SVN_CONFIG_OPTION_AUTHZ_DB, NULL);
131 if (authzdb_path)
133 authzdb_path = svn_path_join(base, authzdb_path, pool);
134 SVN_ERR(svn_repos_authz_read(authzdb, authzdb_path, TRUE, pool));
136 else
138 *authzdb = NULL;
141 return SVN_NO_ERROR;
144 /* Set *FS_PATH to the portion of URL that is the path within the
145 repository, if URL is inside REPOS_URL (if URL is not inside
146 REPOS_URL, then error, with the effect on *FS_PATH undefined).
148 If the resultant fs path would be the empty string (i.e., URL and
149 REPOS_URL are the same), then set *FS_PATH to "/".
151 Assume that REPOS_URL and URL are already URI-decoded. */
152 static svn_error_t *get_fs_path(const char *repos_url, const char *url,
153 const char **fs_path)
155 apr_size_t len;
157 len = strlen(repos_url);
158 if (strncmp(url, repos_url, len) != 0)
159 return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL,
160 "'%s' is not the same repository as '%s'",
161 url, repos_url);
162 *fs_path = url + len;
163 if (! **fs_path)
164 *fs_path = "/";
166 return SVN_NO_ERROR;
169 static void
170 log_fs_warning(void *baton, svn_error_t *err)
172 fs_warning_baton_t *b = baton;
173 server_baton_t *server = b->server;
174 svn_ra_svn_conn_t *conn = b->conn;
175 const char *timestr, *remote_host, *user, *continuation;
176 char errbuf[256];
177 /* 8192 from MAX_STRING_LEN in from httpd-2.2.4/include/httpd.h */
178 char errstr[8192];
180 if (server->log_file == NULL)
181 return;
183 svn_pool_clear(b->pool);
184 timestr = svn_time_to_cstring(apr_time_now(), b->pool);
185 remote_host = svn_ra_svn_conn_remote_host(conn);
186 remote_host = (remote_host ? remote_host : "-");
187 user = (server->user ? server->user : "-");
189 continuation = "";
190 while (err != NULL)
192 const char *message = svn_err_best_message(err, errbuf, sizeof(errbuf));
193 /* based on httpd-2.2.4/server/log.c:log_error_core */
194 apr_size_t len = apr_snprintf(errstr, sizeof(errstr),
195 "%" APR_PID_T_FMT
196 " %s %s %s %s ERR%s %d ",
197 getpid(), timestr, remote_host, user,
198 server->repos_name, continuation,
199 err->apr_err);
201 len += escape_errorlog_item(errstr + len, message,
202 sizeof(errstr) - len);
203 /* Truncate for the terminator (as apr_snprintf does) */
204 if (len > sizeof(errstr) - sizeof(APR_EOL_STR)) {
205 len = sizeof(errstr) - sizeof(APR_EOL_STR);
207 strcpy(errstr + len, APR_EOL_STR);
208 len += strlen(APR_EOL_STR);
209 svn_error_clear(svn_io_file_write(server->log_file, errstr, &len,
210 b->pool));
212 continuation = "-";
213 err = err->child;
217 static svn_error_t *svnserve_log(server_baton_t *b,
218 svn_ra_svn_conn_t *conn,
219 apr_pool_t *pool,
220 const char *fmt, ...)
222 const char *remote_host, *timestr, *log, *line;
223 va_list ap;
224 apr_size_t nbytes;
226 if (b->log_file == NULL)
227 return SVN_NO_ERROR;
229 remote_host = svn_ra_svn_conn_remote_host(conn);
230 timestr = svn_time_to_cstring(apr_time_now(), pool);
232 va_start(ap, fmt);
233 log = apr_pvsprintf(pool, fmt, ap);
234 va_end(ap);
236 line = apr_psprintf(pool, "%" APR_PID_T_FMT
237 " %s %s %s %s %s" APR_EOL_STR,
238 getpid(), timestr,
239 (remote_host ? remote_host : "-"),
240 (b->user ? b->user : "-"), b->repos_name, log);
241 nbytes = strlen(line);
242 return svn_io_file_write(b->log_file, line, &nbytes, pool);
245 #define SLOG(...) SVN_ERR(svnserve_log(baton, conn, pool, __VA_ARGS__))
247 /* --- AUTHENTICATION AND AUTHORIZATION FUNCTIONS --- */
249 /* Set *ALLOWED to TRUE if PATH is accessible in the REQUIRED mode to
250 the user described in BATON according to the authz rules in BATON.
251 Use POOL for temporary allocations only. If no authz rules are
252 present in BATON, grant access by default. */
253 static svn_error_t *authz_check_access(svn_boolean_t *allowed,
254 const char *path,
255 svn_repos_authz_access_t required,
256 server_baton_t *b,
257 apr_pool_t *pool)
259 /* If authz cannot be performed, grant access. This is NOT the same
260 as the default policy when authz is performed on a path with no
261 rules. In the latter case, the default is to deny access, and is
262 set by svn_repos_authz_check_access. */
263 if (!b->authzdb)
265 *allowed = TRUE;
266 return SVN_NO_ERROR;
269 /* If the authz request is for the empty path (ie. ""), replace it
270 with the root path. This happens because of stripping done at
271 various levels in svnserve that remove the leading / on an
272 absolute path. Passing such a malformed path to the authz
273 routines throws them into an infinite loop and makes them miss
274 ACLs. */
275 if (path && *path != '/')
276 path = svn_path_join("/", path, pool);
278 return svn_repos_authz_check_access(b->authzdb, b->authz_repos_name,
279 path, b->user, required,
280 allowed, pool);
283 /* Set *ALLOWED to TRUE if PATH is readable by the user described in
284 * BATON. Use POOL for temporary allocations only. ROOT is not used.
285 * Implements the svn_repos_authz_func_t interface.
287 static svn_error_t *authz_check_access_cb(svn_boolean_t *allowed,
288 svn_fs_root_t *root,
289 const char *path,
290 void *baton,
291 apr_pool_t *pool)
293 server_baton_t *sb = baton;
295 return authz_check_access(allowed, path, svn_authz_read, sb, pool);
298 /* If authz is enabled in the specified BATON, return a read authorization
299 function. Otherwise, return NULL. */
300 static svn_repos_authz_func_t authz_check_access_cb_func(server_baton_t *baton)
302 if (baton->authzdb)
303 return authz_check_access_cb;
304 return NULL;
307 /* Set *ALLOWED to TRUE if the REQUIRED access to PATH is granted,
308 * according to the state in BATON. Use POOL for temporary
309 * allocations only. ROOT is not used. Implements the
310 * svn_repos_authz_callback_t interface.
312 static svn_error_t *authz_commit_cb(svn_repos_authz_access_t required,
313 svn_boolean_t *allowed,
314 svn_fs_root_t *root,
315 const char *path,
316 void *baton,
317 apr_pool_t *pool)
319 server_baton_t *sb = baton;
321 return authz_check_access(allowed, path, required, sb, pool);
325 enum access_type get_access(server_baton_t *b, enum authn_type auth)
327 const char *var = (auth == AUTHENTICATED) ? SVN_CONFIG_OPTION_AUTH_ACCESS :
328 SVN_CONFIG_OPTION_ANON_ACCESS;
329 const char *val, *def = (auth == AUTHENTICATED) ? "write" : "read";
330 enum access_type result;
332 svn_config_get(b->cfg, &val, SVN_CONFIG_SECTION_GENERAL, var, def);
333 result = (strcmp(val, "write") == 0 ? WRITE_ACCESS :
334 strcmp(val, "read") == 0 ? READ_ACCESS : NO_ACCESS);
335 return (result == WRITE_ACCESS && b->read_only) ? READ_ACCESS : result;
338 static enum access_type current_access(server_baton_t *b)
340 return get_access(b, (b->user) ? AUTHENTICATED : UNAUTHENTICATED);
343 /* Send authentication mechs for ACCESS_TYPE to the client. If NEEDS_USERNAME
344 is true, don't send anonymous mech even if that would give the desired
345 access. */
346 static svn_error_t *send_mechs(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
347 server_baton_t *b, enum access_type required,
348 svn_boolean_t needs_username)
350 if (!needs_username && get_access(b, UNAUTHENTICATED) >= required)
351 SVN_ERR(svn_ra_svn_write_word(conn, pool, "ANONYMOUS"));
352 if (b->tunnel_user && get_access(b, AUTHENTICATED) >= required)
353 SVN_ERR(svn_ra_svn_write_word(conn, pool, "EXTERNAL"));
354 if (b->pwdb && get_access(b, AUTHENTICATED) >= required)
355 SVN_ERR(svn_ra_svn_write_word(conn, pool, "CRAM-MD5"));
356 return SVN_NO_ERROR;
359 /* Context for cleanup handler. */
360 struct cleanup_fs_access_baton
362 svn_fs_t *fs;
363 apr_pool_t *pool;
366 /* Pool cleanup handler. Make sure fs's access_t points to NULL when
367 the command pool is destroyed. */
368 static apr_status_t cleanup_fs_access(void *data)
370 svn_error_t *serr;
371 struct cleanup_fs_access_baton *baton = data;
373 serr = svn_fs_set_access(baton->fs, NULL);
374 if (serr)
376 apr_status_t apr_err = serr->apr_err;
377 svn_error_clear(serr);
378 return apr_err;
381 return APR_SUCCESS;
385 /* Create an svn_fs_access_t in POOL for USER and associate it with
386 B's filesystem. Also, register a cleanup handler with POOL which
387 de-associates the svn_fs_access_t from B's filesystem. */
388 static svn_error_t *
389 create_fs_access(server_baton_t *b, apr_pool_t *pool)
391 svn_fs_access_t *fs_access;
392 struct cleanup_fs_access_baton *cleanup_baton;
394 if (!b->user)
395 return SVN_NO_ERROR;
397 SVN_ERR(svn_fs_create_access(&fs_access, b->user, pool));
398 SVN_ERR(svn_fs_set_access(b->fs, fs_access));
400 cleanup_baton = apr_pcalloc(pool, sizeof(*cleanup_baton));
401 cleanup_baton->pool = pool;
402 cleanup_baton->fs = b->fs;
403 apr_pool_cleanup_register(pool, cleanup_baton, cleanup_fs_access,
404 apr_pool_cleanup_null);
406 return SVN_NO_ERROR;
409 /* Authenticate, once the client has chosen a mechanism and possibly
410 * sent an initial mechanism token. On success, set *success to true
411 * and b->user to the authenticated username (or NULL for anonymous).
412 * On authentication failure, report failure to the client and set
413 * *success to FALSE. On communications failure, return an error.
414 * If NEEDS_USERNAME is TRUE, don't allow anonymous authentication. */
415 static svn_error_t *auth(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
416 const char *mech, const char *mecharg,
417 server_baton_t *b, enum access_type required,
418 svn_boolean_t needs_username,
419 svn_boolean_t *success)
421 const char *user;
422 *success = FALSE;
424 if (get_access(b, AUTHENTICATED) >= required
425 && b->tunnel_user && strcmp(mech, "EXTERNAL") == 0)
427 if (*mecharg && strcmp(mecharg, b->tunnel_user) != 0)
428 return svn_ra_svn_write_tuple(conn, pool, "w(c)", "failure",
429 "Requested username does not match");
430 b->user = b->tunnel_user;
431 SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "w()", "success"));
432 *success = TRUE;
433 return SVN_NO_ERROR;
436 if (get_access(b, UNAUTHENTICATED) >= required
437 && strcmp(mech, "ANONYMOUS") == 0 && ! needs_username)
439 SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "w()", "success"));
440 *success = TRUE;
441 return SVN_NO_ERROR;
444 if (get_access(b, AUTHENTICATED) >= required
445 && b->pwdb && strcmp(mech, "CRAM-MD5") == 0)
447 SVN_ERR(svn_ra_svn_cram_server(conn, pool, b->pwdb, &user, success));
448 b->user = apr_pstrdup(b->pool, user);
449 return SVN_NO_ERROR;
452 return svn_ra_svn_write_tuple(conn, pool, "w(c)", "failure",
453 "Must authenticate with listed mechanism");
456 /* Perform an authentication request using the built-in SASL implementation. */
457 static svn_error_t *
458 internal_auth_request(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
459 server_baton_t *b, enum access_type required,
460 svn_boolean_t needs_username)
462 svn_boolean_t success;
463 const char *mech, *mecharg;
465 SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "w((!", "success"));
466 SVN_ERR(send_mechs(conn, pool, b, required, needs_username));
467 SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "!)c)", b->realm));
470 SVN_ERR(svn_ra_svn_read_tuple(conn, pool, "w(?c)", &mech, &mecharg));
471 if (!*mech)
472 break;
473 SVN_ERR(auth(conn, pool, mech, mecharg, b, required, needs_username,
474 &success));
476 while (!success);
477 return SVN_NO_ERROR;
480 /* Perform an authentication request in order to get an access level of
481 * REQUIRED or higher. Since the client may escape the authentication
482 * exchange, the caller should check current_access(b) to see if
483 * authentication succeeded. */
484 static svn_error_t *auth_request(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
485 server_baton_t *b, enum access_type required,
486 svn_boolean_t needs_username)
488 #ifdef SVN_HAVE_SASL
489 if (b->use_sasl)
490 return cyrus_auth_request(conn, pool, b, required, needs_username);
491 #endif
493 return internal_auth_request(conn, pool, b, required, needs_username);
496 /* Send a trivial auth notification on CONN which lists no mechanisms,
497 * indicating that authentication is unnecessary. Usually called in
498 * response to invocation of a svnserve command.
500 static svn_error_t *trivial_auth_request(svn_ra_svn_conn_t *conn,
501 apr_pool_t *pool, server_baton_t *b)
503 return svn_ra_svn_write_cmd_response(conn, pool, "()c", "");
506 /* Ensure that the client has the REQUIRED access by checking the
507 * access directives (both blanket and per-directory) in BATON. If
508 * PATH is NULL, then only the blanket access configuration will
509 * impact the result.
511 * If NEEDS_USERNAME is TRUE, then a lookup is only successful if the
512 * user described in BATON is authenticated and, well, has a username
513 * assigned to him.
515 * Use POOL for temporary allocations only.
517 static svn_boolean_t lookup_access(apr_pool_t *pool,
518 server_baton_t *baton,
519 svn_repos_authz_access_t required,
520 const char *path,
521 svn_boolean_t needs_username)
523 enum access_type req = (required & svn_authz_write) ?
524 WRITE_ACCESS : READ_ACCESS;
525 svn_boolean_t authorized;
526 svn_error_t *err;
528 /* Get authz's opinion on the access. */
529 err = authz_check_access(&authorized, path, required, baton, pool);
531 /* If an error made lookup fail, deny access. ### TODO: Once
532 logging is implemented, this is a perfect place to log the
533 problem. */
534 if (err)
536 svn_error_clear(err);
537 return FALSE;
540 /* If the required access is blanket-granted AND granted by authz
541 AND we already have a username if one is required, then the
542 lookup has succeeded. */
543 if (current_access(baton) >= req
544 && authorized
545 && (! needs_username || baton->user))
546 return TRUE;
548 return FALSE;
551 /* Check that the client has the REQUIRED access by consulting the
552 * authentication and authorization states stored in BATON. If the
553 * client does not have the required access credentials, attempt to
554 * authenticate the client to get that access, using CONN for
555 * communication.
557 * This function is supposed to be called to handle the authentication
558 * half of a standard svn protocol reply. If an error is returned, it
559 * probably means that the server can terminate the client connection
560 * with an apologetic error, as it implies an authentication failure.
562 * PATH and NEEDS_USERNAME are passed along to lookup_access, their
563 * behaviour is documented there.
565 static svn_error_t *must_have_access(svn_ra_svn_conn_t *conn,
566 apr_pool_t *pool,
567 server_baton_t *b,
568 svn_repos_authz_access_t required,
569 const char *path,
570 svn_boolean_t needs_username)
572 enum access_type req = (required & svn_authz_write) ?
573 WRITE_ACCESS : READ_ACCESS;
575 /* See whether the user already has the required access. If so,
576 nothing needs to be done. Create the FS access and send a
577 trivial auth request. */
578 if (lookup_access(pool, b, required, path, needs_username))
580 SVN_ERR(create_fs_access(b, pool));
581 return trivial_auth_request(conn, pool, b);
584 /* If the required blanket access can be obtained by authenticating,
585 try that. Unfortunately, we can't tell until after
586 authentication whether authz will work or not. We force
587 requiring a username because we need one to be able to check
588 authz configuration again with a different user credentials than
589 the first time round. */
590 if (b->user == NULL
591 && get_access(b, AUTHENTICATED) >= req
592 && (b->tunnel_user || b->pwdb || b->use_sasl))
593 SVN_ERR(auth_request(conn, pool, b, req, TRUE));
595 /* Now that an authentication has been done get the new take of
596 authz on the request. */
597 if (! lookup_access(pool, b, required, path, needs_username))
598 return svn_error_create(SVN_ERR_RA_SVN_CMD_ERR,
599 svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED,
600 NULL, NULL), NULL);
602 /* Else, access is granted, and there is much rejoicing. */
603 SVN_ERR(create_fs_access(b, pool));
605 return SVN_NO_ERROR;
608 /* --- REPORTER COMMAND SET --- */
610 /* To allow for pipelining, reporter commands have no reponses. If we
611 * get an error, we ignore all subsequent reporter commands and return
612 * the error finish_report, to be handled by the calling command.
615 static svn_error_t *set_path(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
616 apr_array_header_t *params, void *baton)
618 report_driver_baton_t *b = baton;
619 const char *path, *lock_token, *depth_word;
620 svn_revnum_t rev;
621 /* Default to infinity, for old clients that don't send depth. */
622 svn_depth_t depth = svn_depth_infinity;
623 svn_boolean_t start_empty;
625 SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "crb?(?c)?w",
626 &path, &rev, &start_empty, &lock_token,
627 &depth_word));
628 if (depth_word)
629 depth = svn_depth_from_word(depth_word);
630 path = svn_path_canonicalize(path, pool);
631 if (b->from_rev && strcmp(path, "") == 0)
632 *b->from_rev = rev;
633 if (!b->err)
634 b->err = svn_repos_set_path3(b->report_baton, path, rev, depth,
635 start_empty, lock_token, pool);
636 b->entry_counter++;
637 if (!start_empty)
638 b->only_empty_entries = FALSE;
639 return SVN_NO_ERROR;
642 static svn_error_t *delete_path(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
643 apr_array_header_t *params, void *baton)
645 report_driver_baton_t *b = baton;
646 const char *path;
648 SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "c", &path));
649 path = svn_path_canonicalize(path, pool);
650 if (!b->err)
651 b->err = svn_repos_delete_path(b->report_baton, path, pool);
652 return SVN_NO_ERROR;
655 static svn_error_t *link_path(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
656 apr_array_header_t *params, void *baton)
658 report_driver_baton_t *b = baton;
659 const char *path, *url, *lock_token, *fs_path, *depth_word;
660 svn_revnum_t rev;
661 svn_boolean_t start_empty;
662 /* Default to infinity, for old clients that don't send depth. */
663 svn_depth_t depth = svn_depth_infinity;
665 SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "ccrb?(?c)?w",
666 &path, &url, &rev, &start_empty,
667 &lock_token, &depth_word));
668 path = svn_path_canonicalize(path, pool);
669 url = svn_path_uri_decode(svn_path_canonicalize(url, pool), pool);
670 if (depth_word)
671 depth = svn_depth_from_word(depth_word);
672 if (!b->err)
673 b->err = get_fs_path(svn_path_uri_decode(b->repos_url, pool),
674 url, &fs_path);
675 if (!b->err)
676 b->err = svn_repos_link_path3(b->report_baton, path, fs_path, rev,
677 depth, start_empty, lock_token, pool);
678 b->entry_counter++;
679 return SVN_NO_ERROR;
682 static svn_error_t *finish_report(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
683 apr_array_header_t *params, void *baton)
685 report_driver_baton_t *b = baton;
687 /* No arguments to parse. */
688 SVN_ERR(trivial_auth_request(conn, pool, b->sb));
689 if (!b->err)
690 b->err = svn_repos_finish_report(b->report_baton, pool);
691 return SVN_NO_ERROR;
694 static svn_error_t *abort_report(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
695 apr_array_header_t *params, void *baton)
697 report_driver_baton_t *b = baton;
699 /* No arguments to parse. */
700 svn_error_clear(svn_repos_abort_report(b->report_baton, pool));
701 return SVN_NO_ERROR;
704 static const svn_ra_svn_cmd_entry_t report_commands[] = {
705 { "set-path", set_path },
706 { "delete-path", delete_path },
707 { "link-path", link_path },
708 { "finish-report", finish_report, TRUE },
709 { "abort-report", abort_report, TRUE },
710 { NULL }
713 /* Accept a report from the client, drive the network editor with the
714 * result, and then write an empty command response. If there is a
715 * non-protocol failure, accept_report will abort the edit and return
716 * a command error to be reported by handle_commands().
718 * If only_empty_entry is not NULL and the report contains only one
719 * item, and that item is empty, set *only_empty_entry to TRUE, else
720 * set it to FALSE.
722 * If from_rev is not NULL, set *from_rev to the revision number from
723 * the set-path on ""; if somehow set-path "" never happens, set
724 * *from_rev to SVN_INVALID_REVNUM.
726 static svn_error_t *accept_report(svn_boolean_t *only_empty_entry,
727 svn_revnum_t *from_rev,
728 svn_ra_svn_conn_t *conn, apr_pool_t *pool,
729 server_baton_t *b, svn_revnum_t rev,
730 const char *target, const char *tgt_path,
731 svn_boolean_t text_deltas,
732 svn_depth_t depth,
733 svn_boolean_t send_copyfrom_args,
734 svn_boolean_t ignore_ancestry)
736 const svn_delta_editor_t *editor;
737 void *edit_baton, *report_baton;
738 report_driver_baton_t rb;
739 svn_error_t *err;
741 /* Make an svn_repos report baton. Tell it to drive the network editor
742 * when the report is complete. */
743 svn_ra_svn_get_editor(&editor, &edit_baton, conn, pool, NULL, NULL);
744 SVN_CMD_ERR(svn_repos_begin_report2(&report_baton, rev, b->repos,
745 b->fs_path->data, target, tgt_path,
746 text_deltas, depth, ignore_ancestry,
747 send_copyfrom_args,
748 editor, edit_baton,
749 authz_check_access_cb_func(b),
750 b, pool));
752 rb.sb = b;
753 rb.repos_url = svn_path_uri_decode(b->repos_url, pool);
754 rb.report_baton = report_baton;
755 rb.err = NULL;
756 rb.entry_counter = 0;
757 rb.only_empty_entries = TRUE;
758 rb.from_rev = from_rev;
759 if (from_rev)
760 *from_rev = SVN_INVALID_REVNUM;
761 err = svn_ra_svn_handle_commands(conn, pool, report_commands, &rb);
762 if (err)
764 /* Network or protocol error while handling commands. */
765 svn_error_clear(rb.err);
766 return err;
768 else if (rb.err)
770 /* Some failure during the reporting or editing operations. */
771 SVN_CMD_ERR(rb.err);
773 SVN_ERR(svn_ra_svn_write_cmd_response(conn, pool, ""));
775 if (only_empty_entry)
776 *only_empty_entry = rb.entry_counter == 1 && rb.only_empty_entries;
778 return SVN_NO_ERROR;
781 /* --- MAIN COMMAND SET --- */
783 /* Write out a list of property diffs. PROPDIFFS is an array of svn_prop_t
784 * values. */
785 static svn_error_t *write_prop_diffs(svn_ra_svn_conn_t *conn,
786 apr_pool_t *pool,
787 apr_array_header_t *propdiffs)
789 int i;
791 for (i = 0; i < propdiffs->nelts; ++i)
793 const svn_prop_t *prop = &APR_ARRAY_IDX(propdiffs, i, svn_prop_t);
795 SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "c(?s)",
796 prop->name, prop->value));
799 return SVN_NO_ERROR;
802 /* Write out a lock to the client. */
803 static svn_error_t *write_lock(svn_ra_svn_conn_t *conn,
804 apr_pool_t *pool,
805 svn_lock_t *lock)
807 const char *cdate, *edate;
809 cdate = svn_time_to_cstring(lock->creation_date, pool);
810 edate = lock->expiration_date
811 ? svn_time_to_cstring(lock->expiration_date, pool) : NULL;
812 SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "ccc(?c)c(?c)", lock->path,
813 lock->token, lock->owner, lock->comment,
814 cdate, edate));
816 return SVN_NO_ERROR;
819 static const char *kind_word(svn_node_kind_t kind)
821 switch (kind)
823 case svn_node_none:
824 return "none";
825 case svn_node_file:
826 return "file";
827 case svn_node_dir:
828 return "dir";
829 case svn_node_unknown:
830 return "unknown";
831 default:
832 abort();
835 /* Make the compiler happy */
836 return NULL;
839 /* ### This really belongs in libsvn_repos. */
840 /* Get the properties for a path, with hardcoded committed-info values. */
841 static svn_error_t *get_props(apr_hash_t **props, svn_fs_root_t *root,
842 const char *path, apr_pool_t *pool)
844 svn_string_t *str;
845 svn_revnum_t crev;
846 const char *cdate, *cauthor, *uuid;
848 /* Get the properties. */
849 SVN_ERR(svn_fs_node_proplist(props, root, path, pool));
851 /* Hardcode the values for the committed revision, date, and author. */
852 SVN_ERR(svn_repos_get_committed_info(&crev, &cdate, &cauthor, root,
853 path, pool));
854 str = svn_string_create(apr_psprintf(pool, "%ld", crev),
855 pool);
856 apr_hash_set(*props, SVN_PROP_ENTRY_COMMITTED_REV, APR_HASH_KEY_STRING, str);
857 str = (cdate) ? svn_string_create(cdate, pool) : NULL;
858 apr_hash_set(*props, SVN_PROP_ENTRY_COMMITTED_DATE, APR_HASH_KEY_STRING,
859 str);
860 str = (cauthor) ? svn_string_create(cauthor, pool) : NULL;
861 apr_hash_set(*props, SVN_PROP_ENTRY_LAST_AUTHOR, APR_HASH_KEY_STRING, str);
863 /* Hardcode the values for the UUID. */
864 SVN_ERR(svn_fs_get_uuid(svn_fs_root_fs(root), &uuid, pool));
865 str = (uuid) ? svn_string_create(uuid, pool) : NULL;
866 apr_hash_set(*props, SVN_PROP_ENTRY_UUID, APR_HASH_KEY_STRING, str);
868 return SVN_NO_ERROR;
871 /* Set BATON->FS_PATH for the repository URL found in PARAMS. */
872 static svn_error_t *reparent(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
873 apr_array_header_t *params, void *baton)
875 server_baton_t *b = baton;
876 const char *url;
877 const char *fs_path;
879 SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "c", &url));
880 url = svn_path_uri_decode(svn_path_canonicalize(url, pool), pool);
881 SVN_ERR(trivial_auth_request(conn, pool, b));
882 SVN_CMD_ERR(get_fs_path(svn_path_uri_decode(b->repos_url, pool),
883 url, &fs_path));
884 SLOG("%s", svn_log__reparent(fs_path, pool));
885 svn_stringbuf_set(b->fs_path, fs_path);
886 SVN_ERR(svn_ra_svn_write_cmd_response(conn, pool, ""));
887 return SVN_NO_ERROR;
890 static svn_error_t *get_latest_rev(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
891 apr_array_header_t *params, void *baton)
893 server_baton_t *b = baton;
894 svn_revnum_t rev;
896 SLOG("get-latest-rev");
898 SVN_ERR(trivial_auth_request(conn, pool, b));
899 SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->fs, pool));
900 SVN_ERR(svn_ra_svn_write_cmd_response(conn, pool, "r", rev));
901 return SVN_NO_ERROR;
904 static svn_error_t *get_dated_rev(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
905 apr_array_header_t *params, void *baton)
907 server_baton_t *b = baton;
908 svn_revnum_t rev;
909 apr_time_t tm;
910 const char *timestr;
912 SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "c", &timestr));
913 SLOG("get-dated-rev %s", timestr);
915 SVN_ERR(trivial_auth_request(conn, pool, b));
916 SVN_CMD_ERR(svn_time_from_cstring(&tm, timestr, pool));
917 SVN_CMD_ERR(svn_repos_dated_revision(&rev, b->repos, tm, pool));
918 SVN_ERR(svn_ra_svn_write_cmd_response(conn, pool, "r", rev));
919 return SVN_NO_ERROR;
922 static svn_error_t *change_rev_prop(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
923 apr_array_header_t *params, void *baton)
925 server_baton_t *b = baton;
926 svn_revnum_t rev;
927 const char *name;
928 svn_string_t *value;
930 /* Because the revprop value was at one time mandatory, the usual
931 optional element pattern "(?s)" isn't used. */
932 SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "rc?s", &rev, &name, &value));
934 SVN_ERR(must_have_access(conn, pool, b, svn_authz_write, NULL, FALSE));
935 SLOG("%s", svn_log__change_rev_prop(rev, name, pool));
936 SVN_CMD_ERR(svn_repos_fs_change_rev_prop3(b->repos, rev, b->user,
937 name, value, TRUE, TRUE,
938 authz_check_access_cb_func(b), b,
939 pool));
940 SVN_ERR(svn_ra_svn_write_cmd_response(conn, pool, ""));
941 return SVN_NO_ERROR;
944 static svn_error_t *rev_proplist(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
945 apr_array_header_t *params, void *baton)
947 server_baton_t *b = baton;
948 svn_revnum_t rev;
949 apr_hash_t *props;
951 SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "r", &rev));
952 SLOG("%s", svn_log__rev_proplist(rev, pool));
954 SVN_ERR(trivial_auth_request(conn, pool, b));
955 SVN_CMD_ERR(svn_repos_fs_revision_proplist(&props, b->repos, rev,
956 authz_check_access_cb_func(b), b,
957 pool));
958 SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "w((!", "success"));
959 SVN_ERR(svn_ra_svn_write_proplist(conn, pool, props));
960 SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "!))"));
961 return SVN_NO_ERROR;
964 static svn_error_t *rev_prop(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
965 apr_array_header_t *params, void *baton)
967 server_baton_t *b = baton;
968 svn_revnum_t rev;
969 const char *name;
970 svn_string_t *value;
972 SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "rc", &rev, &name));
973 SLOG("%s", svn_log__rev_prop(rev, name, pool));
975 SVN_ERR(trivial_auth_request(conn, pool, b));
976 SVN_CMD_ERR(svn_repos_fs_revision_prop(&value, b->repos, rev, name,
977 authz_check_access_cb_func(b), b,
978 pool));
979 SVN_ERR(svn_ra_svn_write_cmd_response(conn, pool, "(?s)", value));
980 return SVN_NO_ERROR;
983 static svn_error_t *commit_done(const svn_commit_info_t *commit_info,
984 void *baton, apr_pool_t *pool)
986 commit_callback_baton_t *ccb = baton;
988 *ccb->new_rev = commit_info->revision;
989 *ccb->date = commit_info->date
990 ? apr_pstrdup(ccb->pool, commit_info->date): NULL;
991 *ccb->author = commit_info->author
992 ? apr_pstrdup(ccb->pool, commit_info->author) : NULL;
993 *ccb->post_commit_err = commit_info->post_commit_err
994 ? apr_pstrdup(ccb->pool, commit_info->post_commit_err) : NULL;
995 return SVN_NO_ERROR;
998 /* Add the LOCK_TOKENS (if any) to the filesystem access context,
999 * checking path authorizations using the state in SB as we go.
1000 * LOCK_TOKENS is an array of svn_ra_svn_item_t structs. Return a
1001 * client error if LOCK_TOKENS is not a list of lists. If a lock
1002 * violates the authz configuration, return SVN_ERR_RA_NOT_AUTHORIZED
1003 * to the client. Use POOL for temporary allocations only.
1005 static svn_error_t *add_lock_tokens(svn_ra_svn_conn_t *conn,
1006 apr_array_header_t *lock_tokens,
1007 server_baton_t *sb,
1008 apr_pool_t *pool)
1010 int i;
1011 svn_fs_access_t *fs_access;
1013 SVN_ERR(svn_fs_get_access(&fs_access, sb->fs));
1015 /* If there is no access context, nowhere to add the tokens. */
1016 if (! fs_access)
1017 return SVN_NO_ERROR;
1019 for (i = 0; i < lock_tokens->nelts; ++i)
1021 const char *path, *token, *full_path;
1022 svn_ra_svn_item_t *path_item, *token_item;
1023 svn_ra_svn_item_t *item = &APR_ARRAY_IDX(lock_tokens, i,
1024 svn_ra_svn_item_t);
1025 if (item->kind != SVN_RA_SVN_LIST)
1026 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
1027 "Lock tokens aren't a list of lists");
1029 path_item = &APR_ARRAY_IDX(item->u.list, 0, svn_ra_svn_item_t);
1030 if (path_item->kind != SVN_RA_SVN_STRING)
1031 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
1032 "Lock path isn't a string");
1034 token_item = &APR_ARRAY_IDX(item->u.list, 1, svn_ra_svn_item_t);
1035 if (token_item->kind != SVN_RA_SVN_STRING)
1036 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
1037 "Lock token isn't a string");
1039 path = path_item->u.string->data;
1040 full_path = svn_path_join(sb->fs_path->data,
1041 svn_path_canonicalize(path, pool),
1042 pool);
1044 if (! lookup_access(pool, sb, svn_authz_write,
1045 full_path, TRUE))
1046 return svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED,
1047 NULL, NULL);
1049 token = token_item->u.string->data;
1050 SVN_ERR(svn_fs_access_add_lock_token(fs_access, token));
1053 return SVN_NO_ERROR;
1056 /* Unlock the paths with lock tokens in LOCK_TOKENS, ignoring any errors.
1057 LOCK_TOKENS contains svn_ra_svn_item_t elements, assumed to be lists. */
1058 static svn_error_t *unlock_paths(apr_array_header_t *lock_tokens,
1059 server_baton_t *sb,
1060 apr_pool_t *pool)
1062 int i;
1063 apr_pool_t *iterpool;
1065 iterpool = svn_pool_create(pool);
1067 for (i = 0; i < lock_tokens->nelts; ++i)
1069 svn_ra_svn_item_t *item, *path_item, *token_item;
1070 const char *path, *token, *full_path;
1071 svn_pool_clear(iterpool);
1073 item = &APR_ARRAY_IDX(lock_tokens, i, svn_ra_svn_item_t);
1074 path_item = &APR_ARRAY_IDX(item->u.list, 0, svn_ra_svn_item_t);
1075 token_item = &APR_ARRAY_IDX(item->u.list, 1, svn_ra_svn_item_t);
1077 path = path_item->u.string->data;
1078 token = token_item->u.string->data;
1080 full_path = svn_path_join(sb->fs_path->data,
1081 svn_path_canonicalize(path, iterpool),
1082 iterpool);
1084 /* The lock may have become defunct after the commit, so ignore such
1085 errors.
1087 ### If we ever write a logging facility for svnserve, this
1088 would be a good place to log an error before clearing
1089 it. */
1090 svn_error_clear(svn_repos_fs_unlock(sb->repos, full_path, token,
1091 FALSE, pool));
1094 svn_pool_destroy(iterpool);
1096 return SVN_NO_ERROR;
1099 static svn_error_t *commit(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
1100 apr_array_header_t *params, void *baton)
1102 server_baton_t *b = baton;
1103 const char *log_msg = NULL,
1104 *date = NULL,
1105 *author = NULL,
1106 *post_commit_err = NULL;
1107 apr_array_header_t *lock_tokens;
1108 svn_boolean_t keep_locks;
1109 apr_array_header_t *revprop_list = NULL;
1110 apr_hash_t *revprop_table;
1111 const svn_delta_editor_t *editor;
1112 void *edit_baton;
1113 svn_boolean_t aborted;
1114 commit_callback_baton_t ccb;
1115 svn_revnum_t new_rev;
1117 if (params->nelts == 1)
1119 /* Clients before 1.2 don't send lock-tokens, keep-locks,
1120 and rev-props fields. */
1121 SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "c", &log_msg));
1122 lock_tokens = NULL;
1123 keep_locks = TRUE;
1124 revprop_list = NULL;
1126 else
1128 /* Clients before 1.5 don't send the rev-props field. */
1129 SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "clb?l", &log_msg,
1130 &lock_tokens, &keep_locks,
1131 &revprop_list));
1134 /* The handling for locks is a little problematic, because the
1135 protocol won't let us send several auth requests once one has
1136 succeeded. So we request write access and a username before
1137 adding tokens (if we have any), and subsequently fail if a lock
1138 violates authz. */
1139 SVN_ERR(must_have_access(conn, pool, b, svn_authz_write,
1140 NULL,
1141 (lock_tokens && lock_tokens->nelts) ? TRUE : FALSE));
1143 /* Authorize the lock tokens and give them to the FS if we got
1144 any. */
1145 if (lock_tokens && lock_tokens->nelts)
1146 SVN_CMD_ERR(add_lock_tokens(conn, lock_tokens, b, pool));
1148 if (revprop_list)
1149 SVN_ERR(svn_ra_svn_parse_proplist(revprop_list, pool, &revprop_table));
1150 else
1152 revprop_table = apr_hash_make(pool);
1153 apr_hash_set(revprop_table, SVN_PROP_REVISION_LOG, APR_HASH_KEY_STRING,
1154 svn_string_create(log_msg, pool));
1157 /* Get author from the baton, making sure clients can't circumvent
1158 the authentication via the revision props. */
1159 apr_hash_set(revprop_table, SVN_PROP_REVISION_AUTHOR, APR_HASH_KEY_STRING,
1160 b->user ? svn_string_create(b->user, pool) : NULL);
1162 ccb.pool = pool;
1163 ccb.new_rev = &new_rev;
1164 ccb.date = &date;
1165 ccb.author = &author;
1166 ccb.post_commit_err = &post_commit_err;
1167 /* ### Note that svn_repos_get_commit_editor5 actually wants a decoded URL. */
1168 SVN_CMD_ERR(svn_repos_get_commit_editor5
1169 (&editor, &edit_baton, b->repos, NULL,
1170 svn_path_uri_decode(b->repos_url, pool),
1171 b->fs_path->data, revprop_table,
1172 commit_done, &ccb,
1173 authz_commit_cb, baton, pool));
1174 SVN_ERR(svn_ra_svn_write_cmd_response(conn, pool, ""));
1175 SVN_ERR(svn_ra_svn_drive_editor(conn, pool, editor, edit_baton, &aborted));
1176 if (!aborted)
1178 SLOG("%s", svn_log__commit(new_rev, pool));
1179 SVN_ERR(trivial_auth_request(conn, pool, b));
1181 /* In tunnel mode, deltify before answering the client, because
1182 answering may cause the client to terminate the connection
1183 and thus kill the server. But otherwise, deltify after
1184 answering the client, to avoid user-visible delay. */
1186 if (b->tunnel)
1187 SVN_ERR(svn_fs_deltify_revision(b->fs, new_rev, pool));
1189 /* Unlock the paths. */
1190 if (! keep_locks && lock_tokens && lock_tokens->nelts)
1191 SVN_ERR(unlock_paths(lock_tokens, b, pool));
1193 SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "r(?c)(?c)(?c)",
1194 new_rev, date, author, post_commit_err));
1196 if (! b->tunnel)
1197 SVN_ERR(svn_fs_deltify_revision(b->fs, new_rev, pool));
1199 return SVN_NO_ERROR;
1202 static svn_error_t *get_file(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
1203 apr_array_header_t *params, void *baton)
1205 server_baton_t *b = baton;
1206 const char *path, *full_path, *hex_digest;
1207 svn_revnum_t rev;
1208 svn_fs_root_t *root;
1209 svn_stream_t *contents;
1210 apr_hash_t *props = NULL;
1211 svn_string_t write_str;
1212 char buf[4096];
1213 apr_size_t len;
1214 svn_boolean_t want_props, want_contents;
1215 unsigned char digest[APR_MD5_DIGESTSIZE];
1216 svn_error_t *err, *write_err;
1218 /* Parse arguments. */
1219 SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "c(?r)bb", &path, &rev,
1220 &want_props, &want_contents));
1222 full_path = svn_path_join(b->fs_path->data,
1223 svn_path_canonicalize(path, pool), pool);
1225 /* Check authorizations */
1226 SVN_ERR(must_have_access(conn, pool, b, svn_authz_read,
1227 full_path, FALSE));
1229 if (!SVN_IS_VALID_REVNUM(rev))
1230 SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->fs, pool));
1231 SLOG("%s", svn_log__get_file(full_path, rev, want_contents, want_props, pool));
1233 /* Fetch the properties and a stream for the contents. */
1234 SVN_CMD_ERR(svn_fs_revision_root(&root, b->fs, rev, pool));
1235 SVN_CMD_ERR(svn_fs_file_md5_checksum(digest, root, full_path, pool));
1236 hex_digest = svn_md5_digest_to_cstring_display(digest, pool);
1237 if (want_props)
1238 SVN_CMD_ERR(get_props(&props, root, full_path, pool));
1239 if (want_contents)
1240 SVN_CMD_ERR(svn_fs_file_contents(&contents, root, full_path, pool));
1242 /* Send successful command response with revision and props. */
1243 SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "w((?c)r(!", "success",
1244 hex_digest, rev));
1245 SVN_ERR(svn_ra_svn_write_proplist(conn, pool, props));
1246 SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "!))"));
1248 /* Now send the file's contents. */
1249 if (want_contents)
1251 err = SVN_NO_ERROR;
1252 while (1)
1254 len = sizeof(buf);
1255 err = svn_stream_read(contents, buf, &len);
1256 if (err)
1257 break;
1258 if (len > 0)
1260 write_str.data = buf;
1261 write_str.len = len;
1262 SVN_ERR(svn_ra_svn_write_string(conn, pool, &write_str));
1264 if (len < sizeof(buf))
1266 err = svn_stream_close(contents);
1267 break;
1270 write_err = svn_ra_svn_write_cstring(conn, pool, "");
1271 if (write_err)
1273 svn_error_clear(err);
1274 return write_err;
1276 SVN_CMD_ERR(err);
1277 SVN_ERR(svn_ra_svn_write_cmd_response(conn, pool, ""));
1280 return SVN_NO_ERROR;
1283 static svn_error_t *get_dir(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
1284 apr_array_header_t *params, void *baton)
1286 server_baton_t *b = baton;
1287 const char *path, *full_path, *file_path, *name, *cauthor, *cdate;
1288 svn_revnum_t rev;
1289 apr_hash_t *entries, *props = NULL, *file_props;
1290 apr_hash_index_t *hi;
1291 svn_fs_dirent_t *fsent;
1292 svn_dirent_t *entry;
1293 const void *key;
1294 void *val;
1295 svn_fs_root_t *root;
1296 apr_pool_t *subpool;
1297 svn_boolean_t want_props, want_contents;
1298 apr_uint64_t dirent_fields;
1299 apr_array_header_t *dirent_fields_list = NULL;
1300 svn_ra_svn_item_t *elt;
1301 int i;
1303 SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "c(?r)bb?l", &path, &rev,
1304 &want_props, &want_contents,
1305 &dirent_fields_list));
1307 if (! dirent_fields_list)
1309 dirent_fields = SVN_DIRENT_ALL;
1311 else
1313 dirent_fields = 0;
1315 for (i = 0; i < dirent_fields_list->nelts; ++i)
1317 elt = &APR_ARRAY_IDX(dirent_fields_list, i, svn_ra_svn_item_t);
1319 if (elt->kind != SVN_RA_SVN_WORD)
1320 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
1321 "Dirent field not a string");
1323 if (strcmp(SVN_RA_SVN_DIRENT_KIND, elt->u.word) == 0)
1324 dirent_fields |= SVN_DIRENT_KIND;
1325 else if (strcmp(SVN_RA_SVN_DIRENT_SIZE, elt->u.word) == 0)
1326 dirent_fields |= SVN_DIRENT_SIZE;
1327 else if (strcmp(SVN_RA_SVN_DIRENT_HAS_PROPS, elt->u.word) == 0)
1328 dirent_fields |= SVN_DIRENT_HAS_PROPS;
1329 else if (strcmp(SVN_RA_SVN_DIRENT_CREATED_REV, elt->u.word) == 0)
1330 dirent_fields |= SVN_DIRENT_CREATED_REV;
1331 else if (strcmp(SVN_RA_SVN_DIRENT_TIME, elt->u.word) == 0)
1332 dirent_fields |= SVN_DIRENT_TIME;
1333 else if (strcmp(SVN_RA_SVN_DIRENT_LAST_AUTHOR, elt->u.word) == 0)
1334 dirent_fields |= SVN_DIRENT_LAST_AUTHOR;
1338 full_path = svn_path_join(b->fs_path->data,
1339 svn_path_canonicalize(path, pool), pool);
1341 /* Check authorizations */
1342 SVN_ERR(must_have_access(conn, pool, b, svn_authz_read,
1343 full_path, FALSE));
1345 if (!SVN_IS_VALID_REVNUM(rev))
1346 SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->fs, pool));
1347 SLOG("%s", svn_log__get_dir(full_path, rev, want_contents, want_props,
1348 dirent_fields, pool));
1350 /* Fetch the root of the appropriate revision. */
1351 SVN_CMD_ERR(svn_fs_revision_root(&root, b->fs, rev, pool));
1353 /* Fetch the directory properties if requested. */
1354 if (want_props)
1355 SVN_CMD_ERR(get_props(&props, root, full_path, pool));
1357 /* Fetch the directory entries if requested. */
1358 if (want_contents)
1360 SVN_CMD_ERR(svn_fs_dir_entries(&entries, root, full_path, pool));
1362 /* Transform the hash table's FS entries into dirents. This probably
1363 * belongs in libsvn_repos. */
1364 subpool = svn_pool_create(pool);
1365 for (hi = apr_hash_first(pool, entries); hi; hi = apr_hash_next(hi))
1367 apr_hash_this(hi, &key, NULL, &val);
1368 name = key;
1369 fsent = val;
1371 svn_pool_clear(subpool);
1373 file_path = svn_path_join(full_path, name, subpool);
1374 entry = apr_pcalloc(pool, sizeof(*entry));
1376 if (dirent_fields & SVN_DIRENT_KIND)
1378 /* kind */
1379 entry->kind = fsent->kind;
1382 if (dirent_fields & SVN_DIRENT_SIZE)
1384 /* size */
1385 if (entry->kind == svn_node_dir)
1386 entry->size = 0;
1387 else
1388 SVN_CMD_ERR(svn_fs_file_length(&entry->size, root, file_path,
1389 subpool));
1392 if (dirent_fields & SVN_DIRENT_HAS_PROPS)
1394 /* has_props */
1395 SVN_CMD_ERR(svn_fs_node_proplist(&file_props, root, file_path,
1396 subpool));
1397 entry->has_props = (apr_hash_count(file_props) > 0) ? TRUE
1398 : FALSE;
1401 if ((dirent_fields & SVN_DIRENT_LAST_AUTHOR)
1402 || (dirent_fields & SVN_DIRENT_TIME)
1403 || (dirent_fields & SVN_DIRENT_CREATED_REV))
1405 /* created_rev, last_author, time */
1406 SVN_CMD_ERR(svn_repos_get_committed_info(&entry->created_rev,
1407 &cdate,
1408 &cauthor, root,
1409 file_path,
1410 subpool));
1411 entry->last_author = apr_pstrdup(pool, cauthor);
1412 if (cdate)
1413 SVN_CMD_ERR(svn_time_from_cstring(&entry->time, cdate,
1414 subpool));
1415 else
1416 entry->time = (time_t) -1;
1419 /* Store the entry. */
1420 apr_hash_set(entries, name, APR_HASH_KEY_STRING, entry);
1422 svn_pool_destroy(subpool);
1425 /* Write out response. */
1426 SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "w(r(!", "success", rev));
1427 SVN_ERR(svn_ra_svn_write_proplist(conn, pool, props));
1428 SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "!)(!"));
1429 if (want_contents)
1431 for (hi = apr_hash_first(pool, entries); hi; hi = apr_hash_next(hi))
1433 apr_hash_this(hi, &key, NULL, &val);
1434 name = key;
1435 entry = val;
1436 cdate = (entry->time == (time_t) -1) ? NULL
1437 : svn_time_to_cstring(entry->time, pool);
1438 SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "cwnbr(?c)(?c)", name,
1439 kind_word(entry->kind),
1440 (apr_uint64_t) entry->size,
1441 entry->has_props, entry->created_rev,
1442 cdate, entry->last_author));
1445 SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "!))"));
1446 return SVN_NO_ERROR;
1449 static svn_error_t *update(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
1450 apr_array_header_t *params, void *baton)
1452 server_baton_t *b = baton;
1453 svn_revnum_t rev;
1454 const char *target, *full_path, *depth_word;
1455 svn_boolean_t recurse;
1456 svn_boolean_t send_copyfrom_args;
1457 apr_uint64_t send_copyfrom_param;
1458 /* Default to unknown. Old clients won't send depth, but we'll
1459 handle that by converting recurse if necessary. */
1460 svn_depth_t depth = svn_depth_unknown;
1461 svn_boolean_t is_checkout;
1463 /* Parse the arguments. */
1464 SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "(?r)cb?wB", &rev, &target,
1465 &recurse, &depth_word, &send_copyfrom_param));
1466 target = svn_path_canonicalize(target, pool);
1468 if (depth_word)
1469 depth = svn_depth_from_word(depth_word);
1470 else
1471 depth = SVN_DEPTH_INFINITY_OR_FILES(recurse);
1473 send_copyfrom_args = (send_copyfrom_param == SVN_RA_SVN_UNSPECIFIED_NUMBER) ?
1474 FALSE : (svn_boolean_t) send_copyfrom_param;
1476 full_path = svn_path_join(b->fs_path->data, target, pool);
1477 /* Check authorization and authenticate the user if necessary. */
1478 SVN_ERR(must_have_access(conn, pool, b, svn_authz_read, full_path, FALSE));
1480 if (!SVN_IS_VALID_REVNUM(rev))
1481 SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->fs, pool));
1483 SVN_ERR(accept_report(&is_checkout, NULL,
1484 conn, pool, b, rev, target, NULL, TRUE,
1485 depth, send_copyfrom_args, FALSE));
1486 if (is_checkout)
1487 SLOG("%s", svn_log__checkout(full_path, rev, depth, pool));
1488 else
1489 SLOG("%s", svn_log__update(full_path, rev, depth, send_copyfrom_args,
1490 pool));
1492 return SVN_NO_ERROR;
1495 static svn_error_t *switch_cmd(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
1496 apr_array_header_t *params, void *baton)
1498 server_baton_t *b = baton;
1499 svn_revnum_t rev;
1500 const char *target, *depth_word;
1501 const char *switch_url, *switch_path;
1502 svn_boolean_t recurse;
1503 /* Default to unknown. Old clients won't send depth, but we'll
1504 handle that by converting recurse if necessary. */
1505 svn_depth_t depth = svn_depth_unknown;
1507 /* Parse the arguments. */
1508 SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "(?r)cbc?w", &rev, &target,
1509 &recurse, &switch_url, &depth_word));
1510 target = svn_path_canonicalize(target, pool);
1511 switch_url = svn_path_canonicalize(switch_url, pool);
1513 if (depth_word)
1514 depth = svn_depth_from_word(depth_word);
1515 else
1516 depth = SVN_DEPTH_INFINITY_OR_FILES(recurse);
1518 SVN_ERR(trivial_auth_request(conn, pool, b));
1519 if (!SVN_IS_VALID_REVNUM(rev))
1520 SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->fs, pool));
1522 SVN_CMD_ERR(get_fs_path(svn_path_uri_decode(b->repos_url, pool),
1523 svn_path_uri_decode(switch_url, pool),
1524 &switch_path));
1527 const char *full_path = svn_path_join(b->fs_path->data, target, pool);
1528 SLOG("%s", svn_log__switch(full_path, switch_path, rev, depth, pool));
1531 return accept_report(NULL, NULL,
1532 conn, pool, b, rev, target, switch_path, TRUE,
1533 depth,
1534 FALSE /* TODO(sussman): no copyfrom args for now */,
1535 TRUE);
1538 static svn_error_t *status(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
1539 apr_array_header_t *params, void *baton)
1541 server_baton_t *b = baton;
1542 svn_revnum_t rev;
1543 const char *target, *depth_word;
1544 svn_boolean_t recurse;
1545 /* Default to unknown. Old clients won't send depth, but we'll
1546 handle that by converting recurse if necessary. */
1547 svn_depth_t depth = svn_depth_unknown;
1549 /* Parse the arguments. */
1550 SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "cb?(?r)?w",
1551 &target, &recurse, &rev, &depth_word));
1552 target = svn_path_canonicalize(target, pool);
1554 if (depth_word)
1555 depth = svn_depth_from_word(depth_word);
1556 else
1557 depth = recurse ? svn_depth_infinity : svn_depth_empty;
1559 SVN_ERR(trivial_auth_request(conn, pool, b));
1560 if (!SVN_IS_VALID_REVNUM(rev))
1561 SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->fs, pool));
1564 const char *full_path = svn_path_join(b->fs_path->data, target, pool);
1565 SLOG("%s", svn_log__status(full_path, rev, depth, pool));
1568 return accept_report(NULL, NULL, conn, pool, b, rev, target, NULL, FALSE,
1569 depth, FALSE, FALSE);
1572 static svn_error_t *diff(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
1573 apr_array_header_t *params, void *baton)
1575 server_baton_t *b = baton;
1576 svn_revnum_t rev;
1577 const char *target, *versus_url, *versus_path, *depth_word;
1578 svn_boolean_t recurse, ignore_ancestry;
1579 svn_boolean_t text_deltas;
1580 /* Default to unknown. Old clients won't send depth, but we'll
1581 handle that by converting recurse if necessary. */
1582 svn_depth_t depth = svn_depth_unknown;
1584 /* Parse the arguments. */
1585 if (params->nelts == 5)
1587 /* Clients before 1.4 don't send the text_deltas boolean or depth. */
1588 SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "(?r)cbbc", &rev, &target,
1589 &recurse, &ignore_ancestry, &versus_url));
1590 text_deltas = TRUE;
1591 depth_word = NULL;
1593 else
1595 SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "(?r)cbbcb?w",
1596 &rev, &target, &recurse,
1597 &ignore_ancestry, &versus_url,
1598 &text_deltas, &depth_word));
1600 target = svn_path_canonicalize(target, pool);
1601 versus_url = svn_path_canonicalize(versus_url, pool);
1603 if (depth_word)
1604 depth = svn_depth_from_word(depth_word);
1605 else
1606 depth = SVN_DEPTH_INFINITY_OR_FILES(recurse);
1608 SVN_ERR(trivial_auth_request(conn, pool, b));
1610 if (!SVN_IS_VALID_REVNUM(rev))
1611 SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->fs, pool));
1612 SVN_CMD_ERR(get_fs_path(svn_path_uri_decode(b->repos_url, pool),
1613 svn_path_uri_decode(versus_url, pool),
1614 &versus_path));
1617 const char *full_path = svn_path_join(b->fs_path->data, target, pool);
1618 svn_revnum_t from_rev;
1619 SVN_ERR(accept_report(NULL, &from_rev,
1620 conn, pool, b, rev, target, versus_path,
1621 text_deltas, depth, FALSE, ignore_ancestry));
1622 SLOG("%s", svn_log__diff(full_path, from_rev, versus_path, rev, depth,
1623 ignore_ancestry, pool));
1625 return SVN_NO_ERROR;
1628 /* Regardless of whether a client's capabilities indicate an
1629 understanding of this command (by way of SVN_RA_SVN_CAP_MERGEINFO),
1630 we provide a response.
1632 ASSUMPTION: When performing a 'merge' with two URLs at different
1633 revisions, the client will call this command more than once. */
1634 static svn_error_t *get_mergeinfo(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
1635 apr_array_header_t *params, void *baton)
1637 server_baton_t *b = baton;
1638 svn_revnum_t rev;
1639 apr_array_header_t *paths, *canonical_paths;
1640 svn_mergeinfo_catalog_t mergeinfo;
1641 int i;
1642 apr_hash_index_t *hi;
1643 const char *inherit_word;
1644 svn_mergeinfo_inheritance_t inherit;
1645 svn_boolean_t include_descendants;
1646 apr_pool_t *iterpool;
1648 SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "l(?r)wb", &paths, &rev,
1649 &inherit_word, &include_descendants));
1650 inherit = svn_inheritance_from_word(inherit_word);
1652 /* Canonicalize the paths which mergeinfo has been requested for. */
1653 canonical_paths = apr_array_make(pool, paths->nelts, sizeof(const char *));
1654 for (i = 0; i < paths->nelts; i++)
1656 svn_ra_svn_item_t *item = &APR_ARRAY_IDX(paths, i, svn_ra_svn_item_t);
1657 const char *full_path;
1659 if (item->kind != SVN_RA_SVN_STRING)
1660 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
1661 _("Path is not a string"));
1662 full_path = svn_path_join(b->fs_path->data,
1663 svn_path_canonicalize(item->u.string->data,
1664 pool),
1665 pool);
1666 APR_ARRAY_PUSH(canonical_paths, const char *) = full_path;
1668 SLOG("%s", svn_log__get_mergeinfo(canonical_paths, inherit, include_descendants,
1669 pool));
1671 SVN_ERR(trivial_auth_request(conn, pool, b));
1672 SVN_CMD_ERR(svn_repos_fs_get_mergeinfo(&mergeinfo, b->repos,
1673 canonical_paths, rev,
1674 inherit,
1675 include_descendants,
1676 authz_check_access_cb_func(b), b,
1677 pool));
1678 SVN_ERR(svn_mergeinfo__remove_prefix_from_catalog(&mergeinfo, mergeinfo,
1679 b->fs_path->data, pool));
1680 SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "w((!", "success"));
1681 iterpool = svn_pool_create(pool);
1682 for (hi = apr_hash_first(pool, mergeinfo); hi; hi = apr_hash_next(hi))
1684 const void *key;
1685 void *value;
1686 svn_string_t *mergeinfo_string;
1688 svn_pool_clear(iterpool);
1690 apr_hash_this(hi, &key, NULL, &value);
1691 SVN_ERR(svn_mergeinfo_to_string(&mergeinfo_string,
1692 (svn_mergeinfo_t) value,
1693 iterpool));
1694 SVN_ERR(svn_ra_svn_write_tuple(conn, iterpool, "cs", (const char *) key,
1695 mergeinfo_string));
1697 svn_pool_destroy(iterpool);
1698 SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "!))"));
1700 return SVN_NO_ERROR;
1703 /* Send a log entry to the client. */
1704 static svn_error_t *log_receiver(void *baton,
1705 svn_log_entry_t *log_entry,
1706 apr_pool_t *pool)
1708 log_baton_t *b = baton;
1709 svn_ra_svn_conn_t *conn = b->conn;
1710 apr_hash_index_t *h;
1711 const void *key;
1712 void *val;
1713 const char *path;
1714 svn_log_changed_path_t *change;
1715 svn_boolean_t invalid_revnum = FALSE;
1716 char action[2];
1717 const char *author, *date, *message;
1718 apr_uint64_t revprop_count;
1720 if (log_entry->revision == SVN_INVALID_REVNUM)
1722 /* If the stack depth is zero, we've seen the last revision, so don't
1723 send it, just return. */
1724 if (b->stack_depth == 0)
1725 return SVN_NO_ERROR;
1727 /* Because the svn protocol won't let us send an invalid revnum, we have
1728 to fudge here and send an additional flag. */
1729 log_entry->revision = 0;
1730 invalid_revnum = TRUE;
1731 b->stack_depth--;
1734 SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "(!"));
1735 if (log_entry->changed_paths)
1737 for (h = apr_hash_first(pool, log_entry->changed_paths); h;
1738 h = apr_hash_next(h))
1740 apr_hash_this(h, &key, NULL, &val);
1741 path = key;
1742 change = val;
1743 action[0] = change->action;
1744 action[1] = '\0';
1745 SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "cw(?cr)", path, action,
1746 change->copyfrom_path,
1747 change->copyfrom_rev));
1750 svn_compat_log_revprops_out(&author, &date, &message, log_entry->revprops);
1751 svn_compat_log_revprops_clear(log_entry->revprops);
1752 if (log_entry->revprops)
1753 revprop_count = apr_hash_count(log_entry->revprops);
1754 else
1755 revprop_count = 0;
1756 SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "!)r(?c)(?c)(?c)bbn(!",
1757 log_entry->revision,
1758 author, date, message,
1759 log_entry->has_children,
1760 invalid_revnum, revprop_count));
1761 SVN_ERR(svn_ra_svn_write_proplist(conn, pool, log_entry->revprops));
1762 SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "!)"));
1764 if (log_entry->has_children)
1765 b->stack_depth++;
1767 return SVN_NO_ERROR;
1770 static svn_error_t *log_cmd(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
1771 apr_array_header_t *params, void *baton)
1773 svn_error_t *err, *write_err;
1774 server_baton_t *b = baton;
1775 svn_revnum_t start_rev, end_rev;
1776 const char *full_path;
1777 svn_boolean_t changed_paths, strict_node, include_merged_revisions;
1778 apr_array_header_t *paths, *full_paths, *revprop_items, *revprops;
1779 char *revprop_word;
1780 svn_ra_svn_item_t *elt;
1781 int i;
1782 apr_uint64_t limit, include_merged_revs_param;
1783 log_baton_t lb;
1785 SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "l(?r)(?r)bb?n?Bwl", &paths,
1786 &start_rev, &end_rev, &changed_paths,
1787 &strict_node, &limit,
1788 &include_merged_revs_param,
1789 &revprop_word, &revprop_items));
1791 if (include_merged_revs_param == SVN_RA_SVN_UNSPECIFIED_NUMBER)
1792 include_merged_revisions = FALSE;
1793 else
1794 include_merged_revisions = (svn_boolean_t) include_merged_revs_param;
1796 if (revprop_word == NULL)
1797 /* pre-1.5 client */
1798 revprops = svn_compat_log_revprops_in(pool);
1799 else if (strcmp(revprop_word, "all-revprops") == 0)
1800 revprops = NULL;
1801 else if (strcmp(revprop_word, "revprops") == 0)
1803 revprops = apr_array_make(pool, revprop_items->nelts,
1804 sizeof(char *));
1805 if (revprop_items)
1807 for (i = 0; i < revprop_items->nelts; i++)
1809 elt = &APR_ARRAY_IDX(revprop_items, i, svn_ra_svn_item_t);
1810 if (elt->kind != SVN_RA_SVN_STRING)
1811 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
1812 _("Log revprop entry not a string"));
1813 APR_ARRAY_PUSH(revprops, const char *) = elt->u.string->data;
1817 else
1818 return svn_error_createf(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
1819 _("Unknown revprop word '%s' in log command"),
1820 revprop_word);
1822 /* If we got an unspecified number then the user didn't send us anything,
1823 so we assume no limit. If it's larger than INT_MAX then someone is
1824 messing with us, since we know the svn client libraries will never send
1825 us anything that big, so play it safe and default to no limit. */
1826 if (limit == SVN_RA_SVN_UNSPECIFIED_NUMBER || limit > INT_MAX)
1827 limit = 0;
1829 full_paths = apr_array_make(pool, paths->nelts, sizeof(const char *));
1830 for (i = 0; i < paths->nelts; i++)
1832 elt = &APR_ARRAY_IDX(paths, i, svn_ra_svn_item_t);
1833 if (elt->kind != SVN_RA_SVN_STRING)
1834 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
1835 _("Log path entry not a string"));
1836 full_path = svn_path_join(b->fs_path->data,
1837 svn_path_canonicalize(elt->u.string->data,
1838 pool),
1839 pool);
1840 APR_ARRAY_PUSH(full_paths, const char *) = full_path;
1842 SVN_ERR(trivial_auth_request(conn, pool, b));
1844 SLOG("%s", svn_log__log(full_paths, start_rev, end_rev, limit,
1845 changed_paths, strict_node, include_merged_revisions,
1846 revprops, pool));
1848 /* Get logs. (Can't report errors back to the client at this point.) */
1849 lb.fs_path = b->fs_path->data;
1850 lb.conn = conn;
1851 lb.stack_depth = 0;
1852 err = svn_repos_get_logs4(b->repos, full_paths, start_rev, end_rev,
1853 (int) limit, changed_paths, strict_node,
1854 include_merged_revisions, revprops,
1855 authz_check_access_cb_func(b), b, log_receiver,
1856 &lb, pool);
1858 write_err = svn_ra_svn_write_word(conn, pool, "done");
1859 if (write_err)
1861 svn_error_clear(err);
1862 return write_err;
1864 SVN_CMD_ERR(err);
1865 SVN_ERR(svn_ra_svn_write_cmd_response(conn, pool, ""));
1866 return SVN_NO_ERROR;
1869 static svn_error_t *check_path(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
1870 apr_array_header_t *params, void *baton)
1872 server_baton_t *b = baton;
1873 svn_revnum_t rev;
1874 const char *path, *full_path;
1875 svn_fs_root_t *root;
1876 svn_node_kind_t kind;
1878 SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "c(?r)", &path, &rev));
1879 full_path = svn_path_join(b->fs_path->data,
1880 svn_path_canonicalize(path, pool), pool);
1882 /* Check authorizations */
1883 SVN_ERR(must_have_access(conn, pool, b, svn_authz_read,
1884 full_path, FALSE));
1886 if (!SVN_IS_VALID_REVNUM(rev))
1887 SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->fs, pool));
1888 SLOG("check-path %s@%d", svn_path_uri_encode(full_path, pool), rev);
1890 SVN_CMD_ERR(svn_fs_revision_root(&root, b->fs, rev, pool));
1891 SVN_CMD_ERR(svn_fs_check_path(&kind, root, full_path, pool));
1892 SVN_ERR(svn_ra_svn_write_cmd_response(conn, pool, "w", kind_word(kind)));
1893 return SVN_NO_ERROR;
1896 static svn_error_t *stat(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
1897 apr_array_header_t *params, void *baton)
1899 server_baton_t *b = baton;
1900 svn_revnum_t rev;
1901 const char *path, *full_path, *cdate;
1902 svn_fs_root_t *root;
1903 svn_dirent_t *dirent;
1905 SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "c(?r)", &path, &rev));
1906 full_path = svn_path_join(b->fs_path->data,
1907 svn_path_canonicalize(path, pool), pool);
1909 /* Check authorizations */
1910 SVN_ERR(must_have_access(conn, pool, b, svn_authz_read,
1911 full_path, FALSE));
1913 if (!SVN_IS_VALID_REVNUM(rev))
1914 SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->fs, pool));
1915 SLOG("stat %s@%d", svn_path_uri_encode(full_path, pool), rev);
1917 SVN_CMD_ERR(svn_fs_revision_root(&root, b->fs, rev, pool));
1918 SVN_CMD_ERR(svn_repos_stat(&dirent, root, full_path, pool));
1920 /* Need to return the equivalent of "(?l)", since that's what the
1921 client is reading. */
1923 if (dirent == NULL)
1925 SVN_ERR(svn_ra_svn_write_cmd_response(conn, pool, "()"));
1926 return SVN_NO_ERROR;
1929 cdate = (dirent->time == (time_t) -1) ? NULL
1930 : svn_time_to_cstring(dirent->time, pool);
1932 SVN_ERR(svn_ra_svn_write_cmd_response(conn, pool, "((wnbr(?c)(?c)))",
1933 kind_word(dirent->kind),
1934 (apr_uint64_t) dirent->size,
1935 dirent->has_props, dirent->created_rev,
1936 cdate, dirent->last_author));
1938 return SVN_NO_ERROR;
1941 static svn_error_t *get_locations(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
1942 apr_array_header_t *params, void *baton)
1944 svn_error_t *err, *write_err;
1945 server_baton_t *b = baton;
1946 svn_revnum_t revision;
1947 apr_array_header_t *location_revisions, *loc_revs_proto;
1948 svn_ra_svn_item_t *elt;
1949 int i;
1950 const char *relative_path;
1951 svn_revnum_t peg_revision;
1952 apr_hash_t *fs_locations;
1953 apr_hash_index_t *iter;
1954 const char *abs_path;
1955 const void *iter_key;
1956 void *iter_value;
1958 /* Parse the arguments. */
1959 SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "crl", &relative_path,
1960 &peg_revision,
1961 &loc_revs_proto));
1962 relative_path = svn_path_canonicalize(relative_path, pool);
1964 abs_path = svn_path_join(b->fs_path->data, relative_path, pool);
1966 location_revisions = apr_array_make(pool, loc_revs_proto->nelts,
1967 sizeof(svn_revnum_t));
1968 for (i = 0; i < loc_revs_proto->nelts; i++)
1970 elt = &APR_ARRAY_IDX(loc_revs_proto, i, svn_ra_svn_item_t);
1971 if (elt->kind != SVN_RA_SVN_NUMBER)
1972 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
1973 "Get-locations location revisions entry "
1974 "not a revision number");
1975 revision = (svn_revnum_t)(elt->u.number);
1976 APR_ARRAY_PUSH(location_revisions, svn_revnum_t) = revision;
1978 SVN_ERR(trivial_auth_request(conn, pool, b));
1979 SLOG("%s", svn_log__get_locations(abs_path, peg_revision, location_revisions,
1980 pool));
1982 /* All the parameters are fine - let's perform the query against the
1983 * repository. */
1985 /* We store both err and write_err here, so the client will get
1986 * the "done" even if there was an error in fetching the results. */
1988 err = svn_repos_trace_node_locations(b->fs, &fs_locations, abs_path,
1989 peg_revision, location_revisions,
1990 authz_check_access_cb_func(b), b, pool);
1992 /* Now, write the results to the connection. */
1993 if (!err)
1995 if (fs_locations)
1997 for (iter = apr_hash_first(pool, fs_locations); iter;
1998 iter = apr_hash_next(iter))
2000 apr_hash_this(iter, &iter_key, NULL, &iter_value);
2001 SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "rc",
2002 *(const svn_revnum_t *)iter_key,
2003 (const char *)iter_value));
2008 write_err = svn_ra_svn_write_word(conn, pool, "done");
2009 if (write_err)
2011 svn_error_clear(err);
2012 return write_err;
2014 SVN_CMD_ERR(err);
2016 SVN_ERR(svn_ra_svn_write_cmd_response(conn, pool, ""));
2018 return SVN_NO_ERROR;
2021 static svn_error_t *gls_receiver(svn_location_segment_t *segment,
2022 void *baton,
2023 apr_pool_t *pool)
2025 svn_ra_svn_conn_t *conn = baton;
2026 return svn_ra_svn_write_tuple(conn, pool, "rr(?c)",
2027 segment->range_start,
2028 segment->range_end,
2029 segment->path);
2032 static svn_error_t *get_location_segments(svn_ra_svn_conn_t *conn,
2033 apr_pool_t *pool,
2034 apr_array_header_t *params,
2035 void *baton)
2037 svn_error_t *err, *write_err;
2038 server_baton_t *b = baton;
2039 svn_revnum_t peg_revision, start_rev, end_rev;
2040 const char *relative_path;
2041 const char *abs_path;
2043 /* Parse the arguments. */
2044 SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "c(?r)(?r)(?r)",
2045 &relative_path, &peg_revision,
2046 &start_rev, &end_rev));
2047 relative_path = svn_path_canonicalize(relative_path, pool);
2049 abs_path = svn_path_join(b->fs_path->data, relative_path, pool);
2051 if (SVN_IS_VALID_REVNUM(start_rev)
2052 && SVN_IS_VALID_REVNUM(end_rev)
2053 && (end_rev > start_rev))
2054 return svn_error_createf(SVN_ERR_INCORRECT_PARAMS, NULL,
2055 "Get-location-segments end revision must not be "
2056 "younger than start revision");
2058 if (SVN_IS_VALID_REVNUM(peg_revision)
2059 && SVN_IS_VALID_REVNUM(start_rev)
2060 && (start_rev > peg_revision))
2061 return svn_error_createf(SVN_ERR_INCORRECT_PARAMS, NULL,
2062 "Get-location-segments start revision must not "
2063 "be younger than peg revision");
2065 SVN_ERR(trivial_auth_request(conn, pool, b));
2066 SLOG("%s", svn_log__get_location_segments(abs_path, peg_revision, start_rev,
2067 end_rev, pool));
2069 /* All the parameters are fine - let's perform the query against the
2070 * repository. */
2072 /* We store both err and write_err here, so the client will get
2073 * the "done" even if there was an error in fetching the results. */
2075 err = svn_repos_node_location_segments(b->repos, abs_path,
2076 peg_revision, start_rev, end_rev,
2077 gls_receiver, (void *)conn,
2078 authz_check_access_cb_func(b), b,
2079 pool);
2080 write_err = svn_ra_svn_write_word(conn, pool, "done");
2081 if (write_err)
2083 svn_error_clear(err);
2084 return write_err;
2086 SVN_CMD_ERR(err);
2088 SVN_ERR(svn_ra_svn_write_cmd_response(conn, pool, ""));
2090 return SVN_NO_ERROR;
2093 /* This implements svn_write_fn_t. Write LEN bytes starting at DATA to the
2094 client as a string. */
2095 static svn_error_t *svndiff_handler(void *baton, const char *data,
2096 apr_size_t *len)
2098 file_revs_baton_t *b = baton;
2099 svn_string_t str;
2101 str.data = data;
2102 str.len = *len;
2103 return svn_ra_svn_write_string(b->conn, b->pool, &str);
2106 /* This implements svn_close_fn_t. Mark the end of the data by writing an
2107 empty string to the client. */
2108 static svn_error_t *svndiff_close_handler(void *baton)
2110 file_revs_baton_t *b = baton;
2112 SVN_ERR(svn_ra_svn_write_cstring(b->conn, b->pool, ""));
2113 return SVN_NO_ERROR;
2116 /* This implements the svn_repos_file_rev_handler_t interface. */
2117 static svn_error_t *file_rev_handler(void *baton, const char *path,
2118 svn_revnum_t rev, apr_hash_t *rev_props,
2119 svn_boolean_t merged_revision,
2120 svn_txdelta_window_handler_t *d_handler,
2121 void **d_baton,
2122 apr_array_header_t *prop_diffs,
2123 apr_pool_t *pool)
2125 file_revs_baton_t *frb = baton;
2126 svn_stream_t *stream;
2128 SVN_ERR(svn_ra_svn_write_tuple(frb->conn, pool, "cr(!",
2129 path, rev));
2130 SVN_ERR(svn_ra_svn_write_proplist(frb->conn, pool, rev_props));
2131 SVN_ERR(svn_ra_svn_write_tuple(frb->conn, pool, "!)(!"));
2132 SVN_ERR(write_prop_diffs(frb->conn, pool, prop_diffs));
2133 SVN_ERR(svn_ra_svn_write_tuple(frb->conn, pool, "!)b", merged_revision));
2135 /* Store the pool for the delta stream. */
2136 frb->pool = pool;
2138 /* Prepare for the delta or just write an empty string. */
2139 if (d_handler)
2141 stream = svn_stream_create(baton, pool);
2142 svn_stream_set_write(stream, svndiff_handler);
2143 svn_stream_set_close(stream, svndiff_close_handler);
2145 if (svn_ra_svn_has_capability(frb->conn, SVN_RA_SVN_CAP_SVNDIFF1))
2146 svn_txdelta_to_svndiff2(d_handler, d_baton, stream, 1, pool);
2147 else
2148 svn_txdelta_to_svndiff2(d_handler, d_baton, stream, 0, pool);
2150 else
2151 SVN_ERR(svn_ra_svn_write_cstring(frb->conn, pool, ""));
2153 return SVN_NO_ERROR;
2156 static svn_error_t *get_file_revs(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
2157 apr_array_header_t *params, void *baton)
2159 server_baton_t *b = baton;
2160 svn_error_t *err, *write_err;
2161 file_revs_baton_t frb;
2162 svn_revnum_t start_rev, end_rev;
2163 const char *path;
2164 const char *full_path;
2165 apr_uint64_t include_merged_revs_param;
2166 svn_boolean_t include_merged_revisions;
2168 /* Parse arguments. */
2169 SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "c(?r)(?r)?B",
2170 &path, &start_rev, &end_rev,
2171 &include_merged_revs_param));
2172 path = svn_path_canonicalize(path, pool);
2173 SVN_ERR(trivial_auth_request(conn, pool, b));
2174 full_path = svn_path_join(b->fs_path->data, path, pool);
2176 if (include_merged_revs_param == SVN_RA_SVN_UNSPECIFIED_NUMBER)
2177 include_merged_revisions = FALSE;
2178 else
2179 include_merged_revisions = (svn_boolean_t) include_merged_revs_param;
2181 SLOG("%s", svn_log__get_file_revs(full_path, start_rev, end_rev,
2182 include_merged_revisions, pool));
2184 frb.conn = conn;
2185 frb.pool = NULL;
2187 err = svn_repos_get_file_revs2(b->repos, full_path, start_rev, end_rev,
2188 include_merged_revisions,
2189 authz_check_access_cb_func(b), b,
2190 file_rev_handler, &frb, pool);
2191 write_err = svn_ra_svn_write_word(conn, pool, "done");
2192 if (write_err)
2194 svn_error_clear(err);
2195 return write_err;
2197 SVN_CMD_ERR(err);
2198 SVN_ERR(svn_ra_svn_write_cmd_response(conn, pool, ""));
2200 return SVN_NO_ERROR;
2203 static svn_error_t *lock(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
2204 apr_array_header_t *params, void *baton)
2206 server_baton_t *b = baton;
2207 const char *path;
2208 const char *comment;
2209 const char *full_path;
2210 svn_boolean_t steal_lock;
2211 svn_revnum_t current_rev;
2212 svn_lock_t *l;
2214 SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "c(?c)b(?r)", &path, &comment,
2215 &steal_lock, &current_rev));
2216 full_path = svn_path_join(b->fs_path->data,
2217 svn_path_canonicalize(path, pool), pool);
2219 SVN_ERR(must_have_access(conn, pool, b, svn_authz_write,
2220 full_path, TRUE));
2221 SLOG("%s", svn_log__lock_one_path(full_path, steal_lock, pool));
2223 SVN_CMD_ERR(svn_repos_fs_lock(&l, b->repos, full_path, NULL, comment, 0,
2224 0, /* No expiration time. */
2225 current_rev, steal_lock, pool));
2227 SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "w(!", "success"));
2228 SVN_ERR(write_lock(conn, pool, l));
2229 SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "!)"));
2231 return SVN_NO_ERROR;
2234 static svn_error_t *lock_many(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
2235 apr_array_header_t *params, void *baton)
2237 server_baton_t *b = baton;
2238 apr_array_header_t *path_revs;
2239 const char *comment;
2240 svn_boolean_t steal_lock;
2241 int i;
2242 apr_pool_t *subpool;
2243 const char *path;
2244 const char *full_path;
2245 svn_revnum_t current_rev;
2246 apr_array_header_t *log_paths;
2247 svn_lock_t *l;
2248 svn_error_t *err = SVN_NO_ERROR, *write_err;
2250 SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "(?c)bl", &comment, &steal_lock,
2251 &path_revs));
2253 subpool = svn_pool_create(pool);
2255 /* Because we can only send a single auth reply per request, we send
2256 a reply before parsing the lock commands. This means an authz
2257 access denial will abort the processing of the locks and return
2258 an error. */
2259 SVN_ERR(must_have_access(conn, pool, b, svn_authz_write, NULL, TRUE));
2261 /* Loop through the lock requests. */
2262 log_paths = apr_array_make(pool, path_revs->nelts, sizeof(full_path));
2263 for (i = 0; i < path_revs->nelts; ++i)
2265 svn_ra_svn_item_t *item = &APR_ARRAY_IDX(path_revs, i,
2266 svn_ra_svn_item_t);
2268 svn_pool_clear(subpool);
2270 if (item->kind != SVN_RA_SVN_LIST)
2271 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2272 "Lock requests should be list of lists");
2274 SVN_ERR(svn_ra_svn_parse_tuple(item->u.list, pool, "c(?r)", &path,
2275 &current_rev));
2277 /* Allocate the full_path out of pool so it will survive for use
2278 * by operational logging, after this loop. */
2279 full_path = svn_path_join(b->fs_path->data,
2280 svn_path_canonicalize(path, subpool),
2281 pool);
2282 APR_ARRAY_PUSH(log_paths, const char *) = full_path;
2284 if (! lookup_access(pool, b, svn_authz_write, full_path, TRUE))
2286 err = svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED,
2287 NULL, NULL);
2288 break;
2291 err = svn_repos_fs_lock(&l, b->repos, full_path,
2292 NULL, comment, FALSE,
2293 0, /* No expiration time. */
2294 current_rev,
2295 steal_lock, subpool);
2297 if (err)
2299 if (SVN_ERR_IS_LOCK_ERROR(err))
2301 write_err = svn_ra_svn_write_cmd_failure(conn, pool, err);
2302 svn_error_clear(err);
2303 err = NULL;
2304 SVN_ERR(write_err);
2306 else
2307 break;
2309 else
2311 SVN_ERR(svn_ra_svn_write_tuple(conn, subpool, "w!", "success"));
2312 SVN_ERR(write_lock(conn, subpool, l));
2313 SVN_ERR(svn_ra_svn_write_tuple(conn, subpool, "!"));
2317 svn_pool_destroy(subpool);
2319 SLOG("%s", svn_log__lock(log_paths, steal_lock, pool));
2321 /* NOTE: err might contain a fatal locking error from the loop above. */
2322 write_err = svn_ra_svn_write_word(conn, pool, "done");
2323 if (!write_err)
2324 SVN_CMD_ERR(err);
2325 svn_error_clear(err);
2326 SVN_ERR(write_err);
2327 SVN_ERR(svn_ra_svn_write_cmd_response(conn, pool, ""));
2329 return SVN_NO_ERROR;
2332 static svn_error_t *unlock(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
2333 apr_array_header_t *params, void *baton)
2335 server_baton_t *b = baton;
2336 const char *path, *token, *full_path;
2337 svn_boolean_t break_lock;
2339 SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "c(?c)b", &path, &token,
2340 &break_lock));
2342 full_path = svn_path_join(b->fs_path->data, svn_path_canonicalize(path, pool),
2343 pool);
2345 /* Username required unless break_lock was specified. */
2346 SVN_ERR(must_have_access(conn, pool, b, svn_authz_write,
2347 full_path, ! break_lock));
2348 SLOG("%s", svn_log__unlock_one_path(full_path, break_lock, pool));
2350 SVN_CMD_ERR(svn_repos_fs_unlock(b->repos, full_path, token, break_lock,
2351 pool));
2353 SVN_ERR(svn_ra_svn_write_cmd_response(conn, pool, ""));
2355 return SVN_NO_ERROR;
2358 static svn_error_t *unlock_many(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
2359 apr_array_header_t *params, void *baton)
2361 server_baton_t *b = baton;
2362 svn_boolean_t break_lock;
2363 apr_array_header_t *unlock_tokens;
2364 int i;
2365 apr_pool_t *subpool;
2366 const char *path;
2367 const char *full_path;
2368 apr_array_header_t *log_paths;
2369 const char *token;
2370 svn_error_t *err = SVN_NO_ERROR, *write_err;
2372 SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "bl", &break_lock,
2373 &unlock_tokens));
2375 /* Username required unless break_lock was specified. */
2376 SVN_ERR(must_have_access(conn, pool, b, svn_authz_write, NULL, ! break_lock));
2378 subpool = svn_pool_create(pool);
2380 /* Loop through the unlock requests. */
2381 log_paths = apr_array_make(pool, unlock_tokens->nelts, sizeof(full_path));
2382 for (i = 0; i < unlock_tokens->nelts; i++)
2384 svn_ra_svn_item_t *item = &APR_ARRAY_IDX(unlock_tokens, i,
2385 svn_ra_svn_item_t);
2387 svn_pool_clear(subpool);
2389 if (item->kind != SVN_RA_SVN_LIST)
2390 return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
2391 "Unlock request should be a list of lists");
2393 SVN_ERR(svn_ra_svn_parse_tuple(item->u.list, subpool, "c(?c)", &path,
2394 &token));
2396 /* Allocate the full_path out of pool so it will survive for use
2397 * by operational logging, after this loop. */
2398 full_path = svn_path_join(b->fs_path->data,
2399 svn_path_canonicalize(path, subpool),
2400 pool);
2401 APR_ARRAY_PUSH(log_paths, const char *) = full_path;
2403 if (! lookup_access(subpool, b, svn_authz_write, full_path,
2404 ! break_lock))
2405 return svn_error_create(SVN_ERR_RA_SVN_CMD_ERR,
2406 svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED,
2407 NULL, NULL), NULL);
2409 err = svn_repos_fs_unlock(b->repos, full_path, token, break_lock,
2410 subpool);
2411 if (err)
2413 if (SVN_ERR_IS_UNLOCK_ERROR(err))
2415 write_err = svn_ra_svn_write_cmd_failure(conn, pool, err);
2416 svn_error_clear(err);
2417 err = NULL;
2418 SVN_ERR(write_err);
2420 else
2421 break;
2423 else
2424 SVN_ERR(svn_ra_svn_write_tuple(conn, subpool, "w(c)", "success",
2425 path));
2428 svn_pool_destroy(subpool);
2430 SLOG("%s", svn_log__unlock(log_paths, break_lock, pool));
2432 /* NOTE: err might contain a fatal unlocking error from the loop above. */
2433 write_err = svn_ra_svn_write_word(conn, pool, "done");
2434 if (! write_err)
2435 SVN_CMD_ERR(err);
2436 svn_error_clear(err);
2437 SVN_ERR(svn_ra_svn_write_cmd_response(conn, pool, ""));
2439 return SVN_NO_ERROR;
2442 static svn_error_t *get_lock(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
2443 apr_array_header_t *params, void *baton)
2445 server_baton_t *b = baton;
2446 const char *path;
2447 const char *full_path;
2448 svn_lock_t *l;
2450 SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "c", &path));
2452 full_path = svn_path_join(b->fs_path->data, svn_path_canonicalize(path,
2453 pool),
2454 pool);
2456 SVN_ERR(must_have_access(conn, pool, b, svn_authz_read,
2457 full_path, FALSE));
2458 SLOG("get-lock %s", svn_path_uri_encode(full_path, pool));
2460 SVN_CMD_ERR(svn_fs_get_lock(&l, b->fs, full_path, pool));
2462 SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "w((!", "success"));
2463 if (l)
2464 SVN_ERR(write_lock(conn, pool, l));
2465 SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "!))"));
2467 return SVN_NO_ERROR;
2470 static svn_error_t *get_locks(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
2471 apr_array_header_t *params, void *baton)
2473 server_baton_t *b = baton;
2474 const char *path;
2475 const char *full_path;
2476 apr_hash_t *locks;
2477 apr_hash_index_t *hi;
2478 void *val;
2479 svn_lock_t *l;
2481 SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "c", &path));
2483 full_path = svn_path_join(b->fs_path->data, svn_path_canonicalize(path,
2484 pool),
2485 pool);
2487 SVN_ERR(trivial_auth_request(conn, pool, b));
2489 SLOG("get-locks %s", svn_path_uri_encode(full_path, pool));
2490 SVN_CMD_ERR(svn_repos_fs_get_locks(&locks, b->repos, full_path,
2491 authz_check_access_cb_func(b), b, pool));
2493 SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "w((!", "success"));
2494 for (hi = apr_hash_first(pool, locks); hi; hi = apr_hash_next(hi))
2496 apr_hash_this(hi, NULL, NULL, &val);
2497 l = val;
2498 SVN_ERR(write_lock(conn, pool, l));
2500 SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "!))"));
2502 return SVN_NO_ERROR;
2505 static svn_error_t *replay_one_revision(svn_ra_svn_conn_t *conn,
2506 server_baton_t *b,
2507 svn_revnum_t rev,
2508 svn_revnum_t low_water_mark,
2509 svn_boolean_t send_deltas,
2510 apr_pool_t *pool)
2512 const svn_delta_editor_t *editor;
2513 void *edit_baton;
2514 svn_fs_root_t *root;
2515 svn_error_t *err;
2517 SVN_ERR(svnserve_log(b, conn, pool,
2518 svn_log__replay(b->fs_path->data, low_water_mark,
2519 pool)));
2521 svn_ra_svn_get_editor(&editor, &edit_baton, conn, pool, NULL, NULL);
2523 err = svn_fs_revision_root(&root, b->fs, rev, pool);
2525 if (! err)
2526 err = svn_repos_replay2(root, b->fs_path->data, low_water_mark,
2527 send_deltas, editor, edit_baton,
2528 authz_check_access_cb_func(b), b, pool);
2530 if (err)
2531 svn_error_clear(editor->abort_edit(edit_baton, pool));
2532 SVN_CMD_ERR(err);
2534 return svn_ra_svn_write_cmd(conn, pool, "finish-replay", "");
2537 static svn_error_t *replay(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
2538 apr_array_header_t *params, void *baton)
2540 svn_revnum_t rev, low_water_mark;
2541 svn_boolean_t send_deltas;
2542 server_baton_t *b = baton;
2544 SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "rrb", &rev, &low_water_mark,
2545 &send_deltas));
2547 SVN_ERR(trivial_auth_request(conn, pool, b));
2549 SVN_ERR(replay_one_revision(conn, b, rev, low_water_mark,
2550 send_deltas, pool));
2552 SVN_ERR(svn_ra_svn_write_cmd_response(conn, pool, ""));
2554 return SVN_NO_ERROR;
2557 static svn_error_t *replay_range(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
2558 apr_array_header_t *params, void *baton)
2560 svn_revnum_t start_rev, end_rev, rev, low_water_mark;
2561 svn_boolean_t send_deltas;
2562 server_baton_t *b = baton;
2563 apr_pool_t *iterpool;
2565 SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "rrrb", &start_rev,
2566 &end_rev, &low_water_mark,
2567 &send_deltas));
2569 SVN_ERR(trivial_auth_request(conn, pool, b));
2571 iterpool = svn_pool_create(pool);
2572 for (rev = start_rev; rev <= end_rev; rev++)
2574 apr_hash_t *props;
2576 svn_pool_clear(iterpool);
2578 SVN_CMD_ERR(svn_repos_fs_revision_proplist(&props, b->repos, rev,
2579 authz_check_access_cb_func(b),
2581 iterpool));
2582 SVN_ERR(svn_ra_svn_write_tuple(conn, iterpool, "w(!", "revprops"));
2583 SVN_ERR(svn_ra_svn_write_proplist(conn, iterpool, props));
2584 SVN_ERR(svn_ra_svn_write_tuple(conn, iterpool, "!)"));
2586 SVN_ERR(replay_one_revision(conn, b, rev, low_water_mark,
2587 send_deltas, iterpool));
2590 svn_pool_destroy(iterpool);
2592 SVN_ERR(svn_ra_svn_write_cmd_response(conn, pool, ""));
2594 return SVN_NO_ERROR;
2598 static const svn_ra_svn_cmd_entry_t main_commands[] = {
2599 { "reparent", reparent },
2600 { "get-latest-rev", get_latest_rev },
2601 { "get-dated-rev", get_dated_rev },
2602 { "change-rev-prop", change_rev_prop },
2603 { "rev-proplist", rev_proplist },
2604 { "rev-prop", rev_prop },
2605 { "commit", commit },
2606 { "get-file", get_file },
2607 { "get-dir", get_dir },
2608 { "update", update },
2609 { "switch", switch_cmd },
2610 { "status", status },
2611 { "diff", diff },
2612 { "get-mergeinfo", get_mergeinfo },
2613 { "log", log_cmd },
2614 { "check-path", check_path },
2615 { "stat", stat },
2616 { "get-locations", get_locations },
2617 { "get-location-segments", get_location_segments },
2618 { "get-file-revs", get_file_revs },
2619 { "lock", lock },
2620 { "lock-many", lock_many },
2621 { "unlock", unlock },
2622 { "unlock-many", unlock_many },
2623 { "get-lock", get_lock },
2624 { "get-locks", get_locks },
2625 { "replay", replay },
2626 { "replay-range", replay_range },
2627 { NULL }
2630 /* Skip past the scheme part of a URL, including the tunnel specification
2631 * if present. Return NULL if the scheme part is invalid for ra_svn. */
2632 static const char *skip_scheme_part(const char *url)
2634 if (strncmp(url, "svn", 3) != 0)
2635 return NULL;
2636 url += 3;
2637 if (*url == '+')
2638 url += strcspn(url, ":");
2639 if (strncmp(url, "://", 3) != 0)
2640 return NULL;
2641 return url + 3;
2644 /* Check that PATH is a valid repository path, meaning it doesn't contain any
2645 '..' path segments.
2646 NOTE: This is similar to svn_path_is_backpath_present, but that function
2647 assumes the path separator is '/'. This function also checks for
2648 segments delimited by the local path separator. */
2649 static svn_boolean_t
2650 repos_path_valid(const char *path)
2652 const char *s = path;
2654 while (*s)
2656 /* Scan for the end of the segment. */
2657 while (*path && *path != '/' && *path != SVN_PATH_LOCAL_SEPARATOR)
2658 ++path;
2660 /* Check for '..'. */
2661 #ifdef WIN32
2662 /* On Windows, don't allow sequences of more than one character
2663 consisting of just dots and spaces. Win32 functions treat
2664 paths such as ".. " and "......." inconsistently. Make sure
2665 no one can escape out of the root. */
2666 if (path - s >= 2 && strspn(s, ". ") == path - s)
2667 return FALSE;
2668 #else /* ! WIN32 */
2669 if (path - s == 2 && s[0] == '.' && s[1] == '.')
2670 return FALSE;
2671 #endif
2673 /* Skip all separators. */
2674 while (*path && (*path == '/' || *path == SVN_PATH_LOCAL_SEPARATOR))
2675 ++path;
2676 s = path;
2679 return TRUE;
2682 /* Look for the repository given by URL, using ROOT as the virtual
2683 * repository root. If we find one, fill in the repos, fs, cfg,
2684 * repos_url, and fs_path fields of B. Set B->repos's client
2685 * capabilities to CAPABILITIES, which must be at least as long-lived
2686 * as POOL, and whose elements are SVN_RA_CAPABILITY_*.
2688 static svn_error_t *find_repos(const char *url, const char *root,
2689 server_baton_t *b,
2690 apr_array_header_t *capabilities,
2691 apr_pool_t *pool)
2693 const char *path, *full_path, *repos_root, *fs_path;
2694 svn_stringbuf_t *url_buf;
2696 /* Skip past the scheme and authority part. */
2697 path = skip_scheme_part(url);
2698 if (path == NULL)
2699 return svn_error_createf(SVN_ERR_BAD_URL, NULL,
2700 "Non-svn URL passed to svn server: '%s'", url);
2701 path = strchr(path, '/');
2702 path = (path == NULL) ? "" : path + 1;
2704 /* Decode URI escapes from the path. */
2705 path = svn_path_uri_decode(path, pool);
2707 /* Ensure that it isn't possible to escape the root by skipping leading
2708 slashes and not allowing '..' segments. */
2709 while (*path == '/')
2710 ++path;
2711 if (!repos_path_valid(path))
2712 return svn_error_create(SVN_ERR_BAD_FILENAME, NULL,
2713 "Couldn't determine repository path");
2715 /* Join the server-configured root with the client path. */
2716 full_path = svn_path_join(svn_path_canonicalize(root, pool),
2717 svn_path_canonicalize(path, pool), pool);
2719 /* Search for a repository in the full path. */
2720 repos_root = svn_repos_find_root_path(full_path, pool);
2721 if (!repos_root)
2722 return svn_error_createf(SVN_ERR_RA_SVN_REPOS_NOT_FOUND, NULL,
2723 "No repository found in '%s'", url);
2725 /* Open the repository and fill in b with the resulting information. */
2726 SVN_ERR(svn_repos_open(&b->repos, repos_root, pool));
2727 SVN_ERR(svn_repos_remember_client_capabilities(b->repos, capabilities));
2728 b->fs = svn_repos_fs(b->repos);
2729 fs_path = full_path + strlen(repos_root);
2730 b->fs_path = svn_stringbuf_create(*fs_path ? fs_path : "/", pool);
2731 url_buf = svn_stringbuf_create(url, pool);
2732 svn_path_remove_components(url_buf,
2733 svn_path_component_count(b->fs_path->data));
2734 b->repos_url = url_buf->data;
2735 b->authz_repos_name = svn_path_is_child(root, repos_root, pool);
2736 if (b->authz_repos_name == NULL)
2737 b->repos_name = svn_path_basename(repos_root, pool);
2738 else
2739 b->repos_name = b->authz_repos_name;
2740 b->repos_name = svn_path_uri_encode(b->repos_name, pool);
2742 /* If the svnserve configuration files have not been loaded then
2743 load them from the repository. */
2744 if (NULL == b->cfg)
2745 SVN_ERR(load_configs(&b->cfg, &b->pwdb, &b->authzdb,
2746 svn_repos_svnserve_conf(b->repos, pool), FALSE,
2747 svn_repos_conf_dir(b->repos, pool),
2748 pool));
2750 #ifdef SVN_HAVE_SASL
2751 /* Should we use Cyrus SASL? */
2752 svn_config_get_bool(b->cfg, &b->use_sasl, SVN_CONFIG_SECTION_SASL,
2753 SVN_CONFIG_OPTION_USE_SASL, FALSE);
2754 #endif
2756 /* Use the repository UUID as the default realm. */
2757 SVN_ERR(svn_fs_get_uuid(b->fs, &b->realm, pool));
2758 svn_config_get(b->cfg, &b->realm, SVN_CONFIG_SECTION_GENERAL,
2759 SVN_CONFIG_OPTION_REALM, b->realm);
2761 /* Make sure it's possible for the client to authenticate. Note
2762 that this doesn't take into account any authz configuration read
2763 above, because we can't know about access it grants until paths
2764 are given by the client. */
2765 if (get_access(b, UNAUTHENTICATED) == NO_ACCESS
2766 && (get_access(b, AUTHENTICATED) == NO_ACCESS
2767 || (!b->tunnel_user && !b->pwdb && !b->use_sasl)))
2768 return svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL,
2769 "No access allowed to this repository");
2770 return SVN_NO_ERROR;
2773 /* Compute the authentication name EXTERNAL should be able to get, if any. */
2774 static const char *get_tunnel_user(serve_params_t *params, apr_pool_t *pool)
2776 /* Only offer EXTERNAL for connections tunneled over a login agent. */
2777 if (!params->tunnel)
2778 return NULL;
2780 /* If a tunnel user was provided on the command line, use that. */
2781 if (params->tunnel_user)
2782 return params->tunnel_user;
2784 return svn_user_get_name(pool);
2787 svn_error_t *serve(svn_ra_svn_conn_t *conn, serve_params_t *params,
2788 apr_pool_t *pool)
2790 svn_error_t *err, *io_err;
2791 apr_uint64_t ver;
2792 const char *uuid, *client_url;
2793 apr_array_header_t *caplist, *cap_words;
2794 server_baton_t b;
2795 fs_warning_baton_t warn_baton;
2797 b.tunnel = params->tunnel;
2798 b.tunnel_user = get_tunnel_user(params, pool);
2799 b.read_only = params->read_only;
2800 b.user = NULL;
2801 b.cfg = params->cfg;
2802 b.pwdb = params->pwdb;
2803 b.authzdb = params->authzdb;
2804 b.realm = NULL;
2805 b.log_file = params->log_file;
2806 b.pool = pool;
2807 b.use_sasl = FALSE;
2809 /* Send greeting. We don't support version 1 any more, so we can
2810 * send an empty mechlist. */
2811 SVN_ERR(svn_ra_svn_write_cmd_response(conn, pool, "nn()(wwwwwww)",
2812 (apr_uint64_t) 2, (apr_uint64_t) 2,
2813 SVN_RA_SVN_CAP_EDIT_PIPELINE,
2814 SVN_RA_SVN_CAP_SVNDIFF1,
2815 SVN_RA_SVN_CAP_ABSENT_ENTRIES,
2816 SVN_RA_SVN_CAP_COMMIT_REVPROPS,
2817 SVN_RA_SVN_CAP_DEPTH,
2818 SVN_RA_SVN_CAP_LOG_REVPROPS,
2819 SVN_RA_SVN_CAP_PARTIAL_REPLAY));
2821 /* Read client response, which we assume to be in version 2 format:
2822 * version, capability list, and client URL; then we do an auth
2823 * request. */
2824 SVN_ERR(svn_ra_svn_read_tuple(conn, pool, "nlc",
2825 &ver, &caplist, &client_url));
2826 if (ver != 2)
2827 return SVN_NO_ERROR;
2829 client_url = svn_path_canonicalize(client_url, pool);
2830 SVN_ERR(svn_ra_svn_set_capabilities(conn, caplist));
2832 /* All released versions of Subversion support edit-pipeline,
2833 * so we do not accept connections from clients that do not. */
2834 if (! svn_ra_svn_has_capability(conn, SVN_RA_SVN_CAP_EDIT_PIPELINE))
2835 return SVN_NO_ERROR;
2837 /* find_repos needs the capabilities as a list of words (eventually
2838 they get handed to the start-commit hook). While we could add a
2839 new interface to re-retrieve them from conn and convert the
2840 result to a list, it's simpler to just convert caplist by hand
2841 here, since we already have it and turning 'svn_ra_svn_item_t's
2842 into 'const char *'s is pretty easy.
2844 We only record capabilities we care about. The client may report
2845 more (because it doesn't know what the server cares about). */
2847 int i;
2848 svn_ra_svn_item_t *item;
2850 cap_words = apr_array_make(pool, 1, sizeof(const char *));
2851 for (i = 0; i < caplist->nelts; i++)
2853 item = &APR_ARRAY_IDX(caplist, i, svn_ra_svn_item_t);
2854 /* ra_svn_set_capabilities() already type-checked for us */
2855 if (strcmp(item->u.word, SVN_RA_SVN_CAP_MERGEINFO) == 0)
2857 APR_ARRAY_PUSH(cap_words, const char *)
2858 = SVN_RA_CAPABILITY_MERGEINFO;
2863 err = find_repos(client_url, params->root, &b, cap_words, pool);
2864 if (!err)
2866 SVN_ERR(auth_request(conn, pool, &b, READ_ACCESS, FALSE));
2867 if (current_access(&b) == NO_ACCESS)
2868 err = svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL,
2869 "Not authorized for access");
2871 if (err)
2873 io_err = svn_ra_svn_write_cmd_failure(conn, pool, err);
2874 svn_error_clear(err);
2875 SVN_ERR(io_err);
2876 return svn_ra_svn_flush(conn, pool);
2879 warn_baton.server = &b;
2880 warn_baton.conn = conn;
2881 warn_baton.pool = svn_pool_create(pool);
2882 svn_fs_set_warning_func(b.fs, log_fs_warning, &warn_baton);
2884 SVN_ERR(svn_fs_get_uuid(b.fs, &uuid, pool));
2886 /* We can't claim mergeinfo capability until we know whether the
2887 repository supports mergeinfo (i.e., is not a 1.4 repository),
2888 but we don't get the repository url from the client until after
2889 we've already sent the initial list of server capabilities. So
2890 we list repository capabilities here, in our first response after
2891 the client has sent the url. */
2893 svn_boolean_t supports_mergeinfo;
2894 SVN_ERR(svn_repos_has_capability(b.repos, &supports_mergeinfo,
2895 SVN_REPOS_CAPABILITY_MERGEINFO, pool));
2897 SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "w(cc(!",
2898 "success", uuid, b.repos_url));
2899 if (supports_mergeinfo)
2900 SVN_ERR(svn_ra_svn_write_word(conn, pool, SVN_RA_SVN_CAP_MERGEINFO));
2901 SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "!))"));
2904 return svn_ra_svn_handle_commands(conn, pool, main_commands, &b);