/* * 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 #include #include #include #include #include #include #include #include #include #include #include #include #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 void _exit_handler(int sig __attribute__((unused))) { _shutdown_requested = 1; } #ifdef linux # define OOM_ADJ_FILE "/proc/self/oom_adj" # include /* From linux/oom.h */ # define OOM_DISABLE (-17) # define OOM_ADJUST_MIN (-16) /* * Protection against OOM killer if kernel supports it */ static int _set_oom_adj(int val) { FILE *fp; struct stat st; if (stat(OOM_ADJ_FILE, &st) == -1) { if (errno == ENOENT) perror(OOM_ADJ_FILE " not found"); else perror(OOM_ADJ_FILE ": stat failed"); return 1; } if (!(fp = fopen(OOM_ADJ_FILE, "w"))) { perror(OOM_ADJ_FILE ": fopen failed"); return 0; } fprintf(fp, "%i", val); if (fclose(fp)) perror(OOM_ADJ_FILE ": fclose failed"); return 1; } #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--) 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; dm_asprintf(buf, format, *buf, string); dm_free(old); return 0; } static int buffer_line(const char *line, void *baton) { response *r = baton; if (r->buffer) buffer_rewrite(&r->buffer, "%s\n%s", line); else dm_asprintf(&r->buffer, "%s\n", line); return 0; } 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 = b->s.handler(b->s, b->client, req); if (!res.buffer) { dm_config_write_node(res.cft->root, buffer_line, &res); buffer_rewrite(&res.buffer, "%s\n\n", NULL); 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"); 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 if (s.avoid_oom && !_set_oom_adj(OOM_DISABLE) && !_set_oom_adj(OOM_ADJUST_MIN)) syslog(LOG_ERR, "Failed to set oom_adj to protect against OOM killer"); #endif if (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); }