blob: fa41f95d5b9c031b73a8a551191854f5a6fb020d [file] [log] [blame]
/*
* 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;
}