1/*
2 * svnserve.c :  Main control function for svnserve
3 *
4 * ====================================================================
5 *    Licensed to the Apache Software Foundation (ASF) under one
6 *    or more contributor license agreements.  See the NOTICE file
7 *    distributed with this work for additional information
8 *    regarding copyright ownership.  The ASF licenses this file
9 *    to you under the Apache License, Version 2.0 (the
10 *    "License"); you may not use this file except in compliance
11 *    with the License.  You may obtain a copy of the License at
12 *
13 *      http://www.apache.org/licenses/LICENSE-2.0
14 *
15 *    Unless required by applicable law or agreed to in writing,
16 *    software distributed under the License is distributed on an
17 *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18 *    KIND, either express or implied.  See the License for the
19 *    specific language governing permissions and limitations
20 *    under the License.
21 * ====================================================================
22 */
23
24
25
26#define APR_WANT_STRFUNC
27#include <apr_want.h>
28#include <apr_general.h>
29#include <apr_getopt.h>
30#include <apr_network_io.h>
31#include <apr_signal.h>
32#include <apr_thread_proc.h>
33#include <apr_portable.h>
34
35#include <locale.h>
36
37#include "svn_cmdline.h"
38#include "svn_types.h"
39#include "svn_pools.h"
40#include "svn_error.h"
41#include "svn_ra_svn.h"
42#include "svn_utf.h"
43#include "svn_dirent_uri.h"
44#include "svn_path.h"
45#include "svn_opt.h"
46#include "svn_repos.h"
47#include "svn_string.h"
48#include "svn_cache_config.h"
49#include "svn_version.h"
50#include "svn_io.h"
51
52#include "svn_private_config.h"
53
54#include "private/svn_dep_compat.h"
55#include "private/svn_cmdline_private.h"
56#include "private/svn_atomic.h"
57#include "private/svn_subr_private.h"
58
59#include "winservice.h"
60
61#ifdef HAVE_UNISTD_H
62#include <unistd.h>   /* For getpid() */
63#endif
64
65#include "server.h"
66
67/* The strategy for handling incoming connections.  Some of these may be
68   unavailable due to platform limitations. */
69enum connection_handling_mode {
70  connection_mode_fork,   /* Create a process per connection */
71  connection_mode_thread, /* Create a thread per connection */
72  connection_mode_single  /* One connection at a time in this process */
73};
74
75/* The mode in which to run svnserve */
76enum run_mode {
77  run_mode_unspecified,
78  run_mode_inetd,
79  run_mode_daemon,
80  run_mode_tunnel,
81  run_mode_listen_once,
82  run_mode_service
83};
84
85#if APR_HAS_FORK
86#if APR_HAS_THREADS
87
88#define CONNECTION_DEFAULT connection_mode_fork
89#define CONNECTION_HAVE_THREAD_OPTION
90
91#else /* ! APR_HAS_THREADS */
92
93#define CONNECTION_DEFAULT connection_mode_fork
94
95#endif /* ! APR_HAS_THREADS */
96#elif APR_HAS_THREADS /* and ! APR_HAS_FORK */
97
98#define CONNECTION_DEFAULT connection_mode_thread
99
100#else /* ! APR_HAS_THREADS and ! APR_HAS_FORK */
101
102#define CONNECTION_DEFAULT connection_mode_single
103
104#endif
105
106
107#ifdef WIN32
108static apr_os_sock_t winservice_svnserve_accept_socket = INVALID_SOCKET;
109
110/* The SCM calls this function (on an arbitrary thread, not the main()
111   thread!) when it wants to stop the service.
112
113   For now, our strategy is to close the listener socket, in order to
114   unblock main() and cause it to exit its accept loop.  We cannot use
115   apr_socket_close, because that function deletes the apr_socket_t
116   structure, as well as closing the socket handle.  If we called
117   apr_socket_close here, then main() will also call apr_socket_close,
118   resulting in a double-free.  This way, we just close the kernel
119   socket handle, which causes the accept() function call to fail,
120   which causes main() to clean up the socket.  So, memory gets freed
121   only once.
122
123   This isn't pretty, but it's better than a lot of other options.
124   Currently, there is no "right" way to shut down svnserve.
125
126   We store the OS handle rather than a pointer to the apr_socket_t
127   structure in order to eliminate any possibility of illegal memory
128   access. */
129void winservice_notify_stop(void)
130{
131  if (winservice_svnserve_accept_socket != INVALID_SOCKET)
132    closesocket(winservice_svnserve_accept_socket);
133}
134#endif /* _WIN32 */
135
136
137/* Option codes and descriptions for svnserve.
138 *
139 * The entire list must be terminated with an entry of nulls.
140 *
141 * APR requires that options without abbreviations
142 * have codes greater than 255.
143 */
144#define SVNSERVE_OPT_LISTEN_PORT     256
145#define SVNSERVE_OPT_LISTEN_HOST     257
146#define SVNSERVE_OPT_FOREGROUND      258
147#define SVNSERVE_OPT_TUNNEL_USER     259
148#define SVNSERVE_OPT_VERSION         260
149#define SVNSERVE_OPT_PID_FILE        261
150#define SVNSERVE_OPT_SERVICE         262
151#define SVNSERVE_OPT_CONFIG_FILE     263
152#define SVNSERVE_OPT_LOG_FILE        264
153#define SVNSERVE_OPT_CACHE_TXDELTAS  265
154#define SVNSERVE_OPT_CACHE_FULLTEXTS 266
155#define SVNSERVE_OPT_CACHE_REVPROPS  267
156#define SVNSERVE_OPT_SINGLE_CONN     268
157#define SVNSERVE_OPT_CLIENT_SPEED    269
158#define SVNSERVE_OPT_VIRTUAL_HOST    270
159
160static const apr_getopt_option_t svnserve__options[] =
161  {
162    {"daemon",           'd', 0, N_("daemon mode")},
163    {"inetd",            'i', 0, N_("inetd mode")},
164    {"tunnel",           't', 0, N_("tunnel mode")},
165    {"listen-once",      'X', 0, N_("listen-once mode (useful for debugging)")},
166#ifdef WIN32
167    {"service",          SVNSERVE_OPT_SERVICE, 0,
168     N_("Windows service mode (Service Control Manager)")},
169#endif
170    {"root",             'r', 1, N_("root of directory to serve")},
171    {"read-only",        'R', 0,
172     N_("force read only, overriding repository config file")},
173    {"config-file",      SVNSERVE_OPT_CONFIG_FILE, 1,
174     N_("read configuration from file ARG")},
175    {"listen-port",       SVNSERVE_OPT_LISTEN_PORT, 1,
176#ifdef WIN32
177     N_("listen port. The default port is 3690.\n"
178        "                             "
179        "[mode: daemon, service, listen-once]")},
180#else
181     N_("listen port. The default port is 3690.\n"
182        "                             "
183        "[mode: daemon, listen-once]")},
184#endif
185    {"listen-host",       SVNSERVE_OPT_LISTEN_HOST, 1,
186#ifdef WIN32
187     N_("listen hostname or IP address\n"
188        "                             "
189        "By default svnserve listens on all addresses.\n"
190        "                             "
191        "[mode: daemon, service, listen-once]")},
192#else
193     N_("listen hostname or IP address\n"
194        "                             "
195        "By default svnserve listens on all addresses.\n"
196        "                             "
197        "[mode: daemon, listen-once]")},
198#endif
199    {"prefer-ipv6",      '6', 0,
200     N_("prefer IPv6 when resolving the listen hostname\n"
201        "                             "
202        "[IPv4 is preferred by default. Using IPv4 and IPv6\n"
203        "                             "
204        "at the same time is not supported in daemon mode.\n"
205        "                             "
206        "Use inetd mode or tunnel mode if you need this.]")},
207    {"compression",      'c', 1,
208     N_("compression level to use for network transmissions\n"
209        "                             "
210        "[0 .. no compression, 5 .. default, \n"
211        "                             "
212        " 9 .. maximum compression]")},
213    {"memory-cache-size", 'M', 1,
214     N_("size of the extra in-memory cache in MB used to\n"
215        "                             "
216        "minimize redundant operations.\n"
217        "                             "
218        "Default is 16.\n"
219        "                             "
220        "[used for FSFS repositories only]")},
221    {"cache-txdeltas", SVNSERVE_OPT_CACHE_TXDELTAS, 1,
222     N_("enable or disable caching of deltas between older\n"
223        "                             "
224        "revisions.\n"
225        "                             "
226        "Default is no.\n"
227        "                             "
228        "[used for FSFS repositories only]")},
229    {"cache-fulltexts", SVNSERVE_OPT_CACHE_FULLTEXTS, 1,
230     N_("enable or disable caching of file contents\n"
231        "                             "
232        "Default is yes.\n"
233        "                             "
234        "[used for FSFS repositories only]")},
235    {"cache-revprops", SVNSERVE_OPT_CACHE_REVPROPS, 1,
236     N_("enable or disable caching of revision properties.\n"
237        "                             "
238        "Consult the documentation before activating this.\n"
239        "                             "
240        "Default is no.\n"
241        "                             "
242        "[used for FSFS repositories only]")},
243    {"client-speed", SVNSERVE_OPT_CLIENT_SPEED, 1,
244     N_("Optimize network handling based on the assumption\n"
245        "                             "
246        "that most clients are connected with a bitrate of\n"
247        "                             "
248        "ARG Mbit/s.\n"
249        "                             "
250        "Default is 0 (optimizations disabled).")},
251#ifdef CONNECTION_HAVE_THREAD_OPTION
252    /* ### Making the assumption here that WIN32 never has fork and so
253     * ### this option never exists when --service exists. */
254    {"threads",          'T', 0, N_("use threads instead of fork "
255                                    "[mode: daemon]")},
256#endif
257    {"foreground",        SVNSERVE_OPT_FOREGROUND, 0,
258     N_("run in foreground (useful for debugging)\n"
259        "                             "
260        "[mode: daemon]")},
261    {"single-thread",    SVNSERVE_OPT_SINGLE_CONN, 0,
262     N_("handle one connection at a time in the parent process\n"
263        "                             "
264        "(useful for debugging)")},
265    {"log-file",         SVNSERVE_OPT_LOG_FILE, 1,
266     N_("svnserve log file")},
267    {"pid-file",         SVNSERVE_OPT_PID_FILE, 1,
268#ifdef WIN32
269     N_("write server process ID to file ARG\n"
270        "                             "
271        "[mode: daemon, listen-once, service]")},
272#else
273     N_("write server process ID to file ARG\n"
274        "                             "
275        "[mode: daemon, listen-once]")},
276#endif
277    {"tunnel-user",      SVNSERVE_OPT_TUNNEL_USER, 1,
278     N_("tunnel username (default is current uid's name)\n"
279        "                             "
280        "[mode: tunnel]")},
281    {"help",             'h', 0, N_("display this help")},
282    {"virtual-host",     SVNSERVE_OPT_VIRTUAL_HOST, 0,
283     N_("virtual host mode (look for repo in directory\n"
284        "                             "
285        "of provided hostname)")},
286    {"version",           SVNSERVE_OPT_VERSION, 0,
287     N_("show program version information")},
288    {"quiet",            'q', 0,
289     N_("no progress (only errors) to stderr")},
290    {0,                  0,   0, 0}
291  };
292
293
294static void usage(const char *progname, apr_pool_t *pool)
295{
296  if (!progname)
297    progname = "svnserve";
298
299  svn_error_clear(svn_cmdline_fprintf(stderr, pool,
300                                      _("Type '%s --help' for usage.\n"),
301                                      progname));
302  exit(1);
303}
304
305static void help(apr_pool_t *pool)
306{
307  apr_size_t i;
308
309#ifdef WIN32
310  svn_error_clear(svn_cmdline_fputs(_("usage: svnserve [-d | -i | -t | -X "
311                                      "| --service] [options]\n"
312                                      "\n"
313                                      "Valid options:\n"),
314                                    stdout, pool));
315#else
316  svn_error_clear(svn_cmdline_fputs(_("usage: svnserve [-d | -i | -t | -X] "
317                                      "[options]\n"
318                                      "\n"
319                                      "Valid options:\n"),
320                                    stdout, pool));
321#endif
322  for (i = 0; svnserve__options[i].name && svnserve__options[i].optch; i++)
323    {
324      const char *optstr;
325      svn_opt_format_option(&optstr, svnserve__options + i, TRUE, pool);
326      svn_error_clear(svn_cmdline_fprintf(stdout, pool, "  %s\n", optstr));
327    }
328  svn_error_clear(svn_cmdline_fprintf(stdout, pool, "\n"));
329  exit(0);
330}
331
332static svn_error_t * version(svn_boolean_t quiet, apr_pool_t *pool)
333{
334  const char *fs_desc_start
335    = _("The following repository back-end (FS) modules are available:\n\n");
336
337  svn_stringbuf_t *version_footer;
338
339  version_footer = svn_stringbuf_create(fs_desc_start, pool);
340  SVN_ERR(svn_fs_print_modules(version_footer, pool));
341
342#ifdef SVN_HAVE_SASL
343  svn_stringbuf_appendcstr(version_footer,
344                           _("\nCyrus SASL authentication is available.\n"));
345#endif
346
347  return svn_opt_print_help4(NULL, "svnserve", TRUE, quiet, FALSE,
348                             version_footer->data,
349                             NULL, NULL, NULL, NULL, NULL, pool);
350}
351
352
353#if APR_HAS_FORK
354static void sigchld_handler(int signo)
355{
356  /* Nothing to do; we just need to interrupt the accept(). */
357}
358#endif
359
360/* Redirect stdout to stderr.  ARG is the pool.
361 *
362 * In tunnel or inetd mode, we don't want hook scripts corrupting the
363 * data stream by sending data to stdout, so we need to redirect
364 * stdout somewhere else.  Sending it to stderr is acceptable; sending
365 * it to /dev/null is another option, but apr doesn't provide a way to
366 * do that without also detaching from the controlling terminal.
367 */
368static apr_status_t redirect_stdout(void *arg)
369{
370  apr_pool_t *pool = arg;
371  apr_file_t *out_file, *err_file;
372  apr_status_t apr_err;
373
374  if ((apr_err = apr_file_open_stdout(&out_file, pool)))
375    return apr_err;
376  if ((apr_err = apr_file_open_stderr(&err_file, pool)))
377    return apr_err;
378  return apr_file_dup2(out_file, err_file, pool);
379}
380
381#if APR_HAS_THREADS
382/* The pool passed to apr_thread_create can only be released when both
383
384      A: the call to apr_thread_create has returned to the calling thread
385      B: the new thread has started running and reached apr_thread_start_t
386
387   So we set the atomic counter to 2 then both the calling thread and
388   the new thread decrease it and when it reaches 0 the pool can be
389   released.  */
390struct shared_pool_t {
391  svn_atomic_t count;
392  apr_pool_t *pool;
393};
394
395static struct shared_pool_t *
396attach_shared_pool(apr_pool_t *pool)
397{
398  struct shared_pool_t *shared = apr_palloc(pool, sizeof(struct shared_pool_t));
399
400  shared->pool = pool;
401  svn_atomic_set(&shared->count, 2);
402
403  return shared;
404}
405
406static void
407release_shared_pool(struct shared_pool_t *shared)
408{
409  if (svn_atomic_dec(&shared->count) == 0)
410    svn_pool_destroy(shared->pool);
411}
412#endif
413
414/* "Arguments" passed from the main thread to the connection thread */
415struct serve_thread_t {
416  svn_ra_svn_conn_t *conn;
417  serve_params_t *params;
418  struct shared_pool_t *shared_pool;
419};
420
421#if APR_HAS_THREADS
422static void * APR_THREAD_FUNC serve_thread(apr_thread_t *tid, void *data)
423{
424  struct serve_thread_t *d = data;
425
426  svn_error_clear(serve(d->conn, d->params, d->shared_pool->pool));
427  release_shared_pool(d->shared_pool);
428
429  return NULL;
430}
431#endif
432
433/* Write the PID of the current process as a decimal number, followed by a
434   newline to the file FILENAME, using POOL for temporary allocations. */
435static svn_error_t *write_pid_file(const char *filename, apr_pool_t *pool)
436{
437  apr_file_t *file;
438  const char *contents = apr_psprintf(pool, "%" APR_PID_T_FMT "\n",
439                                             getpid());
440
441  SVN_ERR(svn_io_remove_file2(filename, TRUE, pool));
442  SVN_ERR(svn_io_file_open(&file, filename,
443                           APR_WRITE | APR_CREATE | APR_EXCL,
444                           APR_OS_DEFAULT, pool));
445  SVN_ERR(svn_io_file_write_full(file, contents, strlen(contents), NULL,
446                                 pool));
447
448  SVN_ERR(svn_io_file_close(file, pool));
449
450  return SVN_NO_ERROR;
451}
452
453/* Version compatibility check */
454static svn_error_t *
455check_lib_versions(void)
456{
457  static const svn_version_checklist_t checklist[] =
458    {
459      { "svn_subr",  svn_subr_version },
460      { "svn_repos", svn_repos_version },
461      { "svn_fs",    svn_fs_version },
462      { "svn_delta", svn_delta_version },
463      { "svn_ra_svn", svn_ra_svn_version },
464      { NULL, NULL }
465    };
466  SVN_VERSION_DEFINE(my_version);
467
468  return svn_ver_check_list2(&my_version, checklist, svn_ver_equal);
469}
470
471
472int main(int argc, const char *argv[])
473{
474  enum run_mode run_mode = run_mode_unspecified;
475  svn_boolean_t foreground = FALSE;
476  apr_socket_t *sock, *usock;
477  apr_file_t *in_file, *out_file;
478  apr_sockaddr_t *sa;
479  apr_pool_t *pool;
480  apr_pool_t *connection_pool;
481  svn_error_t *err;
482  apr_getopt_t *os;
483  int opt;
484  serve_params_t params;
485  const char *arg;
486  apr_status_t status;
487  svn_ra_svn_conn_t *conn;
488  apr_proc_t proc;
489#if APR_HAS_THREADS
490  apr_threadattr_t *tattr;
491  apr_thread_t *tid;
492  struct shared_pool_t *shared_pool;
493
494  struct serve_thread_t *thread_data;
495#endif
496  enum connection_handling_mode handling_mode = CONNECTION_DEFAULT;
497  apr_uint16_t port = SVN_RA_SVN_PORT;
498  const char *host = NULL;
499  int family = APR_INET;
500  apr_int32_t sockaddr_info_flags = 0;
501#if APR_HAVE_IPV6
502  svn_boolean_t prefer_v6 = FALSE;
503#endif
504  svn_boolean_t quiet = FALSE;
505  svn_boolean_t is_version = FALSE;
506  int mode_opt_count = 0;
507  int handling_opt_count = 0;
508  const char *config_filename = NULL;
509  const char *pid_filename = NULL;
510  const char *log_filename = NULL;
511  svn_node_kind_t kind;
512
513  /* Initialize the app. */
514  if (svn_cmdline_init("svnserve", stderr) != EXIT_SUCCESS)
515    return EXIT_FAILURE;
516
517  /* Create our top-level pool. */
518  pool = svn_pool_create(NULL);
519
520#ifdef SVN_HAVE_SASL
521  SVN_INT_ERR(cyrus_init(pool));
522#endif
523
524  /* Check library versions */
525  err = check_lib_versions();
526  if (err)
527    return svn_cmdline_handle_exit_error(err, pool, "svnserve: ");
528
529  /* Initialize the FS library. */
530  err = svn_fs_initialize(pool);
531  if (err)
532    return svn_cmdline_handle_exit_error(err, pool, "svnserve: ");
533
534  err = svn_cmdline__getopt_init(&os, argc, argv, pool);
535  if (err)
536    return svn_cmdline_handle_exit_error(err, pool, "svnserve: ");
537
538  params.root = "/";
539  params.tunnel = FALSE;
540  params.tunnel_user = NULL;
541  params.read_only = FALSE;
542  params.base = NULL;
543  params.cfg = NULL;
544  params.compression_level = SVN_DELTA_COMPRESSION_LEVEL_DEFAULT;
545  params.log_file = NULL;
546  params.vhost = FALSE;
547  params.username_case = CASE_ASIS;
548  params.memory_cache_size = (apr_uint64_t)-1;
549  params.cache_fulltexts = TRUE;
550  params.cache_txdeltas = FALSE;
551  params.cache_revprops = FALSE;
552  params.zero_copy_limit = 0;
553  params.error_check_interval = 4096;
554
555  while (1)
556    {
557      status = apr_getopt_long(os, svnserve__options, &opt, &arg);
558      if (APR_STATUS_IS_EOF(status))
559        break;
560      if (status != APR_SUCCESS)
561        usage(argv[0], pool);
562      switch (opt)
563        {
564        case '6':
565#if APR_HAVE_IPV6
566          prefer_v6 = TRUE;
567#endif
568          /* ### Maybe error here if we don't have IPV6 support? */
569          break;
570
571        case 'h':
572          help(pool);
573          break;
574
575        case 'q':
576          quiet = TRUE;
577          break;
578
579        case SVNSERVE_OPT_VERSION:
580          is_version = TRUE;
581          break;
582
583        case 'd':
584          if (run_mode != run_mode_daemon)
585            {
586              run_mode = run_mode_daemon;
587              mode_opt_count++;
588            }
589          break;
590
591        case SVNSERVE_OPT_FOREGROUND:
592          foreground = TRUE;
593          break;
594
595        case SVNSERVE_OPT_SINGLE_CONN:
596          handling_mode = connection_mode_single;
597          handling_opt_count++;
598          break;
599
600        case 'i':
601          if (run_mode != run_mode_inetd)
602            {
603              run_mode = run_mode_inetd;
604              mode_opt_count++;
605            }
606          break;
607
608        case SVNSERVE_OPT_LISTEN_PORT:
609          {
610            apr_uint64_t val;
611
612            err = svn_cstring_strtoui64(&val, arg, 0, APR_UINT16_MAX, 10);
613            if (err)
614              return svn_cmdline_handle_exit_error(
615                       svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, err,
616                                         _("Invalid port '%s'"), arg),
617                       pool, "svnserve: ");
618            port = (apr_uint16_t)val;
619          }
620          break;
621
622        case SVNSERVE_OPT_LISTEN_HOST:
623          host = arg;
624          break;
625
626        case 't':
627          if (run_mode != run_mode_tunnel)
628            {
629              run_mode = run_mode_tunnel;
630              mode_opt_count++;
631            }
632          break;
633
634        case SVNSERVE_OPT_TUNNEL_USER:
635          params.tunnel_user = arg;
636          break;
637
638        case 'X':
639          if (run_mode != run_mode_listen_once)
640            {
641              run_mode = run_mode_listen_once;
642              mode_opt_count++;
643            }
644          break;
645
646        case 'r':
647          SVN_INT_ERR(svn_utf_cstring_to_utf8(&params.root, arg, pool));
648
649          err = svn_io_check_resolved_path(params.root, &kind, pool);
650          if (err)
651            return svn_cmdline_handle_exit_error(err, pool, "svnserve: ");
652          if (kind != svn_node_dir)
653            {
654              svn_error_clear
655                (svn_cmdline_fprintf
656                   (stderr, pool,
657                    _("svnserve: Root path '%s' does not exist "
658                      "or is not a directory.\n"), params.root));
659              return EXIT_FAILURE;
660            }
661
662          params.root = svn_dirent_internal_style(params.root, pool);
663          SVN_INT_ERR(svn_dirent_get_absolute(&params.root, params.root, pool));
664          break;
665
666        case 'R':
667          params.read_only = TRUE;
668          break;
669
670        case 'T':
671          handling_mode = connection_mode_thread;
672          handling_opt_count++;
673          break;
674
675        case 'c':
676          params.compression_level = atoi(arg);
677          if (params.compression_level < SVN_DELTA_COMPRESSION_LEVEL_NONE)
678            params.compression_level = SVN_DELTA_COMPRESSION_LEVEL_NONE;
679          if (params.compression_level > SVN_DELTA_COMPRESSION_LEVEL_MAX)
680            params.compression_level = SVN_DELTA_COMPRESSION_LEVEL_MAX;
681          break;
682
683        case 'M':
684          params.memory_cache_size = 0x100000 * apr_strtoi64(arg, NULL, 0);
685          break;
686
687        case SVNSERVE_OPT_CACHE_TXDELTAS:
688          params.cache_txdeltas
689             = svn_tristate__from_word(arg) == svn_tristate_true;
690          break;
691
692        case SVNSERVE_OPT_CACHE_FULLTEXTS:
693          params.cache_fulltexts
694             = svn_tristate__from_word(arg) == svn_tristate_true;
695          break;
696
697        case SVNSERVE_OPT_CACHE_REVPROPS:
698          params.cache_revprops
699             = svn_tristate__from_word(arg) == svn_tristate_true;
700          break;
701
702        case SVNSERVE_OPT_CLIENT_SPEED:
703          {
704            apr_size_t bandwidth = (apr_size_t)apr_strtoi64(arg, NULL, 0);
705
706            /* for slower clients, don't try anything fancy */
707            if (bandwidth >= 1000)
708              {
709                /* block other clients for at most 1 ms (at full bandwidth).
710                   Note that the send buffer is 16kB anyways. */
711                params.zero_copy_limit = bandwidth * 120;
712
713                /* check for aborted connections at the same rate */
714                params.error_check_interval = bandwidth * 120;
715              }
716          }
717          break;
718
719#ifdef WIN32
720        case SVNSERVE_OPT_SERVICE:
721          if (run_mode != run_mode_service)
722            {
723              run_mode = run_mode_service;
724              mode_opt_count++;
725            }
726          break;
727#endif
728
729        case SVNSERVE_OPT_CONFIG_FILE:
730          SVN_INT_ERR(svn_utf_cstring_to_utf8(&config_filename, arg, pool));
731          config_filename = svn_dirent_internal_style(config_filename, pool);
732          SVN_INT_ERR(svn_dirent_get_absolute(&config_filename, config_filename,
733                                              pool));
734          break;
735
736        case SVNSERVE_OPT_PID_FILE:
737          SVN_INT_ERR(svn_utf_cstring_to_utf8(&pid_filename, arg, pool));
738          pid_filename = svn_dirent_internal_style(pid_filename, pool);
739          SVN_INT_ERR(svn_dirent_get_absolute(&pid_filename, pid_filename,
740                                              pool));
741          break;
742
743         case SVNSERVE_OPT_VIRTUAL_HOST:
744           params.vhost = TRUE;
745           break;
746
747         case SVNSERVE_OPT_LOG_FILE:
748          SVN_INT_ERR(svn_utf_cstring_to_utf8(&log_filename, arg, pool));
749          log_filename = svn_dirent_internal_style(log_filename, pool);
750          SVN_INT_ERR(svn_dirent_get_absolute(&log_filename, log_filename,
751                                              pool));
752          break;
753
754        }
755    }
756
757  if (is_version)
758    {
759      SVN_INT_ERR(version(quiet, pool));
760      exit(0);
761    }
762
763  if (os->ind != argc)
764    usage(argv[0], pool);
765
766  if (mode_opt_count != 1)
767    {
768      svn_error_clear(svn_cmdline_fputs(
769#ifdef WIN32
770                      _("You must specify exactly one of -d, -i, -t, "
771                        "--service or -X.\n"),
772#else
773                      _("You must specify exactly one of -d, -i, -t or -X.\n"),
774#endif
775                       stderr, pool));
776      usage(argv[0], pool);
777    }
778
779  if (handling_opt_count > 1)
780    {
781      svn_error_clear(svn_cmdline_fputs(
782                      _("You may only specify one of -T or --single-thread\n"),
783                      stderr, pool));
784      usage(argv[0], pool);
785    }
786
787  /* If a configuration file is specified, load it and any referenced
788   * password and authorization files. */
789  if (config_filename)
790    {
791      params.base = svn_dirent_dirname(config_filename, pool);
792
793      SVN_INT_ERR(svn_config_read3(&params.cfg, config_filename,
794                                   TRUE, /* must_exist */
795                                   FALSE, /* section_names_case_sensitive */
796                                   FALSE, /* option_names_case_sensitive */
797                                   pool));
798    }
799
800  if (log_filename)
801    SVN_INT_ERR(svn_io_file_open(&params.log_file, log_filename,
802                                 APR_WRITE | APR_CREATE | APR_APPEND,
803                                 APR_OS_DEFAULT, pool));
804
805  if (params.tunnel_user && run_mode != run_mode_tunnel)
806    {
807      svn_error_clear
808        (svn_cmdline_fprintf
809           (stderr, pool,
810            _("Option --tunnel-user is only valid in tunnel mode.\n")));
811      exit(1);
812    }
813
814  if (run_mode == run_mode_inetd || run_mode == run_mode_tunnel)
815    {
816      params.tunnel = (run_mode == run_mode_tunnel);
817      apr_pool_cleanup_register(pool, pool, apr_pool_cleanup_null,
818                                redirect_stdout);
819      status = apr_file_open_stdin(&in_file, pool);
820      if (status)
821        {
822          err = svn_error_wrap_apr(status, _("Can't open stdin"));
823          return svn_cmdline_handle_exit_error(err, pool, "svnserve: ");
824        }
825
826      status = apr_file_open_stdout(&out_file, pool);
827      if (status)
828        {
829          err = svn_error_wrap_apr(status, _("Can't open stdout"));
830          return svn_cmdline_handle_exit_error(err, pool, "svnserve: ");
831        }
832
833      /* Use a subpool for the connection to ensure that if SASL is used
834       * the pool cleanup handlers that call sasl_dispose() (connection_pool)
835       * and sasl_done() (pool) are run in the right order. See issue #3664. */
836      connection_pool = svn_pool_create(pool);
837      conn = svn_ra_svn_create_conn3(NULL, in_file, out_file,
838                                     params.compression_level,
839                                     params.zero_copy_limit,
840                                     params.error_check_interval,
841                                     connection_pool);
842      svn_error_clear(serve(conn, &params, connection_pool));
843      exit(0);
844    }
845
846#ifdef WIN32
847  /* If svnserve needs to run as a Win32 service, then we need to
848     coordinate with the Service Control Manager (SCM) before
849     continuing.  This function call registers the svnserve.exe
850     process with the SCM, waits for the "start" command from the SCM
851     (which will come very quickly), and confirms that those steps
852     succeeded.
853
854     After this call succeeds, the service is free to run.  At some
855     point in the future, the SCM will send a message to the service,
856     requesting that it stop.  This is translated into a call to
857     winservice_notify_stop().  The service is then responsible for
858     cleanly terminating.
859
860     We need to do this before actually starting the service logic
861     (opening files, sockets, etc.) because the SCM wants you to
862     connect *first*, then do your service-specific logic.  If the
863     service process takes too long to connect to the SCM, then the
864     SCM will decide that the service is busted, and will give up on
865     it.
866     */
867  if (run_mode == run_mode_service)
868    {
869      err = winservice_start();
870      if (err)
871        {
872          svn_handle_error2(err, stderr, FALSE, "svnserve: ");
873
874          /* This is the most common error.  It means the user started
875             svnserve from a shell, and specified the --service
876             argument.  svnserve cannot be started, as a service, in
877             this way.  The --service argument is valid only valid if
878             svnserve is started by the SCM. */
879          if (err->apr_err ==
880              APR_FROM_OS_ERROR(ERROR_FAILED_SERVICE_CONTROLLER_CONNECT))
881            {
882              svn_error_clear(svn_cmdline_fprintf(stderr, pool,
883                  _("svnserve: The --service flag is only valid if the"
884                    " process is started by the Service Control Manager.\n")));
885            }
886
887          svn_error_clear(err);
888          exit(1);
889        }
890
891      /* The service is now in the "starting" state.  Before the SCM will
892         consider the service "started", this thread must call the
893         winservice_running() function. */
894    }
895#endif /* WIN32 */
896
897  /* Make sure we have IPV6 support first before giving apr_sockaddr_info_get
898     APR_UNSPEC, because it may give us back an IPV6 address even if we can't
899     create IPV6 sockets. */
900
901#if APR_HAVE_IPV6
902#ifdef MAX_SECS_TO_LINGER
903  /* ### old APR interface */
904  status = apr_socket_create(&sock, APR_INET6, SOCK_STREAM, pool);
905#else
906  status = apr_socket_create(&sock, APR_INET6, SOCK_STREAM, APR_PROTO_TCP,
907                             pool);
908#endif
909  if (status == 0)
910    {
911      apr_socket_close(sock);
912      family = APR_UNSPEC;
913
914      if (prefer_v6)
915        {
916          if (host == NULL)
917            host = "::";
918          sockaddr_info_flags = APR_IPV6_ADDR_OK;
919        }
920      else
921        {
922          if (host == NULL)
923            host = "0.0.0.0";
924          sockaddr_info_flags = APR_IPV4_ADDR_OK;
925        }
926    }
927#endif
928
929  status = apr_sockaddr_info_get(&sa, host, family, port,
930                                 sockaddr_info_flags, pool);
931  if (status)
932    {
933      err = svn_error_wrap_apr(status, _("Can't get address info"));
934      return svn_cmdline_handle_exit_error(err, pool, "svnserve: ");
935    }
936
937
938#ifdef MAX_SECS_TO_LINGER
939  /* ### old APR interface */
940  status = apr_socket_create(&sock, sa->family, SOCK_STREAM, pool);
941#else
942  status = apr_socket_create(&sock, sa->family, SOCK_STREAM, APR_PROTO_TCP,
943                             pool);
944#endif
945  if (status)
946    {
947      err = svn_error_wrap_apr(status, _("Can't create server socket"));
948      return svn_cmdline_handle_exit_error(err, pool, "svnserve: ");
949    }
950
951  /* Prevents "socket in use" errors when server is killed and quickly
952   * restarted. */
953  apr_socket_opt_set(sock, APR_SO_REUSEADDR, 1);
954
955  status = apr_socket_bind(sock, sa);
956  if (status)
957    {
958      err = svn_error_wrap_apr(status, _("Can't bind server socket"));
959      return svn_cmdline_handle_exit_error(err, pool, "svnserve: ");
960    }
961
962  apr_socket_listen(sock, 7);
963
964#if APR_HAS_FORK
965  if (run_mode != run_mode_listen_once && !foreground)
966    apr_proc_detach(APR_PROC_DETACH_DAEMONIZE);
967
968  apr_signal(SIGCHLD, sigchld_handler);
969#endif
970
971#ifdef SIGPIPE
972  /* Disable SIGPIPE generation for the platforms that have it. */
973  apr_signal(SIGPIPE, SIG_IGN);
974#endif
975
976#ifdef SIGXFSZ
977  /* Disable SIGXFSZ generation for the platforms that have it, otherwise
978   * working with large files when compiled against an APR that doesn't have
979   * large file support will crash the program, which is uncool. */
980  apr_signal(SIGXFSZ, SIG_IGN);
981#endif
982
983  if (pid_filename)
984    SVN_INT_ERR(write_pid_file(pid_filename, pool));
985
986#ifdef WIN32
987  status = apr_os_sock_get(&winservice_svnserve_accept_socket, sock);
988  if (status)
989    winservice_svnserve_accept_socket = INVALID_SOCKET;
990
991  /* At this point, the service is "running".  Notify the SCM. */
992  if (run_mode == run_mode_service)
993    winservice_running();
994#endif
995
996  /* Configure FS caches for maximum efficiency with svnserve.
997   * For pre-forked (i.e. multi-processed) mode of operation,
998   * keep the per-process caches smaller than the default.
999   * Also, apply the respective command line parameters, if given. */
1000  {
1001    svn_cache_config_t settings = *svn_cache_config_get();
1002
1003    if (params.memory_cache_size != -1)
1004      settings.cache_size = params.memory_cache_size;
1005
1006    settings.single_threaded = TRUE;
1007    if (handling_mode == connection_mode_thread)
1008      {
1009#if APR_HAS_THREADS
1010        settings.single_threaded = FALSE;
1011#else
1012        /* No requests will be processed at all
1013         * (see "switch (handling_mode)" code further down).
1014         * But if they were, some other synchronization code
1015         * would need to take care of securing integrity of
1016         * APR-based structures. That would include our caches.
1017         */
1018#endif
1019      }
1020
1021    svn_cache_config_set(&settings);
1022  }
1023
1024  while (1)
1025    {
1026#ifdef WIN32
1027      if (winservice_is_stopping())
1028        return ERROR_SUCCESS;
1029#endif
1030
1031      /* Non-standard pool handling.  The main thread never blocks to join
1032         the connection threads so it cannot clean up after each one.  So
1033         separate pools that can be cleared at thread exit are used. */
1034
1035      connection_pool
1036          = apr_allocator_owner_get(svn_pool_create_allocator(FALSE));
1037
1038      status = apr_socket_accept(&usock, sock, connection_pool);
1039      if (handling_mode == connection_mode_fork)
1040        {
1041          /* Collect any zombie child processes. */
1042          while (apr_proc_wait_all_procs(&proc, NULL, NULL, APR_NOWAIT,
1043                                         connection_pool) == APR_CHILD_DONE)
1044            ;
1045        }
1046      if (APR_STATUS_IS_EINTR(status)
1047          || APR_STATUS_IS_ECONNABORTED(status)
1048          || APR_STATUS_IS_ECONNRESET(status))
1049        {
1050          svn_pool_destroy(connection_pool);
1051          continue;
1052        }
1053      if (status)
1054        {
1055          err = svn_error_wrap_apr
1056            (status, _("Can't accept client connection"));
1057          return svn_cmdline_handle_exit_error(err, pool, "svnserve: ");
1058        }
1059
1060      /* Enable TCP keep-alives on the socket so we time out when
1061       * the connection breaks due to network-layer problems.
1062       * If the peer has dropped the connection due to a network partition
1063       * or a crash, or if the peer no longer considers the connection
1064       * valid because we are behind a NAT and our public IP has changed,
1065       * it will respond to the keep-alive probe with a RST instead of an
1066       * acknowledgment segment, which will cause svn to abort the session
1067       * even while it is currently blocked waiting for data from the peer. */
1068      status = apr_socket_opt_set(usock, APR_SO_KEEPALIVE, 1);
1069      if (status)
1070        {
1071          /* It's not a fatal error if we cannot enable keep-alives. */
1072        }
1073
1074      conn = svn_ra_svn_create_conn3(usock, NULL, NULL,
1075                                     params.compression_level,
1076                                     params.zero_copy_limit,
1077                                     params.error_check_interval,
1078                                     connection_pool);
1079
1080      if (run_mode == run_mode_listen_once)
1081        {
1082          err = serve(conn, &params, connection_pool);
1083
1084          if (err)
1085            svn_handle_error2(err, stdout, FALSE, "svnserve: ");
1086          svn_error_clear(err);
1087
1088          apr_socket_close(usock);
1089          apr_socket_close(sock);
1090          exit(0);
1091        }
1092
1093      switch (handling_mode)
1094        {
1095        case connection_mode_fork:
1096#if APR_HAS_FORK
1097          status = apr_proc_fork(&proc, connection_pool);
1098          if (status == APR_INCHILD)
1099            {
1100              apr_socket_close(sock);
1101              err = serve(conn, &params, connection_pool);
1102              log_error(err, params.log_file,
1103                        svn_ra_svn_conn_remote_host(conn),
1104                        NULL, NULL, /* user, repos */
1105                        connection_pool);
1106              svn_error_clear(err);
1107              apr_socket_close(usock);
1108              exit(0);
1109            }
1110          else if (status == APR_INPARENT)
1111            {
1112              apr_socket_close(usock);
1113            }
1114          else
1115            {
1116              err = svn_error_wrap_apr(status, "apr_proc_fork");
1117              log_error(err, params.log_file,
1118                        svn_ra_svn_conn_remote_host(conn),
1119                        NULL, NULL, /* user, repos */
1120                        connection_pool);
1121              svn_error_clear(err);
1122              apr_socket_close(usock);
1123            }
1124          svn_pool_destroy(connection_pool);
1125#endif
1126          break;
1127
1128        case connection_mode_thread:
1129          /* Create a detached thread for each connection.  That's not a
1130             particularly sophisticated strategy for a threaded server, it's
1131             little different from forking one process per connection. */
1132#if APR_HAS_THREADS
1133          shared_pool = attach_shared_pool(connection_pool);
1134          status = apr_threadattr_create(&tattr, connection_pool);
1135          if (status)
1136            {
1137              err = svn_error_wrap_apr(status, _("Can't create threadattr"));
1138              svn_handle_error2(err, stderr, FALSE, "svnserve: ");
1139              svn_error_clear(err);
1140              exit(1);
1141            }
1142          status = apr_threadattr_detach_set(tattr, 1);
1143          if (status)
1144            {
1145              err = svn_error_wrap_apr(status, _("Can't set detached state"));
1146              svn_handle_error2(err, stderr, FALSE, "svnserve: ");
1147              svn_error_clear(err);
1148              exit(1);
1149            }
1150          thread_data = apr_palloc(connection_pool, sizeof(*thread_data));
1151          thread_data->conn = conn;
1152          thread_data->params = &params;
1153          thread_data->shared_pool = shared_pool;
1154          status = apr_thread_create(&tid, tattr, serve_thread, thread_data,
1155                                     shared_pool->pool);
1156          if (status)
1157            {
1158              err = svn_error_wrap_apr(status, _("Can't create thread"));
1159              svn_handle_error2(err, stderr, FALSE, "svnserve: ");
1160              svn_error_clear(err);
1161              exit(1);
1162            }
1163          release_shared_pool(shared_pool);
1164#endif
1165          break;
1166
1167        case connection_mode_single:
1168          /* Serve one connection at a time. */
1169          svn_error_clear(serve(conn, &params, connection_pool));
1170          svn_pool_destroy(connection_pool);
1171        }
1172    }
1173
1174  /* NOTREACHED */
1175}
1176