| /* |
| * This file is part of the ZFS Event Daemon (ZED). |
| * |
| * Developed at Lawrence Livermore National Laboratory (LLNL-CODE-403049). |
| * Copyright (C) 2013-2014 Lawrence Livermore National Security, LLC. |
| * Refer to the OpenZFS git commit log for authoritative copyright attribution. |
| * |
| * The contents of this file are subject to the terms of the |
| * Common Development and Distribution License Version 1.0 (CDDL-1.0). |
| * You can obtain a copy of the license from the top-level file |
| * "OPENSOLARIS.LICENSE" or at <http://opensource.org/licenses/CDDL-1.0>. |
| * You may not use this file except in compliance with the license. |
| */ |
| |
| #include <assert.h> |
| #include <ctype.h> |
| #include <errno.h> |
| #include <fcntl.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <stddef.h> |
| #include <sys/avl.h> |
| #include <sys/resource.h> |
| #include <sys/stat.h> |
| #include <sys/wait.h> |
| #include <time.h> |
| #include <unistd.h> |
| #include <pthread.h> |
| #include <signal.h> |
| |
| #include "zed_exec.h" |
| #include "zed_log.h" |
| #include "zed_strings.h" |
| |
| #define ZEVENT_FILENO 3 |
| |
| struct launched_process_node { |
| avl_node_t node; |
| pid_t pid; |
| uint64_t eid; |
| char *name; |
| }; |
| |
| static int |
| _launched_process_node_compare(const void *x1, const void *x2) |
| { |
| pid_t p1; |
| pid_t p2; |
| |
| assert(x1 != NULL); |
| assert(x2 != NULL); |
| |
| p1 = ((const struct launched_process_node *) x1)->pid; |
| p2 = ((const struct launched_process_node *) x2)->pid; |
| |
| if (p1 < p2) |
| return (-1); |
| else if (p1 == p2) |
| return (0); |
| else |
| return (1); |
| } |
| |
| static pthread_t _reap_children_tid = (pthread_t)-1; |
| static volatile boolean_t _reap_children_stop; |
| static avl_tree_t _launched_processes; |
| static pthread_mutex_t _launched_processes_lock = PTHREAD_MUTEX_INITIALIZER; |
| static int16_t _launched_processes_limit; |
| |
| /* |
| * Create an environment string array for passing to execve() using the |
| * NAME=VALUE strings in container [zsp]. |
| * Return a newly-allocated environment, or NULL on error. |
| */ |
| static char ** |
| _zed_exec_create_env(zed_strings_t *zsp) |
| { |
| int num_ptrs; |
| int buflen; |
| char *buf; |
| char **pp; |
| char *p; |
| const char *q; |
| int i; |
| int len; |
| |
| num_ptrs = zed_strings_count(zsp) + 1; |
| buflen = num_ptrs * sizeof (char *); |
| for (q = zed_strings_first(zsp); q; q = zed_strings_next(zsp)) |
| buflen += strlen(q) + 1; |
| |
| buf = calloc(1, buflen); |
| if (!buf) |
| return (NULL); |
| |
| pp = (char **)buf; |
| p = buf + (num_ptrs * sizeof (char *)); |
| i = 0; |
| for (q = zed_strings_first(zsp); q; q = zed_strings_next(zsp)) { |
| pp[i] = p; |
| len = strlen(q) + 1; |
| memcpy(p, q, len); |
| p += len; |
| i++; |
| } |
| pp[i] = NULL; |
| assert(buf + buflen == p); |
| return ((char **)buf); |
| } |
| |
| /* |
| * Fork a child process to handle event [eid]. The program [prog] |
| * in directory [dir] is executed with the environment [env]. |
| * |
| * The file descriptor [zfd] is the zevent_fd used to track the |
| * current cursor location within the zevent nvlist. |
| */ |
| static void |
| _zed_exec_fork_child(uint64_t eid, const char *dir, const char *prog, |
| char *env[], int zfd, boolean_t in_foreground) |
| { |
| char path[PATH_MAX]; |
| int n; |
| pid_t pid; |
| int fd; |
| struct launched_process_node *node; |
| sigset_t mask; |
| struct timespec launch_timeout = |
| { .tv_sec = 0, .tv_nsec = 200 * 1000 * 1000, }; |
| |
| assert(dir != NULL); |
| assert(prog != NULL); |
| assert(env != NULL); |
| assert(zfd >= 0); |
| |
| while (__atomic_load_n(&_launched_processes_limit, |
| __ATOMIC_SEQ_CST) <= 0) |
| (void) nanosleep(&launch_timeout, NULL); |
| |
| n = snprintf(path, sizeof (path), "%s/%s", dir, prog); |
| if ((n < 0) || (n >= sizeof (path))) { |
| zed_log_msg(LOG_WARNING, |
| "Failed to fork \"%s\" for eid=%llu: %s", |
| prog, eid, strerror(ENAMETOOLONG)); |
| return; |
| } |
| (void) pthread_mutex_lock(&_launched_processes_lock); |
| pid = fork(); |
| if (pid < 0) { |
| (void) pthread_mutex_unlock(&_launched_processes_lock); |
| zed_log_msg(LOG_WARNING, |
| "Failed to fork \"%s\" for eid=%llu: %s", |
| prog, eid, strerror(errno)); |
| return; |
| } else if (pid == 0) { |
| (void) sigemptyset(&mask); |
| (void) sigprocmask(SIG_SETMASK, &mask, NULL); |
| |
| (void) umask(022); |
| if (in_foreground && /* we're already devnulled if daemonised */ |
| (fd = open("/dev/null", O_RDWR | O_CLOEXEC)) != -1) { |
| (void) dup2(fd, STDIN_FILENO); |
| (void) dup2(fd, STDOUT_FILENO); |
| (void) dup2(fd, STDERR_FILENO); |
| } |
| (void) dup2(zfd, ZEVENT_FILENO); |
| execle(path, prog, NULL, env); |
| _exit(127); |
| } |
| |
| /* parent process */ |
| |
| node = calloc(1, sizeof (*node)); |
| if (node) { |
| node->pid = pid; |
| node->eid = eid; |
| node->name = strdup(prog); |
| |
| avl_add(&_launched_processes, node); |
| } |
| (void) pthread_mutex_unlock(&_launched_processes_lock); |
| |
| __atomic_sub_fetch(&_launched_processes_limit, 1, __ATOMIC_SEQ_CST); |
| zed_log_msg(LOG_INFO, "Invoking \"%s\" eid=%llu pid=%d", |
| prog, eid, pid); |
| } |
| |
| static void |
| _nop(int sig) |
| {} |
| |
| static void * |
| _reap_children(void *arg) |
| { |
| struct launched_process_node node, *pnode; |
| pid_t pid; |
| int status; |
| struct rusage usage; |
| struct sigaction sa = {}; |
| |
| (void) sigfillset(&sa.sa_mask); |
| (void) sigdelset(&sa.sa_mask, SIGCHLD); |
| (void) pthread_sigmask(SIG_SETMASK, &sa.sa_mask, NULL); |
| |
| (void) sigemptyset(&sa.sa_mask); |
| sa.sa_handler = _nop; |
| sa.sa_flags = SA_NOCLDSTOP; |
| (void) sigaction(SIGCHLD, &sa, NULL); |
| |
| for (_reap_children_stop = B_FALSE; !_reap_children_stop; ) { |
| (void) pthread_mutex_lock(&_launched_processes_lock); |
| pid = wait4(0, &status, WNOHANG, &usage); |
| |
| if (pid == 0 || pid == (pid_t)-1) { |
| (void) pthread_mutex_unlock(&_launched_processes_lock); |
| if (pid == 0 || errno == ECHILD) |
| pause(); |
| else if (errno != EINTR) |
| zed_log_msg(LOG_WARNING, |
| "Failed to wait for children: %s", |
| strerror(errno)); |
| } else { |
| memset(&node, 0, sizeof (node)); |
| node.pid = pid; |
| pnode = avl_find(&_launched_processes, &node, NULL); |
| if (pnode) { |
| memcpy(&node, pnode, sizeof (node)); |
| |
| avl_remove(&_launched_processes, pnode); |
| free(pnode); |
| } |
| (void) pthread_mutex_unlock(&_launched_processes_lock); |
| __atomic_add_fetch(&_launched_processes_limit, 1, |
| __ATOMIC_SEQ_CST); |
| |
| usage.ru_utime.tv_sec += usage.ru_stime.tv_sec; |
| usage.ru_utime.tv_usec += usage.ru_stime.tv_usec; |
| usage.ru_utime.tv_sec += |
| usage.ru_utime.tv_usec / (1000 * 1000); |
| usage.ru_utime.tv_usec %= 1000 * 1000; |
| |
| if (WIFEXITED(status)) { |
| zed_log_msg(LOG_INFO, |
| "Finished \"%s\" eid=%llu pid=%d " |
| "time=%llu.%06us exit=%d", |
| node.name, node.eid, pid, |
| (unsigned long long) usage.ru_utime.tv_sec, |
| (unsigned int) usage.ru_utime.tv_usec, |
| WEXITSTATUS(status)); |
| } else if (WIFSIGNALED(status)) { |
| zed_log_msg(LOG_INFO, |
| "Finished \"%s\" eid=%llu pid=%d " |
| "time=%llu.%06us sig=%d/%s", |
| node.name, node.eid, pid, |
| (unsigned long long) usage.ru_utime.tv_sec, |
| (unsigned int) usage.ru_utime.tv_usec, |
| WTERMSIG(status), |
| strsignal(WTERMSIG(status))); |
| } else { |
| zed_log_msg(LOG_INFO, |
| "Finished \"%s\" eid=%llu pid=%d " |
| "time=%llu.%06us status=0x%X", |
| node.name, node.eid, |
| (unsigned long long) usage.ru_utime.tv_sec, |
| (unsigned int) usage.ru_utime.tv_usec, |
| (unsigned int) status); |
| } |
| |
| free(node.name); |
| } |
| } |
| |
| return (NULL); |
| } |
| |
| void |
| zed_exec_fini(void) |
| { |
| struct launched_process_node *node; |
| void *ck = NULL; |
| |
| if (_reap_children_tid == (pthread_t)-1) |
| return; |
| |
| _reap_children_stop = B_TRUE; |
| (void) pthread_kill(_reap_children_tid, SIGCHLD); |
| (void) pthread_join(_reap_children_tid, NULL); |
| |
| while ((node = avl_destroy_nodes(&_launched_processes, &ck)) != NULL) { |
| free(node->name); |
| free(node); |
| } |
| avl_destroy(&_launched_processes); |
| |
| (void) pthread_mutex_destroy(&_launched_processes_lock); |
| (void) pthread_mutex_init(&_launched_processes_lock, NULL); |
| |
| _reap_children_tid = (pthread_t)-1; |
| } |
| |
| /* |
| * Process the event [eid] by synchronously invoking all zedlets with a |
| * matching class prefix. |
| * |
| * Each executable in [zcp->zedlets] from the directory [zcp->zedlet_dir] |
| * is matched against the event's [class], [subclass], and the "all" class |
| * (which matches all events). |
| * Every zedlet with a matching class prefix is invoked. |
| * The NAME=VALUE strings in [envs] will be passed to the zedlet as |
| * environment variables. |
| * |
| * The file descriptor [zcp->zevent_fd] is the zevent_fd used to track the |
| * current cursor location within the zevent nvlist. |
| * |
| * Return 0 on success, -1 on error. |
| */ |
| int |
| zed_exec_process(uint64_t eid, const char *class, const char *subclass, |
| struct zed_conf *zcp, zed_strings_t *envs) |
| { |
| const char *class_strings[4]; |
| const char *allclass = "all"; |
| const char **csp; |
| const char *z; |
| char **e; |
| int n; |
| |
| if (!zcp->zedlet_dir || !zcp->zedlets || !envs || zcp->zevent_fd < 0) |
| return (-1); |
| |
| if (_reap_children_tid == (pthread_t)-1) { |
| _launched_processes_limit = zcp->max_jobs; |
| |
| if (pthread_create(&_reap_children_tid, NULL, |
| _reap_children, NULL) != 0) |
| return (-1); |
| pthread_setname_np(_reap_children_tid, "reap ZEDLETs"); |
| |
| avl_create(&_launched_processes, _launched_process_node_compare, |
| sizeof (struct launched_process_node), |
| offsetof(struct launched_process_node, node)); |
| } |
| |
| csp = class_strings; |
| |
| if (class) |
| *csp++ = class; |
| |
| if (subclass) |
| *csp++ = subclass; |
| |
| if (allclass) |
| *csp++ = allclass; |
| |
| *csp = NULL; |
| |
| e = _zed_exec_create_env(envs); |
| |
| for (z = zed_strings_first(zcp->zedlets); z; |
| z = zed_strings_next(zcp->zedlets)) { |
| for (csp = class_strings; *csp; csp++) { |
| n = strlen(*csp); |
| if ((strncmp(z, *csp, n) == 0) && !isalpha(z[n])) |
| _zed_exec_fork_child(eid, zcp->zedlet_dir, |
| z, e, zcp->zevent_fd, zcp->do_foreground); |
| } |
| } |
| free(e); |
| return (0); |
| } |