| /* |
| * 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 <getopt.h> |
| #include <errno.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; |
| |
| /** |
| * The thread_start_handler is invoked once for each server thread at thread startup. |
| */ |
| static void thread_start_handler(void* context, int thread_id) |
| { |
| } |
| |
| |
| /** |
| * This is the OS signal handler, invoked on an undetermined thread at a completely |
| * arbitrary point of time. It is not safe to do anything here but signal the dispatch |
| * server with the signal number. |
| */ |
| static void signal_handler(int signum) |
| { |
| qd_server_signal(dispatch, signum); |
| } |
| |
| |
| /** |
| * This signal handler is called cleanly by one of the server's worker threads in |
| * response to an earlier call to qd_server_signal. |
| */ |
| static void server_signal_handler(void* context, int signum) |
| { |
| qd_server_pause(dispatch); |
| |
| switch (signum) { |
| case SIGINT: |
| exit_with_sigint = 1; |
| // fallthrough |
| |
| case SIGQUIT: |
| case SIGTERM: |
| fflush(stdout); |
| qd_server_stop(dispatch); |
| break; |
| |
| case SIGHUP: |
| break; |
| |
| default: |
| break; |
| } |
| |
| qd_server_resume(dispatch); |
| } |
| |
| static void check(int fd) { |
| if (qd_error_code()) { |
| qd_log(log_source, QD_LOG_CRITICAL, "Router start-up failed: %s", qd_error_message()); |
| dprintf(fd, "%s: %s\n", argv0, qd_error_message()); |
| 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, int fd) |
| { |
| qd_error_clear(); |
| struct stat st; |
| if (stat(python_pkgdir, &st)) |
| fail(fd, "Cannot find python library path '%s'", python_pkgdir); |
| if (!S_ISDIR(st.st_mode)) { |
| qd_error(QD_ERROR_RUNTIME, "Python library path '%s' not a directory", python_pkgdir); |
| check(fd); |
| } |
| |
| dispatch = qd_dispatch(python_pkgdir); |
| check(fd); |
| log_source = qd_log_source("MAIN"); /* Logging is initialized by qd_dispatch. */ |
| qd_dispatch_load_config(dispatch, config_path); |
| check(fd); |
| |
| (void)server_signal_handler; (void)thread_start_handler;(void)signal_handler; |
| qd_server_set_signal_handler(dispatch, server_signal_handler, 0); |
| qd_server_set_start_handler(dispatch, thread_start_handler, 0); |
| |
| 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 */ |
| dprintf(fd, "ok"); // Success signal |
| close(fd); |
| } |
| |
| qd_server_run(dispatch); |
| |
| qd_dispatch_t *d = dispatch; |
| dispatch = NULL; |
| qd_dispatch_free(d); |
| |
| if (exit_with_sigint) { |
| signal(SIGINT, SIG_DFL); |
| kill(getpid(), SIGINT); |
| } |
| } |
| |
| |
| static void daemon_process(const char *config_path, const char *python_pkgdir, |
| 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 |
| // |
| if (umask(0) < 0) fail(pipefd[1], "Can't set umask"); |
| |
| // |
| // 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, python_pkgdir, pipefd[1]); |
| } 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(stderr, "Usage: %s [OPTIONS]\n\n", argv[0]); |
| fprintf(stderr, " -c, --config=PATH (%s)\n", DEFAULT_CONFIG_PATH); |
| fprintf(stderr, " Load configuration from file at PATH\n"); |
| fprintf(stderr, " -I, --include=PATH (%s)\n", DEFAULT_DISPATCH_PYTHON_DIR); |
| fprintf(stderr, " Location of Dispatch's Python library\n"); |
| fprintf(stderr, " -d, --daemon Run process as a SysV-style daemon\n"); |
| fprintf(stderr, " -P, --pidfile If daemon, the file for the stored daemon pid\n"); |
| fprintf(stderr, " -U, --user If daemon, the username to run as\n"); |
| fprintf(stderr, " -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; |
| |
| 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'}, |
| {0, 0, 0, 0} |
| }; |
| |
| while (1) { |
| int c = getopt_long(argc, argv, "c:I:dP:U:h", 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 '?' : |
| 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, pidfile, user); |
| else |
| main_process(config_path, python_pkgdir, 2); |
| |
| return 0; |
| } |