Remove no-longer-used svn_*_get_mergeinfo_for_tree APIs.
[svn.git] / subversion / svnserve / main.c
blobe235fe5138d958c1506057d72638db9062a9e45f
1 /*
2 * main.c : Main control function for svnserve
4 * ====================================================================
5 * Copyright (c) 2000-2007 CollabNet. All rights reserved.
7 * This software is licensed as described in the file COPYING, which
8 * you should have received as part of this distribution. The terms
9 * are also available at http://subversion.tigris.org/license-1.html.
10 * If newer versions of this license are posted there, you may use a
11 * newer version instead, at your option.
13 * This software consists of voluntary contributions made by many
14 * individuals. For exact contribution history, see the revision
15 * history and logs, available at http://subversion.tigris.org/.
16 * ====================================================================
21 #define APR_WANT_STRFUNC
22 #include <apr_want.h>
23 #include <apr_general.h>
24 #include <apr_getopt.h>
25 #include <apr_network_io.h>
26 #include <apr_signal.h>
27 #include <apr_thread_proc.h>
28 #include <apr_portable.h>
30 #include <locale.h>
32 #include "svn_cmdline.h"
33 #include "svn_types.h"
34 #include "svn_pools.h"
35 #include "svn_error.h"
36 #include "svn_ra_svn.h"
37 #include "svn_utf.h"
38 #include "svn_path.h"
39 #include "svn_opt.h"
40 #include "svn_repos.h"
41 #include "svn_fs.h"
42 #include "svn_version.h"
43 #include "svn_io.h"
45 #include "svn_private_config.h"
46 #include "winservice.h"
48 #ifdef HAVE_UNISTD_H
49 #include <unistd.h> /* For getpid() */
50 #endif
52 #include "server.h"
54 /* The strategy for handling incoming connections. Some of these may be
55 unavailable due to platform limitations. */
56 enum connection_handling_mode {
57 connection_mode_fork, /* Create a process per connection */
58 connection_mode_thread, /* Create a thread per connection */
59 connection_mode_single /* One connection at a time in this process */
62 /* The mode in which to run svnserve */
63 enum run_mode {
64 run_mode_unspecified,
65 run_mode_inetd,
66 run_mode_daemon,
67 run_mode_tunnel,
68 run_mode_listen_once,
69 run_mode_service
72 #if APR_HAS_FORK
73 #if APR_HAS_THREADS
75 #define CONNECTION_DEFAULT connection_mode_fork
76 #define CONNECTION_HAVE_THREAD_OPTION
78 #else /* ! APR_HAS_THREADS */
80 #define CONNECTION_DEFAULT connection_mode_fork
82 #endif /* ! APR_HAS_THREADS */
83 #elif APR_HAS_THREADS /* and ! APR_HAS_FORK */
85 #define CONNECTION_DEFAULT connection_mode_thread
87 #else /* ! APR_HAS_THREADS and ! APR_HAS_FORK */
89 #define CONNECTION_DEFAULT connection_mode_single
91 #endif
94 #ifdef WIN32
95 static apr_os_sock_t winservice_svnserve_accept_socket = INVALID_SOCKET;
97 /* The SCM calls this function (on an arbitrary thread, not the main()
98 thread!) when it wants to stop the service.
100 For now, our strategy is to close the listener socket, in order to
101 unblock main() and cause it to exit its accept loop. We cannot use
102 apr_socket_close, because that function deletes the apr_socket_t
103 structure, as well as closing the socket handle. If we called
104 apr_socket_close here, then main() will also call apr_socket_close,
105 resulting in a double-free. This way, we just close the kernel
106 socket handle, which causes the accept() function call to fail,
107 which causes main() to clean up the socket. So, memory gets freed
108 only once.
110 This isn't pretty, but it's better than a lot of other options.
111 Currently, there is no "right" way to shut down svnserve.
113 We store the OS handle rather than a pointer to the apr_socket_t
114 structure in order to eliminate any possibility of illegal memory
115 access. */
116 void winservice_notify_stop(void)
118 if (winservice_svnserve_accept_socket != INVALID_SOCKET)
119 closesocket(winservice_svnserve_accept_socket);
121 #endif /* _WIN32 */
124 /* Option codes and descriptions for svnserve.
126 * The entire list must be terminated with an entry of nulls.
128 * APR requires that options without abbreviations
129 * have codes greater than 255.
131 #define SVNSERVE_OPT_LISTEN_PORT 256
132 #define SVNSERVE_OPT_LISTEN_HOST 257
133 #define SVNSERVE_OPT_FOREGROUND 258
134 #define SVNSERVE_OPT_TUNNEL_USER 259
135 #define SVNSERVE_OPT_VERSION 260
136 #define SVNSERVE_OPT_PID_FILE 261
137 #define SVNSERVE_OPT_SERVICE 262
138 #define SVNSERVE_OPT_CONFIG_FILE 263
140 static const apr_getopt_option_t svnserve__options[] =
142 {"daemon", 'd', 0, N_("daemon mode")},
143 {"listen-port", SVNSERVE_OPT_LISTEN_PORT, 1,
144 N_("listen port (for daemon mode)")},
145 {"listen-host", SVNSERVE_OPT_LISTEN_HOST, 1,
146 N_("listen hostname or IP address (for daemon mode)")},
147 {"foreground", SVNSERVE_OPT_FOREGROUND, 0,
148 N_("run in foreground (useful for debugging)")},
149 {"help", 'h', 0, N_("display this help")},
150 {"version", SVNSERVE_OPT_VERSION, 0,
151 N_("show program version information")},
152 {"inetd", 'i', 0, N_("inetd mode")},
153 {"root", 'r', 1, N_("root of directory to serve")},
154 {"read-only", 'R', 0,
155 N_("force read only, overriding repository config file")},
156 {"tunnel", 't', 0, N_("tunnel mode")},
157 {"tunnel-user", SVNSERVE_OPT_TUNNEL_USER, 1,
158 N_("tunnel username (default is current uid's name)")},
159 #ifdef CONNECTION_HAVE_THREAD_OPTION
160 {"threads", 'T', 0, N_("use threads instead of fork")},
161 #endif
162 {"listen-once", 'X', 0, N_("listen once (useful for debugging)")},
163 {"config-file", SVNSERVE_OPT_CONFIG_FILE, 1,
164 N_("read configuration from file ARG")},
165 {"pid-file", SVNSERVE_OPT_PID_FILE, 1,
166 N_("write server process ID to file ARG")},
167 #ifdef WIN32
168 {"service", SVNSERVE_OPT_SERVICE, 0,
169 N_("run as a windows service (SCM only)")},
170 #endif
171 {0, 0, 0, 0}
175 static void usage(const char *progname, apr_pool_t *pool)
177 if (!progname)
178 progname = "svnserve";
180 svn_error_clear(svn_cmdline_fprintf(stderr, pool,
181 _("Type '%s --help' for usage.\n"),
182 progname));
183 exit(1);
186 static void help(apr_pool_t *pool)
188 apr_size_t i;
190 svn_error_clear(svn_cmdline_fputs(_("usage: svnserve [options]\n"
191 "\n"
192 "Valid options:\n"),
193 stdout, pool));
194 for (i = 0; svnserve__options[i].name && svnserve__options[i].optch; i++)
196 const char *optstr;
197 svn_opt_format_option(&optstr, svnserve__options + i, TRUE, pool);
198 svn_error_clear(svn_cmdline_fprintf(stdout, pool, " %s\n", optstr));
200 svn_error_clear(svn_cmdline_fprintf(stdout, pool, "\n"));
201 exit(0);
204 static svn_error_t * version(apr_pool_t *pool)
206 const char *fs_desc_start
207 = _("The following repository back-end (FS) modules are available:\n\n");
209 svn_stringbuf_t *version_footer;
211 version_footer = svn_stringbuf_create(fs_desc_start, pool);
212 SVN_ERR(svn_fs_print_modules(version_footer, pool));
214 return svn_opt_print_help(NULL, "svnserve", TRUE, FALSE, version_footer->data,
215 NULL, NULL, NULL, NULL, pool);
219 #if APR_HAS_FORK
220 static void sigchld_handler(int signo)
222 /* Nothing to do; we just need to interrupt the accept(). */
224 #endif
226 /* In tunnel or inetd mode, we don't want hook scripts corrupting the
227 * data stream by sending data to stdout, so we need to redirect
228 * stdout somewhere else. Sending it to stderr is acceptable; sending
229 * it to /dev/null is another option, but apr doesn't provide a way to
230 * do that without also detaching from the controlling terminal.
232 static apr_status_t redirect_stdout(void *arg)
234 apr_pool_t *pool = arg;
235 apr_file_t *out_file, *err_file;
236 apr_status_t apr_err;
238 if ((apr_err = apr_file_open_stdout(&out_file, pool)))
239 return apr_err;
240 if ((apr_err = apr_file_open_stderr(&err_file, pool)))
241 return apr_err;
242 return apr_file_dup2(out_file, err_file, pool);
245 /* "Arguments" passed from the main thread to the connection thread */
246 struct serve_thread_t {
247 svn_ra_svn_conn_t *conn;
248 serve_params_t *params;
249 apr_pool_t *pool;
252 #if APR_HAS_THREADS
253 static void * APR_THREAD_FUNC serve_thread(apr_thread_t *tid, void *data)
255 struct serve_thread_t *d = data;
257 svn_error_clear(serve(d->conn, d->params, d->pool));
258 svn_pool_destroy(d->pool);
260 return NULL;
262 #endif
264 /* Write the PID of the current process as a decimal number, followed by a
265 newline to the file FILENAME, using POOL for temporary allocations. */
266 static svn_error_t *write_pid_file(const char *filename, apr_pool_t *pool)
268 apr_file_t *file;
269 const char *contents = apr_psprintf(pool, "%" APR_PID_T_FMT "\n",
270 getpid());
272 SVN_ERR(svn_io_file_open(&file, filename,
273 APR_WRITE | APR_CREATE | APR_TRUNCATE,
274 APR_OS_DEFAULT, pool));
275 SVN_ERR(svn_io_file_write_full(file, contents, strlen(contents), NULL,
276 pool));
278 SVN_ERR(svn_io_file_close(file, pool));
280 return SVN_NO_ERROR;
283 /* Version compatibility check */
284 static svn_error_t *
285 check_lib_versions(void)
287 static const svn_version_checklist_t checklist[] =
289 { "svn_subr", svn_subr_version },
290 { "svn_repos", svn_repos_version },
291 { "svn_fs", svn_fs_version },
292 { "svn_delta", svn_delta_version },
293 { "svn_ra_svn", svn_ra_svn_version },
294 { NULL, NULL }
297 SVN_VERSION_DEFINE(my_version);
298 return svn_ver_check_list(&my_version, checklist);
302 int main(int argc, const char *argv[])
304 enum run_mode run_mode = run_mode_unspecified;
305 svn_boolean_t foreground = FALSE;
306 apr_socket_t *sock, *usock;
307 apr_file_t *in_file, *out_file;
308 apr_sockaddr_t *sa;
309 apr_pool_t *pool;
310 apr_pool_t *connection_pool;
311 svn_error_t *err;
312 apr_getopt_t *os;
313 int opt;
314 serve_params_t params;
315 const char *arg;
316 apr_status_t status;
317 svn_ra_svn_conn_t *conn;
318 apr_proc_t proc;
319 #if APR_HAS_THREADS
320 apr_threadattr_t *tattr;
321 apr_thread_t *tid;
323 struct serve_thread_t *thread_data;
324 #endif
325 enum connection_handling_mode handling_mode = CONNECTION_DEFAULT;
326 apr_uint16_t port = SVN_RA_SVN_PORT;
327 const char *host = NULL;
328 int family = APR_INET;
329 int mode_opt_count = 0;
330 const char *config_filename = NULL;
331 const char *pid_filename = NULL;
332 svn_node_kind_t kind;
334 /* Initialize the app. */
335 if (svn_cmdline_init("svnserve", stderr) != EXIT_SUCCESS)
336 return EXIT_FAILURE;
338 /* Create our top-level pool. */
339 pool = svn_pool_create(NULL);
341 #ifdef SVN_HAVE_SASL
342 SVN_INT_ERR(cyrus_init(pool));
343 #endif
345 /* Check library versions */
346 err = check_lib_versions();
347 if (err)
348 return svn_cmdline_handle_exit_error(err, pool, "svnserve: ");
350 /* Initialize the FS library. */
351 err = svn_fs_initialize(pool);
352 if (err)
353 return svn_cmdline_handle_exit_error(err, pool, "svnserve: ");
355 err = svn_cmdline__getopt_init(&os, argc, argv, pool);
356 if (err)
357 return svn_cmdline_handle_exit_error(err, pool, "svnserve: ");
359 params.root = "/";
360 params.tunnel = FALSE;
361 params.tunnel_user = NULL;
362 params.read_only = FALSE;
363 params.cfg = NULL;
364 params.pwdb = NULL;
365 params.authzdb = NULL;
367 while (1)
369 status = apr_getopt_long(os, svnserve__options, &opt, &arg);
370 if (APR_STATUS_IS_EOF(status))
371 break;
372 if (status != APR_SUCCESS)
373 usage(argv[0], pool);
374 switch (opt)
376 case 'h':
377 help(pool);
378 break;
380 case SVNSERVE_OPT_VERSION:
381 SVN_INT_ERR(version(pool));
382 exit(0);
383 break;
385 case 'd':
386 if (run_mode != run_mode_daemon)
388 run_mode = run_mode_daemon;
389 mode_opt_count++;
391 break;
393 case SVNSERVE_OPT_FOREGROUND:
394 foreground = TRUE;
395 break;
397 case 'i':
398 if (run_mode != run_mode_inetd)
400 run_mode = run_mode_inetd;
401 mode_opt_count++;
403 break;
405 case SVNSERVE_OPT_LISTEN_PORT:
406 port = atoi(arg);
407 break;
409 case SVNSERVE_OPT_LISTEN_HOST:
410 host = arg;
411 break;
413 case 't':
414 if (run_mode != run_mode_tunnel)
416 run_mode = run_mode_tunnel;
417 mode_opt_count++;
419 break;
421 case SVNSERVE_OPT_TUNNEL_USER:
422 params.tunnel_user = arg;
423 break;
425 case 'X':
426 if (run_mode != run_mode_listen_once)
428 run_mode = run_mode_listen_once;
429 mode_opt_count++;
431 break;
433 case 'r':
434 SVN_INT_ERR(svn_utf_cstring_to_utf8(&params.root, arg, pool));
436 err = svn_io_check_resolved_path(params.root, &kind, pool);
437 if (err)
438 return svn_cmdline_handle_exit_error(err, pool, "svnserve: ");
439 if (kind != svn_node_dir)
441 svn_error_clear
442 (svn_cmdline_fprintf
443 (stderr, pool,
444 _("svnserve: Root path '%s' does not exist "
445 "or is not a directory.\n"), params.root));
446 return EXIT_FAILURE;
449 params.root = svn_path_internal_style(params.root, pool);
450 SVN_INT_ERR(svn_path_get_absolute(&params.root, params.root, pool));
451 break;
453 case 'R':
454 params.read_only = TRUE;
455 break;
457 case 'T':
458 handling_mode = connection_mode_thread;
459 break;
461 #ifdef WIN32
462 case SVNSERVE_OPT_SERVICE:
463 if (run_mode != run_mode_service)
465 run_mode = run_mode_service;
466 mode_opt_count++;
468 break;
469 #endif
471 case SVNSERVE_OPT_CONFIG_FILE:
472 SVN_INT_ERR(svn_utf_cstring_to_utf8(&config_filename, arg, pool));
473 config_filename = svn_path_internal_style(config_filename, pool);
474 SVN_INT_ERR(svn_path_get_absolute(&config_filename, config_filename,
475 pool));
476 break;
478 case SVNSERVE_OPT_PID_FILE:
479 SVN_INT_ERR(svn_utf_cstring_to_utf8(&pid_filename, arg, pool));
480 pid_filename = svn_path_internal_style(pid_filename, pool);
481 SVN_INT_ERR(svn_path_get_absolute(&pid_filename, pid_filename,
482 pool));
483 break;
487 if (os->ind != argc)
488 usage(argv[0], pool);
490 if (mode_opt_count != 1)
492 svn_error_clear(svn_cmdline_fputs
493 (_("You must specify exactly one of -d, -i, -t or -X.\n"),
494 stderr, pool));
495 usage(argv[0], pool);
498 /* If a configuration file is specified, load it and any referenced
499 * password and authorization files. */
500 if (config_filename)
501 SVN_INT_ERR(load_configs(&params.cfg, &params.pwdb, &params.authzdb,
502 config_filename, TRUE,
503 svn_path_dirname(config_filename, pool),
504 pool));
506 if (params.tunnel_user && run_mode != run_mode_tunnel)
508 svn_error_clear
509 (svn_cmdline_fprintf
510 (stderr, pool,
511 _("Option --tunnel-user is only valid in tunnel mode.\n")));
512 exit(1);
515 if (run_mode == run_mode_inetd || run_mode == run_mode_tunnel)
517 params.tunnel = (run_mode == run_mode_tunnel);
518 apr_pool_cleanup_register(pool, pool, apr_pool_cleanup_null,
519 redirect_stdout);
520 status = apr_file_open_stdin(&in_file, pool);
521 if (status)
523 err = svn_error_wrap_apr(status, _("Can't open stdin"));
524 return svn_cmdline_handle_exit_error(err, pool, "svnserve: ");
527 status = apr_file_open_stdout(&out_file, pool);
528 if (status)
530 err = svn_error_wrap_apr(status, _("Can't open stdout"));
531 return svn_cmdline_handle_exit_error(err, pool, "svnserve: ");
534 conn = svn_ra_svn_create_conn(NULL, in_file, out_file, pool);
535 svn_error_clear(serve(conn, &params, pool));
536 exit(0);
539 #ifdef WIN32
540 /* If svnserve needs to run as a Win32 service, then we need to
541 coordinate with the Service Control Manager (SCM) before
542 continuing. This function call registers the svnserve.exe
543 process with the SCM, waits for the "start" command from the SCM
544 (which will come very quickly), and confirms that those steps
545 succeeded.
547 After this call succeeds, the service is free to run. At some
548 point in the future, the SCM will send a message to the service,
549 requesting that it stop. This is translated into a call to
550 winservice_notify_stop(). The service is then responsible for
551 cleanly terminating.
553 We need to do this before actually starting the service logic
554 (opening files, sockets, etc.) because the SCM wants you to
555 connect *first*, then do your service-specific logic. If the
556 service process takes too long to connect to the SCM, then the
557 SCM will decide that the service is busted, and will give up on
560 if (run_mode == run_mode_service)
562 err = winservice_start();
563 if (err)
565 svn_handle_error2(err, stderr, FALSE, "svnserve: ");
567 /* This is the most common error. It means the user started
568 svnserve from a shell, and specified the --service
569 argument. svnserve cannot be started, as a service, in
570 this way. The --service argument is valid only valid if
571 svnserve is started by the SCM. */
572 if (err->apr_err ==
573 APR_FROM_OS_ERROR(ERROR_FAILED_SERVICE_CONTROLLER_CONNECT))
575 svn_error_clear(svn_cmdline_fprintf(stderr, pool,
576 _("svnserve: The --service flag is only valid if the"
577 " process is started by the Service Control Manager.\n")));
580 svn_error_clear(err);
581 exit(1);
584 /* The service is now in the "starting" state. Before the SCM will
585 consider the service "started", this thread must call the
586 winservice_running() function. */
588 #endif /* WIN32 */
590 /* Make sure we have IPV6 support first before giving apr_sockaddr_info_get
591 APR_UNSPEC, because it may give us back an IPV6 address even if we can't
592 create IPV6 sockets. */
594 #if APR_HAVE_IPV6
595 #ifdef MAX_SECS_TO_LINGER
596 /* ### old APR interface */
597 status = apr_socket_create(&sock, APR_INET6, SOCK_STREAM, pool);
598 #else
599 status = apr_socket_create(&sock, APR_INET6, SOCK_STREAM, APR_PROTO_TCP,
600 pool);
601 #endif
602 if (status == 0)
604 apr_socket_close(sock);
605 family = APR_UNSPEC;
607 #endif
609 status = apr_sockaddr_info_get(&sa, host, family, port, 0, pool);
610 if (status)
612 err = svn_error_wrap_apr(status, _("Can't get address info"));
613 return svn_cmdline_handle_exit_error(err, pool, "svnserve: ");
617 #ifdef MAX_SECS_TO_LINGER
618 /* ### old APR interface */
619 status = apr_socket_create(&sock, sa->family, SOCK_STREAM, pool);
620 #else
621 status = apr_socket_create(&sock, sa->family, SOCK_STREAM, APR_PROTO_TCP,
622 pool);
623 #endif
624 if (status)
626 err = svn_error_wrap_apr(status, _("Can't create server socket"));
627 return svn_cmdline_handle_exit_error(err, pool, "svnserve: ");
630 /* Prevents "socket in use" errors when server is killed and quickly
631 * restarted. */
632 apr_socket_opt_set(sock, APR_SO_REUSEADDR, 1);
634 status = apr_socket_bind(sock, sa);
635 if (status)
637 err = svn_error_wrap_apr(status, _("Can't bind server socket"));
638 return svn_cmdline_handle_exit_error(err, pool, "svnserve: ");
641 apr_socket_listen(sock, 7);
643 #if APR_HAS_FORK
644 if (run_mode != run_mode_listen_once && !foreground)
645 apr_proc_detach(APR_PROC_DETACH_DAEMONIZE);
647 apr_signal(SIGCHLD, sigchld_handler);
648 #endif
650 #ifdef SIGPIPE
651 /* Disable SIGPIPE generation for the platforms that have it. */
652 apr_signal(SIGPIPE, SIG_IGN);
653 #endif
655 #ifdef SIGXFSZ
656 /* Disable SIGXFSZ generation for the platforms that have it, otherwise
657 * working with large files when compiled against an APR that doesn't have
658 * large file support will crash the program, which is uncool. */
659 apr_signal(SIGXFSZ, SIG_IGN);
660 #endif
662 if (pid_filename)
663 SVN_INT_ERR(write_pid_file(pid_filename, pool));
665 #ifdef WIN32
666 status = apr_os_sock_get(&winservice_svnserve_accept_socket, sock);
667 if (status)
668 winservice_svnserve_accept_socket = INVALID_SOCKET;
670 /* At this point, the service is "running". Notify the SCM. */
671 if (run_mode == run_mode_service)
672 winservice_running();
673 #endif
675 while (1)
677 #ifdef WIN32
678 if (winservice_is_stopping())
679 return ERROR_SUCCESS;
680 #endif
682 /* Non-standard pool handling. The main thread never blocks to join
683 the connection threads so it cannot clean up after each one. So
684 separate pools, that can be cleared at thread exit, are used */
685 connection_pool = svn_pool_create(NULL);
687 status = apr_socket_accept(&usock, sock, connection_pool);
688 if (handling_mode == connection_mode_fork)
690 /* Collect any zombie child processes. */
691 while (apr_proc_wait_all_procs(&proc, NULL, NULL, APR_NOWAIT,
692 connection_pool) == APR_CHILD_DONE)
695 if (APR_STATUS_IS_EINTR(status))
697 svn_pool_destroy(connection_pool);
698 continue;
700 if (status)
702 err = svn_error_wrap_apr
703 (status, _("Can't accept client connection"));
704 return svn_cmdline_handle_exit_error(err, pool, "svnserve: ");
707 conn = svn_ra_svn_create_conn(usock, NULL, NULL, connection_pool);
709 if (run_mode == run_mode_listen_once)
711 err = serve(conn, &params, connection_pool);
713 if (err && err->apr_err != SVN_ERR_RA_SVN_CONNECTION_CLOSED)
714 svn_handle_error2(err, stdout, FALSE, "svnserve: ");
715 svn_error_clear(err);
717 apr_socket_close(usock);
718 apr_socket_close(sock);
719 exit(0);
722 switch (handling_mode)
724 case connection_mode_fork:
725 #if APR_HAS_FORK
726 status = apr_proc_fork(&proc, connection_pool);
727 if (status == APR_INCHILD)
729 apr_socket_close(sock);
730 svn_error_clear(serve(conn, &params, connection_pool));
731 apr_socket_close(usock);
732 exit(0);
734 else if (status == APR_INPARENT)
736 apr_socket_close(usock);
738 else
740 /* Log an error, when we support logging. */
741 apr_socket_close(usock);
743 svn_pool_destroy(connection_pool);
744 #endif
745 break;
747 case connection_mode_thread:
748 /* Create a detached thread for each connection. That's not a
749 particularly sophisticated strategy for a threaded server, it's
750 little different from forking one process per connection. */
751 #if APR_HAS_THREADS
752 status = apr_threadattr_create(&tattr, connection_pool);
753 if (status)
755 err = svn_error_wrap_apr(status, _("Can't create threadattr"));
756 svn_handle_error2(err, stderr, FALSE, "svnserve: ");
757 svn_error_clear(err);
758 exit(1);
760 status = apr_threadattr_detach_set(tattr, 1);
761 if (status)
763 err = svn_error_wrap_apr(status, _("Can't set detached state"));
764 svn_handle_error2(err, stderr, FALSE, "svnserve: ");
765 svn_error_clear(err);
766 exit(1);
768 thread_data = apr_palloc(connection_pool, sizeof(*thread_data));
769 thread_data->conn = conn;
770 thread_data->params = &params;
771 thread_data->pool = connection_pool;
772 status = apr_thread_create(&tid, tattr, serve_thread, thread_data,
773 connection_pool);
774 if (status)
776 err = svn_error_wrap_apr(status, _("Can't create thread"));
777 svn_handle_error2(err, stderr, FALSE, "svnserve: ");
778 svn_error_clear(err);
779 exit(1);
781 #endif
782 break;
784 case connection_mode_single:
785 /* Serve one connection at a time. */
786 svn_error_clear(serve(conn, &params, connection_pool));
787 svn_pool_destroy(connection_pool);
791 /* NOTREACHED */