1 // SPDX-License-Identifier: CDDL-1.0
2 /*
3 * This file is part of the ZFS Event Daemon (ZED).
4 *
5 * Developed at Lawrence Livermore National Laboratory (LLNL-CODE-403049).
6 * Copyright (C) 2013-2014 Lawrence Livermore National Security, LLC.
7 * Refer to the OpenZFS git commit log for authoritative copyright attribution.
8 *
9 * The contents of this file are subject to the terms of the
10 * Common Development and Distribution License Version 1.0 (CDDL-1.0).
11 * You can obtain a copy of the license from the top-level file
12 * "OPENSOLARIS.LICENSE" or at <http://opensource.org/licenses/CDDL-1.0>.
13 * You may not use this file except in compliance with the license.
14 */
15
16 #include <assert.h>
17 #include <ctype.h>
18 #include <errno.h>
19 #include <fcntl.h>
20 #include <stdlib.h>
21 #include <string.h>
22 #include <stddef.h>
23 #include <sys/avl.h>
24 #include <sys/resource.h>
25 #include <sys/stat.h>
26 #include <sys/wait.h>
27 #include <time.h>
28 #include <unistd.h>
29 #include <pthread.h>
30 #include <signal.h>
31
32 #include "zed_exec.h"
33 #include "zed_log.h"
34 #include "zed_strings.h"
35
36 #define ZEVENT_FILENO 3
37
38 struct launched_process_node {
39 avl_node_t node;
40 pid_t pid;
41 uint64_t eid;
42 char *name;
43 };
44
45 static int
_launched_process_node_compare(const void * x1,const void * x2)46 _launched_process_node_compare(const void *x1, const void *x2)
47 {
48 const struct launched_process_node *node1 = x1;
49 const struct launched_process_node *node2 = x2;
50
51 return (TREE_CMP(node1->pid, node2->pid));
52 }
53
54 static pthread_t _reap_children_tid = (pthread_t)-1;
55 static volatile boolean_t _reap_children_stop;
56 static avl_tree_t _launched_processes;
57 static pthread_mutex_t _launched_processes_lock = PTHREAD_MUTEX_INITIALIZER;
58 static int16_t _launched_processes_limit;
59
60 /*
61 * Create an environment string array for passing to execve() using the
62 * NAME=VALUE strings in container [zsp].
63 * Return a newly-allocated environment, or NULL on error.
64 */
65 static char **
_zed_exec_create_env(zed_strings_t * zsp)66 _zed_exec_create_env(zed_strings_t *zsp)
67 {
68 int num_ptrs;
69 int buflen;
70 char *buf;
71 char **pp;
72 char *p;
73 const char *q;
74 int i;
75 int len;
76
77 num_ptrs = zed_strings_count(zsp) + 1;
78 buflen = num_ptrs * sizeof (char *);
79 for (q = zed_strings_first(zsp); q; q = zed_strings_next(zsp))
80 buflen += strlen(q) + 1;
81
82 buf = calloc(1, buflen);
83 if (!buf)
84 return (NULL);
85
86 pp = (char **)buf;
87 p = buf + (num_ptrs * sizeof (char *));
88 i = 0;
89 for (q = zed_strings_first(zsp); q; q = zed_strings_next(zsp)) {
90 pp[i] = p;
91 len = strlen(q) + 1;
92 memcpy(p, q, len);
93 p += len;
94 i++;
95 }
96 pp[i] = NULL;
97 assert(buf + buflen == p);
98 return ((char **)buf);
99 }
100
101 /*
102 * Fork a child process to handle event [eid]. The program [prog]
103 * in directory [dir] is executed with the environment [env].
104 *
105 * The file descriptor [zfd] is the zevent_fd used to track the
106 * current cursor location within the zevent nvlist.
107 */
108 static void
_zed_exec_fork_child(uint64_t eid,const char * dir,const char * prog,char * env[],int zfd,boolean_t in_foreground)109 _zed_exec_fork_child(uint64_t eid, const char *dir, const char *prog,
110 char *env[], int zfd, boolean_t in_foreground)
111 {
112 char path[PATH_MAX];
113 int n;
114 pid_t pid;
115 int fd;
116 struct launched_process_node *node;
117 sigset_t mask;
118 struct timespec launch_timeout =
119 { .tv_sec = 0, .tv_nsec = 200 * 1000 * 1000, };
120
121 assert(dir != NULL);
122 assert(prog != NULL);
123 assert(env != NULL);
124 assert(zfd >= 0);
125
126 while (__atomic_load_n(&_launched_processes_limit,
127 __ATOMIC_SEQ_CST) <= 0)
128 (void) nanosleep(&launch_timeout, NULL);
129
130 n = snprintf(path, sizeof (path), "%s/%s", dir, prog);
131 if ((n < 0) || (n >= sizeof (path))) {
132 zed_log_msg(LOG_WARNING,
133 "Failed to fork \"%s\" for eid=%llu: %s",
134 prog, eid, strerror(ENAMETOOLONG));
135 return;
136 }
137 (void) pthread_mutex_lock(&_launched_processes_lock);
138 pid = fork();
139 if (pid < 0) {
140 (void) pthread_mutex_unlock(&_launched_processes_lock);
141 zed_log_msg(LOG_WARNING,
142 "Failed to fork \"%s\" for eid=%llu: %s",
143 prog, eid, strerror(errno));
144 return;
145 } else if (pid == 0) {
146 (void) sigemptyset(&mask);
147 (void) sigprocmask(SIG_SETMASK, &mask, NULL);
148
149 (void) umask(022);
150 if (in_foreground && /* we're already devnulled if daemonised */
151 (fd = open("/dev/null", O_RDWR | O_CLOEXEC)) != -1) {
152 (void) dup2(fd, STDIN_FILENO);
153 (void) dup2(fd, STDOUT_FILENO);
154 (void) dup2(fd, STDERR_FILENO);
155 }
156 (void) dup2(zfd, ZEVENT_FILENO);
157 execle(path, prog, NULL, env);
158 _exit(127);
159 }
160
161 /* parent process */
162
163 node = calloc(1, sizeof (*node));
164 if (node) {
165 node->pid = pid;
166 node->eid = eid;
167 node->name = strdup(prog);
168 if (node->name == NULL) {
169 perror("strdup");
170 exit(EXIT_FAILURE);
171 }
172
173 avl_add(&_launched_processes, node);
174 }
175 (void) pthread_mutex_unlock(&_launched_processes_lock);
176
177 __atomic_sub_fetch(&_launched_processes_limit, 1, __ATOMIC_SEQ_CST);
178 zed_log_msg(LOG_INFO, "Invoking \"%s\" eid=%llu pid=%d",
179 prog, eid, pid);
180 }
181
182 static void
_nop(int sig)183 _nop(int sig)
184 {
185 (void) sig;
186 }
187
188 static void
wait_for_children(boolean_t do_pause,boolean_t wait)189 wait_for_children(boolean_t do_pause, boolean_t wait)
190 {
191 pid_t pid;
192 struct rusage usage;
193 int status;
194 struct launched_process_node node, *pnode;
195
196 for (_reap_children_stop = B_FALSE; !_reap_children_stop; ) {
197 (void) pthread_mutex_lock(&_launched_processes_lock);
198 pid = wait4(0, &status, wait ? 0 : WNOHANG, &usage);
199 if (pid == 0 || pid == (pid_t)-1) {
200 (void) pthread_mutex_unlock(&_launched_processes_lock);
201 if ((pid == 0) || (errno == ECHILD)) {
202 if (do_pause)
203 pause();
204 } else if (errno != EINTR)
205 zed_log_msg(LOG_WARNING,
206 "Failed to wait for children: %s",
207 strerror(errno));
208 if (!do_pause)
209 return;
210
211 } else {
212 memset(&node, 0, sizeof (node));
213 node.pid = pid;
214 pnode = avl_find(&_launched_processes, &node, NULL);
215 if (pnode) {
216 memcpy(&node, pnode, sizeof (node));
217
218 avl_remove(&_launched_processes, pnode);
219 free(pnode);
220 }
221 (void) pthread_mutex_unlock(&_launched_processes_lock);
222 __atomic_add_fetch(&_launched_processes_limit, 1,
223 __ATOMIC_SEQ_CST);
224
225 usage.ru_utime.tv_sec += usage.ru_stime.tv_sec;
226 usage.ru_utime.tv_usec += usage.ru_stime.tv_usec;
227 usage.ru_utime.tv_sec +=
228 usage.ru_utime.tv_usec / (1000 * 1000);
229 usage.ru_utime.tv_usec %= 1000 * 1000;
230
231 if (WIFEXITED(status)) {
232 zed_log_msg(LOG_INFO,
233 "Finished \"%s\" eid=%llu pid=%d "
234 "time=%llu.%06us exit=%d",
235 node.name, node.eid, pid,
236 (unsigned long long) usage.ru_utime.tv_sec,
237 (unsigned int) usage.ru_utime.tv_usec,
238 WEXITSTATUS(status));
239 } else if (WIFSIGNALED(status)) {
240 zed_log_msg(LOG_INFO,
241 "Finished \"%s\" eid=%llu pid=%d "
242 "time=%llu.%06us sig=%d/%s",
243 node.name, node.eid, pid,
244 (unsigned long long) usage.ru_utime.tv_sec,
245 (unsigned int) usage.ru_utime.tv_usec,
246 WTERMSIG(status),
247 strsignal(WTERMSIG(status)));
248 } else {
249 zed_log_msg(LOG_INFO,
250 "Finished \"%s\" eid=%llu pid=%d "
251 "time=%llu.%06us status=0x%X",
252 node.name, node.eid, pid,
253 (unsigned long long) usage.ru_utime.tv_sec,
254 (unsigned int) usage.ru_utime.tv_usec,
255 (unsigned int) status);
256 }
257
258 free(node.name);
259 }
260 }
261
262 }
263
264 static void *
_reap_children(void * arg)265 _reap_children(void *arg)
266 {
267 (void) arg;
268 struct sigaction sa = {};
269
270 (void) sigfillset(&sa.sa_mask);
271 (void) sigdelset(&sa.sa_mask, SIGCHLD);
272 (void) pthread_sigmask(SIG_SETMASK, &sa.sa_mask, NULL);
273
274 (void) sigemptyset(&sa.sa_mask);
275 sa.sa_handler = _nop;
276 sa.sa_flags = SA_NOCLDSTOP;
277 (void) sigaction(SIGCHLD, &sa, NULL);
278
279 wait_for_children(B_TRUE, B_FALSE);
280
281 return (NULL);
282 }
283
284 void
zed_exec_fini(void)285 zed_exec_fini(void)
286 {
287 struct launched_process_node *node;
288 void *ck = NULL;
289
290 if (_reap_children_tid == (pthread_t)-1)
291 return;
292
293 _reap_children_stop = B_TRUE;
294 (void) pthread_kill(_reap_children_tid, SIGCHLD);
295 (void) pthread_join(_reap_children_tid, NULL);
296
297 while ((node = avl_destroy_nodes(&_launched_processes, &ck)) != NULL) {
298 free(node->name);
299 free(node);
300 }
301 avl_destroy(&_launched_processes);
302
303 (void) pthread_mutex_destroy(&_launched_processes_lock);
304 (void) pthread_mutex_init(&_launched_processes_lock, NULL);
305
306 _reap_children_tid = (pthread_t)-1;
307 }
308
309 /*
310 * Check if the zedlet name indicates if it is a synchronous zedlet
311 *
312 * Synchronous zedlets have a "-sync-" immediately following the event name in
313 * their zedlet filename, like:
314 *
315 * EVENT_NAME-sync-ZEDLETNAME.sh
316 *
317 * For example, if you wanted a synchronous statechange script:
318 *
319 * statechange-sync-myzedlet.sh
320 *
321 * Synchronous zedlets are guaranteed to be the only zedlet running. No other
322 * zedlets may run in parallel with a synchronous zedlet. A synchronous
323 * zedlet will wait for all previously spawned zedlets to finish before running.
324 * Users should be careful to only use synchronous zedlets when needed, since
325 * they decrease parallelism.
326 */
327 static boolean_t
zedlet_is_sync(const char * zedlet,const char * event)328 zedlet_is_sync(const char *zedlet, const char *event)
329 {
330 const char *sync_str = "-sync-";
331 size_t sync_str_len;
332 size_t zedlet_len;
333 size_t event_len;
334
335 sync_str_len = strlen(sync_str);
336 zedlet_len = strlen(zedlet);
337 event_len = strlen(event);
338
339 if (event_len + sync_str_len >= zedlet_len)
340 return (B_FALSE);
341
342 if (strncmp(&zedlet[event_len], sync_str, sync_str_len) == 0)
343 return (B_TRUE);
344
345 return (B_FALSE);
346 }
347
348 /*
349 * Process the event [eid] by synchronously invoking all zedlets with a
350 * matching class prefix.
351 *
352 * Each executable in [zcp->zedlets] from the directory [zcp->zedlet_dir]
353 * is matched against the event's [class], [subclass], and the "all" class
354 * (which matches all events).
355 * Every zedlet with a matching class prefix is invoked.
356 * The NAME=VALUE strings in [envs] will be passed to the zedlet as
357 * environment variables.
358 *
359 * The file descriptor [zcp->zevent_fd] is the zevent_fd used to track the
360 * current cursor location within the zevent nvlist.
361 *
362 * Return 0 on success, -1 on error.
363 */
364 int
zed_exec_process(uint64_t eid,const char * class,const char * subclass,struct zed_conf * zcp,zed_strings_t * envs)365 zed_exec_process(uint64_t eid, const char *class, const char *subclass,
366 struct zed_conf *zcp, zed_strings_t *envs)
367 {
368 const char *class_strings[4];
369 const char *allclass = "all";
370 const char **csp;
371 const char *z;
372 char **e;
373 int n;
374
375 if (!zcp->zedlet_dir || !zcp->zedlets || !envs || zcp->zevent_fd < 0)
376 return (-1);
377
378 if (_reap_children_tid == (pthread_t)-1) {
379 _launched_processes_limit = zcp->max_jobs;
380
381 if (pthread_create(&_reap_children_tid, NULL,
382 _reap_children, NULL) != 0)
383 return (-1);
384 pthread_setname_np(_reap_children_tid, "reap ZEDLETs");
385
386 avl_create(&_launched_processes, _launched_process_node_compare,
387 sizeof (struct launched_process_node),
388 offsetof(struct launched_process_node, node));
389 }
390
391 csp = class_strings;
392
393 if (class)
394 *csp++ = class;
395
396 if (subclass)
397 *csp++ = subclass;
398
399 if (allclass)
400 *csp++ = allclass;
401
402 *csp = NULL;
403
404 e = _zed_exec_create_env(envs);
405
406 for (z = zed_strings_first(zcp->zedlets); z;
407 z = zed_strings_next(zcp->zedlets)) {
408 for (csp = class_strings; *csp; csp++) {
409 n = strlen(*csp);
410 if ((strncmp(z, *csp, n) == 0) && !isalpha(z[n])) {
411 boolean_t is_sync = zedlet_is_sync(z, *csp);
412
413 if (is_sync) {
414 /*
415 * Wait for previous zedlets to
416 * finish
417 */
418 wait_for_children(B_FALSE, B_TRUE);
419 }
420
421 _zed_exec_fork_child(eid, zcp->zedlet_dir,
422 z, e, zcp->zevent_fd, zcp->do_foreground);
423
424 if (is_sync) {
425 /*
426 * Wait for sync zedlet we just launched
427 * to finish.
428 */
429 wait_for_children(B_FALSE, B_TRUE);
430 }
431 }
432 }
433 }
434 free(e);
435 return (0);
436 }
437