diff options
Diffstat (limited to 'cmd/zed/zed_exec.c')
-rw-r--r-- | cmd/zed/zed_exec.c | 232 |
1 files changed, 232 insertions, 0 deletions
diff --git a/cmd/zed/zed_exec.c b/cmd/zed/zed_exec.c new file mode 100644 index 000000000000..08b7b5568362 --- /dev/null +++ b/cmd/zed/zed_exec.c @@ -0,0 +1,232 @@ +/* + * This file is part of the ZFS Event Daemon (ZED) + * for ZFS on Linux (ZoL) <http://zfsonlinux.org/>. + * Developed at Lawrence Livermore National Laboratory (LLNL-CODE-403049). + * Copyright (C) 2013-2014 Lawrence Livermore National Security, LLC. + * Refer to the ZoL 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 <sys/stat.h> +#include <sys/wait.h> +#include <time.h> +#include <unistd.h> +#include "zed_exec.h" +#include "zed_file.h" +#include "zed_log.h" +#include "zed_strings.h" + +#define ZEVENT_FILENO 3 + +/* + * 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) +{ + char path[PATH_MAX]; + int n; + pid_t pid; + int fd; + pid_t wpid; + int status; + + assert(dir != NULL); + assert(prog != NULL); + assert(env != NULL); + assert(zfd >= 0); + + 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; + } + pid = fork(); + if (pid < 0) { + zed_log_msg(LOG_WARNING, + "Failed to fork \"%s\" for eid=%llu: %s", + prog, eid, strerror(errno)); + return; + } else if (pid == 0) { + (void) umask(022); + if ((fd = open("/dev/null", O_RDWR)) != -1) { + (void) dup2(fd, STDIN_FILENO); + (void) dup2(fd, STDOUT_FILENO); + (void) dup2(fd, STDERR_FILENO); + } + (void) dup2(zfd, ZEVENT_FILENO); + zed_file_close_from(ZEVENT_FILENO + 1); + execle(path, prog, NULL, env); + _exit(127); + } + + /* parent process */ + + zed_log_msg(LOG_INFO, "Invoking \"%s\" eid=%llu pid=%d", + prog, eid, pid); + + /* FIXME: Timeout rogue child processes with sigalarm? */ + + /* + * Wait for child process using WNOHANG to limit + * the time spent waiting to 10 seconds (10,000ms). + */ + for (n = 0; n < 1000; n++) { + wpid = waitpid(pid, &status, WNOHANG); + if (wpid == (pid_t)-1) { + if (errno == EINTR) + continue; + zed_log_msg(LOG_WARNING, + "Failed to wait for \"%s\" eid=%llu pid=%d", + prog, eid, pid); + break; + } else if (wpid == 0) { + struct timespec t; + + /* child still running */ + t.tv_sec = 0; + t.tv_nsec = 10000000; /* 10ms */ + (void) nanosleep(&t, NULL); + continue; + } + + if (WIFEXITED(status)) { + zed_log_msg(LOG_INFO, + "Finished \"%s\" eid=%llu pid=%d exit=%d", + prog, eid, pid, WEXITSTATUS(status)); + } else if (WIFSIGNALED(status)) { + zed_log_msg(LOG_INFO, + "Finished \"%s\" eid=%llu pid=%d sig=%d/%s", + prog, eid, pid, WTERMSIG(status), + strsignal(WTERMSIG(status))); + } else { + zed_log_msg(LOG_INFO, + "Finished \"%s\" eid=%llu pid=%d status=0x%X", + prog, eid, (unsigned int) status); + } + break; + } + + /* + * kill child process after 10 seconds + */ + if (wpid == 0) { + zed_log_msg(LOG_WARNING, "Killing hung \"%s\" pid=%d", + prog, pid); + (void) kill(pid, SIGKILL); + } +} + +/* + * Process the event [eid] by synchronously invoking all zedlets with a + * matching class prefix. + * + * Each executable in [zedlets] from the directory [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 [zfd] 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, + const char *dir, zed_strings_t *zedlets, zed_strings_t *envs, int zfd) +{ + const char *class_strings[4]; + const char *allclass = "all"; + const char **csp; + const char *z; + char **e; + int n; + + if (!dir || !zedlets || !envs || zfd < 0) + return (-1); + + 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(zedlets); z; z = zed_strings_next(zedlets)) { + for (csp = class_strings; *csp; csp++) { + n = strlen(*csp); + if ((strncmp(z, *csp, n) == 0) && !isalpha(z[n])) + _zed_exec_fork_child(eid, dir, z, e, zfd); + } + } + free(e); + return (0); +} |