zed: use separate reaper thread and collect ZEDLETs asynchronously

Reviewed-by: Brian Behlendorf <behlendorf1@llnl.gov>
Signed-off-by: Ahelenia Ziemiańska <nabijaczleweli@nabijaczleweli.xyz>
Closes #11807
This commit is contained in:
наб 2021-03-26 14:41:38 +01:00 committed by Brian Behlendorf
parent 84c1652a0a
commit 9160c441a4
5 changed files with 155 additions and 53 deletions

View File

@ -60,8 +60,8 @@ _setup_sig_handlers(void)
zed_log_die("Failed to initialize sigset"); zed_log_die("Failed to initialize sigset");
sa.sa_flags = SA_RESTART; sa.sa_flags = SA_RESTART;
sa.sa_handler = SIG_IGN;
sa.sa_handler = SIG_IGN;
if (sigaction(SIGPIPE, &sa, NULL) < 0) if (sigaction(SIGPIPE, &sa, NULL) < 0)
zed_log_die("Failed to ignore SIGPIPE"); zed_log_die("Failed to ignore SIGPIPE");
@ -75,6 +75,10 @@ _setup_sig_handlers(void)
sa.sa_handler = _hup_handler; sa.sa_handler = _hup_handler;
if (sigaction(SIGHUP, &sa, NULL) < 0) if (sigaction(SIGHUP, &sa, NULL) < 0)
zed_log_die("Failed to register SIGHUP handler"); zed_log_die("Failed to register SIGHUP handler");
(void) sigaddset(&sa.sa_mask, SIGCHLD);
if (pthread_sigmask(SIG_BLOCK, &sa.sa_mask, NULL) < 0)
zed_log_die("Failed to block SIGCHLD");
} }
/* /*

View File

@ -15,7 +15,7 @@
#include <ctype.h> #include <ctype.h>
#include <errno.h> #include <errno.h>
#include <fcntl.h> #include <fcntl.h>
#include <libzfs.h> /* FIXME: Replace with libzfs_core. */ #include <libzfs_core.h>
#include <paths.h> #include <paths.h>
#include <stdarg.h> #include <stdarg.h>
#include <stdio.h> #include <stdio.h>
@ -96,6 +96,8 @@ zed_event_fini(struct zed_conf *zcp)
libzfs_fini(zcp->zfs_hdl); libzfs_fini(zcp->zfs_hdl);
zcp->zfs_hdl = NULL; zcp->zfs_hdl = NULL;
} }
zed_exec_fini();
} }
/* /*

View File

@ -18,10 +18,13 @@
#include <fcntl.h> #include <fcntl.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <stddef.h>
#include <sys/avl.h>
#include <sys/stat.h> #include <sys/stat.h>
#include <sys/wait.h> #include <sys/wait.h>
#include <time.h> #include <time.h>
#include <unistd.h> #include <unistd.h>
#include <pthread.h>
#include "zed_exec.h" #include "zed_exec.h"
#include "zed_file.h" #include "zed_file.h"
#include "zed_log.h" #include "zed_log.h"
@ -29,6 +32,38 @@
#define ZEVENT_FILENO 3 #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;
/* /*
* Create an environment string array for passing to execve() using the * Create an environment string array for passing to execve() using the
* NAME=VALUE strings in container [zsp]. * NAME=VALUE strings in container [zsp].
@ -85,8 +120,8 @@ _zed_exec_fork_child(uint64_t eid, const char *dir, const char *prog,
int n; int n;
pid_t pid; pid_t pid;
int fd; int fd;
pid_t wpid; struct launched_process_node *node;
int status; sigset_t mask;
assert(dir != NULL); assert(dir != NULL);
assert(prog != NULL); assert(prog != NULL);
@ -107,6 +142,9 @@ _zed_exec_fork_child(uint64_t eid, const char *dir, const char *prog,
prog, eid, strerror(errno)); prog, eid, strerror(errno));
return; return;
} else if (pid == 0) { } else if (pid == 0) {
(void) sigemptyset(&mask);
(void) sigprocmask(SIG_SETMASK, &mask, NULL);
(void) umask(022); (void) umask(022);
if ((fd = open("/dev/null", O_RDWR)) != -1) { if ((fd = open("/dev/null", O_RDWR)) != -1) {
(void) dup2(fd, STDIN_FILENO); (void) dup2(fd, STDIN_FILENO);
@ -124,59 +162,110 @@ _zed_exec_fork_child(uint64_t eid, const char *dir, const char *prog,
zed_log_msg(LOG_INFO, "Invoking \"%s\" eid=%llu pid=%d", zed_log_msg(LOG_INFO, "Invoking \"%s\" eid=%llu pid=%d",
prog, eid, pid); prog, eid, pid);
/* FIXME: Timeout rogue child processes with sigalarm? */ node = calloc(1, sizeof (*node));
if (node) {
/* node->pid = pid;
* Wait for child process using WNOHANG to limit node->eid = eid;
* the time spent waiting to 10 seconds (10,000ms). node->name = strdup(prog);
*/ (void) pthread_mutex_lock(&_launched_processes_lock);
for (n = 0; n < 1000; n++) { avl_add(&_launched_processes, node);
wpid = waitpid(pid, &status, WNOHANG); (void) pthread_mutex_unlock(&_launched_processes_lock);
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;
} }
}
static void
_nop(int sig)
{}
static void *
_reap_children(void *arg)
{
struct launched_process_node node, *pnode;
pid_t pid;
int status;
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; ) {
pid = waitpid(0, &status, 0);
if (pid == (pid_t)-1) {
if (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;
(void) pthread_mutex_lock(&_launched_processes_lock);
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);
if (WIFEXITED(status)) { if (WIFEXITED(status)) {
zed_log_msg(LOG_INFO, zed_log_msg(LOG_INFO,
"Finished \"%s\" eid=%llu pid=%d exit=%d", "Finished \"%s\" eid=%llu pid=%d exit=%d",
prog, eid, pid, WEXITSTATUS(status)); node.name, node.eid, pid,
WEXITSTATUS(status));
} else if (WIFSIGNALED(status)) { } else if (WIFSIGNALED(status)) {
zed_log_msg(LOG_INFO, zed_log_msg(LOG_INFO,
"Finished \"%s\" eid=%llu pid=%d sig=%d/%s", "Finished \"%s\" eid=%llu pid=%d sig=%d/%s",
prog, eid, pid, WTERMSIG(status), node.name, node.eid, pid, WTERMSIG(status),
strsignal(WTERMSIG(status))); strsignal(WTERMSIG(status)));
} else { } else {
zed_log_msg(LOG_INFO, zed_log_msg(LOG_INFO,
"Finished \"%s\" eid=%llu pid=%d status=0x%X", "Finished \"%s\" eid=%llu pid=%d "
prog, eid, (unsigned int) status); "status=0x%X",
} node.name, node.eid, (unsigned int) status);
break;
} }
/* free(node.name);
* 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);
(void) waitpid(pid, &status, 0);
} }
} }
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 * Process the event [eid] by synchronously invoking all zedlets with a
* matching class prefix. * matching class prefix.
@ -206,6 +295,17 @@ zed_exec_process(uint64_t eid, const char *class, const char *subclass,
if (!dir || !zedlets || !envs || zfd < 0) if (!dir || !zedlets || !envs || zfd < 0)
return (-1); return (-1);
if (_reap_children_tid == (pthread_t)-1) {
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; csp = class_strings;
if (class) if (class)

View File

@ -18,6 +18,8 @@
#include <stdint.h> #include <stdint.h>
#include "zed_strings.h" #include "zed_strings.h"
void zed_exec_fini(void);
int zed_exec_process(uint64_t eid, const char *class, const char *subclass, int zed_exec_process(uint64_t eid, const char *class, const char *subclass,
const char *dir, zed_strings_t *zedlets, zed_strings_t *envs, const char *dir, zed_strings_t *zedlets, zed_strings_t *envs,
int zevent_fd); int zevent_fd);

View File

@ -231,12 +231,6 @@ Terminate the daemon.
.SH BUGS .SH BUGS
.PP .PP
Events are processed synchronously by a single thread. This can delay the
processing of simultaneous zevents.
.PP
ZEDLETs are killed after a maximum of ten seconds.
This can lead to a violation of a ZEDLET's atomicity assumptions.
.PP
The ownership and permissions of the \fIenabled-zedlets\fR directory (along The ownership and permissions of the \fIenabled-zedlets\fR directory (along
with all parent directories) are not checked. If any of these directories with all parent directories) are not checked. If any of these directories
are improperly owned or permissioned, an unprivileged user could insert a are improperly owned or permissioned, an unprivileged user could insert a