xref: /src/sys/contrib/openzfs/cmd/zed/zed_exec.c (revision 80aae8a3f8aa70712930664572be9e6885dc0be7)
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