#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_hash.h"
#include "svn_private_config.h"
#include "private/svn_dep_compat.h"
#include "private/svn_cmdline_private.h"
#include "private/svn_atomic.h"
#include "private/svn_mutex.h"
#include "private/svn_subr_private.h"
#if APR_HAS_THREADS
# include <apr_thread_pool.h>
#endif
#include "winservice.h"
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#include "server.h"
#include "logger.h"
enum connection_handling_mode {
connection_mode_fork,
connection_mode_thread,
connection_mode_single
};
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
#define CONNECTION_DEFAULT connection_mode_fork
#endif
#elif APR_HAS_THREADS
#define CONNECTION_DEFAULT connection_mode_thread
#else
#define CONNECTION_DEFAULT connection_mode_single
#endif
#define THREADPOOL_MIN_SIZE 1
#if (APR_SIZEOF_VOIDP <= 4)
#define THREADPOOL_MAX_SIZE 64
#else
#define THREADPOOL_MAX_SIZE 256
#endif
#define THREADPOOL_THREAD_IDLE_LIMIT 1000000
#define ACCEPT_BACKLOG 128
#ifdef WIN32
static apr_os_sock_t winservice_svnserve_accept_socket = INVALID_SOCKET;
void winservice_notify_stop(void)
{
if (winservice_svnserve_accept_socket != INVALID_SOCKET)
closesocket(winservice_svnserve_accept_socket);
}
#endif
#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
#define SVNSERVE_OPT_MIN_THREADS 271
#define SVNSERVE_OPT_MAX_THREADS 272
#define SVNSERVE_OPT_BLOCK_READ 273
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 16.\n"
" "
"0 switches to dynamically sized caches.\n"
" "
"[used for FSFS and FSX repositories only]")},
{"cache-txdeltas", SVNSERVE_OPT_CACHE_TXDELTAS, 1,
N_("enable or disable caching of deltas between older\n"
" "
"revisions.\n"
" "
"Default is yes.\n"
" "
"[used for FSFS and FSX 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 and FSX 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 and FSX 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).")},
{"block-read", SVNSERVE_OPT_BLOCK_READ, 1,
N_("Parse and cache all data found in block instead\n"
" "
"of just the requested item.\n"
" "
"Default is no.\n"
" "
"[used for FSFS repositories in 1.9 format only]")},
#ifdef CONNECTION_HAVE_THREAD_OPTION
{"threads", 'T', 0, N_("use threads instead of fork "
"[mode: daemon]")},
{"min-threads", SVNSERVE_OPT_MIN_THREADS, 1,
N_("Minimum number of server threads, even if idle.\n"
" "
"Capped to max-threads; minimum value is 0.\n"
" "
"Default is 1.\n"
" "
"[used only with --threads]")},
#if (APR_SIZEOF_VOIDP <= 4)
{"max-threads", SVNSERVE_OPT_MAX_THREADS, 1,
N_("Maximum number of server threads, even if there\n"
" "
"are more connections. Minimum value is 1.\n"
" "
"Default is 64.\n"
" "
"[used only with --threads]")},
#else
{"max-threads", SVNSERVE_OPT_MAX_THREADS, 1,
N_("Maximum number of server threads, even if there\n"
" "
"are more connections. Minimum value is 1.\n"
" "
"Default is 256.\n"
" "
"[used only with --threads]")},
#endif
#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\n"
" "
"process (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));
}
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"
"Subversion repository server.\n"
"Type 'svnserve --version' to see the "
"program version.\n"
"\n"
"Valid options:\n"),
stdout, pool));
#else
svn_error_clear(svn_cmdline_fputs(_("usage: svnserve [-d | -i | -t | -X] "
"[options]\n"
"Subversion repository server.\n"
"Type 'svnserve --version' to see the "
"program version.\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"));
}
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)
{
}
#endif
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);
}
static svn_error_t *
accept_connection(connection_t **connection,
apr_socket_t *sock,
serve_params_t *params,
enum connection_handling_mode handling_mode,
apr_pool_t *pool)
{
apr_status_t status;
apr_pool_t *connection_pool = svn_pool_create(pool);
*connection = apr_pcalloc(connection_pool, sizeof(**connection));
(*connection)->pool = connection_pool;
(*connection)->params = params;
(*connection)->ref_count = 1;
do
{
#ifdef WIN32
if (winservice_is_stopping())
exit(0);
#endif
status = apr_socket_accept(&(*connection)->usock, sock,
connection_pool);
if (handling_mode == connection_mode_fork)
{
apr_proc_t proc;
while (apr_proc_wait_all_procs(&proc, NULL, NULL, APR_NOWAIT,
connection_pool) == APR_CHILD_DONE)
;
}
}
while (APR_STATUS_IS_EINTR(status)
|| APR_STATUS_IS_ECONNABORTED(status)
|| APR_STATUS_IS_ECONNRESET(status));
return status
? svn_error_wrap_apr(status, _("Can't accept client connection"))
: SVN_NO_ERROR;
}
static void
attach_connection(connection_t *connection)
{
svn_atomic_inc(&connection->ref_count);
}
static void
close_connection(connection_t *connection)
{
if (svn_atomic_dec(&connection->ref_count) == 0)
svn_pool_destroy(connection->pool);
}
static svn_error_t *
serve_socket(connection_t *connection,
apr_pool_t *pool)
{
svn_error_t *err = serve_interruptable(NULL, connection, NULL, pool);
if (err)
logger__log_error(connection->params->logger, err, NULL,
get_client_info(connection->conn, connection->params,
pool));
return svn_error_trace(err);
}
#if APR_HAS_THREADS
static svn_root_pools__t *connection_pools;
static apr_thread_pool_t *threads;
static svn_boolean_t
is_busy(connection_t *connection)
{
return apr_thread_pool_threads_count(threads) * 2
> apr_thread_pool_thread_max_get(threads);
}
static void * APR_THREAD_FUNC serve_thread(apr_thread_t *tid, void *data)
{
svn_boolean_t done;
connection_t *connection = data;
svn_error_t *err;
apr_pool_t *pool = svn_root_pools__acquire_pool(connection_pools);
err = serve_interruptable(&done, connection, is_busy, pool);
if (err)
{
logger__log_error(connection->params->logger, err, NULL,
get_client_info(connection->conn, connection->params,
pool));
svn_error_clear(err);
done = TRUE;
}
svn_root_pools__release_pool(pool, connection_pools);
if (done)
close_connection(connection);
else
apr_thread_pool_push(threads, serve_thread, connection, 0, NULL);
return NULL;
}
#endif
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_remove_file2(filename, TRUE, pool));
SVN_ERR(svn_io_file_open(&file, filename,
APR_WRITE | APR_CREATE | APR_EXCL,
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;
}
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_list2(&my_version, checklist, svn_ver_equal);
}
static svn_error_t *
sub_main(int *exit_code, int argc, const char *argv[], apr_pool_t *pool)
{
enum run_mode run_mode = run_mode_unspecified;
svn_boolean_t foreground = FALSE;
apr_socket_t *sock;
apr_sockaddr_t *sa;
svn_error_t *err;
apr_getopt_t *os;
int opt;
serve_params_t params;
const char *arg;
apr_status_t status;
#ifndef WIN32
apr_proc_t proc;
#endif
svn_boolean_t is_multi_threaded;
enum connection_handling_mode handling_mode = CONNECTION_DEFAULT;
svn_boolean_t cache_fulltexts = TRUE;
svn_boolean_t cache_txdeltas = TRUE;
svn_boolean_t cache_revprops = FALSE;
svn_boolean_t use_block_read = FALSE;
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;
apr_size_t min_thread_count = THREADPOOL_MIN_SIZE;
apr_size_t max_thread_count = THREADPOOL_MAX_SIZE;
#ifdef SVN_HAVE_SASL
SVN_ERR(cyrus_init(pool));
#endif
SVN_ERR(check_lib_versions());
SVN_ERR(svn_fs_initialize(pool));
SVN_ERR(svn_cmdline__getopt_init(&os, argc, argv, pool));
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.logger = NULL;
params.config_pool = NULL;
params.authz_pool = NULL;
params.fs_config = NULL;
params.vhost = FALSE;
params.username_case = CASE_ASIS;
params.memory_cache_size = (apr_uint64_t)-1;
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);
*exit_code = EXIT_FAILURE;
return SVN_NO_ERROR;
}
switch (opt)
{
case '6':
#if APR_HAVE_IPV6
prefer_v6 = TRUE;
#endif
break;
case 'h':
help(pool);
return SVN_NO_ERROR;
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_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, err,
_("Invalid port '%s'"), arg);
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_ERR(svn_utf_cstring_to_utf8(¶ms.root, arg, pool));
SVN_ERR(svn_io_check_resolved_path(params.root, &kind, pool));
if (kind != svn_node_dir)
{
return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
_("Root path '%s' does not exist "
"or is not a directory"), params.root);
}
params.root = svn_dirent_internal_style(params.root, pool);
SVN_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:
cache_txdeltas = svn_tristate__from_word(arg) == svn_tristate_true;
break;
case SVNSERVE_OPT_CACHE_FULLTEXTS:
cache_fulltexts = svn_tristate__from_word(arg) == svn_tristate_true;
break;
case SVNSERVE_OPT_CACHE_REVPROPS:
cache_revprops = svn_tristate__from_word(arg) == svn_tristate_true;
break;
case SVNSERVE_OPT_BLOCK_READ:
use_block_read = 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);
if (bandwidth >= 1000)
{
params.zero_copy_limit = bandwidth * 120;
params.error_check_interval = bandwidth * 120;
}
}
break;
case SVNSERVE_OPT_MIN_THREADS:
min_thread_count = (apr_size_t)apr_strtoi64(arg, NULL, 0);
break;
case SVNSERVE_OPT_MAX_THREADS:
max_thread_count = (apr_size_t)apr_strtoi64(arg, NULL, 0);
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_ERR(svn_utf_cstring_to_utf8(&config_filename, arg, pool));
config_filename = svn_dirent_internal_style(config_filename, pool);
SVN_ERR(svn_dirent_get_absolute(&config_filename, config_filename,
pool));
break;
case SVNSERVE_OPT_PID_FILE:
SVN_ERR(svn_utf_cstring_to_utf8(&pid_filename, arg, pool));
pid_filename = svn_dirent_internal_style(pid_filename, pool);
SVN_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_ERR(svn_utf_cstring_to_utf8(&log_filename, arg, pool));
log_filename = svn_dirent_internal_style(log_filename, pool);
SVN_ERR(svn_dirent_get_absolute(&log_filename, log_filename, pool));
break;
}
}
if (is_version)
{
SVN_ERR(version(quiet, pool));
return SVN_NO_ERROR;
}
if (os->ind != argc)
{
usage(argv[0], pool);
*exit_code = EXIT_FAILURE;
return SVN_NO_ERROR;
}
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);
*exit_code = EXIT_FAILURE;
return SVN_NO_ERROR;
}
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);
*exit_code = EXIT_FAILURE;
return SVN_NO_ERROR;
}
is_multi_threaded = handling_mode == connection_mode_thread;
params.fs_config = apr_hash_make(pool);
svn_hash_sets(params.fs_config, SVN_FS_CONFIG_FSFS_CACHE_DELTAS,
cache_txdeltas ? "1" :"0");
svn_hash_sets(params.fs_config, SVN_FS_CONFIG_FSFS_CACHE_FULLTEXTS,
cache_fulltexts ? "1" :"0");
svn_hash_sets(params.fs_config, SVN_FS_CONFIG_FSFS_CACHE_REVPROPS,
cache_revprops ? "2" :"0");
svn_hash_sets(params.fs_config, SVN_FS_CONFIG_FSFS_BLOCK_READ,
use_block_read ? "1" :"0");
SVN_ERR(svn_repos__config_pool_create(¶ms.config_pool,
is_multi_threaded,
pool));
SVN_ERR(svn_repos__authz_pool_create(¶ms.authz_pool,
params.config_pool,
is_multi_threaded,
pool));
if (config_filename)
{
params.base = svn_dirent_dirname(config_filename, pool);
SVN_ERR(svn_repos__config_pool_get(¶ms.cfg, NULL,
params.config_pool,
config_filename,
TRUE,
FALSE,
NULL,
pool));
}
if (log_filename)
SVN_ERR(logger__create(¶ms.logger, log_filename, pool));
else if (run_mode == run_mode_listen_once)
SVN_ERR(logger__create_for_stderr(¶ms.logger, pool));
if (params.tunnel_user && run_mode != run_mode_tunnel)
{
return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
_("Option --tunnel-user is only valid in tunnel mode"));
}
if (run_mode == run_mode_inetd || run_mode == run_mode_tunnel)
{
apr_pool_t *connection_pool;
svn_ra_svn_conn_t *conn;
svn_stream_t *stdin_stream;
svn_stream_t *stdout_stream;
params.tunnel = (run_mode == run_mode_tunnel);
apr_pool_cleanup_register(pool, pool, apr_pool_cleanup_null,
redirect_stdout);
SVN_ERR(svn_stream_for_stdin(&stdin_stream, pool));
SVN_ERR(svn_stream_for_stdout(&stdout_stream, pool));
connection_pool = svn_pool_create(pool);
conn = svn_ra_svn_create_conn4(NULL, stdin_stream, stdout_stream,
params.compression_level,
params.zero_copy_limit,
params.error_check_interval,
connection_pool);
err = serve(conn, ¶ms, connection_pool);
svn_pool_destroy(connection_pool);
return err;
}
#ifdef WIN32
if (run_mode == run_mode_service)
{
err = winservice_start();
if (err)
{
svn_handle_error2(err, stderr, FALSE, "svnserve: ");
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_code = EXIT_FAILURE;
return SVN_NO_ERROR;
}
}
#endif
#if APR_HAVE_IPV6
#ifdef MAX_SECS_TO_LINGER
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)
{
return svn_error_wrap_apr(status, _("Can't get address info"));
}
#ifdef MAX_SECS_TO_LINGER
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)
{
return svn_error_wrap_apr(status, _("Can't create server socket"));
}
status = apr_socket_opt_set(sock, APR_SO_REUSEADDR, 1);
if (status)
{
return svn_error_wrap_apr(status, _("Can't set options on server socket"));
}
status = apr_socket_bind(sock, sa);
if (status)
{
return svn_error_wrap_apr(status, _("Can't bind server socket"));
}
status = apr_socket_listen(sock, ACCEPT_BACKLOG);
if (status)
{
return svn_error_wrap_apr(status, _("Can't listen on server socket"));
}
#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
apr_signal(SIGPIPE, SIG_IGN);
#endif
#ifdef SIGXFSZ
apr_signal(SIGXFSZ, SIG_IGN);
#endif
if (pid_filename)
SVN_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;
if (run_mode == run_mode_service)
winservice_running();
#endif
{
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
#endif
}
svn_cache_config_set(&settings);
}
#if APR_HAS_THREADS
SVN_ERR(svn_root_pools__create(&connection_pools));
if (handling_mode == connection_mode_thread)
{
if (max_thread_count < 1)
max_thread_count = 1;
if (min_thread_count > max_thread_count)
min_thread_count = max_thread_count;
status = apr_thread_pool_create(&threads,
min_thread_count,
max_thread_count,
pool);
if (status)
{
return svn_error_wrap_apr(status, _("Can't create thread pool"));
}
apr_thread_pool_idle_wait_set(threads, THREADPOOL_THREAD_IDLE_LIMIT);
apr_thread_pool_threshold_set(threads, 0);
}
else
{
threads = NULL;
}
#endif
while (1)
{
connection_t *connection = NULL;
SVN_ERR(accept_connection(&connection, sock, ¶ms, handling_mode,
pool));
if (run_mode == run_mode_listen_once)
{
err = serve_socket(connection, connection->pool);
close_connection(connection);
return err;
}
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);
svn_error_clear(serve_socket(connection, connection->pool));
close_connection(connection);
return SVN_NO_ERROR;
}
else if (status != APR_INPARENT)
{
err = svn_error_wrap_apr(status, "apr_proc_fork");
logger__log_error(params.logger, err, NULL, NULL);
svn_error_clear(err);
}
#endif
break;
case connection_mode_thread:
#if APR_HAS_THREADS
attach_connection(connection);
status = apr_thread_pool_push(threads, serve_thread, connection,
0, NULL);
if (status)
{
return svn_error_wrap_apr(status, _("Can't push task"));
}
#endif
break;
case connection_mode_single:
svn_error_clear(serve_socket(connection, connection->pool));
}
close_connection(connection);
}
}
int
main(int argc, const char *argv[])
{
apr_pool_t *pool;
int exit_code = EXIT_SUCCESS;
svn_error_t *err;
if (svn_cmdline_init("svnserve", stderr) != EXIT_SUCCESS)
return EXIT_FAILURE;
pool = apr_allocator_owner_get(svn_pool_create_allocator(TRUE));
err = sub_main(&exit_code, argc, argv, pool);
err = svn_error_compose_create(err, svn_cmdline_fflush(stdout));
if (err)
{
exit_code = EXIT_FAILURE;
svn_cmdline_handle_exit_error(err, NULL, "svnserve: ");
}
#if APR_HAS_THREADS
if (threads)
apr_thread_pool_destroy(threads);
#endif
svn_pool_destroy(pool);
return exit_code;
}