]> CyberLeo.Net >> Repos - FreeBSD/stable/10.git/blob - contrib/subversion/subversion/svnserve/svnserve.c
MFC r275385 (by bapt):
[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 #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. */
76 enum 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 */
83 enum 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
159 static 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. */
180 void 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
214 static 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
384 static 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
394 static 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
426 static 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
448 static 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  */
462 static 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  */
481 static svn_error_t *
482 accept_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  */
531 static void
532 attach_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  */
540 static void
541 close_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  */
553 static svn_error_t *
554 serve_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. */
571 static svn_root_pools__t *connection_pools;
572
573 /* The global thread pool serving all connections. */
574 static 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. */
579 static svn_boolean_t
580 is_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. */
589 static 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. */
622 static 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 */
641 static svn_error_t *
642 check_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  */
664 static svn_error_t *
665 sub_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
1333 int
1334 main(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 }