xref: /freebsd/sys/contrib/openzfs/cmd/zed/zed_exec.c (revision dd41de95a84d979615a2ef11df6850622bf6184e)
1 /*
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 ZoL git commit log for authoritative copyright attribution.
7  *
8  * The contents of this file are subject to the terms of the
9  * Common Development and Distribution License Version 1.0 (CDDL-1.0).
10  * You can obtain a copy of the license from the top-level file
11  * "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  */
14 
15 #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 <sys/stat.h>
22 #include <sys/wait.h>
23 #include <time.h>
24 #include <unistd.h>
25 #include "zed_exec.h"
26 #include "zed_file.h"
27 #include "zed_log.h"
28 #include "zed_strings.h"
29 
30 #define	ZEVENT_FILENO	3
31 
32 /*
33  * Create an environment string array for passing to execve() using the
34  * NAME=VALUE strings in container [zsp].
35  * Return a newly-allocated environment, or NULL on error.
36  */
37 static char **
38 _zed_exec_create_env(zed_strings_t *zsp)
39 {
40 	int num_ptrs;
41 	int buflen;
42 	char *buf;
43 	char **pp;
44 	char *p;
45 	const char *q;
46 	int i;
47 	int len;
48 
49 	num_ptrs = zed_strings_count(zsp) + 1;
50 	buflen = num_ptrs * sizeof (char *);
51 	for (q = zed_strings_first(zsp); q; q = zed_strings_next(zsp))
52 		buflen += strlen(q) + 1;
53 
54 	buf = calloc(1, buflen);
55 	if (!buf)
56 		return (NULL);
57 
58 	pp = (char **)buf;
59 	p = buf + (num_ptrs * sizeof (char *));
60 	i = 0;
61 	for (q = zed_strings_first(zsp); q; q = zed_strings_next(zsp)) {
62 		pp[i] = p;
63 		len = strlen(q) + 1;
64 		memcpy(p, q, len);
65 		p += len;
66 		i++;
67 	}
68 	pp[i] = NULL;
69 	assert(buf + buflen == p);
70 	return ((char **)buf);
71 }
72 
73 /*
74  * Fork a child process to handle event [eid].  The program [prog]
75  * in directory [dir] is executed with the environment [env].
76  *
77  * The file descriptor [zfd] is the zevent_fd used to track the
78  * current cursor location within the zevent nvlist.
79  */
80 static void
81 _zed_exec_fork_child(uint64_t eid, const char *dir, const char *prog,
82     char *env[], int zfd)
83 {
84 	char path[PATH_MAX];
85 	int n;
86 	pid_t pid;
87 	int fd;
88 	pid_t wpid;
89 	int status;
90 
91 	assert(dir != NULL);
92 	assert(prog != NULL);
93 	assert(env != NULL);
94 	assert(zfd >= 0);
95 
96 	n = snprintf(path, sizeof (path), "%s/%s", dir, prog);
97 	if ((n < 0) || (n >= sizeof (path))) {
98 		zed_log_msg(LOG_WARNING,
99 		    "Failed to fork \"%s\" for eid=%llu: %s",
100 		    prog, eid, strerror(ENAMETOOLONG));
101 		return;
102 	}
103 	pid = fork();
104 	if (pid < 0) {
105 		zed_log_msg(LOG_WARNING,
106 		    "Failed to fork \"%s\" for eid=%llu: %s",
107 		    prog, eid, strerror(errno));
108 		return;
109 	} else if (pid == 0) {
110 		(void) umask(022);
111 		if ((fd = open("/dev/null", O_RDWR)) != -1) {
112 			(void) dup2(fd, STDIN_FILENO);
113 			(void) dup2(fd, STDOUT_FILENO);
114 			(void) dup2(fd, STDERR_FILENO);
115 		}
116 		(void) dup2(zfd, ZEVENT_FILENO);
117 		zed_file_close_from(ZEVENT_FILENO + 1);
118 		execle(path, prog, NULL, env);
119 		_exit(127);
120 	}
121 
122 	/* parent process */
123 
124 	zed_log_msg(LOG_INFO, "Invoking \"%s\" eid=%llu pid=%d",
125 	    prog, eid, pid);
126 
127 	/* FIXME: Timeout rogue child processes with sigalarm? */
128 
129 	/*
130 	 * Wait for child process using WNOHANG to limit
131 	 * the time spent waiting to 10 seconds (10,000ms).
132 	 */
133 	for (n = 0; n < 1000; n++) {
134 		wpid = waitpid(pid, &status, WNOHANG);
135 		if (wpid == (pid_t)-1) {
136 			if (errno == EINTR)
137 				continue;
138 			zed_log_msg(LOG_WARNING,
139 			    "Failed to wait for \"%s\" eid=%llu pid=%d",
140 			    prog, eid, pid);
141 			break;
142 		} else if (wpid == 0) {
143 			struct timespec t;
144 
145 			/* child still running */
146 			t.tv_sec = 0;
147 			t.tv_nsec = 10000000;	/* 10ms */
148 			(void) nanosleep(&t, NULL);
149 			continue;
150 		}
151 
152 		if (WIFEXITED(status)) {
153 			zed_log_msg(LOG_INFO,
154 			    "Finished \"%s\" eid=%llu pid=%d exit=%d",
155 			    prog, eid, pid, WEXITSTATUS(status));
156 		} else if (WIFSIGNALED(status)) {
157 			zed_log_msg(LOG_INFO,
158 			    "Finished \"%s\" eid=%llu pid=%d sig=%d/%s",
159 			    prog, eid, pid, WTERMSIG(status),
160 			    strsignal(WTERMSIG(status)));
161 		} else {
162 			zed_log_msg(LOG_INFO,
163 			    "Finished \"%s\" eid=%llu pid=%d status=0x%X",
164 			    prog, eid, (unsigned int) status);
165 		}
166 		break;
167 	}
168 
169 	/*
170 	 * kill child process after 10 seconds
171 	 */
172 	if (wpid == 0) {
173 		zed_log_msg(LOG_WARNING, "Killing hung \"%s\" pid=%d",
174 		    prog, pid);
175 		(void) kill(pid, SIGKILL);
176 		(void) waitpid(pid, &status, 0);
177 	}
178 }
179 
180 /*
181  * Process the event [eid] by synchronously invoking all zedlets with a
182  * matching class prefix.
183  *
184  * Each executable in [zedlets] from the directory [dir] is matched against
185  * the event's [class], [subclass], and the "all" class (which matches
186  * all events).  Every zedlet with a matching class prefix is invoked.
187  * The NAME=VALUE strings in [envs] will be passed to the zedlet as
188  * environment variables.
189  *
190  * The file descriptor [zfd] is the zevent_fd used to track the
191  * current cursor location within the zevent nvlist.
192  *
193  * Return 0 on success, -1 on error.
194  */
195 int
196 zed_exec_process(uint64_t eid, const char *class, const char *subclass,
197     const char *dir, zed_strings_t *zedlets, zed_strings_t *envs, int zfd)
198 {
199 	const char *class_strings[4];
200 	const char *allclass = "all";
201 	const char **csp;
202 	const char *z;
203 	char **e;
204 	int n;
205 
206 	if (!dir || !zedlets || !envs || zfd < 0)
207 		return (-1);
208 
209 	csp = class_strings;
210 
211 	if (class)
212 		*csp++ = class;
213 
214 	if (subclass)
215 		*csp++ = subclass;
216 
217 	if (allclass)
218 		*csp++ = allclass;
219 
220 	*csp = NULL;
221 
222 	e = _zed_exec_create_env(envs);
223 
224 	for (z = zed_strings_first(zedlets); z; z = zed_strings_next(zedlets)) {
225 		for (csp = class_strings; *csp; csp++) {
226 			n = strlen(*csp);
227 			if ((strncmp(z, *csp, n) == 0) && !isalpha(z[n]))
228 				_zed_exec_fork_child(eid, dir, z, e, zfd);
229 		}
230 	}
231 	free(e);
232 	return (0);
233 }
234