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"
51299742Sdim#include "svn_hash.h"
52251881Speter
53251881Speter#include "svn_private_config.h"
54251881Speter
55251881Speter#include "private/svn_dep_compat.h"
56251881Speter#include "private/svn_cmdline_private.h"
57251881Speter#include "private/svn_atomic.h"
58299742Sdim#include "private/svn_mutex.h"
59262253Speter#include "private/svn_subr_private.h"
60251881Speter
61299742Sdim#if APR_HAS_THREADS
62299742Sdim#    include <apr_thread_pool.h>
63299742Sdim#endif
64299742Sdim
65251881Speter#include "winservice.h"
66251881Speter
67251881Speter#ifdef HAVE_UNISTD_H
68251881Speter#include <unistd.h>   /* For getpid() */
69251881Speter#endif
70251881Speter
71251881Speter#include "server.h"
72299742Sdim#include "logger.h"
73251881Speter
74251881Speter/* The strategy for handling incoming connections.  Some of these may be
75251881Speter   unavailable due to platform limitations. */
76251881Speterenum connection_handling_mode {
77251881Speter  connection_mode_fork,   /* Create a process per connection */
78251881Speter  connection_mode_thread, /* Create a thread per connection */
79251881Speter  connection_mode_single  /* One connection at a time in this process */
80251881Speter};
81251881Speter
82251881Speter/* The mode in which to run svnserve */
83251881Speterenum run_mode {
84251881Speter  run_mode_unspecified,
85251881Speter  run_mode_inetd,
86251881Speter  run_mode_daemon,
87251881Speter  run_mode_tunnel,
88251881Speter  run_mode_listen_once,
89251881Speter  run_mode_service
90251881Speter};
91251881Speter
92251881Speter#if APR_HAS_FORK
93251881Speter#if APR_HAS_THREADS
94251881Speter
95251881Speter#define CONNECTION_DEFAULT connection_mode_fork
96251881Speter#define CONNECTION_HAVE_THREAD_OPTION
97251881Speter
98251881Speter#else /* ! APR_HAS_THREADS */
99251881Speter
100251881Speter#define CONNECTION_DEFAULT connection_mode_fork
101251881Speter
102251881Speter#endif /* ! APR_HAS_THREADS */
103251881Speter#elif APR_HAS_THREADS /* and ! APR_HAS_FORK */
104251881Speter
105251881Speter#define CONNECTION_DEFAULT connection_mode_thread
106251881Speter
107251881Speter#else /* ! APR_HAS_THREADS and ! APR_HAS_FORK */
108251881Speter
109251881Speter#define CONNECTION_DEFAULT connection_mode_single
110251881Speter
111251881Speter#endif
112251881Speter
113299742Sdim/* Parameters for the worker thread pool used in threaded mode. */
114251881Speter
115299742Sdim/* Have at least this many worker threads (even if there are no requests
116299742Sdim * to handle).
117299742Sdim *
118299742Sdim * A 0 value is legal but increases the latency for the next incoming
119299742Sdim * request.  Higher values may be useful for servers that experience short
120299742Sdim * bursts of concurrent requests followed by longer idle periods.
121299742Sdim */
122299742Sdim#define THREADPOOL_MIN_SIZE 1
123299742Sdim
124299742Sdim/* Maximum number of worker threads.  If there are more concurrent requests
125299742Sdim * than worker threads, the extra requests get queued.
126299742Sdim *
127299742Sdim * Since very slow connections will hog a full thread for a potentially
128299742Sdim * long time before timing out, be sure to not set this limit too low.
129299742Sdim *
130299742Sdim * On the other hand, keep in mind that every thread will allocate up to
131299742Sdim * 4MB of unused RAM in the APR allocator of its root pool.  32 bit servers
132299742Sdim * must hence do with fewer threads.
133299742Sdim */
134299742Sdim#if (APR_SIZEOF_VOIDP <= 4)
135299742Sdim#define THREADPOOL_MAX_SIZE 64
136299742Sdim#else
137299742Sdim#define THREADPOOL_MAX_SIZE 256
138299742Sdim#endif
139299742Sdim
140299742Sdim/* Number of microseconds that an unused thread remains in the pool before
141299742Sdim * being terminated.
142299742Sdim *
143299742Sdim * Higher values are useful if clients frequently send small requests and
144299742Sdim * you want to minimize the latency for those.
145299742Sdim */
146299742Sdim#define THREADPOOL_THREAD_IDLE_LIMIT 1000000
147299742Sdim
148299742Sdim/* Number of client to server connections that may concurrently in the
149299742Sdim * TCP 3-way handshake state, i.e. are in the process of being created.
150299742Sdim *
151299742Sdim * Larger values improve scalability with lots of small requests coming
152299742Sdim * on over long latency networks.
153299742Sdim *
154299742Sdim * The OS may actually use a lower limit than specified here.
155299742Sdim */
156299742Sdim#define ACCEPT_BACKLOG 128
157299742Sdim
158251881Speter#ifdef WIN32
159251881Speterstatic apr_os_sock_t winservice_svnserve_accept_socket = INVALID_SOCKET;
160251881Speter
161251881Speter/* The SCM calls this function (on an arbitrary thread, not the main()
162251881Speter   thread!) when it wants to stop the service.
163251881Speter
164251881Speter   For now, our strategy is to close the listener socket, in order to
165251881Speter   unblock main() and cause it to exit its accept loop.  We cannot use
166251881Speter   apr_socket_close, because that function deletes the apr_socket_t
167251881Speter   structure, as well as closing the socket handle.  If we called
168251881Speter   apr_socket_close here, then main() will also call apr_socket_close,
169251881Speter   resulting in a double-free.  This way, we just close the kernel
170251881Speter   socket handle, which causes the accept() function call to fail,
171251881Speter   which causes main() to clean up the socket.  So, memory gets freed
172251881Speter   only once.
173251881Speter
174251881Speter   This isn't pretty, but it's better than a lot of other options.
175251881Speter   Currently, there is no "right" way to shut down svnserve.
176251881Speter
177251881Speter   We store the OS handle rather than a pointer to the apr_socket_t
178251881Speter   structure in order to eliminate any possibility of illegal memory
179251881Speter   access. */
180251881Spetervoid winservice_notify_stop(void)
181251881Speter{
182251881Speter  if (winservice_svnserve_accept_socket != INVALID_SOCKET)
183251881Speter    closesocket(winservice_svnserve_accept_socket);
184251881Speter}
185251881Speter#endif /* _WIN32 */
186251881Speter
187251881Speter
188251881Speter/* Option codes and descriptions for svnserve.
189251881Speter *
190251881Speter * The entire list must be terminated with an entry of nulls.
191251881Speter *
192251881Speter * APR requires that options without abbreviations
193251881Speter * have codes greater than 255.
194251881Speter */
195251881Speter#define SVNSERVE_OPT_LISTEN_PORT     256
196251881Speter#define SVNSERVE_OPT_LISTEN_HOST     257
197251881Speter#define SVNSERVE_OPT_FOREGROUND      258
198251881Speter#define SVNSERVE_OPT_TUNNEL_USER     259
199251881Speter#define SVNSERVE_OPT_VERSION         260
200251881Speter#define SVNSERVE_OPT_PID_FILE        261
201251881Speter#define SVNSERVE_OPT_SERVICE         262
202251881Speter#define SVNSERVE_OPT_CONFIG_FILE     263
203251881Speter#define SVNSERVE_OPT_LOG_FILE        264
204251881Speter#define SVNSERVE_OPT_CACHE_TXDELTAS  265
205251881Speter#define SVNSERVE_OPT_CACHE_FULLTEXTS 266
206251881Speter#define SVNSERVE_OPT_CACHE_REVPROPS  267
207251881Speter#define SVNSERVE_OPT_SINGLE_CONN     268
208251881Speter#define SVNSERVE_OPT_CLIENT_SPEED    269
209251881Speter#define SVNSERVE_OPT_VIRTUAL_HOST    270
210299742Sdim#define SVNSERVE_OPT_MIN_THREADS     271
211299742Sdim#define SVNSERVE_OPT_MAX_THREADS     272
212299742Sdim#define SVNSERVE_OPT_BLOCK_READ      273
213251881Speter
214251881Speterstatic const apr_getopt_option_t svnserve__options[] =
215251881Speter  {
216251881Speter    {"daemon",           'd', 0, N_("daemon mode")},
217251881Speter    {"inetd",            'i', 0, N_("inetd mode")},
218251881Speter    {"tunnel",           't', 0, N_("tunnel mode")},
219251881Speter    {"listen-once",      'X', 0, N_("listen-once mode (useful for debugging)")},
220251881Speter#ifdef WIN32
221251881Speter    {"service",          SVNSERVE_OPT_SERVICE, 0,
222251881Speter     N_("Windows service mode (Service Control Manager)")},
223251881Speter#endif
224251881Speter    {"root",             'r', 1, N_("root of directory to serve")},
225251881Speter    {"read-only",        'R', 0,
226251881Speter     N_("force read only, overriding repository config file")},
227251881Speter    {"config-file",      SVNSERVE_OPT_CONFIG_FILE, 1,
228251881Speter     N_("read configuration from file ARG")},
229251881Speter    {"listen-port",       SVNSERVE_OPT_LISTEN_PORT, 1,
230251881Speter#ifdef WIN32
231251881Speter     N_("listen port. The default port is 3690.\n"
232251881Speter        "                             "
233251881Speter        "[mode: daemon, service, listen-once]")},
234251881Speter#else
235251881Speter     N_("listen port. The default port is 3690.\n"
236251881Speter        "                             "
237251881Speter        "[mode: daemon, listen-once]")},
238251881Speter#endif
239251881Speter    {"listen-host",       SVNSERVE_OPT_LISTEN_HOST, 1,
240251881Speter#ifdef WIN32
241251881Speter     N_("listen hostname or IP address\n"
242251881Speter        "                             "
243251881Speter        "By default svnserve listens on all addresses.\n"
244251881Speter        "                             "
245251881Speter        "[mode: daemon, service, listen-once]")},
246251881Speter#else
247251881Speter     N_("listen hostname or IP address\n"
248251881Speter        "                             "
249251881Speter        "By default svnserve listens on all addresses.\n"
250251881Speter        "                             "
251251881Speter        "[mode: daemon, listen-once]")},
252251881Speter#endif
253251881Speter    {"prefer-ipv6",      '6', 0,
254251881Speter     N_("prefer IPv6 when resolving the listen hostname\n"
255251881Speter        "                             "
256251881Speter        "[IPv4 is preferred by default. Using IPv4 and IPv6\n"
257251881Speter        "                             "
258251881Speter        "at the same time is not supported in daemon mode.\n"
259251881Speter        "                             "
260251881Speter        "Use inetd mode or tunnel mode if you need this.]")},
261251881Speter    {"compression",      'c', 1,
262251881Speter     N_("compression level to use for network transmissions\n"
263251881Speter        "                             "
264251881Speter        "[0 .. no compression, 5 .. default, \n"
265251881Speter        "                             "
266251881Speter        " 9 .. maximum compression]")},
267251881Speter    {"memory-cache-size", 'M', 1,
268251881Speter     N_("size of the extra in-memory cache in MB used to\n"
269251881Speter        "                             "
270251881Speter        "minimize redundant operations.\n"
271251881Speter        "                             "
272262253Speter        "Default is 16.\n"
273251881Speter        "                             "
274299742Sdim        "0 switches to dynamically sized caches.\n"
275299742Sdim        "                             "
276299742Sdim        "[used for FSFS and FSX repositories only]")},
277251881Speter    {"cache-txdeltas", SVNSERVE_OPT_CACHE_TXDELTAS, 1,
278251881Speter     N_("enable or disable caching of deltas between older\n"
279251881Speter        "                             "
280251881Speter        "revisions.\n"
281251881Speter        "                             "
282299742Sdim        "Default is yes.\n"
283251881Speter        "                             "
284299742Sdim        "[used for FSFS and FSX repositories only]")},
285251881Speter    {"cache-fulltexts", SVNSERVE_OPT_CACHE_FULLTEXTS, 1,
286251881Speter     N_("enable or disable caching of file contents\n"
287251881Speter        "                             "
288251881Speter        "Default is yes.\n"
289251881Speter        "                             "
290299742Sdim        "[used for FSFS and FSX repositories only]")},
291251881Speter    {"cache-revprops", SVNSERVE_OPT_CACHE_REVPROPS, 1,
292251881Speter     N_("enable or disable caching of revision properties.\n"
293251881Speter        "                             "
294251881Speter        "Consult the documentation before activating this.\n"
295251881Speter        "                             "
296251881Speter        "Default is no.\n"
297251881Speter        "                             "
298299742Sdim        "[used for FSFS and FSX repositories only]")},
299251881Speter    {"client-speed", SVNSERVE_OPT_CLIENT_SPEED, 1,
300251881Speter     N_("Optimize network handling based on the assumption\n"
301251881Speter        "                             "
302251881Speter        "that most clients are connected with a bitrate of\n"
303251881Speter        "                             "
304251881Speter        "ARG Mbit/s.\n"
305251881Speter        "                             "
306251881Speter        "Default is 0 (optimizations disabled).")},
307299742Sdim    {"block-read", SVNSERVE_OPT_BLOCK_READ, 1,
308299742Sdim     N_("Parse and cache all data found in block instead\n"
309299742Sdim        "                             "
310299742Sdim        "of just the requested item.\n"
311299742Sdim        "                             "
312299742Sdim        "Default is no.\n"
313299742Sdim        "                             "
314299742Sdim        "[used for FSFS repositories in 1.9 format only]")},
315251881Speter#ifdef CONNECTION_HAVE_THREAD_OPTION
316251881Speter    /* ### Making the assumption here that WIN32 never has fork and so
317251881Speter     * ### this option never exists when --service exists. */
318251881Speter    {"threads",          'T', 0, N_("use threads instead of fork "
319251881Speter                                    "[mode: daemon]")},
320299742Sdim    {"min-threads",      SVNSERVE_OPT_MIN_THREADS, 1,
321299742Sdim     N_("Minimum number of server threads, even if idle.\n"
322299742Sdim        "                             "
323299742Sdim        "Capped to max-threads; minimum value is 0.\n"
324299742Sdim        "                             "
325299742Sdim        "Default is 1.\n"
326299742Sdim        "                             "
327299742Sdim        "[used only with --threads]")},
328299742Sdim#if (APR_SIZEOF_VOIDP <= 4)
329299742Sdim    {"max-threads",      SVNSERVE_OPT_MAX_THREADS, 1,
330299742Sdim     N_("Maximum number of server threads, even if there\n"
331299742Sdim        "                             "
332299742Sdim        "are more connections.  Minimum value is 1.\n"
333299742Sdim        "                             "
334299742Sdim        "Default is 64.\n"
335299742Sdim        "                             "
336299742Sdim        "[used only with --threads]")},
337299742Sdim#else
338299742Sdim    {"max-threads",      SVNSERVE_OPT_MAX_THREADS, 1,
339299742Sdim     N_("Maximum number of server threads, even if there\n"
340299742Sdim        "                             "
341299742Sdim        "are more connections.  Minimum value is 1.\n"
342299742Sdim        "                             "
343299742Sdim        "Default is 256.\n"
344299742Sdim        "                             "
345299742Sdim        "[used only with --threads]")},
346251881Speter#endif
347299742Sdim#endif
348251881Speter    {"foreground",        SVNSERVE_OPT_FOREGROUND, 0,
349251881Speter     N_("run in foreground (useful for debugging)\n"
350251881Speter        "                             "
351251881Speter        "[mode: daemon]")},
352251881Speter    {"single-thread",    SVNSERVE_OPT_SINGLE_CONN, 0,
353299742Sdim     N_("handle one connection at a time in the parent\n"
354251881Speter        "                             "
355299742Sdim        "process (useful for debugging)")},
356251881Speter    {"log-file",         SVNSERVE_OPT_LOG_FILE, 1,
357251881Speter     N_("svnserve log file")},
358251881Speter    {"pid-file",         SVNSERVE_OPT_PID_FILE, 1,
359251881Speter#ifdef WIN32
360251881Speter     N_("write server process ID to file ARG\n"
361251881Speter        "                             "
362251881Speter        "[mode: daemon, listen-once, service]")},
363251881Speter#else
364251881Speter     N_("write server process ID to file ARG\n"
365251881Speter        "                             "
366251881Speter        "[mode: daemon, listen-once]")},
367251881Speter#endif
368251881Speter    {"tunnel-user",      SVNSERVE_OPT_TUNNEL_USER, 1,
369251881Speter     N_("tunnel username (default is current uid's name)\n"
370251881Speter        "                             "
371251881Speter        "[mode: tunnel]")},
372251881Speter    {"help",             'h', 0, N_("display this help")},
373251881Speter    {"virtual-host",     SVNSERVE_OPT_VIRTUAL_HOST, 0,
374251881Speter     N_("virtual host mode (look for repo in directory\n"
375251881Speter        "                             "
376251881Speter        "of provided hostname)")},
377251881Speter    {"version",           SVNSERVE_OPT_VERSION, 0,
378251881Speter     N_("show program version information")},
379251881Speter    {"quiet",            'q', 0,
380251881Speter     N_("no progress (only errors) to stderr")},
381251881Speter    {0,                  0,   0, 0}
382251881Speter  };
383251881Speter
384251881Speterstatic void usage(const char *progname, apr_pool_t *pool)
385251881Speter{
386251881Speter  if (!progname)
387251881Speter    progname = "svnserve";
388251881Speter
389251881Speter  svn_error_clear(svn_cmdline_fprintf(stderr, pool,
390251881Speter                                      _("Type '%s --help' for usage.\n"),
391251881Speter                                      progname));
392251881Speter}
393251881Speter
394251881Speterstatic void help(apr_pool_t *pool)
395251881Speter{
396251881Speter  apr_size_t i;
397251881Speter
398251881Speter#ifdef WIN32
399251881Speter  svn_error_clear(svn_cmdline_fputs(_("usage: svnserve [-d | -i | -t | -X "
400251881Speter                                      "| --service] [options]\n"
401299742Sdim                                      "Subversion repository server.\n"
402299742Sdim                                      "Type 'svnserve --version' to see the "
403299742Sdim                                      "program version.\n"
404251881Speter                                      "\n"
405251881Speter                                      "Valid options:\n"),
406299742Sdim                                      stdout, pool));
407251881Speter#else
408251881Speter  svn_error_clear(svn_cmdline_fputs(_("usage: svnserve [-d | -i | -t | -X] "
409251881Speter                                      "[options]\n"
410299742Sdim                                      "Subversion repository server.\n"
411299742Sdim                                      "Type 'svnserve --version' to see the "
412299742Sdim                                      "program version.\n"
413251881Speter                                      "\n"
414251881Speter                                      "Valid options:\n"),
415299742Sdim                                      stdout, pool));
416251881Speter#endif
417251881Speter  for (i = 0; svnserve__options[i].name && svnserve__options[i].optch; i++)
418251881Speter    {
419251881Speter      const char *optstr;
420251881Speter      svn_opt_format_option(&optstr, svnserve__options + i, TRUE, pool);
421251881Speter      svn_error_clear(svn_cmdline_fprintf(stdout, pool, "  %s\n", optstr));
422251881Speter    }
423251881Speter  svn_error_clear(svn_cmdline_fprintf(stdout, pool, "\n"));
424251881Speter}
425251881Speter
426251881Speterstatic svn_error_t * version(svn_boolean_t quiet, apr_pool_t *pool)
427251881Speter{
428251881Speter  const char *fs_desc_start
429251881Speter    = _("The following repository back-end (FS) modules are available:\n\n");
430251881Speter
431251881Speter  svn_stringbuf_t *version_footer;
432251881Speter
433251881Speter  version_footer = svn_stringbuf_create(fs_desc_start, pool);
434251881Speter  SVN_ERR(svn_fs_print_modules(version_footer, pool));
435251881Speter
436251881Speter#ifdef SVN_HAVE_SASL
437251881Speter  svn_stringbuf_appendcstr(version_footer,
438251881Speter                           _("\nCyrus SASL authentication is available.\n"));
439251881Speter#endif
440251881Speter
441251881Speter  return svn_opt_print_help4(NULL, "svnserve", TRUE, quiet, FALSE,
442251881Speter                             version_footer->data,
443251881Speter                             NULL, NULL, NULL, NULL, NULL, pool);
444251881Speter}
445251881Speter
446251881Speter
447251881Speter#if APR_HAS_FORK
448251881Speterstatic void sigchld_handler(int signo)
449251881Speter{
450251881Speter  /* Nothing to do; we just need to interrupt the accept(). */
451251881Speter}
452251881Speter#endif
453251881Speter
454251881Speter/* Redirect stdout to stderr.  ARG is the pool.
455251881Speter *
456251881Speter * In tunnel or inetd mode, we don't want hook scripts corrupting the
457251881Speter * data stream by sending data to stdout, so we need to redirect
458251881Speter * stdout somewhere else.  Sending it to stderr is acceptable; sending
459251881Speter * it to /dev/null is another option, but apr doesn't provide a way to
460251881Speter * do that without also detaching from the controlling terminal.
461251881Speter */
462251881Speterstatic apr_status_t redirect_stdout(void *arg)
463251881Speter{
464251881Speter  apr_pool_t *pool = arg;
465251881Speter  apr_file_t *out_file, *err_file;
466251881Speter  apr_status_t apr_err;
467251881Speter
468251881Speter  if ((apr_err = apr_file_open_stdout(&out_file, pool)))
469251881Speter    return apr_err;
470251881Speter  if ((apr_err = apr_file_open_stderr(&err_file, pool)))
471251881Speter    return apr_err;
472251881Speter  return apr_file_dup2(out_file, err_file, pool);
473251881Speter}
474251881Speter
475299742Sdim/* Wait for the next client connection to come in from SOCK.  Allocate
476299742Sdim * the connection in a root pool from CONNECTION_POOLS and assign PARAMS.
477299742Sdim * Return the connection object in *CONNECTION.
478299742Sdim *
479299742Sdim * Use HANDLING_MODE for proper internal cleanup.
480299742Sdim */
481299742Sdimstatic svn_error_t *
482299742Sdimaccept_connection(connection_t **connection,
483299742Sdim                  apr_socket_t *sock,
484299742Sdim                  serve_params_t *params,
485299742Sdim                  enum connection_handling_mode handling_mode,
486299742Sdim                  apr_pool_t *pool)
487299742Sdim{
488299742Sdim  apr_status_t status;
489251881Speter
490299742Sdim  /* Non-standard pool handling.  The main thread never blocks to join
491299742Sdim   *         the connection threads so it cannot clean up after each one.  So
492299742Sdim   *         separate pools that can be cleared at thread exit are used. */
493251881Speter
494299742Sdim  apr_pool_t *connection_pool = svn_pool_create(pool);
495299742Sdim  *connection = apr_pcalloc(connection_pool, sizeof(**connection));
496299742Sdim  (*connection)->pool = connection_pool;
497299742Sdim  (*connection)->params = params;
498299742Sdim  (*connection)->ref_count = 1;
499251881Speter
500299742Sdim  do
501299742Sdim    {
502299742Sdim      #ifdef WIN32
503299742Sdim      if (winservice_is_stopping())
504299742Sdim        exit(0);
505299742Sdim      #endif
506251881Speter
507299742Sdim      status = apr_socket_accept(&(*connection)->usock, sock,
508299742Sdim                                 connection_pool);
509299742Sdim      if (handling_mode == connection_mode_fork)
510299742Sdim        {
511299742Sdim          apr_proc_t proc;
512251881Speter
513299742Sdim          /* Collect any zombie child processes. */
514299742Sdim          while (apr_proc_wait_all_procs(&proc, NULL, NULL, APR_NOWAIT,
515299742Sdim            connection_pool) == APR_CHILD_DONE)
516299742Sdim            ;
517299742Sdim        }
518299742Sdim    }
519299742Sdim  while (APR_STATUS_IS_EINTR(status)
520299742Sdim    || APR_STATUS_IS_ECONNABORTED(status)
521299742Sdim    || APR_STATUS_IS_ECONNRESET(status));
522299742Sdim
523299742Sdim  return status
524299742Sdim       ? svn_error_wrap_apr(status, _("Can't accept client connection"))
525299742Sdim       : SVN_NO_ERROR;
526251881Speter}
527251881Speter
528299742Sdim/* Add a reference to CONNECTION, i.e. keep it and it's pool valid unless
529299742Sdim * that reference gets released using release_shared_pool().
530299742Sdim */
531251881Speterstatic void
532299742Sdimattach_connection(connection_t *connection)
533251881Speter{
534299742Sdim  svn_atomic_inc(&connection->ref_count);
535251881Speter}
536251881Speter
537299742Sdim/* Release a reference to CONNECTION.  If there are no more references,
538299742Sdim * the connection will be
539299742Sdim */
540299742Sdimstatic void
541299742Sdimclose_connection(connection_t *connection)
542299742Sdim{
543299742Sdim  /* this will automatically close USOCK */
544299742Sdim  if (svn_atomic_dec(&connection->ref_count) == 0)
545299742Sdim    svn_pool_destroy(connection->pool);
546299742Sdim}
547251881Speter
548299742Sdim/* Wrapper around serve() that takes a socket instead of a connection.
549299742Sdim * This is to off-load work from the main thread in threaded and fork modes.
550299742Sdim *
551299742Sdim * If an error occurs, log it and also return it.
552299742Sdim */
553299742Sdimstatic svn_error_t *
554299742Sdimserve_socket(connection_t *connection,
555299742Sdim             apr_pool_t *pool)
556299742Sdim{
557299742Sdim  /* process the actual request and log errors */
558299742Sdim  svn_error_t *err = serve_interruptable(NULL, connection, NULL, pool);
559299742Sdim  if (err)
560299742Sdim    logger__log_error(connection->params->logger, err, NULL,
561299742Sdim                      get_client_info(connection->conn, connection->params,
562299742Sdim                                      pool));
563299742Sdim
564299742Sdim  return svn_error_trace(err);
565299742Sdim}
566299742Sdim
567251881Speter#if APR_HAS_THREADS
568299742Sdim
569299742Sdim/* allocate and recycle root pools for connection objects.
570299742Sdim   There should be at most THREADPOOL_MAX_SIZE such pools. */
571299742Sdimstatic svn_root_pools__t *connection_pools;
572299742Sdim
573299742Sdim/* The global thread pool serving all connections. */
574299742Sdimstatic apr_thread_pool_t *threads;
575299742Sdim
576299742Sdim/* Very simple load determination callback for serve_interruptable:
577299742Sdim   With less than half the threads in THREADS in use, we can afford to
578299742Sdim   wait in the socket read() function.  Otherwise, poll them round-robin. */
579299742Sdimstatic svn_boolean_t
580299742Sdimis_busy(connection_t *connection)
581299742Sdim{
582299742Sdim  return apr_thread_pool_threads_count(threads) * 2
583299742Sdim       > apr_thread_pool_thread_max_get(threads);
584299742Sdim}
585299742Sdim
586299742Sdim/* Serve the connection given by DATA.  Under high load, serve only
587299742Sdim   the current command (if any) and then put the connection back into
588299742Sdim   THREAD's task pool. */
589251881Speterstatic void * APR_THREAD_FUNC serve_thread(apr_thread_t *tid, void *data)
590251881Speter{
591299742Sdim  svn_boolean_t done;
592299742Sdim  connection_t *connection = data;
593299742Sdim  svn_error_t *err;
594251881Speter
595299742Sdim  apr_pool_t *pool = svn_root_pools__acquire_pool(connection_pools);
596251881Speter
597299742Sdim  /* process the actual request and log errors */
598299742Sdim  err = serve_interruptable(&done, connection, is_busy, pool);
599299742Sdim  if (err)
600299742Sdim    {
601299742Sdim      logger__log_error(connection->params->logger, err, NULL,
602299742Sdim                        get_client_info(connection->conn, connection->params,
603299742Sdim                                        pool));
604299742Sdim      svn_error_clear(err);
605299742Sdim      done = TRUE;
606299742Sdim    }
607299742Sdim  svn_root_pools__release_pool(pool, connection_pools);
608299742Sdim
609299742Sdim  /* Close or re-schedule connection. */
610299742Sdim  if (done)
611299742Sdim    close_connection(connection);
612299742Sdim  else
613299742Sdim    apr_thread_pool_push(threads, serve_thread, connection, 0, NULL);
614299742Sdim
615251881Speter  return NULL;
616251881Speter}
617299742Sdim
618251881Speter#endif
619251881Speter
620251881Speter/* Write the PID of the current process as a decimal number, followed by a
621251881Speter   newline to the file FILENAME, using POOL for temporary allocations. */
622251881Speterstatic svn_error_t *write_pid_file(const char *filename, apr_pool_t *pool)
623251881Speter{
624251881Speter  apr_file_t *file;
625251881Speter  const char *contents = apr_psprintf(pool, "%" APR_PID_T_FMT "\n",
626251881Speter                                             getpid());
627251881Speter
628262253Speter  SVN_ERR(svn_io_remove_file2(filename, TRUE, pool));
629251881Speter  SVN_ERR(svn_io_file_open(&file, filename,
630262253Speter                           APR_WRITE | APR_CREATE | APR_EXCL,
631251881Speter                           APR_OS_DEFAULT, pool));
632251881Speter  SVN_ERR(svn_io_file_write_full(file, contents, strlen(contents), NULL,
633251881Speter                                 pool));
634251881Speter
635251881Speter  SVN_ERR(svn_io_file_close(file, pool));
636251881Speter
637251881Speter  return SVN_NO_ERROR;
638251881Speter}
639251881Speter
640251881Speter/* Version compatibility check */
641251881Speterstatic svn_error_t *
642251881Spetercheck_lib_versions(void)
643251881Speter{
644251881Speter  static const svn_version_checklist_t checklist[] =
645251881Speter    {
646251881Speter      { "svn_subr",  svn_subr_version },
647251881Speter      { "svn_repos", svn_repos_version },
648251881Speter      { "svn_fs",    svn_fs_version },
649251881Speter      { "svn_delta", svn_delta_version },
650251881Speter      { "svn_ra_svn", svn_ra_svn_version },
651251881Speter      { NULL, NULL }
652251881Speter    };
653251881Speter  SVN_VERSION_DEFINE(my_version);
654251881Speter
655262253Speter  return svn_ver_check_list2(&my_version, checklist, svn_ver_equal);
656251881Speter}
657251881Speter
658251881Speter
659299742Sdim/*
660299742Sdim * On success, leave *EXIT_CODE untouched and return SVN_NO_ERROR. On error,
661299742Sdim * either return an error to be displayed, or set *EXIT_CODE to non-zero and
662299742Sdim * return SVN_NO_ERROR.
663299742Sdim */
664299742Sdimstatic svn_error_t *
665299742Sdimsub_main(int *exit_code, int argc, const char *argv[], apr_pool_t *pool)
666251881Speter{
667251881Speter  enum run_mode run_mode = run_mode_unspecified;
668251881Speter  svn_boolean_t foreground = FALSE;
669299742Sdim  apr_socket_t *sock;
670251881Speter  apr_sockaddr_t *sa;
671251881Speter  svn_error_t *err;
672251881Speter  apr_getopt_t *os;
673251881Speter  int opt;
674251881Speter  serve_params_t params;
675251881Speter  const char *arg;
676251881Speter  apr_status_t status;
677299742Sdim#ifndef WIN32
678251881Speter  apr_proc_t proc;
679251881Speter#endif
680299742Sdim  svn_boolean_t is_multi_threaded;
681251881Speter  enum connection_handling_mode handling_mode = CONNECTION_DEFAULT;
682299742Sdim  svn_boolean_t cache_fulltexts = TRUE;
683299742Sdim  svn_boolean_t cache_txdeltas = TRUE;
684299742Sdim  svn_boolean_t cache_revprops = FALSE;
685299742Sdim  svn_boolean_t use_block_read = FALSE;
686251881Speter  apr_uint16_t port = SVN_RA_SVN_PORT;
687251881Speter  const char *host = NULL;
688251881Speter  int family = APR_INET;
689251881Speter  apr_int32_t sockaddr_info_flags = 0;
690251881Speter#if APR_HAVE_IPV6
691251881Speter  svn_boolean_t prefer_v6 = FALSE;
692251881Speter#endif
693251881Speter  svn_boolean_t quiet = FALSE;
694251881Speter  svn_boolean_t is_version = FALSE;
695251881Speter  int mode_opt_count = 0;
696251881Speter  int handling_opt_count = 0;
697251881Speter  const char *config_filename = NULL;
698251881Speter  const char *pid_filename = NULL;
699251881Speter  const char *log_filename = NULL;
700251881Speter  svn_node_kind_t kind;
701299742Sdim  apr_size_t min_thread_count = THREADPOOL_MIN_SIZE;
702299742Sdim  apr_size_t max_thread_count = THREADPOOL_MAX_SIZE;
703251881Speter#ifdef SVN_HAVE_SASL
704299742Sdim  SVN_ERR(cyrus_init(pool));
705251881Speter#endif
706251881Speter
707251881Speter  /* Check library versions */
708299742Sdim  SVN_ERR(check_lib_versions());
709251881Speter
710251881Speter  /* Initialize the FS library. */
711299742Sdim  SVN_ERR(svn_fs_initialize(pool));
712251881Speter
713299742Sdim  SVN_ERR(svn_cmdline__getopt_init(&os, argc, argv, pool));
714251881Speter
715251881Speter  params.root = "/";
716251881Speter  params.tunnel = FALSE;
717251881Speter  params.tunnel_user = NULL;
718251881Speter  params.read_only = FALSE;
719251881Speter  params.base = NULL;
720251881Speter  params.cfg = NULL;
721251881Speter  params.compression_level = SVN_DELTA_COMPRESSION_LEVEL_DEFAULT;
722299742Sdim  params.logger = NULL;
723299742Sdim  params.config_pool = NULL;
724299742Sdim  params.authz_pool = NULL;
725299742Sdim  params.fs_config = NULL;
726251881Speter  params.vhost = FALSE;
727251881Speter  params.username_case = CASE_ASIS;
728251881Speter  params.memory_cache_size = (apr_uint64_t)-1;
729251881Speter  params.zero_copy_limit = 0;
730251881Speter  params.error_check_interval = 4096;
731251881Speter
732251881Speter  while (1)
733251881Speter    {
734251881Speter      status = apr_getopt_long(os, svnserve__options, &opt, &arg);
735251881Speter      if (APR_STATUS_IS_EOF(status))
736251881Speter        break;
737251881Speter      if (status != APR_SUCCESS)
738299742Sdim        {
739299742Sdim          usage(argv[0], pool);
740299742Sdim          *exit_code = EXIT_FAILURE;
741299742Sdim          return SVN_NO_ERROR;
742299742Sdim        }
743251881Speter      switch (opt)
744251881Speter        {
745251881Speter        case '6':
746251881Speter#if APR_HAVE_IPV6
747251881Speter          prefer_v6 = TRUE;
748251881Speter#endif
749251881Speter          /* ### Maybe error here if we don't have IPV6 support? */
750251881Speter          break;
751251881Speter
752251881Speter        case 'h':
753251881Speter          help(pool);
754299742Sdim          return SVN_NO_ERROR;
755251881Speter
756251881Speter        case 'q':
757251881Speter          quiet = TRUE;
758251881Speter          break;
759251881Speter
760251881Speter        case SVNSERVE_OPT_VERSION:
761251881Speter          is_version = TRUE;
762251881Speter          break;
763251881Speter
764251881Speter        case 'd':
765251881Speter          if (run_mode != run_mode_daemon)
766251881Speter            {
767251881Speter              run_mode = run_mode_daemon;
768251881Speter              mode_opt_count++;
769251881Speter            }
770251881Speter          break;
771251881Speter
772251881Speter        case SVNSERVE_OPT_FOREGROUND:
773251881Speter          foreground = TRUE;
774251881Speter          break;
775251881Speter
776251881Speter        case SVNSERVE_OPT_SINGLE_CONN:
777251881Speter          handling_mode = connection_mode_single;
778251881Speter          handling_opt_count++;
779251881Speter          break;
780251881Speter
781251881Speter        case 'i':
782251881Speter          if (run_mode != run_mode_inetd)
783251881Speter            {
784251881Speter              run_mode = run_mode_inetd;
785251881Speter              mode_opt_count++;
786251881Speter            }
787251881Speter          break;
788251881Speter
789251881Speter        case SVNSERVE_OPT_LISTEN_PORT:
790251881Speter          {
791251881Speter            apr_uint64_t val;
792251881Speter
793251881Speter            err = svn_cstring_strtoui64(&val, arg, 0, APR_UINT16_MAX, 10);
794251881Speter            if (err)
795299742Sdim              return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, err,
796299742Sdim                                       _("Invalid port '%s'"), arg);
797251881Speter            port = (apr_uint16_t)val;
798251881Speter          }
799251881Speter          break;
800251881Speter
801251881Speter        case SVNSERVE_OPT_LISTEN_HOST:
802251881Speter          host = arg;
803251881Speter          break;
804251881Speter
805251881Speter        case 't':
806251881Speter          if (run_mode != run_mode_tunnel)
807251881Speter            {
808251881Speter              run_mode = run_mode_tunnel;
809251881Speter              mode_opt_count++;
810251881Speter            }
811251881Speter          break;
812251881Speter
813251881Speter        case SVNSERVE_OPT_TUNNEL_USER:
814251881Speter          params.tunnel_user = arg;
815251881Speter          break;
816251881Speter
817251881Speter        case 'X':
818251881Speter          if (run_mode != run_mode_listen_once)
819251881Speter            {
820251881Speter              run_mode = run_mode_listen_once;
821251881Speter              mode_opt_count++;
822251881Speter            }
823251881Speter          break;
824251881Speter
825251881Speter        case 'r':
826299742Sdim          SVN_ERR(svn_utf_cstring_to_utf8(&params.root, arg, pool));
827251881Speter
828299742Sdim          SVN_ERR(svn_io_check_resolved_path(params.root, &kind, pool));
829251881Speter          if (kind != svn_node_dir)
830251881Speter            {
831299742Sdim              return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
832299742Sdim                       _("Root path '%s' does not exist "
833299742Sdim                         "or is not a directory"), params.root);
834251881Speter            }
835251881Speter
836251881Speter          params.root = svn_dirent_internal_style(params.root, pool);
837299742Sdim          SVN_ERR(svn_dirent_get_absolute(&params.root, params.root, pool));
838251881Speter          break;
839251881Speter
840251881Speter        case 'R':
841251881Speter          params.read_only = TRUE;
842251881Speter          break;
843251881Speter
844251881Speter        case 'T':
845251881Speter          handling_mode = connection_mode_thread;
846251881Speter          handling_opt_count++;
847251881Speter          break;
848251881Speter
849251881Speter        case 'c':
850251881Speter          params.compression_level = atoi(arg);
851251881Speter          if (params.compression_level < SVN_DELTA_COMPRESSION_LEVEL_NONE)
852251881Speter            params.compression_level = SVN_DELTA_COMPRESSION_LEVEL_NONE;
853251881Speter          if (params.compression_level > SVN_DELTA_COMPRESSION_LEVEL_MAX)
854251881Speter            params.compression_level = SVN_DELTA_COMPRESSION_LEVEL_MAX;
855251881Speter          break;
856251881Speter
857251881Speter        case 'M':
858251881Speter          params.memory_cache_size = 0x100000 * apr_strtoi64(arg, NULL, 0);
859251881Speter          break;
860251881Speter
861251881Speter        case SVNSERVE_OPT_CACHE_TXDELTAS:
862299742Sdim          cache_txdeltas = svn_tristate__from_word(arg) == svn_tristate_true;
863251881Speter          break;
864251881Speter
865251881Speter        case SVNSERVE_OPT_CACHE_FULLTEXTS:
866299742Sdim          cache_fulltexts = svn_tristate__from_word(arg) == svn_tristate_true;
867251881Speter          break;
868251881Speter
869251881Speter        case SVNSERVE_OPT_CACHE_REVPROPS:
870299742Sdim          cache_revprops = svn_tristate__from_word(arg) == svn_tristate_true;
871251881Speter          break;
872251881Speter
873299742Sdim        case SVNSERVE_OPT_BLOCK_READ:
874299742Sdim          use_block_read = svn_tristate__from_word(arg) == svn_tristate_true;
875299742Sdim          break;
876299742Sdim
877251881Speter        case SVNSERVE_OPT_CLIENT_SPEED:
878251881Speter          {
879251881Speter            apr_size_t bandwidth = (apr_size_t)apr_strtoi64(arg, NULL, 0);
880251881Speter
881251881Speter            /* for slower clients, don't try anything fancy */
882251881Speter            if (bandwidth >= 1000)
883251881Speter              {
884251881Speter                /* block other clients for at most 1 ms (at full bandwidth).
885251881Speter                   Note that the send buffer is 16kB anyways. */
886251881Speter                params.zero_copy_limit = bandwidth * 120;
887251881Speter
888251881Speter                /* check for aborted connections at the same rate */
889251881Speter                params.error_check_interval = bandwidth * 120;
890251881Speter              }
891251881Speter          }
892251881Speter          break;
893251881Speter
894299742Sdim        case SVNSERVE_OPT_MIN_THREADS:
895299742Sdim          min_thread_count = (apr_size_t)apr_strtoi64(arg, NULL, 0);
896299742Sdim          break;
897299742Sdim
898299742Sdim        case SVNSERVE_OPT_MAX_THREADS:
899299742Sdim          max_thread_count = (apr_size_t)apr_strtoi64(arg, NULL, 0);
900299742Sdim          break;
901299742Sdim
902251881Speter#ifdef WIN32
903251881Speter        case SVNSERVE_OPT_SERVICE:
904251881Speter          if (run_mode != run_mode_service)
905251881Speter            {
906251881Speter              run_mode = run_mode_service;
907251881Speter              mode_opt_count++;
908251881Speter            }
909251881Speter          break;
910251881Speter#endif
911251881Speter
912251881Speter        case SVNSERVE_OPT_CONFIG_FILE:
913299742Sdim          SVN_ERR(svn_utf_cstring_to_utf8(&config_filename, arg, pool));
914251881Speter          config_filename = svn_dirent_internal_style(config_filename, pool);
915299742Sdim          SVN_ERR(svn_dirent_get_absolute(&config_filename, config_filename,
916299742Sdim                                          pool));
917251881Speter          break;
918251881Speter
919251881Speter        case SVNSERVE_OPT_PID_FILE:
920299742Sdim          SVN_ERR(svn_utf_cstring_to_utf8(&pid_filename, arg, pool));
921251881Speter          pid_filename = svn_dirent_internal_style(pid_filename, pool);
922299742Sdim          SVN_ERR(svn_dirent_get_absolute(&pid_filename, pid_filename, pool));
923251881Speter          break;
924251881Speter
925251881Speter         case SVNSERVE_OPT_VIRTUAL_HOST:
926251881Speter           params.vhost = TRUE;
927251881Speter           break;
928251881Speter
929251881Speter         case SVNSERVE_OPT_LOG_FILE:
930299742Sdim          SVN_ERR(svn_utf_cstring_to_utf8(&log_filename, arg, pool));
931251881Speter          log_filename = svn_dirent_internal_style(log_filename, pool);
932299742Sdim          SVN_ERR(svn_dirent_get_absolute(&log_filename, log_filename, pool));
933251881Speter          break;
934251881Speter
935251881Speter        }
936251881Speter    }
937251881Speter
938251881Speter  if (is_version)
939251881Speter    {
940299742Sdim      SVN_ERR(version(quiet, pool));
941299742Sdim      return SVN_NO_ERROR;
942251881Speter    }
943251881Speter
944251881Speter  if (os->ind != argc)
945299742Sdim    {
946299742Sdim      usage(argv[0], pool);
947299742Sdim      *exit_code = EXIT_FAILURE;
948299742Sdim      return SVN_NO_ERROR;
949299742Sdim    }
950251881Speter
951251881Speter  if (mode_opt_count != 1)
952251881Speter    {
953251881Speter      svn_error_clear(svn_cmdline_fputs(
954251881Speter#ifdef WIN32
955251881Speter                      _("You must specify exactly one of -d, -i, -t, "
956251881Speter                        "--service or -X.\n"),
957251881Speter#else
958251881Speter                      _("You must specify exactly one of -d, -i, -t or -X.\n"),
959251881Speter#endif
960251881Speter                       stderr, pool));
961251881Speter      usage(argv[0], pool);
962299742Sdim      *exit_code = EXIT_FAILURE;
963299742Sdim      return SVN_NO_ERROR;
964251881Speter    }
965251881Speter
966251881Speter  if (handling_opt_count > 1)
967251881Speter    {
968251881Speter      svn_error_clear(svn_cmdline_fputs(
969251881Speter                      _("You may only specify one of -T or --single-thread\n"),
970251881Speter                      stderr, pool));
971251881Speter      usage(argv[0], pool);
972299742Sdim      *exit_code = EXIT_FAILURE;
973299742Sdim      return SVN_NO_ERROR;
974251881Speter    }
975251881Speter
976299742Sdim  /* construct object pools */
977299742Sdim  is_multi_threaded = handling_mode == connection_mode_thread;
978299742Sdim  params.fs_config = apr_hash_make(pool);
979299742Sdim  svn_hash_sets(params.fs_config, SVN_FS_CONFIG_FSFS_CACHE_DELTAS,
980299742Sdim                cache_txdeltas ? "1" :"0");
981299742Sdim  svn_hash_sets(params.fs_config, SVN_FS_CONFIG_FSFS_CACHE_FULLTEXTS,
982299742Sdim                cache_fulltexts ? "1" :"0");
983299742Sdim  svn_hash_sets(params.fs_config, SVN_FS_CONFIG_FSFS_CACHE_REVPROPS,
984299742Sdim                cache_revprops ? "2" :"0");
985299742Sdim  svn_hash_sets(params.fs_config, SVN_FS_CONFIG_FSFS_BLOCK_READ,
986299742Sdim                use_block_read ? "1" :"0");
987299742Sdim
988299742Sdim  SVN_ERR(svn_repos__config_pool_create(&params.config_pool,
989299742Sdim                                        is_multi_threaded,
990299742Sdim                                        pool));
991299742Sdim  SVN_ERR(svn_repos__authz_pool_create(&params.authz_pool,
992299742Sdim                                       params.config_pool,
993299742Sdim                                       is_multi_threaded,
994299742Sdim                                       pool));
995299742Sdim
996251881Speter  /* If a configuration file is specified, load it and any referenced
997251881Speter   * password and authorization files. */
998251881Speter  if (config_filename)
999251881Speter    {
1000251881Speter      params.base = svn_dirent_dirname(config_filename, pool);
1001251881Speter
1002299742Sdim      SVN_ERR(svn_repos__config_pool_get(&params.cfg, NULL,
1003299742Sdim                                         params.config_pool,
1004299742Sdim                                         config_filename,
1005299742Sdim                                         TRUE, /* must_exist */
1006299742Sdim                                         FALSE, /* names_case_sensitive */
1007299742Sdim                                         NULL,
1008299742Sdim                                         pool));
1009251881Speter    }
1010251881Speter
1011251881Speter  if (log_filename)
1012299742Sdim    SVN_ERR(logger__create(&params.logger, log_filename, pool));
1013299742Sdim  else if (run_mode == run_mode_listen_once)
1014299742Sdim    SVN_ERR(logger__create_for_stderr(&params.logger, pool));
1015251881Speter
1016251881Speter  if (params.tunnel_user && run_mode != run_mode_tunnel)
1017251881Speter    {
1018299742Sdim      return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1019299742Sdim               _("Option --tunnel-user is only valid in tunnel mode"));
1020251881Speter    }
1021251881Speter
1022251881Speter  if (run_mode == run_mode_inetd || run_mode == run_mode_tunnel)
1023251881Speter    {
1024299742Sdim      apr_pool_t *connection_pool;
1025299742Sdim      svn_ra_svn_conn_t *conn;
1026299742Sdim      svn_stream_t *stdin_stream;
1027299742Sdim      svn_stream_t *stdout_stream;
1028299742Sdim
1029251881Speter      params.tunnel = (run_mode == run_mode_tunnel);
1030251881Speter      apr_pool_cleanup_register(pool, pool, apr_pool_cleanup_null,
1031251881Speter                                redirect_stdout);
1032251881Speter
1033299742Sdim      SVN_ERR(svn_stream_for_stdin(&stdin_stream, pool));
1034299742Sdim      SVN_ERR(svn_stream_for_stdout(&stdout_stream, pool));
1035251881Speter
1036251881Speter      /* Use a subpool for the connection to ensure that if SASL is used
1037251881Speter       * the pool cleanup handlers that call sasl_dispose() (connection_pool)
1038251881Speter       * and sasl_done() (pool) are run in the right order. See issue #3664. */
1039251881Speter      connection_pool = svn_pool_create(pool);
1040299742Sdim      conn = svn_ra_svn_create_conn4(NULL, stdin_stream, stdout_stream,
1041251881Speter                                     params.compression_level,
1042251881Speter                                     params.zero_copy_limit,
1043251881Speter                                     params.error_check_interval,
1044251881Speter                                     connection_pool);
1045299742Sdim      err = serve(conn, &params, connection_pool);
1046299742Sdim      svn_pool_destroy(connection_pool);
1047299742Sdim
1048299742Sdim      return err;
1049251881Speter    }
1050251881Speter
1051251881Speter#ifdef WIN32
1052251881Speter  /* If svnserve needs to run as a Win32 service, then we need to
1053251881Speter     coordinate with the Service Control Manager (SCM) before
1054251881Speter     continuing.  This function call registers the svnserve.exe
1055251881Speter     process with the SCM, waits for the "start" command from the SCM
1056251881Speter     (which will come very quickly), and confirms that those steps
1057251881Speter     succeeded.
1058251881Speter
1059251881Speter     After this call succeeds, the service is free to run.  At some
1060251881Speter     point in the future, the SCM will send a message to the service,
1061251881Speter     requesting that it stop.  This is translated into a call to
1062251881Speter     winservice_notify_stop().  The service is then responsible for
1063251881Speter     cleanly terminating.
1064251881Speter
1065251881Speter     We need to do this before actually starting the service logic
1066251881Speter     (opening files, sockets, etc.) because the SCM wants you to
1067251881Speter     connect *first*, then do your service-specific logic.  If the
1068251881Speter     service process takes too long to connect to the SCM, then the
1069251881Speter     SCM will decide that the service is busted, and will give up on
1070251881Speter     it.
1071251881Speter     */
1072251881Speter  if (run_mode == run_mode_service)
1073251881Speter    {
1074251881Speter      err = winservice_start();
1075251881Speter      if (err)
1076251881Speter        {
1077251881Speter          svn_handle_error2(err, stderr, FALSE, "svnserve: ");
1078251881Speter
1079251881Speter          /* This is the most common error.  It means the user started
1080251881Speter             svnserve from a shell, and specified the --service
1081251881Speter             argument.  svnserve cannot be started, as a service, in
1082251881Speter             this way.  The --service argument is valid only valid if
1083251881Speter             svnserve is started by the SCM. */
1084251881Speter          if (err->apr_err ==
1085251881Speter              APR_FROM_OS_ERROR(ERROR_FAILED_SERVICE_CONTROLLER_CONNECT))
1086251881Speter            {
1087251881Speter              svn_error_clear(svn_cmdline_fprintf(stderr, pool,
1088251881Speter                  _("svnserve: The --service flag is only valid if the"
1089251881Speter                    " process is started by the Service Control Manager.\n")));
1090251881Speter            }
1091251881Speter
1092251881Speter          svn_error_clear(err);
1093299742Sdim          *exit_code = EXIT_FAILURE;
1094299742Sdim          return SVN_NO_ERROR;
1095251881Speter        }
1096251881Speter
1097251881Speter      /* The service is now in the "starting" state.  Before the SCM will
1098251881Speter         consider the service "started", this thread must call the
1099251881Speter         winservice_running() function. */
1100251881Speter    }
1101251881Speter#endif /* WIN32 */
1102251881Speter
1103251881Speter  /* Make sure we have IPV6 support first before giving apr_sockaddr_info_get
1104251881Speter     APR_UNSPEC, because it may give us back an IPV6 address even if we can't
1105251881Speter     create IPV6 sockets. */
1106251881Speter
1107251881Speter#if APR_HAVE_IPV6
1108251881Speter#ifdef MAX_SECS_TO_LINGER
1109251881Speter  /* ### old APR interface */
1110251881Speter  status = apr_socket_create(&sock, APR_INET6, SOCK_STREAM, pool);
1111251881Speter#else
1112251881Speter  status = apr_socket_create(&sock, APR_INET6, SOCK_STREAM, APR_PROTO_TCP,
1113251881Speter                             pool);
1114251881Speter#endif
1115251881Speter  if (status == 0)
1116251881Speter    {
1117251881Speter      apr_socket_close(sock);
1118251881Speter      family = APR_UNSPEC;
1119251881Speter
1120251881Speter      if (prefer_v6)
1121251881Speter        {
1122251881Speter          if (host == NULL)
1123251881Speter            host = "::";
1124251881Speter          sockaddr_info_flags = APR_IPV6_ADDR_OK;
1125251881Speter        }
1126251881Speter      else
1127251881Speter        {
1128251881Speter          if (host == NULL)
1129251881Speter            host = "0.0.0.0";
1130251881Speter          sockaddr_info_flags = APR_IPV4_ADDR_OK;
1131251881Speter        }
1132251881Speter    }
1133251881Speter#endif
1134251881Speter
1135251881Speter  status = apr_sockaddr_info_get(&sa, host, family, port,
1136251881Speter                                 sockaddr_info_flags, pool);
1137251881Speter  if (status)
1138251881Speter    {
1139299742Sdim      return svn_error_wrap_apr(status, _("Can't get address info"));
1140251881Speter    }
1141251881Speter
1142251881Speter
1143251881Speter#ifdef MAX_SECS_TO_LINGER
1144251881Speter  /* ### old APR interface */
1145251881Speter  status = apr_socket_create(&sock, sa->family, SOCK_STREAM, pool);
1146251881Speter#else
1147251881Speter  status = apr_socket_create(&sock, sa->family, SOCK_STREAM, APR_PROTO_TCP,
1148251881Speter                             pool);
1149251881Speter#endif
1150251881Speter  if (status)
1151251881Speter    {
1152299742Sdim      return svn_error_wrap_apr(status, _("Can't create server socket"));
1153251881Speter    }
1154251881Speter
1155251881Speter  /* Prevents "socket in use" errors when server is killed and quickly
1156251881Speter   * restarted. */
1157299742Sdim  status = apr_socket_opt_set(sock, APR_SO_REUSEADDR, 1);
1158299742Sdim  if (status)
1159299742Sdim    {
1160299742Sdim      return svn_error_wrap_apr(status, _("Can't set options on server socket"));
1161299742Sdim    }
1162251881Speter
1163251881Speter  status = apr_socket_bind(sock, sa);
1164251881Speter  if (status)
1165251881Speter    {
1166299742Sdim      return svn_error_wrap_apr(status, _("Can't bind server socket"));
1167251881Speter    }
1168251881Speter
1169299742Sdim  status = apr_socket_listen(sock, ACCEPT_BACKLOG);
1170299742Sdim  if (status)
1171299742Sdim    {
1172299742Sdim      return svn_error_wrap_apr(status, _("Can't listen on server socket"));
1173299742Sdim    }
1174251881Speter
1175251881Speter#if APR_HAS_FORK
1176251881Speter  if (run_mode != run_mode_listen_once && !foreground)
1177299742Sdim    /* ### ignoring errors... */
1178251881Speter    apr_proc_detach(APR_PROC_DETACH_DAEMONIZE);
1179251881Speter
1180251881Speter  apr_signal(SIGCHLD, sigchld_handler);
1181251881Speter#endif
1182251881Speter
1183251881Speter#ifdef SIGPIPE
1184251881Speter  /* Disable SIGPIPE generation for the platforms that have it. */
1185251881Speter  apr_signal(SIGPIPE, SIG_IGN);
1186251881Speter#endif
1187251881Speter
1188251881Speter#ifdef SIGXFSZ
1189251881Speter  /* Disable SIGXFSZ generation for the platforms that have it, otherwise
1190251881Speter   * working with large files when compiled against an APR that doesn't have
1191251881Speter   * large file support will crash the program, which is uncool. */
1192251881Speter  apr_signal(SIGXFSZ, SIG_IGN);
1193251881Speter#endif
1194251881Speter
1195251881Speter  if (pid_filename)
1196299742Sdim    SVN_ERR(write_pid_file(pid_filename, pool));
1197251881Speter
1198251881Speter#ifdef WIN32
1199251881Speter  status = apr_os_sock_get(&winservice_svnserve_accept_socket, sock);
1200251881Speter  if (status)
1201251881Speter    winservice_svnserve_accept_socket = INVALID_SOCKET;
1202251881Speter
1203251881Speter  /* At this point, the service is "running".  Notify the SCM. */
1204251881Speter  if (run_mode == run_mode_service)
1205251881Speter    winservice_running();
1206251881Speter#endif
1207251881Speter
1208251881Speter  /* Configure FS caches for maximum efficiency with svnserve.
1209251881Speter   * For pre-forked (i.e. multi-processed) mode of operation,
1210251881Speter   * keep the per-process caches smaller than the default.
1211251881Speter   * Also, apply the respective command line parameters, if given. */
1212251881Speter  {
1213251881Speter    svn_cache_config_t settings = *svn_cache_config_get();
1214251881Speter
1215251881Speter    if (params.memory_cache_size != -1)
1216251881Speter      settings.cache_size = params.memory_cache_size;
1217251881Speter
1218251881Speter    settings.single_threaded = TRUE;
1219251881Speter    if (handling_mode == connection_mode_thread)
1220251881Speter      {
1221251881Speter#if APR_HAS_THREADS
1222251881Speter        settings.single_threaded = FALSE;
1223251881Speter#else
1224251881Speter        /* No requests will be processed at all
1225251881Speter         * (see "switch (handling_mode)" code further down).
1226251881Speter         * But if they were, some other synchronization code
1227251881Speter         * would need to take care of securing integrity of
1228251881Speter         * APR-based structures. That would include our caches.
1229251881Speter         */
1230251881Speter#endif
1231251881Speter      }
1232251881Speter
1233251881Speter    svn_cache_config_set(&settings);
1234251881Speter  }
1235251881Speter
1236299742Sdim#if APR_HAS_THREADS
1237299742Sdim  SVN_ERR(svn_root_pools__create(&connection_pools));
1238299742Sdim
1239299742Sdim  if (handling_mode == connection_mode_thread)
1240251881Speter    {
1241299742Sdim      /* create the thread pool with a valid range of threads */
1242299742Sdim      if (max_thread_count < 1)
1243299742Sdim        max_thread_count = 1;
1244299742Sdim      if (min_thread_count > max_thread_count)
1245299742Sdim        min_thread_count = max_thread_count;
1246251881Speter
1247299742Sdim      status = apr_thread_pool_create(&threads,
1248299742Sdim                                      min_thread_count,
1249299742Sdim                                      max_thread_count,
1250299742Sdim                                      pool);
1251251881Speter      if (status)
1252251881Speter        {
1253299742Sdim          return svn_error_wrap_apr(status, _("Can't create thread pool"));
1254251881Speter        }
1255251881Speter
1256299742Sdim      /* let idle threads linger for a while in case more requests are
1257299742Sdim         coming in */
1258299742Sdim      apr_thread_pool_idle_wait_set(threads, THREADPOOL_THREAD_IDLE_LIMIT);
1259251881Speter
1260299742Sdim      /* don't queue requests unless we reached the worker thread limit */
1261299742Sdim      apr_thread_pool_threshold_set(threads, 0);
1262299742Sdim    }
1263299742Sdim  else
1264299742Sdim    {
1265299742Sdim      threads = NULL;
1266299742Sdim    }
1267299742Sdim#endif
1268251881Speter
1269299742Sdim  while (1)
1270299742Sdim    {
1271299742Sdim      connection_t *connection = NULL;
1272299742Sdim      SVN_ERR(accept_connection(&connection, sock, &params, handling_mode,
1273299742Sdim                                pool));
1274251881Speter      if (run_mode == run_mode_listen_once)
1275251881Speter        {
1276299742Sdim          err = serve_socket(connection, connection->pool);
1277299742Sdim          close_connection(connection);
1278299742Sdim          return err;
1279251881Speter        }
1280251881Speter
1281251881Speter      switch (handling_mode)
1282251881Speter        {
1283251881Speter        case connection_mode_fork:
1284251881Speter#if APR_HAS_FORK
1285299742Sdim          status = apr_proc_fork(&proc, connection->pool);
1286251881Speter          if (status == APR_INCHILD)
1287251881Speter            {
1288299742Sdim              /* the child would't listen to the main server's socket */
1289251881Speter              apr_socket_close(sock);
1290299742Sdim
1291299742Sdim              /* serve_socket() logs any error it returns, so ignore it. */
1292299742Sdim              svn_error_clear(serve_socket(connection, connection->pool));
1293299742Sdim              close_connection(connection);
1294299742Sdim              return SVN_NO_ERROR;
1295251881Speter            }
1296299742Sdim          else if (status != APR_INPARENT)
1297251881Speter            {
1298251881Speter              err = svn_error_wrap_apr(status, "apr_proc_fork");
1299299742Sdim              logger__log_error(params.logger, err, NULL, NULL);
1300251881Speter              svn_error_clear(err);
1301251881Speter            }
1302251881Speter#endif
1303251881Speter          break;
1304251881Speter
1305251881Speter        case connection_mode_thread:
1306251881Speter          /* Create a detached thread for each connection.  That's not a
1307251881Speter             particularly sophisticated strategy for a threaded server, it's
1308251881Speter             little different from forking one process per connection. */
1309251881Speter#if APR_HAS_THREADS
1310299742Sdim          attach_connection(connection);
1311299742Sdim
1312299742Sdim          status = apr_thread_pool_push(threads, serve_thread, connection,
1313299742Sdim                                        0, NULL);
1314251881Speter          if (status)
1315251881Speter            {
1316299742Sdim              return svn_error_wrap_apr(status, _("Can't push task"));
1317251881Speter            }
1318251881Speter#endif
1319251881Speter          break;
1320251881Speter
1321251881Speter        case connection_mode_single:
1322251881Speter          /* Serve one connection at a time. */
1323299742Sdim          /* serve_socket() logs any error it returns, so ignore it. */
1324299742Sdim          svn_error_clear(serve_socket(connection, connection->pool));
1325251881Speter        }
1326299742Sdim
1327299742Sdim      close_connection(connection);
1328251881Speter    }
1329251881Speter
1330251881Speter  /* NOTREACHED */
1331251881Speter}
1332299742Sdim
1333299742Sdimint
1334299742Sdimmain(int argc, const char *argv[])
1335299742Sdim{
1336299742Sdim  apr_pool_t *pool;
1337299742Sdim  int exit_code = EXIT_SUCCESS;
1338299742Sdim  svn_error_t *err;
1339299742Sdim
1340299742Sdim  /* Initialize the app. */
1341299742Sdim  if (svn_cmdline_init("svnserve", stderr) != EXIT_SUCCESS)
1342299742Sdim    return EXIT_FAILURE;
1343299742Sdim
1344299742Sdim  /* Create our top-level pool. */
1345299742Sdim  pool = apr_allocator_owner_get(svn_pool_create_allocator(TRUE));
1346299742Sdim
1347299742Sdim  err = sub_main(&exit_code, argc, argv, pool);
1348299742Sdim
1349299742Sdim  /* Flush stdout and report if it fails. It would be flushed on exit anyway
1350299742Sdim     but this makes sure that output is not silently lost if it fails. */
1351299742Sdim  err = svn_error_compose_create(err, svn_cmdline_fflush(stdout));
1352299742Sdim
1353299742Sdim  if (err)
1354299742Sdim    {
1355299742Sdim      exit_code = EXIT_FAILURE;
1356299742Sdim      svn_cmdline_handle_exit_error(err, NULL, "svnserve: ");
1357299742Sdim    }
1358299742Sdim
1359299742Sdim#if APR_HAS_THREADS
1360299742Sdim  /* Explicitly wait for all threads to exit.  As we found out with similar
1361299742Sdim     code in our C test framework, the memory pool cleanup below cannot be
1362299742Sdim     trusted to do the right thing. */
1363299742Sdim  if (threads)
1364299742Sdim    apr_thread_pool_destroy(threads);
1365299742Sdim#endif
1366299742Sdim
1367299742Sdim  /* this will also close the server's socket */
1368299742Sdim  svn_pool_destroy(pool);
1369299742Sdim  return exit_code;
1370299742Sdim}
1371