Followup to r29625: fix getopt tests.
[svn.git] / subversion / svnserve / main.c
bloba2fe2819638ed62b517731e2ad3f52181de9a5dc
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 #ifdef SVN_HAVE_SASL
215 svn_stringbuf_appendcstr(version_footer,
216 _("\nCyrus SASL authentication is available.\n"));
217 #endif
219 return svn_opt_print_help(NULL, "svnserve", TRUE, FALSE, version_footer->data,
220 NULL, NULL, NULL, NULL, pool);
224 #if APR_HAS_FORK
225 static void sigchld_handler(int signo)
227 /* Nothing to do; we just need to interrupt the accept(). */
229 #endif
231 /* In tunnel or inetd mode, we don't want hook scripts corrupting the
232 * data stream by sending data to stdout, so we need to redirect
233 * stdout somewhere else. Sending it to stderr is acceptable; sending
234 * it to /dev/null is another option, but apr doesn't provide a way to
235 * do that without also detaching from the controlling terminal.
237 static apr_status_t redirect_stdout(void *arg)
239 apr_pool_t *pool = arg;
240 apr_file_t *out_file, *err_file;
241 apr_status_t apr_err;
243 if ((apr_err = apr_file_open_stdout(&out_file, pool)))
244 return apr_err;
245 if ((apr_err = apr_file_open_stderr(&err_file, pool)))
246 return apr_err;
247 return apr_file_dup2(out_file, err_file, pool);
250 /* "Arguments" passed from the main thread to the connection thread */
251 struct serve_thread_t {
252 svn_ra_svn_conn_t *conn;
253 serve_params_t *params;
254 apr_pool_t *pool;
257 #if APR_HAS_THREADS
258 static void * APR_THREAD_FUNC serve_thread(apr_thread_t *tid, void *data)
260 struct serve_thread_t *d = data;
262 svn_error_clear(serve(d->conn, d->params, d->pool));
263 svn_pool_destroy(d->pool);
265 return NULL;
267 #endif
269 /* Write the PID of the current process as a decimal number, followed by a
270 newline to the file FILENAME, using POOL for temporary allocations. */
271 static svn_error_t *write_pid_file(const char *filename, apr_pool_t *pool)
273 apr_file_t *file;
274 const char *contents = apr_psprintf(pool, "%" APR_PID_T_FMT "\n",
275 getpid());
277 SVN_ERR(svn_io_file_open(&file, filename,
278 APR_WRITE | APR_CREATE | APR_TRUNCATE,
279 APR_OS_DEFAULT, pool));
280 SVN_ERR(svn_io_file_write_full(file, contents, strlen(contents), NULL,
281 pool));
283 SVN_ERR(svn_io_file_close(file, pool));
285 return SVN_NO_ERROR;
288 /* Version compatibility check */
289 static svn_error_t *
290 check_lib_versions(void)
292 static const svn_version_checklist_t checklist[] =
294 { "svn_subr", svn_subr_version },
295 { "svn_repos", svn_repos_version },
296 { "svn_fs", svn_fs_version },
297 { "svn_delta", svn_delta_version },
298 { "svn_ra_svn", svn_ra_svn_version },
299 { NULL, NULL }
302 SVN_VERSION_DEFINE(my_version);
303 return svn_ver_check_list(&my_version, checklist);
307 int main(int argc, const char *argv[])
309 enum run_mode run_mode = run_mode_unspecified;
310 svn_boolean_t foreground = FALSE;
311 apr_socket_t *sock, *usock;
312 apr_file_t *in_file, *out_file;
313 apr_sockaddr_t *sa;
314 apr_pool_t *pool;
315 apr_pool_t *connection_pool;
316 svn_error_t *err;
317 apr_getopt_t *os;
318 int opt;
319 serve_params_t params;
320 const char *arg;
321 apr_status_t status;
322 svn_ra_svn_conn_t *conn;
323 apr_proc_t proc;
324 #if APR_HAS_THREADS
325 apr_threadattr_t *tattr;
326 apr_thread_t *tid;
328 struct serve_thread_t *thread_data;
329 #endif
330 enum connection_handling_mode handling_mode = CONNECTION_DEFAULT;
331 apr_uint16_t port = SVN_RA_SVN_PORT;
332 const char *host = NULL;
333 int family = APR_INET;
334 int mode_opt_count = 0;
335 const char *config_filename = NULL;
336 const char *pid_filename = NULL;
337 svn_node_kind_t kind;
339 /* Initialize the app. */
340 if (svn_cmdline_init("svnserve", stderr) != EXIT_SUCCESS)
341 return EXIT_FAILURE;
343 /* Create our top-level pool. */
344 pool = svn_pool_create(NULL);
346 #ifdef SVN_HAVE_SASL
347 SVN_INT_ERR(cyrus_init(pool));
348 #endif
350 /* Check library versions */
351 err = check_lib_versions();
352 if (err)
353 return svn_cmdline_handle_exit_error(err, pool, "svnserve: ");
355 /* Initialize the FS library. */
356 err = svn_fs_initialize(pool);
357 if (err)
358 return svn_cmdline_handle_exit_error(err, pool, "svnserve: ");
360 err = svn_cmdline__getopt_init(&os, argc, argv, pool);
361 if (err)
362 return svn_cmdline_handle_exit_error(err, pool, "svnserve: ");
364 params.root = "/";
365 params.tunnel = FALSE;
366 params.tunnel_user = NULL;
367 params.read_only = FALSE;
368 params.cfg = NULL;
369 params.pwdb = NULL;
370 params.authzdb = NULL;
372 while (1)
374 status = apr_getopt_long(os, svnserve__options, &opt, &arg);
375 if (APR_STATUS_IS_EOF(status))
376 break;
377 if (status != APR_SUCCESS)
378 usage(argv[0], pool);
379 switch (opt)
381 case 'h':
382 help(pool);
383 break;
385 case SVNSERVE_OPT_VERSION:
386 SVN_INT_ERR(version(pool));
387 exit(0);
388 break;
390 case 'd':
391 if (run_mode != run_mode_daemon)
393 run_mode = run_mode_daemon;
394 mode_opt_count++;
396 break;
398 case SVNSERVE_OPT_FOREGROUND:
399 foreground = TRUE;
400 break;
402 case 'i':
403 if (run_mode != run_mode_inetd)
405 run_mode = run_mode_inetd;
406 mode_opt_count++;
408 break;
410 case SVNSERVE_OPT_LISTEN_PORT:
411 port = atoi(arg);
412 break;
414 case SVNSERVE_OPT_LISTEN_HOST:
415 host = arg;
416 break;
418 case 't':
419 if (run_mode != run_mode_tunnel)
421 run_mode = run_mode_tunnel;
422 mode_opt_count++;
424 break;
426 case SVNSERVE_OPT_TUNNEL_USER:
427 params.tunnel_user = arg;
428 break;
430 case 'X':
431 if (run_mode != run_mode_listen_once)
433 run_mode = run_mode_listen_once;
434 mode_opt_count++;
436 break;
438 case 'r':
439 SVN_INT_ERR(svn_utf_cstring_to_utf8(&params.root, arg, pool));
441 err = svn_io_check_resolved_path(params.root, &kind, pool);
442 if (err)
443 return svn_cmdline_handle_exit_error(err, pool, "svnserve: ");
444 if (kind != svn_node_dir)
446 svn_error_clear
447 (svn_cmdline_fprintf
448 (stderr, pool,
449 _("svnserve: Root path '%s' does not exist "
450 "or is not a directory.\n"), params.root));
451 return EXIT_FAILURE;
454 params.root = svn_path_internal_style(params.root, pool);
455 SVN_INT_ERR(svn_path_get_absolute(&params.root, params.root, pool));
456 break;
458 case 'R':
459 params.read_only = TRUE;
460 break;
462 case 'T':
463 handling_mode = connection_mode_thread;
464 break;
466 #ifdef WIN32
467 case SVNSERVE_OPT_SERVICE:
468 if (run_mode != run_mode_service)
470 run_mode = run_mode_service;
471 mode_opt_count++;
473 break;
474 #endif
476 case SVNSERVE_OPT_CONFIG_FILE:
477 SVN_INT_ERR(svn_utf_cstring_to_utf8(&config_filename, arg, pool));
478 config_filename = svn_path_internal_style(config_filename, pool);
479 SVN_INT_ERR(svn_path_get_absolute(&config_filename, config_filename,
480 pool));
481 break;
483 case SVNSERVE_OPT_PID_FILE:
484 SVN_INT_ERR(svn_utf_cstring_to_utf8(&pid_filename, arg, pool));
485 pid_filename = svn_path_internal_style(pid_filename, pool);
486 SVN_INT_ERR(svn_path_get_absolute(&pid_filename, pid_filename,
487 pool));
488 break;
492 if (os->ind != argc)
493 usage(argv[0], pool);
495 if (mode_opt_count != 1)
497 svn_error_clear(svn_cmdline_fputs
498 (_("You must specify exactly one of -d, -i, -t or -X.\n"),
499 stderr, pool));
500 usage(argv[0], pool);
503 /* If a configuration file is specified, load it and any referenced
504 * password and authorization files. */
505 if (config_filename)
506 SVN_INT_ERR(load_configs(&params.cfg, &params.pwdb, &params.authzdb,
507 config_filename, TRUE,
508 svn_path_dirname(config_filename, pool),
509 pool));
511 if (params.tunnel_user && run_mode != run_mode_tunnel)
513 svn_error_clear
514 (svn_cmdline_fprintf
515 (stderr, pool,
516 _("Option --tunnel-user is only valid in tunnel mode.\n")));
517 exit(1);
520 if (run_mode == run_mode_inetd || run_mode == run_mode_tunnel)
522 params.tunnel = (run_mode == run_mode_tunnel);
523 apr_pool_cleanup_register(pool, pool, apr_pool_cleanup_null,
524 redirect_stdout);
525 status = apr_file_open_stdin(&in_file, pool);
526 if (status)
528 err = svn_error_wrap_apr(status, _("Can't open stdin"));
529 return svn_cmdline_handle_exit_error(err, pool, "svnserve: ");
532 status = apr_file_open_stdout(&out_file, pool);
533 if (status)
535 err = svn_error_wrap_apr(status, _("Can't open stdout"));
536 return svn_cmdline_handle_exit_error(err, pool, "svnserve: ");
539 conn = svn_ra_svn_create_conn(NULL, in_file, out_file, pool);
540 svn_error_clear(serve(conn, &params, pool));
541 exit(0);
544 #ifdef WIN32
545 /* If svnserve needs to run as a Win32 service, then we need to
546 coordinate with the Service Control Manager (SCM) before
547 continuing. This function call registers the svnserve.exe
548 process with the SCM, waits for the "start" command from the SCM
549 (which will come very quickly), and confirms that those steps
550 succeeded.
552 After this call succeeds, the service is free to run. At some
553 point in the future, the SCM will send a message to the service,
554 requesting that it stop. This is translated into a call to
555 winservice_notify_stop(). The service is then responsible for
556 cleanly terminating.
558 We need to do this before actually starting the service logic
559 (opening files, sockets, etc.) because the SCM wants you to
560 connect *first*, then do your service-specific logic. If the
561 service process takes too long to connect to the SCM, then the
562 SCM will decide that the service is busted, and will give up on
565 if (run_mode == run_mode_service)
567 err = winservice_start();
568 if (err)
570 svn_handle_error2(err, stderr, FALSE, "svnserve: ");
572 /* This is the most common error. It means the user started
573 svnserve from a shell, and specified the --service
574 argument. svnserve cannot be started, as a service, in
575 this way. The --service argument is valid only valid if
576 svnserve is started by the SCM. */
577 if (err->apr_err ==
578 APR_FROM_OS_ERROR(ERROR_FAILED_SERVICE_CONTROLLER_CONNECT))
580 svn_error_clear(svn_cmdline_fprintf(stderr, pool,
581 _("svnserve: The --service flag is only valid if the"
582 " process is started by the Service Control Manager.\n")));
585 svn_error_clear(err);
586 exit(1);
589 /* The service is now in the "starting" state. Before the SCM will
590 consider the service "started", this thread must call the
591 winservice_running() function. */
593 #endif /* WIN32 */
595 /* Make sure we have IPV6 support first before giving apr_sockaddr_info_get
596 APR_UNSPEC, because it may give us back an IPV6 address even if we can't
597 create IPV6 sockets. */
599 #if APR_HAVE_IPV6
600 #ifdef MAX_SECS_TO_LINGER
601 /* ### old APR interface */
602 status = apr_socket_create(&sock, APR_INET6, SOCK_STREAM, pool);
603 #else
604 status = apr_socket_create(&sock, APR_INET6, SOCK_STREAM, APR_PROTO_TCP,
605 pool);
606 #endif
607 if (status == 0)
609 apr_socket_close(sock);
610 family = APR_UNSPEC;
612 #endif
614 status = apr_sockaddr_info_get(&sa, host, family, port, 0, pool);
615 if (status)
617 err = svn_error_wrap_apr(status, _("Can't get address info"));
618 return svn_cmdline_handle_exit_error(err, pool, "svnserve: ");
622 #ifdef MAX_SECS_TO_LINGER
623 /* ### old APR interface */
624 status = apr_socket_create(&sock, sa->family, SOCK_STREAM, pool);
625 #else
626 status = apr_socket_create(&sock, sa->family, SOCK_STREAM, APR_PROTO_TCP,
627 pool);
628 #endif
629 if (status)
631 err = svn_error_wrap_apr(status, _("Can't create server socket"));
632 return svn_cmdline_handle_exit_error(err, pool, "svnserve: ");
635 /* Prevents "socket in use" errors when server is killed and quickly
636 * restarted. */
637 apr_socket_opt_set(sock, APR_SO_REUSEADDR, 1);
639 status = apr_socket_bind(sock, sa);
640 if (status)
642 err = svn_error_wrap_apr(status, _("Can't bind server socket"));
643 return svn_cmdline_handle_exit_error(err, pool, "svnserve: ");
646 apr_socket_listen(sock, 7);
648 #if APR_HAS_FORK
649 if (run_mode != run_mode_listen_once && !foreground)
650 apr_proc_detach(APR_PROC_DETACH_DAEMONIZE);
652 apr_signal(SIGCHLD, sigchld_handler);
653 #endif
655 #ifdef SIGPIPE
656 /* Disable SIGPIPE generation for the platforms that have it. */
657 apr_signal(SIGPIPE, SIG_IGN);
658 #endif
660 #ifdef SIGXFSZ
661 /* Disable SIGXFSZ generation for the platforms that have it, otherwise
662 * working with large files when compiled against an APR that doesn't have
663 * large file support will crash the program, which is uncool. */
664 apr_signal(SIGXFSZ, SIG_IGN);
665 #endif
667 if (pid_filename)
668 SVN_INT_ERR(write_pid_file(pid_filename, pool));
670 #ifdef WIN32
671 status = apr_os_sock_get(&winservice_svnserve_accept_socket, sock);
672 if (status)
673 winservice_svnserve_accept_socket = INVALID_SOCKET;
675 /* At this point, the service is "running". Notify the SCM. */
676 if (run_mode == run_mode_service)
677 winservice_running();
678 #endif
680 while (1)
682 #ifdef WIN32
683 if (winservice_is_stopping())
684 return ERROR_SUCCESS;
685 #endif
687 /* Non-standard pool handling. The main thread never blocks to join
688 the connection threads so it cannot clean up after each one. So
689 separate pools, that can be cleared at thread exit, are used */
690 connection_pool = svn_pool_create(NULL);
692 status = apr_socket_accept(&usock, sock, connection_pool);
693 if (handling_mode == connection_mode_fork)
695 /* Collect any zombie child processes. */
696 while (apr_proc_wait_all_procs(&proc, NULL, NULL, APR_NOWAIT,
697 connection_pool) == APR_CHILD_DONE)
700 if (APR_STATUS_IS_EINTR(status))
702 svn_pool_destroy(connection_pool);
703 continue;
705 if (status)
707 err = svn_error_wrap_apr
708 (status, _("Can't accept client connection"));
709 return svn_cmdline_handle_exit_error(err, pool, "svnserve: ");
712 conn = svn_ra_svn_create_conn(usock, NULL, NULL, connection_pool);
714 if (run_mode == run_mode_listen_once)
716 err = serve(conn, &params, connection_pool);
718 if (err && err->apr_err != SVN_ERR_RA_SVN_CONNECTION_CLOSED)
719 svn_handle_error2(err, stdout, FALSE, "svnserve: ");
720 svn_error_clear(err);
722 apr_socket_close(usock);
723 apr_socket_close(sock);
724 exit(0);
727 switch (handling_mode)
729 case connection_mode_fork:
730 #if APR_HAS_FORK
731 status = apr_proc_fork(&proc, connection_pool);
732 if (status == APR_INCHILD)
734 apr_socket_close(sock);
735 svn_error_clear(serve(conn, &params, connection_pool));
736 apr_socket_close(usock);
737 exit(0);
739 else if (status == APR_INPARENT)
741 apr_socket_close(usock);
743 else
745 /* Log an error, when we support logging. */
746 apr_socket_close(usock);
748 svn_pool_destroy(connection_pool);
749 #endif
750 break;
752 case connection_mode_thread:
753 /* Create a detached thread for each connection. That's not a
754 particularly sophisticated strategy for a threaded server, it's
755 little different from forking one process per connection. */
756 #if APR_HAS_THREADS
757 status = apr_threadattr_create(&tattr, connection_pool);
758 if (status)
760 err = svn_error_wrap_apr(status, _("Can't create threadattr"));
761 svn_handle_error2(err, stderr, FALSE, "svnserve: ");
762 svn_error_clear(err);
763 exit(1);
765 status = apr_threadattr_detach_set(tattr, 1);
766 if (status)
768 err = svn_error_wrap_apr(status, _("Can't set detached state"));
769 svn_handle_error2(err, stderr, FALSE, "svnserve: ");
770 svn_error_clear(err);
771 exit(1);
773 thread_data = apr_palloc(connection_pool, sizeof(*thread_data));
774 thread_data->conn = conn;
775 thread_data->params = &params;
776 thread_data->pool = connection_pool;
777 status = apr_thread_create(&tid, tattr, serve_thread, thread_data,
778 connection_pool);
779 if (status)
781 err = svn_error_wrap_apr(status, _("Can't create thread"));
782 svn_handle_error2(err, stderr, FALSE, "svnserve: ");
783 svn_error_clear(err);
784 exit(1);
786 #endif
787 break;
789 case connection_mode_single:
790 /* Serve one connection at a time. */
791 svn_error_clear(serve(conn, &params, connection_pool));
792 svn_pool_destroy(connection_pool);
796 /* NOTREACHED */