1251881Speter/*
2251881Speter * svnserve.c :  Main control function for svnserve
3251881Speter *
4251881Speter * ====================================================================
5251881Speter *    Licensed to the Apache Software Foundation (ASF) under one
6251881Speter *    or more contributor license agreements.  See the NOTICE file
7251881Speter *    distributed with this work for additional information
8251881Speter *    regarding copyright ownership.  The ASF licenses this file
9251881Speter *    to you under the Apache License, Version 2.0 (the
10251881Speter *    "License"); you may not use this file except in compliance
11251881Speter *    with the License.  You may obtain a copy of the License at
12251881Speter *
13251881Speter *      http://www.apache.org/licenses/LICENSE-2.0
14251881Speter *
15251881Speter *    Unless required by applicable law or agreed to in writing,
16251881Speter *    software distributed under the License is distributed on an
17251881Speter *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18251881Speter *    KIND, either express or implied.  See the License for the
19251881Speter *    specific language governing permissions and limitations
20251881Speter *    under the License.
21251881Speter * ====================================================================
22251881Speter */
23251881Speter
24251881Speter
25251881Speter
26251881Speter#define APR_WANT_STRFUNC
27251881Speter#include <apr_want.h>
28251881Speter#include <apr_general.h>
29251881Speter#include <apr_getopt.h>
30251881Speter#include <apr_network_io.h>
31251881Speter#include <apr_signal.h>
32251881Speter#include <apr_thread_proc.h>
33251881Speter#include <apr_portable.h>
34251881Speter
35251881Speter#include <locale.h>
36251881Speter
37251881Speter#include "svn_cmdline.h"
38251881Speter#include "svn_types.h"
39251881Speter#include "svn_pools.h"
40251881Speter#include "svn_error.h"
41251881Speter#include "svn_ra_svn.h"
42251881Speter#include "svn_utf.h"
43251881Speter#include "svn_dirent_uri.h"
44251881Speter#include "svn_path.h"
45251881Speter#include "svn_opt.h"
46251881Speter#include "svn_repos.h"
47251881Speter#include "svn_string.h"
48251881Speter#include "svn_cache_config.h"
49251881Speter#include "svn_version.h"
50251881Speter#include "svn_io.h"
51251881Speter
52251881Speter#include "svn_private_config.h"
53251881Speter
54251881Speter#include "private/svn_dep_compat.h"
55251881Speter#include "private/svn_cmdline_private.h"
56251881Speter#include "private/svn_atomic.h"
57251881Speter
58251881Speter#include "winservice.h"
59251881Speter
60251881Speter#ifdef HAVE_UNISTD_H
61251881Speter#include <unistd.h>   /* For getpid() */
62251881Speter#endif
63251881Speter
64251881Speter#include "server.h"
65251881Speter
66251881Speter/* The strategy for handling incoming connections.  Some of these may be
67251881Speter   unavailable due to platform limitations. */
68251881Speterenum connection_handling_mode {
69251881Speter  connection_mode_fork,   /* Create a process per connection */
70251881Speter  connection_mode_thread, /* Create a thread per connection */
71251881Speter  connection_mode_single  /* One connection at a time in this process */
72251881Speter};
73251881Speter
74251881Speter/* The mode in which to run svnserve */
75251881Speterenum run_mode {
76251881Speter  run_mode_unspecified,
77251881Speter  run_mode_inetd,
78251881Speter  run_mode_daemon,
79251881Speter  run_mode_tunnel,
80251881Speter  run_mode_listen_once,
81251881Speter  run_mode_service
82251881Speter};
83251881Speter
84251881Speter#if APR_HAS_FORK
85251881Speter#if APR_HAS_THREADS
86251881Speter
87251881Speter#define CONNECTION_DEFAULT connection_mode_fork
88251881Speter#define CONNECTION_HAVE_THREAD_OPTION
89251881Speter
90251881Speter#else /* ! APR_HAS_THREADS */
91251881Speter
92251881Speter#define CONNECTION_DEFAULT connection_mode_fork
93251881Speter
94251881Speter#endif /* ! APR_HAS_THREADS */
95251881Speter#elif APR_HAS_THREADS /* and ! APR_HAS_FORK */
96251881Speter
97251881Speter#define CONNECTION_DEFAULT connection_mode_thread
98251881Speter
99251881Speter#else /* ! APR_HAS_THREADS and ! APR_HAS_FORK */
100251881Speter
101251881Speter#define CONNECTION_DEFAULT connection_mode_single
102251881Speter
103251881Speter#endif
104251881Speter
105251881Speter
106251881Speter#ifdef WIN32
107251881Speterstatic apr_os_sock_t winservice_svnserve_accept_socket = INVALID_SOCKET;
108251881Speter
109251881Speter/* The SCM calls this function (on an arbitrary thread, not the main()
110251881Speter   thread!) when it wants to stop the service.
111251881Speter
112251881Speter   For now, our strategy is to close the listener socket, in order to
113251881Speter   unblock main() and cause it to exit its accept loop.  We cannot use
114251881Speter   apr_socket_close, because that function deletes the apr_socket_t
115251881Speter   structure, as well as closing the socket handle.  If we called
116251881Speter   apr_socket_close here, then main() will also call apr_socket_close,
117251881Speter   resulting in a double-free.  This way, we just close the kernel
118251881Speter   socket handle, which causes the accept() function call to fail,
119251881Speter   which causes main() to clean up the socket.  So, memory gets freed
120251881Speter   only once.
121251881Speter
122251881Speter   This isn't pretty, but it's better than a lot of other options.
123251881Speter   Currently, there is no "right" way to shut down svnserve.
124251881Speter
125251881Speter   We store the OS handle rather than a pointer to the apr_socket_t
126251881Speter   structure in order to eliminate any possibility of illegal memory
127251881Speter   access. */
128251881Spetervoid winservice_notify_stop(void)
129251881Speter{
130251881Speter  if (winservice_svnserve_accept_socket != INVALID_SOCKET)
131251881Speter    closesocket(winservice_svnserve_accept_socket);
132251881Speter}
133251881Speter#endif /* _WIN32 */
134251881Speter
135251881Speter
136251881Speter/* Option codes and descriptions for svnserve.
137251881Speter *
138251881Speter * The entire list must be terminated with an entry of nulls.
139251881Speter *
140251881Speter * APR requires that options without abbreviations
141251881Speter * have codes greater than 255.
142251881Speter */
143251881Speter#define SVNSERVE_OPT_LISTEN_PORT     256
144251881Speter#define SVNSERVE_OPT_LISTEN_HOST     257
145251881Speter#define SVNSERVE_OPT_FOREGROUND      258
146251881Speter#define SVNSERVE_OPT_TUNNEL_USER     259
147251881Speter#define SVNSERVE_OPT_VERSION         260
148251881Speter#define SVNSERVE_OPT_PID_FILE        261
149251881Speter#define SVNSERVE_OPT_SERVICE         262
150251881Speter#define SVNSERVE_OPT_CONFIG_FILE     263
151251881Speter#define SVNSERVE_OPT_LOG_FILE        264
152251881Speter#define SVNSERVE_OPT_CACHE_TXDELTAS  265
153251881Speter#define SVNSERVE_OPT_CACHE_FULLTEXTS 266
154251881Speter#define SVNSERVE_OPT_CACHE_REVPROPS  267
155251881Speter#define SVNSERVE_OPT_SINGLE_CONN     268
156251881Speter#define SVNSERVE_OPT_CLIENT_SPEED    269
157251881Speter#define SVNSERVE_OPT_VIRTUAL_HOST    270
158251881Speter
159251881Speterstatic const apr_getopt_option_t svnserve__options[] =
160251881Speter  {
161251881Speter    {"daemon",           'd', 0, N_("daemon mode")},
162251881Speter    {"inetd",            'i', 0, N_("inetd mode")},
163251881Speter    {"tunnel",           't', 0, N_("tunnel mode")},
164251881Speter    {"listen-once",      'X', 0, N_("listen-once mode (useful for debugging)")},
165251881Speter#ifdef WIN32
166251881Speter    {"service",          SVNSERVE_OPT_SERVICE, 0,
167251881Speter     N_("Windows service mode (Service Control Manager)")},
168251881Speter#endif
169251881Speter    {"root",             'r', 1, N_("root of directory to serve")},
170251881Speter    {"read-only",        'R', 0,
171251881Speter     N_("force read only, overriding repository config file")},
172251881Speter    {"config-file",      SVNSERVE_OPT_CONFIG_FILE, 1,
173251881Speter     N_("read configuration from file ARG")},
174251881Speter    {"listen-port",       SVNSERVE_OPT_LISTEN_PORT, 1,
175251881Speter#ifdef WIN32
176251881Speter     N_("listen port. The default port is 3690.\n"
177251881Speter        "                             "
178251881Speter        "[mode: daemon, service, listen-once]")},
179251881Speter#else
180251881Speter     N_("listen port. The default port is 3690.\n"
181251881Speter        "                             "
182251881Speter        "[mode: daemon, listen-once]")},
183251881Speter#endif
184251881Speter    {"listen-host",       SVNSERVE_OPT_LISTEN_HOST, 1,
185251881Speter#ifdef WIN32
186251881Speter     N_("listen hostname or IP address\n"
187251881Speter        "                             "
188251881Speter        "By default svnserve listens on all addresses.\n"
189251881Speter        "                             "
190251881Speter        "[mode: daemon, service, listen-once]")},
191251881Speter#else
192251881Speter     N_("listen hostname or IP address\n"
193251881Speter        "                             "
194251881Speter        "By default svnserve listens on all addresses.\n"
195251881Speter        "                             "
196251881Speter        "[mode: daemon, listen-once]")},
197251881Speter#endif
198251881Speter    {"prefer-ipv6",      '6', 0,
199251881Speter     N_("prefer IPv6 when resolving the listen hostname\n"
200251881Speter        "                             "
201251881Speter        "[IPv4 is preferred by default. Using IPv4 and IPv6\n"
202251881Speter        "                             "
203251881Speter        "at the same time is not supported in daemon mode.\n"
204251881Speter        "                             "
205251881Speter        "Use inetd mode or tunnel mode if you need this.]")},
206251881Speter    {"compression",      'c', 1,
207251881Speter     N_("compression level to use for network transmissions\n"
208251881Speter        "                             "
209251881Speter        "[0 .. no compression, 5 .. default, \n"
210251881Speter        "                             "
211251881Speter        " 9 .. maximum compression]")},
212251881Speter    {"memory-cache-size", 'M', 1,
213251881Speter     N_("size of the extra in-memory cache in MB used to\n"
214251881Speter        "                             "
215251881Speter        "minimize redundant operations.\n"
216251881Speter        "                             "
217251881Speter        "Default is 128 for threaded and 16 for non-\n"
218251881Speter        "                             "
219251881Speter        "threaded mode.\n"
220251881Speter        "                             "
221251881Speter        "[used for FSFS repositories only]")},
222251881Speter    {"cache-txdeltas", SVNSERVE_OPT_CACHE_TXDELTAS, 1,
223251881Speter     N_("enable or disable caching of deltas between older\n"
224251881Speter        "                             "
225251881Speter        "revisions.\n"
226251881Speter        "                             "
227251881Speter        "Default is no.\n"
228251881Speter        "                             "
229251881Speter        "[used for FSFS repositories only]")},
230251881Speter    {"cache-fulltexts", SVNSERVE_OPT_CACHE_FULLTEXTS, 1,
231251881Speter     N_("enable or disable caching of file contents\n"
232251881Speter        "                             "
233251881Speter        "Default is yes.\n"
234251881Speter        "                             "
235251881Speter        "[used for FSFS repositories only]")},
236251881Speter    {"cache-revprops", SVNSERVE_OPT_CACHE_REVPROPS, 1,
237251881Speter     N_("enable or disable caching of revision properties.\n"
238251881Speter        "                             "
239251881Speter        "Consult the documentation before activating this.\n"
240251881Speter        "                             "
241251881Speter        "Default is no.\n"
242251881Speter        "                             "
243251881Speter        "[used for FSFS repositories only]")},
244251881Speter    {"client-speed", SVNSERVE_OPT_CLIENT_SPEED, 1,
245251881Speter     N_("Optimize network handling based on the assumption\n"
246251881Speter        "                             "
247251881Speter        "that most clients are connected with a bitrate of\n"
248251881Speter        "                             "
249251881Speter        "ARG Mbit/s.\n"
250251881Speter        "                             "
251251881Speter        "Default is 0 (optimizations disabled).")},
252251881Speter#ifdef CONNECTION_HAVE_THREAD_OPTION
253251881Speter    /* ### Making the assumption here that WIN32 never has fork and so
254251881Speter     * ### this option never exists when --service exists. */
255251881Speter    {"threads",          'T', 0, N_("use threads instead of fork "
256251881Speter                                    "[mode: daemon]")},
257251881Speter#endif
258251881Speter    {"foreground",        SVNSERVE_OPT_FOREGROUND, 0,
259251881Speter     N_("run in foreground (useful for debugging)\n"
260251881Speter        "                             "
261251881Speter        "[mode: daemon]")},
262251881Speter    {"single-thread",    SVNSERVE_OPT_SINGLE_CONN, 0,
263251881Speter     N_("handle one connection at a time in the parent process\n"
264251881Speter        "                             "
265251881Speter        "(useful for debugging)")},
266251881Speter    {"log-file",         SVNSERVE_OPT_LOG_FILE, 1,
267251881Speter     N_("svnserve log file")},
268251881Speter    {"pid-file",         SVNSERVE_OPT_PID_FILE, 1,
269251881Speter#ifdef WIN32
270251881Speter     N_("write server process ID to file ARG\n"
271251881Speter        "                             "
272251881Speter        "[mode: daemon, listen-once, service]")},
273251881Speter#else
274251881Speter     N_("write server process ID to file ARG\n"
275251881Speter        "                             "
276251881Speter        "[mode: daemon, listen-once]")},
277251881Speter#endif
278251881Speter    {"tunnel-user",      SVNSERVE_OPT_TUNNEL_USER, 1,
279251881Speter     N_("tunnel username (default is current uid's name)\n"
280251881Speter        "                             "
281251881Speter        "[mode: tunnel]")},
282251881Speter    {"help",             'h', 0, N_("display this help")},
283251881Speter    {"virtual-host",     SVNSERVE_OPT_VIRTUAL_HOST, 0,
284251881Speter     N_("virtual host mode (look for repo in directory\n"
285251881Speter        "                             "
286251881Speter        "of provided hostname)")},
287251881Speter    {"version",           SVNSERVE_OPT_VERSION, 0,
288251881Speter     N_("show program version information")},
289251881Speter    {"quiet",            'q', 0,
290251881Speter     N_("no progress (only errors) to stderr")},
291251881Speter    {0,                  0,   0, 0}
292251881Speter  };
293251881Speter
294251881Speter
295251881Speterstatic void usage(const char *progname, apr_pool_t *pool)
296251881Speter{
297251881Speter  if (!progname)
298251881Speter    progname = "svnserve";
299251881Speter
300251881Speter  svn_error_clear(svn_cmdline_fprintf(stderr, pool,
301251881Speter                                      _("Type '%s --help' for usage.\n"),
302251881Speter                                      progname));
303251881Speter  exit(1);
304251881Speter}
305251881Speter
306251881Speterstatic void help(apr_pool_t *pool)
307251881Speter{
308251881Speter  apr_size_t i;
309251881Speter
310251881Speter#ifdef WIN32
311251881Speter  svn_error_clear(svn_cmdline_fputs(_("usage: svnserve [-d | -i | -t | -X "
312251881Speter                                      "| --service] [options]\n"
313251881Speter                                      "\n"
314251881Speter                                      "Valid options:\n"),
315251881Speter                                    stdout, pool));
316251881Speter#else
317251881Speter  svn_error_clear(svn_cmdline_fputs(_("usage: svnserve [-d | -i | -t | -X] "
318251881Speter                                      "[options]\n"
319251881Speter                                      "\n"
320251881Speter                                      "Valid options:\n"),
321251881Speter                                    stdout, pool));
322251881Speter#endif
323251881Speter  for (i = 0; svnserve__options[i].name && svnserve__options[i].optch; i++)
324251881Speter    {
325251881Speter      const char *optstr;
326251881Speter      svn_opt_format_option(&optstr, svnserve__options + i, TRUE, pool);
327251881Speter      svn_error_clear(svn_cmdline_fprintf(stdout, pool, "  %s\n", optstr));
328251881Speter    }
329251881Speter  svn_error_clear(svn_cmdline_fprintf(stdout, pool, "\n"));
330251881Speter  exit(0);
331251881Speter}
332251881Speter
333251881Speterstatic svn_error_t * version(svn_boolean_t quiet, apr_pool_t *pool)
334251881Speter{
335251881Speter  const char *fs_desc_start
336251881Speter    = _("The following repository back-end (FS) modules are available:\n\n");
337251881Speter
338251881Speter  svn_stringbuf_t *version_footer;
339251881Speter
340251881Speter  version_footer = svn_stringbuf_create(fs_desc_start, pool);
341251881Speter  SVN_ERR(svn_fs_print_modules(version_footer, pool));
342251881Speter
343251881Speter#ifdef SVN_HAVE_SASL
344251881Speter  svn_stringbuf_appendcstr(version_footer,
345251881Speter                           _("\nCyrus SASL authentication is available.\n"));
346251881Speter#endif
347251881Speter
348251881Speter  return svn_opt_print_help4(NULL, "svnserve", TRUE, quiet, FALSE,
349251881Speter                             version_footer->data,
350251881Speter                             NULL, NULL, NULL, NULL, NULL, pool);
351251881Speter}
352251881Speter
353251881Speter
354251881Speter#if APR_HAS_FORK
355251881Speterstatic void sigchld_handler(int signo)
356251881Speter{
357251881Speter  /* Nothing to do; we just need to interrupt the accept(). */
358251881Speter}
359251881Speter#endif
360251881Speter
361251881Speter/* Redirect stdout to stderr.  ARG is the pool.
362251881Speter *
363251881Speter * In tunnel or inetd mode, we don't want hook scripts corrupting the
364251881Speter * data stream by sending data to stdout, so we need to redirect
365251881Speter * stdout somewhere else.  Sending it to stderr is acceptable; sending
366251881Speter * it to /dev/null is another option, but apr doesn't provide a way to
367251881Speter * do that without also detaching from the controlling terminal.
368251881Speter */
369251881Speterstatic apr_status_t redirect_stdout(void *arg)
370251881Speter{
371251881Speter  apr_pool_t *pool = arg;
372251881Speter  apr_file_t *out_file, *err_file;
373251881Speter  apr_status_t apr_err;
374251881Speter
375251881Speter  if ((apr_err = apr_file_open_stdout(&out_file, pool)))
376251881Speter    return apr_err;
377251881Speter  if ((apr_err = apr_file_open_stderr(&err_file, pool)))
378251881Speter    return apr_err;
379251881Speter  return apr_file_dup2(out_file, err_file, pool);
380251881Speter}
381251881Speter
382251881Speter#if APR_HAS_THREADS
383251881Speter/* The pool passed to apr_thread_create can only be released when both
384251881Speter
385251881Speter      A: the call to apr_thread_create has returned to the calling thread
386251881Speter      B: the new thread has started running and reached apr_thread_start_t
387251881Speter
388251881Speter   So we set the atomic counter to 2 then both the calling thread and
389251881Speter   the new thread decrease it and when it reaches 0 the pool can be
390251881Speter   released.  */
391251881Speterstruct shared_pool_t {
392251881Speter  svn_atomic_t count;
393251881Speter  apr_pool_t *pool;
394251881Speter};
395251881Speter
396251881Speterstatic struct shared_pool_t *
397251881Speterattach_shared_pool(apr_pool_t *pool)
398251881Speter{
399251881Speter  struct shared_pool_t *shared = apr_palloc(pool, sizeof(struct shared_pool_t));
400251881Speter
401251881Speter  shared->pool = pool;
402251881Speter  svn_atomic_set(&shared->count, 2);
403251881Speter
404251881Speter  return shared;
405251881Speter}
406251881Speter
407251881Speterstatic void
408251881Speterrelease_shared_pool(struct shared_pool_t *shared)
409251881Speter{
410251881Speter  if (svn_atomic_dec(&shared->count) == 0)
411251881Speter    svn_pool_destroy(shared->pool);
412251881Speter}
413251881Speter#endif
414251881Speter
415251881Speter/* "Arguments" passed from the main thread to the connection thread */
416251881Speterstruct serve_thread_t {
417251881Speter  svn_ra_svn_conn_t *conn;
418251881Speter  serve_params_t *params;
419251881Speter  struct shared_pool_t *shared_pool;
420251881Speter};
421251881Speter
422251881Speter#if APR_HAS_THREADS
423251881Speterstatic void * APR_THREAD_FUNC serve_thread(apr_thread_t *tid, void *data)
424251881Speter{
425251881Speter  struct serve_thread_t *d = data;
426251881Speter
427251881Speter  svn_error_clear(serve(d->conn, d->params, d->shared_pool->pool));
428251881Speter  release_shared_pool(d->shared_pool);
429251881Speter
430251881Speter  return NULL;
431251881Speter}
432251881Speter#endif
433251881Speter
434251881Speter/* Write the PID of the current process as a decimal number, followed by a
435251881Speter   newline to the file FILENAME, using POOL for temporary allocations. */
436251881Speterstatic svn_error_t *write_pid_file(const char *filename, apr_pool_t *pool)
437251881Speter{
438251881Speter  apr_file_t *file;
439251881Speter  const char *contents = apr_psprintf(pool, "%" APR_PID_T_FMT "\n",
440251881Speter                                             getpid());
441251881Speter
442251881Speter  SVN_ERR(svn_io_file_open(&file, filename,
443251881Speter                           APR_WRITE | APR_CREATE | APR_TRUNCATE,
444251881Speter                           APR_OS_DEFAULT, pool));
445251881Speter  SVN_ERR(svn_io_file_write_full(file, contents, strlen(contents), NULL,
446251881Speter                                 pool));
447251881Speter
448251881Speter  SVN_ERR(svn_io_file_close(file, pool));
449251881Speter
450251881Speter  return SVN_NO_ERROR;
451251881Speter}
452251881Speter
453251881Speter/* Version compatibility check */
454251881Speterstatic svn_error_t *
455251881Spetercheck_lib_versions(void)
456251881Speter{
457251881Speter  static const svn_version_checklist_t checklist[] =
458251881Speter    {
459251881Speter      { "svn_subr",  svn_subr_version },
460251881Speter      { "svn_repos", svn_repos_version },
461251881Speter      { "svn_fs",    svn_fs_version },
462251881Speter      { "svn_delta", svn_delta_version },
463251881Speter      { "svn_ra_svn", svn_ra_svn_version },
464251881Speter      { NULL, NULL }
465251881Speter    };
466251881Speter  SVN_VERSION_DEFINE(my_version);
467251881Speter
468251881Speter  return svn_ver_check_list(&my_version, checklist);
469251881Speter}
470251881Speter
471251881Speter
472251881Speterint main(int argc, const char *argv[])
473251881Speter{
474251881Speter  enum run_mode run_mode = run_mode_unspecified;
475251881Speter  svn_boolean_t foreground = FALSE;
476251881Speter  apr_socket_t *sock, *usock;
477251881Speter  apr_file_t *in_file, *out_file;
478251881Speter  apr_sockaddr_t *sa;
479251881Speter  apr_pool_t *pool;
480251881Speter  apr_pool_t *connection_pool;
481251881Speter  svn_error_t *err;
482251881Speter  apr_getopt_t *os;
483251881Speter  int opt;
484251881Speter  serve_params_t params;
485251881Speter  const char *arg;
486251881Speter  apr_status_t status;
487251881Speter  svn_ra_svn_conn_t *conn;
488251881Speter  apr_proc_t proc;
489251881Speter#if APR_HAS_THREADS
490251881Speter  apr_threadattr_t *tattr;
491251881Speter  apr_thread_t *tid;
492251881Speter  struct shared_pool_t *shared_pool;
493251881Speter
494251881Speter  struct serve_thread_t *thread_data;
495251881Speter#endif
496251881Speter  enum connection_handling_mode handling_mode = CONNECTION_DEFAULT;
497251881Speter  apr_uint16_t port = SVN_RA_SVN_PORT;
498251881Speter  const char *host = NULL;
499251881Speter  int family = APR_INET;
500251881Speter  apr_int32_t sockaddr_info_flags = 0;
501251881Speter#if APR_HAVE_IPV6
502251881Speter  svn_boolean_t prefer_v6 = FALSE;
503251881Speter#endif
504251881Speter  svn_boolean_t quiet = FALSE;
505251881Speter  svn_boolean_t is_version = FALSE;
506251881Speter  int mode_opt_count = 0;
507251881Speter  int handling_opt_count = 0;
508251881Speter  const char *config_filename = NULL;
509251881Speter  const char *pid_filename = NULL;
510251881Speter  const char *log_filename = NULL;
511251881Speter  svn_node_kind_t kind;
512251881Speter
513251881Speter  /* Initialize the app. */
514251881Speter  if (svn_cmdline_init("svnserve", stderr) != EXIT_SUCCESS)
515251881Speter    return EXIT_FAILURE;
516251881Speter
517251881Speter  /* Create our top-level pool. */
518251881Speter  pool = svn_pool_create(NULL);
519251881Speter
520251881Speter#ifdef SVN_HAVE_SASL
521251881Speter  SVN_INT_ERR(cyrus_init(pool));
522251881Speter#endif
523251881Speter
524251881Speter  /* Check library versions */
525251881Speter  err = check_lib_versions();
526251881Speter  if (err)
527251881Speter    return svn_cmdline_handle_exit_error(err, pool, "svnserve: ");
528251881Speter
529251881Speter  /* Initialize the FS library. */
530251881Speter  err = svn_fs_initialize(pool);
531251881Speter  if (err)
532251881Speter    return svn_cmdline_handle_exit_error(err, pool, "svnserve: ");
533251881Speter
534251881Speter  err = svn_cmdline__getopt_init(&os, argc, argv, pool);
535251881Speter  if (err)
536251881Speter    return svn_cmdline_handle_exit_error(err, pool, "svnserve: ");
537251881Speter
538251881Speter  params.root = "/";
539251881Speter  params.tunnel = FALSE;
540251881Speter  params.tunnel_user = NULL;
541251881Speter  params.read_only = FALSE;
542251881Speter  params.base = NULL;
543251881Speter  params.cfg = NULL;
544251881Speter  params.compression_level = SVN_DELTA_COMPRESSION_LEVEL_DEFAULT;
545251881Speter  params.log_file = NULL;
546251881Speter  params.vhost = FALSE;
547251881Speter  params.username_case = CASE_ASIS;
548251881Speter  params.memory_cache_size = (apr_uint64_t)-1;
549251881Speter  params.cache_fulltexts = TRUE;
550251881Speter  params.cache_txdeltas = FALSE;
551251881Speter  params.cache_revprops = FALSE;
552251881Speter  params.zero_copy_limit = 0;
553251881Speter  params.error_check_interval = 4096;
554251881Speter
555251881Speter  while (1)
556251881Speter    {
557251881Speter      status = apr_getopt_long(os, svnserve__options, &opt, &arg);
558251881Speter      if (APR_STATUS_IS_EOF(status))
559251881Speter        break;
560251881Speter      if (status != APR_SUCCESS)
561251881Speter        usage(argv[0], pool);
562251881Speter      switch (opt)
563251881Speter        {
564251881Speter        case '6':
565251881Speter#if APR_HAVE_IPV6
566251881Speter          prefer_v6 = TRUE;
567251881Speter#endif
568251881Speter          /* ### Maybe error here if we don't have IPV6 support? */
569251881Speter          break;
570251881Speter
571251881Speter        case 'h':
572251881Speter          help(pool);
573251881Speter          break;
574251881Speter
575251881Speter        case 'q':
576251881Speter          quiet = TRUE;
577251881Speter          break;
578251881Speter
579251881Speter        case SVNSERVE_OPT_VERSION:
580251881Speter          is_version = TRUE;
581251881Speter          break;
582251881Speter
583251881Speter        case 'd':
584251881Speter          if (run_mode != run_mode_daemon)
585251881Speter            {
586251881Speter              run_mode = run_mode_daemon;
587251881Speter              mode_opt_count++;
588251881Speter            }
589251881Speter          break;
590251881Speter
591251881Speter        case SVNSERVE_OPT_FOREGROUND:
592251881Speter          foreground = TRUE;
593251881Speter          break;
594251881Speter
595251881Speter        case SVNSERVE_OPT_SINGLE_CONN:
596251881Speter          handling_mode = connection_mode_single;
597251881Speter          handling_opt_count++;
598251881Speter          break;
599251881Speter
600251881Speter        case 'i':
601251881Speter          if (run_mode != run_mode_inetd)
602251881Speter            {
603251881Speter              run_mode = run_mode_inetd;
604251881Speter              mode_opt_count++;
605251881Speter            }
606251881Speter          break;
607251881Speter
608251881Speter        case SVNSERVE_OPT_LISTEN_PORT:
609251881Speter          {
610251881Speter            apr_uint64_t val;
611251881Speter
612251881Speter            err = svn_cstring_strtoui64(&val, arg, 0, APR_UINT16_MAX, 10);
613251881Speter            if (err)
614251881Speter              return svn_cmdline_handle_exit_error(
615251881Speter                       svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, err,
616251881Speter                                         _("Invalid port '%s'"), arg),
617251881Speter                       pool, "svnserve: ");
618251881Speter            port = (apr_uint16_t)val;
619251881Speter          }
620251881Speter          break;
621251881Speter
622251881Speter        case SVNSERVE_OPT_LISTEN_HOST:
623251881Speter          host = arg;
624251881Speter          break;
625251881Speter
626251881Speter        case 't':
627251881Speter          if (run_mode != run_mode_tunnel)
628251881Speter            {
629251881Speter              run_mode = run_mode_tunnel;
630251881Speter              mode_opt_count++;
631251881Speter            }
632251881Speter          break;
633251881Speter
634251881Speter        case SVNSERVE_OPT_TUNNEL_USER:
635251881Speter          params.tunnel_user = arg;
636251881Speter          break;
637251881Speter
638251881Speter        case 'X':
639251881Speter          if (run_mode != run_mode_listen_once)
640251881Speter            {
641251881Speter              run_mode = run_mode_listen_once;
642251881Speter              mode_opt_count++;
643251881Speter            }
644251881Speter          break;
645251881Speter
646251881Speter        case 'r':
647251881Speter          SVN_INT_ERR(svn_utf_cstring_to_utf8(&params.root, arg, pool));
648251881Speter
649251881Speter          err = svn_io_check_resolved_path(params.root, &kind, pool);
650251881Speter          if (err)
651251881Speter            return svn_cmdline_handle_exit_error(err, pool, "svnserve: ");
652251881Speter          if (kind != svn_node_dir)
653251881Speter            {
654251881Speter              svn_error_clear
655251881Speter                (svn_cmdline_fprintf
656251881Speter                   (stderr, pool,
657251881Speter                    _("svnserve: Root path '%s' does not exist "
658251881Speter                      "or is not a directory.\n"), params.root));
659251881Speter              return EXIT_FAILURE;
660251881Speter            }
661251881Speter
662251881Speter          params.root = svn_dirent_internal_style(params.root, pool);
663251881Speter          SVN_INT_ERR(svn_dirent_get_absolute(&params.root, params.root, pool));
664251881Speter          break;
665251881Speter
666251881Speter        case 'R':
667251881Speter          params.read_only = TRUE;
668251881Speter          break;
669251881Speter
670251881Speter        case 'T':
671251881Speter          handling_mode = connection_mode_thread;
672251881Speter          handling_opt_count++;
673251881Speter          break;
674251881Speter
675251881Speter        case 'c':
676251881Speter          params.compression_level = atoi(arg);
677251881Speter          if (params.compression_level < SVN_DELTA_COMPRESSION_LEVEL_NONE)
678251881Speter            params.compression_level = SVN_DELTA_COMPRESSION_LEVEL_NONE;
679251881Speter          if (params.compression_level > SVN_DELTA_COMPRESSION_LEVEL_MAX)
680251881Speter            params.compression_level = SVN_DELTA_COMPRESSION_LEVEL_MAX;
681251881Speter          break;
682251881Speter
683251881Speter        case 'M':
684251881Speter          params.memory_cache_size = 0x100000 * apr_strtoi64(arg, NULL, 0);
685251881Speter          break;
686251881Speter
687251881Speter        case SVNSERVE_OPT_CACHE_TXDELTAS:
688251881Speter          params.cache_txdeltas
689251881Speter             = svn_tristate__from_word(arg) == svn_tristate_true;
690251881Speter          break;
691251881Speter
692251881Speter        case SVNSERVE_OPT_CACHE_FULLTEXTS:
693251881Speter          params.cache_fulltexts
694251881Speter             = svn_tristate__from_word(arg) == svn_tristate_true;
695251881Speter          break;
696251881Speter
697251881Speter        case SVNSERVE_OPT_CACHE_REVPROPS:
698251881Speter          params.cache_revprops
699251881Speter             = svn_tristate__from_word(arg) == svn_tristate_true;
700251881Speter          break;
701251881Speter
702251881Speter        case SVNSERVE_OPT_CLIENT_SPEED:
703251881Speter          {
704251881Speter            apr_size_t bandwidth = (apr_size_t)apr_strtoi64(arg, NULL, 0);
705251881Speter
706251881Speter            /* for slower clients, don't try anything fancy */
707251881Speter            if (bandwidth >= 1000)
708251881Speter              {
709251881Speter                /* block other clients for at most 1 ms (at full bandwidth).
710251881Speter                   Note that the send buffer is 16kB anyways. */
711251881Speter                params.zero_copy_limit = bandwidth * 120;
712251881Speter
713251881Speter                /* check for aborted connections at the same rate */
714251881Speter                params.error_check_interval = bandwidth * 120;
715251881Speter              }
716251881Speter          }
717251881Speter          break;
718251881Speter
719251881Speter#ifdef WIN32
720251881Speter        case SVNSERVE_OPT_SERVICE:
721251881Speter          if (run_mode != run_mode_service)
722251881Speter            {
723251881Speter              run_mode = run_mode_service;
724251881Speter              mode_opt_count++;
725251881Speter            }
726251881Speter          break;
727251881Speter#endif
728251881Speter
729251881Speter        case SVNSERVE_OPT_CONFIG_FILE:
730251881Speter          SVN_INT_ERR(svn_utf_cstring_to_utf8(&config_filename, arg, pool));
731251881Speter          config_filename = svn_dirent_internal_style(config_filename, pool);
732251881Speter          SVN_INT_ERR(svn_dirent_get_absolute(&config_filename, config_filename,
733251881Speter                                              pool));
734251881Speter          break;
735251881Speter
736251881Speter        case SVNSERVE_OPT_PID_FILE:
737251881Speter          SVN_INT_ERR(svn_utf_cstring_to_utf8(&pid_filename, arg, pool));
738251881Speter          pid_filename = svn_dirent_internal_style(pid_filename, pool);
739251881Speter          SVN_INT_ERR(svn_dirent_get_absolute(&pid_filename, pid_filename,
740251881Speter                                              pool));
741251881Speter          break;
742251881Speter
743251881Speter         case SVNSERVE_OPT_VIRTUAL_HOST:
744251881Speter           params.vhost = TRUE;
745251881Speter           break;
746251881Speter
747251881Speter         case SVNSERVE_OPT_LOG_FILE:
748251881Speter          SVN_INT_ERR(svn_utf_cstring_to_utf8(&log_filename, arg, pool));
749251881Speter          log_filename = svn_dirent_internal_style(log_filename, pool);
750251881Speter          SVN_INT_ERR(svn_dirent_get_absolute(&log_filename, log_filename,
751251881Speter                                              pool));
752251881Speter          break;
753251881Speter
754251881Speter        }
755251881Speter    }
756251881Speter
757251881Speter  if (is_version)
758251881Speter    {
759251881Speter      SVN_INT_ERR(version(quiet, pool));
760251881Speter      exit(0);
761251881Speter    }
762251881Speter
763251881Speter  if (os->ind != argc)
764251881Speter    usage(argv[0], pool);
765251881Speter
766251881Speter  if (mode_opt_count != 1)
767251881Speter    {
768251881Speter      svn_error_clear(svn_cmdline_fputs(
769251881Speter#ifdef WIN32
770251881Speter                      _("You must specify exactly one of -d, -i, -t, "
771251881Speter                        "--service or -X.\n"),
772251881Speter#else
773251881Speter                      _("You must specify exactly one of -d, -i, -t or -X.\n"),
774251881Speter#endif
775251881Speter                       stderr, pool));
776251881Speter      usage(argv[0], pool);
777251881Speter    }
778251881Speter
779251881Speter  if (handling_opt_count > 1)
780251881Speter    {
781251881Speter      svn_error_clear(svn_cmdline_fputs(
782251881Speter                      _("You may only specify one of -T or --single-thread\n"),
783251881Speter                      stderr, pool));
784251881Speter      usage(argv[0], pool);
785251881Speter    }
786251881Speter
787251881Speter  /* If a configuration file is specified, load it and any referenced
788251881Speter   * password and authorization files. */
789251881Speter  if (config_filename)
790251881Speter    {
791251881Speter      params.base = svn_dirent_dirname(config_filename, pool);
792251881Speter
793251881Speter      SVN_INT_ERR(svn_config_read3(&params.cfg, config_filename,
794251881Speter                                   TRUE, /* must_exist */
795251881Speter                                   FALSE, /* section_names_case_sensitive */
796251881Speter                                   FALSE, /* option_names_case_sensitive */
797251881Speter                                   pool));
798251881Speter    }
799251881Speter
800251881Speter  if (log_filename)
801251881Speter    SVN_INT_ERR(svn_io_file_open(&params.log_file, log_filename,
802251881Speter                                 APR_WRITE | APR_CREATE | APR_APPEND,
803251881Speter                                 APR_OS_DEFAULT, pool));
804251881Speter
805251881Speter  if (params.tunnel_user && run_mode != run_mode_tunnel)
806251881Speter    {
807251881Speter      svn_error_clear
808251881Speter        (svn_cmdline_fprintf
809251881Speter           (stderr, pool,
810251881Speter            _("Option --tunnel-user is only valid in tunnel mode.\n")));
811251881Speter      exit(1);
812251881Speter    }
813251881Speter
814251881Speter  if (run_mode == run_mode_inetd || run_mode == run_mode_tunnel)
815251881Speter    {
816251881Speter      params.tunnel = (run_mode == run_mode_tunnel);
817251881Speter      apr_pool_cleanup_register(pool, pool, apr_pool_cleanup_null,
818251881Speter                                redirect_stdout);
819251881Speter      status = apr_file_open_stdin(&in_file, pool);
820251881Speter      if (status)
821251881Speter        {
822251881Speter          err = svn_error_wrap_apr(status, _("Can't open stdin"));
823251881Speter          return svn_cmdline_handle_exit_error(err, pool, "svnserve: ");
824251881Speter        }
825251881Speter
826251881Speter      status = apr_file_open_stdout(&out_file, pool);
827251881Speter      if (status)
828251881Speter        {
829251881Speter          err = svn_error_wrap_apr(status, _("Can't open stdout"));
830251881Speter          return svn_cmdline_handle_exit_error(err, pool, "svnserve: ");
831251881Speter        }
832251881Speter
833251881Speter      /* Use a subpool for the connection to ensure that if SASL is used
834251881Speter       * the pool cleanup handlers that call sasl_dispose() (connection_pool)
835251881Speter       * and sasl_done() (pool) are run in the right order. See issue #3664. */
836251881Speter      connection_pool = svn_pool_create(pool);
837251881Speter      conn = svn_ra_svn_create_conn3(NULL, in_file, out_file,
838251881Speter                                     params.compression_level,
839251881Speter                                     params.zero_copy_limit,
840251881Speter                                     params.error_check_interval,
841251881Speter                                     connection_pool);
842251881Speter      svn_error_clear(serve(conn, &params, connection_pool));
843251881Speter      exit(0);
844251881Speter    }
845251881Speter
846251881Speter#ifdef WIN32
847251881Speter  /* If svnserve needs to run as a Win32 service, then we need to
848251881Speter     coordinate with the Service Control Manager (SCM) before
849251881Speter     continuing.  This function call registers the svnserve.exe
850251881Speter     process with the SCM, waits for the "start" command from the SCM
851251881Speter     (which will come very quickly), and confirms that those steps
852251881Speter     succeeded.
853251881Speter
854251881Speter     After this call succeeds, the service is free to run.  At some
855251881Speter     point in the future, the SCM will send a message to the service,
856251881Speter     requesting that it stop.  This is translated into a call to
857251881Speter     winservice_notify_stop().  The service is then responsible for
858251881Speter     cleanly terminating.
859251881Speter
860251881Speter     We need to do this before actually starting the service logic
861251881Speter     (opening files, sockets, etc.) because the SCM wants you to
862251881Speter     connect *first*, then do your service-specific logic.  If the
863251881Speter     service process takes too long to connect to the SCM, then the
864251881Speter     SCM will decide that the service is busted, and will give up on
865251881Speter     it.
866251881Speter     */
867251881Speter  if (run_mode == run_mode_service)
868251881Speter    {
869251881Speter      err = winservice_start();
870251881Speter      if (err)
871251881Speter        {
872251881Speter          svn_handle_error2(err, stderr, FALSE, "svnserve: ");
873251881Speter
874251881Speter          /* This is the most common error.  It means the user started
875251881Speter             svnserve from a shell, and specified the --service
876251881Speter             argument.  svnserve cannot be started, as a service, in
877251881Speter             this way.  The --service argument is valid only valid if
878251881Speter             svnserve is started by the SCM. */
879251881Speter          if (err->apr_err ==
880251881Speter              APR_FROM_OS_ERROR(ERROR_FAILED_SERVICE_CONTROLLER_CONNECT))
881251881Speter            {
882251881Speter              svn_error_clear(svn_cmdline_fprintf(stderr, pool,
883251881Speter                  _("svnserve: The --service flag is only valid if the"
884251881Speter                    " process is started by the Service Control Manager.\n")));
885251881Speter            }
886251881Speter
887251881Speter          svn_error_clear(err);
888251881Speter          exit(1);
889251881Speter        }
890251881Speter
891251881Speter      /* The service is now in the "starting" state.  Before the SCM will
892251881Speter         consider the service "started", this thread must call the
893251881Speter         winservice_running() function. */
894251881Speter    }
895251881Speter#endif /* WIN32 */
896251881Speter
897251881Speter  /* Make sure we have IPV6 support first before giving apr_sockaddr_info_get
898251881Speter     APR_UNSPEC, because it may give us back an IPV6 address even if we can't
899251881Speter     create IPV6 sockets. */
900251881Speter
901251881Speter#if APR_HAVE_IPV6
902251881Speter#ifdef MAX_SECS_TO_LINGER
903251881Speter  /* ### old APR interface */
904251881Speter  status = apr_socket_create(&sock, APR_INET6, SOCK_STREAM, pool);
905251881Speter#else
906251881Speter  status = apr_socket_create(&sock, APR_INET6, SOCK_STREAM, APR_PROTO_TCP,
907251881Speter                             pool);
908251881Speter#endif
909251881Speter  if (status == 0)
910251881Speter    {
911251881Speter      apr_socket_close(sock);
912251881Speter      family = APR_UNSPEC;
913251881Speter
914251881Speter      if (prefer_v6)
915251881Speter        {
916251881Speter          if (host == NULL)
917251881Speter            host = "::";
918251881Speter          sockaddr_info_flags = APR_IPV6_ADDR_OK;
919251881Speter        }
920251881Speter      else
921251881Speter        {
922251881Speter          if (host == NULL)
923251881Speter            host = "0.0.0.0";
924251881Speter          sockaddr_info_flags = APR_IPV4_ADDR_OK;
925251881Speter        }
926251881Speter    }
927251881Speter#endif
928251881Speter
929251881Speter  status = apr_sockaddr_info_get(&sa, host, family, port,
930251881Speter                                 sockaddr_info_flags, pool);
931251881Speter  if (status)
932251881Speter    {
933251881Speter      err = svn_error_wrap_apr(status, _("Can't get address info"));
934251881Speter      return svn_cmdline_handle_exit_error(err, pool, "svnserve: ");
935251881Speter    }
936251881Speter
937251881Speter
938251881Speter#ifdef MAX_SECS_TO_LINGER
939251881Speter  /* ### old APR interface */
940251881Speter  status = apr_socket_create(&sock, sa->family, SOCK_STREAM, pool);
941251881Speter#else
942251881Speter  status = apr_socket_create(&sock, sa->family, SOCK_STREAM, APR_PROTO_TCP,
943251881Speter                             pool);
944251881Speter#endif
945251881Speter  if (status)
946251881Speter    {
947251881Speter      err = svn_error_wrap_apr(status, _("Can't create server socket"));
948251881Speter      return svn_cmdline_handle_exit_error(err, pool, "svnserve: ");
949251881Speter    }
950251881Speter
951251881Speter  /* Prevents "socket in use" errors when server is killed and quickly
952251881Speter   * restarted. */
953251881Speter  apr_socket_opt_set(sock, APR_SO_REUSEADDR, 1);
954251881Speter
955251881Speter  status = apr_socket_bind(sock, sa);
956251881Speter  if (status)
957251881Speter    {
958251881Speter      err = svn_error_wrap_apr(status, _("Can't bind server socket"));
959251881Speter      return svn_cmdline_handle_exit_error(err, pool, "svnserve: ");
960251881Speter    }
961251881Speter
962251881Speter  apr_socket_listen(sock, 7);
963251881Speter
964251881Speter#if APR_HAS_FORK
965251881Speter  if (run_mode != run_mode_listen_once && !foreground)
966251881Speter    apr_proc_detach(APR_PROC_DETACH_DAEMONIZE);
967251881Speter
968251881Speter  apr_signal(SIGCHLD, sigchld_handler);
969251881Speter#endif
970251881Speter
971251881Speter#ifdef SIGPIPE
972251881Speter  /* Disable SIGPIPE generation for the platforms that have it. */
973251881Speter  apr_signal(SIGPIPE, SIG_IGN);
974251881Speter#endif
975251881Speter
976251881Speter#ifdef SIGXFSZ
977251881Speter  /* Disable SIGXFSZ generation for the platforms that have it, otherwise
978251881Speter   * working with large files when compiled against an APR that doesn't have
979251881Speter   * large file support will crash the program, which is uncool. */
980251881Speter  apr_signal(SIGXFSZ, SIG_IGN);
981251881Speter#endif
982251881Speter
983251881Speter  if (pid_filename)
984251881Speter    SVN_INT_ERR(write_pid_file(pid_filename, pool));
985251881Speter
986251881Speter#ifdef WIN32
987251881Speter  status = apr_os_sock_get(&winservice_svnserve_accept_socket, sock);
988251881Speter  if (status)
989251881Speter    winservice_svnserve_accept_socket = INVALID_SOCKET;
990251881Speter
991251881Speter  /* At this point, the service is "running".  Notify the SCM. */
992251881Speter  if (run_mode == run_mode_service)
993251881Speter    winservice_running();
994251881Speter#endif
995251881Speter
996251881Speter  /* Configure FS caches for maximum efficiency with svnserve.
997251881Speter   * For pre-forked (i.e. multi-processed) mode of operation,
998251881Speter   * keep the per-process caches smaller than the default.
999251881Speter   * Also, apply the respective command line parameters, if given. */
1000251881Speter  {
1001251881Speter    svn_cache_config_t settings = *svn_cache_config_get();
1002251881Speter
1003251881Speter    if (params.memory_cache_size != -1)
1004251881Speter      settings.cache_size = params.memory_cache_size;
1005251881Speter
1006251881Speter    settings.single_threaded = TRUE;
1007251881Speter    if (handling_mode == connection_mode_thread)
1008251881Speter      {
1009251881Speter#if APR_HAS_THREADS
1010251881Speter        settings.single_threaded = FALSE;
1011251881Speter#else
1012251881Speter        /* No requests will be processed at all
1013251881Speter         * (see "switch (handling_mode)" code further down).
1014251881Speter         * But if they were, some other synchronization code
1015251881Speter         * would need to take care of securing integrity of
1016251881Speter         * APR-based structures. That would include our caches.
1017251881Speter         */
1018251881Speter#endif
1019251881Speter      }
1020251881Speter
1021251881Speter    svn_cache_config_set(&settings);
1022251881Speter  }
1023251881Speter
1024251881Speter  while (1)
1025251881Speter    {
1026251881Speter#ifdef WIN32
1027251881Speter      if (winservice_is_stopping())
1028251881Speter        return ERROR_SUCCESS;
1029251881Speter#endif
1030251881Speter
1031251881Speter      /* Non-standard pool handling.  The main thread never blocks to join
1032251881Speter         the connection threads so it cannot clean up after each one.  So
1033251881Speter         separate pools that can be cleared at thread exit are used. */
1034251881Speter
1035251881Speter      connection_pool
1036251881Speter          = apr_allocator_owner_get(svn_pool_create_allocator(FALSE));
1037251881Speter
1038251881Speter      status = apr_socket_accept(&usock, sock, connection_pool);
1039251881Speter      if (handling_mode == connection_mode_fork)
1040251881Speter        {
1041251881Speter          /* Collect any zombie child processes. */
1042251881Speter          while (apr_proc_wait_all_procs(&proc, NULL, NULL, APR_NOWAIT,
1043251881Speter                                         connection_pool) == APR_CHILD_DONE)
1044251881Speter            ;
1045251881Speter        }
1046251881Speter      if (APR_STATUS_IS_EINTR(status)
1047251881Speter          || APR_STATUS_IS_ECONNABORTED(status)
1048251881Speter          || APR_STATUS_IS_ECONNRESET(status))
1049251881Speter        {
1050251881Speter          svn_pool_destroy(connection_pool);
1051251881Speter          continue;
1052251881Speter        }
1053251881Speter      if (status)
1054251881Speter        {
1055251881Speter          err = svn_error_wrap_apr
1056251881Speter            (status, _("Can't accept client connection"));
1057251881Speter          return svn_cmdline_handle_exit_error(err, pool, "svnserve: ");
1058251881Speter        }
1059251881Speter
1060251881Speter      /* Enable TCP keep-alives on the socket so we time out when
1061251881Speter       * the connection breaks due to network-layer problems.
1062251881Speter       * If the peer has dropped the connection due to a network partition
1063251881Speter       * or a crash, or if the peer no longer considers the connection
1064251881Speter       * valid because we are behind a NAT and our public IP has changed,
1065251881Speter       * it will respond to the keep-alive probe with a RST instead of an
1066251881Speter       * acknowledgment segment, which will cause svn to abort the session
1067251881Speter       * even while it is currently blocked waiting for data from the peer. */
1068251881Speter      status = apr_socket_opt_set(usock, APR_SO_KEEPALIVE, 1);
1069251881Speter      if (status)
1070251881Speter        {
1071251881Speter          /* It's not a fatal error if we cannot enable keep-alives. */
1072251881Speter        }
1073251881Speter
1074251881Speter      conn = svn_ra_svn_create_conn3(usock, NULL, NULL,
1075251881Speter                                     params.compression_level,
1076251881Speter                                     params.zero_copy_limit,
1077251881Speter                                     params.error_check_interval,
1078251881Speter                                     connection_pool);
1079251881Speter
1080251881Speter      if (run_mode == run_mode_listen_once)
1081251881Speter        {
1082251881Speter          err = serve(conn, &params, connection_pool);
1083251881Speter
1084251881Speter          if (err)
1085251881Speter            svn_handle_error2(err, stdout, FALSE, "svnserve: ");
1086251881Speter          svn_error_clear(err);
1087251881Speter
1088251881Speter          apr_socket_close(usock);
1089251881Speter          apr_socket_close(sock);
1090251881Speter          exit(0);
1091251881Speter        }
1092251881Speter
1093251881Speter      switch (handling_mode)
1094251881Speter        {
1095251881Speter        case connection_mode_fork:
1096251881Speter#if APR_HAS_FORK
1097251881Speter          status = apr_proc_fork(&proc, connection_pool);
1098251881Speter          if (status == APR_INCHILD)
1099251881Speter            {
1100251881Speter              apr_socket_close(sock);
1101251881Speter              err = serve(conn, &params, connection_pool);
1102251881Speter              log_error(err, params.log_file,
1103251881Speter                        svn_ra_svn_conn_remote_host(conn),
1104251881Speter                        NULL, NULL, /* user, repos */
1105251881Speter                        connection_pool);
1106251881Speter              svn_error_clear(err);
1107251881Speter              apr_socket_close(usock);
1108251881Speter              exit(0);
1109251881Speter            }
1110251881Speter          else if (status == APR_INPARENT)
1111251881Speter            {
1112251881Speter              apr_socket_close(usock);
1113251881Speter            }
1114251881Speter          else
1115251881Speter            {
1116251881Speter              err = svn_error_wrap_apr(status, "apr_proc_fork");
1117251881Speter              log_error(err, params.log_file,
1118251881Speter                        svn_ra_svn_conn_remote_host(conn),
1119251881Speter                        NULL, NULL, /* user, repos */
1120251881Speter                        connection_pool);
1121251881Speter              svn_error_clear(err);
1122251881Speter              apr_socket_close(usock);
1123251881Speter            }
1124251881Speter          svn_pool_destroy(connection_pool);
1125251881Speter#endif
1126251881Speter          break;
1127251881Speter
1128251881Speter        case connection_mode_thread:
1129251881Speter          /* Create a detached thread for each connection.  That's not a
1130251881Speter             particularly sophisticated strategy for a threaded server, it's
1131251881Speter             little different from forking one process per connection. */
1132251881Speter#if APR_HAS_THREADS
1133251881Speter          shared_pool = attach_shared_pool(connection_pool);
1134251881Speter          status = apr_threadattr_create(&tattr, connection_pool);
1135251881Speter          if (status)
1136251881Speter            {
1137251881Speter              err = svn_error_wrap_apr(status, _("Can't create threadattr"));
1138251881Speter              svn_handle_error2(err, stderr, FALSE, "svnserve: ");
1139251881Speter              svn_error_clear(err);
1140251881Speter              exit(1);
1141251881Speter            }
1142251881Speter          status = apr_threadattr_detach_set(tattr, 1);
1143251881Speter          if (status)
1144251881Speter            {
1145251881Speter              err = svn_error_wrap_apr(status, _("Can't set detached state"));
1146251881Speter              svn_handle_error2(err, stderr, FALSE, "svnserve: ");
1147251881Speter              svn_error_clear(err);
1148251881Speter              exit(1);
1149251881Speter            }
1150251881Speter          thread_data = apr_palloc(connection_pool, sizeof(*thread_data));
1151251881Speter          thread_data->conn = conn;
1152251881Speter          thread_data->params = &params;
1153251881Speter          thread_data->shared_pool = shared_pool;
1154251881Speter          status = apr_thread_create(&tid, tattr, serve_thread, thread_data,
1155251881Speter                                     shared_pool->pool);
1156251881Speter          if (status)
1157251881Speter            {
1158251881Speter              err = svn_error_wrap_apr(status, _("Can't create thread"));
1159251881Speter              svn_handle_error2(err, stderr, FALSE, "svnserve: ");
1160251881Speter              svn_error_clear(err);
1161251881Speter              exit(1);
1162251881Speter            }
1163251881Speter          release_shared_pool(shared_pool);
1164251881Speter#endif
1165251881Speter          break;
1166251881Speter
1167251881Speter        case connection_mode_single:
1168251881Speter          /* Serve one connection at a time. */
1169251881Speter          svn_error_clear(serve(conn, &params, connection_pool));
1170251881Speter          svn_pool_destroy(connection_pool);
1171251881Speter        }
1172251881Speter    }
1173251881Speter
1174251881Speter  /* NOTREACHED */
1175251881Speter}
1176