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#include "svn_hash.h"
52
53#include "svn_private_config.h"
54
55#include "private/svn_dep_compat.h"
56#include "private/svn_cmdline_private.h"
57#include "private/svn_atomic.h"
58#include "private/svn_mutex.h"
59#include "private/svn_subr_private.h"
60
61#if APR_HAS_THREADS
62#    include <apr_thread_pool.h>
63#endif
64
65#include "winservice.h"
66
67#ifdef HAVE_UNISTD_H
68#include <unistd.h>   /* For getpid() */
69#endif
70
71#include "server.h"
72#include "logger.h"
73
74/* The strategy for handling incoming connections.  Some of these may be
75   unavailable due to platform limitations. */
76enum connection_handling_mode {
77  connection_mode_fork,   /* Create a process per connection */
78  connection_mode_thread, /* Create a thread per connection */
79  connection_mode_single  /* One connection at a time in this process */
80};
81
82/* The mode in which to run svnserve */
83enum run_mode {
84  run_mode_unspecified,
85  run_mode_inetd,
86  run_mode_daemon,
87  run_mode_tunnel,
88  run_mode_listen_once,
89  run_mode_service
90};
91
92#if APR_HAS_FORK
93#if APR_HAS_THREADS
94
95#define CONNECTION_DEFAULT connection_mode_fork
96#define CONNECTION_HAVE_THREAD_OPTION
97
98#else /* ! APR_HAS_THREADS */
99
100#define CONNECTION_DEFAULT connection_mode_fork
101
102#endif /* ! APR_HAS_THREADS */
103#elif APR_HAS_THREADS /* and ! APR_HAS_FORK */
104
105#define CONNECTION_DEFAULT connection_mode_thread
106
107#else /* ! APR_HAS_THREADS and ! APR_HAS_FORK */
108
109#define CONNECTION_DEFAULT connection_mode_single
110
111#endif
112
113/* Parameters for the worker thread pool used in threaded mode. */
114
115/* Have at least this many worker threads (even if there are no requests
116 * to handle).
117 *
118 * A 0 value is legal but increases the latency for the next incoming
119 * request.  Higher values may be useful for servers that experience short
120 * bursts of concurrent requests followed by longer idle periods.
121 */
122#define THREADPOOL_MIN_SIZE 1
123
124/* Maximum number of worker threads.  If there are more concurrent requests
125 * than worker threads, the extra requests get queued.
126 *
127 * Since very slow connections will hog a full thread for a potentially
128 * long time before timing out, be sure to not set this limit too low.
129 *
130 * On the other hand, keep in mind that every thread will allocate up to
131 * 4MB of unused RAM in the APR allocator of its root pool.  32 bit servers
132 * must hence do with fewer threads.
133 */
134#if (APR_SIZEOF_VOIDP <= 4)
135#define THREADPOOL_MAX_SIZE 64
136#else
137#define THREADPOOL_MAX_SIZE 256
138#endif
139
140/* Number of microseconds that an unused thread remains in the pool before
141 * being terminated.
142 *
143 * Higher values are useful if clients frequently send small requests and
144 * you want to minimize the latency for those.
145 */
146#define THREADPOOL_THREAD_IDLE_LIMIT 1000000
147
148/* Number of client to server connections that may concurrently in the
149 * TCP 3-way handshake state, i.e. are in the process of being created.
150 *
151 * Larger values improve scalability with lots of small requests coming
152 * on over long latency networks.
153 *
154 * The OS may actually use a lower limit than specified here.
155 */
156#define ACCEPT_BACKLOG 128
157
158#ifdef WIN32
159static apr_os_sock_t winservice_svnserve_accept_socket = INVALID_SOCKET;
160
161/* The SCM calls this function (on an arbitrary thread, not the main()
162   thread!) when it wants to stop the service.
163
164   For now, our strategy is to close the listener socket, in order to
165   unblock main() and cause it to exit its accept loop.  We cannot use
166   apr_socket_close, because that function deletes the apr_socket_t
167   structure, as well as closing the socket handle.  If we called
168   apr_socket_close here, then main() will also call apr_socket_close,
169   resulting in a double-free.  This way, we just close the kernel
170   socket handle, which causes the accept() function call to fail,
171   which causes main() to clean up the socket.  So, memory gets freed
172   only once.
173
174   This isn't pretty, but it's better than a lot of other options.
175   Currently, there is no "right" way to shut down svnserve.
176
177   We store the OS handle rather than a pointer to the apr_socket_t
178   structure in order to eliminate any possibility of illegal memory
179   access. */
180void winservice_notify_stop(void)
181{
182  if (winservice_svnserve_accept_socket != INVALID_SOCKET)
183    closesocket(winservice_svnserve_accept_socket);
184}
185#endif /* _WIN32 */
186
187
188/* Option codes and descriptions for svnserve.
189 *
190 * The entire list must be terminated with an entry of nulls.
191 *
192 * APR requires that options without abbreviations
193 * have codes greater than 255.
194 */
195#define SVNSERVE_OPT_LISTEN_PORT     256
196#define SVNSERVE_OPT_LISTEN_HOST     257
197#define SVNSERVE_OPT_FOREGROUND      258
198#define SVNSERVE_OPT_TUNNEL_USER     259
199#define SVNSERVE_OPT_VERSION         260
200#define SVNSERVE_OPT_PID_FILE        261
201#define SVNSERVE_OPT_SERVICE         262
202#define SVNSERVE_OPT_CONFIG_FILE     263
203#define SVNSERVE_OPT_LOG_FILE        264
204#define SVNSERVE_OPT_CACHE_TXDELTAS  265
205#define SVNSERVE_OPT_CACHE_FULLTEXTS 266
206#define SVNSERVE_OPT_CACHE_REVPROPS  267
207#define SVNSERVE_OPT_SINGLE_CONN     268
208#define SVNSERVE_OPT_CLIENT_SPEED    269
209#define SVNSERVE_OPT_VIRTUAL_HOST    270
210#define SVNSERVE_OPT_MIN_THREADS     271
211#define SVNSERVE_OPT_MAX_THREADS     272
212#define SVNSERVE_OPT_BLOCK_READ      273
213
214static const apr_getopt_option_t svnserve__options[] =
215  {
216    {"daemon",           'd', 0, N_("daemon mode")},
217    {"inetd",            'i', 0, N_("inetd mode")},
218    {"tunnel",           't', 0, N_("tunnel mode")},
219    {"listen-once",      'X', 0, N_("listen-once mode (useful for debugging)")},
220#ifdef WIN32
221    {"service",          SVNSERVE_OPT_SERVICE, 0,
222     N_("Windows service mode (Service Control Manager)")},
223#endif
224    {"root",             'r', 1, N_("root of directory to serve")},
225    {"read-only",        'R', 0,
226     N_("force read only, overriding repository config file")},
227    {"config-file",      SVNSERVE_OPT_CONFIG_FILE, 1,
228     N_("read configuration from file ARG")},
229    {"listen-port",       SVNSERVE_OPT_LISTEN_PORT, 1,
230#ifdef WIN32
231     N_("listen port. The default port is 3690.\n"
232        "                             "
233        "[mode: daemon, service, listen-once]")},
234#else
235     N_("listen port. The default port is 3690.\n"
236        "                             "
237        "[mode: daemon, listen-once]")},
238#endif
239    {"listen-host",       SVNSERVE_OPT_LISTEN_HOST, 1,
240#ifdef WIN32
241     N_("listen hostname or IP address\n"
242        "                             "
243        "By default svnserve listens on all addresses.\n"
244        "                             "
245        "[mode: daemon, service, listen-once]")},
246#else
247     N_("listen hostname or IP address\n"
248        "                             "
249        "By default svnserve listens on all addresses.\n"
250        "                             "
251        "[mode: daemon, listen-once]")},
252#endif
253    {"prefer-ipv6",      '6', 0,
254     N_("prefer IPv6 when resolving the listen hostname\n"
255        "                             "
256        "[IPv4 is preferred by default. Using IPv4 and IPv6\n"
257        "                             "
258        "at the same time is not supported in daemon mode.\n"
259        "                             "
260        "Use inetd mode or tunnel mode if you need this.]")},
261    {"compression",      'c', 1,
262     N_("compression level to use for network transmissions\n"
263        "                             "
264        "[0 .. no compression, 5 .. default, \n"
265        "                             "
266        " 9 .. maximum compression]")},
267    {"memory-cache-size", 'M', 1,
268     N_("size of the extra in-memory cache in MB used to\n"
269        "                             "
270        "minimize redundant operations.\n"
271        "                             "
272        "Default is 16.\n"
273        "                             "
274        "0 switches to dynamically sized caches.\n"
275        "                             "
276        "[used for FSFS and FSX repositories only]")},
277    {"cache-txdeltas", SVNSERVE_OPT_CACHE_TXDELTAS, 1,
278     N_("enable or disable caching of deltas between older\n"
279        "                             "
280        "revisions.\n"
281        "                             "
282        "Default is yes.\n"
283        "                             "
284        "[used for FSFS and FSX repositories only]")},
285    {"cache-fulltexts", SVNSERVE_OPT_CACHE_FULLTEXTS, 1,
286     N_("enable or disable caching of file contents\n"
287        "                             "
288        "Default is yes.\n"
289        "                             "
290        "[used for FSFS and FSX repositories only]")},
291    {"cache-revprops", SVNSERVE_OPT_CACHE_REVPROPS, 1,
292     N_("enable or disable caching of revision properties.\n"
293        "                             "
294        "Consult the documentation before activating this.\n"
295        "                             "
296        "Default is no.\n"
297        "                             "
298        "[used for FSFS and FSX repositories only]")},
299    {"client-speed", SVNSERVE_OPT_CLIENT_SPEED, 1,
300     N_("Optimize network handling based on the assumption\n"
301        "                             "
302        "that most clients are connected with a bitrate of\n"
303        "                             "
304        "ARG Mbit/s.\n"
305        "                             "
306        "Default is 0 (optimizations disabled).")},
307    {"block-read", SVNSERVE_OPT_BLOCK_READ, 1,
308     N_("Parse and cache all data found in block instead\n"
309        "                             "
310        "of just the requested item.\n"
311        "                             "
312        "Default is no.\n"
313        "                             "
314        "[used for FSFS repositories in 1.9 format only]")},
315#ifdef CONNECTION_HAVE_THREAD_OPTION
316    /* ### Making the assumption here that WIN32 never has fork and so
317     * ### this option never exists when --service exists. */
318    {"threads",          'T', 0, N_("use threads instead of fork "
319                                    "[mode: daemon]")},
320    {"min-threads",      SVNSERVE_OPT_MIN_THREADS, 1,
321     N_("Minimum number of server threads, even if idle.\n"
322        "                             "
323        "Capped to max-threads; minimum value is 0.\n"
324        "                             "
325        "Default is 1.\n"
326        "                             "
327        "[used only with --threads]")},
328#if (APR_SIZEOF_VOIDP <= 4)
329    {"max-threads",      SVNSERVE_OPT_MAX_THREADS, 1,
330     N_("Maximum number of server threads, even if there\n"
331        "                             "
332        "are more connections.  Minimum value is 1.\n"
333        "                             "
334        "Default is 64.\n"
335        "                             "
336        "[used only with --threads]")},
337#else
338    {"max-threads",      SVNSERVE_OPT_MAX_THREADS, 1,
339     N_("Maximum number of server threads, even if there\n"
340        "                             "
341        "are more connections.  Minimum value is 1.\n"
342        "                             "
343        "Default is 256.\n"
344        "                             "
345        "[used only with --threads]")},
346#endif
347#endif
348    {"foreground",        SVNSERVE_OPT_FOREGROUND, 0,
349     N_("run in foreground (useful for debugging)\n"
350        "                             "
351        "[mode: daemon]")},
352    {"single-thread",    SVNSERVE_OPT_SINGLE_CONN, 0,
353     N_("handle one connection at a time in the parent\n"
354        "                             "
355        "process (useful for debugging)")},
356    {"log-file",         SVNSERVE_OPT_LOG_FILE, 1,
357     N_("svnserve log file")},
358    {"pid-file",         SVNSERVE_OPT_PID_FILE, 1,
359#ifdef WIN32
360     N_("write server process ID to file ARG\n"
361        "                             "
362        "[mode: daemon, listen-once, service]")},
363#else
364     N_("write server process ID to file ARG\n"
365        "                             "
366        "[mode: daemon, listen-once]")},
367#endif
368    {"tunnel-user",      SVNSERVE_OPT_TUNNEL_USER, 1,
369     N_("tunnel username (default is current uid's name)\n"
370        "                             "
371        "[mode: tunnel]")},
372    {"help",             'h', 0, N_("display this help")},
373    {"virtual-host",     SVNSERVE_OPT_VIRTUAL_HOST, 0,
374     N_("virtual host mode (look for repo in directory\n"
375        "                             "
376        "of provided hostname)")},
377    {"version",           SVNSERVE_OPT_VERSION, 0,
378     N_("show program version information")},
379    {"quiet",            'q', 0,
380     N_("no progress (only errors) to stderr")},
381    {0,                  0,   0, 0}
382  };
383
384static void usage(const char *progname, apr_pool_t *pool)
385{
386  if (!progname)
387    progname = "svnserve";
388
389  svn_error_clear(svn_cmdline_fprintf(stderr, pool,
390                                      _("Type '%s --help' for usage.\n"),
391                                      progname));
392}
393
394static void help(apr_pool_t *pool)
395{
396  apr_size_t i;
397
398#ifdef WIN32
399  svn_error_clear(svn_cmdline_fputs(_("usage: svnserve [-d | -i | -t | -X "
400                                      "| --service] [options]\n"
401                                      "Subversion repository server.\n"
402                                      "Type 'svnserve --version' to see the "
403                                      "program version.\n"
404                                      "\n"
405                                      "Valid options:\n"),
406                                      stdout, pool));
407#else
408  svn_error_clear(svn_cmdline_fputs(_("usage: svnserve [-d | -i | -t | -X] "
409                                      "[options]\n"
410                                      "Subversion repository server.\n"
411                                      "Type 'svnserve --version' to see the "
412                                      "program version.\n"
413                                      "\n"
414                                      "Valid options:\n"),
415                                      stdout, pool));
416#endif
417  for (i = 0; svnserve__options[i].name && svnserve__options[i].optch; i++)
418    {
419      const char *optstr;
420      svn_opt_format_option(&optstr, svnserve__options + i, TRUE, pool);
421      svn_error_clear(svn_cmdline_fprintf(stdout, pool, "  %s\n", optstr));
422    }
423  svn_error_clear(svn_cmdline_fprintf(stdout, pool, "\n"));
424}
425
426static svn_error_t * version(svn_boolean_t quiet, apr_pool_t *pool)
427{
428  const char *fs_desc_start
429    = _("The following repository back-end (FS) modules are available:\n\n");
430
431  svn_stringbuf_t *version_footer;
432
433  version_footer = svn_stringbuf_create(fs_desc_start, pool);
434  SVN_ERR(svn_fs_print_modules(version_footer, pool));
435
436#ifdef SVN_HAVE_SASL
437  svn_stringbuf_appendcstr(version_footer,
438                           _("\nCyrus SASL authentication is available.\n"));
439#endif
440
441  return svn_opt_print_help4(NULL, "svnserve", TRUE, quiet, FALSE,
442                             version_footer->data,
443                             NULL, NULL, NULL, NULL, NULL, pool);
444}
445
446
447#if APR_HAS_FORK
448static void sigchld_handler(int signo)
449{
450  /* Nothing to do; we just need to interrupt the accept(). */
451}
452#endif
453
454/* Redirect stdout to stderr.  ARG is the pool.
455 *
456 * In tunnel or inetd mode, we don't want hook scripts corrupting the
457 * data stream by sending data to stdout, so we need to redirect
458 * stdout somewhere else.  Sending it to stderr is acceptable; sending
459 * it to /dev/null is another option, but apr doesn't provide a way to
460 * do that without also detaching from the controlling terminal.
461 */
462static apr_status_t redirect_stdout(void *arg)
463{
464  apr_pool_t *pool = arg;
465  apr_file_t *out_file, *err_file;
466  apr_status_t apr_err;
467
468  if ((apr_err = apr_file_open_stdout(&out_file, pool)))
469    return apr_err;
470  if ((apr_err = apr_file_open_stderr(&err_file, pool)))
471    return apr_err;
472  return apr_file_dup2(out_file, err_file, pool);
473}
474
475/* Wait for the next client connection to come in from SOCK.  Allocate
476 * the connection in a root pool from CONNECTION_POOLS and assign PARAMS.
477 * Return the connection object in *CONNECTION.
478 *
479 * Use HANDLING_MODE for proper internal cleanup.
480 */
481static svn_error_t *
482accept_connection(connection_t **connection,
483                  apr_socket_t *sock,
484                  serve_params_t *params,
485                  enum connection_handling_mode handling_mode,
486                  apr_pool_t *pool)
487{
488  apr_status_t status;
489
490  /* Non-standard pool handling.  The main thread never blocks to join
491   *         the connection threads so it cannot clean up after each one.  So
492   *         separate pools that can be cleared at thread exit are used. */
493
494  apr_pool_t *connection_pool = svn_pool_create(pool);
495  *connection = apr_pcalloc(connection_pool, sizeof(**connection));
496  (*connection)->pool = connection_pool;
497  (*connection)->params = params;
498  (*connection)->ref_count = 1;
499
500  do
501    {
502      #ifdef WIN32
503      if (winservice_is_stopping())
504        exit(0);
505      #endif
506
507      status = apr_socket_accept(&(*connection)->usock, sock,
508                                 connection_pool);
509      if (handling_mode == connection_mode_fork)
510        {
511          apr_proc_t proc;
512
513          /* Collect any zombie child processes. */
514          while (apr_proc_wait_all_procs(&proc, NULL, NULL, APR_NOWAIT,
515            connection_pool) == APR_CHILD_DONE)
516            ;
517        }
518    }
519  while (APR_STATUS_IS_EINTR(status)
520    || APR_STATUS_IS_ECONNABORTED(status)
521    || APR_STATUS_IS_ECONNRESET(status));
522
523  return status
524       ? svn_error_wrap_apr(status, _("Can't accept client connection"))
525       : SVN_NO_ERROR;
526}
527
528/* Add a reference to CONNECTION, i.e. keep it and it's pool valid unless
529 * that reference gets released using release_shared_pool().
530 */
531static void
532attach_connection(connection_t *connection)
533{
534  svn_atomic_inc(&connection->ref_count);
535}
536
537/* Release a reference to CONNECTION.  If there are no more references,
538 * the connection will be
539 */
540static void
541close_connection(connection_t *connection)
542{
543  /* this will automatically close USOCK */
544  if (svn_atomic_dec(&connection->ref_count) == 0)
545    svn_pool_destroy(connection->pool);
546}
547
548/* Wrapper around serve() that takes a socket instead of a connection.
549 * This is to off-load work from the main thread in threaded and fork modes.
550 *
551 * If an error occurs, log it and also return it.
552 */
553static svn_error_t *
554serve_socket(connection_t *connection,
555             apr_pool_t *pool)
556{
557  /* process the actual request and log errors */
558  svn_error_t *err = serve_interruptable(NULL, connection, NULL, pool);
559  if (err)
560    logger__log_error(connection->params->logger, err, NULL,
561                      get_client_info(connection->conn, connection->params,
562                                      pool));
563
564  return svn_error_trace(err);
565}
566
567#if APR_HAS_THREADS
568
569/* allocate and recycle root pools for connection objects.
570   There should be at most THREADPOOL_MAX_SIZE such pools. */
571static svn_root_pools__t *connection_pools;
572
573/* The global thread pool serving all connections. */
574static apr_thread_pool_t *threads;
575
576/* Very simple load determination callback for serve_interruptable:
577   With less than half the threads in THREADS in use, we can afford to
578   wait in the socket read() function.  Otherwise, poll them round-robin. */
579static svn_boolean_t
580is_busy(connection_t *connection)
581{
582  return apr_thread_pool_threads_count(threads) * 2
583       > apr_thread_pool_thread_max_get(threads);
584}
585
586/* Serve the connection given by DATA.  Under high load, serve only
587   the current command (if any) and then put the connection back into
588   THREAD's task pool. */
589static void * APR_THREAD_FUNC serve_thread(apr_thread_t *tid, void *data)
590{
591  svn_boolean_t done;
592  connection_t *connection = data;
593  svn_error_t *err;
594
595  apr_pool_t *pool = svn_root_pools__acquire_pool(connection_pools);
596
597  /* process the actual request and log errors */
598  err = serve_interruptable(&done, connection, is_busy, pool);
599  if (err)
600    {
601      logger__log_error(connection->params->logger, err, NULL,
602                        get_client_info(connection->conn, connection->params,
603                                        pool));
604      svn_error_clear(err);
605      done = TRUE;
606    }
607  svn_root_pools__release_pool(pool, connection_pools);
608
609  /* Close or re-schedule connection. */
610  if (done)
611    close_connection(connection);
612  else
613    apr_thread_pool_push(threads, serve_thread, connection, 0, NULL);
614
615  return NULL;
616}
617
618#endif
619
620/* Write the PID of the current process as a decimal number, followed by a
621   newline to the file FILENAME, using POOL for temporary allocations. */
622static svn_error_t *write_pid_file(const char *filename, apr_pool_t *pool)
623{
624  apr_file_t *file;
625  const char *contents = apr_psprintf(pool, "%" APR_PID_T_FMT "\n",
626                                             getpid());
627
628  SVN_ERR(svn_io_remove_file2(filename, TRUE, pool));
629  SVN_ERR(svn_io_file_open(&file, filename,
630                           APR_WRITE | APR_CREATE | APR_EXCL,
631                           APR_OS_DEFAULT, pool));
632  SVN_ERR(svn_io_file_write_full(file, contents, strlen(contents), NULL,
633                                 pool));
634
635  SVN_ERR(svn_io_file_close(file, pool));
636
637  return SVN_NO_ERROR;
638}
639
640/* Version compatibility check */
641static svn_error_t *
642check_lib_versions(void)
643{
644  static const svn_version_checklist_t checklist[] =
645    {
646      { "svn_subr",  svn_subr_version },
647      { "svn_repos", svn_repos_version },
648      { "svn_fs",    svn_fs_version },
649      { "svn_delta", svn_delta_version },
650      { "svn_ra_svn", svn_ra_svn_version },
651      { NULL, NULL }
652    };
653  SVN_VERSION_DEFINE(my_version);
654
655  return svn_ver_check_list2(&my_version, checklist, svn_ver_equal);
656}
657
658
659/*
660 * On success, leave *EXIT_CODE untouched and return SVN_NO_ERROR. On error,
661 * either return an error to be displayed, or set *EXIT_CODE to non-zero and
662 * return SVN_NO_ERROR.
663 */
664static svn_error_t *
665sub_main(int *exit_code, int argc, const char *argv[], apr_pool_t *pool)
666{
667  enum run_mode run_mode = run_mode_unspecified;
668  svn_boolean_t foreground = FALSE;
669  apr_socket_t *sock;
670  apr_sockaddr_t *sa;
671  svn_error_t *err;
672  apr_getopt_t *os;
673  int opt;
674  serve_params_t params;
675  const char *arg;
676  apr_status_t status;
677#ifndef WIN32
678  apr_proc_t proc;
679#endif
680  svn_boolean_t is_multi_threaded;
681  enum connection_handling_mode handling_mode = CONNECTION_DEFAULT;
682  svn_boolean_t cache_fulltexts = TRUE;
683  svn_boolean_t cache_txdeltas = TRUE;
684  svn_boolean_t cache_revprops = FALSE;
685  svn_boolean_t use_block_read = FALSE;
686  apr_uint16_t port = SVN_RA_SVN_PORT;
687  const char *host = NULL;
688  int family = APR_INET;
689  apr_int32_t sockaddr_info_flags = 0;
690#if APR_HAVE_IPV6
691  svn_boolean_t prefer_v6 = FALSE;
692#endif
693  svn_boolean_t quiet = FALSE;
694  svn_boolean_t is_version = FALSE;
695  int mode_opt_count = 0;
696  int handling_opt_count = 0;
697  const char *config_filename = NULL;
698  const char *pid_filename = NULL;
699  const char *log_filename = NULL;
700  svn_node_kind_t kind;
701  apr_size_t min_thread_count = THREADPOOL_MIN_SIZE;
702  apr_size_t max_thread_count = THREADPOOL_MAX_SIZE;
703#ifdef SVN_HAVE_SASL
704  SVN_ERR(cyrus_init(pool));
705#endif
706
707  /* Check library versions */
708  SVN_ERR(check_lib_versions());
709
710  /* Initialize the FS library. */
711  SVN_ERR(svn_fs_initialize(pool));
712
713  SVN_ERR(svn_cmdline__getopt_init(&os, argc, argv, pool));
714
715  params.root = "/";
716  params.tunnel = FALSE;
717  params.tunnel_user = NULL;
718  params.read_only = FALSE;
719  params.base = NULL;
720  params.cfg = NULL;
721  params.compression_level = SVN_DELTA_COMPRESSION_LEVEL_DEFAULT;
722  params.logger = NULL;
723  params.config_pool = NULL;
724  params.authz_pool = NULL;
725  params.fs_config = NULL;
726  params.vhost = FALSE;
727  params.username_case = CASE_ASIS;
728  params.memory_cache_size = (apr_uint64_t)-1;
729  params.zero_copy_limit = 0;
730  params.error_check_interval = 4096;
731
732  while (1)
733    {
734      status = apr_getopt_long(os, svnserve__options, &opt, &arg);
735      if (APR_STATUS_IS_EOF(status))
736        break;
737      if (status != APR_SUCCESS)
738        {
739          usage(argv[0], pool);
740          *exit_code = EXIT_FAILURE;
741          return SVN_NO_ERROR;
742        }
743      switch (opt)
744        {
745        case '6':
746#if APR_HAVE_IPV6
747          prefer_v6 = TRUE;
748#endif
749          /* ### Maybe error here if we don't have IPV6 support? */
750          break;
751
752        case 'h':
753          help(pool);
754          return SVN_NO_ERROR;
755
756        case 'q':
757          quiet = TRUE;
758          break;
759
760        case SVNSERVE_OPT_VERSION:
761          is_version = TRUE;
762          break;
763
764        case 'd':
765          if (run_mode != run_mode_daemon)
766            {
767              run_mode = run_mode_daemon;
768              mode_opt_count++;
769            }
770          break;
771
772        case SVNSERVE_OPT_FOREGROUND:
773          foreground = TRUE;
774          break;
775
776        case SVNSERVE_OPT_SINGLE_CONN:
777          handling_mode = connection_mode_single;
778          handling_opt_count++;
779          break;
780
781        case 'i':
782          if (run_mode != run_mode_inetd)
783            {
784              run_mode = run_mode_inetd;
785              mode_opt_count++;
786            }
787          break;
788
789        case SVNSERVE_OPT_LISTEN_PORT:
790          {
791            apr_uint64_t val;
792
793            err = svn_cstring_strtoui64(&val, arg, 0, APR_UINT16_MAX, 10);
794            if (err)
795              return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, err,
796                                       _("Invalid port '%s'"), arg);
797            port = (apr_uint16_t)val;
798          }
799          break;
800
801        case SVNSERVE_OPT_LISTEN_HOST:
802          host = arg;
803          break;
804
805        case 't':
806          if (run_mode != run_mode_tunnel)
807            {
808              run_mode = run_mode_tunnel;
809              mode_opt_count++;
810            }
811          break;
812
813        case SVNSERVE_OPT_TUNNEL_USER:
814          params.tunnel_user = arg;
815          break;
816
817        case 'X':
818          if (run_mode != run_mode_listen_once)
819            {
820              run_mode = run_mode_listen_once;
821              mode_opt_count++;
822            }
823          break;
824
825        case 'r':
826          SVN_ERR(svn_utf_cstring_to_utf8(&params.root, arg, pool));
827
828          SVN_ERR(svn_io_check_resolved_path(params.root, &kind, pool));
829          if (kind != svn_node_dir)
830            {
831              return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
832                       _("Root path '%s' does not exist "
833                         "or is not a directory"), params.root);
834            }
835
836          params.root = svn_dirent_internal_style(params.root, pool);
837          SVN_ERR(svn_dirent_get_absolute(&params.root, params.root, pool));
838          break;
839
840        case 'R':
841          params.read_only = TRUE;
842          break;
843
844        case 'T':
845          handling_mode = connection_mode_thread;
846          handling_opt_count++;
847          break;
848
849        case 'c':
850          params.compression_level = atoi(arg);
851          if (params.compression_level < SVN_DELTA_COMPRESSION_LEVEL_NONE)
852            params.compression_level = SVN_DELTA_COMPRESSION_LEVEL_NONE;
853          if (params.compression_level > SVN_DELTA_COMPRESSION_LEVEL_MAX)
854            params.compression_level = SVN_DELTA_COMPRESSION_LEVEL_MAX;
855          break;
856
857        case 'M':
858          params.memory_cache_size = 0x100000 * apr_strtoi64(arg, NULL, 0);
859          break;
860
861        case SVNSERVE_OPT_CACHE_TXDELTAS:
862          cache_txdeltas = svn_tristate__from_word(arg) == svn_tristate_true;
863          break;
864
865        case SVNSERVE_OPT_CACHE_FULLTEXTS:
866          cache_fulltexts = svn_tristate__from_word(arg) == svn_tristate_true;
867          break;
868
869        case SVNSERVE_OPT_CACHE_REVPROPS:
870          cache_revprops = svn_tristate__from_word(arg) == svn_tristate_true;
871          break;
872
873        case SVNSERVE_OPT_BLOCK_READ:
874          use_block_read = svn_tristate__from_word(arg) == svn_tristate_true;
875          break;
876
877        case SVNSERVE_OPT_CLIENT_SPEED:
878          {
879            apr_size_t bandwidth = (apr_size_t)apr_strtoi64(arg, NULL, 0);
880
881            /* for slower clients, don't try anything fancy */
882            if (bandwidth >= 1000)
883              {
884                /* block other clients for at most 1 ms (at full bandwidth).
885                   Note that the send buffer is 16kB anyways. */
886                params.zero_copy_limit = bandwidth * 120;
887
888                /* check for aborted connections at the same rate */
889                params.error_check_interval = bandwidth * 120;
890              }
891          }
892          break;
893
894        case SVNSERVE_OPT_MIN_THREADS:
895          min_thread_count = (apr_size_t)apr_strtoi64(arg, NULL, 0);
896          break;
897
898        case SVNSERVE_OPT_MAX_THREADS:
899          max_thread_count = (apr_size_t)apr_strtoi64(arg, NULL, 0);
900          break;
901
902#ifdef WIN32
903        case SVNSERVE_OPT_SERVICE:
904          if (run_mode != run_mode_service)
905            {
906              run_mode = run_mode_service;
907              mode_opt_count++;
908            }
909          break;
910#endif
911
912        case SVNSERVE_OPT_CONFIG_FILE:
913          SVN_ERR(svn_utf_cstring_to_utf8(&config_filename, arg, pool));
914          config_filename = svn_dirent_internal_style(config_filename, pool);
915          SVN_ERR(svn_dirent_get_absolute(&config_filename, config_filename,
916                                          pool));
917          break;
918
919        case SVNSERVE_OPT_PID_FILE:
920          SVN_ERR(svn_utf_cstring_to_utf8(&pid_filename, arg, pool));
921          pid_filename = svn_dirent_internal_style(pid_filename, pool);
922          SVN_ERR(svn_dirent_get_absolute(&pid_filename, pid_filename, pool));
923          break;
924
925         case SVNSERVE_OPT_VIRTUAL_HOST:
926           params.vhost = TRUE;
927           break;
928
929         case SVNSERVE_OPT_LOG_FILE:
930          SVN_ERR(svn_utf_cstring_to_utf8(&log_filename, arg, pool));
931          log_filename = svn_dirent_internal_style(log_filename, pool);
932          SVN_ERR(svn_dirent_get_absolute(&log_filename, log_filename, pool));
933          break;
934
935        }
936    }
937
938  if (is_version)
939    {
940      SVN_ERR(version(quiet, pool));
941      return SVN_NO_ERROR;
942    }
943
944  if (os->ind != argc)
945    {
946      usage(argv[0], pool);
947      *exit_code = EXIT_FAILURE;
948      return SVN_NO_ERROR;
949    }
950
951  if (mode_opt_count != 1)
952    {
953      svn_error_clear(svn_cmdline_fputs(
954#ifdef WIN32
955                      _("You must specify exactly one of -d, -i, -t, "
956                        "--service or -X.\n"),
957#else
958                      _("You must specify exactly one of -d, -i, -t or -X.\n"),
959#endif
960                       stderr, pool));
961      usage(argv[0], pool);
962      *exit_code = EXIT_FAILURE;
963      return SVN_NO_ERROR;
964    }
965
966  if (handling_opt_count > 1)
967    {
968      svn_error_clear(svn_cmdline_fputs(
969                      _("You may only specify one of -T or --single-thread\n"),
970                      stderr, pool));
971      usage(argv[0], pool);
972      *exit_code = EXIT_FAILURE;
973      return SVN_NO_ERROR;
974    }
975
976  /* construct object pools */
977  is_multi_threaded = handling_mode == connection_mode_thread;
978  params.fs_config = apr_hash_make(pool);
979  svn_hash_sets(params.fs_config, SVN_FS_CONFIG_FSFS_CACHE_DELTAS,
980                cache_txdeltas ? "1" :"0");
981  svn_hash_sets(params.fs_config, SVN_FS_CONFIG_FSFS_CACHE_FULLTEXTS,
982                cache_fulltexts ? "1" :"0");
983  svn_hash_sets(params.fs_config, SVN_FS_CONFIG_FSFS_CACHE_REVPROPS,
984                cache_revprops ? "2" :"0");
985  svn_hash_sets(params.fs_config, SVN_FS_CONFIG_FSFS_BLOCK_READ,
986                use_block_read ? "1" :"0");
987
988  SVN_ERR(svn_repos__config_pool_create(&params.config_pool,
989                                        is_multi_threaded,
990                                        pool));
991  SVN_ERR(svn_repos__authz_pool_create(&params.authz_pool,
992                                       params.config_pool,
993                                       is_multi_threaded,
994                                       pool));
995
996  /* If a configuration file is specified, load it and any referenced
997   * password and authorization files. */
998  if (config_filename)
999    {
1000      params.base = svn_dirent_dirname(config_filename, pool);
1001
1002      SVN_ERR(svn_repos__config_pool_get(&params.cfg, NULL,
1003                                         params.config_pool,
1004                                         config_filename,
1005                                         TRUE, /* must_exist */
1006                                         FALSE, /* names_case_sensitive */
1007                                         NULL,
1008                                         pool));
1009    }
1010
1011  if (log_filename)
1012    SVN_ERR(logger__create(&params.logger, log_filename, pool));
1013  else if (run_mode == run_mode_listen_once)
1014    SVN_ERR(logger__create_for_stderr(&params.logger, pool));
1015
1016  if (params.tunnel_user && run_mode != run_mode_tunnel)
1017    {
1018      return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1019               _("Option --tunnel-user is only valid in tunnel mode"));
1020    }
1021
1022  if (run_mode == run_mode_inetd || run_mode == run_mode_tunnel)
1023    {
1024      apr_pool_t *connection_pool;
1025      svn_ra_svn_conn_t *conn;
1026      svn_stream_t *stdin_stream;
1027      svn_stream_t *stdout_stream;
1028
1029      params.tunnel = (run_mode == run_mode_tunnel);
1030      apr_pool_cleanup_register(pool, pool, apr_pool_cleanup_null,
1031                                redirect_stdout);
1032
1033      SVN_ERR(svn_stream_for_stdin(&stdin_stream, pool));
1034      SVN_ERR(svn_stream_for_stdout(&stdout_stream, pool));
1035
1036      /* Use a subpool for the connection to ensure that if SASL is used
1037       * the pool cleanup handlers that call sasl_dispose() (connection_pool)
1038       * and sasl_done() (pool) are run in the right order. See issue #3664. */
1039      connection_pool = svn_pool_create(pool);
1040      conn = svn_ra_svn_create_conn4(NULL, stdin_stream, stdout_stream,
1041                                     params.compression_level,
1042                                     params.zero_copy_limit,
1043                                     params.error_check_interval,
1044                                     connection_pool);
1045      err = serve(conn, &params, connection_pool);
1046      svn_pool_destroy(connection_pool);
1047
1048      return err;
1049    }
1050
1051#ifdef WIN32
1052  /* If svnserve needs to run as a Win32 service, then we need to
1053     coordinate with the Service Control Manager (SCM) before
1054     continuing.  This function call registers the svnserve.exe
1055     process with the SCM, waits for the "start" command from the SCM
1056     (which will come very quickly), and confirms that those steps
1057     succeeded.
1058
1059     After this call succeeds, the service is free to run.  At some
1060     point in the future, the SCM will send a message to the service,
1061     requesting that it stop.  This is translated into a call to
1062     winservice_notify_stop().  The service is then responsible for
1063     cleanly terminating.
1064
1065     We need to do this before actually starting the service logic
1066     (opening files, sockets, etc.) because the SCM wants you to
1067     connect *first*, then do your service-specific logic.  If the
1068     service process takes too long to connect to the SCM, then the
1069     SCM will decide that the service is busted, and will give up on
1070     it.
1071     */
1072  if (run_mode == run_mode_service)
1073    {
1074      err = winservice_start();
1075      if (err)
1076        {
1077          svn_handle_error2(err, stderr, FALSE, "svnserve: ");
1078
1079          /* This is the most common error.  It means the user started
1080             svnserve from a shell, and specified the --service
1081             argument.  svnserve cannot be started, as a service, in
1082             this way.  The --service argument is valid only valid if
1083             svnserve is started by the SCM. */
1084          if (err->apr_err ==
1085              APR_FROM_OS_ERROR(ERROR_FAILED_SERVICE_CONTROLLER_CONNECT))
1086            {
1087              svn_error_clear(svn_cmdline_fprintf(stderr, pool,
1088                  _("svnserve: The --service flag is only valid if the"
1089                    " process is started by the Service Control Manager.\n")));
1090            }
1091
1092          svn_error_clear(err);
1093          *exit_code = EXIT_FAILURE;
1094          return SVN_NO_ERROR;
1095        }
1096
1097      /* The service is now in the "starting" state.  Before the SCM will
1098         consider the service "started", this thread must call the
1099         winservice_running() function. */
1100    }
1101#endif /* WIN32 */
1102
1103  /* Make sure we have IPV6 support first before giving apr_sockaddr_info_get
1104     APR_UNSPEC, because it may give us back an IPV6 address even if we can't
1105     create IPV6 sockets. */
1106
1107#if APR_HAVE_IPV6
1108#ifdef MAX_SECS_TO_LINGER
1109  /* ### old APR interface */
1110  status = apr_socket_create(&sock, APR_INET6, SOCK_STREAM, pool);
1111#else
1112  status = apr_socket_create(&sock, APR_INET6, SOCK_STREAM, APR_PROTO_TCP,
1113                             pool);
1114#endif
1115  if (status == 0)
1116    {
1117      apr_socket_close(sock);
1118      family = APR_UNSPEC;
1119
1120      if (prefer_v6)
1121        {
1122          if (host == NULL)
1123            host = "::";
1124          sockaddr_info_flags = APR_IPV6_ADDR_OK;
1125        }
1126      else
1127        {
1128          if (host == NULL)
1129            host = "0.0.0.0";
1130          sockaddr_info_flags = APR_IPV4_ADDR_OK;
1131        }
1132    }
1133#endif
1134
1135  status = apr_sockaddr_info_get(&sa, host, family, port,
1136                                 sockaddr_info_flags, pool);
1137  if (status)
1138    {
1139      return svn_error_wrap_apr(status, _("Can't get address info"));
1140    }
1141
1142
1143#ifdef MAX_SECS_TO_LINGER
1144  /* ### old APR interface */
1145  status = apr_socket_create(&sock, sa->family, SOCK_STREAM, pool);
1146#else
1147  status = apr_socket_create(&sock, sa->family, SOCK_STREAM, APR_PROTO_TCP,
1148                             pool);
1149#endif
1150  if (status)
1151    {
1152      return svn_error_wrap_apr(status, _("Can't create server socket"));
1153    }
1154
1155  /* Prevents "socket in use" errors when server is killed and quickly
1156   * restarted. */
1157  status = apr_socket_opt_set(sock, APR_SO_REUSEADDR, 1);
1158  if (status)
1159    {
1160      return svn_error_wrap_apr(status, _("Can't set options on server socket"));
1161    }
1162
1163  status = apr_socket_bind(sock, sa);
1164  if (status)
1165    {
1166      return svn_error_wrap_apr(status, _("Can't bind server socket"));
1167    }
1168
1169  status = apr_socket_listen(sock, ACCEPT_BACKLOG);
1170  if (status)
1171    {
1172      return svn_error_wrap_apr(status, _("Can't listen on server socket"));
1173    }
1174
1175#if APR_HAS_FORK
1176  if (run_mode != run_mode_listen_once && !foreground)
1177    /* ### ignoring errors... */
1178    apr_proc_detach(APR_PROC_DETACH_DAEMONIZE);
1179
1180  apr_signal(SIGCHLD, sigchld_handler);
1181#endif
1182
1183#ifdef SIGPIPE
1184  /* Disable SIGPIPE generation for the platforms that have it. */
1185  apr_signal(SIGPIPE, SIG_IGN);
1186#endif
1187
1188#ifdef SIGXFSZ
1189  /* Disable SIGXFSZ generation for the platforms that have it, otherwise
1190   * working with large files when compiled against an APR that doesn't have
1191   * large file support will crash the program, which is uncool. */
1192  apr_signal(SIGXFSZ, SIG_IGN);
1193#endif
1194
1195  if (pid_filename)
1196    SVN_ERR(write_pid_file(pid_filename, pool));
1197
1198#ifdef WIN32
1199  status = apr_os_sock_get(&winservice_svnserve_accept_socket, sock);
1200  if (status)
1201    winservice_svnserve_accept_socket = INVALID_SOCKET;
1202
1203  /* At this point, the service is "running".  Notify the SCM. */
1204  if (run_mode == run_mode_service)
1205    winservice_running();
1206#endif
1207
1208  /* Configure FS caches for maximum efficiency with svnserve.
1209   * For pre-forked (i.e. multi-processed) mode of operation,
1210   * keep the per-process caches smaller than the default.
1211   * Also, apply the respective command line parameters, if given. */
1212  {
1213    svn_cache_config_t settings = *svn_cache_config_get();
1214
1215    if (params.memory_cache_size != -1)
1216      settings.cache_size = params.memory_cache_size;
1217
1218    settings.single_threaded = TRUE;
1219    if (handling_mode == connection_mode_thread)
1220      {
1221#if APR_HAS_THREADS
1222        settings.single_threaded = FALSE;
1223#else
1224        /* No requests will be processed at all
1225         * (see "switch (handling_mode)" code further down).
1226         * But if they were, some other synchronization code
1227         * would need to take care of securing integrity of
1228         * APR-based structures. That would include our caches.
1229         */
1230#endif
1231      }
1232
1233    svn_cache_config_set(&settings);
1234  }
1235
1236#if APR_HAS_THREADS
1237  SVN_ERR(svn_root_pools__create(&connection_pools));
1238
1239  if (handling_mode == connection_mode_thread)
1240    {
1241      /* create the thread pool with a valid range of threads */
1242      if (max_thread_count < 1)
1243        max_thread_count = 1;
1244      if (min_thread_count > max_thread_count)
1245        min_thread_count = max_thread_count;
1246
1247      status = apr_thread_pool_create(&threads,
1248                                      min_thread_count,
1249                                      max_thread_count,
1250                                      pool);
1251      if (status)
1252        {
1253          return svn_error_wrap_apr(status, _("Can't create thread pool"));
1254        }
1255
1256      /* let idle threads linger for a while in case more requests are
1257         coming in */
1258      apr_thread_pool_idle_wait_set(threads, THREADPOOL_THREAD_IDLE_LIMIT);
1259
1260      /* don't queue requests unless we reached the worker thread limit */
1261      apr_thread_pool_threshold_set(threads, 0);
1262    }
1263  else
1264    {
1265      threads = NULL;
1266    }
1267#endif
1268
1269  while (1)
1270    {
1271      connection_t *connection = NULL;
1272      SVN_ERR(accept_connection(&connection, sock, &params, handling_mode,
1273                                pool));
1274      if (run_mode == run_mode_listen_once)
1275        {
1276          err = serve_socket(connection, connection->pool);
1277          close_connection(connection);
1278          return err;
1279        }
1280
1281      switch (handling_mode)
1282        {
1283        case connection_mode_fork:
1284#if APR_HAS_FORK
1285          status = apr_proc_fork(&proc, connection->pool);
1286          if (status == APR_INCHILD)
1287            {
1288              /* the child would't listen to the main server's socket */
1289              apr_socket_close(sock);
1290
1291              /* serve_socket() logs any error it returns, so ignore it. */
1292              svn_error_clear(serve_socket(connection, connection->pool));
1293              close_connection(connection);
1294              return SVN_NO_ERROR;
1295            }
1296          else if (status != APR_INPARENT)
1297            {
1298              err = svn_error_wrap_apr(status, "apr_proc_fork");
1299              logger__log_error(params.logger, err, NULL, NULL);
1300              svn_error_clear(err);
1301            }
1302#endif
1303          break;
1304
1305        case connection_mode_thread:
1306          /* Create a detached thread for each connection.  That's not a
1307             particularly sophisticated strategy for a threaded server, it's
1308             little different from forking one process per connection. */
1309#if APR_HAS_THREADS
1310          attach_connection(connection);
1311
1312          status = apr_thread_pool_push(threads, serve_thread, connection,
1313                                        0, NULL);
1314          if (status)
1315            {
1316              return svn_error_wrap_apr(status, _("Can't push task"));
1317            }
1318#endif
1319          break;
1320
1321        case connection_mode_single:
1322          /* Serve one connection at a time. */
1323          /* serve_socket() logs any error it returns, so ignore it. */
1324          svn_error_clear(serve_socket(connection, connection->pool));
1325        }
1326
1327      close_connection(connection);
1328    }
1329
1330  /* NOTREACHED */
1331}
1332
1333int
1334main(int argc, const char *argv[])
1335{
1336  apr_pool_t *pool;
1337  int exit_code = EXIT_SUCCESS;
1338  svn_error_t *err;
1339
1340  /* Initialize the app. */
1341  if (svn_cmdline_init("svnserve", stderr) != EXIT_SUCCESS)
1342    return EXIT_FAILURE;
1343
1344  /* Create our top-level pool. */
1345  pool = apr_allocator_owner_get(svn_pool_create_allocator(TRUE));
1346
1347  err = sub_main(&exit_code, argc, argv, pool);
1348
1349  /* Flush stdout and report if it fails. It would be flushed on exit anyway
1350     but this makes sure that output is not silently lost if it fails. */
1351  err = svn_error_compose_create(err, svn_cmdline_fflush(stdout));
1352
1353  if (err)
1354    {
1355      exit_code = EXIT_FAILURE;
1356      svn_cmdline_handle_exit_error(err, NULL, "svnserve: ");
1357    }
1358
1359#if APR_HAS_THREADS
1360  /* Explicitly wait for all threads to exit.  As we found out with similar
1361     code in our C test framework, the memory pool cleanup below cannot be
1362     trusted to do the right thing. */
1363  if (threads)
1364    apr_thread_pool_destroy(threads);
1365#endif
1366
1367  /* this will also close the server's socket */
1368  svn_pool_destroy(pool);
1369  return exit_code;
1370}
1371