Update Red Hat Copyright Notices
[nbdkit.git] / filters / exitwhen / exitwhen.c
blob080467f69c469c833eae4cbaecca91f53a500764
1 /* nbdkit
2 * Copyright Red Hat
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are
6 * met:
8 * * Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
11 * * Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
15 * * Neither the name of Red Hat nor the names of its contributors may be
16 * used to endorse or promote products derived from this software without
17 * specific prior written permission.
19 * THIS SOFTWARE IS PROVIDED BY RED HAT AND CONTRIBUTORS ''AS IS'' AND
20 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
21 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
22 * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL RED HAT OR
23 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
26 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
27 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
29 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
30 * SUCH DAMAGE.
33 #include <config.h>
35 #include <stdio.h>
36 #include <stdlib.h>
37 #include <stdbool.h>
38 #include <stdint.h>
39 #include <inttypes.h>
40 #include <unistd.h>
41 #include <signal.h>
42 #include <fcntl.h>
43 #include <assert.h>
44 #include <errno.h>
45 #include <sys/types.h>
47 #ifdef HAVE_SYS_WAIT_H
48 #include <sys/wait.h>
49 #endif
51 #include <pthread.h>
53 #include <nbdkit-filter.h>
55 #include "cleanup.h"
56 #include "poll.h"
57 #include "utils.h"
58 #include "vector.h"
60 static unsigned pollsecs = 60;
62 static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
63 static unsigned connections = 0; /* NB: Protected by 'lock' */
64 static bool exiting = false;
66 /* The list of events generated from command line parameters. */
67 struct event {
68 int type;
69 #define EVENT_FILE_CREATED 1
70 #define EVENT_FILE_DELETED 2
71 #ifndef WIN32
72 #define EVENT_PROCESS_EXITS 3
73 #define EVENT_FD_CLOSED 4
74 #define EVENT_SCRIPT 5
75 #endif
76 union {
77 char *filename; /* Filename or script. */
78 int fd; /* For PROCESS_EXITS or FD_CLOSED. */
79 #ifndef __linux__
80 pid_t pid; /* For PROCESS_EXITS on non-Linux. */
81 #endif
82 } u;
84 DEFINE_VECTOR_TYPE (event_list, struct event);
85 static event_list events = empty_vector;
87 static void
88 free_event (struct event event)
90 switch (event.type) {
91 #ifdef EVENT_FILE_CREATED
92 case EVENT_FILE_CREATED:
93 #endif
94 #ifdef EVENT_FILE_DELETED
95 case EVENT_FILE_DELETED:
96 #endif
97 #ifdef EVENT_SCRIPT
98 case EVENT_SCRIPT:
99 #endif
100 free (event.u.filename);
101 break;
102 #ifdef EVENT_PROCESS_EXITS
103 case EVENT_PROCESS_EXITS:
104 #endif
105 #ifdef EVENT_FD_CLOSED
106 case EVENT_FD_CLOSED:
107 #endif
108 #ifdef __linux__
109 close (event.u.fd);
110 #endif
111 break;
115 static void
116 exitwhen_unload (void)
118 event_list_iter (&events, free_event);
119 free (events.ptr);
122 /* If exiting is already true, this does nothing and returns true.
123 * Otherwise it checks if any event in the list has happened. If an
124 * event has happened, sets exiting to true. It returns the exiting
125 * flag.
127 * This must be called with &lock held.
129 #ifdef EVENT_FILE_CREATED
130 static void check_for_event_file_created (const struct event *);
131 #endif
132 #ifdef EVENT_FILE_DELETED
133 static void check_for_event_file_deleted (const struct event *);
134 #endif
135 #ifdef EVENT_PROCESS_EXITS
136 static void check_for_event_process_exits (const struct event *);
137 #endif
138 #ifdef EVENT_FD_CLOSED
139 static void check_for_event_fd_closed (const struct event *);
140 #endif
141 #ifdef EVENT_SCRIPT
142 static void check_for_event_script (const struct event *);
143 #endif
145 static bool
146 check_for_event (void)
148 size_t i;
150 if (!exiting) {
151 for (i = 0; i < events.len; ++i) {
152 const struct event *event = &events.ptr[i];
154 switch (event->type) {
155 #ifdef EVENT_FILE_CREATED
156 case EVENT_FILE_CREATED:
157 check_for_event_file_created (event);
158 break;
159 #endif
160 #ifdef EVENT_FILE_DELETED
161 case EVENT_FILE_DELETED:
162 check_for_event_file_deleted (event);
163 break;
164 #endif
165 #ifdef EVENT_PROCESS_EXITS
166 case EVENT_PROCESS_EXITS:
167 check_for_event_process_exits (event);
168 break;
169 #endif
170 #ifdef EVENT_FD_CLOSED
171 case EVENT_FD_CLOSED:
172 check_for_event_fd_closed (event);
173 break;
174 #endif
175 #ifdef EVENT_SCRIPT
176 case EVENT_SCRIPT:
177 check_for_event_script (event);
178 break;
179 #endif
184 return exiting;
187 #ifdef EVENT_FILE_CREATED
188 static void
189 check_for_event_file_created (const struct event *event)
191 if (access (event->u.filename, R_OK) == 0) {
192 nbdkit_debug ("exit-when-file-created: detected %s created",
193 event->u.filename);
194 exiting = true;
197 #endif
199 #ifdef EVENT_FILE_DELETED
200 static void
201 check_for_event_file_deleted (const struct event *event)
203 if (access (event->u.filename, R_OK) == -1) {
204 if (errno == ENOTDIR || errno == ENOENT) {
205 nbdkit_debug ("exit-when-file-deleted: detected %s deleted",
206 event->u.filename);
207 exiting = true;
209 else {
210 /* Log the error but continue. */
211 nbdkit_error ("exit-when-file-deleted: access: %s: %m",
212 event->u.filename);
216 #endif
218 #ifdef EVENT_PROCESS_EXITS
219 static void
220 check_for_event_process_exits (const struct event *event)
222 #ifdef __linux__
223 char c;
225 /* https://gitlab.freedesktop.org/polkit/polkit/-/issues/75
227 * event->u.fd holds /proc/PID/stat of the original process open.
228 * If we can still read a byte from it then the original process is
229 * still around. If we get ESRCH then the process has exited.
231 lseek (event->u.fd, 0, SEEK_SET);
232 if (read (event->u.fd, &c, 1) == -1) {
233 if (errno == ESRCH) {
234 nbdkit_debug ("exit-when-process-exits: detected process exit");
235 exiting = true;
237 else {
238 /* Log the error but continue. */
239 nbdkit_error ("exit-when-process-exits: read: %m");
242 #else /* !__linux__ */
243 /* XXX Find a safe way to do this on BSD at least. */
244 if (kill (event->u.pid, 0) == -1 && errno == ESRCH) {
245 nbdkit_debug ("exit-when-process-exits: detected process exit");
246 exiting = true;
248 #endif /* !__linux__ */
250 #endif
252 #ifdef EVENT_FD_CLOSED
253 static void
254 check_for_event_fd_closed (const struct event *event)
256 int r;
257 struct pollfd fds[1];
259 /* event->u.fd is the read side of a pipe or socket. Check it is
260 * not closed. We don't actually read anything from the pipe.
262 fds[0].fd = event->u.fd;
263 fds[0].events = 0;
264 r = poll (fds, 1, 0);
265 if (r == 1) {
266 if ((fds[0].revents & POLLHUP) != 0) {
267 nbdkit_debug ("exit-when-pipe-closed: detected pipe closed");
268 exiting = true;
270 else if ((fds[0].revents & POLLNVAL) != 0) {
271 /* If we were passed a bad file descriptor that is user error
272 * and we should exit with an error early. Because
273 * check_for_event() is called first in get_ready() this should
274 * cause this to happen.
276 nbdkit_error ("exit-when-pipe-closed: invalid file descriptor");
277 exiting = true;
280 else if (r == -1) {
281 /* Log the error but continue. */
282 nbdkit_error ("exit-when-pipe-closed: poll: %m");
285 #endif
287 #ifdef EVENT_SCRIPT
288 static void
289 check_for_event_script (const struct event *event)
291 int r;
293 /* event->u.filename is a script filename or command. Exit code 88
294 * indicates the event has happened.
296 r = system (event->u.filename);
297 if (r == -1) {
298 /* Log the error but continue. */
299 nbdkit_error ("exit-when-script: %m");
301 else if (WIFEXITED (r) && WEXITSTATUS (r) == 0) {
302 /* Normal case, do nothing. */
304 else if (WIFEXITED (r) && WEXITSTATUS (r) == 88) {
305 nbdkit_debug ("exit-when-script: detected scripted event");
306 exiting = true;
308 else {
309 /* Log the error but continue. */
310 exit_status_to_nbd_error (r, "exit-when-script");
313 #endif
315 /* The background polling thread.
317 * This always polls every pollsecs seconds, but only checks for
318 * events when there are no connections.
320 static void * __attribute__ ((noreturn))
321 polling_thread (void *vp)
323 for (;;) {
325 ACQUIRE_LOCK_FOR_CURRENT_SCOPE (&lock);
326 if (connections == 0 && check_for_event ()) {
327 nbdkit_debug ("exitwhen: shutdown from polling thread");
328 nbdkit_shutdown ();
332 sleep (pollsecs);
336 /* Read command line parameters are build events list. */
337 static int
338 exitwhen_config (nbdkit_next_config *next, nbdkit_backend *nxdata,
339 const char *key, const char *value)
341 struct event event;
343 if (0) ;
344 #if defined (EVENT_FILE_CREATED) && defined (EVENT_FILE_DELETED)
345 else if (strcmp (key, "exit-when-file-created") == 0 ||
346 strcmp (key, "exit-when-file-deleted") == 0) {
347 char c = key[15];
349 assert (c == 'c' || c == 'd');
350 event.type = c == 'c' ? EVENT_FILE_CREATED : EVENT_FILE_DELETED;
351 event.u.filename = nbdkit_absolute_path (value);
352 if (event.u.filename == NULL)
353 return -1;
354 #ifdef EVENT_FD_CLOSED
355 append_event:
356 #endif
357 if (event_list_append (&events, event) == -1)
358 return -1;
359 return 0;
361 #endif
362 #ifdef EVENT_FD_CLOSED
363 else if (strcmp (key, "exit-when-pipe-closed") == 0 ||
364 strcmp (key, "exit-when-fd-closed") == 0) {
365 event.type = EVENT_FD_CLOSED;
366 if (nbdkit_parse_int ("exit-when-pipe-closed", value, &event.u.fd) == -1)
367 return -1;
368 goto append_event;
370 #endif
371 #ifdef EVENT_PROCESS_EXITS
372 else if (strcmp (key, "exit-when-process-exits") == 0 ||
373 strcmp (key, "exit-when-pid-exits") == 0) {
374 uint64_t pid;
375 #ifdef __linux__
376 CLEANUP_FREE char *str = NULL;
377 #endif
379 event.type = EVENT_PROCESS_EXITS;
380 if (nbdkit_parse_uint64_t ("exit-when-process-exits", value, &pid) == -1)
381 return -1;
382 #ifdef __linux__
383 /* See: https://gitlab.freedesktop.org/polkit/polkit/-/issues/75 */
384 if (asprintf (&str, "/proc/%" PRIu64 "/stat", pid) == -1) {
385 nbdkit_error ("asprintf: %m");
386 return -1;
388 event.u.fd = open (str, O_RDONLY);
389 if (event.u.fd == -1) {
390 nbdkit_error ("exit-when-process-exits: %s: %m", str);
391 return -1;
393 #else
394 event.u.pid = (pid_t) pid;
395 #endif
396 goto append_event;
398 #endif
399 #ifdef EVENT_SCRIPT
400 else if (strcmp (key, "exit-when-script") == 0) {
401 event.type = EVENT_SCRIPT;
402 event.u.filename = strdup (value);
403 if (event.u.filename == NULL) {
404 nbdkit_error ("strdup: %m");
405 return -1;
407 goto append_event;
409 #endif
411 /* This is a setting, not an event. */
412 if (strcmp (key, "exit-when-poll") == 0) {
413 if (nbdkit_parse_unsigned ("exit-when-poll", value, &pollsecs) == -1)
414 return -1;
415 return 0;
418 /* Otherwise pass the parameter to the plugin. */
419 return next (nxdata, key, value);
422 /* Before forking, run the check. If the event has already happened
423 * then we exit immediately.
425 static int
426 exitwhen_get_ready (int thread_model)
428 ACQUIRE_LOCK_FOR_CURRENT_SCOPE (&lock);
430 if (check_for_event ())
431 exit (EXIT_SUCCESS);
433 return 0;
436 /* After forking, start the background thread. Initially it is
437 * polling.
439 static int
440 exitwhen_after_fork (nbdkit_backend *nxdata)
442 int err;
443 pthread_t thread;
445 err = pthread_create (&thread, NULL, polling_thread, NULL);
446 if (err != 0) {
447 errno = err;
448 nbdkit_error ("pthread_create: %m");
449 return -1;
451 return 0;
454 static int
455 exitwhen_preconnect (nbdkit_next_preconnect *next, nbdkit_backend *nxdata,
456 int readonly)
458 ACQUIRE_LOCK_FOR_CURRENT_SCOPE (&lock);
460 if (check_for_event ()) {
461 nbdkit_error ("exitwhen: nbdkit is exiting: rejecting new connection");
462 return -1;
465 if (next (nxdata, readonly) == -1)
466 return -1;
468 return 0;
471 static void *
472 exitwhen_open (nbdkit_next_open *next, nbdkit_context *nxdata,
473 int readonly, const char *exportname, int is_tls)
475 if (next (nxdata, readonly, exportname) == -1)
476 return NULL;
478 ACQUIRE_LOCK_FOR_CURRENT_SCOPE (&lock);
479 connections++;
481 return NBDKIT_HANDLE_NOT_NEEDED;
484 static void
485 exitwhen_close (void *handle)
487 ACQUIRE_LOCK_FOR_CURRENT_SCOPE (&lock);
489 check_for_event ();
491 --connections;
492 if (connections == 0) {
493 if (exiting) {
494 nbdkit_debug ("exitwhen: exiting on last client connection");
495 nbdkit_shutdown ();
500 static struct nbdkit_filter filter = {
501 .name = "exitwhen",
502 .longname = "nbdkit exitwhen filter",
503 .unload = exitwhen_unload,
505 .config = exitwhen_config,
506 .get_ready = exitwhen_get_ready,
507 .after_fork = exitwhen_after_fork,
509 .preconnect = exitwhen_preconnect,
510 .open = exitwhen_open,
511 .close = exitwhen_close,
514 NBDKIT_REGISTER_FILTER (filter)