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