summaryrefslogtreecommitdiff
path: root/subversion/svnserve
diff options
context:
space:
mode:
authorLorry <lorry@roadtrain.codethink.co.uk>2012-08-22 14:29:52 +0100
committerLorry <lorry@roadtrain.codethink.co.uk>2012-08-22 14:29:52 +0100
commitf1bdf13786f0752c0846cf36f0d91e4fc6747929 (patch)
tree4223b2035bf2240d681a53822808b3c7f687b905 /subversion/svnserve
downloadsubversion-tarball-f1bdf13786f0752c0846cf36f0d91e4fc6747929.tar.gz
Tarball conversion
Diffstat (limited to 'subversion/svnserve')
-rw-r--r--subversion/svnserve/cyrus_auth.c381
-rw-r--r--subversion/svnserve/log-escape.c143
-rw-r--r--subversion/svnserve/main.c1054
-rw-r--r--subversion/svnserve/serve.c3238
-rw-r--r--subversion/svnserve/server.h187
-rw-r--r--subversion/svnserve/svnserve.8138
-rw-r--r--subversion/svnserve/svnserve.conf.598
-rw-r--r--subversion/svnserve/winservice.c490
-rw-r--r--subversion/svnserve/winservice.h64
9 files changed, 5793 insertions, 0 deletions
diff --git a/subversion/svnserve/cyrus_auth.c b/subversion/svnserve/cyrus_auth.c
new file mode 100644
index 0000000..8b4dda9
--- /dev/null
+++ b/subversion/svnserve/cyrus_auth.c
@@ -0,0 +1,381 @@
+/*
+ * sasl_auth.c : Functions for SASL-based authentication
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#include "svn_private_config.h"
+#ifdef SVN_HAVE_SASL
+
+#define APR_WANT_STRFUNC
+#include <apr_want.h>
+#include <apr_general.h>
+#include <apr_strings.h>
+
+#include "svn_types.h"
+#include "svn_string.h"
+#include "svn_pools.h"
+#include "svn_error.h"
+#include "svn_ra_svn.h"
+#include "svn_base64.h"
+
+#include "private/svn_atomic.h"
+#include "private/ra_svn_sasl.h"
+
+#include "server.h"
+
+/* SASL calls this function before doing anything with a username, which gives
+ us an opportunity to do some sanity-checking. If the username contains
+ an '@', SASL interprets the part following the '@' as the name of the
+ authentication realm, and worst of all, this realm overrides the one that
+ we pass to sasl_server_new(). If we didn't check this, a user that could
+ successfully authenticate in one realm would be able to authenticate
+ in any other realm, simply by appending '@realm' to his username.
+
+ Note that the value returned in *OUT does not need to be
+ '\0'-terminated; we just need to set *OUT_LEN correctly.
+*/
+static int canonicalize_username(sasl_conn_t *conn,
+ void *context, /* not used */
+ const char *in, /* the username */
+ unsigned inlen, /* its length */
+ unsigned flags, /* not used */
+ const char *user_realm,
+ char *out, /* the output buffer */
+ unsigned out_max, unsigned *out_len)
+{
+ int realm_len = strlen(user_realm);
+ char *pos;
+
+ *out_len = inlen;
+
+ /* If the username contains an '@', the part after the '@' is the realm
+ that the user wants to authenticate in. */
+ pos = memchr(in, '@', inlen);
+ if (pos)
+ {
+ /* The only valid realm is user_realm (i.e. the repository's realm).
+ If the user gave us another realm, complain. */
+ if (strncmp(pos+1, user_realm, inlen-(pos-in+1)) != 0)
+ return SASL_BADPROT;
+ }
+ else
+ *out_len += realm_len + 1;
+
+ /* First, check that the output buffer is large enough. */
+ if (*out_len > out_max)
+ return SASL_BADPROT;
+
+ /* Copy the username part. */
+ strncpy(out, in, inlen);
+
+ /* If necessary, copy the realm part. */
+ if (!pos)
+ {
+ out[inlen] = '@';
+ strncpy(&out[inlen+1], user_realm, realm_len);
+ }
+
+ return SASL_OK;
+}
+
+static sasl_callback_t callbacks[] =
+{
+ { SASL_CB_CANON_USER, canonicalize_username, NULL },
+ { SASL_CB_LIST_END, NULL, NULL }
+};
+
+static svn_error_t *initialize(void *baton, apr_pool_t *pool)
+{
+ int result;
+ apr_status_t status;
+
+ status = svn_ra_svn__sasl_common_init(pool);
+ if (status)
+ return svn_error_wrap_apr(status,
+ _("Could not initialize the SASL library"));
+
+ /* The second parameter tells SASL to look for a configuration file
+ named subversion.conf. */
+ result = sasl_server_init(callbacks, SVN_RA_SVN_SASL_NAME);
+ if (result != SASL_OK)
+ {
+ svn_error_t *err = svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL,
+ sasl_errstring(result, NULL, NULL));
+ return svn_error_quick_wrap(err,
+ _("Could not initialize the SASL library"));
+ }
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *cyrus_init(apr_pool_t *pool)
+{
+ SVN_ERR(svn_atomic__init_once(&svn_ra_svn__sasl_status,
+ initialize, NULL, pool));
+ return SVN_NO_ERROR;
+}
+
+/* Tell the client the authentication failed. This is only used during
+ the authentication exchange (i.e. inside try_auth()). */
+static svn_error_t *
+fail_auth(svn_ra_svn_conn_t *conn, apr_pool_t *pool, sasl_conn_t *sasl_ctx)
+{
+ const char *msg = sasl_errdetail(sasl_ctx);
+ SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "w(c)", "failure", msg));
+ return svn_ra_svn_flush(conn, pool);
+}
+
+/* Like svn_ra_svn_write_cmd_failure, but also clears the given error
+ and sets it to SVN_NO_ERROR. */
+static svn_error_t *
+write_failure(svn_ra_svn_conn_t *conn, apr_pool_t *pool, svn_error_t **err_p)
+{
+ svn_error_t *write_err = svn_ra_svn_write_cmd_failure(conn, pool, *err_p);
+ svn_error_clear(*err_p);
+ *err_p = SVN_NO_ERROR;
+ return write_err;
+}
+
+/* Used if we run into a SASL error outside try_auth(). */
+static svn_error_t *
+fail_cmd(svn_ra_svn_conn_t *conn, apr_pool_t *pool, sasl_conn_t *sasl_ctx)
+{
+ svn_error_t *err = svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL,
+ sasl_errdetail(sasl_ctx));
+ SVN_ERR(write_failure(conn, pool, &err));
+ return svn_ra_svn_flush(conn, pool);
+}
+
+static svn_error_t *try_auth(svn_ra_svn_conn_t *conn,
+ sasl_conn_t *sasl_ctx,
+ apr_pool_t *pool,
+ server_baton_t *b,
+ svn_boolean_t *success)
+{
+ const char *out, *mech;
+ const svn_string_t *arg = NULL, *in;
+ unsigned int outlen;
+ int result;
+ svn_boolean_t use_base64;
+
+ *success = FALSE;
+
+ /* Read the client's chosen mech and the initial token. */
+ SVN_ERR(svn_ra_svn_read_tuple(conn, pool, "w(?s)", &mech, &in));
+
+ if (strcmp(mech, "EXTERNAL") == 0 && !in)
+ in = svn_string_create(b->tunnel_user, pool);
+ else if (in)
+ in = svn_base64_decode_string(in, pool);
+
+ /* For CRAM-MD5, we don't base64-encode stuff. */
+ use_base64 = (strcmp(mech, "CRAM-MD5") != 0);
+
+ result = sasl_server_start(sasl_ctx, mech,
+ in ? in->data : NULL,
+ in ? in->len : 0, &out, &outlen);
+
+ if (result != SASL_OK && result != SASL_CONTINUE)
+ return fail_auth(conn, pool, sasl_ctx);
+
+ while (result == SASL_CONTINUE)
+ {
+ svn_ra_svn_item_t *item;
+
+ arg = svn_string_ncreate(out, outlen, pool);
+ /* Encode what we send to the client. */
+ if (use_base64)
+ arg = svn_base64_encode_string2(arg, TRUE, pool);
+
+ SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "w(s)", "step", arg));
+
+ /* Read and decode the client response. */
+ SVN_ERR(svn_ra_svn_read_item(conn, pool, &item));
+ if (item->kind != SVN_RA_SVN_STRING)
+ return SVN_NO_ERROR;
+
+ in = item->u.string;
+ if (use_base64)
+ in = svn_base64_decode_string(in, pool);
+ result = sasl_server_step(sasl_ctx, in->data, in->len, &out, &outlen);
+ }
+
+ if (result != SASL_OK)
+ return fail_auth(conn, pool, sasl_ctx);
+
+ /* Send our last response, if necessary. */
+ if (outlen)
+ arg = svn_base64_encode_string2(svn_string_ncreate(out, outlen, pool), TRUE,
+ pool);
+ else
+ arg = NULL;
+
+ *success = TRUE;
+ SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "w(?s)", "success", arg));
+
+ return SVN_NO_ERROR;
+}
+
+static apr_status_t sasl_dispose_cb(void *data)
+{
+ sasl_conn_t *sasl_ctx = (sasl_conn_t*) data;
+ sasl_dispose(&sasl_ctx);
+ return APR_SUCCESS;
+}
+
+svn_error_t *cyrus_auth_request(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ server_baton_t *b,
+ enum access_type required,
+ svn_boolean_t needs_username)
+{
+ sasl_conn_t *sasl_ctx;
+ apr_pool_t *subpool;
+ apr_status_t apr_err;
+ const char *localaddrport = NULL, *remoteaddrport = NULL;
+ const char *mechlist, *val;
+ char hostname[APRMAXHOSTLEN + 1];
+ sasl_security_properties_t secprops;
+ svn_boolean_t success, no_anonymous;
+ int mech_count, result = SASL_OK;
+
+ SVN_ERR(svn_ra_svn__get_addresses(&localaddrport, &remoteaddrport,
+ conn, pool));
+ apr_err = apr_gethostname(hostname, sizeof(hostname), pool);
+ if (apr_err)
+ {
+ svn_error_t *err = svn_error_wrap_apr(apr_err, _("Can't get hostname"));
+ SVN_ERR(write_failure(conn, pool, &err));
+ return svn_ra_svn_flush(conn, pool);
+ }
+
+ /* Create a SASL context. SASL_SUCCESS_DATA tells SASL that the protocol
+ supports sending data along with the final "success" message. */
+ result = sasl_server_new(SVN_RA_SVN_SASL_NAME,
+ hostname, b->realm,
+ localaddrport, remoteaddrport,
+ NULL, SASL_SUCCESS_DATA,
+ &sasl_ctx);
+ if (result != SASL_OK)
+ {
+ svn_error_t *err = svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL,
+ sasl_errstring(result, NULL, NULL));
+ SVN_ERR(write_failure(conn, pool, &err));
+ return svn_ra_svn_flush(conn, pool);
+ }
+
+ /* Make sure the context is always destroyed. */
+ apr_pool_cleanup_register(b->pool, sasl_ctx, sasl_dispose_cb,
+ apr_pool_cleanup_null);
+
+ /* Initialize security properties. */
+ svn_ra_svn__default_secprops(&secprops);
+
+ /* Don't allow ANONYMOUS if a username is required. */
+ no_anonymous = needs_username || get_access(b, UNAUTHENTICATED) < required;
+ if (no_anonymous)
+ secprops.security_flags |= SASL_SEC_NOANONYMOUS;
+
+ svn_config_get(b->cfg, &val,
+ SVN_CONFIG_SECTION_SASL,
+ SVN_CONFIG_OPTION_MIN_SSF,
+ "0");
+ SVN_ERR(svn_cstring_atoui(&secprops.min_ssf, val));
+
+ svn_config_get(b->cfg, &val,
+ SVN_CONFIG_SECTION_SASL,
+ SVN_CONFIG_OPTION_MAX_SSF,
+ "256");
+ SVN_ERR(svn_cstring_atoui(&secprops.max_ssf, val));
+
+ /* Set security properties. */
+ result = sasl_setprop(sasl_ctx, SASL_SEC_PROPS, &secprops);
+ if (result != SASL_OK)
+ return fail_cmd(conn, pool, sasl_ctx);
+
+ /* SASL needs to know if we are externally authenticated. */
+ if (b->tunnel_user)
+ result = sasl_setprop(sasl_ctx, SASL_AUTH_EXTERNAL, b->tunnel_user);
+ if (result != SASL_OK)
+ return fail_cmd(conn, pool, sasl_ctx);
+
+ /* Get the list of mechanisms. */
+ result = sasl_listmech(sasl_ctx, NULL, NULL, " ", NULL,
+ &mechlist, NULL, &mech_count);
+
+ if (result != SASL_OK)
+ return fail_cmd(conn, pool, sasl_ctx);
+
+ if (mech_count == 0)
+ {
+ svn_error_t *err = svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL,
+ _("Could not obtain the list"
+ " of SASL mechanisms"));
+ SVN_ERR(write_failure(conn, pool, &err));
+ return svn_ra_svn_flush(conn, pool);
+ }
+
+ /* Send the list of mechanisms and the realm to the client. */
+ SVN_ERR(svn_ra_svn_write_cmd_response(conn, pool, "(w)c",
+ mechlist, b->realm));
+
+ /* The main authentication loop. */
+ subpool = svn_pool_create(pool);
+ do
+ {
+ svn_pool_clear(subpool);
+ SVN_ERR(try_auth(conn, sasl_ctx, subpool, b, &success));
+ }
+ while (!success);
+ svn_pool_destroy(subpool);
+
+ SVN_ERR(svn_ra_svn__enable_sasl_encryption(conn, sasl_ctx, pool));
+
+ if (no_anonymous)
+ {
+ char *p;
+ const void *user;
+
+ /* Get the authenticated username. */
+ result = sasl_getprop(sasl_ctx, SASL_USERNAME, &user);
+
+ if (result != SASL_OK)
+ return fail_cmd(conn, pool, sasl_ctx);
+
+ if ((p = strchr(user, '@')) != NULL)
+ {
+ /* Drop the realm part. */
+ b->user = apr_pstrndup(b->pool, user, p - (const char *)user);
+ }
+ else
+ {
+ svn_error_t *err;
+ err = svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL,
+ _("Couldn't obtain the authenticated"
+ " username"));
+ SVN_ERR(write_failure(conn, pool, &err));
+ return svn_ra_svn_flush(conn, pool);
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+#endif /* SVN_HAVE_SASL */
diff --git a/subversion/svnserve/log-escape.c b/subversion/svnserve/log-escape.c
new file mode 100644
index 0000000..79f5312
--- /dev/null
+++ b/subversion/svnserve/log-escape.c
@@ -0,0 +1,143 @@
+/*
+ * log-escape.c : Functions for escaping log items
+ * copied from Apache httpd
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+#include <apr.h>
+#define APR_WANT_STRFUNC
+#include <apr_want.h>
+#include "server.h"
+#include "svn_ctype.h"
+
+/* copied from httpd-2.2.4/server/util.c */
+/* c2x takes an unsigned, and expects the caller has guaranteed that
+ * 0 <= what < 256... which usually means that you have to cast to
+ * unsigned char first, because (unsigned)(char)(x) first goes through
+ * signed extension to an int before the unsigned cast.
+ *
+ * The reason for this assumption is to assist gcc code generation --
+ * the unsigned char -> unsigned extension is already done earlier in
+ * both uses of this code, so there's no need to waste time doing it
+ * again.
+ */
+static const char c2x_table[] = "0123456789abcdef";
+
+/* copied from httpd-2.2.4/server/util.c */
+static APR_INLINE unsigned char *c2x(unsigned what, unsigned char prefix,
+ unsigned char *where)
+{
+#if APR_CHARSET_EBCDIC
+ what = apr_xlate_conv_byte(ap_hdrs_to_ascii, (unsigned char)what);
+#endif /*APR_CHARSET_EBCDIC*/
+ *where++ = prefix;
+ *where++ = c2x_table[what >> 4];
+ *where++ = c2x_table[what & 0xf];
+ return where;
+}
+
+/* copied from httpd-2.2.4/server/util.c */
+apr_size_t escape_errorlog_item(char *dest, const char *source,
+ apr_size_t buflen)
+{
+ unsigned char *d, *ep;
+ const unsigned char *s;
+
+ if (!source || !buflen) { /* be safe */
+ return 0;
+ }
+
+ d = (unsigned char *)dest;
+ s = (const unsigned char *)source;
+ ep = d + buflen - 1;
+
+ for (; d < ep && *s; ++s) {
+
+ /* httpd-2.2.4/server/util.c has this:
+ if (TEST_CHAR(*s, T_ESCAPE_LOGITEM)) {
+ which does this same check with a fast lookup table. Well,
+ mostly the same; we don't escape quotes, as that does.
+ */
+ if (*s && ( !svn_ctype_isprint(*s)
+ || *s == '\\'
+ || svn_ctype_iscntrl(*s))) {
+ *d++ = '\\';
+ if (d >= ep) {
+ --d;
+ break;
+ }
+
+ switch(*s) {
+ case '\b':
+ *d++ = 'b';
+ break;
+ case '\n':
+ *d++ = 'n';
+ break;
+ case '\r':
+ *d++ = 'r';
+ break;
+ case '\t':
+ *d++ = 't';
+ break;
+ case '\v':
+ *d++ = 'v';
+ break;
+ case '\\':
+ *d++ = *s;
+ break;
+ case '"': /* no need for this in error log */
+ d[-1] = *s;
+ break;
+ default:
+ if (d >= ep - 2) {
+ ep = --d; /* break the for loop as well */
+ break;
+ }
+ c2x(*s, 'x', d);
+ d += 3;
+ }
+ }
+ else {
+ *d++ = *s;
+ }
+ }
+ *d = '\0';
+
+ return (d - (unsigned char *)dest);
+}
diff --git a/subversion/svnserve/main.c b/subversion/svnserve/main.c
new file mode 100644
index 0000000..a3378bf
--- /dev/null
+++ b/subversion/svnserve/main.c
@@ -0,0 +1,1054 @@
+/*
+ * main.c : Main control function for svnserve
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+
+#define APR_WANT_STRFUNC
+#include <apr_want.h>
+#include <apr_general.h>
+#include <apr_getopt.h>
+#include <apr_network_io.h>
+#include <apr_signal.h>
+#include <apr_thread_proc.h>
+#include <apr_portable.h>
+
+#include <locale.h>
+
+#include "svn_cmdline.h"
+#include "svn_types.h"
+#include "svn_pools.h"
+#include "svn_error.h"
+#include "svn_ra_svn.h"
+#include "svn_utf.h"
+#include "svn_dirent_uri.h"
+#include "svn_path.h"
+#include "svn_opt.h"
+#include "svn_repos.h"
+#include "svn_string.h"
+#include "svn_cache_config.h"
+#include "svn_version.h"
+#include "svn_io.h"
+
+#include "svn_private_config.h"
+#include "private/svn_dep_compat.h"
+#include "winservice.h"
+
+#ifdef HAVE_UNISTD_H
+#include <unistd.h> /* For getpid() */
+#endif
+
+#include "server.h"
+
+/* The strategy for handling incoming connections. Some of these may be
+ unavailable due to platform limitations. */
+enum connection_handling_mode {
+ connection_mode_fork, /* Create a process per connection */
+ connection_mode_thread, /* Create a thread per connection */
+ connection_mode_single /* One connection at a time in this process */
+};
+
+/* The mode in which to run svnserve */
+enum run_mode {
+ run_mode_unspecified,
+ run_mode_inetd,
+ run_mode_daemon,
+ run_mode_tunnel,
+ run_mode_listen_once,
+ run_mode_service
+};
+
+#if APR_HAS_FORK
+#if APR_HAS_THREADS
+
+#define CONNECTION_DEFAULT connection_mode_fork
+#define CONNECTION_HAVE_THREAD_OPTION
+
+#else /* ! APR_HAS_THREADS */
+
+#define CONNECTION_DEFAULT connection_mode_fork
+
+#endif /* ! APR_HAS_THREADS */
+#elif APR_HAS_THREADS /* and ! APR_HAS_FORK */
+
+#define CONNECTION_DEFAULT connection_mode_thread
+
+#else /* ! APR_HAS_THREADS and ! APR_HAS_FORK */
+
+#define CONNECTION_DEFAULT connection_mode_single
+
+#endif
+
+
+#ifdef WIN32
+static apr_os_sock_t winservice_svnserve_accept_socket = INVALID_SOCKET;
+
+/* The SCM calls this function (on an arbitrary thread, not the main()
+ thread!) when it wants to stop the service.
+
+ For now, our strategy is to close the listener socket, in order to
+ unblock main() and cause it to exit its accept loop. We cannot use
+ apr_socket_close, because that function deletes the apr_socket_t
+ structure, as well as closing the socket handle. If we called
+ apr_socket_close here, then main() will also call apr_socket_close,
+ resulting in a double-free. This way, we just close the kernel
+ socket handle, which causes the accept() function call to fail,
+ which causes main() to clean up the socket. So, memory gets freed
+ only once.
+
+ This isn't pretty, but it's better than a lot of other options.
+ Currently, there is no "right" way to shut down svnserve.
+
+ We store the OS handle rather than a pointer to the apr_socket_t
+ structure in order to eliminate any possibility of illegal memory
+ access. */
+void winservice_notify_stop(void)
+{
+ if (winservice_svnserve_accept_socket != INVALID_SOCKET)
+ closesocket(winservice_svnserve_accept_socket);
+}
+#endif /* _WIN32 */
+
+
+/* Option codes and descriptions for svnserve.
+ *
+ * The entire list must be terminated with an entry of nulls.
+ *
+ * APR requires that options without abbreviations
+ * have codes greater than 255.
+ */
+#define SVNSERVE_OPT_LISTEN_PORT 256
+#define SVNSERVE_OPT_LISTEN_HOST 257
+#define SVNSERVE_OPT_FOREGROUND 258
+#define SVNSERVE_OPT_TUNNEL_USER 259
+#define SVNSERVE_OPT_VERSION 260
+#define SVNSERVE_OPT_PID_FILE 261
+#define SVNSERVE_OPT_SERVICE 262
+#define SVNSERVE_OPT_CONFIG_FILE 263
+#define SVNSERVE_OPT_LOG_FILE 264
+#define SVNSERVE_OPT_CACHE_TXDELTAS 265
+#define SVNSERVE_OPT_CACHE_FULLTEXTS 266
+
+static const apr_getopt_option_t svnserve__options[] =
+ {
+ {"daemon", 'd', 0, N_("daemon mode")},
+ {"inetd", 'i', 0, N_("inetd mode")},
+ {"tunnel", 't', 0, N_("tunnel mode")},
+ {"listen-once", 'X', 0, N_("listen-once mode (useful for debugging)")},
+#ifdef WIN32
+ {"service", SVNSERVE_OPT_SERVICE, 0,
+ N_("Windows service mode (Service Control Manager)")},
+#endif
+ {"root", 'r', 1, N_("root of directory to serve")},
+ {"read-only", 'R', 0,
+ N_("force read only, overriding repository config file")},
+ {"config-file", SVNSERVE_OPT_CONFIG_FILE, 1,
+ N_("read configuration from file ARG")},
+ {"listen-port", SVNSERVE_OPT_LISTEN_PORT, 1,
+#ifdef WIN32
+ N_("listen port\n"
+ " "
+ "[mode: daemon, service, listen-once]")},
+#else
+ N_("listen port\n"
+ " "
+ "[mode: daemon, listen-once]")},
+#endif
+ {"listen-host", SVNSERVE_OPT_LISTEN_HOST, 1,
+#ifdef WIN32
+ N_("listen hostname or IP address\n"
+ " "
+ "[mode: daemon, service, listen-once]")},
+#else
+ N_("listen hostname or IP address\n"
+ " "
+ "[mode: daemon, listen-once]")},
+#endif
+ {"prefer-ipv6", '6', 0,
+ N_("prefer IPv6 when resolving the listen hostname\n"
+ " "
+ "[IPv4 is preferred by default. Using IPv4 and IPv6\n"
+ " "
+ "at the same time is not supported in daemon mode.\n"
+ " "
+ "Use inetd mode or tunnel mode if you need this.]")},
+ {"compression", 'c', 1,
+ N_("compression level to use for network transmissions\n"
+ " "
+ "[0 .. no compression, 5 .. default, \n"
+ " "
+ " 9 .. maximum compression]")},
+ {"memory-cache-size", 'M', 1,
+ N_("size of the extra in-memory cache in MB used to\n"
+ " "
+ "minimize redundant operations.\n"
+ " "
+ "Default is 128 for threaded and 16 for non-\n"
+ " "
+ "threaded mode.\n"
+ " "
+ "[used for FSFS repositories only]")},
+ {"cache-txdeltas", SVNSERVE_OPT_CACHE_TXDELTAS, 1,
+ N_("enable or disable caching of deltas between older\n"
+ " "
+ "revisions.\n"
+ " "
+ "Default is no.\n"
+ " "
+ "[used for FSFS repositories only]")},
+ {"cache-fulltexts", SVNSERVE_OPT_CACHE_FULLTEXTS, 1,
+ N_("enable or disable caching of file contents\n"
+ " "
+ "Default is yes.\n"
+ " "
+ "[used for FSFS repositories only]")},
+#ifdef CONNECTION_HAVE_THREAD_OPTION
+ /* ### Making the assumption here that WIN32 never has fork and so
+ * ### this option never exists when --service exists. */
+ {"threads", 'T', 0, N_("use threads instead of fork "
+ "[mode: daemon]")},
+#endif
+ {"foreground", SVNSERVE_OPT_FOREGROUND, 0,
+ N_("run in foreground (useful for debugging)\n"
+ " "
+ "[mode: daemon]")},
+ {"log-file", SVNSERVE_OPT_LOG_FILE, 1,
+ N_("svnserve log file")},
+ {"pid-file", SVNSERVE_OPT_PID_FILE, 1,
+#ifdef WIN32
+ N_("write server process ID to file ARG\n"
+ " "
+ "[mode: daemon, listen-once, service]")},
+#else
+ N_("write server process ID to file ARG\n"
+ " "
+ "[mode: daemon, listen-once]")},
+#endif
+ {"tunnel-user", SVNSERVE_OPT_TUNNEL_USER, 1,
+ N_("tunnel username (default is current uid's name)\n"
+ " "
+ "[mode: tunnel]")},
+ {"help", 'h', 0, N_("display this help")},
+ {"version", SVNSERVE_OPT_VERSION, 0,
+ N_("show program version information")},
+ {"quiet", 'q', 0,
+ N_("no progress (only errors) to stderr")},
+ {0, 0, 0, 0}
+ };
+
+
+static void usage(const char *progname, apr_pool_t *pool)
+{
+ if (!progname)
+ progname = "svnserve";
+
+ svn_error_clear(svn_cmdline_fprintf(stderr, pool,
+ _("Type '%s --help' for usage.\n"),
+ progname));
+ exit(1);
+}
+
+static void help(apr_pool_t *pool)
+{
+ apr_size_t i;
+
+#ifdef WIN32
+ svn_error_clear(svn_cmdline_fputs(_("usage: svnserve [-d | -i | -t | -X "
+ "| --service] [options]\n"
+ "\n"
+ "Valid options:\n"),
+ stdout, pool));
+#else
+ svn_error_clear(svn_cmdline_fputs(_("usage: svnserve [-d | -i | -t | -X] "
+ "[options]\n"
+ "\n"
+ "Valid options:\n"),
+ stdout, pool));
+#endif
+ for (i = 0; svnserve__options[i].name && svnserve__options[i].optch; i++)
+ {
+ const char *optstr;
+ svn_opt_format_option(&optstr, svnserve__options + i, TRUE, pool);
+ svn_error_clear(svn_cmdline_fprintf(stdout, pool, " %s\n", optstr));
+ }
+ svn_error_clear(svn_cmdline_fprintf(stdout, pool, "\n"));
+ exit(0);
+}
+
+static svn_error_t * version(svn_boolean_t quiet, apr_pool_t *pool)
+{
+ const char *fs_desc_start
+ = _("The following repository back-end (FS) modules are available:\n\n");
+
+ svn_stringbuf_t *version_footer;
+
+ version_footer = svn_stringbuf_create(fs_desc_start, pool);
+ SVN_ERR(svn_fs_print_modules(version_footer, pool));
+
+#ifdef SVN_HAVE_SASL
+ svn_stringbuf_appendcstr(version_footer,
+ _("\nCyrus SASL authentication is available.\n"));
+#endif
+
+ return svn_opt_print_help3(NULL, "svnserve", TRUE, quiet, version_footer->data,
+ NULL, NULL, NULL, NULL, NULL, pool);
+}
+
+
+#if APR_HAS_FORK
+static void sigchld_handler(int signo)
+{
+ /* Nothing to do; we just need to interrupt the accept(). */
+}
+#endif
+
+/* Redirect stdout to stderr. ARG is the pool.
+ *
+ * In tunnel or inetd mode, we don't want hook scripts corrupting the
+ * data stream by sending data to stdout, so we need to redirect
+ * stdout somewhere else. Sending it to stderr is acceptable; sending
+ * it to /dev/null is another option, but apr doesn't provide a way to
+ * do that without also detaching from the controlling terminal.
+ */
+static apr_status_t redirect_stdout(void *arg)
+{
+ apr_pool_t *pool = arg;
+ apr_file_t *out_file, *err_file;
+ apr_status_t apr_err;
+
+ if ((apr_err = apr_file_open_stdout(&out_file, pool)))
+ return apr_err;
+ if ((apr_err = apr_file_open_stderr(&err_file, pool)))
+ return apr_err;
+ return apr_file_dup2(out_file, err_file, pool);
+}
+
+/* "Arguments" passed from the main thread to the connection thread */
+struct serve_thread_t {
+ svn_ra_svn_conn_t *conn;
+ serve_params_t *params;
+ apr_pool_t *pool;
+};
+
+#if APR_HAS_THREADS
+static void * APR_THREAD_FUNC serve_thread(apr_thread_t *tid, void *data)
+{
+ struct serve_thread_t *d = data;
+
+ svn_error_clear(serve(d->conn, d->params, d->pool));
+ svn_pool_destroy(d->pool);
+
+ return NULL;
+}
+#endif
+
+/* Write the PID of the current process as a decimal number, followed by a
+ newline to the file FILENAME, using POOL for temporary allocations. */
+static svn_error_t *write_pid_file(const char *filename, apr_pool_t *pool)
+{
+ apr_file_t *file;
+ const char *contents = apr_psprintf(pool, "%" APR_PID_T_FMT "\n",
+ getpid());
+
+ SVN_ERR(svn_io_file_open(&file, filename,
+ APR_WRITE | APR_CREATE | APR_TRUNCATE,
+ APR_OS_DEFAULT, pool));
+ SVN_ERR(svn_io_file_write_full(file, contents, strlen(contents), NULL,
+ pool));
+
+ SVN_ERR(svn_io_file_close(file, pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* Version compatibility check */
+static svn_error_t *
+check_lib_versions(void)
+{
+ static const svn_version_checklist_t checklist[] =
+ {
+ { "svn_subr", svn_subr_version },
+ { "svn_repos", svn_repos_version },
+ { "svn_fs", svn_fs_version },
+ { "svn_delta", svn_delta_version },
+ { "svn_ra_svn", svn_ra_svn_version },
+ { NULL, NULL }
+ };
+
+ SVN_VERSION_DEFINE(my_version);
+ return svn_ver_check_list(&my_version, checklist);
+}
+
+
+int main(int argc, const char *argv[])
+{
+ enum run_mode run_mode = run_mode_unspecified;
+ svn_boolean_t foreground = FALSE;
+ apr_socket_t *sock, *usock;
+ apr_file_t *in_file, *out_file;
+ apr_sockaddr_t *sa;
+ apr_pool_t *pool;
+ apr_pool_t *connection_pool;
+ apr_allocator_t *allocator;
+ svn_error_t *err;
+ apr_getopt_t *os;
+ int opt;
+ serve_params_t params;
+ const char *arg;
+ apr_status_t status;
+ svn_ra_svn_conn_t *conn;
+ apr_proc_t proc;
+#if APR_HAS_THREADS
+ apr_threadattr_t *tattr;
+ apr_thread_t *tid;
+
+ struct serve_thread_t *thread_data;
+#endif
+ enum connection_handling_mode handling_mode = CONNECTION_DEFAULT;
+ apr_uint16_t port = SVN_RA_SVN_PORT;
+ const char *host = NULL;
+ int family = APR_INET;
+ apr_int32_t sockaddr_info_flags = 0;
+ svn_boolean_t prefer_v6 = FALSE;
+ svn_boolean_t quiet = FALSE;
+ svn_boolean_t is_version = FALSE;
+ int mode_opt_count = 0;
+ const char *config_filename = NULL;
+ const char *pid_filename = NULL;
+ const char *log_filename = NULL;
+ svn_node_kind_t kind;
+
+ /* Initialize the app. */
+ if (svn_cmdline_init("svnserve", stderr) != EXIT_SUCCESS)
+ return EXIT_FAILURE;
+
+ /* Create our top-level pool. */
+ pool = svn_pool_create(NULL);
+
+#ifdef SVN_HAVE_SASL
+ SVN_INT_ERR(cyrus_init(pool));
+#endif
+
+ /* Check library versions */
+ err = check_lib_versions();
+ if (err)
+ return svn_cmdline_handle_exit_error(err, pool, "svnserve: ");
+
+ /* Initialize the FS library. */
+ err = svn_fs_initialize(pool);
+ if (err)
+ return svn_cmdline_handle_exit_error(err, pool, "svnserve: ");
+
+ err = svn_cmdline__getopt_init(&os, argc, argv, pool);
+ if (err)
+ return svn_cmdline_handle_exit_error(err, pool, "svnserve: ");
+
+ params.root = "/";
+ params.tunnel = FALSE;
+ params.tunnel_user = NULL;
+ params.read_only = FALSE;
+ params.cfg = NULL;
+ params.pwdb = NULL;
+ params.authzdb = NULL;
+ params.compression_level = SVN_DELTA_COMPRESSION_LEVEL_DEFAULT;
+ params.log_file = NULL;
+ params.username_case = CASE_ASIS;
+ params.memory_cache_size = (apr_uint64_t)-1;
+ params.cache_fulltexts = TRUE;
+ params.cache_txdeltas = FALSE;
+
+ while (1)
+ {
+ status = apr_getopt_long(os, svnserve__options, &opt, &arg);
+ if (APR_STATUS_IS_EOF(status))
+ break;
+ if (status != APR_SUCCESS)
+ usage(argv[0], pool);
+ switch (opt)
+ {
+ case '6':
+ prefer_v6 = TRUE;
+ break;
+
+ case 'h':
+ help(pool);
+ break;
+
+ case 'q':
+ quiet = TRUE;
+ break;
+
+ case SVNSERVE_OPT_VERSION:
+ is_version = TRUE;
+ break;
+
+ case 'd':
+ if (run_mode != run_mode_daemon)
+ {
+ run_mode = run_mode_daemon;
+ mode_opt_count++;
+ }
+ break;
+
+ case SVNSERVE_OPT_FOREGROUND:
+ foreground = TRUE;
+ break;
+
+ case 'i':
+ if (run_mode != run_mode_inetd)
+ {
+ run_mode = run_mode_inetd;
+ mode_opt_count++;
+ }
+ break;
+
+ case SVNSERVE_OPT_LISTEN_PORT:
+ {
+ apr_uint64_t val;
+
+ err = svn_cstring_strtoui64(&val, arg, 0, APR_UINT16_MAX, 10);
+ if (err)
+ return svn_cmdline_handle_exit_error(
+ svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, err,
+ _("Invalid port '%s'"), arg),
+ pool, "svnserve: ");
+ port = (apr_uint16_t)val;
+ }
+ break;
+
+ case SVNSERVE_OPT_LISTEN_HOST:
+ host = arg;
+ break;
+
+ case 't':
+ if (run_mode != run_mode_tunnel)
+ {
+ run_mode = run_mode_tunnel;
+ mode_opt_count++;
+ }
+ break;
+
+ case SVNSERVE_OPT_TUNNEL_USER:
+ params.tunnel_user = arg;
+ break;
+
+ case 'X':
+ if (run_mode != run_mode_listen_once)
+ {
+ run_mode = run_mode_listen_once;
+ mode_opt_count++;
+ }
+ break;
+
+ case 'r':
+ SVN_INT_ERR(svn_utf_cstring_to_utf8(&params.root, arg, pool));
+
+ err = svn_io_check_resolved_path(params.root, &kind, pool);
+ if (err)
+ return svn_cmdline_handle_exit_error(err, pool, "svnserve: ");
+ if (kind != svn_node_dir)
+ {
+ svn_error_clear
+ (svn_cmdline_fprintf
+ (stderr, pool,
+ _("svnserve: Root path '%s' does not exist "
+ "or is not a directory.\n"), params.root));
+ return EXIT_FAILURE;
+ }
+
+ params.root = svn_dirent_internal_style(params.root, pool);
+ SVN_INT_ERR(svn_dirent_get_absolute(&params.root, params.root, pool));
+ break;
+
+ case 'R':
+ params.read_only = TRUE;
+ break;
+
+ case 'T':
+ handling_mode = connection_mode_thread;
+ break;
+
+ case 'c':
+ params.compression_level = atoi(arg);
+ if (params.compression_level < SVN_DELTA_COMPRESSION_LEVEL_NONE)
+ params.compression_level = SVN_DELTA_COMPRESSION_LEVEL_NONE;
+ if (params.compression_level > SVN_DELTA_COMPRESSION_LEVEL_MAX)
+ params.compression_level = SVN_DELTA_COMPRESSION_LEVEL_MAX;
+ break;
+
+ case 'M':
+ params.memory_cache_size = 0x100000 * apr_strtoi64(arg, NULL, 0);
+ break;
+
+ case SVNSERVE_OPT_CACHE_TXDELTAS:
+ params.cache_txdeltas
+ = svn_tristate__from_word(arg) == svn_tristate_true;
+ break;
+
+ case SVNSERVE_OPT_CACHE_FULLTEXTS:
+ params.cache_fulltexts
+ = svn_tristate__from_word(arg) == svn_tristate_true;
+ break;
+
+#ifdef WIN32
+ case SVNSERVE_OPT_SERVICE:
+ if (run_mode != run_mode_service)
+ {
+ run_mode = run_mode_service;
+ mode_opt_count++;
+ }
+ break;
+#endif
+
+ case SVNSERVE_OPT_CONFIG_FILE:
+ SVN_INT_ERR(svn_utf_cstring_to_utf8(&config_filename, arg, pool));
+ config_filename = svn_dirent_internal_style(config_filename, pool);
+ SVN_INT_ERR(svn_dirent_get_absolute(&config_filename, config_filename,
+ pool));
+ break;
+
+ case SVNSERVE_OPT_PID_FILE:
+ SVN_INT_ERR(svn_utf_cstring_to_utf8(&pid_filename, arg, pool));
+ pid_filename = svn_dirent_internal_style(pid_filename, pool);
+ SVN_INT_ERR(svn_dirent_get_absolute(&pid_filename, pid_filename,
+ pool));
+ break;
+
+ case SVNSERVE_OPT_LOG_FILE:
+ SVN_INT_ERR(svn_utf_cstring_to_utf8(&log_filename, arg, pool));
+ log_filename = svn_dirent_internal_style(log_filename, pool);
+ SVN_INT_ERR(svn_dirent_get_absolute(&log_filename, log_filename,
+ pool));
+ break;
+
+ }
+ }
+
+ if (is_version)
+ {
+ SVN_INT_ERR(version(quiet, pool));
+ exit(0);
+ }
+
+ if (os->ind != argc)
+ usage(argv[0], pool);
+
+ if (mode_opt_count != 1)
+ {
+ svn_error_clear(svn_cmdline_fputs(
+#ifdef WIN32
+ _("You must specify exactly one of -d, -i, -t, "
+ "--service or -X.\n"),
+#else
+ _("You must specify exactly one of -d, -i, -t or -X.\n"),
+#endif
+ stderr, pool));
+ usage(argv[0], pool);
+ }
+
+ /* If a configuration file is specified, load it and any referenced
+ * password and authorization files. */
+ if (config_filename)
+ SVN_INT_ERR(load_configs(&params.cfg, &params.pwdb, &params.authzdb,
+ &params.username_case, config_filename, TRUE,
+ svn_dirent_dirname(config_filename, pool),
+ NULL, NULL, /* server baton, conn */
+ pool));
+
+ if (log_filename)
+ SVN_INT_ERR(svn_io_file_open(&params.log_file, log_filename,
+ APR_WRITE | APR_CREATE | APR_APPEND,
+ APR_OS_DEFAULT, pool));
+
+ if (params.tunnel_user && run_mode != run_mode_tunnel)
+ {
+ svn_error_clear
+ (svn_cmdline_fprintf
+ (stderr, pool,
+ _("Option --tunnel-user is only valid in tunnel mode.\n")));
+ exit(1);
+ }
+
+ if (run_mode == run_mode_inetd || run_mode == run_mode_tunnel)
+ {
+ params.tunnel = (run_mode == run_mode_tunnel);
+ apr_pool_cleanup_register(pool, pool, apr_pool_cleanup_null,
+ redirect_stdout);
+ status = apr_file_open_stdin(&in_file, pool);
+ if (status)
+ {
+ err = svn_error_wrap_apr(status, _("Can't open stdin"));
+ return svn_cmdline_handle_exit_error(err, pool, "svnserve: ");
+ }
+
+ status = apr_file_open_stdout(&out_file, pool);
+ if (status)
+ {
+ err = svn_error_wrap_apr(status, _("Can't open stdout"));
+ return svn_cmdline_handle_exit_error(err, pool, "svnserve: ");
+ }
+
+ /* Use a subpool for the connection to ensure that if SASL is used
+ * the pool cleanup handlers that call sasl_dispose() (connection_pool)
+ * and sasl_done() (pool) are run in the right order. See issue #3664. */
+ connection_pool = svn_pool_create(pool);
+ conn = svn_ra_svn_create_conn2(NULL, in_file, out_file,
+ params.compression_level,
+ connection_pool);
+ svn_error_clear(serve(conn, &params, connection_pool));
+ exit(0);
+ }
+
+#ifdef WIN32
+ /* If svnserve needs to run as a Win32 service, then we need to
+ coordinate with the Service Control Manager (SCM) before
+ continuing. This function call registers the svnserve.exe
+ process with the SCM, waits for the "start" command from the SCM
+ (which will come very quickly), and confirms that those steps
+ succeeded.
+
+ After this call succeeds, the service is free to run. At some
+ point in the future, the SCM will send a message to the service,
+ requesting that it stop. This is translated into a call to
+ winservice_notify_stop(). The service is then responsible for
+ cleanly terminating.
+
+ We need to do this before actually starting the service logic
+ (opening files, sockets, etc.) because the SCM wants you to
+ connect *first*, then do your service-specific logic. If the
+ service process takes too long to connect to the SCM, then the
+ SCM will decide that the service is busted, and will give up on
+ it.
+ */
+ if (run_mode == run_mode_service)
+ {
+ err = winservice_start();
+ if (err)
+ {
+ svn_handle_error2(err, stderr, FALSE, "svnserve: ");
+
+ /* This is the most common error. It means the user started
+ svnserve from a shell, and specified the --service
+ argument. svnserve cannot be started, as a service, in
+ this way. The --service argument is valid only valid if
+ svnserve is started by the SCM. */
+ if (err->apr_err ==
+ APR_FROM_OS_ERROR(ERROR_FAILED_SERVICE_CONTROLLER_CONNECT))
+ {
+ svn_error_clear(svn_cmdline_fprintf(stderr, pool,
+ _("svnserve: The --service flag is only valid if the"
+ " process is started by the Service Control Manager.\n")));
+ }
+
+ svn_error_clear(err);
+ exit(1);
+ }
+
+ /* The service is now in the "starting" state. Before the SCM will
+ consider the service "started", this thread must call the
+ winservice_running() function. */
+ }
+#endif /* WIN32 */
+
+ /* Make sure we have IPV6 support first before giving apr_sockaddr_info_get
+ APR_UNSPEC, because it may give us back an IPV6 address even if we can't
+ create IPV6 sockets. */
+
+#if APR_HAVE_IPV6
+#ifdef MAX_SECS_TO_LINGER
+ /* ### old APR interface */
+ status = apr_socket_create(&sock, APR_INET6, SOCK_STREAM, pool);
+#else
+ status = apr_socket_create(&sock, APR_INET6, SOCK_STREAM, APR_PROTO_TCP,
+ pool);
+#endif
+ if (status == 0)
+ {
+ apr_socket_close(sock);
+ family = APR_UNSPEC;
+
+ if (prefer_v6)
+ {
+ if (host == NULL)
+ host = "::";
+ sockaddr_info_flags = APR_IPV6_ADDR_OK;
+ }
+ else
+ {
+ if (host == NULL)
+ host = "0.0.0.0";
+ sockaddr_info_flags = APR_IPV4_ADDR_OK;
+ }
+ }
+#endif
+
+ status = apr_sockaddr_info_get(&sa, host, family, port,
+ sockaddr_info_flags, pool);
+ if (status)
+ {
+ err = svn_error_wrap_apr(status, _("Can't get address info"));
+ return svn_cmdline_handle_exit_error(err, pool, "svnserve: ");
+ }
+
+
+#ifdef MAX_SECS_TO_LINGER
+ /* ### old APR interface */
+ status = apr_socket_create(&sock, sa->family, SOCK_STREAM, pool);
+#else
+ status = apr_socket_create(&sock, sa->family, SOCK_STREAM, APR_PROTO_TCP,
+ pool);
+#endif
+ if (status)
+ {
+ err = svn_error_wrap_apr(status, _("Can't create server socket"));
+ return svn_cmdline_handle_exit_error(err, pool, "svnserve: ");
+ }
+
+ /* Prevents "socket in use" errors when server is killed and quickly
+ * restarted. */
+ apr_socket_opt_set(sock, APR_SO_REUSEADDR, 1);
+
+ status = apr_socket_bind(sock, sa);
+ if (status)
+ {
+ err = svn_error_wrap_apr(status, _("Can't bind server socket"));
+ return svn_cmdline_handle_exit_error(err, pool, "svnserve: ");
+ }
+
+ apr_socket_listen(sock, 7);
+
+#if APR_HAS_FORK
+ if (run_mode != run_mode_listen_once && !foreground)
+ apr_proc_detach(APR_PROC_DETACH_DAEMONIZE);
+
+ apr_signal(SIGCHLD, sigchld_handler);
+#endif
+
+#ifdef SIGPIPE
+ /* Disable SIGPIPE generation for the platforms that have it. */
+ apr_signal(SIGPIPE, SIG_IGN);
+#endif
+
+#ifdef SIGXFSZ
+ /* Disable SIGXFSZ generation for the platforms that have it, otherwise
+ * working with large files when compiled against an APR that doesn't have
+ * large file support will crash the program, which is uncool. */
+ apr_signal(SIGXFSZ, SIG_IGN);
+#endif
+
+ if (pid_filename)
+ SVN_INT_ERR(write_pid_file(pid_filename, pool));
+
+#ifdef WIN32
+ status = apr_os_sock_get(&winservice_svnserve_accept_socket, sock);
+ if (status)
+ winservice_svnserve_accept_socket = INVALID_SOCKET;
+
+ /* At this point, the service is "running". Notify the SCM. */
+ if (run_mode == run_mode_service)
+ winservice_running();
+#endif
+
+ /* Configure FS caches for maximum efficiency with svnserve.
+ * For pre-forked (i.e. multi-processed) mode of operation,
+ * keep the per-process caches smaller than the default.
+ * Also, apply the respective command line parameters, if given. */
+ {
+ svn_cache_config_t settings = *svn_cache_config_get();
+
+ if (params.memory_cache_size != -1)
+ settings.cache_size = params.memory_cache_size;
+
+ settings.single_threaded = TRUE;
+ if (handling_mode == connection_mode_thread)
+ {
+#ifdef APR_HAS_THREADS
+ settings.single_threaded = FALSE;
+#else
+ /* No requests will be processed at all
+ * (see "switch (handling_mode)" code further down).
+ * But if they were, some other synchronization code
+ * would need to take care of securing integrity of
+ * APR-based structures. That would include our caches.
+ */
+#endif
+ }
+
+ svn_cache_config_set(&settings);
+ }
+
+ while (1)
+ {
+#ifdef WIN32
+ if (winservice_is_stopping())
+ return ERROR_SUCCESS;
+#endif
+
+ /* If we are using fulltext caches etc. we will allocate many large
+ chunks of memory of various sizes outside the cache for those
+ fulltexts. Make sure we use the memory wisely: use an allocator
+ that causes memory fragments to be given back to the OS early. */
+
+ if (apr_allocator_create(&allocator))
+ return EXIT_FAILURE;
+
+ apr_allocator_max_free_set(allocator, SVN_ALLOCATOR_RECOMMENDED_MAX_FREE);
+
+ /* Non-standard pool handling. The main thread never blocks to join
+ the connection threads so it cannot clean up after each one. So
+ separate pools that can be cleared at thread exit are used. */
+
+ connection_pool = svn_pool_create_ex(NULL, allocator);
+ apr_allocator_owner_set(allocator, connection_pool);
+
+ status = apr_socket_accept(&usock, sock, connection_pool);
+ if (handling_mode == connection_mode_fork)
+ {
+ /* Collect any zombie child processes. */
+ while (apr_proc_wait_all_procs(&proc, NULL, NULL, APR_NOWAIT,
+ connection_pool) == APR_CHILD_DONE)
+ ;
+ }
+ if (APR_STATUS_IS_EINTR(status))
+ {
+ svn_pool_destroy(connection_pool);
+ continue;
+ }
+ if (status)
+ {
+ err = svn_error_wrap_apr
+ (status, _("Can't accept client connection"));
+ return svn_cmdline_handle_exit_error(err, pool, "svnserve: ");
+ }
+
+ /* Enable TCP keep-alives on the socket so we time out when
+ * the connection breaks due to network-layer problems.
+ * If the peer has dropped the connection due to a network partition
+ * or a crash, or if the peer no longer considers the connection
+ * valid because we are behind a NAT and our public IP has changed,
+ * it will respond to the keep-alive probe with a RST instead of an
+ * acknowledgment segment, which will cause svn to abort the session
+ * even while it is currently blocked waiting for data from the peer. */
+ status = apr_socket_opt_set(usock, APR_SO_KEEPALIVE, 1);
+ if (status)
+ {
+ /* It's not a fatal error if we cannot enable keep-alives. */
+ }
+
+ conn = svn_ra_svn_create_conn2(usock, NULL, NULL,
+ params.compression_level,
+ connection_pool);
+
+ if (run_mode == run_mode_listen_once)
+ {
+ err = serve(conn, &params, connection_pool);
+
+ if (err)
+ svn_handle_error2(err, stdout, FALSE, "svnserve: ");
+ svn_error_clear(err);
+
+ apr_socket_close(usock);
+ apr_socket_close(sock);
+ exit(0);
+ }
+
+ switch (handling_mode)
+ {
+ case connection_mode_fork:
+#if APR_HAS_FORK
+ status = apr_proc_fork(&proc, connection_pool);
+ if (status == APR_INCHILD)
+ {
+ apr_socket_close(sock);
+ err = serve(conn, &params, connection_pool);
+ log_error(err, params.log_file,
+ svn_ra_svn_conn_remote_host(conn),
+ NULL, NULL, /* user, repos */
+ connection_pool);
+ svn_error_clear(err);
+ apr_socket_close(usock);
+ exit(0);
+ }
+ else if (status == APR_INPARENT)
+ {
+ apr_socket_close(usock);
+ }
+ else
+ {
+ err = svn_error_wrap_apr(status, "apr_proc_fork");
+ log_error(err, params.log_file,
+ svn_ra_svn_conn_remote_host(conn),
+ NULL, NULL, /* user, repos */
+ connection_pool);
+ svn_error_clear(err);
+ apr_socket_close(usock);
+ }
+ svn_pool_destroy(connection_pool);
+#endif
+ break;
+
+ case connection_mode_thread:
+ /* Create a detached thread for each connection. That's not a
+ particularly sophisticated strategy for a threaded server, it's
+ little different from forking one process per connection. */
+#if APR_HAS_THREADS
+ status = apr_threadattr_create(&tattr, connection_pool);
+ if (status)
+ {
+ err = svn_error_wrap_apr(status, _("Can't create threadattr"));
+ svn_handle_error2(err, stderr, FALSE, "svnserve: ");
+ svn_error_clear(err);
+ exit(1);
+ }
+ status = apr_threadattr_detach_set(tattr, 1);
+ if (status)
+ {
+ err = svn_error_wrap_apr(status, _("Can't set detached state"));
+ svn_handle_error2(err, stderr, FALSE, "svnserve: ");
+ svn_error_clear(err);
+ exit(1);
+ }
+ thread_data = apr_palloc(connection_pool, sizeof(*thread_data));
+ thread_data->conn = conn;
+ thread_data->params = &params;
+ thread_data->pool = connection_pool;
+ status = apr_thread_create(&tid, tattr, serve_thread, thread_data,
+ connection_pool);
+ if (status)
+ {
+ err = svn_error_wrap_apr(status, _("Can't create thread"));
+ svn_handle_error2(err, stderr, FALSE, "svnserve: ");
+ svn_error_clear(err);
+ exit(1);
+ }
+#endif
+ break;
+
+ case connection_mode_single:
+ /* Serve one connection at a time. */
+ svn_error_clear(serve(conn, &params, connection_pool));
+ svn_pool_destroy(connection_pool);
+ }
+ }
+
+ /* NOTREACHED */
+}
diff --git a/subversion/svnserve/serve.c b/subversion/svnserve/serve.c
new file mode 100644
index 0000000..8c4708d
--- /dev/null
+++ b/subversion/svnserve/serve.c
@@ -0,0 +1,3238 @@
+/*
+ * serve.c : Functions for serving the Subversion protocol
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+
+
+#include <limits.h> /* for UINT_MAX */
+#include <stdarg.h>
+
+#define APR_WANT_STRFUNC
+#include <apr_want.h>
+#include <apr_general.h>
+#include <apr_lib.h>
+#include <apr_strings.h>
+
+#include "svn_compat.h"
+#include "svn_private_config.h" /* For SVN_PATH_LOCAL_SEPARATOR */
+#include "svn_types.h"
+#include "svn_string.h"
+#include "svn_pools.h"
+#include "svn_error.h"
+#include "svn_ra.h" /* for SVN_RA_CAPABILITY_* */
+#include "svn_ra_svn.h"
+#include "svn_repos.h"
+#include "svn_dirent_uri.h"
+#include "svn_path.h"
+#include "svn_time.h"
+#include "svn_config.h"
+#include "svn_props.h"
+#include "svn_mergeinfo.h"
+#include "svn_user.h"
+
+#include "private/svn_log.h"
+#include "private/svn_mergeinfo_private.h"
+#include "private/svn_fspath.h"
+
+#ifdef HAVE_UNISTD_H
+#include <unistd.h> /* For getpid() */
+#endif
+
+#include "server.h"
+
+typedef struct commit_callback_baton_t {
+ apr_pool_t *pool;
+ svn_revnum_t *new_rev;
+ const char **date;
+ const char **author;
+ const char **post_commit_err;
+} commit_callback_baton_t;
+
+typedef struct report_driver_baton_t {
+ server_baton_t *sb;
+ const char *repos_url; /* Decoded repository URL. */
+ void *report_baton;
+ svn_error_t *err;
+ /* so update() can distinguish checkout from update in logging */
+ int entry_counter;
+ svn_boolean_t only_empty_entries;
+ /* for diff() logging */
+ svn_revnum_t *from_rev;
+} report_driver_baton_t;
+
+typedef struct log_baton_t {
+ const char *fs_path;
+ svn_ra_svn_conn_t *conn;
+ int stack_depth;
+} log_baton_t;
+
+typedef struct file_revs_baton_t {
+ svn_ra_svn_conn_t *conn;
+ apr_pool_t *pool; /* Pool provided in the handler call. */
+} file_revs_baton_t;
+
+typedef struct fs_warning_baton_t {
+ server_baton_t *server;
+ svn_ra_svn_conn_t *conn;
+ apr_pool_t *pool;
+} fs_warning_baton_t;
+
+
+/* Write LEN bytes of ERRSTR to LOG_FILE with svn_io_file_write(). */
+static svn_error_t *
+log_write(apr_file_t *log_file, const char *errstr, apr_size_t len,
+ apr_pool_t *pool)
+{
+ return svn_io_file_write(log_file, errstr, &len, pool);
+}
+
+void
+log_error(svn_error_t *err, apr_file_t *log_file, const char *remote_host,
+ const char *user, const char *repos, apr_pool_t *pool)
+{
+ const char *timestr, *continuation;
+ char errbuf[256];
+ /* 8192 from MAX_STRING_LEN in from httpd-2.2.4/include/httpd.h */
+ char errstr[8192];
+
+ if (err == SVN_NO_ERROR)
+ return;
+
+ if (log_file == NULL)
+ return;
+
+ timestr = svn_time_to_cstring(apr_time_now(), pool);
+ remote_host = (remote_host ? remote_host : "-");
+ user = (user ? user : "-");
+ repos = (repos ? repos : "-");
+
+ continuation = "";
+ while (err != NULL)
+ {
+ const char *message = svn_err_best_message(err, errbuf, sizeof(errbuf));
+ /* based on httpd-2.2.4/server/log.c:log_error_core */
+ apr_size_t len = apr_snprintf(errstr, sizeof(errstr),
+ "%" APR_PID_T_FMT
+ " %s %s %s %s ERR%s %s %ld %d ",
+ getpid(), timestr, remote_host, user,
+ repos, continuation,
+ err->file ? err->file : "-", err->line,
+ err->apr_err);
+
+ len += escape_errorlog_item(errstr + len, message,
+ sizeof(errstr) - len);
+ /* Truncate for the terminator (as apr_snprintf does) */
+ if (len > sizeof(errstr) - sizeof(APR_EOL_STR)) {
+ len = sizeof(errstr) - sizeof(APR_EOL_STR);
+ }
+ strcpy(errstr + len, APR_EOL_STR);
+ len += strlen(APR_EOL_STR);
+ svn_error_clear(log_write(log_file, errstr, len, pool));
+
+ continuation = "-";
+ err = err->child;
+ }
+}
+
+/* Call log_error with log_file, remote_host, user, and repos
+ arguments from SERVER and CONN. */
+static void
+log_server_error(svn_error_t *err, server_baton_t *server,
+ svn_ra_svn_conn_t *conn, apr_pool_t *pool)
+{
+ log_error(err, server->log_file, svn_ra_svn_conn_remote_host(conn),
+ server->user, server->repos_name, pool);
+}
+
+/* svn_error_create() a new error, log_server_error() it, and
+ return it. */
+static svn_error_t *
+error_create_and_log(apr_status_t apr_err, svn_error_t *child,
+ const char *message, server_baton_t *server,
+ svn_ra_svn_conn_t *conn, apr_pool_t *pool)
+{
+ svn_error_t *err = svn_error_create(apr_err, child, message);
+ log_server_error(err, server, conn, pool);
+ return err;
+}
+
+/* Log a failure ERR, transmit ERR back to the client (as part of a
+ "failure" notification), consume ERR, and flush the connection. */
+static svn_error_t *
+log_fail_and_flush(svn_error_t *err, server_baton_t *server,
+ svn_ra_svn_conn_t *conn, apr_pool_t *pool)
+{
+ svn_error_t *io_err;
+
+ log_server_error(err, server, conn, pool);
+ io_err = svn_ra_svn_write_cmd_failure(conn, pool, err);
+ svn_error_clear(err);
+ SVN_ERR(io_err);
+ return svn_ra_svn_flush(conn, pool);
+}
+
+/* Log a client command. */
+static svn_error_t *log_command(server_baton_t *b,
+ svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ const char *fmt, ...)
+{
+ const char *remote_host, *timestr, *log, *line;
+ va_list ap;
+ apr_size_t nbytes;
+
+ if (b->log_file == NULL)
+ return SVN_NO_ERROR;
+
+ remote_host = svn_ra_svn_conn_remote_host(conn);
+ timestr = svn_time_to_cstring(apr_time_now(), pool);
+
+ va_start(ap, fmt);
+ log = apr_pvsprintf(pool, fmt, ap);
+ va_end(ap);
+
+ line = apr_psprintf(pool, "%" APR_PID_T_FMT
+ " %s %s %s %s %s" APR_EOL_STR,
+ getpid(), timestr,
+ (remote_host ? remote_host : "-"),
+ (b->user ? b->user : "-"), b->repos_name, log);
+ nbytes = strlen(line);
+
+ return log_write(b->log_file, line, nbytes, pool);
+}
+
+svn_error_t *load_configs(svn_config_t **cfg,
+ svn_config_t **pwdb,
+ svn_authz_t **authzdb,
+ enum username_case_type *username_case,
+ const char *filename,
+ svn_boolean_t must_exist,
+ const char *base,
+ server_baton_t *server,
+ svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool)
+{
+ const char *pwdb_path, *authzdb_path;
+ svn_error_t *err;
+
+ SVN_ERR(svn_config_read2(cfg, filename, must_exist, FALSE, pool));
+
+ svn_config_get(*cfg, &pwdb_path, SVN_CONFIG_SECTION_GENERAL,
+ SVN_CONFIG_OPTION_PASSWORD_DB, NULL);
+
+ *pwdb = NULL;
+ if (pwdb_path)
+ {
+ pwdb_path = svn_dirent_canonicalize(pwdb_path, pool);
+ pwdb_path = svn_dirent_join(base, pwdb_path, pool);
+
+ err = svn_config_read2(pwdb, pwdb_path, TRUE, FALSE, pool);
+ if (err)
+ {
+ if (server)
+ /* Called by listening server; log error no matter what it is. */
+ log_server_error(err, server, conn, pool);
+
+ /* Because it may be possible to read the pwdb file with some
+ access methods and not others, ignore errors reading the pwdb
+ file and just don't present password authentication as an
+ option. Also, some authentications (e.g. --tunnel) can
+ proceed without it anyway.
+
+ ### Not entirely sure why SVN_ERR_BAD_FILENAME is checked
+ ### for here. That seems to have been introduced in r856914,
+ ### and only in r870942 was the APR_EACCES check introduced. */
+ if (err->apr_err != SVN_ERR_BAD_FILENAME
+ && ! APR_STATUS_IS_EACCES(err->apr_err))
+ {
+ if (server)
+ {
+ /* Called by listening server: Now that we've logged
+ * the error, clear it and return a nice, generic
+ * error to the user
+ * (http://subversion.tigris.org/issues/show_bug.cgi?id=2271). */
+ svn_error_clear(err);
+ return svn_error_create(SVN_ERR_AUTHN_FAILED, NULL, NULL);
+ }
+ /* Called during startup; return the error, whereupon it
+ * will go to standard error for the admin to see. */
+ return err;
+ }
+ else
+ /* Ignore SVN_ERR_BAD_FILENAME and APR_EACCES and proceed. */
+ svn_error_clear(err);
+ }
+ }
+
+ /* Read authz configuration. */
+ svn_config_get(*cfg, &authzdb_path, SVN_CONFIG_SECTION_GENERAL,
+ SVN_CONFIG_OPTION_AUTHZ_DB, NULL);
+ if (authzdb_path)
+ {
+ const char *case_force_val;
+
+ authzdb_path = svn_dirent_canonicalize(authzdb_path, pool);
+ authzdb_path = svn_dirent_join(base, authzdb_path, pool);
+ err = svn_repos_authz_read(authzdb, authzdb_path, TRUE, pool);
+ if (err)
+ {
+ if (server)
+ {
+ /* Called by listening server: Log the error, clear it,
+ * and return a nice, generic error to the user
+ * (http://subversion.tigris.org/issues/show_bug.cgi?id=2271). */
+ log_server_error(err, server, conn, pool);
+ svn_error_clear(err);
+ return svn_error_create(SVN_ERR_AUTHZ_INVALID_CONFIG, NULL, NULL);
+ }
+ else
+ /* Called during startup; return the error, whereupon it
+ * will go to standard error for the admin to see. */
+ return err;
+ }
+
+ /* Are we going to be case-normalizing usernames when we consult
+ * this authz file? */
+ svn_config_get(*cfg, &case_force_val, SVN_CONFIG_SECTION_GENERAL,
+ SVN_CONFIG_OPTION_FORCE_USERNAME_CASE, NULL);
+ if (case_force_val)
+ {
+ if (strcmp(case_force_val, "upper") == 0)
+ *username_case = CASE_FORCE_UPPER;
+ else if (strcmp(case_force_val, "lower") == 0)
+ *username_case = CASE_FORCE_LOWER;
+ else
+ *username_case = CASE_ASIS;
+ }
+ }
+ else
+ {
+ *authzdb = NULL;
+ *username_case = CASE_ASIS;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Set *FS_PATH to the portion of URL that is the path within the
+ repository, if URL is inside REPOS_URL (if URL is not inside
+ REPOS_URL, then error, with the effect on *FS_PATH undefined).
+
+ If the resultant fs path would be the empty string (i.e., URL and
+ REPOS_URL are the same), then set *FS_PATH to "/".
+
+ Assume that REPOS_URL and URL are already URI-decoded. */
+static svn_error_t *get_fs_path(const char *repos_url, const char *url,
+ const char **fs_path)
+{
+ apr_size_t len;
+
+ len = strlen(repos_url);
+ if (strncmp(url, repos_url, len) != 0)
+ return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL,
+ "'%s' is not the same repository as '%s'",
+ url, repos_url);
+ *fs_path = url + len;
+ if (! **fs_path)
+ *fs_path = "/";
+
+ return SVN_NO_ERROR;
+}
+
+/* --- AUTHENTICATION AND AUTHORIZATION FUNCTIONS --- */
+
+/* Convert TEXT to upper case if TO_UPPERCASE is TRUE, else
+ converts it to lower case. */
+static void convert_case(char *text, svn_boolean_t to_uppercase)
+{
+ char *c = text;
+ while (*c)
+ {
+ *c = (char)(to_uppercase ? apr_toupper(*c) : apr_tolower(*c));
+ ++c;
+ }
+}
+
+/* Set *ALLOWED to TRUE if PATH is accessible in the REQUIRED mode to
+ the user described in BATON according to the authz rules in BATON.
+ Use POOL for temporary allocations only. If no authz rules are
+ present in BATON, grant access by default. */
+static svn_error_t *authz_check_access(svn_boolean_t *allowed,
+ const char *path,
+ svn_repos_authz_access_t required,
+ server_baton_t *b,
+ apr_pool_t *pool)
+{
+ /* If authz cannot be performed, grant access. This is NOT the same
+ as the default policy when authz is performed on a path with no
+ rules. In the latter case, the default is to deny access, and is
+ set by svn_repos_authz_check_access. */
+ if (!b->authzdb)
+ {
+ *allowed = TRUE;
+ return SVN_NO_ERROR;
+ }
+
+ /* If the authz request is for the empty path (ie. ""), replace it
+ with the root path. This happens because of stripping done at
+ various levels in svnserve that remove the leading / on an
+ absolute path. Passing such a malformed path to the authz
+ routines throws them into an infinite loop and makes them miss
+ ACLs. */
+ if (path)
+ path = svn_fspath__canonicalize(path, pool);
+
+ /* If we have a username, and we've not yet used it + any username
+ case normalization that might be requested to determine "the
+ username we used for authz purposes", do so now. */
+ if (b->user && (! b->authz_user))
+ {
+ char *authz_user = apr_pstrdup(b->pool, b->user);
+ if (b->username_case == CASE_FORCE_UPPER)
+ convert_case(authz_user, TRUE);
+ else if (b->username_case == CASE_FORCE_LOWER)
+ convert_case(authz_user, FALSE);
+ b->authz_user = authz_user;
+ }
+
+ return svn_repos_authz_check_access(b->authzdb, b->authz_repos_name,
+ path, b->authz_user, required,
+ allowed, pool);
+}
+
+/* Set *ALLOWED to TRUE if PATH is readable by the user described in
+ * BATON. Use POOL for temporary allocations only. ROOT is not used.
+ * Implements the svn_repos_authz_func_t interface.
+ */
+static svn_error_t *authz_check_access_cb(svn_boolean_t *allowed,
+ svn_fs_root_t *root,
+ const char *path,
+ void *baton,
+ apr_pool_t *pool)
+{
+ server_baton_t *sb = baton;
+
+ return authz_check_access(allowed, path, svn_authz_read, sb, pool);
+}
+
+/* If authz is enabled in the specified BATON, return a read authorization
+ function. Otherwise, return NULL. */
+static svn_repos_authz_func_t authz_check_access_cb_func(server_baton_t *baton)
+{
+ if (baton->authzdb)
+ return authz_check_access_cb;
+ return NULL;
+}
+
+/* Set *ALLOWED to TRUE if the REQUIRED access to PATH is granted,
+ * according to the state in BATON. Use POOL for temporary
+ * allocations only. ROOT is not used. Implements the
+ * svn_repos_authz_callback_t interface.
+ */
+static svn_error_t *authz_commit_cb(svn_repos_authz_access_t required,
+ svn_boolean_t *allowed,
+ svn_fs_root_t *root,
+ const char *path,
+ void *baton,
+ apr_pool_t *pool)
+{
+ server_baton_t *sb = baton;
+
+ return authz_check_access(allowed, path, required, sb, pool);
+}
+
+
+enum access_type get_access(server_baton_t *b, enum authn_type auth)
+{
+ const char *var = (auth == AUTHENTICATED) ? SVN_CONFIG_OPTION_AUTH_ACCESS :
+ SVN_CONFIG_OPTION_ANON_ACCESS;
+ const char *val, *def = (auth == AUTHENTICATED) ? "write" : "read";
+ enum access_type result;
+
+ svn_config_get(b->cfg, &val, SVN_CONFIG_SECTION_GENERAL, var, def);
+ result = (strcmp(val, "write") == 0 ? WRITE_ACCESS :
+ strcmp(val, "read") == 0 ? READ_ACCESS : NO_ACCESS);
+ return (result == WRITE_ACCESS && b->read_only) ? READ_ACCESS : result;
+}
+
+static enum access_type current_access(server_baton_t *b)
+{
+ return get_access(b, (b->user) ? AUTHENTICATED : UNAUTHENTICATED);
+}
+
+/* Send authentication mechs for ACCESS_TYPE to the client. If NEEDS_USERNAME
+ is true, don't send anonymous mech even if that would give the desired
+ access. */
+static svn_error_t *send_mechs(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
+ server_baton_t *b, enum access_type required,
+ svn_boolean_t needs_username)
+{
+ if (!needs_username && get_access(b, UNAUTHENTICATED) >= required)
+ SVN_ERR(svn_ra_svn_write_word(conn, pool, "ANONYMOUS"));
+ if (b->tunnel_user && get_access(b, AUTHENTICATED) >= required)
+ SVN_ERR(svn_ra_svn_write_word(conn, pool, "EXTERNAL"));
+ if (b->pwdb && get_access(b, AUTHENTICATED) >= required)
+ SVN_ERR(svn_ra_svn_write_word(conn, pool, "CRAM-MD5"));
+ return SVN_NO_ERROR;
+}
+
+/* Context for cleanup handler. */
+struct cleanup_fs_access_baton
+{
+ svn_fs_t *fs;
+ apr_pool_t *pool;
+};
+
+/* Pool cleanup handler. Make sure fs's access_t points to NULL when
+ the command pool is destroyed. */
+static apr_status_t cleanup_fs_access(void *data)
+{
+ svn_error_t *serr;
+ struct cleanup_fs_access_baton *baton = data;
+
+ serr = svn_fs_set_access(baton->fs, NULL);
+ if (serr)
+ {
+ apr_status_t apr_err = serr->apr_err;
+ svn_error_clear(serr);
+ return apr_err;
+ }
+
+ return APR_SUCCESS;
+}
+
+
+/* Create an svn_fs_access_t in POOL for USER and associate it with
+ B's filesystem. Also, register a cleanup handler with POOL which
+ de-associates the svn_fs_access_t from B's filesystem. */
+static svn_error_t *
+create_fs_access(server_baton_t *b, apr_pool_t *pool)
+{
+ svn_fs_access_t *fs_access;
+ struct cleanup_fs_access_baton *cleanup_baton;
+
+ if (!b->user)
+ return SVN_NO_ERROR;
+
+ SVN_ERR(svn_fs_create_access(&fs_access, b->user, pool));
+ SVN_ERR(svn_fs_set_access(b->fs, fs_access));
+
+ cleanup_baton = apr_pcalloc(pool, sizeof(*cleanup_baton));
+ cleanup_baton->pool = pool;
+ cleanup_baton->fs = b->fs;
+ apr_pool_cleanup_register(pool, cleanup_baton, cleanup_fs_access,
+ apr_pool_cleanup_null);
+
+ return SVN_NO_ERROR;
+}
+
+/* Authenticate, once the client has chosen a mechanism and possibly
+ * sent an initial mechanism token. On success, set *success to true
+ * and b->user to the authenticated username (or NULL for anonymous).
+ * On authentication failure, report failure to the client and set
+ * *success to FALSE. On communications failure, return an error.
+ * If NEEDS_USERNAME is TRUE, don't allow anonymous authentication. */
+static svn_error_t *auth(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
+ const char *mech, const char *mecharg,
+ server_baton_t *b, enum access_type required,
+ svn_boolean_t needs_username,
+ svn_boolean_t *success)
+{
+ const char *user;
+ *success = FALSE;
+
+ if (get_access(b, AUTHENTICATED) >= required
+ && b->tunnel_user && strcmp(mech, "EXTERNAL") == 0)
+ {
+ if (*mecharg && strcmp(mecharg, b->tunnel_user) != 0)
+ return svn_ra_svn_write_tuple(conn, pool, "w(c)", "failure",
+ "Requested username does not match");
+ b->user = b->tunnel_user;
+ SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "w()", "success"));
+ *success = TRUE;
+ return SVN_NO_ERROR;
+ }
+
+ if (get_access(b, UNAUTHENTICATED) >= required
+ && strcmp(mech, "ANONYMOUS") == 0 && ! needs_username)
+ {
+ SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "w()", "success"));
+ *success = TRUE;
+ return SVN_NO_ERROR;
+ }
+
+ if (get_access(b, AUTHENTICATED) >= required
+ && b->pwdb && strcmp(mech, "CRAM-MD5") == 0)
+ {
+ SVN_ERR(svn_ra_svn_cram_server(conn, pool, b->pwdb, &user, success));
+ b->user = apr_pstrdup(b->pool, user);
+ return SVN_NO_ERROR;
+ }
+
+ return svn_ra_svn_write_tuple(conn, pool, "w(c)", "failure",
+ "Must authenticate with listed mechanism");
+}
+
+/* Perform an authentication request using the built-in SASL implementation. */
+static svn_error_t *
+internal_auth_request(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
+ server_baton_t *b, enum access_type required,
+ svn_boolean_t needs_username)
+{
+ svn_boolean_t success;
+ const char *mech, *mecharg;
+
+ SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "w((!", "success"));
+ SVN_ERR(send_mechs(conn, pool, b, required, needs_username));
+ SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "!)c)", b->realm));
+ do
+ {
+ SVN_ERR(svn_ra_svn_read_tuple(conn, pool, "w(?c)", &mech, &mecharg));
+ if (!*mech)
+ break;
+ SVN_ERR(auth(conn, pool, mech, mecharg, b, required, needs_username,
+ &success));
+ }
+ while (!success);
+ return SVN_NO_ERROR;
+}
+
+/* Perform an authentication request in order to get an access level of
+ * REQUIRED or higher. Since the client may escape the authentication
+ * exchange, the caller should check current_access(b) to see if
+ * authentication succeeded. */
+static svn_error_t *auth_request(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
+ server_baton_t *b, enum access_type required,
+ svn_boolean_t needs_username)
+{
+#ifdef SVN_HAVE_SASL
+ if (b->use_sasl)
+ return cyrus_auth_request(conn, pool, b, required, needs_username);
+#endif
+
+ return internal_auth_request(conn, pool, b, required, needs_username);
+}
+
+/* Send a trivial auth notification on CONN which lists no mechanisms,
+ * indicating that authentication is unnecessary. Usually called in
+ * response to invocation of a svnserve command.
+ */
+static svn_error_t *trivial_auth_request(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool, server_baton_t *b)
+{
+ return svn_ra_svn_write_cmd_response(conn, pool, "()c", "");
+}
+
+/* Ensure that the client has the REQUIRED access by checking the
+ * access directives (both blanket and per-directory) in BATON. If
+ * PATH is NULL, then only the blanket access configuration will
+ * impact the result.
+ *
+ * If NEEDS_USERNAME is TRUE, then a lookup is only successful if the
+ * user described in BATON is authenticated and, well, has a username
+ * assigned to him.
+ *
+ * Use POOL for temporary allocations only.
+ */
+static svn_boolean_t lookup_access(apr_pool_t *pool,
+ server_baton_t *baton,
+ svn_ra_svn_conn_t *conn,
+ svn_repos_authz_access_t required,
+ const char *path,
+ svn_boolean_t needs_username)
+{
+ enum access_type req = (required & svn_authz_write) ?
+ WRITE_ACCESS : READ_ACCESS;
+ svn_boolean_t authorized;
+ svn_error_t *err;
+
+ /* Get authz's opinion on the access. */
+ err = authz_check_access(&authorized, path, required, baton, pool);
+
+ /* If an error made lookup fail, deny access. */
+ if (err)
+ {
+ log_server_error(err, baton, conn, pool);
+ svn_error_clear(err);
+ return FALSE;
+ }
+
+ /* If the required access is blanket-granted AND granted by authz
+ AND we already have a username if one is required, then the
+ lookup has succeeded. */
+ if (current_access(baton) >= req
+ && authorized
+ && (! needs_username || baton->user))
+ return TRUE;
+
+ return FALSE;
+}
+
+/* Check that the client has the REQUIRED access by consulting the
+ * authentication and authorization states stored in BATON. If the
+ * client does not have the required access credentials, attempt to
+ * authenticate the client to get that access, using CONN for
+ * communication.
+ *
+ * This function is supposed to be called to handle the authentication
+ * half of a standard svn protocol reply. If an error is returned, it
+ * probably means that the server can terminate the client connection
+ * with an apologetic error, as it implies an authentication failure.
+ *
+ * PATH and NEEDS_USERNAME are passed along to lookup_access, their
+ * behaviour is documented there.
+ */
+static svn_error_t *must_have_access(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ server_baton_t *b,
+ svn_repos_authz_access_t required,
+ const char *path,
+ svn_boolean_t needs_username)
+{
+ enum access_type req = (required & svn_authz_write) ?
+ WRITE_ACCESS : READ_ACCESS;
+
+ /* See whether the user already has the required access. If so,
+ nothing needs to be done. Create the FS access and send a
+ trivial auth request. */
+ if (lookup_access(pool, b, conn, required, path, needs_username))
+ {
+ SVN_ERR(create_fs_access(b, pool));
+ return trivial_auth_request(conn, pool, b);
+ }
+
+ /* If the required blanket access can be obtained by authenticating,
+ try that. Unfortunately, we can't tell until after
+ authentication whether authz will work or not. We force
+ requiring a username because we need one to be able to check
+ authz configuration again with a different user credentials than
+ the first time round. */
+ if (b->user == NULL
+ && get_access(b, AUTHENTICATED) >= req
+ && (b->tunnel_user || b->pwdb || b->use_sasl))
+ SVN_ERR(auth_request(conn, pool, b, req, TRUE));
+
+ /* Now that an authentication has been done get the new take of
+ authz on the request. */
+ if (! lookup_access(pool, b, conn, required, path, needs_username))
+ return svn_error_create(SVN_ERR_RA_SVN_CMD_ERR,
+ error_create_and_log(SVN_ERR_RA_NOT_AUTHORIZED,
+ NULL, NULL, b, conn, pool),
+ NULL);
+
+ /* Else, access is granted, and there is much rejoicing. */
+ SVN_ERR(create_fs_access(b, pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* --- REPORTER COMMAND SET --- */
+
+/* To allow for pipelining, reporter commands have no reponses. If we
+ * get an error, we ignore all subsequent reporter commands and return
+ * the error finish_report, to be handled by the calling command.
+ */
+
+static svn_error_t *set_path(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
+ apr_array_header_t *params, void *baton)
+{
+ report_driver_baton_t *b = baton;
+ const char *path, *lock_token, *depth_word;
+ svn_revnum_t rev;
+ /* Default to infinity, for old clients that don't send depth. */
+ svn_depth_t depth = svn_depth_infinity;
+ svn_boolean_t start_empty;
+
+ SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "crb?(?c)?w",
+ &path, &rev, &start_empty, &lock_token,
+ &depth_word));
+ if (depth_word)
+ depth = svn_depth_from_word(depth_word);
+ path = svn_relpath_canonicalize(path, pool);
+ if (b->from_rev && strcmp(path, "") == 0)
+ *b->from_rev = rev;
+ if (!b->err)
+ b->err = svn_repos_set_path3(b->report_baton, path, rev, depth,
+ start_empty, lock_token, pool);
+ b->entry_counter++;
+ if (!start_empty)
+ b->only_empty_entries = FALSE;
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *delete_path(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
+ apr_array_header_t *params, void *baton)
+{
+ report_driver_baton_t *b = baton;
+ const char *path;
+
+ SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "c", &path));
+ path = svn_relpath_canonicalize(path, pool);
+ if (!b->err)
+ b->err = svn_repos_delete_path(b->report_baton, path, pool);
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *link_path(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
+ apr_array_header_t *params, void *baton)
+{
+ report_driver_baton_t *b = baton;
+ const char *path, *url, *lock_token, *fs_path, *depth_word;
+ svn_revnum_t rev;
+ svn_boolean_t start_empty;
+ /* Default to infinity, for old clients that don't send depth. */
+ svn_depth_t depth = svn_depth_infinity;
+
+ SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "ccrb?(?c)?w",
+ &path, &url, &rev, &start_empty,
+ &lock_token, &depth_word));
+
+ /* ### WHAT?! The link path is an absolute URL?! Didn't see that
+ coming... -- cmpilato */
+ path = svn_relpath_canonicalize(path, pool);
+ url = svn_uri_canonicalize(url, pool);
+ if (depth_word)
+ depth = svn_depth_from_word(depth_word);
+ if (!b->err)
+ b->err = get_fs_path(svn_path_uri_decode(b->repos_url, pool),
+ svn_path_uri_decode(url, pool),
+ &fs_path);
+ if (!b->err)
+ b->err = svn_repos_link_path3(b->report_baton, path, fs_path, rev,
+ depth, start_empty, lock_token, pool);
+ b->entry_counter++;
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *finish_report(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
+ apr_array_header_t *params, void *baton)
+{
+ report_driver_baton_t *b = baton;
+
+ /* No arguments to parse. */
+ SVN_ERR(trivial_auth_request(conn, pool, b->sb));
+ if (!b->err)
+ b->err = svn_repos_finish_report(b->report_baton, pool);
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *abort_report(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
+ apr_array_header_t *params, void *baton)
+{
+ report_driver_baton_t *b = baton;
+
+ /* No arguments to parse. */
+ svn_error_clear(svn_repos_abort_report(b->report_baton, pool));
+ return SVN_NO_ERROR;
+}
+
+static const svn_ra_svn_cmd_entry_t report_commands[] = {
+ { "set-path", set_path },
+ { "delete-path", delete_path },
+ { "link-path", link_path },
+ { "finish-report", finish_report, TRUE },
+ { "abort-report", abort_report, TRUE },
+ { NULL }
+};
+
+/* Accept a report from the client, drive the network editor with the
+ * result, and then write an empty command response. If there is a
+ * non-protocol failure, accept_report will abort the edit and return
+ * a command error to be reported by handle_commands().
+ *
+ * If only_empty_entry is not NULL and the report contains only one
+ * item, and that item is empty, set *only_empty_entry to TRUE, else
+ * set it to FALSE.
+ *
+ * If from_rev is not NULL, set *from_rev to the revision number from
+ * the set-path on ""; if somehow set-path "" never happens, set
+ * *from_rev to SVN_INVALID_REVNUM.
+ */
+static svn_error_t *accept_report(svn_boolean_t *only_empty_entry,
+ svn_revnum_t *from_rev,
+ svn_ra_svn_conn_t *conn, apr_pool_t *pool,
+ server_baton_t *b, svn_revnum_t rev,
+ const char *target, const char *tgt_path,
+ svn_boolean_t text_deltas,
+ svn_depth_t depth,
+ svn_boolean_t send_copyfrom_args,
+ svn_boolean_t ignore_ancestry)
+{
+ const svn_delta_editor_t *editor;
+ void *edit_baton, *report_baton;
+ report_driver_baton_t rb;
+ svn_error_t *err;
+
+ /* Make an svn_repos report baton. Tell it to drive the network editor
+ * when the report is complete. */
+ svn_ra_svn_get_editor(&editor, &edit_baton, conn, pool, NULL, NULL);
+ SVN_CMD_ERR(svn_repos_begin_report2(&report_baton, rev, b->repos,
+ b->fs_path->data, target, tgt_path,
+ text_deltas, depth, ignore_ancestry,
+ send_copyfrom_args,
+ editor, edit_baton,
+ authz_check_access_cb_func(b),
+ b, pool));
+
+ rb.sb = b;
+ rb.repos_url = svn_path_uri_decode(b->repos_url, pool);
+ rb.report_baton = report_baton;
+ rb.err = NULL;
+ rb.entry_counter = 0;
+ rb.only_empty_entries = TRUE;
+ rb.from_rev = from_rev;
+ if (from_rev)
+ *from_rev = SVN_INVALID_REVNUM;
+ err = svn_ra_svn_handle_commands2(conn, pool, report_commands, &rb, TRUE);
+ if (err)
+ {
+ /* Network or protocol error while handling commands. */
+ svn_error_clear(rb.err);
+ return err;
+ }
+ else if (rb.err)
+ {
+ /* Some failure during the reporting or editing operations. */
+ SVN_CMD_ERR(rb.err);
+ }
+ SVN_ERR(svn_ra_svn_write_cmd_response(conn, pool, ""));
+
+ if (only_empty_entry)
+ *only_empty_entry = rb.entry_counter == 1 && rb.only_empty_entries;
+
+ return SVN_NO_ERROR;
+}
+
+/* --- MAIN COMMAND SET --- */
+
+/* Write out a list of property diffs. PROPDIFFS is an array of svn_prop_t
+ * values. */
+static svn_error_t *write_prop_diffs(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ const apr_array_header_t *propdiffs)
+{
+ int i;
+
+ for (i = 0; i < propdiffs->nelts; ++i)
+ {
+ const svn_prop_t *prop = &APR_ARRAY_IDX(propdiffs, i, svn_prop_t);
+
+ SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "c(?s)",
+ prop->name, prop->value));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Write out a lock to the client. */
+static svn_error_t *write_lock(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ svn_lock_t *lock)
+{
+ const char *cdate, *edate;
+
+ cdate = svn_time_to_cstring(lock->creation_date, pool);
+ edate = lock->expiration_date
+ ? svn_time_to_cstring(lock->expiration_date, pool) : NULL;
+ SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "ccc(?c)c(?c)", lock->path,
+ lock->token, lock->owner, lock->comment,
+ cdate, edate));
+
+ return SVN_NO_ERROR;
+}
+
+/* ### This really belongs in libsvn_repos. */
+/* Get the properties for a path, with hardcoded committed-info values. */
+static svn_error_t *get_props(apr_hash_t **props, svn_fs_root_t *root,
+ const char *path, apr_pool_t *pool)
+{
+ svn_string_t *str;
+ svn_revnum_t crev;
+ const char *cdate, *cauthor, *uuid;
+
+ /* Get the properties. */
+ SVN_ERR(svn_fs_node_proplist(props, root, path, pool));
+
+ /* Hardcode the values for the committed revision, date, and author. */
+ SVN_ERR(svn_repos_get_committed_info(&crev, &cdate, &cauthor, root,
+ path, pool));
+ str = svn_string_create(apr_psprintf(pool, "%ld", crev),
+ pool);
+ apr_hash_set(*props, SVN_PROP_ENTRY_COMMITTED_REV, APR_HASH_KEY_STRING, str);
+ str = (cdate) ? svn_string_create(cdate, pool) : NULL;
+ apr_hash_set(*props, SVN_PROP_ENTRY_COMMITTED_DATE, APR_HASH_KEY_STRING,
+ str);
+ str = (cauthor) ? svn_string_create(cauthor, pool) : NULL;
+ apr_hash_set(*props, SVN_PROP_ENTRY_LAST_AUTHOR, APR_HASH_KEY_STRING, str);
+
+ /* Hardcode the values for the UUID. */
+ SVN_ERR(svn_fs_get_uuid(svn_fs_root_fs(root), &uuid, pool));
+ str = (uuid) ? svn_string_create(uuid, pool) : NULL;
+ apr_hash_set(*props, SVN_PROP_ENTRY_UUID, APR_HASH_KEY_STRING, str);
+
+ return SVN_NO_ERROR;
+}
+
+/* Set BATON->FS_PATH for the repository URL found in PARAMS. */
+static svn_error_t *reparent(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
+ apr_array_header_t *params, void *baton)
+{
+ server_baton_t *b = baton;
+ const char *url;
+ const char *fs_path;
+
+ SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "c", &url));
+ url = svn_uri_canonicalize(url, pool);
+ SVN_ERR(trivial_auth_request(conn, pool, b));
+ SVN_CMD_ERR(get_fs_path(svn_path_uri_decode(b->repos_url, pool),
+ svn_path_uri_decode(url, pool),
+ &fs_path));
+ SVN_ERR(log_command(b, conn, pool, "%s", svn_log__reparent(fs_path, pool)));
+ svn_stringbuf_set(b->fs_path, fs_path);
+ SVN_ERR(svn_ra_svn_write_cmd_response(conn, pool, ""));
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *get_latest_rev(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
+ apr_array_header_t *params, void *baton)
+{
+ server_baton_t *b = baton;
+ svn_revnum_t rev;
+
+ SVN_ERR(log_command(b, conn, pool, "get-latest-rev"));
+
+ SVN_ERR(trivial_auth_request(conn, pool, b));
+ SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->fs, pool));
+ SVN_ERR(svn_ra_svn_write_cmd_response(conn, pool, "r", rev));
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *get_dated_rev(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
+ apr_array_header_t *params, void *baton)
+{
+ server_baton_t *b = baton;
+ svn_revnum_t rev;
+ apr_time_t tm;
+ const char *timestr;
+
+ SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "c", &timestr));
+ SVN_ERR(log_command(b, conn, pool, "get-dated-rev %s", timestr));
+
+ SVN_ERR(trivial_auth_request(conn, pool, b));
+ SVN_CMD_ERR(svn_time_from_cstring(&tm, timestr, pool));
+ SVN_CMD_ERR(svn_repos_dated_revision(&rev, b->repos, tm, pool));
+ SVN_ERR(svn_ra_svn_write_cmd_response(conn, pool, "r", rev));
+ return SVN_NO_ERROR;
+}
+
+/* Common logic for change_rev_prop() and change_rev_prop2(). */
+static svn_error_t *do_change_rev_prop(svn_ra_svn_conn_t *conn,
+ server_baton_t *b,
+ svn_revnum_t rev,
+ const char *name,
+ const svn_string_t *const *old_value_p,
+ const svn_string_t *value,
+ apr_pool_t *pool)
+{
+ SVN_ERR(must_have_access(conn, pool, b, svn_authz_write, NULL, FALSE));
+ SVN_ERR(log_command(b, conn, pool, "%s",
+ svn_log__change_rev_prop(rev, name, pool)));
+ SVN_CMD_ERR(svn_repos_fs_change_rev_prop4(b->repos, rev, b->user,
+ name, old_value_p, value,
+ TRUE, TRUE,
+ authz_check_access_cb_func(b), b,
+ pool));
+ SVN_ERR(svn_ra_svn_write_cmd_response(conn, pool, ""));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *change_rev_prop2(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
+ apr_array_header_t *params, void *baton)
+{
+ server_baton_t *b = baton;
+ svn_revnum_t rev;
+ const char *name;
+ svn_string_t *value;
+ const svn_string_t *const *old_value_p;
+ svn_string_t *old_value;
+ svn_boolean_t dont_care;
+
+ SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "rc(?s)(b?s)",
+ &rev, &name, &value,
+ &dont_care, &old_value));
+
+ /* Argument parsing. */
+ if (dont_care)
+ old_value_p = NULL;
+ else
+ old_value_p = (const svn_string_t *const *)&old_value;
+
+ /* Input validation. */
+ if (dont_care && old_value)
+ {
+ svn_error_t *err;
+ err = svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL,
+ "'previous-value' and 'dont-care' cannot both be "
+ "set in 'change-rev-prop2' request");
+ return log_fail_and_flush(err, b, conn, pool);
+ }
+
+ /* Do it. */
+ SVN_ERR(do_change_rev_prop(conn, b, rev, name, old_value_p, value, pool));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *change_rev_prop(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
+ apr_array_header_t *params, void *baton)
+{
+ server_baton_t *b = baton;
+ svn_revnum_t rev;
+ const char *name;
+ svn_string_t *value;
+
+ /* Because the revprop value was at one time mandatory, the usual
+ optional element pattern "(?s)" isn't used. */
+ SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "rc?s", &rev, &name, &value));
+
+ SVN_ERR(do_change_rev_prop(conn, b, rev, name, NULL, value, pool));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *rev_proplist(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
+ apr_array_header_t *params, void *baton)
+{
+ server_baton_t *b = baton;
+ svn_revnum_t rev;
+ apr_hash_t *props;
+
+ SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "r", &rev));
+ SVN_ERR(log_command(b, conn, pool, "%s", svn_log__rev_proplist(rev, pool)));
+
+ SVN_ERR(trivial_auth_request(conn, pool, b));
+ SVN_CMD_ERR(svn_repos_fs_revision_proplist(&props, b->repos, rev,
+ authz_check_access_cb_func(b), b,
+ pool));
+ SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "w((!", "success"));
+ SVN_ERR(svn_ra_svn_write_proplist(conn, pool, props));
+ SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "!))"));
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *rev_prop(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
+ apr_array_header_t *params, void *baton)
+{
+ server_baton_t *b = baton;
+ svn_revnum_t rev;
+ const char *name;
+ svn_string_t *value;
+
+ SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "rc", &rev, &name));
+ SVN_ERR(log_command(b, conn, pool, "%s",
+ svn_log__rev_prop(rev, name, pool)));
+
+ SVN_ERR(trivial_auth_request(conn, pool, b));
+ SVN_CMD_ERR(svn_repos_fs_revision_prop(&value, b->repos, rev, name,
+ authz_check_access_cb_func(b), b,
+ pool));
+ SVN_ERR(svn_ra_svn_write_cmd_response(conn, pool, "(?s)", value));
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *commit_done(const svn_commit_info_t *commit_info,
+ void *baton, apr_pool_t *pool)
+{
+ commit_callback_baton_t *ccb = baton;
+
+ *ccb->new_rev = commit_info->revision;
+ *ccb->date = commit_info->date
+ ? apr_pstrdup(ccb->pool, commit_info->date): NULL;
+ *ccb->author = commit_info->author
+ ? apr_pstrdup(ccb->pool, commit_info->author) : NULL;
+ *ccb->post_commit_err = commit_info->post_commit_err
+ ? apr_pstrdup(ccb->pool, commit_info->post_commit_err) : NULL;
+ return SVN_NO_ERROR;
+}
+
+/* Add the LOCK_TOKENS (if any) to the filesystem access context,
+ * checking path authorizations using the state in SB as we go.
+ * LOCK_TOKENS is an array of svn_ra_svn_item_t structs. Return a
+ * client error if LOCK_TOKENS is not a list of lists. If a lock
+ * violates the authz configuration, return SVN_ERR_RA_NOT_AUTHORIZED
+ * to the client. Use POOL for temporary allocations only.
+ */
+static svn_error_t *add_lock_tokens(svn_ra_svn_conn_t *conn,
+ const apr_array_header_t *lock_tokens,
+ server_baton_t *sb,
+ apr_pool_t *pool)
+{
+ int i;
+ svn_fs_access_t *fs_access;
+
+ SVN_ERR(svn_fs_get_access(&fs_access, sb->fs));
+
+ /* If there is no access context, nowhere to add the tokens. */
+ if (! fs_access)
+ return SVN_NO_ERROR;
+
+ for (i = 0; i < lock_tokens->nelts; ++i)
+ {
+ const char *path, *token, *full_path;
+ svn_ra_svn_item_t *path_item, *token_item;
+ svn_ra_svn_item_t *item = &APR_ARRAY_IDX(lock_tokens, i,
+ svn_ra_svn_item_t);
+ if (item->kind != SVN_RA_SVN_LIST)
+ return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
+ "Lock tokens aren't a list of lists");
+
+ path_item = &APR_ARRAY_IDX(item->u.list, 0, svn_ra_svn_item_t);
+ if (path_item->kind != SVN_RA_SVN_STRING)
+ return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
+ "Lock path isn't a string");
+
+ token_item = &APR_ARRAY_IDX(item->u.list, 1, svn_ra_svn_item_t);
+ if (token_item->kind != SVN_RA_SVN_STRING)
+ return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
+ "Lock token isn't a string");
+
+ path = path_item->u.string->data;
+ full_path = svn_fspath__join(sb->fs_path->data,
+ svn_relpath_canonicalize(path, pool),
+ pool);
+
+ if (! lookup_access(pool, sb, conn, svn_authz_write,
+ full_path, TRUE))
+ return error_create_and_log(SVN_ERR_RA_NOT_AUTHORIZED, NULL, NULL,
+ sb, conn, pool);
+
+ token = token_item->u.string->data;
+ SVN_ERR(svn_fs_access_add_lock_token2(fs_access, path, token));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Unlock the paths with lock tokens in LOCK_TOKENS, ignoring any errors.
+ LOCK_TOKENS contains svn_ra_svn_item_t elements, assumed to be lists. */
+static svn_error_t *unlock_paths(const apr_array_header_t *lock_tokens,
+ server_baton_t *sb,
+ svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool)
+{
+ int i;
+ apr_pool_t *iterpool;
+
+ iterpool = svn_pool_create(pool);
+
+ for (i = 0; i < lock_tokens->nelts; ++i)
+ {
+ svn_ra_svn_item_t *item, *path_item, *token_item;
+ const char *path, *token, *full_path;
+ svn_error_t *err;
+ svn_pool_clear(iterpool);
+
+ item = &APR_ARRAY_IDX(lock_tokens, i, svn_ra_svn_item_t);
+ path_item = &APR_ARRAY_IDX(item->u.list, 0, svn_ra_svn_item_t);
+ token_item = &APR_ARRAY_IDX(item->u.list, 1, svn_ra_svn_item_t);
+
+ path = path_item->u.string->data;
+ token = token_item->u.string->data;
+
+ full_path = svn_fspath__join(sb->fs_path->data,
+ svn_relpath_canonicalize(path, iterpool),
+ iterpool);
+
+ /* The lock may have become defunct after the commit, so ignore such
+ errors. */
+ err = svn_repos_fs_unlock(sb->repos, full_path, token,
+ FALSE, iterpool);
+ log_server_error(err, sb, conn, iterpool);
+ svn_error_clear(err);
+ }
+
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *commit(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
+ apr_array_header_t *params, void *baton)
+{
+ server_baton_t *b = baton;
+ const char *log_msg = NULL,
+ *date = NULL,
+ *author = NULL,
+ *post_commit_err = NULL;
+ apr_array_header_t *lock_tokens;
+ svn_boolean_t keep_locks;
+ apr_array_header_t *revprop_list = NULL;
+ apr_hash_t *revprop_table;
+ const svn_delta_editor_t *editor;
+ void *edit_baton;
+ svn_boolean_t aborted;
+ commit_callback_baton_t ccb;
+ svn_revnum_t new_rev;
+
+ if (params->nelts == 1)
+ {
+ /* Clients before 1.2 don't send lock-tokens, keep-locks,
+ and rev-props fields. */
+ SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "c", &log_msg));
+ lock_tokens = NULL;
+ keep_locks = TRUE;
+ revprop_list = NULL;
+ }
+ else
+ {
+ /* Clients before 1.5 don't send the rev-props field. */
+ SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "clb?l", &log_msg,
+ &lock_tokens, &keep_locks,
+ &revprop_list));
+ }
+
+ /* The handling for locks is a little problematic, because the
+ protocol won't let us send several auth requests once one has
+ succeeded. So we request write access and a username before
+ adding tokens (if we have any), and subsequently fail if a lock
+ violates authz. */
+ SVN_ERR(must_have_access(conn, pool, b, svn_authz_write,
+ NULL,
+ (lock_tokens && lock_tokens->nelts)));
+
+ /* Authorize the lock tokens and give them to the FS if we got
+ any. */
+ if (lock_tokens && lock_tokens->nelts)
+ SVN_CMD_ERR(add_lock_tokens(conn, lock_tokens, b, pool));
+
+ if (revprop_list)
+ SVN_ERR(svn_ra_svn_parse_proplist(revprop_list, pool, &revprop_table));
+ else
+ {
+ revprop_table = apr_hash_make(pool);
+ apr_hash_set(revprop_table, SVN_PROP_REVISION_LOG, APR_HASH_KEY_STRING,
+ svn_string_create(log_msg, pool));
+ }
+
+ /* Get author from the baton, making sure clients can't circumvent
+ the authentication via the revision props. */
+ apr_hash_set(revprop_table, SVN_PROP_REVISION_AUTHOR, APR_HASH_KEY_STRING,
+ b->user ? svn_string_create(b->user, pool) : NULL);
+
+ ccb.pool = pool;
+ ccb.new_rev = &new_rev;
+ ccb.date = &date;
+ ccb.author = &author;
+ ccb.post_commit_err = &post_commit_err;
+ /* ### Note that svn_repos_get_commit_editor5 actually wants a decoded URL. */
+ SVN_CMD_ERR(svn_repos_get_commit_editor5
+ (&editor, &edit_baton, b->repos, NULL,
+ svn_path_uri_decode(b->repos_url, pool),
+ b->fs_path->data, revprop_table,
+ commit_done, &ccb,
+ authz_commit_cb, baton, pool));
+ SVN_ERR(svn_ra_svn_write_cmd_response(conn, pool, ""));
+ SVN_ERR(svn_ra_svn_drive_editor(conn, pool, editor, edit_baton, &aborted));
+ if (!aborted)
+ {
+ SVN_ERR(log_command(b, conn, pool, "%s",
+ svn_log__commit(new_rev, pool)));
+ SVN_ERR(trivial_auth_request(conn, pool, b));
+
+ /* In tunnel mode, deltify before answering the client, because
+ answering may cause the client to terminate the connection
+ and thus kill the server. But otherwise, deltify after
+ answering the client, to avoid user-visible delay. */
+
+ if (b->tunnel)
+ SVN_ERR(svn_fs_deltify_revision(b->fs, new_rev, pool));
+
+ /* Unlock the paths. */
+ if (! keep_locks && lock_tokens && lock_tokens->nelts)
+ SVN_ERR(unlock_paths(lock_tokens, b, conn, pool));
+
+ SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "r(?c)(?c)(?c)",
+ new_rev, date, author, post_commit_err));
+
+ if (! b->tunnel)
+ SVN_ERR(svn_fs_deltify_revision(b->fs, new_rev, pool));
+ }
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *get_file(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
+ apr_array_header_t *params, void *baton)
+{
+ server_baton_t *b = baton;
+ const char *path, *full_path, *hex_digest;
+ svn_revnum_t rev;
+ svn_fs_root_t *root;
+ svn_stream_t *contents;
+ apr_hash_t *props = NULL;
+ svn_string_t write_str;
+ char buf[4096];
+ apr_size_t len;
+ svn_boolean_t want_props, want_contents;
+ svn_checksum_t *checksum;
+ svn_error_t *err, *write_err;
+
+ /* Parse arguments. */
+ SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "c(?r)bb", &path, &rev,
+ &want_props, &want_contents));
+
+ full_path = svn_fspath__join(b->fs_path->data,
+ svn_relpath_canonicalize(path, pool), pool);
+
+ /* Check authorizations */
+ SVN_ERR(must_have_access(conn, pool, b, svn_authz_read,
+ full_path, FALSE));
+
+ if (!SVN_IS_VALID_REVNUM(rev))
+ SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->fs, pool));
+
+ SVN_ERR(log_command(b, conn, pool, "%s",
+ svn_log__get_file(full_path, rev,
+ want_contents, want_props, pool)));
+
+ /* Fetch the properties and a stream for the contents. */
+ SVN_CMD_ERR(svn_fs_revision_root(&root, b->fs, rev, pool));
+ SVN_CMD_ERR(svn_fs_file_checksum(&checksum, svn_checksum_md5, root,
+ full_path, TRUE, pool));
+ hex_digest = svn_checksum_to_cstring_display(checksum, pool);
+ if (want_props)
+ SVN_CMD_ERR(get_props(&props, root, full_path, pool));
+ if (want_contents)
+ SVN_CMD_ERR(svn_fs_file_contents(&contents, root, full_path, pool));
+
+ /* Send successful command response with revision and props. */
+ SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "w((?c)r(!", "success",
+ hex_digest, rev));
+ SVN_ERR(svn_ra_svn_write_proplist(conn, pool, props));
+ SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "!))"));
+
+ /* Now send the file's contents. */
+ if (want_contents)
+ {
+ err = SVN_NO_ERROR;
+ while (1)
+ {
+ len = sizeof(buf);
+ err = svn_stream_read(contents, buf, &len);
+ if (err)
+ break;
+ if (len > 0)
+ {
+ write_str.data = buf;
+ write_str.len = len;
+ SVN_ERR(svn_ra_svn_write_string(conn, pool, &write_str));
+ }
+ if (len < sizeof(buf))
+ {
+ err = svn_stream_close(contents);
+ break;
+ }
+ }
+ write_err = svn_ra_svn_write_cstring(conn, pool, "");
+ if (write_err)
+ {
+ svn_error_clear(err);
+ return write_err;
+ }
+ SVN_CMD_ERR(err);
+ SVN_ERR(svn_ra_svn_write_cmd_response(conn, pool, ""));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *get_dir(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
+ apr_array_header_t *params, void *baton)
+{
+ server_baton_t *b = baton;
+ const char *path, *full_path, *file_path, *cauthor, *cdate;
+ svn_revnum_t rev;
+ apr_hash_t *entries, *props = NULL, *file_props;
+ apr_hash_index_t *hi;
+ svn_fs_root_t *root;
+ apr_pool_t *subpool;
+ svn_boolean_t want_props, want_contents;
+ apr_uint64_t dirent_fields;
+ apr_array_header_t *dirent_fields_list = NULL;
+ svn_ra_svn_item_t *elt;
+
+ SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "c(?r)bb?l", &path, &rev,
+ &want_props, &want_contents,
+ &dirent_fields_list));
+
+ if (! dirent_fields_list)
+ {
+ dirent_fields = SVN_DIRENT_ALL;
+ }
+ else
+ {
+ int i;
+
+ dirent_fields = 0;
+
+ for (i = 0; i < dirent_fields_list->nelts; ++i)
+ {
+ elt = &APR_ARRAY_IDX(dirent_fields_list, i, svn_ra_svn_item_t);
+
+ if (elt->kind != SVN_RA_SVN_WORD)
+ return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
+ "Dirent field not a string");
+
+ if (strcmp(SVN_RA_SVN_DIRENT_KIND, elt->u.word) == 0)
+ dirent_fields |= SVN_DIRENT_KIND;
+ else if (strcmp(SVN_RA_SVN_DIRENT_SIZE, elt->u.word) == 0)
+ dirent_fields |= SVN_DIRENT_SIZE;
+ else if (strcmp(SVN_RA_SVN_DIRENT_HAS_PROPS, elt->u.word) == 0)
+ dirent_fields |= SVN_DIRENT_HAS_PROPS;
+ else if (strcmp(SVN_RA_SVN_DIRENT_CREATED_REV, elt->u.word) == 0)
+ dirent_fields |= SVN_DIRENT_CREATED_REV;
+ else if (strcmp(SVN_RA_SVN_DIRENT_TIME, elt->u.word) == 0)
+ dirent_fields |= SVN_DIRENT_TIME;
+ else if (strcmp(SVN_RA_SVN_DIRENT_LAST_AUTHOR, elt->u.word) == 0)
+ dirent_fields |= SVN_DIRENT_LAST_AUTHOR;
+ }
+ }
+
+ full_path = svn_fspath__join(b->fs_path->data,
+ svn_relpath_canonicalize(path, pool), pool);
+
+ /* Check authorizations */
+ SVN_ERR(must_have_access(conn, pool, b, svn_authz_read,
+ full_path, FALSE));
+
+ if (!SVN_IS_VALID_REVNUM(rev))
+ SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->fs, pool));
+
+ SVN_ERR(log_command(b, conn, pool, "%s",
+ svn_log__get_dir(full_path, rev,
+ want_contents, want_props,
+ dirent_fields, pool)));
+
+ /* Fetch the root of the appropriate revision. */
+ SVN_CMD_ERR(svn_fs_revision_root(&root, b->fs, rev, pool));
+
+ /* Fetch the directory properties if requested. */
+ if (want_props)
+ SVN_CMD_ERR(get_props(&props, root, full_path, pool));
+
+ /* Fetch the directory entries if requested. */
+ if (want_contents)
+ {
+ SVN_CMD_ERR(svn_fs_dir_entries(&entries, root, full_path, pool));
+
+ /* Transform the hash table's FS entries into dirents. This probably
+ * belongs in libsvn_repos. */
+ subpool = svn_pool_create(pool);
+ for (hi = apr_hash_first(pool, entries); hi; hi = apr_hash_next(hi))
+ {
+ const char *name = svn__apr_hash_index_key(hi);
+ svn_fs_dirent_t *fsent = svn__apr_hash_index_val(hi);
+ svn_dirent_t *entry;
+
+ svn_pool_clear(subpool);
+
+ file_path = svn_fspath__join(full_path, name, subpool);
+
+ if (! lookup_access(subpool, b, conn, svn_authz_read,
+ file_path, FALSE))
+ {
+ apr_hash_set(entries, name, APR_HASH_KEY_STRING, NULL);
+ continue;
+ }
+
+ entry = apr_pcalloc(pool, sizeof(*entry));
+
+ if (dirent_fields & SVN_DIRENT_KIND)
+ {
+ /* kind */
+ entry->kind = fsent->kind;
+ }
+
+ if (dirent_fields & SVN_DIRENT_SIZE)
+ {
+ /* size */
+ if (entry->kind == svn_node_dir)
+ entry->size = 0;
+ else
+ SVN_CMD_ERR(svn_fs_file_length(&entry->size, root, file_path,
+ subpool));
+ }
+
+ if (dirent_fields & SVN_DIRENT_HAS_PROPS)
+ {
+ /* has_props */
+ SVN_CMD_ERR(svn_fs_node_proplist(&file_props, root, file_path,
+ subpool));
+ entry->has_props = (apr_hash_count(file_props) > 0);
+ }
+
+ if ((dirent_fields & SVN_DIRENT_LAST_AUTHOR)
+ || (dirent_fields & SVN_DIRENT_TIME)
+ || (dirent_fields & SVN_DIRENT_CREATED_REV))
+ {
+ /* created_rev, last_author, time */
+ SVN_CMD_ERR(svn_repos_get_committed_info(&entry->created_rev,
+ &cdate,
+ &cauthor, root,
+ file_path,
+ subpool));
+ entry->last_author = apr_pstrdup(pool, cauthor);
+ if (cdate)
+ SVN_CMD_ERR(svn_time_from_cstring(&entry->time, cdate,
+ subpool));
+ else
+ entry->time = (time_t) -1;
+ }
+
+ /* Store the entry. */
+ apr_hash_set(entries, name, APR_HASH_KEY_STRING, entry);
+ }
+ svn_pool_destroy(subpool);
+ }
+
+ /* Write out response. */
+ SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "w(r(!", "success", rev));
+ SVN_ERR(svn_ra_svn_write_proplist(conn, pool, props));
+ SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "!)(!"));
+ if (want_contents)
+ {
+ const char *missing_date = svn_time_to_cstring(0, pool);
+ for (hi = apr_hash_first(pool, entries); hi; hi = apr_hash_next(hi))
+ {
+ const char *name = svn__apr_hash_index_key(hi);
+ svn_dirent_t *entry = svn__apr_hash_index_val(hi);
+
+ /* The client does not properly handle a missing CDATE. For
+ interoperability purposes, we must fill in some junk.
+
+ See libsvn_ra_svn/client.c:ra_svn_get_dir() */
+ cdate = (entry->time == (time_t) -1)
+ ? missing_date
+ : svn_time_to_cstring(entry->time, pool);
+ SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "cwnbr(?c)(?c)", name,
+ svn_node_kind_to_word(entry->kind),
+ (apr_uint64_t) entry->size,
+ entry->has_props, entry->created_rev,
+ cdate, entry->last_author));
+ }
+ }
+ SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "!))"));
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *update(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
+ apr_array_header_t *params, void *baton)
+{
+ server_baton_t *b = baton;
+ svn_revnum_t rev;
+ const char *target, *full_path, *depth_word;
+ svn_boolean_t recurse;
+ svn_boolean_t send_copyfrom_args;
+ apr_uint64_t send_copyfrom_param;
+ /* Default to unknown. Old clients won't send depth, but we'll
+ handle that by converting recurse if necessary. */
+ svn_depth_t depth = svn_depth_unknown;
+ svn_boolean_t is_checkout;
+
+ /* Parse the arguments. */
+ SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "(?r)cb?wB", &rev, &target,
+ &recurse, &depth_word, &send_copyfrom_param));
+ target = svn_relpath_canonicalize(target, pool);
+
+ if (depth_word)
+ depth = svn_depth_from_word(depth_word);
+ else
+ depth = SVN_DEPTH_INFINITY_OR_FILES(recurse);
+
+ send_copyfrom_args = (send_copyfrom_param == SVN_RA_SVN_UNSPECIFIED_NUMBER) ?
+ FALSE : (svn_boolean_t) send_copyfrom_param;
+
+ full_path = svn_fspath__join(b->fs_path->data, target, pool);
+ /* Check authorization and authenticate the user if necessary. */
+ SVN_ERR(must_have_access(conn, pool, b, svn_authz_read, full_path, FALSE));
+
+ if (!SVN_IS_VALID_REVNUM(rev))
+ SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->fs, pool));
+
+ SVN_ERR(accept_report(&is_checkout, NULL,
+ conn, pool, b, rev, target, NULL, TRUE,
+ depth, send_copyfrom_args, FALSE));
+ if (is_checkout)
+ {
+ SVN_ERR(log_command(b, conn, pool, "%s",
+ svn_log__checkout(full_path, rev,
+ depth, pool)));
+ }
+ else
+ {
+ SVN_ERR(log_command(b, conn, pool, "%s",
+ svn_log__update(full_path, rev, depth,
+ send_copyfrom_args, pool)));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *switch_cmd(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
+ apr_array_header_t *params, void *baton)
+{
+ server_baton_t *b = baton;
+ svn_revnum_t rev;
+ const char *target, *depth_word;
+ const char *switch_url, *switch_path;
+ svn_boolean_t recurse;
+ /* Default to unknown. Old clients won't send depth, but we'll
+ handle that by converting recurse if necessary. */
+ svn_depth_t depth = svn_depth_unknown;
+
+ /* Parse the arguments. */
+ SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "(?r)cbc?w", &rev, &target,
+ &recurse, &switch_url, &depth_word));
+ target = svn_relpath_canonicalize(target, pool);
+ switch_url = svn_uri_canonicalize(switch_url, pool);
+
+ if (depth_word)
+ depth = svn_depth_from_word(depth_word);
+ else
+ depth = SVN_DEPTH_INFINITY_OR_FILES(recurse);
+
+ SVN_ERR(trivial_auth_request(conn, pool, b));
+ if (!SVN_IS_VALID_REVNUM(rev))
+ SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->fs, pool));
+
+ SVN_CMD_ERR(get_fs_path(svn_path_uri_decode(b->repos_url, pool),
+ svn_path_uri_decode(switch_url, pool),
+ &switch_path));
+
+ {
+ const char *full_path = svn_fspath__join(b->fs_path->data, target, pool);
+ SVN_ERR(log_command(b, conn, pool, "%s",
+ svn_log__switch(full_path, switch_path, rev,
+ depth, pool)));
+ }
+
+ return accept_report(NULL, NULL,
+ conn, pool, b, rev, target, switch_path, TRUE,
+ depth,
+ FALSE /* TODO(sussman): no copyfrom args for now */,
+ TRUE);
+}
+
+static svn_error_t *status(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
+ apr_array_header_t *params, void *baton)
+{
+ server_baton_t *b = baton;
+ svn_revnum_t rev;
+ const char *target, *depth_word;
+ svn_boolean_t recurse;
+ /* Default to unknown. Old clients won't send depth, but we'll
+ handle that by converting recurse if necessary. */
+ svn_depth_t depth = svn_depth_unknown;
+
+ /* Parse the arguments. */
+ SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "cb?(?r)?w",
+ &target, &recurse, &rev, &depth_word));
+ target = svn_relpath_canonicalize(target, pool);
+
+ if (depth_word)
+ depth = svn_depth_from_word(depth_word);
+ else
+ depth = SVN_DEPTH_INFINITY_OR_EMPTY(recurse);
+
+ SVN_ERR(trivial_auth_request(conn, pool, b));
+ if (!SVN_IS_VALID_REVNUM(rev))
+ SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->fs, pool));
+
+ {
+ const char *full_path = svn_fspath__join(b->fs_path->data, target, pool);
+ SVN_ERR(log_command(b, conn, pool, "%s",
+ svn_log__status(full_path, rev, depth, pool)));
+ }
+
+ return accept_report(NULL, NULL, conn, pool, b, rev, target, NULL, FALSE,
+ depth, FALSE, FALSE);
+}
+
+static svn_error_t *diff(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
+ apr_array_header_t *params, void *baton)
+{
+ server_baton_t *b = baton;
+ svn_revnum_t rev;
+ const char *target, *versus_url, *versus_path, *depth_word;
+ svn_boolean_t recurse, ignore_ancestry;
+ svn_boolean_t text_deltas;
+ /* Default to unknown. Old clients won't send depth, but we'll
+ handle that by converting recurse if necessary. */
+ svn_depth_t depth = svn_depth_unknown;
+
+ /* Parse the arguments. */
+ if (params->nelts == 5)
+ {
+ /* Clients before 1.4 don't send the text_deltas boolean or depth. */
+ SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "(?r)cbbc", &rev, &target,
+ &recurse, &ignore_ancestry, &versus_url));
+ text_deltas = TRUE;
+ depth_word = NULL;
+ }
+ else
+ {
+ SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "(?r)cbbcb?w",
+ &rev, &target, &recurse,
+ &ignore_ancestry, &versus_url,
+ &text_deltas, &depth_word));
+ }
+ target = svn_relpath_canonicalize(target, pool);
+ versus_url = svn_uri_canonicalize(versus_url, pool);
+
+ if (depth_word)
+ depth = svn_depth_from_word(depth_word);
+ else
+ depth = SVN_DEPTH_INFINITY_OR_FILES(recurse);
+
+ SVN_ERR(trivial_auth_request(conn, pool, b));
+
+ if (!SVN_IS_VALID_REVNUM(rev))
+ SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->fs, pool));
+ SVN_CMD_ERR(get_fs_path(svn_path_uri_decode(b->repos_url, pool),
+ svn_path_uri_decode(versus_url, pool),
+ &versus_path));
+
+ {
+ const char *full_path = svn_fspath__join(b->fs_path->data, target, pool);
+ svn_revnum_t from_rev;
+ SVN_ERR(accept_report(NULL, &from_rev,
+ conn, pool, b, rev, target, versus_path,
+ text_deltas, depth, FALSE, ignore_ancestry));
+ SVN_ERR(log_command(b, conn, pool, "%s",
+ svn_log__diff(full_path, from_rev, versus_path,
+ rev, depth, ignore_ancestry,
+ pool)));
+ }
+ return SVN_NO_ERROR;
+}
+
+/* Regardless of whether a client's capabilities indicate an
+ understanding of this command (by way of SVN_RA_SVN_CAP_MERGEINFO),
+ we provide a response.
+
+ ASSUMPTION: When performing a 'merge' with two URLs at different
+ revisions, the client will call this command more than once. */
+static svn_error_t *get_mergeinfo(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
+ apr_array_header_t *params, void *baton)
+{
+ server_baton_t *b = baton;
+ svn_revnum_t rev;
+ apr_array_header_t *paths, *canonical_paths;
+ svn_mergeinfo_catalog_t mergeinfo;
+ int i;
+ apr_hash_index_t *hi;
+ const char *inherit_word;
+ svn_mergeinfo_inheritance_t inherit;
+ svn_boolean_t include_descendants;
+ apr_pool_t *iterpool;
+
+ SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "l(?r)wb", &paths, &rev,
+ &inherit_word, &include_descendants));
+ inherit = svn_inheritance_from_word(inherit_word);
+
+ /* Canonicalize the paths which mergeinfo has been requested for. */
+ canonical_paths = apr_array_make(pool, paths->nelts, sizeof(const char *));
+ for (i = 0; i < paths->nelts; i++)
+ {
+ svn_ra_svn_item_t *item = &APR_ARRAY_IDX(paths, i, svn_ra_svn_item_t);
+ const char *full_path;
+
+ if (item->kind != SVN_RA_SVN_STRING)
+ return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
+ _("Path is not a string"));
+ full_path = svn_relpath_canonicalize(item->u.string->data, pool);
+ full_path = svn_fspath__join(b->fs_path->data, full_path, pool);
+ APR_ARRAY_PUSH(canonical_paths, const char *) = full_path;
+ }
+
+ SVN_ERR(log_command(b, conn, pool, "%s",
+ svn_log__get_mergeinfo(canonical_paths, inherit,
+ include_descendants,
+ pool)));
+
+ SVN_ERR(trivial_auth_request(conn, pool, b));
+ SVN_CMD_ERR(svn_repos_fs_get_mergeinfo(&mergeinfo, b->repos,
+ canonical_paths, rev,
+ inherit,
+ include_descendants,
+ authz_check_access_cb_func(b), b,
+ pool));
+ SVN_ERR(svn_mergeinfo__remove_prefix_from_catalog(&mergeinfo, mergeinfo,
+ b->fs_path->data, pool));
+ SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "w((!", "success"));
+ iterpool = svn_pool_create(pool);
+ for (hi = apr_hash_first(pool, mergeinfo); hi; hi = apr_hash_next(hi))
+ {
+ const char *key = svn__apr_hash_index_key(hi);
+ svn_mergeinfo_t value = svn__apr_hash_index_val(hi);
+ svn_string_t *mergeinfo_string;
+
+ svn_pool_clear(iterpool);
+
+ SVN_ERR(svn_mergeinfo_to_string(&mergeinfo_string, value, iterpool));
+ SVN_ERR(svn_ra_svn_write_tuple(conn, iterpool, "cs", key,
+ mergeinfo_string));
+ }
+ svn_pool_destroy(iterpool);
+ SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "!))"));
+
+ return SVN_NO_ERROR;
+}
+
+/* Send a log entry to the client. */
+static svn_error_t *log_receiver(void *baton,
+ svn_log_entry_t *log_entry,
+ apr_pool_t *pool)
+{
+ log_baton_t *b = baton;
+ svn_ra_svn_conn_t *conn = b->conn;
+ apr_hash_index_t *h;
+ svn_boolean_t invalid_revnum = FALSE;
+ char action[2];
+ const char *author, *date, *message;
+ apr_uint64_t revprop_count;
+
+ if (log_entry->revision == SVN_INVALID_REVNUM)
+ {
+ /* If the stack depth is zero, we've seen the last revision, so don't
+ send it, just return. */
+ if (b->stack_depth == 0)
+ return SVN_NO_ERROR;
+
+ /* Because the svn protocol won't let us send an invalid revnum, we have
+ to fudge here and send an additional flag. */
+ log_entry->revision = 0;
+ invalid_revnum = TRUE;
+ b->stack_depth--;
+ }
+
+ SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "(!"));
+ if (log_entry->changed_paths2)
+ {
+ for (h = apr_hash_first(pool, log_entry->changed_paths2); h;
+ h = apr_hash_next(h))
+ {
+ const char *path = svn__apr_hash_index_key(h);
+ svn_log_changed_path2_t *change = svn__apr_hash_index_val(h);
+
+ action[0] = change->action;
+ action[1] = '\0';
+ SVN_ERR(svn_ra_svn_write_tuple(
+ conn, pool, "cw(?cr)(cbb)",
+ path,
+ action,
+ change->copyfrom_path,
+ change->copyfrom_rev,
+ svn_node_kind_to_word(change->node_kind),
+ /* text_modified and props_modified are never unknown */
+ change->text_modified == svn_tristate_true,
+ change->props_modified == svn_tristate_true));
+ }
+ }
+ svn_compat_log_revprops_out(&author, &date, &message, log_entry->revprops);
+ svn_compat_log_revprops_clear(log_entry->revprops);
+ if (log_entry->revprops)
+ revprop_count = apr_hash_count(log_entry->revprops);
+ else
+ revprop_count = 0;
+ SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "!)r(?c)(?c)(?c)bbn(!",
+ log_entry->revision,
+ author, date, message,
+ log_entry->has_children,
+ invalid_revnum, revprop_count));
+ SVN_ERR(svn_ra_svn_write_proplist(conn, pool, log_entry->revprops));
+ SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "!)b",
+ log_entry->subtractive_merge));
+
+ if (log_entry->has_children)
+ b->stack_depth++;
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *log_cmd(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
+ apr_array_header_t *params, void *baton)
+{
+ svn_error_t *err, *write_err;
+ server_baton_t *b = baton;
+ svn_revnum_t start_rev, end_rev;
+ const char *full_path;
+ svn_boolean_t changed_paths, strict_node, include_merged_revisions;
+ apr_array_header_t *paths, *full_paths, *revprop_items, *revprops;
+ char *revprop_word;
+ svn_ra_svn_item_t *elt;
+ int i;
+ apr_uint64_t limit, include_merged_revs_param;
+ log_baton_t lb;
+
+ SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "l(?r)(?r)bb?n?Bwl", &paths,
+ &start_rev, &end_rev, &changed_paths,
+ &strict_node, &limit,
+ &include_merged_revs_param,
+ &revprop_word, &revprop_items));
+
+ if (include_merged_revs_param == SVN_RA_SVN_UNSPECIFIED_NUMBER)
+ include_merged_revisions = FALSE;
+ else
+ include_merged_revisions = (svn_boolean_t) include_merged_revs_param;
+
+ if (revprop_word == NULL)
+ /* pre-1.5 client */
+ revprops = svn_compat_log_revprops_in(pool);
+ else if (strcmp(revprop_word, "all-revprops") == 0)
+ revprops = NULL;
+ else if (strcmp(revprop_word, "revprops") == 0)
+ {
+ SVN_ERR_ASSERT(revprop_items);
+
+ revprops = apr_array_make(pool, revprop_items->nelts,
+ sizeof(char *));
+ for (i = 0; i < revprop_items->nelts; i++)
+ {
+ elt = &APR_ARRAY_IDX(revprop_items, i, svn_ra_svn_item_t);
+ if (elt->kind != SVN_RA_SVN_STRING)
+ return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
+ _("Log revprop entry not a string"));
+ APR_ARRAY_PUSH(revprops, const char *) = elt->u.string->data;
+ }
+ }
+ else
+ return svn_error_createf(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
+ _("Unknown revprop word '%s' in log command"),
+ revprop_word);
+
+ /* If we got an unspecified number then the user didn't send us anything,
+ so we assume no limit. If it's larger than INT_MAX then someone is
+ messing with us, since we know the svn client libraries will never send
+ us anything that big, so play it safe and default to no limit. */
+ if (limit == SVN_RA_SVN_UNSPECIFIED_NUMBER || limit > INT_MAX)
+ limit = 0;
+
+ full_paths = apr_array_make(pool, paths->nelts, sizeof(const char *));
+ for (i = 0; i < paths->nelts; i++)
+ {
+ elt = &APR_ARRAY_IDX(paths, i, svn_ra_svn_item_t);
+ if (elt->kind != SVN_RA_SVN_STRING)
+ return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
+ _("Log path entry not a string"));
+ full_path = svn_relpath_canonicalize(elt->u.string->data, pool),
+ full_path = svn_fspath__join(b->fs_path->data, full_path, pool);
+ APR_ARRAY_PUSH(full_paths, const char *) = full_path;
+ }
+ SVN_ERR(trivial_auth_request(conn, pool, b));
+
+ SVN_ERR(log_command(b, conn, pool, "%s",
+ svn_log__log(full_paths, start_rev, end_rev,
+ limit, changed_paths, strict_node,
+ include_merged_revisions, revprops,
+ pool)));
+
+ /* Get logs. (Can't report errors back to the client at this point.) */
+ lb.fs_path = b->fs_path->data;
+ lb.conn = conn;
+ lb.stack_depth = 0;
+ err = svn_repos_get_logs4(b->repos, full_paths, start_rev, end_rev,
+ (int) limit, changed_paths, strict_node,
+ include_merged_revisions, revprops,
+ authz_check_access_cb_func(b), b, log_receiver,
+ &lb, pool);
+
+ write_err = svn_ra_svn_write_word(conn, pool, "done");
+ if (write_err)
+ {
+ svn_error_clear(err);
+ return write_err;
+ }
+ SVN_CMD_ERR(err);
+ SVN_ERR(svn_ra_svn_write_cmd_response(conn, pool, ""));
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *check_path(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
+ apr_array_header_t *params, void *baton)
+{
+ server_baton_t *b = baton;
+ svn_revnum_t rev;
+ const char *path, *full_path;
+ svn_fs_root_t *root;
+ svn_node_kind_t kind;
+
+ SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "c(?r)", &path, &rev));
+ full_path = svn_fspath__join(b->fs_path->data,
+ svn_relpath_canonicalize(path, pool), pool);
+
+ /* Check authorizations */
+ SVN_ERR(must_have_access(conn, pool, b, svn_authz_read,
+ full_path, FALSE));
+
+ if (!SVN_IS_VALID_REVNUM(rev))
+ SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->fs, pool));
+
+ SVN_ERR(log_command(b, conn, pool, "check-path %s@%d",
+ svn_path_uri_encode(full_path, pool), rev));
+
+ SVN_CMD_ERR(svn_fs_revision_root(&root, b->fs, rev, pool));
+ SVN_CMD_ERR(svn_fs_check_path(&kind, root, full_path, pool));
+ SVN_ERR(svn_ra_svn_write_cmd_response(conn, pool, "w",
+ svn_node_kind_to_word(kind)));
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *stat_cmd(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
+ apr_array_header_t *params, void *baton)
+{
+ server_baton_t *b = baton;
+ svn_revnum_t rev;
+ const char *path, *full_path, *cdate;
+ svn_fs_root_t *root;
+ svn_dirent_t *dirent;
+
+ SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "c(?r)", &path, &rev));
+ full_path = svn_fspath__join(b->fs_path->data,
+ svn_relpath_canonicalize(path, pool), pool);
+
+ /* Check authorizations */
+ SVN_ERR(must_have_access(conn, pool, b, svn_authz_read,
+ full_path, FALSE));
+
+ if (!SVN_IS_VALID_REVNUM(rev))
+ SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->fs, pool));
+
+ SVN_ERR(log_command(b, conn, pool, "stat %s@%d",
+ svn_path_uri_encode(full_path, pool), rev));
+
+ SVN_CMD_ERR(svn_fs_revision_root(&root, b->fs, rev, pool));
+ SVN_CMD_ERR(svn_repos_stat(&dirent, root, full_path, pool));
+
+ /* Need to return the equivalent of "(?l)", since that's what the
+ client is reading. */
+
+ if (dirent == NULL)
+ {
+ SVN_ERR(svn_ra_svn_write_cmd_response(conn, pool, "()"));
+ return SVN_NO_ERROR;
+ }
+
+ cdate = (dirent->time == (time_t) -1) ? NULL
+ : svn_time_to_cstring(dirent->time, pool);
+
+ SVN_ERR(svn_ra_svn_write_cmd_response(conn, pool, "((wnbr(?c)(?c)))",
+ svn_node_kind_to_word(dirent->kind),
+ (apr_uint64_t) dirent->size,
+ dirent->has_props, dirent->created_rev,
+ cdate, dirent->last_author));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *get_locations(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
+ apr_array_header_t *params, void *baton)
+{
+ svn_error_t *err, *write_err;
+ server_baton_t *b = baton;
+ svn_revnum_t revision;
+ apr_array_header_t *location_revisions, *loc_revs_proto;
+ svn_ra_svn_item_t *elt;
+ int i;
+ const char *relative_path;
+ svn_revnum_t peg_revision;
+ apr_hash_t *fs_locations;
+ const char *abs_path;
+
+ /* Parse the arguments. */
+ SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "crl", &relative_path,
+ &peg_revision,
+ &loc_revs_proto));
+ relative_path = svn_relpath_canonicalize(relative_path, pool);
+
+ abs_path = svn_fspath__join(b->fs_path->data, relative_path, pool);
+
+ location_revisions = apr_array_make(pool, loc_revs_proto->nelts,
+ sizeof(svn_revnum_t));
+ for (i = 0; i < loc_revs_proto->nelts; i++)
+ {
+ elt = &APR_ARRAY_IDX(loc_revs_proto, i, svn_ra_svn_item_t);
+ if (elt->kind != SVN_RA_SVN_NUMBER)
+ return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
+ "Get-locations location revisions entry "
+ "not a revision number");
+ revision = (svn_revnum_t)(elt->u.number);
+ APR_ARRAY_PUSH(location_revisions, svn_revnum_t) = revision;
+ }
+ SVN_ERR(trivial_auth_request(conn, pool, b));
+ SVN_ERR(log_command(b, conn, pool, "%s",
+ svn_log__get_locations(abs_path, peg_revision,
+ location_revisions, pool)));
+
+ /* All the parameters are fine - let's perform the query against the
+ * repository. */
+
+ /* We store both err and write_err here, so the client will get
+ * the "done" even if there was an error in fetching the results. */
+
+ err = svn_repos_trace_node_locations(b->fs, &fs_locations, abs_path,
+ peg_revision, location_revisions,
+ authz_check_access_cb_func(b), b, pool);
+
+ /* Now, write the results to the connection. */
+ if (!err)
+ {
+ if (fs_locations)
+ {
+ apr_hash_index_t *iter;
+
+ for (iter = apr_hash_first(pool, fs_locations); iter;
+ iter = apr_hash_next(iter))
+ {
+ const svn_revnum_t *iter_key = svn__apr_hash_index_key(iter);
+ const char *iter_value = svn__apr_hash_index_val(iter);
+
+ SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "rc",
+ *iter_key, iter_value));
+ }
+ }
+ }
+
+ write_err = svn_ra_svn_write_word(conn, pool, "done");
+ if (write_err)
+ {
+ svn_error_clear(err);
+ return write_err;
+ }
+ SVN_CMD_ERR(err);
+
+ SVN_ERR(svn_ra_svn_write_cmd_response(conn, pool, ""));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *gls_receiver(svn_location_segment_t *segment,
+ void *baton,
+ apr_pool_t *pool)
+{
+ svn_ra_svn_conn_t *conn = baton;
+ return svn_ra_svn_write_tuple(conn, pool, "rr(?c)",
+ segment->range_start,
+ segment->range_end,
+ segment->path);
+}
+
+static svn_error_t *get_location_segments(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ apr_array_header_t *params,
+ void *baton)
+{
+ svn_error_t *err, *write_err;
+ server_baton_t *b = baton;
+ svn_revnum_t peg_revision, start_rev, end_rev;
+ const char *relative_path;
+ const char *abs_path;
+
+ /* Parse the arguments. */
+ SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "c(?r)(?r)(?r)",
+ &relative_path, &peg_revision,
+ &start_rev, &end_rev));
+ relative_path = svn_relpath_canonicalize(relative_path, pool);
+
+ abs_path = svn_fspath__join(b->fs_path->data, relative_path, pool);
+
+ if (SVN_IS_VALID_REVNUM(start_rev)
+ && SVN_IS_VALID_REVNUM(end_rev)
+ && (end_rev > start_rev))
+ {
+ err = svn_error_createf(SVN_ERR_INCORRECT_PARAMS, NULL,
+ "Get-location-segments end revision must not be "
+ "younger than start revision");
+ return log_fail_and_flush(err, b, conn, pool);
+ }
+
+ if (SVN_IS_VALID_REVNUM(peg_revision)
+ && SVN_IS_VALID_REVNUM(start_rev)
+ && (start_rev > peg_revision))
+ {
+ err = svn_error_createf(SVN_ERR_INCORRECT_PARAMS, NULL,
+ "Get-location-segments start revision must not "
+ "be younger than peg revision");
+ return log_fail_and_flush(err, b, conn, pool);
+ }
+
+ SVN_ERR(trivial_auth_request(conn, pool, b));
+ SVN_ERR(log_command(baton, conn, pool, "%s",
+ svn_log__get_location_segments(abs_path, peg_revision,
+ start_rev, end_rev,
+ pool)));
+
+ /* All the parameters are fine - let's perform the query against the
+ * repository. */
+
+ /* We store both err and write_err here, so the client will get
+ * the "done" even if there was an error in fetching the results. */
+
+ err = svn_repos_node_location_segments(b->repos, abs_path,
+ peg_revision, start_rev, end_rev,
+ gls_receiver, (void *)conn,
+ authz_check_access_cb_func(b), b,
+ pool);
+ write_err = svn_ra_svn_write_word(conn, pool, "done");
+ if (write_err)
+ {
+ svn_error_clear(err);
+ return write_err;
+ }
+ SVN_CMD_ERR(err);
+
+ SVN_ERR(svn_ra_svn_write_cmd_response(conn, pool, ""));
+
+ return SVN_NO_ERROR;
+}
+
+/* This implements svn_write_fn_t. Write LEN bytes starting at DATA to the
+ client as a string. */
+static svn_error_t *svndiff_handler(void *baton, const char *data,
+ apr_size_t *len)
+{
+ file_revs_baton_t *b = baton;
+ svn_string_t str;
+
+ str.data = data;
+ str.len = *len;
+ return svn_ra_svn_write_string(b->conn, b->pool, &str);
+}
+
+/* This implements svn_close_fn_t. Mark the end of the data by writing an
+ empty string to the client. */
+static svn_error_t *svndiff_close_handler(void *baton)
+{
+ file_revs_baton_t *b = baton;
+
+ SVN_ERR(svn_ra_svn_write_cstring(b->conn, b->pool, ""));
+ return SVN_NO_ERROR;
+}
+
+/* This implements the svn_repos_file_rev_handler_t interface. */
+static svn_error_t *file_rev_handler(void *baton, const char *path,
+ svn_revnum_t rev, apr_hash_t *rev_props,
+ svn_boolean_t merged_revision,
+ svn_txdelta_window_handler_t *d_handler,
+ void **d_baton,
+ apr_array_header_t *prop_diffs,
+ apr_pool_t *pool)
+{
+ file_revs_baton_t *frb = baton;
+ svn_stream_t *stream;
+
+ SVN_ERR(svn_ra_svn_write_tuple(frb->conn, pool, "cr(!",
+ path, rev));
+ SVN_ERR(svn_ra_svn_write_proplist(frb->conn, pool, rev_props));
+ SVN_ERR(svn_ra_svn_write_tuple(frb->conn, pool, "!)(!"));
+ SVN_ERR(write_prop_diffs(frb->conn, pool, prop_diffs));
+ SVN_ERR(svn_ra_svn_write_tuple(frb->conn, pool, "!)b", merged_revision));
+
+ /* Store the pool for the delta stream. */
+ frb->pool = pool;
+
+ /* Prepare for the delta or just write an empty string. */
+ if (d_handler)
+ {
+ stream = svn_stream_create(baton, pool);
+ svn_stream_set_write(stream, svndiff_handler);
+ svn_stream_set_close(stream, svndiff_close_handler);
+
+ /* If the connection does not support SVNDIFF1 or if we don't want to use
+ * compression, use the non-compressing "version 0" implementation */
+ if ( svn_ra_svn_compression_level(frb->conn) > 0
+ && svn_ra_svn_has_capability(frb->conn, SVN_RA_SVN_CAP_SVNDIFF1))
+ svn_txdelta_to_svndiff3(d_handler, d_baton, stream, 1,
+ svn_ra_svn_compression_level(frb->conn), pool);
+ else
+ svn_txdelta_to_svndiff3(d_handler, d_baton, stream, 0,
+ svn_ra_svn_compression_level(frb->conn), pool);
+ }
+ else
+ SVN_ERR(svn_ra_svn_write_cstring(frb->conn, pool, ""));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *get_file_revs(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
+ apr_array_header_t *params, void *baton)
+{
+ server_baton_t *b = baton;
+ svn_error_t *err, *write_err;
+ file_revs_baton_t frb;
+ svn_revnum_t start_rev, end_rev;
+ const char *path;
+ const char *full_path;
+ apr_uint64_t include_merged_revs_param;
+ svn_boolean_t include_merged_revisions;
+
+ /* Parse arguments. */
+ SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "c(?r)(?r)?B",
+ &path, &start_rev, &end_rev,
+ &include_merged_revs_param));
+ path = svn_relpath_canonicalize(path, pool);
+ SVN_ERR(trivial_auth_request(conn, pool, b));
+ full_path = svn_fspath__join(b->fs_path->data, path, pool);
+
+ if (include_merged_revs_param == SVN_RA_SVN_UNSPECIFIED_NUMBER)
+ include_merged_revisions = FALSE;
+ else
+ include_merged_revisions = (svn_boolean_t) include_merged_revs_param;
+
+ SVN_ERR(log_command(b, conn, pool, "%s",
+ svn_log__get_file_revs(full_path, start_rev, end_rev,
+ include_merged_revisions,
+ pool)));
+
+ frb.conn = conn;
+ frb.pool = NULL;
+
+ err = svn_repos_get_file_revs2(b->repos, full_path, start_rev, end_rev,
+ include_merged_revisions,
+ authz_check_access_cb_func(b), b,
+ file_rev_handler, &frb, pool);
+ write_err = svn_ra_svn_write_word(conn, pool, "done");
+ if (write_err)
+ {
+ svn_error_clear(err);
+ return write_err;
+ }
+ SVN_CMD_ERR(err);
+ SVN_ERR(svn_ra_svn_write_cmd_response(conn, pool, ""));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *lock(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
+ apr_array_header_t *params, void *baton)
+{
+ server_baton_t *b = baton;
+ const char *path;
+ const char *comment;
+ const char *full_path;
+ svn_boolean_t steal_lock;
+ svn_revnum_t current_rev;
+ svn_lock_t *l;
+
+ SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "c(?c)b(?r)", &path, &comment,
+ &steal_lock, &current_rev));
+ full_path = svn_fspath__join(b->fs_path->data,
+ svn_relpath_canonicalize(path, pool), pool);
+
+ SVN_ERR(must_have_access(conn, pool, b, svn_authz_write,
+ full_path, TRUE));
+ SVN_ERR(log_command(b, conn, pool, "%s",
+ svn_log__lock_one_path(full_path, steal_lock, pool)));
+
+ SVN_CMD_ERR(svn_repos_fs_lock(&l, b->repos, full_path, NULL, comment, 0,
+ 0, /* No expiration time. */
+ current_rev, steal_lock, pool));
+
+ SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "w(!", "success"));
+ SVN_ERR(write_lock(conn, pool, l));
+ SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "!)"));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *lock_many(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
+ apr_array_header_t *params, void *baton)
+{
+ server_baton_t *b = baton;
+ apr_array_header_t *path_revs;
+ const char *comment;
+ svn_boolean_t steal_lock;
+ int i;
+ apr_pool_t *subpool;
+ const char *path;
+ const char *full_path;
+ svn_revnum_t current_rev;
+ apr_array_header_t *log_paths;
+ svn_lock_t *l;
+ svn_error_t *err = SVN_NO_ERROR, *write_err;
+
+ SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "(?c)bl", &comment, &steal_lock,
+ &path_revs));
+
+ subpool = svn_pool_create(pool);
+
+ /* Because we can only send a single auth reply per request, we send
+ a reply before parsing the lock commands. This means an authz
+ access denial will abort the processing of the locks and return
+ an error. */
+ SVN_ERR(must_have_access(conn, pool, b, svn_authz_write, NULL, TRUE));
+
+ /* Loop through the lock requests. */
+ log_paths = apr_array_make(pool, path_revs->nelts, sizeof(full_path));
+ for (i = 0; i < path_revs->nelts; ++i)
+ {
+ svn_ra_svn_item_t *item = &APR_ARRAY_IDX(path_revs, i,
+ svn_ra_svn_item_t);
+
+ svn_pool_clear(subpool);
+
+ if (item->kind != SVN_RA_SVN_LIST)
+ return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
+ "Lock requests should be list of lists");
+
+ SVN_ERR(svn_ra_svn_parse_tuple(item->u.list, pool, "c(?r)", &path,
+ &current_rev));
+
+ /* Allocate the full_path out of pool so it will survive for use
+ * by operational logging, after this loop. */
+ full_path = svn_fspath__join(b->fs_path->data,
+ svn_relpath_canonicalize(path, subpool),
+ pool);
+ APR_ARRAY_PUSH(log_paths, const char *) = full_path;
+
+ if (! lookup_access(pool, b, conn, svn_authz_write, full_path, TRUE))
+ {
+ err = error_create_and_log(SVN_ERR_RA_NOT_AUTHORIZED, NULL, NULL,
+ b, conn, pool);
+ break;
+ }
+
+ err = svn_repos_fs_lock(&l, b->repos, full_path,
+ NULL, comment, FALSE,
+ 0, /* No expiration time. */
+ current_rev,
+ steal_lock, subpool);
+
+ if (err)
+ {
+ if (SVN_ERR_IS_LOCK_ERROR(err))
+ {
+ write_err = svn_ra_svn_write_cmd_failure(conn, pool, err);
+ svn_error_clear(err);
+ err = NULL;
+ SVN_ERR(write_err);
+ }
+ else
+ break;
+ }
+ else
+ {
+ SVN_ERR(svn_ra_svn_write_tuple(conn, subpool, "w!", "success"));
+ SVN_ERR(write_lock(conn, subpool, l));
+ SVN_ERR(svn_ra_svn_write_tuple(conn, subpool, "!"));
+ }
+ }
+
+ svn_pool_destroy(subpool);
+
+ SVN_ERR(log_command(b, conn, pool, "%s",
+ svn_log__lock(log_paths, steal_lock, pool)));
+
+ /* NOTE: err might contain a fatal locking error from the loop above. */
+ write_err = svn_ra_svn_write_word(conn, pool, "done");
+ if (!write_err)
+ SVN_CMD_ERR(err);
+ svn_error_clear(err);
+ SVN_ERR(write_err);
+ SVN_ERR(svn_ra_svn_write_cmd_response(conn, pool, ""));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *unlock(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
+ apr_array_header_t *params, void *baton)
+{
+ server_baton_t *b = baton;
+ const char *path, *token, *full_path;
+ svn_boolean_t break_lock;
+
+ SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "c(?c)b", &path, &token,
+ &break_lock));
+
+ full_path = svn_fspath__join(b->fs_path->data,
+ svn_relpath_canonicalize(path, pool), pool);
+
+ /* Username required unless break_lock was specified. */
+ SVN_ERR(must_have_access(conn, pool, b, svn_authz_write,
+ full_path, ! break_lock));
+ SVN_ERR(log_command(b, conn, pool, "%s",
+ svn_log__unlock_one_path(full_path, break_lock, pool)));
+
+ SVN_CMD_ERR(svn_repos_fs_unlock(b->repos, full_path, token, break_lock,
+ pool));
+
+ SVN_ERR(svn_ra_svn_write_cmd_response(conn, pool, ""));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *unlock_many(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
+ apr_array_header_t *params, void *baton)
+{
+ server_baton_t *b = baton;
+ svn_boolean_t break_lock;
+ apr_array_header_t *unlock_tokens;
+ int i;
+ apr_pool_t *subpool;
+ const char *path;
+ const char *full_path;
+ apr_array_header_t *log_paths;
+ const char *token;
+ svn_error_t *err = SVN_NO_ERROR, *write_err;
+
+ SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "bl", &break_lock,
+ &unlock_tokens));
+
+ /* Username required unless break_lock was specified. */
+ SVN_ERR(must_have_access(conn, pool, b, svn_authz_write, NULL, ! break_lock));
+
+ subpool = svn_pool_create(pool);
+
+ /* Loop through the unlock requests. */
+ log_paths = apr_array_make(pool, unlock_tokens->nelts, sizeof(full_path));
+ for (i = 0; i < unlock_tokens->nelts; i++)
+ {
+ svn_ra_svn_item_t *item = &APR_ARRAY_IDX(unlock_tokens, i,
+ svn_ra_svn_item_t);
+
+ svn_pool_clear(subpool);
+
+ if (item->kind != SVN_RA_SVN_LIST)
+ return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
+ "Unlock request should be a list of lists");
+
+ SVN_ERR(svn_ra_svn_parse_tuple(item->u.list, subpool, "c(?c)", &path,
+ &token));
+
+ /* Allocate the full_path out of pool so it will survive for use
+ * by operational logging, after this loop. */
+ full_path = svn_fspath__join(b->fs_path->data,
+ svn_relpath_canonicalize(path, subpool),
+ pool);
+ APR_ARRAY_PUSH(log_paths, const char *) = full_path;
+
+ if (! lookup_access(subpool, b, conn, svn_authz_write, full_path,
+ ! break_lock))
+ return svn_error_create(SVN_ERR_RA_SVN_CMD_ERR,
+ error_create_and_log(SVN_ERR_RA_NOT_AUTHORIZED,
+ NULL, NULL,
+ b, conn, pool),
+ NULL);
+
+ err = svn_repos_fs_unlock(b->repos, full_path, token, break_lock,
+ subpool);
+ if (err)
+ {
+ if (SVN_ERR_IS_UNLOCK_ERROR(err))
+ {
+ write_err = svn_ra_svn_write_cmd_failure(conn, pool, err);
+ svn_error_clear(err);
+ err = NULL;
+ SVN_ERR(write_err);
+ }
+ else
+ break;
+ }
+ else
+ SVN_ERR(svn_ra_svn_write_tuple(conn, subpool, "w(c)", "success",
+ path));
+ }
+
+ svn_pool_destroy(subpool);
+
+ SVN_ERR(log_command(b, conn, pool, "%s",
+ svn_log__unlock(log_paths, break_lock, pool)));
+
+ /* NOTE: err might contain a fatal unlocking error from the loop above. */
+ write_err = svn_ra_svn_write_word(conn, pool, "done");
+ if (! write_err)
+ SVN_CMD_ERR(err);
+ svn_error_clear(err);
+ SVN_ERR(svn_ra_svn_write_cmd_response(conn, pool, ""));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *get_lock(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
+ apr_array_header_t *params, void *baton)
+{
+ server_baton_t *b = baton;
+ const char *path;
+ const char *full_path;
+ svn_lock_t *l;
+
+ SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "c", &path));
+
+ full_path = svn_fspath__join(b->fs_path->data,
+ svn_relpath_canonicalize(path, pool), pool);
+
+ SVN_ERR(must_have_access(conn, pool, b, svn_authz_read,
+ full_path, FALSE));
+ SVN_ERR(log_command(b, conn, pool, "get-lock %s",
+ svn_path_uri_encode(full_path, pool)));
+
+ SVN_CMD_ERR(svn_fs_get_lock(&l, b->fs, full_path, pool));
+
+ SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "w((!", "success"));
+ if (l)
+ SVN_ERR(write_lock(conn, pool, l));
+ SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "!))"));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *get_locks(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
+ apr_array_header_t *params, void *baton)
+{
+ server_baton_t *b = baton;
+ const char *path;
+ const char *full_path;
+ const char *depth_word;
+ svn_depth_t depth;
+ apr_hash_t *locks;
+ apr_hash_index_t *hi;
+ svn_error_t *err;
+
+ SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "c?(?w)", &path, &depth_word));
+
+ depth = depth_word ? svn_depth_from_word(depth_word) : svn_depth_infinity;
+ if ((depth != svn_depth_empty) &&
+ (depth != svn_depth_files) &&
+ (depth != svn_depth_immediates) &&
+ (depth != svn_depth_infinity))
+ {
+ err = svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL,
+ "Invalid 'depth' specified in get-locks request");
+ return log_fail_and_flush(err, b, conn, pool);
+ }
+
+ full_path = svn_fspath__join(b->fs_path->data,
+ svn_relpath_canonicalize(path, pool), pool);
+
+ SVN_ERR(trivial_auth_request(conn, pool, b));
+
+ SVN_ERR(log_command(b, conn, pool, "get-locks %s",
+ svn_path_uri_encode(full_path, pool)));
+ SVN_CMD_ERR(svn_repos_fs_get_locks2(&locks, b->repos, full_path, depth,
+ authz_check_access_cb_func(b), b, pool));
+
+ SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "w((!", "success"));
+ for (hi = apr_hash_first(pool, locks); hi; hi = apr_hash_next(hi))
+ {
+ svn_lock_t *l = svn__apr_hash_index_val(hi);
+
+ SVN_ERR(write_lock(conn, pool, l));
+ }
+ SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "!))"));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *replay_one_revision(svn_ra_svn_conn_t *conn,
+ server_baton_t *b,
+ svn_revnum_t rev,
+ svn_revnum_t low_water_mark,
+ svn_boolean_t send_deltas,
+ apr_pool_t *pool)
+{
+ const svn_delta_editor_t *editor;
+ void *edit_baton;
+ svn_fs_root_t *root;
+ svn_error_t *err;
+
+ SVN_ERR(log_command(b, conn, pool,
+ svn_log__replay(b->fs_path->data, low_water_mark,
+ pool)));
+
+ svn_ra_svn_get_editor(&editor, &edit_baton, conn, pool, NULL, NULL);
+
+ err = svn_fs_revision_root(&root, b->fs, rev, pool);
+
+ if (! err)
+ err = svn_repos_replay2(root, b->fs_path->data, low_water_mark,
+ send_deltas, editor, edit_baton,
+ authz_check_access_cb_func(b), b, pool);
+
+ if (err)
+ svn_error_clear(editor->abort_edit(edit_baton, pool));
+ SVN_CMD_ERR(err);
+
+ return svn_ra_svn_write_cmd(conn, pool, "finish-replay", "");
+}
+
+static svn_error_t *replay(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
+ apr_array_header_t *params, void *baton)
+{
+ svn_revnum_t rev, low_water_mark;
+ svn_boolean_t send_deltas;
+ server_baton_t *b = baton;
+
+ SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "rrb", &rev, &low_water_mark,
+ &send_deltas));
+
+ SVN_ERR(trivial_auth_request(conn, pool, b));
+
+ SVN_ERR(replay_one_revision(conn, b, rev, low_water_mark,
+ send_deltas, pool));
+
+ SVN_ERR(svn_ra_svn_write_cmd_response(conn, pool, ""));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *replay_range(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
+ apr_array_header_t *params, void *baton)
+{
+ svn_revnum_t start_rev, end_rev, rev, low_water_mark;
+ svn_boolean_t send_deltas;
+ server_baton_t *b = baton;
+ apr_pool_t *iterpool;
+
+ SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "rrrb", &start_rev,
+ &end_rev, &low_water_mark,
+ &send_deltas));
+
+ SVN_ERR(trivial_auth_request(conn, pool, b));
+
+ iterpool = svn_pool_create(pool);
+ for (rev = start_rev; rev <= end_rev; rev++)
+ {
+ apr_hash_t *props;
+
+ svn_pool_clear(iterpool);
+
+ SVN_CMD_ERR(svn_repos_fs_revision_proplist(&props, b->repos, rev,
+ authz_check_access_cb_func(b),
+ b,
+ iterpool));
+ SVN_ERR(svn_ra_svn_write_tuple(conn, iterpool, "w(!", "revprops"));
+ SVN_ERR(svn_ra_svn_write_proplist(conn, iterpool, props));
+ SVN_ERR(svn_ra_svn_write_tuple(conn, iterpool, "!)"));
+
+ SVN_ERR(replay_one_revision(conn, b, rev, low_water_mark,
+ send_deltas, iterpool));
+
+ }
+ svn_pool_destroy(iterpool);
+
+ SVN_ERR(svn_ra_svn_write_cmd_response(conn, pool, ""));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+get_deleted_rev(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ apr_array_header_t *params,
+ void *baton)
+{
+ server_baton_t *b = baton;
+ const char *path, *full_path;
+ svn_revnum_t peg_revision;
+ svn_revnum_t end_revision;
+ svn_revnum_t revision_deleted;
+
+ SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "crr",
+ &path, &peg_revision, &end_revision));
+ full_path = svn_fspath__join(b->fs_path->data,
+ svn_relpath_canonicalize(path, pool), pool);
+ SVN_ERR(log_command(b, conn, pool, "get-deleted-rev"));
+ SVN_ERR(trivial_auth_request(conn, pool, b));
+ SVN_ERR(svn_repos_deleted_rev(b->fs, full_path, peg_revision, end_revision,
+ &revision_deleted, pool));
+ SVN_ERR(svn_ra_svn_write_cmd_response(conn, pool, "r", revision_deleted));
+ return SVN_NO_ERROR;
+}
+
+static const svn_ra_svn_cmd_entry_t main_commands[] = {
+ { "reparent", reparent },
+ { "get-latest-rev", get_latest_rev },
+ { "get-dated-rev", get_dated_rev },
+ { "change-rev-prop", change_rev_prop },
+ { "change-rev-prop2",change_rev_prop2 },
+ { "rev-proplist", rev_proplist },
+ { "rev-prop", rev_prop },
+ { "commit", commit },
+ { "get-file", get_file },
+ { "get-dir", get_dir },
+ { "update", update },
+ { "switch", switch_cmd },
+ { "status", status },
+ { "diff", diff },
+ { "get-mergeinfo", get_mergeinfo },
+ { "log", log_cmd },
+ { "check-path", check_path },
+ { "stat", stat_cmd },
+ { "get-locations", get_locations },
+ { "get-location-segments", get_location_segments },
+ { "get-file-revs", get_file_revs },
+ { "lock", lock },
+ { "lock-many", lock_many },
+ { "unlock", unlock },
+ { "unlock-many", unlock_many },
+ { "get-lock", get_lock },
+ { "get-locks", get_locks },
+ { "replay", replay },
+ { "replay-range", replay_range },
+ { "get-deleted-rev", get_deleted_rev },
+ { NULL }
+};
+
+/* Skip past the scheme part of a URL, including the tunnel specification
+ * if present. Return NULL if the scheme part is invalid for ra_svn. */
+static const char *skip_scheme_part(const char *url)
+{
+ if (strncmp(url, "svn", 3) != 0)
+ return NULL;
+ url += 3;
+ if (*url == '+')
+ url += strcspn(url, ":");
+ if (strncmp(url, "://", 3) != 0)
+ return NULL;
+ return url + 3;
+}
+
+/* Check that PATH is a valid repository path, meaning it doesn't contain any
+ '..' path segments.
+ NOTE: This is similar to svn_path_is_backpath_present, but that function
+ assumes the path separator is '/'. This function also checks for
+ segments delimited by the local path separator. */
+static svn_boolean_t
+repos_path_valid(const char *path)
+{
+ const char *s = path;
+
+ while (*s)
+ {
+ /* Scan for the end of the segment. */
+ while (*path && *path != '/' && *path != SVN_PATH_LOCAL_SEPARATOR)
+ ++path;
+
+ /* Check for '..'. */
+#ifdef WIN32
+ /* On Windows, don't allow sequences of more than one character
+ consisting of just dots and spaces. Win32 functions treat
+ paths such as ".. " and "......." inconsistently. Make sure
+ no one can escape out of the root. */
+ if (path - s >= 2 && strspn(s, ". ") == path - s)
+ return FALSE;
+#else /* ! WIN32 */
+ if (path - s == 2 && s[0] == '.' && s[1] == '.')
+ return FALSE;
+#endif
+
+ /* Skip all separators. */
+ while (*path && (*path == '/' || *path == SVN_PATH_LOCAL_SEPARATOR))
+ ++path;
+ s = path;
+ }
+
+ return TRUE;
+}
+
+/* Look for the repository given by URL, using ROOT as the virtual
+ * repository root. If we find one, fill in the repos, fs, cfg,
+ * repos_url, and fs_path fields of B. Set B->repos's client
+ * capabilities to CAPABILITIES, which must be at least as long-lived
+ * as POOL, and whose elements are SVN_RA_CAPABILITY_*.
+ */
+static svn_error_t *find_repos(const char *url, const char *root,
+ server_baton_t *b,
+ svn_ra_svn_conn_t *conn,
+ const apr_array_header_t *capabilities,
+ apr_pool_t *pool)
+{
+ const char *path, *full_path, *repos_root, *fs_path;
+ svn_stringbuf_t *url_buf;
+
+ /* Skip past the scheme and authority part. */
+ path = skip_scheme_part(url);
+ if (path == NULL)
+ return svn_error_createf(SVN_ERR_BAD_URL, NULL,
+ "Non-svn URL passed to svn server: '%s'", url);
+
+
+ path = strchr(path, '/');
+ path = (path == NULL) ? "" : svn_relpath_canonicalize(path, pool);
+ path = svn_path_uri_decode(path, pool);
+
+ /* Ensure that it isn't possible to escape the root by disallowing
+ '..' segments. */
+ if (!repos_path_valid(path))
+ return svn_error_create(SVN_ERR_BAD_FILENAME, NULL,
+ "Couldn't determine repository path");
+
+ /* Join the server-configured root with the client path. */
+ full_path = svn_dirent_join(svn_dirent_canonicalize(root, pool),
+ path, pool);
+
+ /* Search for a repository in the full path. */
+ repos_root = svn_repos_find_root_path(full_path, pool);
+ if (!repos_root)
+ return svn_error_createf(SVN_ERR_RA_SVN_REPOS_NOT_FOUND, NULL,
+ "No repository found in '%s'", url);
+
+ /* Open the repository and fill in b with the resulting information. */
+ SVN_ERR(svn_repos_open2(&b->repos, repos_root, b->fs_config, pool));
+ SVN_ERR(svn_repos_remember_client_capabilities(b->repos, capabilities));
+ b->fs = svn_repos_fs(b->repos);
+ fs_path = full_path + strlen(repos_root);
+ b->fs_path = svn_stringbuf_create(*fs_path ? fs_path : "/", pool);
+ url_buf = svn_stringbuf_create(url, pool);
+ svn_path_remove_components(url_buf,
+ svn_path_component_count(b->fs_path->data));
+ b->repos_url = url_buf->data;
+ b->authz_repos_name = svn_dirent_is_child(root, repos_root, pool);
+ if (b->authz_repos_name == NULL)
+ b->repos_name = svn_dirent_basename(repos_root, pool);
+ else
+ b->repos_name = b->authz_repos_name;
+ b->repos_name = svn_path_uri_encode(b->repos_name, pool);
+
+ /* If the svnserve configuration files have not been loaded then
+ load them from the repository. */
+ if (NULL == b->cfg)
+ SVN_ERR(load_configs(&b->cfg, &b->pwdb, &b->authzdb, &b->username_case,
+ svn_repos_svnserve_conf(b->repos, pool), FALSE,
+ svn_repos_conf_dir(b->repos, pool),
+ b, conn,
+ pool));
+
+#ifdef SVN_HAVE_SASL
+ /* Should we use Cyrus SASL? */
+ svn_config_get_bool(b->cfg, &b->use_sasl, SVN_CONFIG_SECTION_SASL,
+ SVN_CONFIG_OPTION_USE_SASL, FALSE);
+#endif
+
+ /* Use the repository UUID as the default realm. */
+ SVN_ERR(svn_fs_get_uuid(b->fs, &b->realm, pool));
+ svn_config_get(b->cfg, &b->realm, SVN_CONFIG_SECTION_GENERAL,
+ SVN_CONFIG_OPTION_REALM, b->realm);
+
+ /* Make sure it's possible for the client to authenticate. Note
+ that this doesn't take into account any authz configuration read
+ above, because we can't know about access it grants until paths
+ are given by the client. */
+ if (get_access(b, UNAUTHENTICATED) == NO_ACCESS
+ && (get_access(b, AUTHENTICATED) == NO_ACCESS
+ || (!b->tunnel_user && !b->pwdb && !b->use_sasl)))
+ return error_create_and_log(SVN_ERR_RA_NOT_AUTHORIZED, NULL,
+ "No access allowed to this repository",
+ b, conn, pool);
+
+ return SVN_NO_ERROR;
+}
+
+/* Compute the authentication name EXTERNAL should be able to get, if any. */
+static const char *get_tunnel_user(serve_params_t *params, apr_pool_t *pool)
+{
+ /* Only offer EXTERNAL for connections tunneled over a login agent. */
+ if (!params->tunnel)
+ return NULL;
+
+ /* If a tunnel user was provided on the command line, use that. */
+ if (params->tunnel_user)
+ return params->tunnel_user;
+
+ return svn_user_get_name(pool);
+}
+
+static void
+fs_warning_func(void *baton, svn_error_t *err)
+{
+ fs_warning_baton_t *b = baton;
+ log_server_error(err, b->server, b->conn, b->pool);
+ /* TODO: Keep log_pool in the server baton, cleared after every log? */
+ svn_pool_clear(b->pool);
+}
+
+svn_error_t *serve(svn_ra_svn_conn_t *conn, serve_params_t *params,
+ apr_pool_t *pool)
+{
+ svn_error_t *err, *io_err;
+ apr_uint64_t ver;
+ const char *uuid, *client_url, *ra_client_string, *client_string;
+ apr_array_header_t *caplist, *cap_words;
+ server_baton_t b;
+ fs_warning_baton_t warn_baton;
+ svn_stringbuf_t *cap_log = svn_stringbuf_create("", pool);
+
+ b.tunnel = params->tunnel;
+ b.tunnel_user = get_tunnel_user(params, pool);
+ b.read_only = params->read_only;
+ b.user = NULL;
+ b.username_case = params->username_case;
+ b.authz_user = NULL;
+ b.cfg = params->cfg;
+ b.pwdb = params->pwdb;
+ b.authzdb = params->authzdb;
+ b.realm = NULL;
+ b.log_file = params->log_file;
+ b.pool = pool;
+ b.use_sasl = FALSE;
+
+ /* construct FS configuration parameters */
+ b.fs_config = apr_hash_make(pool);
+ apr_hash_set(b.fs_config, SVN_FS_CONFIG_FSFS_CACHE_DELTAS,
+ APR_HASH_KEY_STRING, params->cache_txdeltas ? "1" : "0");
+ apr_hash_set(b.fs_config, SVN_FS_CONFIG_FSFS_CACHE_FULLTEXTS,
+ APR_HASH_KEY_STRING, params->cache_fulltexts ? "1" : "0");
+
+ /* Send greeting. We don't support version 1 any more, so we can
+ * send an empty mechlist. */
+ if (params->compression_level > 0)
+ SVN_ERR(svn_ra_svn_write_cmd_response(conn, pool, "nn()(wwwwwwww)",
+ (apr_uint64_t) 2, (apr_uint64_t) 2,
+ SVN_RA_SVN_CAP_EDIT_PIPELINE,
+ SVN_RA_SVN_CAP_SVNDIFF1,
+ SVN_RA_SVN_CAP_ABSENT_ENTRIES,
+ SVN_RA_SVN_CAP_COMMIT_REVPROPS,
+ SVN_RA_SVN_CAP_DEPTH,
+ SVN_RA_SVN_CAP_LOG_REVPROPS,
+ SVN_RA_SVN_CAP_ATOMIC_REVPROPS,
+ SVN_RA_SVN_CAP_PARTIAL_REPLAY));
+ else
+ SVN_ERR(svn_ra_svn_write_cmd_response(conn, pool, "nn()(wwwwwww)",
+ (apr_uint64_t) 2, (apr_uint64_t) 2,
+ SVN_RA_SVN_CAP_EDIT_PIPELINE,
+ SVN_RA_SVN_CAP_ABSENT_ENTRIES,
+ SVN_RA_SVN_CAP_COMMIT_REVPROPS,
+ SVN_RA_SVN_CAP_DEPTH,
+ SVN_RA_SVN_CAP_LOG_REVPROPS,
+ SVN_RA_SVN_CAP_ATOMIC_REVPROPS,
+ SVN_RA_SVN_CAP_PARTIAL_REPLAY));
+
+ /* Read client response, which we assume to be in version 2 format:
+ * version, capability list, and client URL; then we do an auth
+ * request. */
+ SVN_ERR(svn_ra_svn_read_tuple(conn, pool, "nlc?c(?c)",
+ &ver, &caplist, &client_url,
+ &ra_client_string,
+ &client_string));
+ if (ver != 2)
+ return SVN_NO_ERROR;
+
+ client_url = svn_uri_canonicalize(client_url, pool);
+ SVN_ERR(svn_ra_svn_set_capabilities(conn, caplist));
+
+ /* All released versions of Subversion support edit-pipeline,
+ * so we do not accept connections from clients that do not. */
+ if (! svn_ra_svn_has_capability(conn, SVN_RA_SVN_CAP_EDIT_PIPELINE))
+ return SVN_NO_ERROR;
+
+ /* find_repos needs the capabilities as a list of words (eventually
+ they get handed to the start-commit hook). While we could add a
+ new interface to re-retrieve them from conn and convert the
+ result to a list, it's simpler to just convert caplist by hand
+ here, since we already have it and turning 'svn_ra_svn_item_t's
+ into 'const char *'s is pretty easy.
+
+ We only record capabilities we care about. The client may report
+ more (because it doesn't know what the server cares about). */
+ {
+ int i;
+ svn_ra_svn_item_t *item;
+
+ cap_words = apr_array_make(pool, 1, sizeof(const char *));
+ for (i = 0; i < caplist->nelts; i++)
+ {
+ item = &APR_ARRAY_IDX(caplist, i, svn_ra_svn_item_t);
+ /* ra_svn_set_capabilities() already type-checked for us */
+ if (strcmp(item->u.word, SVN_RA_SVN_CAP_MERGEINFO) == 0)
+ {
+ APR_ARRAY_PUSH(cap_words, const char *)
+ = SVN_RA_CAPABILITY_MERGEINFO;
+ }
+ /* Save for operational log. */
+ if (cap_log->len > 0)
+ svn_stringbuf_appendcstr(cap_log, " ");
+ svn_stringbuf_appendcstr(cap_log, item->u.word);
+ }
+ }
+
+ err = find_repos(client_url, params->root, &b, conn, cap_words, pool);
+ if (!err)
+ {
+ SVN_ERR(auth_request(conn, pool, &b, READ_ACCESS, FALSE));
+ if (current_access(&b) == NO_ACCESS)
+ err = error_create_and_log(SVN_ERR_RA_NOT_AUTHORIZED, NULL,
+ "Not authorized for access",
+ &b, conn, pool);
+ }
+ if (err)
+ {
+ log_error(err, b.log_file, svn_ra_svn_conn_remote_host(conn),
+ b.user, NULL, pool);
+ io_err = svn_ra_svn_write_cmd_failure(conn, pool, err);
+ svn_error_clear(err);
+ SVN_ERR(io_err);
+ return svn_ra_svn_flush(conn, pool);
+ }
+
+ /* Log the open. */
+ if (ra_client_string == NULL || ra_client_string[0] == '\0')
+ ra_client_string = "-";
+ else
+ ra_client_string = svn_path_uri_encode(ra_client_string, pool);
+ if (client_string == NULL || client_string[0] == '\0')
+ client_string = "-";
+ else
+ client_string = svn_path_uri_encode(client_string, pool);
+ SVN_ERR(log_command(&b, conn, pool,
+ "open %" APR_UINT64_T_FMT " cap=(%s) %s %s %s",
+ ver, cap_log->data,
+ svn_path_uri_encode(b.fs_path->data, pool),
+ ra_client_string, client_string));
+
+ warn_baton.server = &b;
+ warn_baton.conn = conn;
+ warn_baton.pool = svn_pool_create(pool);
+ svn_fs_set_warning_func(b.fs, fs_warning_func, &warn_baton);
+
+ SVN_ERR(svn_fs_get_uuid(b.fs, &uuid, pool));
+
+ /* We can't claim mergeinfo capability until we know whether the
+ repository supports mergeinfo (i.e., is not a 1.4 repository),
+ but we don't get the repository url from the client until after
+ we've already sent the initial list of server capabilities. So
+ we list repository capabilities here, in our first response after
+ the client has sent the url. */
+ {
+ svn_boolean_t supports_mergeinfo;
+ SVN_ERR(svn_repos_has_capability(b.repos, &supports_mergeinfo,
+ SVN_REPOS_CAPABILITY_MERGEINFO, pool));
+
+ SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "w(cc(!",
+ "success", uuid, b.repos_url));
+ if (supports_mergeinfo)
+ SVN_ERR(svn_ra_svn_write_word(conn, pool, SVN_RA_SVN_CAP_MERGEINFO));
+ SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "!))"));
+ }
+
+ return svn_ra_svn_handle_commands2(conn, pool, main_commands, &b, FALSE);
+}
diff --git a/subversion/svnserve/server.h b/subversion/svnserve/server.h
new file mode 100644
index 0000000..41ab09c
--- /dev/null
+++ b/subversion/svnserve/server.h
@@ -0,0 +1,187 @@
+/*
+ * svn_server.h : declarations for the svn server
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+
+#ifndef SERVER_H
+#define SERVER_H
+
+#include <apr_network_io.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+#include "svn_config.h"
+#include "svn_repos.h"
+#include "svn_ra_svn.h"
+
+enum username_case_type { CASE_FORCE_UPPER, CASE_FORCE_LOWER, CASE_ASIS };
+
+typedef struct server_baton_t {
+ svn_repos_t *repos;
+ const char *repos_name; /* URI-encoded name of repository (not for authz) */
+ svn_fs_t *fs; /* For convenience; same as svn_repos_fs(repos) */
+ svn_config_t *cfg; /* Parsed repository svnserve.conf */
+ svn_config_t *pwdb; /* Parsed password database */
+ svn_authz_t *authzdb; /* Parsed authz rules */
+ const char *authz_repos_name; /* The name of the repository for authz */
+ const char *realm; /* Authentication realm */
+ const char *repos_url; /* URL to base of repository */
+ svn_stringbuf_t *fs_path;/* Decoded base in-repos path (w/ leading slash) */
+ apr_hash_t *fs_config; /* Additional FS configuration parameters */
+ const char *user; /* Authenticated username of the user */
+ enum username_case_type username_case; /* Case-normalize the username? */
+ const char *authz_user; /* Username for authz ('user' + 'username_case') */
+ svn_boolean_t tunnel; /* Tunneled through login agent */
+ const char *tunnel_user; /* Allow EXTERNAL to authenticate as this */
+ svn_boolean_t read_only; /* Disallow write access (global flag) */
+ svn_boolean_t use_sasl; /* Use Cyrus SASL for authentication;
+ always false if SVN_HAVE_SASL not defined */
+ apr_file_t *log_file; /* Log filehandle. */
+ apr_pool_t *pool;
+} server_baton_t;
+
+enum authn_type { UNAUTHENTICATED, AUTHENTICATED };
+enum access_type { NO_ACCESS, READ_ACCESS, WRITE_ACCESS };
+
+enum access_type get_access(server_baton_t *b, enum authn_type auth);
+
+typedef struct serve_params_t {
+ /* The virtual root of the repositories to serve. The client URL
+ path is interpreted relative to this root and is not allowed to
+ escape it. */
+ const char *root;
+
+ /* True if the connection is tunneled over an ssh-like transport,
+ such that the client may use EXTERNAL to authenticate as the
+ current uid's username. */
+ svn_boolean_t tunnel;
+
+ /* If tunnel is true, overrides the current uid's username as the
+ identity EXTERNAL authenticates as. */
+ const char *tunnel_user;
+
+ /* True if the read-only flag was specified on the command-line,
+ which forces all connections to be read-only. */
+ svn_boolean_t read_only;
+
+ /* A parsed repository svnserve configuration file, ala
+ svnserve.conf. If this is NULL, then no configuration file was
+ specified on the command line. If this is non-NULL, then
+ per-repository svnserve.conf are not read. */
+ svn_config_t *cfg;
+
+ /* A parsed repository password database. If this is NULL, then
+ either no svnserve configuration file was specified on the
+ command line, or it was specified and it did not refer to a
+ password database. */
+ svn_config_t *pwdb;
+
+ /* A parsed repository authorization database. If this is NULL,
+ then either no svnserve configuration file was specified on the
+ command line, or it was specified and it did not refer to a
+ authorization database. */
+ svn_authz_t *authzdb;
+
+ /* A filehandle open for writing logs to; possibly NULL. */
+ apr_file_t *log_file;
+
+ /* Username case normalization style. */
+ enum username_case_type username_case;
+
+ /* Enable text delta caching for all FSFS repositories. */
+ svn_boolean_t cache_txdeltas;
+
+ /* Enable full-text caching for all FSFS repositories. */
+ svn_boolean_t cache_fulltexts;
+
+ /* Size of the in-memory cache (used by FSFS only). */
+ apr_uint64_t memory_cache_size;
+
+ /* Data compression level to reduce for network traffic. If this
+ is 0, no compression should be applied and the protocol may
+ fall back to svndiff "version 0" bypassing zlib entirely.
+ Defaults to SVN_DELTA_COMPRESSION_LEVEL_DEFAULT. */
+ int compression_level;
+
+} serve_params_t;
+
+/* Serve the connection CONN according to the parameters PARAMS. */
+svn_error_t *serve(svn_ra_svn_conn_t *conn, serve_params_t *params,
+ apr_pool_t *pool);
+
+/* Load a svnserve configuration file located at FILENAME into CFG,
+ and if such as found, then:
+
+ - set *PWDB to any referenced password database,
+ - set *AUTHZDB to any referenced authorization database, and
+ - set *USERNAME_CASE to the enumerated value of the
+ 'force-username-case' configuration value (or its default).
+
+ If MUST_EXIST is true and FILENAME does not exist, then return an
+ error. BASE may be specified as the base path to any referenced
+ password and authorization files found in FILENAME.
+
+ If SERVER is not NULL, log the real errors with SERVER and CONN but
+ return generic errors to the client. CONN must not be NULL if SERVER
+ is not NULL. */
+svn_error_t *load_configs(svn_config_t **cfg,
+ svn_config_t **pwdb,
+ svn_authz_t **authzdb,
+ enum username_case_type *username_case,
+ const char *filename,
+ svn_boolean_t must_exist,
+ const char *base,
+ server_baton_t *server,
+ svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool);
+
+/* Initialize the Cyrus SASL library. POOL is used for allocations. */
+svn_error_t *cyrus_init(apr_pool_t *pool);
+
+/* Authenticate using Cyrus SASL. */
+svn_error_t *cyrus_auth_request(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ server_baton_t *b,
+ enum access_type required,
+ svn_boolean_t needs_username);
+
+/* Escape SOURCE into DEST where SOURCE is null-terminated and DEST is
+ size BUFLEN DEST will be null-terminated. Returns number of bytes
+ written, including terminating null byte. */
+apr_size_t escape_errorlog_item(char *dest, const char *source,
+ apr_size_t buflen);
+
+/* Log ERR to LOG_FILE if LOG_FILE is not NULL. Include REMOTE_HOST,
+ USER, and REPOS in the log if they are not NULL. Allocate temporary
+ char buffers in POOL (which caller can then clear or dispose of). */
+void
+log_error(svn_error_t *err, apr_file_t *log_file, const char *remote_host,
+ const char *user, const char *repos, apr_pool_t *pool);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SERVER_H */
diff --git a/subversion/svnserve/svnserve.8 b/subversion/svnserve/svnserve.8
new file mode 100644
index 0000000..66cb983
--- /dev/null
+++ b/subversion/svnserve/svnserve.8
@@ -0,0 +1,138 @@
+.\"
+.\"
+.\" Licensed to the Apache Software Foundation (ASF) under one
+.\" or more contributor license agreements. See the NOTICE file
+.\" distributed with this work for additional information
+.\" regarding copyright ownership. The ASF licenses this file
+.\" to you under the Apache License, Version 2.0 (the
+.\" "License"); you may not use this file except in compliance
+.\" with the License. You may obtain a copy of the License at
+.\"
+.\" http://www.apache.org/licenses/LICENSE-2.0
+.\"
+.\" Unless required by applicable law or agreed to in writing,
+.\" software distributed under the License is distributed on an
+.\" "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+.\" KIND, either express or implied. See the License for the
+.\" specific language governing permissions and limitations
+.\" under the License.
+.\"
+.\"
+.\" You can view this file with:
+.\" nroff -man [filename]
+.\"
+.TH svnserve 8
+.SH NAME
+svnserve \- Server for the 'svn' repository access method
+.SH SYNOPSIS
+.TP
+\fBsvnserve\fP [\fIoptions\fP]
+.SH DESCRIPTION
+\fBsvnserve\fP allows access to Subversion repositories using the svn
+network protocol. It can both run as a standalone server process, or
+it can run out of inetd. You must choose a mode of operation when you
+start \fBsvnserve\fP. The following options are recognized:
+.PP
+.TP 5
+\fB\-d\fP, \fB\-\-daemon\fP
+Causes \fBsvnserve\fP to run in daemon mode. \fBsvnserve\fP
+backgrounds itself and accepts and serves TCP/IP connections on the
+svn port (3690, by default).
+.PP
+.TP 5
+\fB\-\-listen\-port\fP=\fIport\fP
+Causes \fBsvnserve\fP to listen on \fIport\fP when run in daemon mode.
+.PP
+.TP 5
+\fB\-\-listen\-host\fP=\fIhost\fP
+Causes \fBsvnserve\fP to listen on the interface specified by \fIhost\fP,
+which may be either a hostname or an IP address.
+.PP
+.TP 5
+\fB\-\-foreground\fP
+When used together with \fB\-d\fP, this option causes \fBsvnserve\fP
+to stay in the foreground. This option is mainly useful for
+debugging.
+.PP
+.TP 5
+\fB\-i\fP, \fB\-\-inetd\fP
+Causes \fBsvnserve\fP to use the stdin/stdout file descriptors, as is
+appropriate for a daemon running out of inetd.
+.PP
+.TP 5
+\fB\-h\fP, \fB\-\-help\fP
+Displays a usage summary and exits.
+.PP
+.TP 5
+\fB\-\-version\fP
+Print \fBsvnserve\fP's version and the repository filesystem
+back-end(s) a particular \fBsvnserve\fP supports.
+.PP
+.TP 5
+\fB\-r\fP \fIroot\fP, \fB\-\-root\fP=\fIroot\fP
+Sets the virtual root for repositories served by \fBsvnserve\fP. The
+pathname in URLs provided by the client will be interpreted relative
+to this root, and will not be allowed to escape this root.
+.PP
+.TP 5
+\fB\-R\fP \fB\-\-read\-only\fP
+Force all write operations through this \fBsvnserve\fP instance to be
+forbidden, overriding all other access policy configuration. Do not
+use this option to set general repository access policy - that is what
+the \fBconf/svnserve.conf\fP repository configuration file is for.
+This option should be used only to restrict access via a certain
+method of invoking \fBsvnserve\fP - for example, to allow write access
+via SSH, but not via a \fBsvnserve\fP daemon, or to create a
+restricted SSH key which is only capable of read access.
+.PP
+.TP 5
+\fB\-t\fP, \fB\-\-tunnel\fP
+Causes \fBsvnserve\fP to run in tunnel mode, which is just like the
+inetd mode of operation (serve one connection over stdin/stdout)
+except that the connection is considered to be pre-authenticated with
+the username of the current uid. This flag is selected by the client
+when running over a tunnel agent.
+.PP
+.TP 5
+\fB\-\-tunnel\-user\fP=\fIusername\fP
+When combined with \fB\-\-tunnel\fP, overrides the pre-authenticated
+username with the supplied \fIusername\fP. This is useful in
+combination with the ssh authorized_key file's "command" directive to
+allow a single system account to be used by multiple committers, each
+having a distinct ssh identity.
+.PP
+.TP 5
+\fB\-T\fP, \fB\-\-threads\fP
+When running in daemon mode, causes \fBsvnserve\fP to spawn a thread
+instead of a process for each connection. The \fBsvnserve\fP process
+still backgrounds itself at startup time.
+.PP
+.TP 5
+\fB\-\-config\-file\fP=\fIfilename\fP
+When specified, \fBsvnserve\fP reads \fIfilename\fP once at program
+startup and caches the \fBsvnserve\fP configuration and any passwords
+and authorization configuration referenced from \fIfilename\fP.
+\fBsvnserve\fP will not read any per-repository
+\fBconf/svnserve.conf\fP files when this option is used. See the
+\fBsvnserve.conf\fP(5) man page for details of the file format for
+this option.
+.PP
+.TP 5
+\fB\-\-pid\-file\fP=\fIfilename\fP
+When specified, \fBsvnserve\fP will write its process ID to
+\fIfilename\fP.
+.PP
+.TP 5
+\fB\-X\fP, \fB\-\-listen\-once\fP
+Causes \fBsvnserve\fP to accept one connection on the svn port, serve
+it, and exit. This option is mainly useful for debugging.
+.PP
+Unless the \fB\-\-config\-file\fP option was specified on the command
+line, once the client has selected a repository by transmitting its
+URL, \fBsvnserve\fP reads a file named \fBconf/svnserve.conf\fP in the
+repository directory to determine repository-specific settings such as
+what authentication database to use and what authorization policies to
+apply. See the \fBsvnserve.conf\fP(5) man page for details of that
+file format.
+.SH SEE ALSO
+.BR svnserve.conf (5)
diff --git a/subversion/svnserve/svnserve.conf.5 b/subversion/svnserve/svnserve.conf.5
new file mode 100644
index 0000000..fefccce
--- /dev/null
+++ b/subversion/svnserve/svnserve.conf.5
@@ -0,0 +1,98 @@
+.\"
+.\"
+.\" Licensed to the Apache Software Foundation (ASF) under one
+.\" or more contributor license agreements. See the NOTICE file
+.\" distributed with this work for additional information
+.\" regarding copyright ownership. The ASF licenses this file
+.\" to you under the Apache License, Version 2.0 (the
+.\" "License"); you may not use this file except in compliance
+.\" with the License. You may obtain a copy of the License at
+.\"
+.\" http://www.apache.org/licenses/LICENSE-2.0
+.\"
+.\" Unless required by applicable law or agreed to in writing,
+.\" software distributed under the License is distributed on an
+.\" "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+.\" KIND, either express or implied. See the License for the
+.\" specific language governing permissions and limitations
+.\" under the License.
+.\"
+.\"
+.\" You can view this file with:
+.\" nroff -man [filename]
+.\"
+.TH svnserve.conf 5
+.SH NAME
+svnserve.conf \- Repository configuration file for svnserve
+.SH SYNOPSIS
+.TP
+\fIrepository-path\fP\fB/conf/svnserve.conf\fP
+.SH DESCRIPTION
+\fBsvnserve.conf\fP controls the behavior of the \fBsvnserve\fP daemon
+on a per-repository basis. It is located in the \fBconf\fP
+subdirectory of the repository.
+.PP
+The overall structure of the file is the same as the structure of
+Subversion user configuration files. At the top level are sections,
+which are specified by words in square brackets; inside each section
+are variable definitions of the form "variable = value". Lines
+beginning with '#' are ignored. \fBsvnserve.conf\fP currently uses
+only one section named "general", and supports the following
+variables:
+.PP
+.TP 5
+\fBanon-access\fP = \fBnone\fP|\fBread\fP|\fBwrite\fP
+Determines the access level for unauthenticated users. \fBwrite\fP
+access allows all repository operations. \fBread\fP access allows all
+operations except committing and changing revision properties.
+\fBnone\fP access allows no access. The default level is \fBread\fP.
+.PP
+.TP 5
+\fBauth-access\fP = \fBnone\fP|\fBread\fP|\fBwrite\fP
+Determines the access level for authenticated users, using the same
+access levels as above. The default level is \fBwrite\fP.
+.PP
+.TP 5
+\fBpassword-db\fP = \fIfilename\fP
+Sets the location of the password database. \fIfilename\fP may be
+relative to the repository conf directory. There is no default value.
+The password database has the same overall format as this file. It
+uses only one section "users"; each variable within the section is a
+username, and each value is a password.
+.PP
+.TP 5
+\fBauthz-db\fP = \fIfilename\fP
+The authz-db option controls the location of the authorization
+rules for path-based access control. \fIfilename\fP may be
+relative to the repository conf directory. There is no default value.
+If you don't specify an authz-db, no path-based access control is done.
+.PP
+.TP 5
+\fBrealm\fP = \fIrealm\-name\fP
+Sets the authentication realm of the repository. If two repositories
+have the same password database, they should have the same realm, and
+vice versa; this association allows clients to use a single cached
+password for several repositories. The default realm value is the
+repository's uuid.
+.SH EXAMPLE
+The following example \fBsvnserve.conf\fP allows read access for
+authenticated users, no access for anonymous users, points to a passwd
+database in the same directory, and defines a realm name.
+.PP
+.nf
+ [general]
+ anon-access = none
+ auth-access = read
+ password-db = passwd
+ realm = My First Repository
+.fi
+.PP
+The file "passwd" would look like:
+.PP
+.nf
+ [users]
+ joeuser = joepassword
+ jayrandom = randomjay
+.fi
+.SH SEE ALSO
+.BR svnserve (8)
diff --git a/subversion/svnserve/winservice.c b/subversion/svnserve/winservice.c
new file mode 100644
index 0000000..dcb399e
--- /dev/null
+++ b/subversion/svnserve/winservice.c
@@ -0,0 +1,490 @@
+/*
+ * winservice.c : Implementation of Windows Service support
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+
+#define APR_WANT_STRFUNC
+#include <apr_want.h>
+#include <apr_errno.h>
+
+#include "svn_error.h"
+
+#include "svn_private_config.h"
+#include "winservice.h"
+
+/*
+Design Notes
+------------
+
+The code in this file allows svnserve to run as a Windows service.
+Windows Services are only supported on operating systems derived
+from Windows NT, which is basically all modern versions of Windows
+(2000, XP, Server, Vista, etc.) and excludes the Windows 9x line.
+
+Windows Services are processes that are started and controlled by
+the Service Control Manager. When the SCM wants to start a service,
+it creates the process, then waits for the process to connect to
+the SCM, so that the SCM and service process can communicate.
+This is done using the StartServiceCtrlDispatcher function.
+
+In order to minimize changes to the svnserve startup logic, this
+implementation differs slightly from most service implementations.
+In most services, main() immediately calls StartServiceCtrlDispatcher,
+which does not return control to main() until the SCM sends the
+"stop" request to the service, and the service stops.
+
+
+Installing the Service
+----------------------
+
+Installation is beyond the scope of source code comments. There
+is a separate document that describes how to install and uninstall
+the service. Basically, you create a Windows Service, give it a
+binary path that points to svnserve.exe, and make sure that you
+specify --service on the command line.
+
+
+Starting the Service
+--------------------
+
+First, the SCM decides that it wants to start a service. It creates
+the process for the service, passing it the command-line that is
+stored in the service configuration (stored in the registry).
+
+Next, main() runs. The command-line should contain the --service
+argument, which is the hint that svnserve is running under the SCM,
+not as a standalone process. main() calls winservice_start().
+
+winservice_start() creates an event object (winservice_start_event),
+and creates and starts a separate thread, the "dispatcher" thread.
+winservice_start() then waits for either winservice_start_event
+to fire (meaning: "the dispatcher thread successfully connected
+to the SCM, and now the service is starting") or for the dispatcher
+thread to exit (meaning: "failed to connect to SCM").
+
+If the dispatcher thread quit, then winservice_start returns an error.
+If the start event fired, then winservice_start returns a success code
+(SVN_NO_ERROR). At this point, the service is now in the "starting"
+state, from the perspective of the SCM. winservice_start also registers
+an atexit handler, which handles cleaning up some of the service logic,
+as explained below in "Stopping the Service".
+
+Next, control returns to main(), which performs the usual startup
+logic for svnserve. Mostly, it creates the listener socket. If
+main() was able to start the service, then it calls the function
+winservice_running().
+
+winservice_running() informs the SCM that the service has finished
+starting, and is now in the "running" state. main() then does its
+work, accepting client sockets and processing SVN requests.
+
+Stopping the Service
+--------------------
+
+At some point, the SCM will decide to stop the service, either because
+an administrator chose to stop the service, or the system is shutting
+down. To do this, the SCM calls winservice_handler() with the
+SERVICE_CONTROL_STOP control code. When this happens,
+winservice_handler() will inform the SCM that the service is now
+in the "stopping" state, and will call winservice_notify_stop().
+
+winservice_notify_stop() is responsible for cleanly shutting down the
+svnserve logic (waiting for client requests to finish, stopping database
+access, etc.). Right now, all it does is close the listener socket,
+which causes the apr_socket_accept() call in main() to fail. main()
+then calls exit(), which processes all atexit() handlers, which
+results in winservice_stop() being called.
+
+winservice_stop() notifies the SCM that the service is now stopped,
+and then waits for the dispatcher thread to exit. Because all services
+in the process have now stopped, the call to StartServiceCtrlDispatcher
+(in the dispatcher thread) finally returns, and winservice_stop() returns,
+and the process finally exits.
+*/
+
+
+#ifdef WIN32
+
+#include <assert.h>
+#include <winsvc.h>
+
+/* This is just a placeholder, and doesn't actually constrain the
+ service name. You have to provide *some* service name to the SCM
+ API, but for services that are marked SERVICE_WIN32_OWN_PROCESS (as
+ is the case for svnserve), the service name is ignored. It *is*
+ relevant for service binaries that run more than one service in a
+ single process. */
+#define WINSERVICE_SERVICE_NAME "svnserve"
+
+
+/* Win32 handle to the dispatcher thread. */
+static HANDLE winservice_dispatcher_thread = NULL;
+
+/* Win32 event handle, used to notify winservice_start() that we have
+ successfully connected to the SCM. */
+static HANDLE winservice_start_event = NULL;
+
+/* RPC handle that allows us to notify the SCM of changes in our
+ service status. */
+static SERVICE_STATUS_HANDLE winservice_status_handle = NULL;
+
+/* Our current idea of the service status (stopped, running, controls
+ accepted, exit code, etc.) */
+static SERVICE_STATUS winservice_status;
+
+
+#ifdef SVN_DEBUG
+static void dbg_print(const char* text)
+{
+ OutputDebugStringA(text);
+}
+#else
+/* Make sure dbg_print compiles to nothing in release builds. */
+#define dbg_print(text)
+#endif
+
+
+static void winservice_atexit(void);
+
+/* Notifies the Service Control Manager of the current state of the
+ service. */
+static void
+winservice_update_state(void)
+{
+ if (winservice_status_handle != NULL)
+ {
+ if (!SetServiceStatus(winservice_status_handle, &winservice_status))
+ {
+ dbg_print("SetServiceStatus - FAILED\r\n");
+ }
+ }
+}
+
+
+/* This function cleans up state associated with the service support.
+ If the dispatcher thread handle is non-NULL, then this function
+ will wait for the dispatcher thread to exit. */
+static void
+winservice_cleanup(void)
+{
+ if (winservice_start_event != NULL)
+ {
+ CloseHandle(winservice_start_event);
+ winservice_start_event = NULL;
+ }
+
+ if (winservice_dispatcher_thread != NULL)
+ {
+ dbg_print("winservice_cleanup:"
+ " waiting for dispatcher thread to exit\r\n");
+ WaitForSingleObject(winservice_dispatcher_thread, INFINITE);
+ CloseHandle(winservice_dispatcher_thread);
+ winservice_dispatcher_thread = NULL;
+ }
+}
+
+
+/* The SCM invokes this function to cause state changes in the
+ service. */
+static void WINAPI
+winservice_handler(DWORD control)
+{
+ switch (control)
+ {
+ case SERVICE_CONTROL_INTERROGATE:
+ /* The SCM just wants to check our state. We are required to
+ call SetServiceStatus, but we don't need to make any state
+ changes. */
+ dbg_print("SERVICE_CONTROL_INTERROGATE\r\n");
+ winservice_update_state();
+ break;
+
+ case SERVICE_CONTROL_STOP:
+ dbg_print("SERVICE_CONTROL_STOP\r\n");
+ winservice_status.dwCurrentState = SERVICE_STOP_PENDING;
+ winservice_update_state();
+ winservice_notify_stop();
+ break;
+ }
+}
+
+
+/* This is the "service main" routine (in the Win32 terminology).
+
+ Normally, this function (thread) implements the "main" loop of a
+ service. However, in order to minimize changes to the svnserve
+ main() function, this function is running in a different thread,
+ and main() is blocked in winservice_start(), waiting for
+ winservice_start_event. So this function (thread) only needs to
+ signal that event to "start" the service.
+
+ If this function succeeds, it signals winservice_start_event, which
+ wakes up the winservice_start() frame that is blocked. */
+static void WINAPI
+winservice_service_main(DWORD argc, LPTSTR *argv)
+{
+ DWORD error;
+
+ assert(winservice_start_event != NULL);
+
+ winservice_status_handle =
+ RegisterServiceCtrlHandler(WINSERVICE_SERVICE_NAME, winservice_handler);
+ if (winservice_status_handle == NULL)
+ {
+ /* Ok, that's not fair. We received a request to start a service,
+ and now we cannot bind to the SCM in order to update status?
+ Bring down the app. */
+ error = GetLastError();
+ dbg_print("RegisterServiceCtrlHandler FAILED\r\n");
+ /* Put the error code somewhere where winservice_start can find it. */
+ winservice_status.dwWin32ExitCode = error;
+ SetEvent(winservice_start_event);
+ return;
+ }
+
+ winservice_status.dwCurrentState = SERVICE_START_PENDING;
+ winservice_status.dwWin32ExitCode = ERROR_SUCCESS;
+ winservice_update_state();
+
+ dbg_print("winservice_service_main: service is starting\r\n");
+ SetEvent(winservice_start_event);
+}
+
+
+static const SERVICE_TABLE_ENTRY winservice_service_table[] =
+ {
+ { WINSERVICE_SERVICE_NAME, winservice_service_main },
+ { NULL, NULL }
+ };
+
+
+/* This is the thread routine for the "dispatcher" thread. The
+ purpose of this thread is to connect this process with the Service
+ Control Manager, which allows this process to receive control
+ requests from the SCM, and allows this process to update the SCM
+ with status information.
+
+ The StartServiceCtrlDispatcher connects this process to the SCM.
+ If it succeeds, then it will not return until all of the services
+ running in this process have stopped. (In our case, there is only
+ one service per process.) */
+static DWORD WINAPI
+winservice_dispatcher_thread_routine(PVOID arg)
+{
+ dbg_print("winservice_dispatcher_thread_routine: starting\r\n");
+
+ if (!StartServiceCtrlDispatcher(winservice_service_table))
+ {
+ /* This is a common error. Usually, it means the user has
+ invoked the service with the --service flag directly. This
+ is incorrect. The only time the --service flag is passed is
+ when the process is being started by the SCM. */
+ DWORD error = GetLastError();
+
+ dbg_print("dispatcher: FAILED to connect to SCM\r\n");
+ return error;
+ }
+
+ dbg_print("dispatcher: SCM is done using this process -- exiting\r\n");
+ return ERROR_SUCCESS;
+}
+
+
+/* If svnserve needs to run as a Win32 service, then we need to
+ coordinate with the Service Control Manager (SCM) before
+ continuing. This function call registers the svnserve.exe process
+ with the SCM, waits for the "start" command from the SCM (which
+ will come very quickly), and confirms that those steps succeeded.
+
+ After this call succeeds, the service should perform whatever work
+ it needs to start the service, and then the service should call
+ winservice_running() (if no errors occurred) or winservice_stop()
+ (if something failed during startup). */
+svn_error_t *
+winservice_start(void)
+{
+ HANDLE handles[2];
+ DWORD thread_id;
+ DWORD error_code;
+ apr_status_t apr_status;
+ DWORD wait_status;
+
+ dbg_print("winservice_start: starting svnserve as a service...\r\n");
+
+ ZeroMemory(&winservice_status, sizeof(winservice_status));
+ winservice_status.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
+ winservice_status.dwControlsAccepted = SERVICE_ACCEPT_STOP;
+ winservice_status.dwCurrentState = SERVICE_STOPPED;
+
+ /* Create the event that will wake up this thread when the SCM
+ creates the ServiceMain thread. */
+ winservice_start_event = CreateEvent(NULL, FALSE, FALSE, NULL);
+ if (winservice_start_event == NULL)
+ {
+ apr_status = apr_get_os_error();
+ return svn_error_wrap_apr(apr_status,
+ _("Failed to create winservice_start_event"));
+ }
+
+ winservice_dispatcher_thread =
+ (HANDLE)CreateThread(NULL, 0, winservice_dispatcher_thread_routine,
+ NULL, 0, &thread_id);
+ if (winservice_dispatcher_thread == NULL)
+ {
+ apr_status = apr_get_os_error();
+ winservice_cleanup();
+ return svn_error_wrap_apr(apr_status,
+ _("The service failed to start"));
+ }
+
+ /* Next, we wait for the "start" event to fire (meaning the service
+ logic has successfully started), or for the dispatch thread to
+ exit (meaning the service logic could not start). */
+
+ handles[0] = winservice_start_event;
+ handles[1] = winservice_dispatcher_thread;
+ wait_status = WaitForMultipleObjects(2, handles, FALSE, INFINITE);
+ switch (wait_status)
+ {
+ case WAIT_OBJECT_0:
+ dbg_print("winservice_start: service is now starting\r\n");
+
+ /* We no longer need the start event. */
+ CloseHandle(winservice_start_event);
+ winservice_start_event = NULL;
+
+ /* Register our cleanup logic. */
+ atexit(winservice_atexit);
+ return SVN_NO_ERROR;
+
+ case WAIT_OBJECT_0+1:
+ /* The dispatcher thread exited without starting the service.
+ This happens when the dispatcher fails to connect to the SCM. */
+ dbg_print("winservice_start: dispatcher thread has failed\r\n");
+
+ if (GetExitCodeThread(winservice_dispatcher_thread, &error_code))
+ {
+ dbg_print("winservice_start: dispatcher thread failed\r\n");
+
+ if (error_code == ERROR_SUCCESS)
+ error_code = ERROR_INTERNAL_ERROR;
+
+ }
+ else
+ {
+ error_code = ERROR_INTERNAL_ERROR;
+ }
+
+ CloseHandle(winservice_dispatcher_thread);
+ winservice_dispatcher_thread = NULL;
+
+ winservice_cleanup();
+
+ return svn_error_wrap_apr
+ (APR_FROM_OS_ERROR(error_code),
+ _("Failed to connect to Service Control Manager"));
+
+ default:
+ /* This should never happen! This indicates that our handles are
+ broken, or some other highly unusual error. There is nothing
+ rational that we can do to recover. */
+ apr_status = apr_get_os_error();
+ dbg_print("winservice_start: WaitForMultipleObjects failed!\r\n");
+
+ winservice_cleanup();
+ return svn_error_wrap_apr
+ (apr_status, _("The service failed to start; an internal error"
+ " occurred while starting the service"));
+ }
+}
+
+
+/* main() calls this function in order to inform the SCM that the
+ service has successfully started. This is required; otherwise, the
+ SCM will believe that the service is stuck in the "starting" state,
+ and management tools will also believe that the service is stuck. */
+void
+winservice_running(void)
+{
+ winservice_status.dwCurrentState = SERVICE_RUNNING;
+ winservice_update_state();
+ dbg_print("winservice_notify_running: service is now running\r\n");
+}
+
+
+/* main() calls this function in order to notify the SCM that the
+ service has stopped. This function also handles cleaning up the
+ dispatcher thread (the one that we created above in
+ winservice_start. */
+static void
+winservice_stop(DWORD exit_code)
+{
+ dbg_print("winservice_stop - notifying SCM that service has stopped\r\n");
+ winservice_status.dwCurrentState = SERVICE_STOPPED;
+ winservice_status.dwWin32ExitCode = exit_code;
+ winservice_update_state();
+
+ if (winservice_dispatcher_thread != NULL)
+ {
+ dbg_print("waiting for dispatcher thread to exit...\r\n");
+ WaitForSingleObject(winservice_dispatcher_thread, INFINITE);
+ dbg_print("dispatcher thread has exited.\r\n");
+
+ CloseHandle(winservice_dispatcher_thread);
+ winservice_dispatcher_thread = NULL;
+ }
+ else
+ {
+ /* There was no dispatcher thread. So we never started in
+ the first place. */
+ exit_code = winservice_status.dwWin32ExitCode;
+ dbg_print("dispatcher thread was not running\r\n");
+ }
+
+ if (winservice_start_event != NULL)
+ {
+ CloseHandle(winservice_start_event);
+ winservice_start_event = NULL;
+ }
+
+ dbg_print("winservice_stop - service has stopped\r\n");
+}
+
+
+/* This function is installed as an atexit-handler. This is done so
+ that we don't need to alter every exit() call in main(). */
+static void
+winservice_atexit(void)
+{
+ dbg_print("winservice_atexit - stopping\r\n");
+ winservice_stop(ERROR_SUCCESS);
+}
+
+
+svn_boolean_t
+winservice_is_stopping(void)
+{
+ return (winservice_status.dwCurrentState == SERVICE_STOP_PENDING);
+}
+
+#endif /* WIN32 */
diff --git a/subversion/svnserve/winservice.h b/subversion/svnserve/winservice.h
new file mode 100644
index 0000000..8e5ac52
--- /dev/null
+++ b/subversion/svnserve/winservice.h
@@ -0,0 +1,64 @@
+/*
+ * winservice.h : Public definitions for Windows Service support
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#ifndef WINSERVICE_H
+#define WINSERVICE_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+#ifdef WIN32
+
+/* Connects to the Windows Service Control Manager and allows this
+ process to run as a service. This function can only succeed if the
+ process was started by the SCM, not directly by a user. After this
+ call succeeds, the service should perform whatever work it needs to
+ start the service, and then the service should call
+ winservice_running() (if no errors occurred) or winservice_stop()
+ (if something failed during startup). */
+svn_error_t *winservice_start(void);
+
+/* Notifies the SCM that the service is now running. The caller must
+ already have called winservice_start successfully. */
+void winservice_running(void);
+
+/* This function is called by the SCM in an arbitrary thread when the
+ SCM wants the service to stop. The implementation of this function
+ can return immediately; all that is necessary is that the service
+ eventually stop in response. */
+void winservice_notify_stop(void);
+
+/* Evaluates to TRUE if the SCM has requested that the service stop.
+ This allows for the service to poll, in addition to being notified
+ in the winservice_notify_stop callback. */
+svn_boolean_t winservice_is_stopping(void);
+
+#endif /* WIN32 */
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* WINSERVICE_H */