4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are
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
45 #include <sys/types.h>
47 #ifdef HAVE_SYS_WAIT_H
53 #include <nbdkit-filter.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. */
69 #define EVENT_FILE_CREATED 1
70 #define EVENT_FILE_DELETED 2
72 #define EVENT_PROCESS_EXITS 3
73 #define EVENT_FD_CLOSED 4
74 #define EVENT_SCRIPT 5
77 char *filename
; /* Filename or script. */
78 int fd
; /* For PROCESS_EXITS or FD_CLOSED. */
80 pid_t pid
; /* For PROCESS_EXITS on non-Linux. */
84 DEFINE_VECTOR_TYPE (event_list
, struct event
);
85 static event_list events
= empty_vector
;
88 free_event (struct event event
)
91 #ifdef EVENT_FILE_CREATED
92 case EVENT_FILE_CREATED
:
94 #ifdef EVENT_FILE_DELETED
95 case EVENT_FILE_DELETED
:
100 free (event
.u
.filename
);
102 #ifdef EVENT_PROCESS_EXITS
103 case EVENT_PROCESS_EXITS
:
105 #ifdef EVENT_FD_CLOSED
106 case EVENT_FD_CLOSED
:
116 exitwhen_unload (void)
118 event_list_iter (&events
, free_event
);
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
127 * This must be called with &lock held.
129 #ifdef EVENT_FILE_CREATED
130 static void check_for_event_file_created (const struct event
*);
132 #ifdef EVENT_FILE_DELETED
133 static void check_for_event_file_deleted (const struct event
*);
135 #ifdef EVENT_PROCESS_EXITS
136 static void check_for_event_process_exits (const struct event
*);
138 #ifdef EVENT_FD_CLOSED
139 static void check_for_event_fd_closed (const struct event
*);
142 static void check_for_event_script (const struct event
*);
146 check_for_event (void)
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
);
160 #ifdef EVENT_FILE_DELETED
161 case EVENT_FILE_DELETED
:
162 check_for_event_file_deleted (event
);
165 #ifdef EVENT_PROCESS_EXITS
166 case EVENT_PROCESS_EXITS
:
167 check_for_event_process_exits (event
);
170 #ifdef EVENT_FD_CLOSED
171 case EVENT_FD_CLOSED
:
172 check_for_event_fd_closed (event
);
177 check_for_event_script (event
);
187 #ifdef EVENT_FILE_CREATED
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",
199 #ifdef EVENT_FILE_DELETED
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",
210 /* Log the error but continue. */
211 nbdkit_error ("exit-when-file-deleted: access: %s: %m",
218 #ifdef EVENT_PROCESS_EXITS
220 check_for_event_process_exits (const struct event
*event
)
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");
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");
248 #endif /* !__linux__ */
252 #ifdef EVENT_FD_CLOSED
254 check_for_event_fd_closed (const struct event
*event
)
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
;
264 r
= poll (fds
, 1, 0);
266 if ((fds
[0].revents
& POLLHUP
) != 0) {
267 nbdkit_debug ("exit-when-pipe-closed: detected pipe closed");
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");
281 /* Log the error but continue. */
282 nbdkit_error ("exit-when-pipe-closed: poll: %m");
289 check_for_event_script (const struct event
*event
)
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
);
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");
309 /* Log the error but continue. */
310 exit_status_to_nbd_error (r
, "exit-when-script");
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
)
325 ACQUIRE_LOCK_FOR_CURRENT_SCOPE (&lock
);
326 if (connections
== 0 && check_for_event ()) {
327 nbdkit_debug ("exitwhen: shutdown from polling thread");
336 /* Read command line parameters are build events list. */
338 exitwhen_config (nbdkit_next_config
*next
, nbdkit_backend
*nxdata
,
339 const char *key
, const char *value
)
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) {
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
)
354 #ifdef EVENT_FD_CLOSED
357 if (event_list_append (&events
, event
) == -1)
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)
371 #ifdef EVENT_PROCESS_EXITS
372 else if (strcmp (key
, "exit-when-process-exits") == 0 ||
373 strcmp (key
, "exit-when-pid-exits") == 0) {
376 CLEANUP_FREE
char *str
= NULL
;
379 event
.type
= EVENT_PROCESS_EXITS
;
380 if (nbdkit_parse_uint64_t ("exit-when-process-exits", value
, &pid
) == -1)
383 /* See: https://gitlab.freedesktop.org/polkit/polkit/-/issues/75 */
384 if (asprintf (&str
, "/proc/%" PRIu64
"/stat", pid
) == -1) {
385 nbdkit_error ("asprintf: %m");
388 event
.u
.fd
= open (str
, O_RDONLY
);
389 if (event
.u
.fd
== -1) {
390 nbdkit_error ("exit-when-process-exits: %s: %m", str
);
394 event
.u
.pid
= (pid_t
) pid
;
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");
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)
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.
426 exitwhen_get_ready (int thread_model
)
428 ACQUIRE_LOCK_FOR_CURRENT_SCOPE (&lock
);
430 if (check_for_event ())
436 /* After forking, start the background thread. Initially it is
440 exitwhen_after_fork (nbdkit_backend
*nxdata
)
445 err
= pthread_create (&thread
, NULL
, polling_thread
, NULL
);
448 nbdkit_error ("pthread_create: %m");
455 exitwhen_preconnect (nbdkit_next_preconnect
*next
, nbdkit_backend
*nxdata
,
458 ACQUIRE_LOCK_FOR_CURRENT_SCOPE (&lock
);
460 if (check_for_event ()) {
461 nbdkit_error ("exitwhen: nbdkit is exiting: rejecting new connection");
465 if (next (nxdata
, readonly
) == -1)
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)
478 ACQUIRE_LOCK_FOR_CURRENT_SCOPE (&lock
);
481 return NBDKIT_HANDLE_NOT_NEEDED
;
485 exitwhen_close (void *handle
)
487 ACQUIRE_LOCK_FOR_CURRENT_SCOPE (&lock
);
492 if (connections
== 0) {
494 nbdkit_debug ("exitwhen: exiting on last client connection");
500 static struct nbdkit_filter filter
= {
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
)