| /* |
| * 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_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); |
| } |