| /* |
| * 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. |
| */ |
| |
| #include <stdio.h> |
| #include <qpid/dispatch.h> |
| #include <signal.h> |
| #include <sys/types.h> |
| #include <sys/stat.h> |
| #include <pwd.h> |
| #include <fcntl.h> |
| #include <unistd.h> |
| #include <string.h> |
| #include <stdlib.h> |
| #include <getopt.h> |
| #include <errno.h> |
| #include <limits.h> |
| #include "config.h" |
| |
| static int exit_with_sigint = 0; |
| static qd_dispatch_t *dispatch = 0; |
| static qd_log_source_t *log_source = 0; |
| static const char* argv0 = 0; |
| |
| |
| /** |
| * This is the OS signal handler, invoked on an undetermined thread at a completely |
| * arbitrary point of time. |
| */ |
| static void signal_handler(int signum) |
| { |
| /* Ignore future signals, dispatch may already be freed */ |
| signal(SIGHUP, SIG_IGN); |
| signal(SIGQUIT, SIG_IGN); |
| signal(SIGTERM, SIG_IGN); |
| signal(SIGINT, SIG_IGN); |
| switch (signum) { |
| case SIGINT: |
| exit_with_sigint = 1; |
| // fallthrough |
| case SIGQUIT: |
| case SIGTERM: |
| qd_server_stop(dispatch); /* qpid_server_stop is signal-safe */ |
| break; |
| default: |
| break; |
| } |
| } |
| |
| static void check(int fd) { |
| if (qd_error_code()) { |
| qd_log(log_source, QD_LOG_CRITICAL, "Router start-up failed: %s", qd_error_message()); |
| #ifdef __sun |
| FILE *file = fdopen(fd, "a+"); |
| fprintf(file, "%s: %s\n", argv0, qd_error_message()); |
| #else |
| dprintf(fd, "%s: %s\n", argv0, qd_error_message()); |
| #endif |
| close(fd); |
| exit(1); |
| } |
| } |
| |
| #define fail(fd, ...) \ |
| do { \ |
| if (errno) \ |
| qd_error_errno(errno, __VA_ARGS__); \ |
| else \ |
| qd_error(QD_ERROR_RUNTIME, __VA_ARGS__); \ |
| check(fd); \ |
| } while(false) |
| |
| static void main_process(const char *config_path, const char *python_pkgdir, bool test_hooks, int fd) |
| { |
| dispatch = qd_dispatch(python_pkgdir, test_hooks); |
| check(fd); |
| log_source = qd_log_source("MAIN"); /* Logging is initialized by qd_dispatch. */ |
| qd_dispatch_validate_config(config_path); |
| check(fd); |
| qd_dispatch_load_config(dispatch, config_path); |
| check(fd); |
| |
| signal(SIGHUP, signal_handler); |
| signal(SIGQUIT, signal_handler); |
| signal(SIGTERM, signal_handler); |
| signal(SIGINT, signal_handler); |
| |
| if (fd > 2) { /* Daemon mode, fd is one end of a pipe not stdout or stderr */ |
| #ifdef __sun |
| const char * okResult = "ok"; |
| write(fd, okResult, (strlen(okResult)+1)); |
| #else |
| dprintf(fd, "ok"); // Success signal |
| #endif |
| close(fd); |
| } |
| |
| qd_server_run(dispatch); |
| |
| qd_dispatch_t *d = dispatch; |
| dispatch = NULL; |
| qd_dispatch_free(d); |
| |
| fflush(stdout); |
| if (exit_with_sigint) { |
| signal(SIGINT, SIG_DFL); |
| kill(getpid(), SIGINT); |
| } |
| } |
| |
| |
| static void daemon_process(const char *config_path, const char *python_pkgdir, bool test_hooks, |
| const char *pidfile, const char *user) |
| { |
| int pipefd[2]; |
| |
| // |
| // This daemonization process is based on that outlined in the |
| // "daemon" manpage from Linux. |
| // |
| |
| // |
| // Create an unnamed pipe for communication from the daemon to the main process |
| // |
| if (pipe(pipefd) < 0) { |
| perror("Error creating inter-process pipe"); |
| exit(1); |
| } |
| |
| // |
| // First fork |
| // |
| pid_t pid = fork(); |
| if (pid == 0) { |
| // |
| // Child Process |
| // |
| |
| // |
| // Detach any terminals and create an independent session |
| // |
| if (setsid() < 0) fail(pipefd[1], "Cannot start a new session"); |
| // |
| // Second fork |
| // |
| pid_t pid2 = fork(); |
| if (pid2 == 0) { |
| close(pipefd[0]); // Close read end. |
| |
| // |
| // Assign stdin, stdout, and stderr to /dev/null |
| // |
| close(2); |
| close(1); |
| close(0); |
| int fd = open("/dev/null", O_RDWR); |
| if (fd != 0) fail(pipefd[1], "Can't redirect stdin to /dev/null"); |
| if (dup(fd) < 0) fail(pipefd[1], "Can't redirect stdout to /dev/null"); |
| if (dup(fd) < 0) fail(pipefd[1], "Can't redirect stderr /dev/null"); |
| |
| // |
| // Set the umask to 0 |
| // |
| umask(0); |
| |
| |
| // |
| // If config path is not a fully qualified path, then construct the |
| // fully qualified path to the config file. This needs to be done |
| // since the daemon will set "/" to its working directory. |
| // |
| char *config_path_full = NULL; |
| if (strncmp("/", config_path, 1)) { |
| size_t path_size = PATH_MAX; |
| char *cur_path = (char *) calloc(path_size, sizeof(char)); |
| errno = 0; |
| |
| while (getcwd(cur_path, path_size) == NULL) { |
| free(cur_path); |
| if (errno != ERANGE) { |
| // Hard failure - can't recover from this |
| perror("Unable to determine current directory"); |
| exit(1); |
| } |
| // errno == ERANGE: the current path does not fit, allocate |
| // more memory |
| path_size += 256; |
| cur_path = (char *) calloc(path_size, sizeof(char)); |
| errno = 0; |
| } |
| |
| // Populating fully qualified config file name |
| const char *path_sep = !strcmp("/", cur_path) ? "" : "/"; |
| size_t cpf_len = strlen(cur_path) + strlen(path_sep) + strlen(config_path) + 1; |
| config_path_full = calloc(cpf_len, sizeof(char)); |
| snprintf(config_path_full, cpf_len, "%s%s%s", |
| cur_path, path_sep, config_path); |
| |
| // Releasing temporary path variable |
| memset(cur_path, 0, path_size * sizeof(char)); |
| free(cur_path); |
| } |
| |
| // |
| // Set the current directory to "/" to avoid blocking |
| // mount points |
| // |
| if (chdir("/") < 0) fail(pipefd[1], "Can't chdir /"); |
| |
| // |
| // If a pidfile was provided, write the daemon pid there. |
| // |
| if (pidfile) { |
| FILE *pf = fopen(pidfile, "w"); |
| if (pf == 0) fail(pipefd[1], "Can't write pidfile %s", pidfile); |
| fprintf(pf, "%d\n", getpid()); |
| fclose(pf); |
| } |
| |
| // |
| // If a user was provided, drop privileges to the user's |
| // privilege level. |
| // |
| if (user) { |
| struct passwd *pwd = getpwnam(user); |
| if (pwd == 0) fail(pipefd[1], "Can't look up user %s", user); |
| if (setuid(pwd->pw_uid) < 0) fail(pipefd[1], "Can't set user ID for user %s, errno=%d", user, errno); |
| //if (setgid(pwd->pw_gid) < 0) fail(pipefd[1], "Can't set group ID for user %s, errno=%d", user, errno); |
| } |
| |
| main_process((config_path_full ? config_path_full : config_path), python_pkgdir, test_hooks, pipefd[1]); |
| |
| free(config_path_full); |
| } else |
| // |
| // Exit first child |
| // |
| exit(0); |
| } else { |
| // |
| // Parent Process |
| // Wait for a success signal ('0') from the daemon process. |
| // If we get success, exit with 0. Otherwise, exit with 1. |
| // |
| close(pipefd[1]); // Close write end. |
| char result[256]; |
| memset(result, 0, sizeof(result)); |
| if (read(pipefd[0], &result, sizeof(result)-1) < 0) { |
| perror("Error reading inter-process pipe"); |
| exit(1); |
| } |
| |
| if (strcmp(result, "ok") == 0) |
| exit(0); |
| fprintf(stderr, "%s", result); |
| exit(1); |
| } |
| } |
| |
| #define DEFAULT_DISPATCH_PYTHON_DIR QPID_DISPATCH_HOME_INSTALLED "/python" |
| |
| void usage(char **argv) { |
| fprintf(stdout, "Usage: %s [OPTIONS]\n\n", argv[0]); |
| fprintf(stdout, " -c, --config=PATH (%s)\n", DEFAULT_CONFIG_PATH); |
| fprintf(stdout, " Load configuration from file at PATH\n"); |
| fprintf(stdout, " -I, --include=PATH (%s)\n", DEFAULT_DISPATCH_PYTHON_DIR); |
| fprintf(stdout, " Location of Dispatch's Python library\n"); |
| fprintf(stdout, " -d, --daemon Run process as a SysV-style daemon\n"); |
| fprintf(stdout, " -P, --pidfile If daemon, the file for the stored daemon pid\n"); |
| fprintf(stdout, " -U, --user If daemon, the username to run as\n"); |
| fprintf(stdout, " -T, --test-hooks Enable internal system testing features\n"); |
| fprintf(stdout, " -v, --version Print the version of Qpid Dispatch Router\n"); |
| fprintf(stdout, " -h, --help Print this help\n"); |
| } |
| |
| int main(int argc, char **argv) |
| { |
| argv0 = argv[0]; |
| const char *config_path = DEFAULT_CONFIG_PATH; |
| const char *python_pkgdir = DEFAULT_DISPATCH_PYTHON_DIR; |
| const char *pidfile = 0; |
| const char *user = 0; |
| bool daemon_mode = false; |
| bool test_hooks = false; |
| |
| static struct option long_options[] = { |
| {"config", required_argument, 0, 'c'}, |
| {"include", required_argument, 0, 'I'}, |
| {"daemon", no_argument, 0, 'd'}, |
| {"pidfile", required_argument, 0, 'P'}, |
| {"user", required_argument, 0, 'U'}, |
| {"help", no_argument, 0, 'h'}, |
| {"version", no_argument, 0, 'v'}, |
| {"test-hooks", no_argument, 0, 'T'}, |
| {0, 0, 0, 0} |
| }; |
| |
| while (1) { |
| int c = getopt_long(argc, argv, "c:I:dP:U:h:vT", long_options, 0); |
| if (c == -1) |
| break; |
| |
| switch (c) { |
| case 'c' : |
| config_path = optarg; |
| break; |
| |
| case 'I' : |
| python_pkgdir = optarg; |
| break; |
| |
| case 'd' : |
| daemon_mode = true; |
| break; |
| |
| case 'P' : |
| pidfile = optarg; |
| break; |
| |
| case 'U' : |
| user = optarg; |
| break; |
| |
| case 'h' : |
| usage(argv); |
| exit(0); |
| |
| case 'v' : |
| fprintf(stdout, "%s\n", QPID_DISPATCH_VERSION); |
| exit(0); |
| |
| case 'T' : |
| test_hooks = true; |
| break; |
| |
| case '?' : |
| usage(argv); |
| exit(1); |
| } |
| } |
| if (optind < argc) { |
| fprintf(stderr, "Unexpected arguments:"); |
| for (; optind < argc; ++optind) fprintf(stderr, " %s", argv[optind]); |
| fprintf(stderr, "\n\n"); |
| usage(argv); |
| exit(1); |
| } |
| |
| if (daemon_mode) |
| daemon_process(config_path, python_pkgdir, test_hooks, pidfile, user); |
| else |
| main_process(config_path, python_pkgdir, test_hooks, 2); |
| |
| return 0; |
| } |