Path: blob/main/sys/contrib/openzfs/cmd/zed/zed_exec.c
48380 views
// SPDX-License-Identifier: CDDL-1.01/*2* This file is part of the ZFS Event Daemon (ZED).3*4* Developed at Lawrence Livermore National Laboratory (LLNL-CODE-403049).5* Copyright (C) 2013-2014 Lawrence Livermore National Security, LLC.6* Refer to the OpenZFS git commit log for authoritative copyright attribution.7*8* The contents of this file are subject to the terms of the9* Common Development and Distribution License Version 1.0 (CDDL-1.0).10* You can obtain a copy of the license from the top-level file11* "OPENSOLARIS.LICENSE" or at <http://opensource.org/licenses/CDDL-1.0>.12* You may not use this file except in compliance with the license.13*/1415#include <assert.h>16#include <ctype.h>17#include <errno.h>18#include <fcntl.h>19#include <stdlib.h>20#include <string.h>21#include <stddef.h>22#include <sys/avl.h>23#include <sys/resource.h>24#include <sys/stat.h>25#include <sys/wait.h>26#include <time.h>27#include <unistd.h>28#include <pthread.h>29#include <signal.h>3031#include "zed_exec.h"32#include "zed_log.h"33#include "zed_strings.h"3435#define ZEVENT_FILENO 33637struct launched_process_node {38avl_node_t node;39pid_t pid;40uint64_t eid;41char *name;42};4344static int45_launched_process_node_compare(const void *x1, const void *x2)46{47pid_t p1;48pid_t p2;4950assert(x1 != NULL);51assert(x2 != NULL);5253p1 = ((const struct launched_process_node *) x1)->pid;54p2 = ((const struct launched_process_node *) x2)->pid;5556if (p1 < p2)57return (-1);58else if (p1 == p2)59return (0);60else61return (1);62}6364static pthread_t _reap_children_tid = (pthread_t)-1;65static volatile boolean_t _reap_children_stop;66static avl_tree_t _launched_processes;67static pthread_mutex_t _launched_processes_lock = PTHREAD_MUTEX_INITIALIZER;68static int16_t _launched_processes_limit;6970/*71* Create an environment string array for passing to execve() using the72* NAME=VALUE strings in container [zsp].73* Return a newly-allocated environment, or NULL on error.74*/75static char **76_zed_exec_create_env(zed_strings_t *zsp)77{78int num_ptrs;79int buflen;80char *buf;81char **pp;82char *p;83const char *q;84int i;85int len;8687num_ptrs = zed_strings_count(zsp) + 1;88buflen = num_ptrs * sizeof (char *);89for (q = zed_strings_first(zsp); q; q = zed_strings_next(zsp))90buflen += strlen(q) + 1;9192buf = calloc(1, buflen);93if (!buf)94return (NULL);9596pp = (char **)buf;97p = buf + (num_ptrs * sizeof (char *));98i = 0;99for (q = zed_strings_first(zsp); q; q = zed_strings_next(zsp)) {100pp[i] = p;101len = strlen(q) + 1;102memcpy(p, q, len);103p += len;104i++;105}106pp[i] = NULL;107assert(buf + buflen == p);108return ((char **)buf);109}110111/*112* Fork a child process to handle event [eid]. The program [prog]113* in directory [dir] is executed with the environment [env].114*115* The file descriptor [zfd] is the zevent_fd used to track the116* current cursor location within the zevent nvlist.117*/118static void119_zed_exec_fork_child(uint64_t eid, const char *dir, const char *prog,120char *env[], int zfd, boolean_t in_foreground)121{122char path[PATH_MAX];123int n;124pid_t pid;125int fd;126struct launched_process_node *node;127sigset_t mask;128struct timespec launch_timeout =129{ .tv_sec = 0, .tv_nsec = 200 * 1000 * 1000, };130131assert(dir != NULL);132assert(prog != NULL);133assert(env != NULL);134assert(zfd >= 0);135136while (__atomic_load_n(&_launched_processes_limit,137__ATOMIC_SEQ_CST) <= 0)138(void) nanosleep(&launch_timeout, NULL);139140n = snprintf(path, sizeof (path), "%s/%s", dir, prog);141if ((n < 0) || (n >= sizeof (path))) {142zed_log_msg(LOG_WARNING,143"Failed to fork \"%s\" for eid=%llu: %s",144prog, eid, strerror(ENAMETOOLONG));145return;146}147(void) pthread_mutex_lock(&_launched_processes_lock);148pid = fork();149if (pid < 0) {150(void) pthread_mutex_unlock(&_launched_processes_lock);151zed_log_msg(LOG_WARNING,152"Failed to fork \"%s\" for eid=%llu: %s",153prog, eid, strerror(errno));154return;155} else if (pid == 0) {156(void) sigemptyset(&mask);157(void) sigprocmask(SIG_SETMASK, &mask, NULL);158159(void) umask(022);160if (in_foreground && /* we're already devnulled if daemonised */161(fd = open("/dev/null", O_RDWR | O_CLOEXEC)) != -1) {162(void) dup2(fd, STDIN_FILENO);163(void) dup2(fd, STDOUT_FILENO);164(void) dup2(fd, STDERR_FILENO);165}166(void) dup2(zfd, ZEVENT_FILENO);167execle(path, prog, NULL, env);168_exit(127);169}170171/* parent process */172173node = calloc(1, sizeof (*node));174if (node) {175node->pid = pid;176node->eid = eid;177node->name = strdup(prog);178if (node->name == NULL) {179perror("strdup");180exit(EXIT_FAILURE);181}182183avl_add(&_launched_processes, node);184}185(void) pthread_mutex_unlock(&_launched_processes_lock);186187__atomic_sub_fetch(&_launched_processes_limit, 1, __ATOMIC_SEQ_CST);188zed_log_msg(LOG_INFO, "Invoking \"%s\" eid=%llu pid=%d",189prog, eid, pid);190}191192static void193_nop(int sig)194{195(void) sig;196}197198static void199wait_for_children(boolean_t do_pause, boolean_t wait)200{201pid_t pid;202struct rusage usage;203int status;204struct launched_process_node node, *pnode;205206for (_reap_children_stop = B_FALSE; !_reap_children_stop; ) {207(void) pthread_mutex_lock(&_launched_processes_lock);208pid = wait4(0, &status, wait ? 0 : WNOHANG, &usage);209if (pid == 0 || pid == (pid_t)-1) {210(void) pthread_mutex_unlock(&_launched_processes_lock);211if ((pid == 0) || (errno == ECHILD)) {212if (do_pause)213pause();214} else if (errno != EINTR)215zed_log_msg(LOG_WARNING,216"Failed to wait for children: %s",217strerror(errno));218if (!do_pause)219return;220221} else {222memset(&node, 0, sizeof (node));223node.pid = pid;224pnode = avl_find(&_launched_processes, &node, NULL);225if (pnode) {226memcpy(&node, pnode, sizeof (node));227228avl_remove(&_launched_processes, pnode);229free(pnode);230}231(void) pthread_mutex_unlock(&_launched_processes_lock);232__atomic_add_fetch(&_launched_processes_limit, 1,233__ATOMIC_SEQ_CST);234235usage.ru_utime.tv_sec += usage.ru_stime.tv_sec;236usage.ru_utime.tv_usec += usage.ru_stime.tv_usec;237usage.ru_utime.tv_sec +=238usage.ru_utime.tv_usec / (1000 * 1000);239usage.ru_utime.tv_usec %= 1000 * 1000;240241if (WIFEXITED(status)) {242zed_log_msg(LOG_INFO,243"Finished \"%s\" eid=%llu pid=%d "244"time=%llu.%06us exit=%d",245node.name, node.eid, pid,246(unsigned long long) usage.ru_utime.tv_sec,247(unsigned int) usage.ru_utime.tv_usec,248WEXITSTATUS(status));249} else if (WIFSIGNALED(status)) {250zed_log_msg(LOG_INFO,251"Finished \"%s\" eid=%llu pid=%d "252"time=%llu.%06us sig=%d/%s",253node.name, node.eid, pid,254(unsigned long long) usage.ru_utime.tv_sec,255(unsigned int) usage.ru_utime.tv_usec,256WTERMSIG(status),257strsignal(WTERMSIG(status)));258} else {259zed_log_msg(LOG_INFO,260"Finished \"%s\" eid=%llu pid=%d "261"time=%llu.%06us status=0x%X",262node.name, node.eid, pid,263(unsigned long long) usage.ru_utime.tv_sec,264(unsigned int) usage.ru_utime.tv_usec,265(unsigned int) status);266}267268free(node.name);269}270}271272}273274static void *275_reap_children(void *arg)276{277(void) arg;278struct sigaction sa = {};279280(void) sigfillset(&sa.sa_mask);281(void) sigdelset(&sa.sa_mask, SIGCHLD);282(void) pthread_sigmask(SIG_SETMASK, &sa.sa_mask, NULL);283284(void) sigemptyset(&sa.sa_mask);285sa.sa_handler = _nop;286sa.sa_flags = SA_NOCLDSTOP;287(void) sigaction(SIGCHLD, &sa, NULL);288289wait_for_children(B_TRUE, B_FALSE);290291return (NULL);292}293294void295zed_exec_fini(void)296{297struct launched_process_node *node;298void *ck = NULL;299300if (_reap_children_tid == (pthread_t)-1)301return;302303_reap_children_stop = B_TRUE;304(void) pthread_kill(_reap_children_tid, SIGCHLD);305(void) pthread_join(_reap_children_tid, NULL);306307while ((node = avl_destroy_nodes(&_launched_processes, &ck)) != NULL) {308free(node->name);309free(node);310}311avl_destroy(&_launched_processes);312313(void) pthread_mutex_destroy(&_launched_processes_lock);314(void) pthread_mutex_init(&_launched_processes_lock, NULL);315316_reap_children_tid = (pthread_t)-1;317}318319/*320* Check if the zedlet name indicates if it is a synchronous zedlet321*322* Synchronous zedlets have a "-sync-" immediately following the event name in323* their zedlet filename, like:324*325* EVENT_NAME-sync-ZEDLETNAME.sh326*327* For example, if you wanted a synchronous statechange script:328*329* statechange-sync-myzedlet.sh330*331* Synchronous zedlets are guaranteed to be the only zedlet running. No other332* zedlets may run in parallel with a synchronous zedlet. A synchronous333* zedlet will wait for all previously spawned zedlets to finish before running.334* Users should be careful to only use synchronous zedlets when needed, since335* they decrease parallelism.336*/337static boolean_t338zedlet_is_sync(const char *zedlet, const char *event)339{340const char *sync_str = "-sync-";341size_t sync_str_len;342size_t zedlet_len;343size_t event_len;344345sync_str_len = strlen(sync_str);346zedlet_len = strlen(zedlet);347event_len = strlen(event);348349if (event_len + sync_str_len >= zedlet_len)350return (B_FALSE);351352if (strncmp(&zedlet[event_len], sync_str, sync_str_len) == 0)353return (B_TRUE);354355return (B_FALSE);356}357358/*359* Process the event [eid] by synchronously invoking all zedlets with a360* matching class prefix.361*362* Each executable in [zcp->zedlets] from the directory [zcp->zedlet_dir]363* is matched against the event's [class], [subclass], and the "all" class364* (which matches all events).365* Every zedlet with a matching class prefix is invoked.366* The NAME=VALUE strings in [envs] will be passed to the zedlet as367* environment variables.368*369* The file descriptor [zcp->zevent_fd] is the zevent_fd used to track the370* current cursor location within the zevent nvlist.371*372* Return 0 on success, -1 on error.373*/374int375zed_exec_process(uint64_t eid, const char *class, const char *subclass,376struct zed_conf *zcp, zed_strings_t *envs)377{378const char *class_strings[4];379const char *allclass = "all";380const char **csp;381const char *z;382char **e;383int n;384385if (!zcp->zedlet_dir || !zcp->zedlets || !envs || zcp->zevent_fd < 0)386return (-1);387388if (_reap_children_tid == (pthread_t)-1) {389_launched_processes_limit = zcp->max_jobs;390391if (pthread_create(&_reap_children_tid, NULL,392_reap_children, NULL) != 0)393return (-1);394pthread_setname_np(_reap_children_tid, "reap ZEDLETs");395396avl_create(&_launched_processes, _launched_process_node_compare,397sizeof (struct launched_process_node),398offsetof(struct launched_process_node, node));399}400401csp = class_strings;402403if (class)404*csp++ = class;405406if (subclass)407*csp++ = subclass;408409if (allclass)410*csp++ = allclass;411412*csp = NULL;413414e = _zed_exec_create_env(envs);415416for (z = zed_strings_first(zcp->zedlets); z;417z = zed_strings_next(zcp->zedlets)) {418for (csp = class_strings; *csp; csp++) {419n = strlen(*csp);420if ((strncmp(z, *csp, n) == 0) && !isalpha(z[n])) {421boolean_t is_sync = zedlet_is_sync(z, *csp);422423if (is_sync) {424/*425* Wait for previous zedlets to426* finish427*/428wait_for_children(B_FALSE, B_TRUE);429}430431_zed_exec_fork_child(eid, zcp->zedlet_dir,432z, e, zcp->zevent_fd, zcp->do_foreground);433434if (is_sync) {435/*436* Wait for sync zedlet we just launched437* to finish.438*/439wait_for_children(B_FALSE, B_TRUE);440}441}442}443}444free(e);445return (0);446}447448449