]> CyberLeo.Net >> Repos - FreeBSD/stable/10.git/blob - contrib/subversion/subversion/svnserve/svnserve.c
Copy head (r256279) to stable/10 as part of the 10.0-RELEASE cycle.
[FreeBSD/stable/10.git] / contrib / subversion / subversion / svnserve / svnserve.c
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 \f
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
58 #include "winservice.h"
59
60 #ifdef HAVE_UNISTD_H
61 #include <unistd.h>   /* For getpid() */
62 #endif
63
64 #include "server.h"
65
66 /* The strategy for handling incoming connections.  Some of these may be
67    unavailable due to platform limitations. */
68 enum connection_handling_mode {
69   connection_mode_fork,   /* Create a process per connection */
70   connection_mode_thread, /* Create a thread per connection */
71   connection_mode_single  /* One connection at a time in this process */
72 };
73
74 /* The mode in which to run svnserve */
75 enum run_mode {
76   run_mode_unspecified,
77   run_mode_inetd,
78   run_mode_daemon,
79   run_mode_tunnel,
80   run_mode_listen_once,
81   run_mode_service
82 };
83
84 #if APR_HAS_FORK
85 #if APR_HAS_THREADS
86
87 #define CONNECTION_DEFAULT connection_mode_fork
88 #define CONNECTION_HAVE_THREAD_OPTION
89
90 #else /* ! APR_HAS_THREADS */
91
92 #define CONNECTION_DEFAULT connection_mode_fork
93
94 #endif /* ! APR_HAS_THREADS */
95 #elif APR_HAS_THREADS /* and ! APR_HAS_FORK */
96
97 #define CONNECTION_DEFAULT connection_mode_thread
98
99 #else /* ! APR_HAS_THREADS and ! APR_HAS_FORK */
100
101 #define CONNECTION_DEFAULT connection_mode_single
102
103 #endif
104
105
106 #ifdef WIN32
107 static apr_os_sock_t winservice_svnserve_accept_socket = INVALID_SOCKET;
108
109 /* The SCM calls this function (on an arbitrary thread, not the main()
110    thread!) when it wants to stop the service.
111
112    For now, our strategy is to close the listener socket, in order to
113    unblock main() and cause it to exit its accept loop.  We cannot use
114    apr_socket_close, because that function deletes the apr_socket_t
115    structure, as well as closing the socket handle.  If we called
116    apr_socket_close here, then main() will also call apr_socket_close,
117    resulting in a double-free.  This way, we just close the kernel
118    socket handle, which causes the accept() function call to fail,
119    which causes main() to clean up the socket.  So, memory gets freed
120    only once.
121
122    This isn't pretty, but it's better than a lot of other options.
123    Currently, there is no "right" way to shut down svnserve.
124
125    We store the OS handle rather than a pointer to the apr_socket_t
126    structure in order to eliminate any possibility of illegal memory
127    access. */
128 void winservice_notify_stop(void)
129 {
130   if (winservice_svnserve_accept_socket != INVALID_SOCKET)
131     closesocket(winservice_svnserve_accept_socket);
132 }
133 #endif /* _WIN32 */
134
135
136 /* Option codes and descriptions for svnserve.
137  *
138  * The entire list must be terminated with an entry of nulls.
139  *
140  * APR requires that options without abbreviations
141  * have codes greater than 255.
142  */
143 #define SVNSERVE_OPT_LISTEN_PORT     256
144 #define SVNSERVE_OPT_LISTEN_HOST     257
145 #define SVNSERVE_OPT_FOREGROUND      258
146 #define SVNSERVE_OPT_TUNNEL_USER     259
147 #define SVNSERVE_OPT_VERSION         260
148 #define SVNSERVE_OPT_PID_FILE        261
149 #define SVNSERVE_OPT_SERVICE         262
150 #define SVNSERVE_OPT_CONFIG_FILE     263
151 #define SVNSERVE_OPT_LOG_FILE        264
152 #define SVNSERVE_OPT_CACHE_TXDELTAS  265
153 #define SVNSERVE_OPT_CACHE_FULLTEXTS 266
154 #define SVNSERVE_OPT_CACHE_REVPROPS  267
155 #define SVNSERVE_OPT_SINGLE_CONN     268
156 #define SVNSERVE_OPT_CLIENT_SPEED    269
157 #define SVNSERVE_OPT_VIRTUAL_HOST    270
158
159 static const apr_getopt_option_t svnserve__options[] =
160   {
161     {"daemon",           'd', 0, N_("daemon mode")},
162     {"inetd",            'i', 0, N_("inetd mode")},
163     {"tunnel",           't', 0, N_("tunnel mode")},
164     {"listen-once",      'X', 0, N_("listen-once mode (useful for debugging)")},
165 #ifdef WIN32
166     {"service",          SVNSERVE_OPT_SERVICE, 0,
167      N_("Windows service mode (Service Control Manager)")},
168 #endif
169     {"root",             'r', 1, N_("root of directory to serve")},
170     {"read-only",        'R', 0,
171      N_("force read only, overriding repository config file")},
172     {"config-file",      SVNSERVE_OPT_CONFIG_FILE, 1,
173      N_("read configuration from file ARG")},
174     {"listen-port",       SVNSERVE_OPT_LISTEN_PORT, 1,
175 #ifdef WIN32
176      N_("listen port. The default port is 3690.\n"
177         "                             "
178         "[mode: daemon, service, listen-once]")},
179 #else
180      N_("listen port. The default port is 3690.\n"
181         "                             "
182         "[mode: daemon, listen-once]")},
183 #endif
184     {"listen-host",       SVNSERVE_OPT_LISTEN_HOST, 1,
185 #ifdef WIN32
186      N_("listen hostname or IP address\n"
187         "                             "
188         "By default svnserve listens on all addresses.\n"
189         "                             "
190         "[mode: daemon, service, listen-once]")},
191 #else
192      N_("listen hostname or IP address\n"
193         "                             "
194         "By default svnserve listens on all addresses.\n"
195         "                             "
196         "[mode: daemon, listen-once]")},
197 #endif
198     {"prefer-ipv6",      '6', 0,
199      N_("prefer IPv6 when resolving the listen hostname\n"
200         "                             "
201         "[IPv4 is preferred by default. Using IPv4 and IPv6\n"
202         "                             "
203         "at the same time is not supported in daemon mode.\n"
204         "                             "
205         "Use inetd mode or tunnel mode if you need this.]")},
206     {"compression",      'c', 1,
207      N_("compression level to use for network transmissions\n"
208         "                             "
209         "[0 .. no compression, 5 .. default, \n"
210         "                             "
211         " 9 .. maximum compression]")},
212     {"memory-cache-size", 'M', 1,
213      N_("size of the extra in-memory cache in MB used to\n"
214         "                             "
215         "minimize redundant operations.\n"
216         "                             "
217         "Default is 128 for threaded and 16 for non-\n"
218         "                             "
219         "threaded mode.\n"
220         "                             "
221         "[used for FSFS repositories only]")},
222     {"cache-txdeltas", SVNSERVE_OPT_CACHE_TXDELTAS, 1,
223      N_("enable or disable caching of deltas between older\n"
224         "                             "
225         "revisions.\n"
226         "                             "
227         "Default is no.\n"
228         "                             "
229         "[used for FSFS repositories only]")},
230     {"cache-fulltexts", SVNSERVE_OPT_CACHE_FULLTEXTS, 1,
231      N_("enable or disable caching of file contents\n"
232         "                             "
233         "Default is yes.\n"
234         "                             "
235         "[used for FSFS repositories only]")},
236     {"cache-revprops", SVNSERVE_OPT_CACHE_REVPROPS, 1,
237      N_("enable or disable caching of revision properties.\n"
238         "                             "
239         "Consult the documentation before activating this.\n"
240         "                             "
241         "Default is no.\n"
242         "                             "
243         "[used for FSFS repositories only]")},
244     {"client-speed", SVNSERVE_OPT_CLIENT_SPEED, 1,
245      N_("Optimize network handling based on the assumption\n"
246         "                             "
247         "that most clients are connected with a bitrate of\n"
248         "                             "
249         "ARG Mbit/s.\n"
250         "                             "
251         "Default is 0 (optimizations disabled).")},
252 #ifdef CONNECTION_HAVE_THREAD_OPTION
253     /* ### Making the assumption here that WIN32 never has fork and so
254      * ### this option never exists when --service exists. */
255     {"threads",          'T', 0, N_("use threads instead of fork "
256                                     "[mode: daemon]")},
257 #endif
258     {"foreground",        SVNSERVE_OPT_FOREGROUND, 0,
259      N_("run in foreground (useful for debugging)\n"
260         "                             "
261         "[mode: daemon]")},
262     {"single-thread",    SVNSERVE_OPT_SINGLE_CONN, 0,
263      N_("handle one connection at a time in the parent process\n"
264         "                             "
265         "(useful for debugging)")},
266     {"log-file",         SVNSERVE_OPT_LOG_FILE, 1,
267      N_("svnserve log file")},
268     {"pid-file",         SVNSERVE_OPT_PID_FILE, 1,
269 #ifdef WIN32
270      N_("write server process ID to file ARG\n"
271         "                             "
272         "[mode: daemon, listen-once, service]")},
273 #else
274      N_("write server process ID to file ARG\n"
275         "                             "
276         "[mode: daemon, listen-once]")},
277 #endif
278     {"tunnel-user",      SVNSERVE_OPT_TUNNEL_USER, 1,
279      N_("tunnel username (default is current uid's name)\n"
280         "                             "
281         "[mode: tunnel]")},
282     {"help",             'h', 0, N_("display this help")},
283     {"virtual-host",     SVNSERVE_OPT_VIRTUAL_HOST, 0,
284      N_("virtual host mode (look for repo in directory\n"
285         "                             "
286         "of provided hostname)")},
287     {"version",           SVNSERVE_OPT_VERSION, 0,
288      N_("show program version information")},
289     {"quiet",            'q', 0,
290      N_("no progress (only errors) to stderr")},
291     {0,                  0,   0, 0}
292   };
293
294
295 static void usage(const char *progname, apr_pool_t *pool)
296 {
297   if (!progname)
298     progname = "svnserve";
299
300   svn_error_clear(svn_cmdline_fprintf(stderr, pool,
301                                       _("Type '%s --help' for usage.\n"),
302                                       progname));
303   exit(1);
304 }
305
306 static void help(apr_pool_t *pool)
307 {
308   apr_size_t i;
309
310 #ifdef WIN32
311   svn_error_clear(svn_cmdline_fputs(_("usage: svnserve [-d | -i | -t | -X "
312                                       "| --service] [options]\n"
313                                       "\n"
314                                       "Valid options:\n"),
315                                     stdout, pool));
316 #else
317   svn_error_clear(svn_cmdline_fputs(_("usage: svnserve [-d | -i | -t | -X] "
318                                       "[options]\n"
319                                       "\n"
320                                       "Valid options:\n"),
321                                     stdout, pool));
322 #endif
323   for (i = 0; svnserve__options[i].name && svnserve__options[i].optch; i++)
324     {
325       const char *optstr;
326       svn_opt_format_option(&optstr, svnserve__options + i, TRUE, pool);
327       svn_error_clear(svn_cmdline_fprintf(stdout, pool, "  %s\n", optstr));
328     }
329   svn_error_clear(svn_cmdline_fprintf(stdout, pool, "\n"));
330   exit(0);
331 }
332
333 static svn_error_t * version(svn_boolean_t quiet, apr_pool_t *pool)
334 {
335   const char *fs_desc_start
336     = _("The following repository back-end (FS) modules are available:\n\n");
337
338   svn_stringbuf_t *version_footer;
339
340   version_footer = svn_stringbuf_create(fs_desc_start, pool);
341   SVN_ERR(svn_fs_print_modules(version_footer, pool));
342
343 #ifdef SVN_HAVE_SASL
344   svn_stringbuf_appendcstr(version_footer,
345                            _("\nCyrus SASL authentication is available.\n"));
346 #endif
347
348   return svn_opt_print_help4(NULL, "svnserve", TRUE, quiet, FALSE,
349                              version_footer->data,
350                              NULL, NULL, NULL, NULL, NULL, pool);
351 }
352
353
354 #if APR_HAS_FORK
355 static void sigchld_handler(int signo)
356 {
357   /* Nothing to do; we just need to interrupt the accept(). */
358 }
359 #endif
360
361 /* Redirect stdout to stderr.  ARG is the pool.
362  *
363  * In tunnel or inetd mode, we don't want hook scripts corrupting the
364  * data stream by sending data to stdout, so we need to redirect
365  * stdout somewhere else.  Sending it to stderr is acceptable; sending
366  * it to /dev/null is another option, but apr doesn't provide a way to
367  * do that without also detaching from the controlling terminal.
368  */
369 static apr_status_t redirect_stdout(void *arg)
370 {
371   apr_pool_t *pool = arg;
372   apr_file_t *out_file, *err_file;
373   apr_status_t apr_err;
374
375   if ((apr_err = apr_file_open_stdout(&out_file, pool)))
376     return apr_err;
377   if ((apr_err = apr_file_open_stderr(&err_file, pool)))
378     return apr_err;
379   return apr_file_dup2(out_file, err_file, pool);
380 }
381
382 #if APR_HAS_THREADS
383 /* The pool passed to apr_thread_create can only be released when both
384
385       A: the call to apr_thread_create has returned to the calling thread
386       B: the new thread has started running and reached apr_thread_start_t
387
388    So we set the atomic counter to 2 then both the calling thread and
389    the new thread decrease it and when it reaches 0 the pool can be
390    released.  */
391 struct shared_pool_t {
392   svn_atomic_t count;
393   apr_pool_t *pool;
394 };
395
396 static struct shared_pool_t *
397 attach_shared_pool(apr_pool_t *pool)
398 {
399   struct shared_pool_t *shared = apr_palloc(pool, sizeof(struct shared_pool_t));
400
401   shared->pool = pool;
402   svn_atomic_set(&shared->count, 2);
403
404   return shared;
405 }
406
407 static void
408 release_shared_pool(struct shared_pool_t *shared)
409 {
410   if (svn_atomic_dec(&shared->count) == 0)
411     svn_pool_destroy(shared->pool);
412 }
413 #endif
414
415 /* "Arguments" passed from the main thread to the connection thread */
416 struct serve_thread_t {
417   svn_ra_svn_conn_t *conn;
418   serve_params_t *params;
419   struct shared_pool_t *shared_pool;
420 };
421
422 #if APR_HAS_THREADS
423 static void * APR_THREAD_FUNC serve_thread(apr_thread_t *tid, void *data)
424 {
425   struct serve_thread_t *d = data;
426
427   svn_error_clear(serve(d->conn, d->params, d->shared_pool->pool));
428   release_shared_pool(d->shared_pool);
429
430   return NULL;
431 }
432 #endif
433
434 /* Write the PID of the current process as a decimal number, followed by a
435    newline to the file FILENAME, using POOL for temporary allocations. */
436 static svn_error_t *write_pid_file(const char *filename, apr_pool_t *pool)
437 {
438   apr_file_t *file;
439   const char *contents = apr_psprintf(pool, "%" APR_PID_T_FMT "\n",
440                                              getpid());
441
442   SVN_ERR(svn_io_file_open(&file, filename,
443                            APR_WRITE | APR_CREATE | APR_TRUNCATE,
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 */
454 static svn_error_t *
455 check_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_list(&my_version, checklist);
469 }
470
471
472 int 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 }