| /* |
| * Copyright (C) 2011-2012 Red Hat, Inc. |
| * |
| * This copyrighted material is made available to anyone wishing to use, |
| * modify, copy, or redistribute it subject to the terms and conditions |
| * of the GNU Lesser General Public License v.2.1. |
| * |
| * You should have received a copy of the GNU Lesser General Public License |
| * along with this program; if not, write to the Free Software Foundation, |
| * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
| */ |
| |
| #define _REENTRANT |
| |
| #include "tool.h" |
| |
| #include "daemon-io.h" |
| #include "daemon-server.h" |
| #include "daemon-log.h" |
| |
| #include <dlfcn.h> |
| #include <errno.h> |
| #include <pthread.h> |
| #include <sys/file.h> |
| #include <sys/stat.h> |
| #include <sys/wait.h> |
| #include <sys/time.h> |
| #include <sys/resource.h> |
| #include <netinet/in.h> |
| #include <sys/un.h> |
| #include <unistd.h> |
| #include <signal.h> |
| |
| #include <syslog.h> /* FIXME. For the global closelog(). */ |
| |
| #if 0 |
| /* Create a device monitoring thread. */ |
| static int _pthread_create(pthread_t *t, void *(*fun)(void *), void *arg, int stacksize) |
| { |
| pthread_attr_t attr; |
| pthread_attr_init(&attr); |
| /* |
| * We use a smaller stack since it gets preallocated in its entirety |
| */ |
| pthread_attr_setstacksize(&attr, stacksize); |
| return pthread_create(t, &attr, fun, arg); |
| } |
| #endif |
| |
| static volatile sig_atomic_t _shutdown_requested = 0; |
| static int _systemd_activation = 0; |
| |
| static void _exit_handler(int sig __attribute__((unused))) |
| { |
| _shutdown_requested = 1; |
| } |
| |
| #define EXIT_ALREADYRUNNING 13 |
| |
| #ifdef __linux__ |
| |
| #include <stddef.h> |
| |
| /* |
| * Kernel version 2.6.36 and higher has |
| * new OOM killer adjustment interface. |
| */ |
| # define OOM_ADJ_FILE_OLD "/proc/self/oom_adj" |
| # define OOM_ADJ_FILE "/proc/self/oom_score_adj" |
| |
| /* From linux/oom.h */ |
| /* Old interface */ |
| # define OOM_DISABLE (-17) |
| # define OOM_ADJUST_MIN (-16) |
| /* New interface */ |
| # define OOM_SCORE_ADJ_MIN (-1000) |
| |
| /* Systemd on-demand activation support */ |
| # define SD_ACTIVATION_ENV_VAR_NAME "SD_ACTIVATION" |
| # define SD_LISTEN_PID_ENV_VAR_NAME "LISTEN_PID" |
| # define SD_LISTEN_FDS_ENV_VAR_NAME "LISTEN_FDS" |
| # define SD_LISTEN_FDS_START 3 |
| # define SD_FD_SOCKET_SERVER SD_LISTEN_FDS_START |
| |
| static int _is_idle(daemon_state s) |
| { |
| return s.idle && s.idle->is_idle && !s.threads->next; |
| } |
| |
| static struct timeval *_get_timeout(daemon_state s) |
| { |
| return s.idle ? s.idle->ptimeout : NULL; |
| } |
| |
| static void _reset_timeout(daemon_state s) |
| { |
| if (s.idle) { |
| s.idle->ptimeout->tv_sec = 1; |
| s.idle->ptimeout->tv_usec = 0; |
| } |
| } |
| |
| static unsigned _get_max_timeouts(daemon_state s) |
| { |
| return s.idle ? s.idle->max_timeouts : 0; |
| } |
| |
| static int _set_oom_adj(const char *oom_adj_path, int val) |
| { |
| FILE *fp; |
| |
| if (!(fp = fopen(oom_adj_path, "w"))) { |
| perror("oom_adj: fopen failed"); |
| return 0; |
| } |
| |
| fprintf(fp, "%i", val); |
| |
| if (dm_fclose(fp)) |
| perror("oom_adj: fclose failed"); |
| |
| return 1; |
| } |
| |
| /* |
| * Protection against OOM killer if kernel supports it |
| */ |
| static int _protect_against_oom_killer(void) |
| { |
| struct stat st; |
| |
| if (stat(OOM_ADJ_FILE, &st) == -1) { |
| if (errno != ENOENT) |
| perror(OOM_ADJ_FILE ": stat failed"); |
| |
| /* Try old oom_adj interface as a fallback */ |
| if (stat(OOM_ADJ_FILE_OLD, &st) == -1) { |
| if (errno == ENOENT) |
| perror(OOM_ADJ_FILE_OLD " not found"); |
| else |
| perror(OOM_ADJ_FILE_OLD ": stat failed"); |
| return 1; |
| } |
| |
| return _set_oom_adj(OOM_ADJ_FILE_OLD, OOM_DISABLE) || |
| _set_oom_adj(OOM_ADJ_FILE_OLD, OOM_ADJUST_MIN); |
| } |
| |
| return _set_oom_adj(OOM_ADJ_FILE, OOM_SCORE_ADJ_MIN); |
| } |
| |
| union sockaddr_union { |
| struct sockaddr sa; |
| struct sockaddr_un un; |
| }; |
| |
| static int _handle_preloaded_socket(int fd, const char *path) |
| { |
| struct stat st_fd; |
| union sockaddr_union sockaddr = { .sa.sa_family = 0 }; |
| int type = 0; |
| socklen_t len = sizeof(type); |
| size_t path_len = strlen(path); |
| |
| if (fd < 0) |
| return 0; |
| |
| if (fstat(fd, &st_fd) < 0 || !S_ISSOCK(st_fd.st_mode)) |
| return 0; |
| |
| if (getsockopt(fd, SOL_SOCKET, SO_TYPE, &type, &len) < 0 || |
| len != sizeof(type) || type != SOCK_STREAM) |
| return 0; |
| |
| len = sizeof(sockaddr); |
| if (getsockname(fd, &sockaddr.sa, &len) < 0 || |
| len < sizeof(sa_family_t) || |
| sockaddr.sa.sa_family != PF_UNIX) |
| return 0; |
| |
| if (!(len >= offsetof(struct sockaddr_un, sun_path) + path_len + 1 && |
| memcmp(path, sockaddr.un.sun_path, path_len) == 0)) |
| return 0; |
| |
| return 1; |
| } |
| |
| static int _systemd_handover(struct daemon_state *ds) |
| { |
| const char *e; |
| char *p; |
| unsigned long env_pid, env_listen_fds; |
| int r = 0; |
| |
| /* SD_ACTIVATION must be set! */ |
| if (!(e = getenv(SD_ACTIVATION_ENV_VAR_NAME)) || strcmp(e, "1")) |
| goto out; |
| |
| /* LISTEN_PID must be equal to our PID! */ |
| if (!(e = getenv(SD_LISTEN_PID_ENV_VAR_NAME))) |
| goto out; |
| |
| errno = 0; |
| env_pid = strtoul(e, &p, 10); |
| if (errno || !p || *p || env_pid <= 0 || |
| getpid() != (pid_t) env_pid) |
| goto out; |
| |
| /* LISTEN_FDS must be 1 and the fd must be a socket! */ |
| if (!(e = getenv(SD_LISTEN_FDS_ENV_VAR_NAME))) |
| goto out; |
| |
| errno = 0; |
| env_listen_fds = strtoul(e, &p, 10); |
| if (errno || !p || *p || env_listen_fds != 1) |
| goto out; |
| |
| /* Check and handle the socket passed in */ |
| if ((r = _handle_preloaded_socket(SD_FD_SOCKET_SERVER, ds->socket_path))) |
| ds->socket_fd = SD_FD_SOCKET_SERVER; |
| |
| out: |
| unsetenv(SD_ACTIVATION_ENV_VAR_NAME); |
| unsetenv(SD_LISTEN_PID_ENV_VAR_NAME); |
| unsetenv(SD_LISTEN_FDS_ENV_VAR_NAME); |
| return r; |
| } |
| |
| #endif |
| |
| static int _open_socket(daemon_state s) |
| { |
| int fd; |
| int file_created = 0; |
| struct sockaddr_un sockaddr = { .sun_family = AF_UNIX }; |
| struct stat buf; |
| mode_t old_mask; |
| |
| (void) dm_prepare_selinux_context(s.socket_path, S_IFSOCK); |
| old_mask = umask(0077); |
| |
| /* Open local socket */ |
| fd = socket(PF_UNIX, SOCK_STREAM, 0); |
| if (fd < 0) { |
| perror("Can't create local socket."); |
| goto error; |
| } |
| |
| /* Set non-blocking */ |
| if (fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) | O_NONBLOCK)) |
| fprintf(stderr, "setting O_NONBLOCK on socket fd %d failed: %s\n", fd, strerror(errno)); |
| |
| fprintf(stderr, "[D] creating %s\n", s.socket_path); |
| if (!dm_strncpy(sockaddr.sun_path, s.socket_path, sizeof(sockaddr.sun_path))) { |
| fprintf(stderr, "%s: daemon socket path too long.\n", s.socket_path); |
| goto error; |
| } |
| |
| if (bind(fd, (struct sockaddr *) &sockaddr, sizeof(sockaddr))) { |
| if (errno != EADDRINUSE) { |
| perror("can't bind local socket"); |
| goto error; |
| } |
| |
| /* Socket already exists. If it's stale, remove it. */ |
| if (lstat(sockaddr.sun_path, &buf)) { |
| perror("stat failed"); |
| goto error; |
| } |
| |
| if (!S_ISSOCK(buf.st_mode)) { |
| fprintf(stderr, "%s: not a socket\n", sockaddr.sun_path); |
| goto error; |
| } |
| |
| if (buf.st_uid || (buf.st_mode & (S_IRWXG | S_IRWXO))) { |
| fprintf(stderr, "%s: unrecognised permissions\n", sockaddr.sun_path); |
| goto error; |
| } |
| |
| if (!connect(fd, (struct sockaddr *) &sockaddr, sizeof(sockaddr))) { |
| fprintf(stderr, "Socket %s already in use\n", sockaddr.sun_path); |
| goto error; |
| } |
| |
| fprintf(stderr, "removing stale socket %s\n", sockaddr.sun_path); |
| |
| if (unlink(sockaddr.sun_path) && (errno != ENOENT)) { |
| perror("unlink failed"); |
| goto error; |
| } |
| |
| if (bind(fd, (struct sockaddr *) &sockaddr, sizeof(sockaddr))) { |
| perror("local socket bind failed after unlink"); |
| goto error; |
| } |
| } |
| |
| file_created = 1; |
| |
| if (listen(fd, 1) != 0) { |
| perror("listen local"); |
| goto error; |
| } |
| |
| out: |
| umask(old_mask); |
| (void) dm_prepare_selinux_context(NULL, 0); |
| return fd; |
| |
| error: |
| if (fd >= 0) { |
| if (close(fd)) |
| perror("close failed"); |
| if (file_created && unlink(s.socket_path)) |
| perror("unlink failed"); |
| fd = -1; |
| } |
| goto out; |
| } |
| |
| static void _remove_lockfile(const char *file) |
| { |
| if (unlink(file)) |
| perror("unlink failed"); |
| } |
| |
| static void _daemonise(daemon_state s) |
| { |
| int child_status; |
| int fd; |
| pid_t pid; |
| struct rlimit rlim; |
| struct timeval tval; |
| sigset_t my_sigset; |
| |
| sigemptyset(&my_sigset); |
| if (sigprocmask(SIG_SETMASK, &my_sigset, NULL) < 0) { |
| fprintf(stderr, "Unable to restore signals.\n"); |
| exit(EXIT_FAILURE); |
| } |
| signal(SIGTERM, &_exit_handler); |
| |
| switch (pid = fork()) { |
| case -1: |
| perror("fork failed:"); |
| exit(EXIT_FAILURE); |
| |
| case 0: /* Child */ |
| break; |
| |
| default: |
| /* Wait for response from child */ |
| while (!waitpid(pid, &child_status, WNOHANG) && !_shutdown_requested) { |
| tval.tv_sec = 0; |
| tval.tv_usec = 250000; /* .25 sec */ |
| select(0, NULL, NULL, NULL, &tval); |
| } |
| |
| if (_shutdown_requested) /* Child has signaled it is ok - we can exit now */ |
| exit(0); |
| |
| switch (WEXITSTATUS(child_status)) { |
| case EXIT_ALREADYRUNNING: |
| fprintf(stderr, "Failed to acquire lock on %s. Already running?\n", s.pidfile); |
| break; |
| default: |
| /* Problem with child. Determine what it is by exit code */ |
| fprintf(stderr, "Child exited with code %d\n", WEXITSTATUS(child_status)); |
| } |
| exit(WEXITSTATUS(child_status)); |
| } |
| |
| if (chdir("/")) |
| exit(1); |
| |
| if (getrlimit(RLIMIT_NOFILE, &rlim) < 0) |
| fd = 256; /* just have to guess */ |
| else |
| fd = rlim.rlim_cur; |
| |
| for (--fd; fd >= 0; fd--) { |
| #ifdef __linux__ |
| /* Do not close fds preloaded by systemd! */ |
| if (_systemd_activation && fd == SD_FD_SOCKET_SERVER) |
| continue; |
| #endif |
| (void) close(fd); |
| } |
| |
| if ((open("/dev/null", O_RDONLY) < 0) || |
| (open("/dev/null", O_WRONLY) < 0) || |
| (open("/dev/null", O_WRONLY) < 0)) |
| exit(1); |
| |
| setsid(); |
| } |
| |
| response daemon_reply_simple(const char *id, ...) |
| { |
| va_list ap; |
| response res = { .cft = NULL }; |
| |
| va_start(ap, id); |
| |
| buffer_init(&res.buffer); |
| if (!buffer_append_f(&res.buffer, "response = %s", id, NULL)) { |
| res.error = ENOMEM; |
| goto end; |
| } |
| if (!buffer_append_vf(&res.buffer, ap)) { |
| res.error = ENOMEM; |
| goto end; |
| } |
| |
| end: |
| va_end(ap); |
| return res; |
| } |
| |
| static response _builtin_handler(daemon_state s, client_handle h, request r) |
| { |
| const char *rq = daemon_request_str(r, "request", "NONE"); |
| response res = { .error = EPROTO }; |
| |
| if (!strcmp(rq, "hello")) { |
| return daemon_reply_simple("OK", "protocol = %s", s.protocol ?: "default", |
| "version = %" PRId64, (int64_t) s.protocol_version, NULL); |
| } |
| |
| buffer_init(&res.buffer); |
| return res; |
| } |
| |
| static void *_client_thread(void *state) |
| { |
| thread_state *ts = state; |
| request req; |
| response res; |
| |
| buffer_init(&req.buffer); |
| |
| while (1) { |
| if (!buffer_read(ts->client.socket_fd, &req.buffer)) |
| goto fail; |
| |
| req.cft = dm_config_from_string(req.buffer.mem); |
| |
| if (!req.cft) |
| fprintf(stderr, "error parsing request:\n %s\n", req.buffer.mem); |
| else |
| daemon_log_cft(ts->s.log, DAEMON_LOG_WIRE, "<- ", req.cft->root); |
| |
| res = _builtin_handler(ts->s, ts->client, req); |
| |
| if (res.error == EPROTO) /* Not a builtin, delegate to the custom handler. */ |
| res = ts->s.handler(ts->s, ts->client, req); |
| |
| if (!res.buffer.mem) { |
| if (!dm_config_write_node(res.cft->root, buffer_line, &res.buffer)) |
| goto fail; |
| if (!buffer_append(&res.buffer, "\n\n")) |
| goto fail; |
| dm_config_destroy(res.cft); |
| } |
| |
| if (req.cft) |
| dm_config_destroy(req.cft); |
| buffer_destroy(&req.buffer); |
| |
| daemon_log_multi(ts->s.log, DAEMON_LOG_WIRE, "-> ", res.buffer.mem); |
| buffer_write(ts->client.socket_fd, &res.buffer); |
| |
| buffer_destroy(&res.buffer); |
| } |
| fail: |
| /* TODO what should we really do here? */ |
| if (close(ts->client.socket_fd)) |
| perror("close"); |
| buffer_destroy(&req.buffer); |
| ts->active = 0; |
| return NULL; |
| } |
| |
| static int handle_connect(daemon_state s) |
| { |
| thread_state *ts; |
| struct sockaddr_un sockaddr; |
| client_handle client = { .thread_id = 0 }; |
| socklen_t sl = sizeof(sockaddr); |
| |
| client.socket_fd = accept(s.socket_fd, (struct sockaddr *) &sockaddr, &sl); |
| if (client.socket_fd < 0) { |
| ERROR(&s, "Failed to accept connection."); |
| return 0; |
| } |
| |
| if (fcntl(client.socket_fd, F_SETFD, FD_CLOEXEC)) |
| WARN(&s, "setting CLOEXEC on client socket fd %d failed", client.socket_fd); |
| |
| if (!(ts = dm_malloc(sizeof(thread_state)))) { |
| if (close(client.socket_fd)) |
| perror("close"); |
| ERROR(&s, "Failed to allocate thread state"); |
| return 0; |
| } |
| |
| ts->next = s.threads->next; |
| s.threads->next = ts; |
| |
| ts->active = 1; |
| ts->s = s; |
| ts->client = client; |
| |
| if (pthread_create(&ts->client.thread_id, NULL, _client_thread, ts)) { |
| ERROR(&s, "Failed to create client thread."); |
| return 0; |
| } |
| |
| return 1; |
| } |
| |
| static void _reap(daemon_state s, int waiting) |
| { |
| thread_state *last = s.threads, *ts = last->next; |
| void *rv; |
| |
| while (ts) { |
| if (waiting || !ts->active) { |
| if ((errno = pthread_join(ts->client.thread_id, &rv))) |
| ERROR(&s, "pthread_join failed: %s", strerror(errno)); |
| last->next = ts->next; |
| dm_free(ts); |
| } else |
| last = ts; |
| ts = last->next; |
| } |
| } |
| |
| void daemon_start(daemon_state s) |
| { |
| int failed = 0; |
| log_state _log = { { 0 } }; |
| thread_state _threads = { .next = NULL }; |
| unsigned timeout_count = 0; |
| fd_set in; |
| |
| /* |
| * Switch to C locale to avoid reading large locale-archive file used by |
| * some glibc (on some distributions it takes over 100MB). Some daemons |
| * need to use mlockall(). |
| */ |
| if (setenv("LC_ALL", "C", 1)) |
| perror("Cannot set LC_ALL to C"); |
| |
| #ifdef __linux__ |
| _systemd_activation = _systemd_handover(&s); |
| #endif |
| |
| if (!s.foreground) |
| _daemonise(s); |
| |
| s.log = &_log; |
| s.log->name = s.name; |
| s.threads = &_threads; |
| |
| /* Log important things to syslog by default. */ |
| daemon_log_enable(s.log, DAEMON_LOG_OUTLET_SYSLOG, DAEMON_LOG_FATAL, 1); |
| daemon_log_enable(s.log, DAEMON_LOG_OUTLET_SYSLOG, DAEMON_LOG_ERROR, 1); |
| |
| if (s.pidfile) { |
| (void) dm_prepare_selinux_context(s.pidfile, S_IFREG); |
| |
| /* |
| * NB. Take care to not keep stale locks around. Best not exit(...) |
| * after this point. |
| */ |
| if (dm_create_lockfile(s.pidfile) == 0) { |
| ERROR(&s, "Failed to acquire lock on %s. Already running?\n", s.pidfile); |
| exit(EXIT_ALREADYRUNNING); |
| } |
| |
| (void) dm_prepare_selinux_context(NULL, 0); |
| } |
| |
| /* Set normal exit signals to request shutdown instead of dying. */ |
| signal(SIGINT, &_exit_handler); |
| signal(SIGHUP, &_exit_handler); |
| signal(SIGQUIT, &_exit_handler); |
| signal(SIGTERM, &_exit_handler); |
| signal(SIGALRM, &_exit_handler); |
| signal(SIGPIPE, SIG_IGN); |
| |
| #ifdef __linux__ |
| /* Systemd has adjusted oom killer for us already */ |
| if (s.avoid_oom && !_systemd_activation && !_protect_against_oom_killer()) |
| ERROR(&s, "Failed to protect against OOM killer"); |
| #endif |
| |
| if (!_systemd_activation && s.socket_path) { |
| s.socket_fd = _open_socket(s); |
| if (s.socket_fd < 0) |
| failed = 1; |
| } |
| |
| /* Set Close-on-exec */ |
| if (!failed && fcntl(s.socket_fd, F_SETFD, 1)) |
| ERROR(&s, "setting CLOEXEC on socket fd %d failed: %s\n", s.socket_fd, strerror(errno)); |
| |
| /* Signal parent, letting them know we are ready to go. */ |
| if (!s.foreground) |
| kill(getppid(), SIGTERM); |
| |
| /* |
| * Use daemon_main for daemon-specific init and polling, or |
| * use daemon_init for daemon-specific init and generic lib polling. |
| */ |
| |
| if (s.daemon_main) { |
| if (!s.daemon_main(&s)) |
| failed = 1; |
| goto out; |
| } |
| |
| if (s.daemon_init) |
| if (!s.daemon_init(&s)) |
| failed = 1; |
| |
| while (!failed) { |
| _reset_timeout(s); |
| FD_ZERO(&in); |
| FD_SET(s.socket_fd, &in); |
| if (select(FD_SETSIZE, &in, NULL, NULL, _get_timeout(s)) < 0 && errno != EINTR) |
| perror("select error"); |
| if (FD_ISSET(s.socket_fd, &in)) { |
| timeout_count = 0; |
| handle_connect(s); |
| } |
| |
| _reap(s, 0); |
| |
| if (_shutdown_requested && !s.threads->next) |
| break; |
| |
| /* s.idle == NULL equals no shutdown on timeout */ |
| if (_is_idle(s)) { |
| DEBUGLOG(&s, "timeout occured"); |
| if (++timeout_count >= _get_max_timeouts(s)) { |
| INFO(&s, "Inactive for %d seconds. Exiting.", timeout_count); |
| break; |
| } |
| } |
| } |
| |
| INFO(&s, "%s waiting for client threads to finish", s.name); |
| _reap(s, 1); |
| out: |
| /* If activated by systemd, do not unlink the socket - systemd takes care of that! */ |
| if (!_systemd_activation && s.socket_fd >= 0) |
| if (unlink(s.socket_path)) |
| perror("unlink error"); |
| |
| if (s.socket_fd >= 0) |
| if (close(s.socket_fd)) |
| perror("socket close"); |
| |
| if (s.daemon_fini) |
| if (!s.daemon_fini(&s)) |
| failed = 1; |
| |
| INFO(&s, "%s shutting down", s.name); |
| |
| closelog(); /* FIXME */ |
| if (s.pidfile) |
| _remove_lockfile(s.pidfile); |
| if (failed) |
| exit(1); |
| } |