| /* |
| * main.c : Main control function for svnserve |
| * |
| * ==================================================================== |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| * ==================================================================== |
| */ |
| |
| |
| |
| #define APR_WANT_STRFUNC |
| #include <apr_want.h> |
| #include <apr_general.h> |
| #include <apr_getopt.h> |
| #include <apr_network_io.h> |
| #include <apr_signal.h> |
| #include <apr_thread_proc.h> |
| #include <apr_portable.h> |
| |
| #include <locale.h> |
| |
| #include "svn_cmdline.h" |
| #include "svn_types.h" |
| #include "svn_pools.h" |
| #include "svn_error.h" |
| #include "svn_ra_svn.h" |
| #include "svn_utf.h" |
| #include "svn_dirent_uri.h" |
| #include "svn_path.h" |
| #include "svn_opt.h" |
| #include "svn_repos.h" |
| #include "svn_string.h" |
| #include "svn_fs.h" |
| #include "svn_version.h" |
| #include "svn_io.h" |
| |
| #include "svn_private_config.h" |
| #include "private/svn_dep_compat.h" |
| #include "winservice.h" |
| |
| #ifdef HAVE_UNISTD_H |
| #include <unistd.h> /* For getpid() */ |
| #endif |
| |
| #include "server.h" |
| |
| /* The strategy for handling incoming connections. Some of these may be |
| unavailable due to platform limitations. */ |
| enum connection_handling_mode { |
| connection_mode_fork, /* Create a process per connection */ |
| connection_mode_thread, /* Create a thread per connection */ |
| connection_mode_single /* One connection at a time in this process */ |
| }; |
| |
| /* The mode in which to run svnserve */ |
| enum run_mode { |
| run_mode_unspecified, |
| run_mode_inetd, |
| run_mode_daemon, |
| run_mode_tunnel, |
| run_mode_listen_once, |
| run_mode_service |
| }; |
| |
| #if APR_HAS_FORK |
| #if APR_HAS_THREADS |
| |
| #define CONNECTION_DEFAULT connection_mode_fork |
| #define CONNECTION_HAVE_THREAD_OPTION |
| |
| #else /* ! APR_HAS_THREADS */ |
| |
| #define CONNECTION_DEFAULT connection_mode_fork |
| |
| #endif /* ! APR_HAS_THREADS */ |
| #elif APR_HAS_THREADS /* and ! APR_HAS_FORK */ |
| |
| #define CONNECTION_DEFAULT connection_mode_thread |
| |
| #else /* ! APR_HAS_THREADS and ! APR_HAS_FORK */ |
| |
| #define CONNECTION_DEFAULT connection_mode_single |
| |
| #endif |
| |
| |
| #ifdef WIN32 |
| static apr_os_sock_t winservice_svnserve_accept_socket = INVALID_SOCKET; |
| |
| /* The SCM calls this function (on an arbitrary thread, not the main() |
| thread!) when it wants to stop the service. |
| |
| For now, our strategy is to close the listener socket, in order to |
| unblock main() and cause it to exit its accept loop. We cannot use |
| apr_socket_close, because that function deletes the apr_socket_t |
| structure, as well as closing the socket handle. If we called |
| apr_socket_close here, then main() will also call apr_socket_close, |
| resulting in a double-free. This way, we just close the kernel |
| socket handle, which causes the accept() function call to fail, |
| which causes main() to clean up the socket. So, memory gets freed |
| only once. |
| |
| This isn't pretty, but it's better than a lot of other options. |
| Currently, there is no "right" way to shut down svnserve. |
| |
| We store the OS handle rather than a pointer to the apr_socket_t |
| structure in order to eliminate any possibility of illegal memory |
| access. */ |
| void winservice_notify_stop(void) |
| { |
| if (winservice_svnserve_accept_socket != INVALID_SOCKET) |
| closesocket(winservice_svnserve_accept_socket); |
| } |
| #endif /* _WIN32 */ |
| |
| |
| /* Option codes and descriptions for svnserve. |
| * |
| * The entire list must be terminated with an entry of nulls. |
| * |
| * APR requires that options without abbreviations |
| * have codes greater than 255. |
| */ |
| #define SVNSERVE_OPT_LISTEN_PORT 256 |
| #define SVNSERVE_OPT_LISTEN_HOST 257 |
| #define SVNSERVE_OPT_FOREGROUND 258 |
| #define SVNSERVE_OPT_TUNNEL_USER 259 |
| #define SVNSERVE_OPT_VERSION 260 |
| #define SVNSERVE_OPT_PID_FILE 261 |
| #define SVNSERVE_OPT_SERVICE 262 |
| #define SVNSERVE_OPT_CONFIG_FILE 263 |
| #define SVNSERVE_OPT_LOG_FILE 264 |
| |
| static const apr_getopt_option_t svnserve__options[] = |
| { |
| {"daemon", 'd', 0, N_("daemon mode")}, |
| {"inetd", 'i', 0, N_("inetd mode")}, |
| {"tunnel", 't', 0, N_("tunnel mode")}, |
| {"listen-once", 'X', 0, N_("listen-once mode (useful for debugging)")}, |
| #ifdef WIN32 |
| {"service", SVNSERVE_OPT_SERVICE, 0, |
| N_("Windows service mode (Service Control Manager)")}, |
| #endif |
| {"root", 'r', 1, N_("root of directory to serve")}, |
| {"read-only", 'R', 0, |
| N_("force read only, overriding repository config file")}, |
| {"config-file", SVNSERVE_OPT_CONFIG_FILE, 1, |
| N_("read configuration from file ARG")}, |
| {"listen-port", SVNSERVE_OPT_LISTEN_PORT, 1, |
| #ifdef WIN32 |
| N_("listen port\n" |
| " " |
| "[mode: daemon, service, listen-once]")}, |
| #else |
| N_("listen port\n" |
| " " |
| "[mode: daemon, listen-once]")}, |
| #endif |
| {"listen-host", SVNSERVE_OPT_LISTEN_HOST, 1, |
| #ifdef WIN32 |
| N_("listen hostname or IP address\n" |
| " " |
| "[mode: daemon, service, listen-once]")}, |
| #else |
| N_("listen hostname or IP address\n" |
| " " |
| "[mode: daemon, listen-once]")}, |
| #endif |
| {"prefer-ipv6", '6', 0, |
| N_("prefer IPv6 when resolving the listen hostname\n" |
| " " |
| "[IPv4 is preferred by default. Using IPv4 and IPv6\n" |
| " " |
| "at the same time is not supported in daemon mode.\n" |
| " " |
| "Use inetd mode or tunnel mode if you need this.]")}, |
| {"compression", 'c', 1, |
| N_("compression level to use for network transmissions\n" |
| " " |
| "[0 .. no compression, 5 .. default, \n" |
| " " |
| " 9 .. maximum compression]")}, |
| {"memory-cache-size", 'M', 1, |
| N_("size of the extra in-memory cache in MB used to\n" |
| " " |
| "minimize redundant operations.\n" |
| " " |
| "Default is 128 for threaded and 16 for non-\n" |
| " " |
| "threaded mode.\n" |
| " " |
| "[used for FSFS repositories only]")}, |
| #ifdef CONNECTION_HAVE_THREAD_OPTION |
| /* ### Making the assumption here that WIN32 never has fork and so |
| * ### this option never exists when --service exists. */ |
| {"threads", 'T', 0, N_("use threads instead of fork " |
| "[mode: daemon]")}, |
| #endif |
| {"foreground", SVNSERVE_OPT_FOREGROUND, 0, |
| N_("run in foreground (useful for debugging)\n" |
| " " |
| "[mode: daemon]")}, |
| {"log-file", SVNSERVE_OPT_LOG_FILE, 1, |
| N_("svnserve log file")}, |
| {"pid-file", SVNSERVE_OPT_PID_FILE, 1, |
| #ifdef WIN32 |
| N_("write server process ID to file ARG\n" |
| " " |
| "[mode: daemon, listen-once, service]")}, |
| #else |
| N_("write server process ID to file ARG\n" |
| " " |
| "[mode: daemon, listen-once]")}, |
| #endif |
| {"tunnel-user", SVNSERVE_OPT_TUNNEL_USER, 1, |
| N_("tunnel username (default is current uid's name)\n" |
| " " |
| "[mode: tunnel]")}, |
| {"help", 'h', 0, N_("display this help")}, |
| {"version", SVNSERVE_OPT_VERSION, 0, |
| N_("show program version information")}, |
| {"quiet", 'q', 0, |
| N_("no progress (only errors) to stderr")}, |
| {0, 0, 0, 0} |
| }; |
| |
| |
| static void usage(const char *progname, apr_pool_t *pool) |
| { |
| if (!progname) |
| progname = "svnserve"; |
| |
| svn_error_clear(svn_cmdline_fprintf(stderr, pool, |
| _("Type '%s --help' for usage.\n"), |
| progname)); |
| exit(1); |
| } |
| |
| static void help(apr_pool_t *pool) |
| { |
| apr_size_t i; |
| |
| #ifdef WIN32 |
| svn_error_clear(svn_cmdline_fputs(_("usage: svnserve [-d | -i | -t | -X " |
| "| --service] [options]\n" |
| "\n" |
| "Valid options:\n"), |
| stdout, pool)); |
| #else |
| svn_error_clear(svn_cmdline_fputs(_("usage: svnserve [-d | -i | -t | -X] " |
| "[options]\n" |
| "\n" |
| "Valid options:\n"), |
| stdout, pool)); |
| #endif |
| for (i = 0; svnserve__options[i].name && svnserve__options[i].optch; i++) |
| { |
| const char *optstr; |
| svn_opt_format_option(&optstr, svnserve__options + i, TRUE, pool); |
| svn_error_clear(svn_cmdline_fprintf(stdout, pool, " %s\n", optstr)); |
| } |
| svn_error_clear(svn_cmdline_fprintf(stdout, pool, "\n")); |
| exit(0); |
| } |
| |
| static svn_error_t * version(svn_boolean_t quiet, apr_pool_t *pool) |
| { |
| const char *fs_desc_start |
| = _("The following repository back-end (FS) modules are available:\n\n"); |
| |
| svn_stringbuf_t *version_footer; |
| |
| version_footer = svn_stringbuf_create(fs_desc_start, pool); |
| SVN_ERR(svn_fs_print_modules(version_footer, pool)); |
| |
| #ifdef SVN_HAVE_SASL |
| svn_stringbuf_appendcstr(version_footer, |
| _("\nCyrus SASL authentication is available.\n")); |
| #endif |
| |
| return svn_opt_print_help3(NULL, "svnserve", TRUE, quiet, version_footer->data, |
| NULL, NULL, NULL, NULL, NULL, pool); |
| } |
| |
| |
| #if APR_HAS_FORK |
| static void sigchld_handler(int signo) |
| { |
| /* Nothing to do; we just need to interrupt the accept(). */ |
| } |
| #endif |
| |
| /* In tunnel or inetd mode, we don't want hook scripts corrupting the |
| * data stream by sending data to stdout, so we need to redirect |
| * stdout somewhere else. Sending it to stderr is acceptable; sending |
| * it to /dev/null is another option, but apr doesn't provide a way to |
| * do that without also detaching from the controlling terminal. |
| */ |
| static apr_status_t redirect_stdout(void *arg) |
| { |
| apr_pool_t *pool = arg; |
| apr_file_t *out_file, *err_file; |
| apr_status_t apr_err; |
| |
| if ((apr_err = apr_file_open_stdout(&out_file, pool))) |
| return apr_err; |
| if ((apr_err = apr_file_open_stderr(&err_file, pool))) |
| return apr_err; |
| return apr_file_dup2(out_file, err_file, pool); |
| } |
| |
| /* "Arguments" passed from the main thread to the connection thread */ |
| struct serve_thread_t { |
| svn_ra_svn_conn_t *conn; |
| serve_params_t *params; |
| apr_pool_t *pool; |
| }; |
| |
| #if APR_HAS_THREADS |
| static void * APR_THREAD_FUNC serve_thread(apr_thread_t *tid, void *data) |
| { |
| struct serve_thread_t *d = data; |
| |
| svn_error_clear(serve(d->conn, d->params, d->pool)); |
| svn_pool_destroy(d->pool); |
| |
| return NULL; |
| } |
| #endif |
| |
| /* Write the PID of the current process as a decimal number, followed by a |
| newline to the file FILENAME, using POOL for temporary allocations. */ |
| static svn_error_t *write_pid_file(const char *filename, apr_pool_t *pool) |
| { |
| apr_file_t *file; |
| const char *contents = apr_psprintf(pool, "%" APR_PID_T_FMT "\n", |
| getpid()); |
| |
| SVN_ERR(svn_io_file_open(&file, filename, |
| APR_WRITE | APR_CREATE | APR_TRUNCATE, |
| APR_OS_DEFAULT, pool)); |
| SVN_ERR(svn_io_file_write_full(file, contents, strlen(contents), NULL, |
| pool)); |
| |
| SVN_ERR(svn_io_file_close(file, pool)); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| /* Version compatibility check */ |
| static svn_error_t * |
| check_lib_versions(void) |
| { |
| static const svn_version_checklist_t checklist[] = |
| { |
| { "svn_subr", svn_subr_version }, |
| { "svn_repos", svn_repos_version }, |
| { "svn_fs", svn_fs_version }, |
| { "svn_delta", svn_delta_version }, |
| { "svn_ra_svn", svn_ra_svn_version }, |
| { NULL, NULL } |
| }; |
| |
| SVN_VERSION_DEFINE(my_version); |
| return svn_ver_check_list(&my_version, checklist); |
| } |
| |
| |
| int main(int argc, const char *argv[]) |
| { |
| enum run_mode run_mode = run_mode_unspecified; |
| svn_boolean_t foreground = FALSE; |
| apr_socket_t *sock, *usock; |
| apr_file_t *in_file, *out_file; |
| apr_sockaddr_t *sa; |
| apr_pool_t *pool; |
| apr_pool_t *connection_pool; |
| apr_allocator_t *allocator; |
| svn_error_t *err; |
| apr_getopt_t *os; |
| int opt; |
| serve_params_t params; |
| const char *arg; |
| apr_status_t status; |
| svn_ra_svn_conn_t *conn; |
| apr_proc_t proc; |
| #if APR_HAS_THREADS |
| apr_threadattr_t *tattr; |
| apr_thread_t *tid; |
| |
| struct serve_thread_t *thread_data; |
| #endif |
| enum connection_handling_mode handling_mode = CONNECTION_DEFAULT; |
| apr_uint16_t port = SVN_RA_SVN_PORT; |
| const char *host = NULL; |
| int family = APR_INET; |
| apr_int32_t sockaddr_info_flags = 0; |
| svn_boolean_t prefer_v6 = FALSE; |
| svn_boolean_t quiet = FALSE; |
| svn_boolean_t is_version = FALSE; |
| int mode_opt_count = 0; |
| const char *config_filename = NULL; |
| const char *pid_filename = NULL; |
| const char *log_filename = NULL; |
| svn_node_kind_t kind; |
| |
| /* Initialize the app. */ |
| if (svn_cmdline_init("svnserve", stderr) != EXIT_SUCCESS) |
| return EXIT_FAILURE; |
| |
| /* Create our top-level pool. */ |
| pool = svn_pool_create(NULL); |
| |
| #ifdef SVN_HAVE_SASL |
| SVN_INT_ERR(cyrus_init(pool)); |
| #endif |
| |
| /* Check library versions */ |
| err = check_lib_versions(); |
| if (err) |
| return svn_cmdline_handle_exit_error(err, pool, "svnserve: "); |
| |
| /* Initialize the FS library. */ |
| err = svn_fs_initialize(pool); |
| if (err) |
| return svn_cmdline_handle_exit_error(err, pool, "svnserve: "); |
| |
| err = svn_cmdline__getopt_init(&os, argc, argv, pool); |
| if (err) |
| return svn_cmdline_handle_exit_error(err, pool, "svnserve: "); |
| |
| params.root = "/"; |
| params.tunnel = FALSE; |
| params.tunnel_user = NULL; |
| params.read_only = FALSE; |
| params.cfg = NULL; |
| params.pwdb = NULL; |
| params.authzdb = NULL; |
| params.compression_level = SVN_DEFAULT_COMPRESSION_LEVEL; |
| params.log_file = NULL; |
| params.username_case = CASE_ASIS; |
| params.memory_cache_size = (apr_uint64_t)-1; |
| |
| while (1) |
| { |
| status = apr_getopt_long(os, svnserve__options, &opt, &arg); |
| if (APR_STATUS_IS_EOF(status)) |
| break; |
| if (status != APR_SUCCESS) |
| usage(argv[0], pool); |
| switch (opt) |
| { |
| case '6': |
| prefer_v6 = TRUE; |
| break; |
| |
| case 'h': |
| help(pool); |
| break; |
| |
| case 'q': |
| quiet = TRUE; |
| break; |
| |
| case SVNSERVE_OPT_VERSION: |
| is_version = TRUE; |
| break; |
| |
| case 'd': |
| if (run_mode != run_mode_daemon) |
| { |
| run_mode = run_mode_daemon; |
| mode_opt_count++; |
| } |
| break; |
| |
| case SVNSERVE_OPT_FOREGROUND: |
| foreground = TRUE; |
| break; |
| |
| case 'i': |
| if (run_mode != run_mode_inetd) |
| { |
| run_mode = run_mode_inetd; |
| mode_opt_count++; |
| } |
| break; |
| |
| case SVNSERVE_OPT_LISTEN_PORT: |
| { |
| apr_uint64_t val; |
| |
| err = svn_cstring_strtoui64(&val, arg, 0, APR_UINT16_MAX, 10); |
| if (err) |
| return svn_cmdline_handle_exit_error( |
| svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, err, |
| _("Invalid port '%s'"), arg), |
| pool, "svnserve: "); |
| port = (apr_uint16_t)val; |
| } |
| break; |
| |
| case SVNSERVE_OPT_LISTEN_HOST: |
| host = arg; |
| break; |
| |
| case 't': |
| if (run_mode != run_mode_tunnel) |
| { |
| run_mode = run_mode_tunnel; |
| mode_opt_count++; |
| } |
| break; |
| |
| case SVNSERVE_OPT_TUNNEL_USER: |
| params.tunnel_user = arg; |
| break; |
| |
| case 'X': |
| if (run_mode != run_mode_listen_once) |
| { |
| run_mode = run_mode_listen_once; |
| mode_opt_count++; |
| } |
| break; |
| |
| case 'r': |
| SVN_INT_ERR(svn_utf_cstring_to_utf8(¶ms.root, arg, pool)); |
| |
| err = svn_io_check_resolved_path(params.root, &kind, pool); |
| if (err) |
| return svn_cmdline_handle_exit_error(err, pool, "svnserve: "); |
| if (kind != svn_node_dir) |
| { |
| svn_error_clear |
| (svn_cmdline_fprintf |
| (stderr, pool, |
| _("svnserve: Root path '%s' does not exist " |
| "or is not a directory.\n"), params.root)); |
| return EXIT_FAILURE; |
| } |
| |
| params.root = svn_dirent_internal_style(params.root, pool); |
| SVN_INT_ERR(svn_dirent_get_absolute(¶ms.root, params.root, pool)); |
| break; |
| |
| case 'R': |
| params.read_only = TRUE; |
| break; |
| |
| case 'T': |
| handling_mode = connection_mode_thread; |
| break; |
| |
| case 'c': |
| params.compression_level = atoi(arg); |
| if (params.compression_level < SVN_NO_COMPRESSION_LEVEL) |
| params.compression_level = SVN_NO_COMPRESSION_LEVEL; |
| if (params.compression_level > SVN_MAX_COMPRESSION_LEVEL) |
| params.compression_level = SVN_MAX_COMPRESSION_LEVEL; |
| break; |
| |
| case 'M': |
| params.memory_cache_size = 0x100000 * apr_strtoi64(arg, NULL, 0); |
| break; |
| |
| #ifdef WIN32 |
| case SVNSERVE_OPT_SERVICE: |
| if (run_mode != run_mode_service) |
| { |
| run_mode = run_mode_service; |
| mode_opt_count++; |
| } |
| break; |
| #endif |
| |
| case SVNSERVE_OPT_CONFIG_FILE: |
| SVN_INT_ERR(svn_utf_cstring_to_utf8(&config_filename, arg, pool)); |
| config_filename = svn_dirent_internal_style(config_filename, pool); |
| SVN_INT_ERR(svn_dirent_get_absolute(&config_filename, config_filename, |
| pool)); |
| break; |
| |
| case SVNSERVE_OPT_PID_FILE: |
| SVN_INT_ERR(svn_utf_cstring_to_utf8(&pid_filename, arg, pool)); |
| pid_filename = svn_dirent_internal_style(pid_filename, pool); |
| SVN_INT_ERR(svn_dirent_get_absolute(&pid_filename, pid_filename, |
| pool)); |
| break; |
| |
| case SVNSERVE_OPT_LOG_FILE: |
| SVN_INT_ERR(svn_utf_cstring_to_utf8(&log_filename, arg, pool)); |
| log_filename = svn_dirent_internal_style(log_filename, pool); |
| SVN_INT_ERR(svn_dirent_get_absolute(&log_filename, log_filename, |
| pool)); |
| break; |
| |
| } |
| } |
| |
| if (is_version) |
| { |
| SVN_INT_ERR(version(quiet, pool)); |
| exit(0); |
| } |
| |
| if (os->ind != argc) |
| usage(argv[0], pool); |
| |
| if (mode_opt_count != 1) |
| { |
| svn_error_clear(svn_cmdline_fputs( |
| #ifdef WIN32 |
| _("You must specify exactly one of -d, -i, -t, " |
| "--service or -X.\n"), |
| #else |
| _("You must specify exactly one of -d, -i, -t or -X.\n"), |
| #endif |
| stderr, pool)); |
| usage(argv[0], pool); |
| } |
| |
| /* If a configuration file is specified, load it and any referenced |
| * password and authorization files. */ |
| if (config_filename) |
| SVN_INT_ERR(load_configs(¶ms.cfg, ¶ms.pwdb, ¶ms.authzdb, |
| ¶ms.username_case, config_filename, TRUE, |
| svn_dirent_dirname(config_filename, pool), |
| NULL, NULL, /* server baton, conn */ |
| pool)); |
| |
| if (log_filename) |
| SVN_INT_ERR(svn_io_file_open(¶ms.log_file, log_filename, |
| APR_WRITE | APR_CREATE | APR_APPEND, |
| APR_OS_DEFAULT, pool)); |
| |
| if (params.tunnel_user && run_mode != run_mode_tunnel) |
| { |
| svn_error_clear |
| (svn_cmdline_fprintf |
| (stderr, pool, |
| _("Option --tunnel-user is only valid in tunnel mode.\n"))); |
| exit(1); |
| } |
| |
| if (run_mode == run_mode_inetd || run_mode == run_mode_tunnel) |
| { |
| params.tunnel = (run_mode == run_mode_tunnel); |
| apr_pool_cleanup_register(pool, pool, apr_pool_cleanup_null, |
| redirect_stdout); |
| status = apr_file_open_stdin(&in_file, pool); |
| if (status) |
| { |
| err = svn_error_wrap_apr(status, _("Can't open stdin")); |
| return svn_cmdline_handle_exit_error(err, pool, "svnserve: "); |
| } |
| |
| status = apr_file_open_stdout(&out_file, pool); |
| if (status) |
| { |
| err = svn_error_wrap_apr(status, _("Can't open stdout")); |
| return svn_cmdline_handle_exit_error(err, pool, "svnserve: "); |
| } |
| |
| conn = svn_ra_svn_create_conn2(NULL, in_file, out_file, |
| params.compression_level, pool); |
| svn_error_clear(serve(conn, ¶ms, pool)); |
| exit(0); |
| } |
| |
| #ifdef WIN32 |
| /* If svnserve needs to run as a Win32 service, then we need to |
| coordinate with the Service Control Manager (SCM) before |
| continuing. This function call registers the svnserve.exe |
| process with the SCM, waits for the "start" command from the SCM |
| (which will come very quickly), and confirms that those steps |
| succeeded. |
| |
| After this call succeeds, the service is free to run. At some |
| point in the future, the SCM will send a message to the service, |
| requesting that it stop. This is translated into a call to |
| winservice_notify_stop(). The service is then responsible for |
| cleanly terminating. |
| |
| We need to do this before actually starting the service logic |
| (opening files, sockets, etc.) because the SCM wants you to |
| connect *first*, then do your service-specific logic. If the |
| service process takes too long to connect to the SCM, then the |
| SCM will decide that the service is busted, and will give up on |
| it. |
| */ |
| if (run_mode == run_mode_service) |
| { |
| err = winservice_start(); |
| if (err) |
| { |
| svn_handle_error2(err, stderr, FALSE, "svnserve: "); |
| |
| /* This is the most common error. It means the user started |
| svnserve from a shell, and specified the --service |
| argument. svnserve cannot be started, as a service, in |
| this way. The --service argument is valid only valid if |
| svnserve is started by the SCM. */ |
| if (err->apr_err == |
| APR_FROM_OS_ERROR(ERROR_FAILED_SERVICE_CONTROLLER_CONNECT)) |
| { |
| svn_error_clear(svn_cmdline_fprintf(stderr, pool, |
| _("svnserve: The --service flag is only valid if the" |
| " process is started by the Service Control Manager.\n"))); |
| } |
| |
| svn_error_clear(err); |
| exit(1); |
| } |
| |
| /* The service is now in the "starting" state. Before the SCM will |
| consider the service "started", this thread must call the |
| winservice_running() function. */ |
| } |
| #endif /* WIN32 */ |
| |
| /* Make sure we have IPV6 support first before giving apr_sockaddr_info_get |
| APR_UNSPEC, because it may give us back an IPV6 address even if we can't |
| create IPV6 sockets. */ |
| |
| #if APR_HAVE_IPV6 |
| #ifdef MAX_SECS_TO_LINGER |
| /* ### old APR interface */ |
| status = apr_socket_create(&sock, APR_INET6, SOCK_STREAM, pool); |
| #else |
| status = apr_socket_create(&sock, APR_INET6, SOCK_STREAM, APR_PROTO_TCP, |
| pool); |
| #endif |
| if (status == 0) |
| { |
| apr_socket_close(sock); |
| family = APR_UNSPEC; |
| |
| if (prefer_v6) |
| { |
| if (host == NULL) |
| host = "::"; |
| sockaddr_info_flags = APR_IPV6_ADDR_OK; |
| } |
| else |
| { |
| if (host == NULL) |
| host = "0.0.0.0"; |
| sockaddr_info_flags = APR_IPV4_ADDR_OK; |
| } |
| } |
| #endif |
| |
| status = apr_sockaddr_info_get(&sa, host, family, port, |
| sockaddr_info_flags, pool); |
| if (status) |
| { |
| err = svn_error_wrap_apr(status, _("Can't get address info")); |
| return svn_cmdline_handle_exit_error(err, pool, "svnserve: "); |
| } |
| |
| |
| #ifdef MAX_SECS_TO_LINGER |
| /* ### old APR interface */ |
| status = apr_socket_create(&sock, sa->family, SOCK_STREAM, pool); |
| #else |
| status = apr_socket_create(&sock, sa->family, SOCK_STREAM, APR_PROTO_TCP, |
| pool); |
| #endif |
| if (status) |
| { |
| err = svn_error_wrap_apr(status, _("Can't create server socket")); |
| return svn_cmdline_handle_exit_error(err, pool, "svnserve: "); |
| } |
| |
| /* Prevents "socket in use" errors when server is killed and quickly |
| * restarted. */ |
| apr_socket_opt_set(sock, APR_SO_REUSEADDR, 1); |
| |
| status = apr_socket_bind(sock, sa); |
| if (status) |
| { |
| err = svn_error_wrap_apr(status, _("Can't bind server socket")); |
| return svn_cmdline_handle_exit_error(err, pool, "svnserve: "); |
| } |
| |
| apr_socket_listen(sock, 7); |
| |
| #if APR_HAS_FORK |
| if (run_mode != run_mode_listen_once && !foreground) |
| apr_proc_detach(APR_PROC_DETACH_DAEMONIZE); |
| |
| apr_signal(SIGCHLD, sigchld_handler); |
| #endif |
| |
| #ifdef SIGPIPE |
| /* Disable SIGPIPE generation for the platforms that have it. */ |
| apr_signal(SIGPIPE, SIG_IGN); |
| #endif |
| |
| #ifdef SIGXFSZ |
| /* Disable SIGXFSZ generation for the platforms that have it, otherwise |
| * working with large files when compiled against an APR that doesn't have |
| * large file support will crash the program, which is uncool. */ |
| apr_signal(SIGXFSZ, SIG_IGN); |
| #endif |
| |
| if (pid_filename) |
| SVN_INT_ERR(write_pid_file(pid_filename, pool)); |
| |
| #ifdef WIN32 |
| status = apr_os_sock_get(&winservice_svnserve_accept_socket, sock); |
| if (status) |
| winservice_svnserve_accept_socket = INVALID_SOCKET; |
| |
| /* At this point, the service is "running". Notify the SCM. */ |
| if (run_mode == run_mode_service) |
| winservice_running(); |
| #endif |
| |
| /* Configure FS caches for maximum efficiency with svnserve. |
| * For pre-forked (i.e. multi-processed) mode of operation, |
| * keep the per-process caches smaller than the default. |
| * Also, apply the respective command line parameters, if given. */ |
| { |
| svn_fs_cache_config_t settings = *svn_fs_get_cache_config(); |
| |
| if (params.memory_cache_size != -1) |
| settings.cache_size = params.memory_cache_size; |
| else if (handling_mode != connection_mode_thread) |
| settings.cache_size = 0x1000000; |
| |
| settings.cache_fulltexts = TRUE; |
| settings.cache_txdeltas = FALSE; |
| settings.single_threaded = TRUE; |
| |
| if (handling_mode == connection_mode_thread) |
| { |
| #ifdef APR_HAS_THREADS |
| settings.single_threaded = FALSE; |
| #else |
| /* No requests will be processed at all |
| * (see "switch (handling_mode)" code further down). |
| * But if they were, some other synchronization code |
| * would need to take care of securing integrity of |
| * APR-based structures. That would include our caches. |
| */ |
| #endif |
| } |
| |
| svn_fs_set_cache_config(&settings); |
| } |
| |
| while (1) |
| { |
| #ifdef WIN32 |
| if (winservice_is_stopping()) |
| return ERROR_SUCCESS; |
| #endif |
| |
| /* If we are using fulltext caches etc. we will allocate many large |
| chunks of memory of various sizes outside the cache for those |
| fulltexts. Make sure we use the memory wisely: use an allocator |
| that causes memory fragments to be given back to the OS early. */ |
| |
| if (apr_allocator_create(&allocator)) |
| return EXIT_FAILURE; |
| |
| apr_allocator_max_free_set(allocator, SVN_ALLOCATOR_RECOMMENDED_MAX_FREE); |
| |
| /* Non-standard pool handling. The main thread never blocks to join |
| the connection threads so it cannot clean up after each one. So |
| separate pools that can be cleared at thread exit are used. */ |
| |
| connection_pool = svn_pool_create_ex(NULL, allocator); |
| apr_allocator_owner_set(allocator, connection_pool); |
| |
| status = apr_socket_accept(&usock, sock, connection_pool); |
| if (handling_mode == connection_mode_fork) |
| { |
| /* Collect any zombie child processes. */ |
| while (apr_proc_wait_all_procs(&proc, NULL, NULL, APR_NOWAIT, |
| connection_pool) == APR_CHILD_DONE) |
| ; |
| } |
| if (APR_STATUS_IS_EINTR(status)) |
| { |
| svn_pool_destroy(connection_pool); |
| continue; |
| } |
| if (status) |
| { |
| err = svn_error_wrap_apr |
| (status, _("Can't accept client connection")); |
| return svn_cmdline_handle_exit_error(err, pool, "svnserve: "); |
| } |
| |
| /* Enable TCP keep-alives on the socket so we time out when |
| * the connection breaks due to network-layer problems. |
| * If the peer has dropped the connection due to a network partition |
| * or a crash, or if the peer no longer considers the connection |
| * valid because we are behind a NAT and our public IP has changed, |
| * it will respond to the keep-alive probe with a RST instead of an |
| * acknowledgment segment, which will cause svn to abort the session |
| * even while it is currently blocked waiting for data from the peer. */ |
| status = apr_socket_opt_set(usock, APR_SO_KEEPALIVE, 1); |
| if (status) |
| { |
| /* It's not a fatal error if we cannot enable keep-alives. */ |
| } |
| |
| conn = svn_ra_svn_create_conn2(usock, NULL, NULL, |
| params.compression_level, |
| connection_pool); |
| |
| if (run_mode == run_mode_listen_once) |
| { |
| err = serve(conn, ¶ms, connection_pool); |
| |
| if (err) |
| svn_handle_error2(err, stdout, FALSE, "svnserve: "); |
| svn_error_clear(err); |
| |
| apr_socket_close(usock); |
| apr_socket_close(sock); |
| exit(0); |
| } |
| |
| switch (handling_mode) |
| { |
| case connection_mode_fork: |
| #if APR_HAS_FORK |
| status = apr_proc_fork(&proc, connection_pool); |
| if (status == APR_INCHILD) |
| { |
| apr_socket_close(sock); |
| err = serve(conn, ¶ms, connection_pool); |
| log_error(err, params.log_file, |
| svn_ra_svn_conn_remote_host(conn), |
| NULL, NULL, /* user, repos */ |
| connection_pool); |
| svn_error_clear(err); |
| apr_socket_close(usock); |
| exit(0); |
| } |
| else if (status == APR_INPARENT) |
| { |
| apr_socket_close(usock); |
| } |
| else |
| { |
| err = svn_error_wrap_apr(status, "apr_proc_fork"); |
| log_error(err, params.log_file, |
| svn_ra_svn_conn_remote_host(conn), |
| NULL, NULL, /* user, repos */ |
| connection_pool); |
| svn_error_clear(err); |
| apr_socket_close(usock); |
| } |
| svn_pool_destroy(connection_pool); |
| #endif |
| break; |
| |
| case connection_mode_thread: |
| /* Create a detached thread for each connection. That's not a |
| particularly sophisticated strategy for a threaded server, it's |
| little different from forking one process per connection. */ |
| #if APR_HAS_THREADS |
| status = apr_threadattr_create(&tattr, connection_pool); |
| if (status) |
| { |
| err = svn_error_wrap_apr(status, _("Can't create threadattr")); |
| svn_handle_error2(err, stderr, FALSE, "svnserve: "); |
| svn_error_clear(err); |
| exit(1); |
| } |
| status = apr_threadattr_detach_set(tattr, 1); |
| if (status) |
| { |
| err = svn_error_wrap_apr(status, _("Can't set detached state")); |
| svn_handle_error2(err, stderr, FALSE, "svnserve: "); |
| svn_error_clear(err); |
| exit(1); |
| } |
| thread_data = apr_palloc(connection_pool, sizeof(*thread_data)); |
| thread_data->conn = conn; |
| thread_data->params = ¶ms; |
| thread_data->pool = connection_pool; |
| status = apr_thread_create(&tid, tattr, serve_thread, thread_data, |
| connection_pool); |
| if (status) |
| { |
| err = svn_error_wrap_apr(status, _("Can't create thread")); |
| svn_handle_error2(err, stderr, FALSE, "svnserve: "); |
| svn_error_clear(err); |
| exit(1); |
| } |
| #endif |
| break; |
| |
| case connection_mode_single: |
| /* Serve one connection at a time. */ |
| svn_error_clear(serve(conn, ¶ms, connection_pool)); |
| svn_pool_destroy(connection_pool); |
| } |
| } |
| |
| /* NOTREACHED */ |
| } |