diff options
Diffstat (limited to 'subversion/svnserve/svnserve.c')
-rw-r--r-- | subversion/svnserve/svnserve.c | 1175 |
1 files changed, 1175 insertions, 0 deletions
diff --git a/subversion/svnserve/svnserve.c b/subversion/svnserve/svnserve.c new file mode 100644 index 0000000..7648ea8 --- /dev/null +++ b/subversion/svnserve/svnserve.c @@ -0,0 +1,1175 @@ +/* + * svnserve.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 "private/svn_cmdline_private.h" +#include "private/svn_atomic.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 +#define SVNSERVE_OPT_CACHE_REVPROPS 267 +#define SVNSERVE_OPT_SINGLE_CONN 268 +#define SVNSERVE_OPT_CLIENT_SPEED 269 +#define SVNSERVE_OPT_VIRTUAL_HOST 270 + +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. The default port is 3690.\n" + " " + "[mode: daemon, service, listen-once]")}, +#else + N_("listen port. The default port is 3690.\n" + " " + "[mode: daemon, listen-once]")}, +#endif + {"listen-host", SVNSERVE_OPT_LISTEN_HOST, 1, +#ifdef WIN32 + N_("listen hostname or IP address\n" + " " + "By default svnserve listens on all addresses.\n" + " " + "[mode: daemon, service, listen-once]")}, +#else + N_("listen hostname or IP address\n" + " " + "By default svnserve listens on all addresses.\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]")}, + {"cache-revprops", SVNSERVE_OPT_CACHE_REVPROPS, 1, + N_("enable or disable caching of revision properties.\n" + " " + "Consult the documentation before activating this.\n" + " " + "Default is no.\n" + " " + "[used for FSFS repositories only]")}, + {"client-speed", SVNSERVE_OPT_CLIENT_SPEED, 1, + N_("Optimize network handling based on the assumption\n" + " " + "that most clients are connected with a bitrate of\n" + " " + "ARG Mbit/s.\n" + " " + "Default is 0 (optimizations disabled).")}, +#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]")}, + {"single-thread", SVNSERVE_OPT_SINGLE_CONN, 0, + N_("handle one connection at a time in the parent process\n" + " " + "(useful for debugging)")}, + {"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")}, + {"virtual-host", SVNSERVE_OPT_VIRTUAL_HOST, 0, + N_("virtual host mode (look for repo in directory\n" + " " + "of provided hostname)")}, + {"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_help4(NULL, "svnserve", TRUE, quiet, FALSE, + 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); +} + +#if APR_HAS_THREADS +/* The pool passed to apr_thread_create can only be released when both + + A: the call to apr_thread_create has returned to the calling thread + B: the new thread has started running and reached apr_thread_start_t + + So we set the atomic counter to 2 then both the calling thread and + the new thread decrease it and when it reaches 0 the pool can be + released. */ +struct shared_pool_t { + svn_atomic_t count; + apr_pool_t *pool; +}; + +static struct shared_pool_t * +attach_shared_pool(apr_pool_t *pool) +{ + struct shared_pool_t *shared = apr_palloc(pool, sizeof(struct shared_pool_t)); + + shared->pool = pool; + svn_atomic_set(&shared->count, 2); + + return shared; +} + +static void +release_shared_pool(struct shared_pool_t *shared) +{ + if (svn_atomic_dec(&shared->count) == 0) + svn_pool_destroy(shared->pool); +} +#endif + +/* "Arguments" passed from the main thread to the connection thread */ +struct serve_thread_t { + svn_ra_svn_conn_t *conn; + serve_params_t *params; + struct shared_pool_t *shared_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->shared_pool->pool)); + release_shared_pool(d->shared_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; + 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 shared_pool_t *shared_pool; + + 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; +#if APR_HAVE_IPV6 + svn_boolean_t prefer_v6 = FALSE; +#endif + svn_boolean_t quiet = FALSE; + svn_boolean_t is_version = FALSE; + int mode_opt_count = 0; + int handling_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.base = NULL; + params.cfg = NULL; + params.compression_level = SVN_DELTA_COMPRESSION_LEVEL_DEFAULT; + params.log_file = NULL; + params.vhost = FALSE; + params.username_case = CASE_ASIS; + params.memory_cache_size = (apr_uint64_t)-1; + params.cache_fulltexts = TRUE; + params.cache_txdeltas = FALSE; + params.cache_revprops = FALSE; + params.zero_copy_limit = 0; + params.error_check_interval = 4096; + + 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': +#if APR_HAVE_IPV6 + prefer_v6 = TRUE; +#endif + /* ### Maybe error here if we don't have IPV6 support? */ + 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 SVNSERVE_OPT_SINGLE_CONN: + handling_mode = connection_mode_single; + handling_opt_count++; + 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(¶ms.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(¶ms.root, params.root, pool)); + break; + + case 'R': + params.read_only = TRUE; + break; + + case 'T': + handling_mode = connection_mode_thread; + handling_opt_count++; + 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; + + case SVNSERVE_OPT_CACHE_REVPROPS: + params.cache_revprops + = svn_tristate__from_word(arg) == svn_tristate_true; + break; + + case SVNSERVE_OPT_CLIENT_SPEED: + { + apr_size_t bandwidth = (apr_size_t)apr_strtoi64(arg, NULL, 0); + + /* for slower clients, don't try anything fancy */ + if (bandwidth >= 1000) + { + /* block other clients for at most 1 ms (at full bandwidth). + Note that the send buffer is 16kB anyways. */ + params.zero_copy_limit = bandwidth * 120; + + /* check for aborted connections at the same rate */ + params.error_check_interval = bandwidth * 120; + } + } + 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_VIRTUAL_HOST: + params.vhost = TRUE; + 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 (handling_opt_count > 1) + { + svn_error_clear(svn_cmdline_fputs( + _("You may only specify one of -T or --single-thread\n"), + stderr, pool)); + usage(argv[0], pool); + } + + /* If a configuration file is specified, load it and any referenced + * password and authorization files. */ + if (config_filename) + { + params.base = svn_dirent_dirname(config_filename, pool); + + SVN_INT_ERR(svn_config_read3(¶ms.cfg, config_filename, + TRUE, /* must_exist */ + FALSE, /* section_names_case_sensitive */ + FALSE, /* option_names_case_sensitive */ + pool)); + } + + if (log_filename) + SVN_INT_ERR(svn_io_file_open(¶ms.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_conn3(NULL, in_file, out_file, + params.compression_level, + params.zero_copy_limit, + params.error_check_interval, + connection_pool); + svn_error_clear(serve(conn, ¶ms, 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) + { +#if 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 + + /* 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 + = apr_allocator_owner_get(svn_pool_create_allocator(FALSE)); + + 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) + || APR_STATUS_IS_ECONNABORTED(status) + || APR_STATUS_IS_ECONNRESET(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_conn3(usock, NULL, NULL, + params.compression_level, + params.zero_copy_limit, + params.error_check_interval, + connection_pool); + + if (run_mode == run_mode_listen_once) + { + err = serve(conn, ¶ms, 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, ¶ms, 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 + shared_pool = attach_shared_pool(connection_pool); + 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 = ¶ms; + thread_data->shared_pool = shared_pool; + status = apr_thread_create(&tid, tattr, serve_thread, thread_data, + shared_pool->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); + } + release_shared_pool(shared_pool); +#endif + break; + + case connection_mode_single: + /* Serve one connection at a time. */ + svn_error_clear(serve(conn, ¶ms, connection_pool)); + svn_pool_destroy(connection_pool); + } + } + + /* NOTREACHED */ +} |