diff options
author | Alasdair Kergon <agk@redhat.com> | 2012-02-28 18:30:39 +0000 |
---|---|---|
committer | Alasdair Kergon <agk@redhat.com> | 2012-02-28 18:30:39 +0000 |
commit | ec6559c93e0b433cd9b2200e1be4a9b80e27a965 (patch) | |
tree | 7d1c6999d78cb127765c0d5ef9317a183c4e66cf /libdaemon/server/daemon-server.c | |
parent | 6d41ef6ce5132133f165f95cc6b2f7491f6e5119 (diff) | |
download | lvm2-ec6559c93e0b433cd9b2200e1be4a9b80e27a965.tar.gz lvm2-ec6559c93e0b433cd9b2200e1be4a9b80e27a965.tar.xz lvm2-ec6559c93e0b433cd9b2200e1be4a9b80e27a965.zip |
move daemon files
Diffstat (limited to 'libdaemon/server/daemon-server.c')
-rw-r--r-- | libdaemon/server/daemon-server.c | 522 |
1 files changed, 522 insertions, 0 deletions
diff --git a/libdaemon/server/daemon-server.c b/libdaemon/server/daemon-server.c new file mode 100644 index 00000000..c2ba7f34 --- /dev/null +++ b/libdaemon/server/daemon-server.c @@ -0,0 +1,522 @@ +/* + * Copyright (C) 2011 Red Hat, Inc. All rights reserved. + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#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> +#include "daemon-server.h" +#include "daemon-shared.h" +#include "libdevmapper.h" + +#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; +} + +#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_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 + +# include <stdio.h> + +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; + 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; + + memset(&sockaddr, 0, sizeof(sockaddr)); + 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; + + /* 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) + ; + + /* 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_LISTEN_PID_ENV_VAR_NAME); + unsetenv(SD_LISTEN_FDS_ENV_VAR_NAME); + return r; +} + +#endif + +static int _open_socket(daemon_state s) +{ + int fd = -1; + struct sockaddr_un sockaddr; + 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 Close-on-exec & non-blocking */ + if (fcntl(fd, F_SETFD, 1)) + fprintf(stderr, "setting CLOEXEC on socket fd %d failed: %s\n", fd, strerror(errno)); + fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) | O_NONBLOCK); + + fprintf(stderr, "[D] creating %s\n", s.socket_path); + memset(&sockaddr, 0, sizeof(sockaddr)); + strcpy(sockaddr.sun_path, s.socket_path); + sockaddr.sun_family = AF_UNIX; + + if (bind(fd, (struct sockaddr *) &sockaddr, sizeof(sockaddr))) { + perror("can't bind local socket."); + goto error; + } + 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 (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(void) +{ + 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); + + /* 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 + 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); + + if (!(res.buffer = format_buffer("response", id, ap))) + res.error = ENOMEM; + + va_end(ap); + + return res; +} + +struct thread_baton { + daemon_state s; + client_handle client; +}; + +static int buffer_rewrite(char **buf, const char *format, const char *string) { + char *old = *buf; + int r = dm_asprintf(buf, format, *buf, string); + + dm_free(old); + + return (r < 0) ? 0 : 1; +} + +static int buffer_line(const char *line, void *baton) { + response *r = baton; + + if (r->buffer) { + if (!buffer_rewrite(&r->buffer, "%s\n%s", line)) + return 0; + } else if (dm_asprintf(&r->buffer, "%s\n", line) < 0) + return 0; + + return 1; +} + +static response builtin_handler(daemon_state s, client_handle h, request r) +{ + const char *rq = daemon_request_str(r, "request", "NONE"); + + if (!strcmp(rq, "hello")) { + return daemon_reply_simple("OK", "protocol = %s", s.protocol ?: "default", + "version = %d", s.protocol_version, NULL); + } + + response res = { .buffer = NULL, .error = EPROTO }; + return res; +} + +static void *client_thread(void *baton) +{ + struct thread_baton *b = baton; + request req; + response res; + + while (1) { + if (!read_buffer(b->client.socket_fd, &req.buffer)) + goto fail; + + req.cft = dm_config_from_string(req.buffer); + if (!req.cft) + fprintf(stderr, "error parsing request:\n %s\n", req.buffer); + + res = builtin_handler(b->s, b->client, req); + + if (res.error == EPROTO) /* Not a builtin, delegate to the custom handler. */ + res = b->s.handler(b->s, b->client, req); + + if (!res.buffer) { + dm_config_write_node(res.cft->root, buffer_line, &res); + if (!buffer_rewrite(&res.buffer, "%s\n\n", NULL)) + goto fail; + dm_config_destroy(res.cft); + } + + if (req.cft) + dm_config_destroy(req.cft); + dm_free(req.buffer); + + write_buffer(b->client.socket_fd, res.buffer, strlen(res.buffer)); + + free(res.buffer); + } +fail: + /* TODO what should we really do here? */ + close(b->client.socket_fd); + free(baton); + return NULL; +} + +static int handle_connect(daemon_state s) +{ + struct thread_baton *baton; + 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) + return 0; + + if (!(baton = malloc(sizeof(struct thread_baton)))) + return 0; + + baton->s = s; + baton->client = client; + + if (pthread_create(&baton->client.thread_id, NULL, client_thread, baton)) + return 0; + + pthread_detach(baton->client.thread_id); + + return 1; +} + +void daemon_start(daemon_state s) +{ + int failed = 0; + /* + * 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("LANG", "C", 1)) + perror("Cannot set LANG to C"); + +#ifdef linux + _systemd_activation = _systemd_handover(&s); +#endif + + if (!s.foreground) + _daemonise(); + + /* TODO logging interface should be somewhat more elaborate */ + openlog(s.name, LOG_PID, LOG_DAEMON); + + (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) + exit(1); + + (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()) + syslog(LOG_ERR, "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; + } + + /* Signal parent, letting them know we are ready to go. */ + if (!s.foreground) + kill(getppid(), SIGTERM); + + if (s.daemon_init) + s.daemon_init(&s); + + while (!_shutdown_requested && !failed) { + fd_set in; + FD_ZERO(&in); + FD_SET(s.socket_fd, &in); + if (select(FD_SETSIZE, &in, NULL, NULL, NULL) < 0 && errno != EINTR) + perror("select error"); + if (FD_ISSET(s.socket_fd, &in)) + if (!handle_connect(s)) + syslog(LOG_ERR, "Failed to handle a client connection."); + } + + if (s.socket_fd >= 0) + if (unlink(s.socket_path)) + perror("unlink error"); + + if (s.daemon_fini) + s.daemon_fini(&s); + + syslog(LOG_NOTICE, "%s shutting down", s.name); + closelog(); + remove_lockfile(s.pidfile); + if (failed) + exit(1); +} |