1251881Speter/* 2251881Speter * svnserve.c : Main control function for svnserve 3251881Speter * 4251881Speter * ==================================================================== 5251881Speter * Licensed to the Apache Software Foundation (ASF) under one 6251881Speter * or more contributor license agreements. See the NOTICE file 7251881Speter * distributed with this work for additional information 8251881Speter * regarding copyright ownership. The ASF licenses this file 9251881Speter * to you under the Apache License, Version 2.0 (the 10251881Speter * "License"); you may not use this file except in compliance 11251881Speter * with the License. You may obtain a copy of the License at 12251881Speter * 13251881Speter * http://www.apache.org/licenses/LICENSE-2.0 14251881Speter * 15251881Speter * Unless required by applicable law or agreed to in writing, 16251881Speter * software distributed under the License is distributed on an 17251881Speter * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 18251881Speter * KIND, either express or implied. See the License for the 19251881Speter * specific language governing permissions and limitations 20251881Speter * under the License. 21251881Speter * ==================================================================== 22251881Speter */ 23251881Speter 24251881Speter 25251881Speter 26251881Speter#define APR_WANT_STRFUNC 27251881Speter#include <apr_want.h> 28251881Speter#include <apr_general.h> 29251881Speter#include <apr_getopt.h> 30251881Speter#include <apr_network_io.h> 31251881Speter#include <apr_signal.h> 32251881Speter#include <apr_thread_proc.h> 33251881Speter#include <apr_portable.h> 34251881Speter 35251881Speter#include <locale.h> 36251881Speter 37251881Speter#include "svn_cmdline.h" 38251881Speter#include "svn_types.h" 39251881Speter#include "svn_pools.h" 40251881Speter#include "svn_error.h" 41251881Speter#include "svn_ra_svn.h" 42251881Speter#include "svn_utf.h" 43251881Speter#include "svn_dirent_uri.h" 44251881Speter#include "svn_path.h" 45251881Speter#include "svn_opt.h" 46251881Speter#include "svn_repos.h" 47251881Speter#include "svn_string.h" 48251881Speter#include "svn_cache_config.h" 49251881Speter#include "svn_version.h" 50251881Speter#include "svn_io.h" 51299742Sdim#include "svn_hash.h" 52251881Speter 53251881Speter#include "svn_private_config.h" 54251881Speter 55251881Speter#include "private/svn_dep_compat.h" 56251881Speter#include "private/svn_cmdline_private.h" 57251881Speter#include "private/svn_atomic.h" 58299742Sdim#include "private/svn_mutex.h" 59262253Speter#include "private/svn_subr_private.h" 60251881Speter 61299742Sdim#if APR_HAS_THREADS 62299742Sdim# include <apr_thread_pool.h> 63299742Sdim#endif 64299742Sdim 65251881Speter#include "winservice.h" 66251881Speter 67251881Speter#ifdef HAVE_UNISTD_H 68251881Speter#include <unistd.h> /* For getpid() */ 69251881Speter#endif 70251881Speter 71251881Speter#include "server.h" 72299742Sdim#include "logger.h" 73251881Speter 74251881Speter/* The strategy for handling incoming connections. Some of these may be 75251881Speter unavailable due to platform limitations. */ 76251881Speterenum connection_handling_mode { 77251881Speter connection_mode_fork, /* Create a process per connection */ 78251881Speter connection_mode_thread, /* Create a thread per connection */ 79251881Speter connection_mode_single /* One connection at a time in this process */ 80251881Speter}; 81251881Speter 82251881Speter/* The mode in which to run svnserve */ 83251881Speterenum run_mode { 84251881Speter run_mode_unspecified, 85251881Speter run_mode_inetd, 86251881Speter run_mode_daemon, 87251881Speter run_mode_tunnel, 88251881Speter run_mode_listen_once, 89251881Speter run_mode_service 90251881Speter}; 91251881Speter 92251881Speter#if APR_HAS_FORK 93251881Speter#if APR_HAS_THREADS 94251881Speter 95251881Speter#define CONNECTION_DEFAULT connection_mode_fork 96251881Speter#define CONNECTION_HAVE_THREAD_OPTION 97251881Speter 98251881Speter#else /* ! APR_HAS_THREADS */ 99251881Speter 100251881Speter#define CONNECTION_DEFAULT connection_mode_fork 101251881Speter 102251881Speter#endif /* ! APR_HAS_THREADS */ 103251881Speter#elif APR_HAS_THREADS /* and ! APR_HAS_FORK */ 104251881Speter 105251881Speter#define CONNECTION_DEFAULT connection_mode_thread 106251881Speter 107251881Speter#else /* ! APR_HAS_THREADS and ! APR_HAS_FORK */ 108251881Speter 109251881Speter#define CONNECTION_DEFAULT connection_mode_single 110251881Speter 111251881Speter#endif 112251881Speter 113299742Sdim/* Parameters for the worker thread pool used in threaded mode. */ 114251881Speter 115299742Sdim/* Have at least this many worker threads (even if there are no requests 116299742Sdim * to handle). 117299742Sdim * 118299742Sdim * A 0 value is legal but increases the latency for the next incoming 119299742Sdim * request. Higher values may be useful for servers that experience short 120299742Sdim * bursts of concurrent requests followed by longer idle periods. 121299742Sdim */ 122299742Sdim#define THREADPOOL_MIN_SIZE 1 123299742Sdim 124299742Sdim/* Maximum number of worker threads. If there are more concurrent requests 125299742Sdim * than worker threads, the extra requests get queued. 126299742Sdim * 127299742Sdim * Since very slow connections will hog a full thread for a potentially 128299742Sdim * long time before timing out, be sure to not set this limit too low. 129299742Sdim * 130299742Sdim * On the other hand, keep in mind that every thread will allocate up to 131299742Sdim * 4MB of unused RAM in the APR allocator of its root pool. 32 bit servers 132299742Sdim * must hence do with fewer threads. 133299742Sdim */ 134299742Sdim#if (APR_SIZEOF_VOIDP <= 4) 135299742Sdim#define THREADPOOL_MAX_SIZE 64 136299742Sdim#else 137299742Sdim#define THREADPOOL_MAX_SIZE 256 138299742Sdim#endif 139299742Sdim 140299742Sdim/* Number of microseconds that an unused thread remains in the pool before 141299742Sdim * being terminated. 142299742Sdim * 143299742Sdim * Higher values are useful if clients frequently send small requests and 144299742Sdim * you want to minimize the latency for those. 145299742Sdim */ 146299742Sdim#define THREADPOOL_THREAD_IDLE_LIMIT 1000000 147299742Sdim 148299742Sdim/* Number of client to server connections that may concurrently in the 149299742Sdim * TCP 3-way handshake state, i.e. are in the process of being created. 150299742Sdim * 151299742Sdim * Larger values improve scalability with lots of small requests coming 152299742Sdim * on over long latency networks. 153299742Sdim * 154299742Sdim * The OS may actually use a lower limit than specified here. 155299742Sdim */ 156299742Sdim#define ACCEPT_BACKLOG 128 157299742Sdim 158251881Speter#ifdef WIN32 159251881Speterstatic apr_os_sock_t winservice_svnserve_accept_socket = INVALID_SOCKET; 160251881Speter 161251881Speter/* The SCM calls this function (on an arbitrary thread, not the main() 162251881Speter thread!) when it wants to stop the service. 163251881Speter 164251881Speter For now, our strategy is to close the listener socket, in order to 165251881Speter unblock main() and cause it to exit its accept loop. We cannot use 166251881Speter apr_socket_close, because that function deletes the apr_socket_t 167251881Speter structure, as well as closing the socket handle. If we called 168251881Speter apr_socket_close here, then main() will also call apr_socket_close, 169251881Speter resulting in a double-free. This way, we just close the kernel 170251881Speter socket handle, which causes the accept() function call to fail, 171251881Speter which causes main() to clean up the socket. So, memory gets freed 172251881Speter only once. 173251881Speter 174251881Speter This isn't pretty, but it's better than a lot of other options. 175251881Speter Currently, there is no "right" way to shut down svnserve. 176251881Speter 177251881Speter We store the OS handle rather than a pointer to the apr_socket_t 178251881Speter structure in order to eliminate any possibility of illegal memory 179251881Speter access. */ 180251881Spetervoid winservice_notify_stop(void) 181251881Speter{ 182251881Speter if (winservice_svnserve_accept_socket != INVALID_SOCKET) 183251881Speter closesocket(winservice_svnserve_accept_socket); 184251881Speter} 185251881Speter#endif /* _WIN32 */ 186251881Speter 187251881Speter 188251881Speter/* Option codes and descriptions for svnserve. 189251881Speter * 190251881Speter * The entire list must be terminated with an entry of nulls. 191251881Speter * 192251881Speter * APR requires that options without abbreviations 193251881Speter * have codes greater than 255. 194251881Speter */ 195251881Speter#define SVNSERVE_OPT_LISTEN_PORT 256 196251881Speter#define SVNSERVE_OPT_LISTEN_HOST 257 197251881Speter#define SVNSERVE_OPT_FOREGROUND 258 198251881Speter#define SVNSERVE_OPT_TUNNEL_USER 259 199251881Speter#define SVNSERVE_OPT_VERSION 260 200251881Speter#define SVNSERVE_OPT_PID_FILE 261 201251881Speter#define SVNSERVE_OPT_SERVICE 262 202251881Speter#define SVNSERVE_OPT_CONFIG_FILE 263 203251881Speter#define SVNSERVE_OPT_LOG_FILE 264 204251881Speter#define SVNSERVE_OPT_CACHE_TXDELTAS 265 205251881Speter#define SVNSERVE_OPT_CACHE_FULLTEXTS 266 206251881Speter#define SVNSERVE_OPT_CACHE_REVPROPS 267 207251881Speter#define SVNSERVE_OPT_SINGLE_CONN 268 208251881Speter#define SVNSERVE_OPT_CLIENT_SPEED 269 209251881Speter#define SVNSERVE_OPT_VIRTUAL_HOST 270 210299742Sdim#define SVNSERVE_OPT_MIN_THREADS 271 211299742Sdim#define SVNSERVE_OPT_MAX_THREADS 272 212299742Sdim#define SVNSERVE_OPT_BLOCK_READ 273 213251881Speter 214251881Speterstatic const apr_getopt_option_t svnserve__options[] = 215251881Speter { 216251881Speter {"daemon", 'd', 0, N_("daemon mode")}, 217251881Speter {"inetd", 'i', 0, N_("inetd mode")}, 218251881Speter {"tunnel", 't', 0, N_("tunnel mode")}, 219251881Speter {"listen-once", 'X', 0, N_("listen-once mode (useful for debugging)")}, 220251881Speter#ifdef WIN32 221251881Speter {"service", SVNSERVE_OPT_SERVICE, 0, 222251881Speter N_("Windows service mode (Service Control Manager)")}, 223251881Speter#endif 224251881Speter {"root", 'r', 1, N_("root of directory to serve")}, 225251881Speter {"read-only", 'R', 0, 226251881Speter N_("force read only, overriding repository config file")}, 227251881Speter {"config-file", SVNSERVE_OPT_CONFIG_FILE, 1, 228251881Speter N_("read configuration from file ARG")}, 229251881Speter {"listen-port", SVNSERVE_OPT_LISTEN_PORT, 1, 230251881Speter#ifdef WIN32 231251881Speter N_("listen port. The default port is 3690.\n" 232251881Speter " " 233251881Speter "[mode: daemon, service, listen-once]")}, 234251881Speter#else 235251881Speter N_("listen port. The default port is 3690.\n" 236251881Speter " " 237251881Speter "[mode: daemon, listen-once]")}, 238251881Speter#endif 239251881Speter {"listen-host", SVNSERVE_OPT_LISTEN_HOST, 1, 240251881Speter#ifdef WIN32 241251881Speter N_("listen hostname or IP address\n" 242251881Speter " " 243251881Speter "By default svnserve listens on all addresses.\n" 244251881Speter " " 245251881Speter "[mode: daemon, service, listen-once]")}, 246251881Speter#else 247251881Speter N_("listen hostname or IP address\n" 248251881Speter " " 249251881Speter "By default svnserve listens on all addresses.\n" 250251881Speter " " 251251881Speter "[mode: daemon, listen-once]")}, 252251881Speter#endif 253251881Speter {"prefer-ipv6", '6', 0, 254251881Speter N_("prefer IPv6 when resolving the listen hostname\n" 255251881Speter " " 256251881Speter "[IPv4 is preferred by default. Using IPv4 and IPv6\n" 257251881Speter " " 258251881Speter "at the same time is not supported in daemon mode.\n" 259251881Speter " " 260251881Speter "Use inetd mode or tunnel mode if you need this.]")}, 261251881Speter {"compression", 'c', 1, 262251881Speter N_("compression level to use for network transmissions\n" 263251881Speter " " 264251881Speter "[0 .. no compression, 5 .. default, \n" 265251881Speter " " 266251881Speter " 9 .. maximum compression]")}, 267251881Speter {"memory-cache-size", 'M', 1, 268251881Speter N_("size of the extra in-memory cache in MB used to\n" 269251881Speter " " 270251881Speter "minimize redundant operations.\n" 271251881Speter " " 272262253Speter "Default is 16.\n" 273251881Speter " " 274299742Sdim "0 switches to dynamically sized caches.\n" 275299742Sdim " " 276299742Sdim "[used for FSFS and FSX repositories only]")}, 277251881Speter {"cache-txdeltas", SVNSERVE_OPT_CACHE_TXDELTAS, 1, 278251881Speter N_("enable or disable caching of deltas between older\n" 279251881Speter " " 280251881Speter "revisions.\n" 281251881Speter " " 282299742Sdim "Default is yes.\n" 283251881Speter " " 284299742Sdim "[used for FSFS and FSX repositories only]")}, 285251881Speter {"cache-fulltexts", SVNSERVE_OPT_CACHE_FULLTEXTS, 1, 286251881Speter N_("enable or disable caching of file contents\n" 287251881Speter " " 288251881Speter "Default is yes.\n" 289251881Speter " " 290299742Sdim "[used for FSFS and FSX repositories only]")}, 291251881Speter {"cache-revprops", SVNSERVE_OPT_CACHE_REVPROPS, 1, 292251881Speter N_("enable or disable caching of revision properties.\n" 293251881Speter " " 294251881Speter "Consult the documentation before activating this.\n" 295251881Speter " " 296251881Speter "Default is no.\n" 297251881Speter " " 298299742Sdim "[used for FSFS and FSX repositories only]")}, 299251881Speter {"client-speed", SVNSERVE_OPT_CLIENT_SPEED, 1, 300251881Speter N_("Optimize network handling based on the assumption\n" 301251881Speter " " 302251881Speter "that most clients are connected with a bitrate of\n" 303251881Speter " " 304251881Speter "ARG Mbit/s.\n" 305251881Speter " " 306251881Speter "Default is 0 (optimizations disabled).")}, 307299742Sdim {"block-read", SVNSERVE_OPT_BLOCK_READ, 1, 308299742Sdim N_("Parse and cache all data found in block instead\n" 309299742Sdim " " 310299742Sdim "of just the requested item.\n" 311299742Sdim " " 312299742Sdim "Default is no.\n" 313299742Sdim " " 314299742Sdim "[used for FSFS repositories in 1.9 format only]")}, 315251881Speter#ifdef CONNECTION_HAVE_THREAD_OPTION 316251881Speter /* ### Making the assumption here that WIN32 never has fork and so 317251881Speter * ### this option never exists when --service exists. */ 318251881Speter {"threads", 'T', 0, N_("use threads instead of fork " 319251881Speter "[mode: daemon]")}, 320299742Sdim {"min-threads", SVNSERVE_OPT_MIN_THREADS, 1, 321299742Sdim N_("Minimum number of server threads, even if idle.\n" 322299742Sdim " " 323299742Sdim "Capped to max-threads; minimum value is 0.\n" 324299742Sdim " " 325299742Sdim "Default is 1.\n" 326299742Sdim " " 327299742Sdim "[used only with --threads]")}, 328299742Sdim#if (APR_SIZEOF_VOIDP <= 4) 329299742Sdim {"max-threads", SVNSERVE_OPT_MAX_THREADS, 1, 330299742Sdim N_("Maximum number of server threads, even if there\n" 331299742Sdim " " 332299742Sdim "are more connections. Minimum value is 1.\n" 333299742Sdim " " 334299742Sdim "Default is 64.\n" 335299742Sdim " " 336299742Sdim "[used only with --threads]")}, 337299742Sdim#else 338299742Sdim {"max-threads", SVNSERVE_OPT_MAX_THREADS, 1, 339299742Sdim N_("Maximum number of server threads, even if there\n" 340299742Sdim " " 341299742Sdim "are more connections. Minimum value is 1.\n" 342299742Sdim " " 343299742Sdim "Default is 256.\n" 344299742Sdim " " 345299742Sdim "[used only with --threads]")}, 346251881Speter#endif 347299742Sdim#endif 348251881Speter {"foreground", SVNSERVE_OPT_FOREGROUND, 0, 349251881Speter N_("run in foreground (useful for debugging)\n" 350251881Speter " " 351251881Speter "[mode: daemon]")}, 352251881Speter {"single-thread", SVNSERVE_OPT_SINGLE_CONN, 0, 353299742Sdim N_("handle one connection at a time in the parent\n" 354251881Speter " " 355299742Sdim "process (useful for debugging)")}, 356251881Speter {"log-file", SVNSERVE_OPT_LOG_FILE, 1, 357251881Speter N_("svnserve log file")}, 358251881Speter {"pid-file", SVNSERVE_OPT_PID_FILE, 1, 359251881Speter#ifdef WIN32 360251881Speter N_("write server process ID to file ARG\n" 361251881Speter " " 362251881Speter "[mode: daemon, listen-once, service]")}, 363251881Speter#else 364251881Speter N_("write server process ID to file ARG\n" 365251881Speter " " 366251881Speter "[mode: daemon, listen-once]")}, 367251881Speter#endif 368251881Speter {"tunnel-user", SVNSERVE_OPT_TUNNEL_USER, 1, 369251881Speter N_("tunnel username (default is current uid's name)\n" 370251881Speter " " 371251881Speter "[mode: tunnel]")}, 372251881Speter {"help", 'h', 0, N_("display this help")}, 373251881Speter {"virtual-host", SVNSERVE_OPT_VIRTUAL_HOST, 0, 374251881Speter N_("virtual host mode (look for repo in directory\n" 375251881Speter " " 376251881Speter "of provided hostname)")}, 377251881Speter {"version", SVNSERVE_OPT_VERSION, 0, 378251881Speter N_("show program version information")}, 379251881Speter {"quiet", 'q', 0, 380251881Speter N_("no progress (only errors) to stderr")}, 381251881Speter {0, 0, 0, 0} 382251881Speter }; 383251881Speter 384251881Speterstatic void usage(const char *progname, apr_pool_t *pool) 385251881Speter{ 386251881Speter if (!progname) 387251881Speter progname = "svnserve"; 388251881Speter 389251881Speter svn_error_clear(svn_cmdline_fprintf(stderr, pool, 390251881Speter _("Type '%s --help' for usage.\n"), 391251881Speter progname)); 392251881Speter} 393251881Speter 394251881Speterstatic void help(apr_pool_t *pool) 395251881Speter{ 396251881Speter apr_size_t i; 397251881Speter 398251881Speter#ifdef WIN32 399251881Speter svn_error_clear(svn_cmdline_fputs(_("usage: svnserve [-d | -i | -t | -X " 400251881Speter "| --service] [options]\n" 401299742Sdim "Subversion repository server.\n" 402299742Sdim "Type 'svnserve --version' to see the " 403299742Sdim "program version.\n" 404251881Speter "\n" 405251881Speter "Valid options:\n"), 406299742Sdim stdout, pool)); 407251881Speter#else 408251881Speter svn_error_clear(svn_cmdline_fputs(_("usage: svnserve [-d | -i | -t | -X] " 409251881Speter "[options]\n" 410299742Sdim "Subversion repository server.\n" 411299742Sdim "Type 'svnserve --version' to see the " 412299742Sdim "program version.\n" 413251881Speter "\n" 414251881Speter "Valid options:\n"), 415299742Sdim stdout, pool)); 416251881Speter#endif 417251881Speter for (i = 0; svnserve__options[i].name && svnserve__options[i].optch; i++) 418251881Speter { 419251881Speter const char *optstr; 420251881Speter svn_opt_format_option(&optstr, svnserve__options + i, TRUE, pool); 421251881Speter svn_error_clear(svn_cmdline_fprintf(stdout, pool, " %s\n", optstr)); 422251881Speter } 423251881Speter svn_error_clear(svn_cmdline_fprintf(stdout, pool, "\n")); 424251881Speter} 425251881Speter 426251881Speterstatic svn_error_t * version(svn_boolean_t quiet, apr_pool_t *pool) 427251881Speter{ 428251881Speter const char *fs_desc_start 429251881Speter = _("The following repository back-end (FS) modules are available:\n\n"); 430251881Speter 431251881Speter svn_stringbuf_t *version_footer; 432251881Speter 433251881Speter version_footer = svn_stringbuf_create(fs_desc_start, pool); 434251881Speter SVN_ERR(svn_fs_print_modules(version_footer, pool)); 435251881Speter 436251881Speter#ifdef SVN_HAVE_SASL 437251881Speter svn_stringbuf_appendcstr(version_footer, 438251881Speter _("\nCyrus SASL authentication is available.\n")); 439251881Speter#endif 440251881Speter 441251881Speter return svn_opt_print_help4(NULL, "svnserve", TRUE, quiet, FALSE, 442251881Speter version_footer->data, 443251881Speter NULL, NULL, NULL, NULL, NULL, pool); 444251881Speter} 445251881Speter 446251881Speter 447251881Speter#if APR_HAS_FORK 448251881Speterstatic void sigchld_handler(int signo) 449251881Speter{ 450251881Speter /* Nothing to do; we just need to interrupt the accept(). */ 451251881Speter} 452251881Speter#endif 453251881Speter 454251881Speter/* Redirect stdout to stderr. ARG is the pool. 455251881Speter * 456251881Speter * In tunnel or inetd mode, we don't want hook scripts corrupting the 457251881Speter * data stream by sending data to stdout, so we need to redirect 458251881Speter * stdout somewhere else. Sending it to stderr is acceptable; sending 459251881Speter * it to /dev/null is another option, but apr doesn't provide a way to 460251881Speter * do that without also detaching from the controlling terminal. 461251881Speter */ 462251881Speterstatic apr_status_t redirect_stdout(void *arg) 463251881Speter{ 464251881Speter apr_pool_t *pool = arg; 465251881Speter apr_file_t *out_file, *err_file; 466251881Speter apr_status_t apr_err; 467251881Speter 468251881Speter if ((apr_err = apr_file_open_stdout(&out_file, pool))) 469251881Speter return apr_err; 470251881Speter if ((apr_err = apr_file_open_stderr(&err_file, pool))) 471251881Speter return apr_err; 472251881Speter return apr_file_dup2(out_file, err_file, pool); 473251881Speter} 474251881Speter 475299742Sdim/* Wait for the next client connection to come in from SOCK. Allocate 476299742Sdim * the connection in a root pool from CONNECTION_POOLS and assign PARAMS. 477299742Sdim * Return the connection object in *CONNECTION. 478299742Sdim * 479299742Sdim * Use HANDLING_MODE for proper internal cleanup. 480299742Sdim */ 481299742Sdimstatic svn_error_t * 482299742Sdimaccept_connection(connection_t **connection, 483299742Sdim apr_socket_t *sock, 484299742Sdim serve_params_t *params, 485299742Sdim enum connection_handling_mode handling_mode, 486299742Sdim apr_pool_t *pool) 487299742Sdim{ 488299742Sdim apr_status_t status; 489251881Speter 490299742Sdim /* Non-standard pool handling. The main thread never blocks to join 491299742Sdim * the connection threads so it cannot clean up after each one. So 492299742Sdim * separate pools that can be cleared at thread exit are used. */ 493251881Speter 494299742Sdim apr_pool_t *connection_pool = svn_pool_create(pool); 495299742Sdim *connection = apr_pcalloc(connection_pool, sizeof(**connection)); 496299742Sdim (*connection)->pool = connection_pool; 497299742Sdim (*connection)->params = params; 498299742Sdim (*connection)->ref_count = 1; 499251881Speter 500299742Sdim do 501299742Sdim { 502299742Sdim #ifdef WIN32 503299742Sdim if (winservice_is_stopping()) 504299742Sdim exit(0); 505299742Sdim #endif 506251881Speter 507299742Sdim status = apr_socket_accept(&(*connection)->usock, sock, 508299742Sdim connection_pool); 509299742Sdim if (handling_mode == connection_mode_fork) 510299742Sdim { 511299742Sdim apr_proc_t proc; 512251881Speter 513299742Sdim /* Collect any zombie child processes. */ 514299742Sdim while (apr_proc_wait_all_procs(&proc, NULL, NULL, APR_NOWAIT, 515299742Sdim connection_pool) == APR_CHILD_DONE) 516299742Sdim ; 517299742Sdim } 518299742Sdim } 519299742Sdim while (APR_STATUS_IS_EINTR(status) 520299742Sdim || APR_STATUS_IS_ECONNABORTED(status) 521299742Sdim || APR_STATUS_IS_ECONNRESET(status)); 522299742Sdim 523299742Sdim return status 524299742Sdim ? svn_error_wrap_apr(status, _("Can't accept client connection")) 525299742Sdim : SVN_NO_ERROR; 526251881Speter} 527251881Speter 528299742Sdim/* Add a reference to CONNECTION, i.e. keep it and it's pool valid unless 529299742Sdim * that reference gets released using release_shared_pool(). 530299742Sdim */ 531251881Speterstatic void 532299742Sdimattach_connection(connection_t *connection) 533251881Speter{ 534299742Sdim svn_atomic_inc(&connection->ref_count); 535251881Speter} 536251881Speter 537299742Sdim/* Release a reference to CONNECTION. If there are no more references, 538299742Sdim * the connection will be 539299742Sdim */ 540299742Sdimstatic void 541299742Sdimclose_connection(connection_t *connection) 542299742Sdim{ 543299742Sdim /* this will automatically close USOCK */ 544299742Sdim if (svn_atomic_dec(&connection->ref_count) == 0) 545299742Sdim svn_pool_destroy(connection->pool); 546299742Sdim} 547251881Speter 548299742Sdim/* Wrapper around serve() that takes a socket instead of a connection. 549299742Sdim * This is to off-load work from the main thread in threaded and fork modes. 550299742Sdim * 551299742Sdim * If an error occurs, log it and also return it. 552299742Sdim */ 553299742Sdimstatic svn_error_t * 554299742Sdimserve_socket(connection_t *connection, 555299742Sdim apr_pool_t *pool) 556299742Sdim{ 557299742Sdim /* process the actual request and log errors */ 558299742Sdim svn_error_t *err = serve_interruptable(NULL, connection, NULL, pool); 559299742Sdim if (err) 560299742Sdim logger__log_error(connection->params->logger, err, NULL, 561299742Sdim get_client_info(connection->conn, connection->params, 562299742Sdim pool)); 563299742Sdim 564299742Sdim return svn_error_trace(err); 565299742Sdim} 566299742Sdim 567251881Speter#if APR_HAS_THREADS 568299742Sdim 569299742Sdim/* allocate and recycle root pools for connection objects. 570299742Sdim There should be at most THREADPOOL_MAX_SIZE such pools. */ 571299742Sdimstatic svn_root_pools__t *connection_pools; 572299742Sdim 573299742Sdim/* The global thread pool serving all connections. */ 574299742Sdimstatic apr_thread_pool_t *threads; 575299742Sdim 576299742Sdim/* Very simple load determination callback for serve_interruptable: 577299742Sdim With less than half the threads in THREADS in use, we can afford to 578299742Sdim wait in the socket read() function. Otherwise, poll them round-robin. */ 579299742Sdimstatic svn_boolean_t 580299742Sdimis_busy(connection_t *connection) 581299742Sdim{ 582299742Sdim return apr_thread_pool_threads_count(threads) * 2 583299742Sdim > apr_thread_pool_thread_max_get(threads); 584299742Sdim} 585299742Sdim 586299742Sdim/* Serve the connection given by DATA. Under high load, serve only 587299742Sdim the current command (if any) and then put the connection back into 588299742Sdim THREAD's task pool. */ 589251881Speterstatic void * APR_THREAD_FUNC serve_thread(apr_thread_t *tid, void *data) 590251881Speter{ 591299742Sdim svn_boolean_t done; 592299742Sdim connection_t *connection = data; 593299742Sdim svn_error_t *err; 594251881Speter 595299742Sdim apr_pool_t *pool = svn_root_pools__acquire_pool(connection_pools); 596251881Speter 597299742Sdim /* process the actual request and log errors */ 598299742Sdim err = serve_interruptable(&done, connection, is_busy, pool); 599299742Sdim if (err) 600299742Sdim { 601299742Sdim logger__log_error(connection->params->logger, err, NULL, 602299742Sdim get_client_info(connection->conn, connection->params, 603299742Sdim pool)); 604299742Sdim svn_error_clear(err); 605299742Sdim done = TRUE; 606299742Sdim } 607299742Sdim svn_root_pools__release_pool(pool, connection_pools); 608299742Sdim 609299742Sdim /* Close or re-schedule connection. */ 610299742Sdim if (done) 611299742Sdim close_connection(connection); 612299742Sdim else 613299742Sdim apr_thread_pool_push(threads, serve_thread, connection, 0, NULL); 614299742Sdim 615251881Speter return NULL; 616251881Speter} 617299742Sdim 618251881Speter#endif 619251881Speter 620251881Speter/* Write the PID of the current process as a decimal number, followed by a 621251881Speter newline to the file FILENAME, using POOL for temporary allocations. */ 622251881Speterstatic svn_error_t *write_pid_file(const char *filename, apr_pool_t *pool) 623251881Speter{ 624251881Speter apr_file_t *file; 625251881Speter const char *contents = apr_psprintf(pool, "%" APR_PID_T_FMT "\n", 626251881Speter getpid()); 627251881Speter 628262253Speter SVN_ERR(svn_io_remove_file2(filename, TRUE, pool)); 629251881Speter SVN_ERR(svn_io_file_open(&file, filename, 630262253Speter APR_WRITE | APR_CREATE | APR_EXCL, 631251881Speter APR_OS_DEFAULT, pool)); 632251881Speter SVN_ERR(svn_io_file_write_full(file, contents, strlen(contents), NULL, 633251881Speter pool)); 634251881Speter 635251881Speter SVN_ERR(svn_io_file_close(file, pool)); 636251881Speter 637251881Speter return SVN_NO_ERROR; 638251881Speter} 639251881Speter 640251881Speter/* Version compatibility check */ 641251881Speterstatic svn_error_t * 642251881Spetercheck_lib_versions(void) 643251881Speter{ 644251881Speter static const svn_version_checklist_t checklist[] = 645251881Speter { 646251881Speter { "svn_subr", svn_subr_version }, 647251881Speter { "svn_repos", svn_repos_version }, 648251881Speter { "svn_fs", svn_fs_version }, 649251881Speter { "svn_delta", svn_delta_version }, 650251881Speter { "svn_ra_svn", svn_ra_svn_version }, 651251881Speter { NULL, NULL } 652251881Speter }; 653251881Speter SVN_VERSION_DEFINE(my_version); 654251881Speter 655262253Speter return svn_ver_check_list2(&my_version, checklist, svn_ver_equal); 656251881Speter} 657251881Speter 658251881Speter 659299742Sdim/* 660299742Sdim * On success, leave *EXIT_CODE untouched and return SVN_NO_ERROR. On error, 661299742Sdim * either return an error to be displayed, or set *EXIT_CODE to non-zero and 662299742Sdim * return SVN_NO_ERROR. 663299742Sdim */ 664299742Sdimstatic svn_error_t * 665299742Sdimsub_main(int *exit_code, int argc, const char *argv[], apr_pool_t *pool) 666251881Speter{ 667251881Speter enum run_mode run_mode = run_mode_unspecified; 668251881Speter svn_boolean_t foreground = FALSE; 669299742Sdim apr_socket_t *sock; 670251881Speter apr_sockaddr_t *sa; 671251881Speter svn_error_t *err; 672251881Speter apr_getopt_t *os; 673251881Speter int opt; 674251881Speter serve_params_t params; 675251881Speter const char *arg; 676251881Speter apr_status_t status; 677299742Sdim#ifndef WIN32 678251881Speter apr_proc_t proc; 679251881Speter#endif 680299742Sdim svn_boolean_t is_multi_threaded; 681251881Speter enum connection_handling_mode handling_mode = CONNECTION_DEFAULT; 682299742Sdim svn_boolean_t cache_fulltexts = TRUE; 683299742Sdim svn_boolean_t cache_txdeltas = TRUE; 684299742Sdim svn_boolean_t cache_revprops = FALSE; 685299742Sdim svn_boolean_t use_block_read = FALSE; 686251881Speter apr_uint16_t port = SVN_RA_SVN_PORT; 687251881Speter const char *host = NULL; 688251881Speter int family = APR_INET; 689251881Speter apr_int32_t sockaddr_info_flags = 0; 690251881Speter#if APR_HAVE_IPV6 691251881Speter svn_boolean_t prefer_v6 = FALSE; 692251881Speter#endif 693251881Speter svn_boolean_t quiet = FALSE; 694251881Speter svn_boolean_t is_version = FALSE; 695251881Speter int mode_opt_count = 0; 696251881Speter int handling_opt_count = 0; 697251881Speter const char *config_filename = NULL; 698251881Speter const char *pid_filename = NULL; 699251881Speter const char *log_filename = NULL; 700251881Speter svn_node_kind_t kind; 701299742Sdim apr_size_t min_thread_count = THREADPOOL_MIN_SIZE; 702299742Sdim apr_size_t max_thread_count = THREADPOOL_MAX_SIZE; 703251881Speter#ifdef SVN_HAVE_SASL 704299742Sdim SVN_ERR(cyrus_init(pool)); 705251881Speter#endif 706251881Speter 707251881Speter /* Check library versions */ 708299742Sdim SVN_ERR(check_lib_versions()); 709251881Speter 710251881Speter /* Initialize the FS library. */ 711299742Sdim SVN_ERR(svn_fs_initialize(pool)); 712251881Speter 713299742Sdim SVN_ERR(svn_cmdline__getopt_init(&os, argc, argv, pool)); 714251881Speter 715251881Speter params.root = "/"; 716251881Speter params.tunnel = FALSE; 717251881Speter params.tunnel_user = NULL; 718251881Speter params.read_only = FALSE; 719251881Speter params.base = NULL; 720251881Speter params.cfg = NULL; 721251881Speter params.compression_level = SVN_DELTA_COMPRESSION_LEVEL_DEFAULT; 722299742Sdim params.logger = NULL; 723299742Sdim params.config_pool = NULL; 724299742Sdim params.authz_pool = NULL; 725299742Sdim params.fs_config = NULL; 726251881Speter params.vhost = FALSE; 727251881Speter params.username_case = CASE_ASIS; 728251881Speter params.memory_cache_size = (apr_uint64_t)-1; 729251881Speter params.zero_copy_limit = 0; 730251881Speter params.error_check_interval = 4096; 731251881Speter 732251881Speter while (1) 733251881Speter { 734251881Speter status = apr_getopt_long(os, svnserve__options, &opt, &arg); 735251881Speter if (APR_STATUS_IS_EOF(status)) 736251881Speter break; 737251881Speter if (status != APR_SUCCESS) 738299742Sdim { 739299742Sdim usage(argv[0], pool); 740299742Sdim *exit_code = EXIT_FAILURE; 741299742Sdim return SVN_NO_ERROR; 742299742Sdim } 743251881Speter switch (opt) 744251881Speter { 745251881Speter case '6': 746251881Speter#if APR_HAVE_IPV6 747251881Speter prefer_v6 = TRUE; 748251881Speter#endif 749251881Speter /* ### Maybe error here if we don't have IPV6 support? */ 750251881Speter break; 751251881Speter 752251881Speter case 'h': 753251881Speter help(pool); 754299742Sdim return SVN_NO_ERROR; 755251881Speter 756251881Speter case 'q': 757251881Speter quiet = TRUE; 758251881Speter break; 759251881Speter 760251881Speter case SVNSERVE_OPT_VERSION: 761251881Speter is_version = TRUE; 762251881Speter break; 763251881Speter 764251881Speter case 'd': 765251881Speter if (run_mode != run_mode_daemon) 766251881Speter { 767251881Speter run_mode = run_mode_daemon; 768251881Speter mode_opt_count++; 769251881Speter } 770251881Speter break; 771251881Speter 772251881Speter case SVNSERVE_OPT_FOREGROUND: 773251881Speter foreground = TRUE; 774251881Speter break; 775251881Speter 776251881Speter case SVNSERVE_OPT_SINGLE_CONN: 777251881Speter handling_mode = connection_mode_single; 778251881Speter handling_opt_count++; 779251881Speter break; 780251881Speter 781251881Speter case 'i': 782251881Speter if (run_mode != run_mode_inetd) 783251881Speter { 784251881Speter run_mode = run_mode_inetd; 785251881Speter mode_opt_count++; 786251881Speter } 787251881Speter break; 788251881Speter 789251881Speter case SVNSERVE_OPT_LISTEN_PORT: 790251881Speter { 791251881Speter apr_uint64_t val; 792251881Speter 793251881Speter err = svn_cstring_strtoui64(&val, arg, 0, APR_UINT16_MAX, 10); 794251881Speter if (err) 795299742Sdim return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, err, 796299742Sdim _("Invalid port '%s'"), arg); 797251881Speter port = (apr_uint16_t)val; 798251881Speter } 799251881Speter break; 800251881Speter 801251881Speter case SVNSERVE_OPT_LISTEN_HOST: 802251881Speter host = arg; 803251881Speter break; 804251881Speter 805251881Speter case 't': 806251881Speter if (run_mode != run_mode_tunnel) 807251881Speter { 808251881Speter run_mode = run_mode_tunnel; 809251881Speter mode_opt_count++; 810251881Speter } 811251881Speter break; 812251881Speter 813251881Speter case SVNSERVE_OPT_TUNNEL_USER: 814251881Speter params.tunnel_user = arg; 815251881Speter break; 816251881Speter 817251881Speter case 'X': 818251881Speter if (run_mode != run_mode_listen_once) 819251881Speter { 820251881Speter run_mode = run_mode_listen_once; 821251881Speter mode_opt_count++; 822251881Speter } 823251881Speter break; 824251881Speter 825251881Speter case 'r': 826299742Sdim SVN_ERR(svn_utf_cstring_to_utf8(¶ms.root, arg, pool)); 827251881Speter 828299742Sdim SVN_ERR(svn_io_check_resolved_path(params.root, &kind, pool)); 829251881Speter if (kind != svn_node_dir) 830251881Speter { 831299742Sdim return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL, 832299742Sdim _("Root path '%s' does not exist " 833299742Sdim "or is not a directory"), params.root); 834251881Speter } 835251881Speter 836251881Speter params.root = svn_dirent_internal_style(params.root, pool); 837299742Sdim SVN_ERR(svn_dirent_get_absolute(¶ms.root, params.root, pool)); 838251881Speter break; 839251881Speter 840251881Speter case 'R': 841251881Speter params.read_only = TRUE; 842251881Speter break; 843251881Speter 844251881Speter case 'T': 845251881Speter handling_mode = connection_mode_thread; 846251881Speter handling_opt_count++; 847251881Speter break; 848251881Speter 849251881Speter case 'c': 850251881Speter params.compression_level = atoi(arg); 851251881Speter if (params.compression_level < SVN_DELTA_COMPRESSION_LEVEL_NONE) 852251881Speter params.compression_level = SVN_DELTA_COMPRESSION_LEVEL_NONE; 853251881Speter if (params.compression_level > SVN_DELTA_COMPRESSION_LEVEL_MAX) 854251881Speter params.compression_level = SVN_DELTA_COMPRESSION_LEVEL_MAX; 855251881Speter break; 856251881Speter 857251881Speter case 'M': 858251881Speter params.memory_cache_size = 0x100000 * apr_strtoi64(arg, NULL, 0); 859251881Speter break; 860251881Speter 861251881Speter case SVNSERVE_OPT_CACHE_TXDELTAS: 862299742Sdim cache_txdeltas = svn_tristate__from_word(arg) == svn_tristate_true; 863251881Speter break; 864251881Speter 865251881Speter case SVNSERVE_OPT_CACHE_FULLTEXTS: 866299742Sdim cache_fulltexts = svn_tristate__from_word(arg) == svn_tristate_true; 867251881Speter break; 868251881Speter 869251881Speter case SVNSERVE_OPT_CACHE_REVPROPS: 870299742Sdim cache_revprops = svn_tristate__from_word(arg) == svn_tristate_true; 871251881Speter break; 872251881Speter 873299742Sdim case SVNSERVE_OPT_BLOCK_READ: 874299742Sdim use_block_read = svn_tristate__from_word(arg) == svn_tristate_true; 875299742Sdim break; 876299742Sdim 877251881Speter case SVNSERVE_OPT_CLIENT_SPEED: 878251881Speter { 879251881Speter apr_size_t bandwidth = (apr_size_t)apr_strtoi64(arg, NULL, 0); 880251881Speter 881251881Speter /* for slower clients, don't try anything fancy */ 882251881Speter if (bandwidth >= 1000) 883251881Speter { 884251881Speter /* block other clients for at most 1 ms (at full bandwidth). 885251881Speter Note that the send buffer is 16kB anyways. */ 886251881Speter params.zero_copy_limit = bandwidth * 120; 887251881Speter 888251881Speter /* check for aborted connections at the same rate */ 889251881Speter params.error_check_interval = bandwidth * 120; 890251881Speter } 891251881Speter } 892251881Speter break; 893251881Speter 894299742Sdim case SVNSERVE_OPT_MIN_THREADS: 895299742Sdim min_thread_count = (apr_size_t)apr_strtoi64(arg, NULL, 0); 896299742Sdim break; 897299742Sdim 898299742Sdim case SVNSERVE_OPT_MAX_THREADS: 899299742Sdim max_thread_count = (apr_size_t)apr_strtoi64(arg, NULL, 0); 900299742Sdim break; 901299742Sdim 902251881Speter#ifdef WIN32 903251881Speter case SVNSERVE_OPT_SERVICE: 904251881Speter if (run_mode != run_mode_service) 905251881Speter { 906251881Speter run_mode = run_mode_service; 907251881Speter mode_opt_count++; 908251881Speter } 909251881Speter break; 910251881Speter#endif 911251881Speter 912251881Speter case SVNSERVE_OPT_CONFIG_FILE: 913299742Sdim SVN_ERR(svn_utf_cstring_to_utf8(&config_filename, arg, pool)); 914251881Speter config_filename = svn_dirent_internal_style(config_filename, pool); 915299742Sdim SVN_ERR(svn_dirent_get_absolute(&config_filename, config_filename, 916299742Sdim pool)); 917251881Speter break; 918251881Speter 919251881Speter case SVNSERVE_OPT_PID_FILE: 920299742Sdim SVN_ERR(svn_utf_cstring_to_utf8(&pid_filename, arg, pool)); 921251881Speter pid_filename = svn_dirent_internal_style(pid_filename, pool); 922299742Sdim SVN_ERR(svn_dirent_get_absolute(&pid_filename, pid_filename, pool)); 923251881Speter break; 924251881Speter 925251881Speter case SVNSERVE_OPT_VIRTUAL_HOST: 926251881Speter params.vhost = TRUE; 927251881Speter break; 928251881Speter 929251881Speter case SVNSERVE_OPT_LOG_FILE: 930299742Sdim SVN_ERR(svn_utf_cstring_to_utf8(&log_filename, arg, pool)); 931251881Speter log_filename = svn_dirent_internal_style(log_filename, pool); 932299742Sdim SVN_ERR(svn_dirent_get_absolute(&log_filename, log_filename, pool)); 933251881Speter break; 934251881Speter 935251881Speter } 936251881Speter } 937251881Speter 938251881Speter if (is_version) 939251881Speter { 940299742Sdim SVN_ERR(version(quiet, pool)); 941299742Sdim return SVN_NO_ERROR; 942251881Speter } 943251881Speter 944251881Speter if (os->ind != argc) 945299742Sdim { 946299742Sdim usage(argv[0], pool); 947299742Sdim *exit_code = EXIT_FAILURE; 948299742Sdim return SVN_NO_ERROR; 949299742Sdim } 950251881Speter 951251881Speter if (mode_opt_count != 1) 952251881Speter { 953251881Speter svn_error_clear(svn_cmdline_fputs( 954251881Speter#ifdef WIN32 955251881Speter _("You must specify exactly one of -d, -i, -t, " 956251881Speter "--service or -X.\n"), 957251881Speter#else 958251881Speter _("You must specify exactly one of -d, -i, -t or -X.\n"), 959251881Speter#endif 960251881Speter stderr, pool)); 961251881Speter usage(argv[0], pool); 962299742Sdim *exit_code = EXIT_FAILURE; 963299742Sdim return SVN_NO_ERROR; 964251881Speter } 965251881Speter 966251881Speter if (handling_opt_count > 1) 967251881Speter { 968251881Speter svn_error_clear(svn_cmdline_fputs( 969251881Speter _("You may only specify one of -T or --single-thread\n"), 970251881Speter stderr, pool)); 971251881Speter usage(argv[0], pool); 972299742Sdim *exit_code = EXIT_FAILURE; 973299742Sdim return SVN_NO_ERROR; 974251881Speter } 975251881Speter 976299742Sdim /* construct object pools */ 977299742Sdim is_multi_threaded = handling_mode == connection_mode_thread; 978299742Sdim params.fs_config = apr_hash_make(pool); 979299742Sdim svn_hash_sets(params.fs_config, SVN_FS_CONFIG_FSFS_CACHE_DELTAS, 980299742Sdim cache_txdeltas ? "1" :"0"); 981299742Sdim svn_hash_sets(params.fs_config, SVN_FS_CONFIG_FSFS_CACHE_FULLTEXTS, 982299742Sdim cache_fulltexts ? "1" :"0"); 983299742Sdim svn_hash_sets(params.fs_config, SVN_FS_CONFIG_FSFS_CACHE_REVPROPS, 984299742Sdim cache_revprops ? "2" :"0"); 985299742Sdim svn_hash_sets(params.fs_config, SVN_FS_CONFIG_FSFS_BLOCK_READ, 986299742Sdim use_block_read ? "1" :"0"); 987299742Sdim 988299742Sdim SVN_ERR(svn_repos__config_pool_create(¶ms.config_pool, 989299742Sdim is_multi_threaded, 990299742Sdim pool)); 991299742Sdim SVN_ERR(svn_repos__authz_pool_create(¶ms.authz_pool, 992299742Sdim params.config_pool, 993299742Sdim is_multi_threaded, 994299742Sdim pool)); 995299742Sdim 996251881Speter /* If a configuration file is specified, load it and any referenced 997251881Speter * password and authorization files. */ 998251881Speter if (config_filename) 999251881Speter { 1000251881Speter params.base = svn_dirent_dirname(config_filename, pool); 1001251881Speter 1002299742Sdim SVN_ERR(svn_repos__config_pool_get(¶ms.cfg, NULL, 1003299742Sdim params.config_pool, 1004299742Sdim config_filename, 1005299742Sdim TRUE, /* must_exist */ 1006299742Sdim FALSE, /* names_case_sensitive */ 1007299742Sdim NULL, 1008299742Sdim pool)); 1009251881Speter } 1010251881Speter 1011251881Speter if (log_filename) 1012299742Sdim SVN_ERR(logger__create(¶ms.logger, log_filename, pool)); 1013299742Sdim else if (run_mode == run_mode_listen_once) 1014299742Sdim SVN_ERR(logger__create_for_stderr(¶ms.logger, pool)); 1015251881Speter 1016251881Speter if (params.tunnel_user && run_mode != run_mode_tunnel) 1017251881Speter { 1018299742Sdim return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 1019299742Sdim _("Option --tunnel-user is only valid in tunnel mode")); 1020251881Speter } 1021251881Speter 1022251881Speter if (run_mode == run_mode_inetd || run_mode == run_mode_tunnel) 1023251881Speter { 1024299742Sdim apr_pool_t *connection_pool; 1025299742Sdim svn_ra_svn_conn_t *conn; 1026299742Sdim svn_stream_t *stdin_stream; 1027299742Sdim svn_stream_t *stdout_stream; 1028299742Sdim 1029251881Speter params.tunnel = (run_mode == run_mode_tunnel); 1030251881Speter apr_pool_cleanup_register(pool, pool, apr_pool_cleanup_null, 1031251881Speter redirect_stdout); 1032251881Speter 1033299742Sdim SVN_ERR(svn_stream_for_stdin(&stdin_stream, pool)); 1034299742Sdim SVN_ERR(svn_stream_for_stdout(&stdout_stream, pool)); 1035251881Speter 1036251881Speter /* Use a subpool for the connection to ensure that if SASL is used 1037251881Speter * the pool cleanup handlers that call sasl_dispose() (connection_pool) 1038251881Speter * and sasl_done() (pool) are run in the right order. See issue #3664. */ 1039251881Speter connection_pool = svn_pool_create(pool); 1040299742Sdim conn = svn_ra_svn_create_conn4(NULL, stdin_stream, stdout_stream, 1041251881Speter params.compression_level, 1042251881Speter params.zero_copy_limit, 1043251881Speter params.error_check_interval, 1044251881Speter connection_pool); 1045299742Sdim err = serve(conn, ¶ms, connection_pool); 1046299742Sdim svn_pool_destroy(connection_pool); 1047299742Sdim 1048299742Sdim return err; 1049251881Speter } 1050251881Speter 1051251881Speter#ifdef WIN32 1052251881Speter /* If svnserve needs to run as a Win32 service, then we need to 1053251881Speter coordinate with the Service Control Manager (SCM) before 1054251881Speter continuing. This function call registers the svnserve.exe 1055251881Speter process with the SCM, waits for the "start" command from the SCM 1056251881Speter (which will come very quickly), and confirms that those steps 1057251881Speter succeeded. 1058251881Speter 1059251881Speter After this call succeeds, the service is free to run. At some 1060251881Speter point in the future, the SCM will send a message to the service, 1061251881Speter requesting that it stop. This is translated into a call to 1062251881Speter winservice_notify_stop(). The service is then responsible for 1063251881Speter cleanly terminating. 1064251881Speter 1065251881Speter We need to do this before actually starting the service logic 1066251881Speter (opening files, sockets, etc.) because the SCM wants you to 1067251881Speter connect *first*, then do your service-specific logic. If the 1068251881Speter service process takes too long to connect to the SCM, then the 1069251881Speter SCM will decide that the service is busted, and will give up on 1070251881Speter it. 1071251881Speter */ 1072251881Speter if (run_mode == run_mode_service) 1073251881Speter { 1074251881Speter err = winservice_start(); 1075251881Speter if (err) 1076251881Speter { 1077251881Speter svn_handle_error2(err, stderr, FALSE, "svnserve: "); 1078251881Speter 1079251881Speter /* This is the most common error. It means the user started 1080251881Speter svnserve from a shell, and specified the --service 1081251881Speter argument. svnserve cannot be started, as a service, in 1082251881Speter this way. The --service argument is valid only valid if 1083251881Speter svnserve is started by the SCM. */ 1084251881Speter if (err->apr_err == 1085251881Speter APR_FROM_OS_ERROR(ERROR_FAILED_SERVICE_CONTROLLER_CONNECT)) 1086251881Speter { 1087251881Speter svn_error_clear(svn_cmdline_fprintf(stderr, pool, 1088251881Speter _("svnserve: The --service flag is only valid if the" 1089251881Speter " process is started by the Service Control Manager.\n"))); 1090251881Speter } 1091251881Speter 1092251881Speter svn_error_clear(err); 1093299742Sdim *exit_code = EXIT_FAILURE; 1094299742Sdim return SVN_NO_ERROR; 1095251881Speter } 1096251881Speter 1097251881Speter /* The service is now in the "starting" state. Before the SCM will 1098251881Speter consider the service "started", this thread must call the 1099251881Speter winservice_running() function. */ 1100251881Speter } 1101251881Speter#endif /* WIN32 */ 1102251881Speter 1103251881Speter /* Make sure we have IPV6 support first before giving apr_sockaddr_info_get 1104251881Speter APR_UNSPEC, because it may give us back an IPV6 address even if we can't 1105251881Speter create IPV6 sockets. */ 1106251881Speter 1107251881Speter#if APR_HAVE_IPV6 1108251881Speter#ifdef MAX_SECS_TO_LINGER 1109251881Speter /* ### old APR interface */ 1110251881Speter status = apr_socket_create(&sock, APR_INET6, SOCK_STREAM, pool); 1111251881Speter#else 1112251881Speter status = apr_socket_create(&sock, APR_INET6, SOCK_STREAM, APR_PROTO_TCP, 1113251881Speter pool); 1114251881Speter#endif 1115251881Speter if (status == 0) 1116251881Speter { 1117251881Speter apr_socket_close(sock); 1118251881Speter family = APR_UNSPEC; 1119251881Speter 1120251881Speter if (prefer_v6) 1121251881Speter { 1122251881Speter if (host == NULL) 1123251881Speter host = "::"; 1124251881Speter sockaddr_info_flags = APR_IPV6_ADDR_OK; 1125251881Speter } 1126251881Speter else 1127251881Speter { 1128251881Speter if (host == NULL) 1129251881Speter host = "0.0.0.0"; 1130251881Speter sockaddr_info_flags = APR_IPV4_ADDR_OK; 1131251881Speter } 1132251881Speter } 1133251881Speter#endif 1134251881Speter 1135251881Speter status = apr_sockaddr_info_get(&sa, host, family, port, 1136251881Speter sockaddr_info_flags, pool); 1137251881Speter if (status) 1138251881Speter { 1139299742Sdim return svn_error_wrap_apr(status, _("Can't get address info")); 1140251881Speter } 1141251881Speter 1142251881Speter 1143251881Speter#ifdef MAX_SECS_TO_LINGER 1144251881Speter /* ### old APR interface */ 1145251881Speter status = apr_socket_create(&sock, sa->family, SOCK_STREAM, pool); 1146251881Speter#else 1147251881Speter status = apr_socket_create(&sock, sa->family, SOCK_STREAM, APR_PROTO_TCP, 1148251881Speter pool); 1149251881Speter#endif 1150251881Speter if (status) 1151251881Speter { 1152299742Sdim return svn_error_wrap_apr(status, _("Can't create server socket")); 1153251881Speter } 1154251881Speter 1155251881Speter /* Prevents "socket in use" errors when server is killed and quickly 1156251881Speter * restarted. */ 1157299742Sdim status = apr_socket_opt_set(sock, APR_SO_REUSEADDR, 1); 1158299742Sdim if (status) 1159299742Sdim { 1160299742Sdim return svn_error_wrap_apr(status, _("Can't set options on server socket")); 1161299742Sdim } 1162251881Speter 1163251881Speter status = apr_socket_bind(sock, sa); 1164251881Speter if (status) 1165251881Speter { 1166299742Sdim return svn_error_wrap_apr(status, _("Can't bind server socket")); 1167251881Speter } 1168251881Speter 1169299742Sdim status = apr_socket_listen(sock, ACCEPT_BACKLOG); 1170299742Sdim if (status) 1171299742Sdim { 1172299742Sdim return svn_error_wrap_apr(status, _("Can't listen on server socket")); 1173299742Sdim } 1174251881Speter 1175251881Speter#if APR_HAS_FORK 1176251881Speter if (run_mode != run_mode_listen_once && !foreground) 1177299742Sdim /* ### ignoring errors... */ 1178251881Speter apr_proc_detach(APR_PROC_DETACH_DAEMONIZE); 1179251881Speter 1180251881Speter apr_signal(SIGCHLD, sigchld_handler); 1181251881Speter#endif 1182251881Speter 1183251881Speter#ifdef SIGPIPE 1184251881Speter /* Disable SIGPIPE generation for the platforms that have it. */ 1185251881Speter apr_signal(SIGPIPE, SIG_IGN); 1186251881Speter#endif 1187251881Speter 1188251881Speter#ifdef SIGXFSZ 1189251881Speter /* Disable SIGXFSZ generation for the platforms that have it, otherwise 1190251881Speter * working with large files when compiled against an APR that doesn't have 1191251881Speter * large file support will crash the program, which is uncool. */ 1192251881Speter apr_signal(SIGXFSZ, SIG_IGN); 1193251881Speter#endif 1194251881Speter 1195251881Speter if (pid_filename) 1196299742Sdim SVN_ERR(write_pid_file(pid_filename, pool)); 1197251881Speter 1198251881Speter#ifdef WIN32 1199251881Speter status = apr_os_sock_get(&winservice_svnserve_accept_socket, sock); 1200251881Speter if (status) 1201251881Speter winservice_svnserve_accept_socket = INVALID_SOCKET; 1202251881Speter 1203251881Speter /* At this point, the service is "running". Notify the SCM. */ 1204251881Speter if (run_mode == run_mode_service) 1205251881Speter winservice_running(); 1206251881Speter#endif 1207251881Speter 1208251881Speter /* Configure FS caches for maximum efficiency with svnserve. 1209251881Speter * For pre-forked (i.e. multi-processed) mode of operation, 1210251881Speter * keep the per-process caches smaller than the default. 1211251881Speter * Also, apply the respective command line parameters, if given. */ 1212251881Speter { 1213251881Speter svn_cache_config_t settings = *svn_cache_config_get(); 1214251881Speter 1215251881Speter if (params.memory_cache_size != -1) 1216251881Speter settings.cache_size = params.memory_cache_size; 1217251881Speter 1218251881Speter settings.single_threaded = TRUE; 1219251881Speter if (handling_mode == connection_mode_thread) 1220251881Speter { 1221251881Speter#if APR_HAS_THREADS 1222251881Speter settings.single_threaded = FALSE; 1223251881Speter#else 1224251881Speter /* No requests will be processed at all 1225251881Speter * (see "switch (handling_mode)" code further down). 1226251881Speter * But if they were, some other synchronization code 1227251881Speter * would need to take care of securing integrity of 1228251881Speter * APR-based structures. That would include our caches. 1229251881Speter */ 1230251881Speter#endif 1231251881Speter } 1232251881Speter 1233251881Speter svn_cache_config_set(&settings); 1234251881Speter } 1235251881Speter 1236299742Sdim#if APR_HAS_THREADS 1237299742Sdim SVN_ERR(svn_root_pools__create(&connection_pools)); 1238299742Sdim 1239299742Sdim if (handling_mode == connection_mode_thread) 1240251881Speter { 1241299742Sdim /* create the thread pool with a valid range of threads */ 1242299742Sdim if (max_thread_count < 1) 1243299742Sdim max_thread_count = 1; 1244299742Sdim if (min_thread_count > max_thread_count) 1245299742Sdim min_thread_count = max_thread_count; 1246251881Speter 1247299742Sdim status = apr_thread_pool_create(&threads, 1248299742Sdim min_thread_count, 1249299742Sdim max_thread_count, 1250299742Sdim pool); 1251251881Speter if (status) 1252251881Speter { 1253299742Sdim return svn_error_wrap_apr(status, _("Can't create thread pool")); 1254251881Speter } 1255251881Speter 1256299742Sdim /* let idle threads linger for a while in case more requests are 1257299742Sdim coming in */ 1258299742Sdim apr_thread_pool_idle_wait_set(threads, THREADPOOL_THREAD_IDLE_LIMIT); 1259251881Speter 1260299742Sdim /* don't queue requests unless we reached the worker thread limit */ 1261299742Sdim apr_thread_pool_threshold_set(threads, 0); 1262299742Sdim } 1263299742Sdim else 1264299742Sdim { 1265299742Sdim threads = NULL; 1266299742Sdim } 1267299742Sdim#endif 1268251881Speter 1269299742Sdim while (1) 1270299742Sdim { 1271299742Sdim connection_t *connection = NULL; 1272299742Sdim SVN_ERR(accept_connection(&connection, sock, ¶ms, handling_mode, 1273299742Sdim pool)); 1274251881Speter if (run_mode == run_mode_listen_once) 1275251881Speter { 1276299742Sdim err = serve_socket(connection, connection->pool); 1277299742Sdim close_connection(connection); 1278299742Sdim return err; 1279251881Speter } 1280251881Speter 1281251881Speter switch (handling_mode) 1282251881Speter { 1283251881Speter case connection_mode_fork: 1284251881Speter#if APR_HAS_FORK 1285299742Sdim status = apr_proc_fork(&proc, connection->pool); 1286251881Speter if (status == APR_INCHILD) 1287251881Speter { 1288299742Sdim /* the child would't listen to the main server's socket */ 1289251881Speter apr_socket_close(sock); 1290299742Sdim 1291299742Sdim /* serve_socket() logs any error it returns, so ignore it. */ 1292299742Sdim svn_error_clear(serve_socket(connection, connection->pool)); 1293299742Sdim close_connection(connection); 1294299742Sdim return SVN_NO_ERROR; 1295251881Speter } 1296299742Sdim else if (status != APR_INPARENT) 1297251881Speter { 1298251881Speter err = svn_error_wrap_apr(status, "apr_proc_fork"); 1299299742Sdim logger__log_error(params.logger, err, NULL, NULL); 1300251881Speter svn_error_clear(err); 1301251881Speter } 1302251881Speter#endif 1303251881Speter break; 1304251881Speter 1305251881Speter case connection_mode_thread: 1306251881Speter /* Create a detached thread for each connection. That's not a 1307251881Speter particularly sophisticated strategy for a threaded server, it's 1308251881Speter little different from forking one process per connection. */ 1309251881Speter#if APR_HAS_THREADS 1310299742Sdim attach_connection(connection); 1311299742Sdim 1312299742Sdim status = apr_thread_pool_push(threads, serve_thread, connection, 1313299742Sdim 0, NULL); 1314251881Speter if (status) 1315251881Speter { 1316299742Sdim return svn_error_wrap_apr(status, _("Can't push task")); 1317251881Speter } 1318251881Speter#endif 1319251881Speter break; 1320251881Speter 1321251881Speter case connection_mode_single: 1322251881Speter /* Serve one connection at a time. */ 1323299742Sdim /* serve_socket() logs any error it returns, so ignore it. */ 1324299742Sdim svn_error_clear(serve_socket(connection, connection->pool)); 1325251881Speter } 1326299742Sdim 1327299742Sdim close_connection(connection); 1328251881Speter } 1329251881Speter 1330251881Speter /* NOTREACHED */ 1331251881Speter} 1332299742Sdim 1333299742Sdimint 1334299742Sdimmain(int argc, const char *argv[]) 1335299742Sdim{ 1336299742Sdim apr_pool_t *pool; 1337299742Sdim int exit_code = EXIT_SUCCESS; 1338299742Sdim svn_error_t *err; 1339299742Sdim 1340299742Sdim /* Initialize the app. */ 1341299742Sdim if (svn_cmdline_init("svnserve", stderr) != EXIT_SUCCESS) 1342299742Sdim return EXIT_FAILURE; 1343299742Sdim 1344299742Sdim /* Create our top-level pool. */ 1345299742Sdim pool = apr_allocator_owner_get(svn_pool_create_allocator(TRUE)); 1346299742Sdim 1347299742Sdim err = sub_main(&exit_code, argc, argv, pool); 1348299742Sdim 1349299742Sdim /* Flush stdout and report if it fails. It would be flushed on exit anyway 1350299742Sdim but this makes sure that output is not silently lost if it fails. */ 1351299742Sdim err = svn_error_compose_create(err, svn_cmdline_fflush(stdout)); 1352299742Sdim 1353299742Sdim if (err) 1354299742Sdim { 1355299742Sdim exit_code = EXIT_FAILURE; 1356299742Sdim svn_cmdline_handle_exit_error(err, NULL, "svnserve: "); 1357299742Sdim } 1358299742Sdim 1359299742Sdim#if APR_HAS_THREADS 1360299742Sdim /* Explicitly wait for all threads to exit. As we found out with similar 1361299742Sdim code in our C test framework, the memory pool cleanup below cannot be 1362299742Sdim trusted to do the right thing. */ 1363299742Sdim if (threads) 1364299742Sdim apr_thread_pool_destroy(threads); 1365299742Sdim#endif 1366299742Sdim 1367299742Sdim /* this will also close the server's socket */ 1368299742Sdim svn_pool_destroy(pool); 1369299742Sdim return exit_code; 1370299742Sdim} 1371