/*
* main.c : Main control function for svnserve
*
* ====================================================================
* Copyright (c) 2000-2008 CollabNet. All rights reserved.
*
* This software is licensed as described in the file COPYING, which
* you should have received as part of this distribution. The terms
* are also available at http://subversion.tigris.org/license-1.html.
* If newer versions of this license are posted there, you may use a
* newer version instead, at your option.
*
* This software consists of voluntary contributions made by many
* individuals. For exact contribution history, see the revision
* history and logs, available at http://subversion.tigris.org/.
* ====================================================================
*/
#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_path.h"
#include "svn_opt.h"
#include "svn_repos.h"
#include "svn_fs.h"
#include "svn_version.h"
#include "svn_io.h"
#include "svn_private_config.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
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
#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")},
{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(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, 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
/* 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;
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;
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.log_file = NULL;
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 'h':
help(pool);
break;
case SVNSERVE_OPT_VERSION:
SVN_INT_ERR(version(pool));
exit(0);
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:
port = atoi(arg);
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_path_internal_style(params.root, pool);
SVN_INT_ERR(svn_path_get_absolute(¶ms.root, params.root, pool));
break;
case 'R':
params.read_only = TRUE;
break;
case 'T':
handling_mode = connection_mode_thread;
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_path_internal_style(config_filename, pool);
SVN_INT_ERR(svn_path_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_path_internal_style(pid_filename, pool);
SVN_INT_ERR(svn_path_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_path_internal_style(log_filename, pool);
SVN_INT_ERR(svn_path_get_absolute(&log_filename, log_filename,
pool));
break;
}
}
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(¶ms.cfg, ¶ms.pwdb, ¶ms.authzdb,
config_filename, TRUE,
svn_path_dirname(config_filename, pool),
NULL, NULL, /* server baton, conn */
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: ");
}
conn = svn_ra_svn_create_conn(NULL, in_file, out_file, pool);
svn_error_clear(serve(conn, ¶ms, 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;
}
#endif
status = apr_sockaddr_info_get(&sa, host, family, port, 0, 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
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 = svn_pool_create(NULL);
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_conn(usock, NULL, NULL, 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
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->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, ¶ms, connection_pool));
svn_pool_destroy(connection_pool);
}
}
/* NOTREACHED */
}