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