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