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 */
25 #define APR_WANT_STRFUNC
27 #include <apr_general.h>
29 #include <apr_strings.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"
44 #include "svn_config.h"
45 #include "svn_props.h"
46 #include "svn_mergeinfo.h"
49 #include "private/svn_log.h"
50 #include "private/svn_mergeinfo_private.h"
53 #include <unistd.h> /* For getpid() */
60 svn_revnum_t
*new_rev
;
63 const char **post_commit_err
;
64 } commit_callback_baton_t
;
68 const char *repos_url
; /* Decoded repository URL. */
71 /* so update() can distinguish checkout from update in logging */
73 svn_boolean_t only_empty_entries
;
74 /* for diff() logging */
75 svn_revnum_t
*from_rev
;
76 } report_driver_baton_t
;
80 svn_ra_svn_conn_t
*conn
;
85 svn_ra_svn_conn_t
*conn
;
86 apr_pool_t
*pool
; /* Pool provided in the handler call. */
90 server_baton_t
*server
;
91 svn_ra_svn_conn_t
*conn
;
95 svn_error_t
*load_configs(svn_config_t
**cfg
,
97 svn_authz_t
**authzdb
,
99 svn_boolean_t must_exist
,
103 const char *pwdb_path
, *authzdb_path
;
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
);
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
);
128 /* Read authz configuration. */
129 svn_config_get(*cfg
, &authzdb_path
, SVN_CONFIG_SECTION_GENERAL
,
130 SVN_CONFIG_OPTION_AUTHZ_DB
, NULL
);
133 authzdb_path
= svn_path_join(base
, authzdb_path
, pool
);
134 SVN_ERR(svn_repos_authz_read(authzdb
, authzdb_path
, TRUE
, pool
));
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
)
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'",
162 *fs_path
= url
+ len
;
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
;
177 /* 8192 from MAX_STRING_LEN in from httpd-2.2.4/include/httpd.h */
180 if (server
->log_file
== NULL
)
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
: "-");
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
),
196 " %s %s %s %s ERR%s %d ",
197 getpid(), timestr
, remote_host
, user
,
198 server
->repos_name
, continuation
,
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
,
217 static svn_error_t
*svnserve_log(server_baton_t
*b
,
218 svn_ra_svn_conn_t
*conn
,
220 const char *fmt
, ...)
222 const char *remote_host
, *timestr
, *log
, *line
;
226 if (b
->log_file
== NULL
)
229 remote_host
= svn_ra_svn_conn_remote_host(conn
);
230 timestr
= svn_time_to_cstring(apr_time_now(), pool
);
233 log
= apr_pvsprintf(pool
, fmt
, ap
);
236 line
= apr_psprintf(pool
, "%" APR_PID_T_FMT
237 " %s %s %s %s %s" APR_EOL_STR
,
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
,
255 svn_repos_authz_access_t required
,
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. */
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
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
,
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
,
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
)
303 return authz_check_access_cb
;
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
,
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
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"));
359 /* Context for cleanup handler. */
360 struct cleanup_fs_access_baton
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
)
371 struct cleanup_fs_access_baton
*baton
= data
;
373 serr
= svn_fs_set_access(baton
->fs
, NULL
);
376 apr_status_t apr_err
= serr
->apr_err
;
377 svn_error_clear(serr
);
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. */
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
;
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
);
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
)
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"));
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"));
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
);
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. */
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
));
473 SVN_ERR(auth(conn
, pool
, mech
, mecharg
, b
, required
, needs_username
,
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
)
490 return cyrus_auth_request(conn
, pool
, b
, required
, needs_username
);
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
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
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
,
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
;
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
536 svn_error_clear(err
);
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
545 && (! needs_username
|| baton
->user
))
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
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
,
568 svn_repos_authz_access_t required
,
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. */
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
,
602 /* Else, access is granted, and there is much rejoicing. */
603 SVN_ERR(create_fs_access(b
, pool
));
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
;
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
,
629 depth
= svn_depth_from_word(depth_word
);
630 path
= svn_path_canonicalize(path
, pool
);
631 if (b
->from_rev
&& strcmp(path
, "") == 0)
634 b
->err
= svn_repos_set_path3(b
->report_baton
, path
, rev
, depth
,
635 start_empty
, lock_token
, pool
);
638 b
->only_empty_entries
= FALSE
;
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
;
648 SVN_ERR(svn_ra_svn_parse_tuple(params
, pool
, "c", &path
));
649 path
= svn_path_canonicalize(path
, pool
);
651 b
->err
= svn_repos_delete_path(b
->report_baton
, path
, pool
);
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
;
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
);
671 depth
= svn_depth_from_word(depth_word
);
673 b
->err
= get_fs_path(svn_path_uri_decode(b
->repos_url
, pool
),
676 b
->err
= svn_repos_link_path3(b
->report_baton
, path
, fs_path
, rev
,
677 depth
, start_empty
, lock_token
, pool
);
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
));
690 b
->err
= svn_repos_finish_report(b
->report_baton
, pool
);
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
));
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
},
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
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
,
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
;
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
,
749 authz_check_access_cb_func(b
),
753 rb
.repos_url
= svn_path_uri_decode(b
->repos_url
, pool
);
754 rb
.report_baton
= report_baton
;
756 rb
.entry_counter
= 0;
757 rb
.only_empty_entries
= TRUE
;
758 rb
.from_rev
= from_rev
;
760 *from_rev
= SVN_INVALID_REVNUM
;
761 err
= svn_ra_svn_handle_commands(conn
, pool
, report_commands
, &rb
);
764 /* Network or protocol error while handling commands. */
765 svn_error_clear(rb
.err
);
770 /* Some failure during the reporting or editing operations. */
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
;
781 /* --- MAIN COMMAND SET --- */
783 /* Write out a list of property diffs. PROPDIFFS is an array of svn_prop_t
785 static svn_error_t
*write_prop_diffs(svn_ra_svn_conn_t
*conn
,
787 apr_array_header_t
*propdiffs
)
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
));
802 /* Write out a lock to the client. */
803 static svn_error_t
*write_lock(svn_ra_svn_conn_t
*conn
,
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
,
819 static const char *kind_word(svn_node_kind_t kind
)
829 case svn_node_unknown
:
835 /* Make the compiler happy */
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
)
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
,
854 str
= svn_string_create(apr_psprintf(pool
, "%ld", crev
),
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
,
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
);
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
;
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
),
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
, ""));
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
;
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
));
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
;
912 SVN_ERR(svn_ra_svn_parse_tuple(params
, pool
, "c", ×tr
));
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
));
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
;
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
,
940 SVN_ERR(svn_ra_svn_write_cmd_response(conn
, pool
, ""));
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
;
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
,
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
, "!))"));
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
;
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
,
979 SVN_ERR(svn_ra_svn_write_cmd_response(conn
, pool
, "(?s)", value
));
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
;
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
,
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. */
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
,
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
),
1044 if (! lookup_access(pool
, sb
, svn_authz_write
,
1046 return svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED
,
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
,
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
),
1084 /* The lock may have become defunct after the commit, so ignore such
1087 ### If we ever write a logging facility for svnserve, this
1088 would be a good place to log an error before clearing
1090 svn_error_clear(svn_repos_fs_unlock(sb
->repos
, full_path
, token
,
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
,
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
;
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
));
1124 revprop_list
= NULL
;
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
,
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
1139 SVN_ERR(must_have_access(conn
, pool
, b
, svn_authz_write
,
1141 (lock_tokens
&& lock_tokens
->nelts
) ? TRUE
: FALSE
));
1143 /* Authorize the lock tokens and give them to the FS if we got
1145 if (lock_tokens
&& lock_tokens
->nelts
)
1146 SVN_CMD_ERR(add_lock_tokens(conn
, lock_tokens
, b
, pool
));
1149 SVN_ERR(svn_ra_svn_parse_proplist(revprop_list
, pool
, &revprop_table
));
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
);
1163 ccb
.new_rev
= &new_rev
;
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
,
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
));
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. */
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
));
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
;
1208 svn_fs_root_t
*root
;
1209 svn_stream_t
*contents
;
1210 apr_hash_t
*props
= NULL
;
1211 svn_string_t write_str
;
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
,
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
);
1238 SVN_CMD_ERR(get_props(&props
, root
, full_path
, pool
));
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",
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. */
1255 err
= svn_stream_read(contents
, buf
, &len
);
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
);
1270 write_err
= svn_ra_svn_write_cstring(conn
, pool
, "");
1273 svn_error_clear(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
;
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
;
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
;
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
;
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
,
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. */
1355 SVN_CMD_ERR(get_props(&props
, root
, full_path
, pool
));
1357 /* Fetch the directory entries if requested. */
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
);
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
)
1379 entry
->kind
= fsent
->kind
;
1382 if (dirent_fields
& SVN_DIRENT_SIZE
)
1385 if (entry
->kind
== svn_node_dir
)
1388 SVN_CMD_ERR(svn_fs_file_length(&entry
->size
, root
, file_path
,
1392 if (dirent_fields
& SVN_DIRENT_HAS_PROPS
)
1395 SVN_CMD_ERR(svn_fs_node_proplist(&file_props
, root
, file_path
,
1397 entry
->has_props
= (apr_hash_count(file_props
) > 0) ? TRUE
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
,
1411 entry
->last_author
= apr_pstrdup(pool
, cauthor
);
1413 SVN_CMD_ERR(svn_time_from_cstring(&entry
->time
, cdate
,
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
, "!)(!"));
1431 for (hi
= apr_hash_first(pool
, entries
); hi
; hi
= apr_hash_next(hi
))
1433 apr_hash_this(hi
, &key
, NULL
, &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
;
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
);
1469 depth
= svn_depth_from_word(depth_word
);
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
));
1487 SLOG("%s", svn_log__checkout(full_path
, rev
, depth
, pool
));
1489 SLOG("%s", svn_log__update(full_path
, rev
, depth
, send_copyfrom_args
,
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
;
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
);
1514 depth
= svn_depth_from_word(depth_word
);
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
),
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
,
1534 FALSE
/* TODO(sussman): no copyfrom args for now */,
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
;
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
);
1555 depth
= svn_depth_from_word(depth_word
);
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
;
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
));
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
);
1604 depth
= svn_depth_from_word(depth_word
);
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
),
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
;
1639 apr_array_header_t
*paths
, *canonical_paths
;
1640 svn_mergeinfo_catalog_t mergeinfo
;
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
,
1666 APR_ARRAY_PUSH(canonical_paths
, const char *) = full_path
;
1668 SLOG("%s", svn_log__get_mergeinfo(canonical_paths
, inherit
, include_descendants
,
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
,
1675 include_descendants
,
1676 authz_check_access_cb_func(b
), b
,
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
))
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
,
1694 SVN_ERR(svn_ra_svn_write_tuple(conn
, iterpool
, "cs", (const char *) key
,
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
,
1708 log_baton_t
*b
= baton
;
1709 svn_ra_svn_conn_t
*conn
= b
->conn
;
1710 apr_hash_index_t
*h
;
1714 svn_log_changed_path_t
*change
;
1715 svn_boolean_t invalid_revnum
= FALSE
;
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
;
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
);
1743 action
[0] = change
->action
;
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
);
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
)
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
;
1780 svn_ra_svn_item_t
*elt
;
1782 apr_uint64_t limit
, include_merged_revs_param
;
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
;
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)
1801 else if (strcmp(revprop_word
, "revprops") == 0)
1803 revprops
= apr_array_make(pool
, revprop_items
->nelts
,
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
;
1818 return svn_error_createf(SVN_ERR_RA_SVN_MALFORMED_DATA
, NULL
,
1819 _("Unknown revprop word '%s' in log command"),
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
)
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
,
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
,
1848 /* Get logs. (Can't report errors back to the client at this point.) */
1849 lb
.fs_path
= b
->fs_path
->data
;
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
,
1858 write_err
= svn_ra_svn_write_word(conn
, pool
, "done");
1861 svn_error_clear(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
;
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
,
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
;
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
,
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. */
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
;
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
;
1958 /* Parse the arguments. */
1959 SVN_ERR(svn_ra_svn_parse_tuple(params
, pool
, "crl", &relative_path
,
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
,
1982 /* All the parameters are fine - let's perform the query against the
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. */
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");
2011 svn_error_clear(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
,
2025 svn_ra_svn_conn_t
*conn
= baton
;
2026 return svn_ra_svn_write_tuple(conn
, pool
, "rr(?c)",
2027 segment
->range_start
,
2032 static svn_error_t
*get_location_segments(svn_ra_svn_conn_t
*conn
,
2034 apr_array_header_t
*params
,
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
,
2069 /* All the parameters are fine - let's perform the query against the
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
,
2080 write_err
= svn_ra_svn_write_word(conn
, pool
, "done");
2083 svn_error_clear(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
,
2098 file_revs_baton_t
*b
= baton
;
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
,
2122 apr_array_header_t
*prop_diffs
,
2125 file_revs_baton_t
*frb
= baton
;
2126 svn_stream_t
*stream
;
2128 SVN_ERR(svn_ra_svn_write_tuple(frb
->conn
, pool
, "cr(!",
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. */
2138 /* Prepare for the delta or just write an empty string. */
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
);
2148 svn_txdelta_to_svndiff2(d_handler
, d_baton
, stream
, 0, pool
);
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
;
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
;
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
));
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");
2194 svn_error_clear(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
;
2208 const char *comment
;
2209 const char *full_path
;
2210 svn_boolean_t steal_lock
;
2211 svn_revnum_t current_rev
;
2214 SVN_ERR(svn_ra_svn_parse_tuple(params
, pool
, "c(?c)b(?r)", &path
, &comment
,
2215 &steal_lock
, ¤t_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
,
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
;
2242 apr_pool_t
*subpool
;
2244 const char *full_path
;
2245 svn_revnum_t current_rev
;
2246 apr_array_header_t
*log_paths
;
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
,
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
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
,
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
,
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
),
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
,
2291 err
= svn_repos_fs_lock(&l
, b
->repos
, full_path
,
2292 NULL
, comment
, FALSE
,
2293 0, /* No expiration time. */
2295 steal_lock
, subpool
);
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
);
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");
2325 svn_error_clear(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
,
2342 full_path
= svn_path_join(b
->fs_path
->data
, svn_path_canonicalize(path
, 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
,
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
;
2365 apr_pool_t
*subpool
;
2367 const char *full_path
;
2368 apr_array_header_t
*log_paths
;
2370 svn_error_t
*err
= SVN_NO_ERROR
, *write_err
;
2372 SVN_ERR(svn_ra_svn_parse_tuple(params
, pool
, "bl", &break_lock
,
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
,
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
,
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
),
2401 APR_ARRAY_PUSH(log_paths
, const char *) = full_path
;
2403 if (! lookup_access(subpool
, b
, svn_authz_write
, full_path
,
2405 return svn_error_create(SVN_ERR_RA_SVN_CMD_ERR
,
2406 svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED
,
2409 err
= svn_repos_fs_unlock(b
->repos
, full_path
, token
, break_lock
,
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
);
2424 SVN_ERR(svn_ra_svn_write_tuple(conn
, subpool
, "w(c)", "success",
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");
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
;
2447 const char *full_path
;
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
,
2456 SVN_ERR(must_have_access(conn
, pool
, b
, svn_authz_read
,
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"));
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
;
2475 const char *full_path
;
2477 apr_hash_index_t
*hi
;
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
,
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
);
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
,
2508 svn_revnum_t low_water_mark
,
2509 svn_boolean_t send_deltas
,
2512 const svn_delta_editor_t
*editor
;
2514 svn_fs_root_t
*root
;
2517 SVN_ERR(svnserve_log(b
, conn
, pool
,
2518 svn_log__replay(b
->fs_path
->data
, low_water_mark
,
2521 svn_ra_svn_get_editor(&editor
, &edit_baton
, conn
, pool
, NULL
, NULL
);
2523 err
= svn_fs_revision_root(&root
, b
->fs
, rev
, pool
);
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
);
2531 svn_error_clear(editor
->abort_edit(edit_baton
, pool
));
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
,
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
,
2569 SVN_ERR(trivial_auth_request(conn
, pool
, b
));
2571 iterpool
= svn_pool_create(pool
);
2572 for (rev
= start_rev
; rev
<= end_rev
; rev
++)
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
),
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
},
2612 { "get-mergeinfo", get_mergeinfo
},
2614 { "check-path", check_path
},
2616 { "get-locations", get_locations
},
2617 { "get-location-segments", get_location_segments
},
2618 { "get-file-revs", get_file_revs
},
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
},
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)
2638 url
+= strcspn(url
, ":");
2639 if (strncmp(url
, "://", 3) != 0)
2644 /* Check that PATH is a valid repository path, meaning it doesn't contain any
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
;
2656 /* Scan for the end of the segment. */
2657 while (*path
&& *path
!= '/' && *path
!= SVN_PATH_LOCAL_SEPARATOR
)
2660 /* Check for '..'. */
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
)
2669 if (path
- s
== 2 && s
[0] == '.' && s
[1] == '.')
2673 /* Skip all separators. */
2674 while (*path
&& (*path
== '/' || *path
== SVN_PATH_LOCAL_SEPARATOR
))
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
,
2690 apr_array_header_t
*capabilities
,
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
);
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
== '/')
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
);
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
);
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. */
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
),
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
);
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
)
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
,
2790 svn_error_t
*err
, *io_err
;
2792 const char *uuid
, *client_url
;
2793 apr_array_header_t
*caplist
, *cap_words
;
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
;
2801 b
.cfg
= params
->cfg
;
2802 b
.pwdb
= params
->pwdb
;
2803 b
.authzdb
= params
->authzdb
;
2805 b
.log_file
= params
->log_file
;
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
2824 SVN_ERR(svn_ra_svn_read_tuple(conn
, pool
, "nlc",
2825 &ver
, &caplist
, &client_url
));
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). */
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
);
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");
2873 io_err
= svn_ra_svn_write_cmd_failure(conn
, pool
, err
);
2874 svn_error_clear(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
);