Richard W.M. Jones
2020-Oct-20 11:23 UTC
[Libguestfs] [PATCH nbdkit INCOMPLETE] New filter: exitwhen: exit gracefully when an event occurs.
This incomplete patch adds a new filter allowing more control over when nbdkit exits. You can now get nbdkit to exit gracefully on certain events, such as a file being created, a pipe held open by another process going away, or when another PID exits. There is also a script option to allow for completely custom events. It is untested at the moment, I'm posting it to get feedback on the idea. The manual page is a complete description of the proposed feature. To do: - implement the polling thread - add tests Rich.
Richard W.M. Jones
2020-Oct-20 11:23 UTC
[Libguestfs] [PATCH nbdkit INCOMPLETE] New filter: exitwhen: exit gracefully when an event occurs.
--- docs/nbdkit-captive.pod | 6 +- docs/nbdkit-service.pod | 1 + filters/exitlast/nbdkit-exitlast-filter.pod | 3 + filters/exitwhen/nbdkit-exitwhen-filter.pod | 150 ++++++++ filters/ip/nbdkit-ip-filter.pod | 1 + filters/limit/nbdkit-limit-filter.pod | 1 + filters/rate/nbdkit-rate-filter.pod | 1 + configure.ac | 2 + filters/exitwhen/Makefile.am | 67 ++++ filters/exitwhen/exitwhen.c | 406 ++++++++++++++++++++ 10 files changed, 636 insertions(+), 2 deletions(-) diff --git a/docs/nbdkit-captive.pod b/docs/nbdkit-captive.pod index 472f1951..0de963bc 100644 --- a/docs/nbdkit-captive.pod +++ b/docs/nbdkit-captive.pod @@ -15,8 +15,9 @@ You can run nbdkit under another process and have nbdkit reliably clean up. There are two techniques depending on whether you want nbdkit to start the other process (L</CAPTIVE NBDKIT>), or if you want the other process to start nbdkit (L</EXIT WITH PARENT>). Another way -is to have nbdkit exit after the last client connection, see -L<nbdkit-exitlast-filter(1)>. +is to have nbdkit exit after the last client connection +(L<nbdkit-exitlast-filter(1)>) or after an event +(L<nbdkit-exitwhen-filter(1)>). =head1 CAPTIVE NBDKIT @@ -154,6 +155,7 @@ reliably on all operating systems). L<nbdkit(1)>, L<nbdkit-exitlast-filter(1)>, +L<nbdkit-exitwhen-filter(1)>, L<prctl(2)> (on Linux), L<procctl(2)> (on FreeBSD). diff --git a/docs/nbdkit-service.pod b/docs/nbdkit-service.pod index 42fbedd8..cd76ef52 100644 --- a/docs/nbdkit-service.pod +++ b/docs/nbdkit-service.pod @@ -145,6 +145,7 @@ L</SOCKET ACTIVATION>. L<nbdkit(1)>, L<nbdkit-client(1)>, L<nbdkit-exitlast-filter(1)>, +L<nbdkit-exitwhen-filter(1)>, L<nbdkit-ip-filter(1)>, L<nbdkit-limit-filter(1)>, L<systemd(1)>, diff --git a/filters/exitlast/nbdkit-exitlast-filter.pod b/filters/exitlast/nbdkit-exitlast-filter.pod index 207ad4c8..1791d2cf 100644 --- a/filters/exitlast/nbdkit-exitlast-filter.pod +++ b/filters/exitlast/nbdkit-exitlast-filter.pod @@ -17,6 +17,8 @@ resources when nbdkit is not in use (see L<nbdkit-service(1)>). Another use is to ensure nbdkit exits after the client has finished (but see also nbdkit-captive(1) for other ways to do this). +To exit when an event occurs, try L<nbdkit-exitwhen-filter(1)>. + =head1 PARAMETERS There are no parameters specific to nbdkit-exitlast-filter. Any @@ -42,6 +44,7 @@ C<nbdkit-exitlast-filter> first appeared in nbdkit 1.20. =head1 SEE ALSO L<nbdkit(1)>, +L<nbdkit-exitwhen-filter(1)>, L<nbdkit-ip-filter(1)>, L<nbdkit-limit-filter(1)>, L<nbdkit-rate-filter(1)>, diff --git a/filters/exitwhen/nbdkit-exitwhen-filter.pod b/filters/exitwhen/nbdkit-exitwhen-filter.pod new file mode 100644 index 00000000..5662da80 --- /dev/null +++ b/filters/exitwhen/nbdkit-exitwhen-filter.pod @@ -0,0 +1,150 @@ +=head1 NAME + +nbdkit-exitwhen-filter - exit gracefully when an event occurs + +=head1 SYNOPSIS + + nbdkit --filter=exitwhen PLUGIN + [exit-when-file-created=FILENAME] + [exit-when-file-deleted=FILENAME] + [exit-when-pipe-closed=FD] + [exit-when-process-exits=PID] + [exit-when-script=SCRIPT] + [exit-when-poll=SECS] + +=head1 DESCRIPTION + +C<nbdkit-exitwhen-filter> is an nbdkit filter that causes nbdkit to +exit gracefully when some external event occurs. Built-in events are: +a file being created or deleted, a pipe being closed, or a process +exiting. You can also define custom events using an external script +or command. + +After the event occurs nbdkit refuses new connections, waits for all +current clients to disconnect, and then exits. + +A similar filter is L<nbdkit-exitlast-filter(1)>. For other ways to +ensure that nbdkit exits when you want see L<nbdkit-captive(1)> and +L<nbdkit-service(1)>. + +=head1 EXAMPLES + + nbdkit --filter=exitwhen memory 1G exit-when-file-created=/tmp/stop + +nbdkit will run normally until something creates F</tmp/stop>, +whereupon nbdkit will refuse new connections and exit as soon as the +last client has disconnected. If F</tmp/stop> exists before nbdkit +starts, it will exit immediately. + + nbdkit --filter=exitwhen memory 1G exit-when-process-exits=1234 + +nbdkit will exit gracefully when PID 1234 exits and all connections +close. If you want to exit when the parent process of nbdkit exits, +consider using the I<--exit-with-parent> flag instead. + +=head1 PARAMETERS + +You can define multiple C<exit-when-*> events on the command line: +nbdkit will exit if any of the events happens. If there are no +C<exit-when-*> events then the filter does nothing. + +=over 4 + +=item B<exit-when-file-created=>FILENAME + +=item B<exit-when-file-deleted=>FILENAME + +Exit when the named file is created or deleted. + +=item B<exit-when-pipe-closed=>FD + +The read end of a L<pipe(2)> is passed to nbdkit in the given file +descriptor number. Exit when the pipe is closed. The filter does not +read any data from the pipe. + +=item B<exit-when-process-exits=>PID + +Exit when process ID C<PID> exits. + +Note there is a small race between passing the process ID to the +filter and the filter checking it for the first time. During this +window the original PID might exit and an unrelated program might get +the same PID, thus holding nbdkit open for longer than wanted. The +pipe method above is more reliable if you can modify the other +process. + +=item B<exit-when-script=>SCRIPT + +Create a custom event using the script or command C<SCRIPT>. The +filter does different things depending on the exit code of the script: + +=over 4 + +=item C<0> + +I<The event has not been triggered>, so nbdkit continues to process +requests as normal. + +=item C<1-87> + +An error is logged, but the event is I<not> triggered and nbdkit +continues to process requests as normal. + +=item C<88> + +I<The event has been triggered>. nbdkit will refuse new connections +and exit gracefully as soon as all current clients disconnect. + +=item C<89-> + +Exit codes 89 and above are reserved for future use. The behaviour of +nbdkit may change in future if scripts return any of these exit codes. + +=back + +=item B<exit-when-poll=>SECS + +When nbdkit is serving clients this filter does not need to poll +because it can check for events when a client connects or disconnects. +However when nbdkit is idle the filter needs to poll for events every +C<SECS> seconds and if any event happens exit immediately. + +The default is 60 seconds. + +=back + +=head1 FILES + +=over 4 + +=item F<$filterdir/nbdkit-exitwhen-filter.so> + +The filter. + +Use C<nbdkit --dump-config> to find the location of C<$filterdir>. + +=back + +=head1 VERSION + +C<nbdkit-exitwhen-filter> first appeared in nbdkit 1.24. + +=head1 SEE ALSO + +L<nbdkit(1)>, +L<nbdkit-exitlast-filter(1)>, +L<nbdkit-ip-filter(1)>, +L<nbdkit-limit-filter(1)>, +L<nbdkit-rate-filter(1)>, +L<nbdkit-captive(1)>, +L<nbdkit-service(1)>, +L<nbdkit-filter(3)>, +L<nbdkit-plugin(3)>. + +=head1 AUTHORS + +Richard W.M. Jones + +=head1 COPYRIGHT + +Copyright (C) 2020 Red Hat Inc. diff --git a/filters/ip/nbdkit-ip-filter.pod b/filters/ip/nbdkit-ip-filter.pod index 5d9e4616..48731a98 100644 --- a/filters/ip/nbdkit-ip-filter.pod +++ b/filters/ip/nbdkit-ip-filter.pod @@ -225,6 +225,7 @@ C<nbdkit-ip-filter> first appeared in nbdkit 1.18. L<nbdkit(1)>, L<nbdkit-exitlast-filter(1)>, +L<nbdkit-exitwhen-filter(1)>, L<nbdkit-limit-filter(1)>, L<nbdkit-filter(3)>. diff --git a/filters/limit/nbdkit-limit-filter.pod b/filters/limit/nbdkit-limit-filter.pod index 434dde5c..5d211047 100644 --- a/filters/limit/nbdkit-limit-filter.pod +++ b/filters/limit/nbdkit-limit-filter.pod @@ -46,6 +46,7 @@ C<nbdkit-limit-filter> first appeared in nbdkit 1.20. L<nbdkit(1)>, L<nbdkit-exitlast-filter(1)>, +L<nbdkit-exitwhen-filter(1)>, L<nbdkit-ip-filter(1)>, L<nbdkit-noparallel-filter(1)>, L<nbdkit-rate-filter(1)>, diff --git a/filters/rate/nbdkit-rate-filter.pod b/filters/rate/nbdkit-rate-filter.pod index 7aef4ec6..8956e641 100644 --- a/filters/rate/nbdkit-rate-filter.pod +++ b/filters/rate/nbdkit-rate-filter.pod @@ -130,6 +130,7 @@ L<nbdkit(1)>, L<nbdkit-blocksize-filter(1)>, L<nbdkit-delay-filter(1)>, L<nbdkit-exitlast-filter(1)>, +L<nbdkit-exitwhen-filter(1)>, L<nbdkit-limit-filter(1)>, L<nbdkit-pause-filter(1)>, L<nbdkit-filter(3)>, diff --git a/configure.ac b/configure.ac index b4302dfc..ce7209da 100644 --- a/configure.ac +++ b/configure.ac @@ -104,6 +104,7 @@ filters="\ delay \ error \ exitlast \ + exitwhen \ exportname \ ext2 \ extentlist \ @@ -1228,6 +1229,7 @@ AC_CONFIG_FILES([Makefile filters/delay/Makefile filters/error/Makefile filters/exitlast/Makefile + filters/exitwhen/Makefile filters/exportname/Makefile filters/ext2/Makefile filters/extentlist/Makefile diff --git a/filters/exitwhen/Makefile.am b/filters/exitwhen/Makefile.am new file mode 100644 index 00000000..dfef6abe --- /dev/null +++ b/filters/exitwhen/Makefile.am @@ -0,0 +1,67 @@ +# nbdkit +# Copyright (C) 2019-2020 Red Hat Inc. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# * Neither the name of Red Hat nor the names of its contributors may be +# used to endorse or promote products derived from this software without +# specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY RED HAT AND CONTRIBUTORS ''AS IS'' AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +# PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL RED HAT OR +# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF +# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +# OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +# SUCH DAMAGE. + +include $(top_srcdir)/common-rules.mk + +EXTRA_DIST = nbdkit-exitwhen-filter.pod + +filter_LTLIBRARIES = nbdkit-exitwhen-filter.la + +nbdkit_exitwhen_filter_la_SOURCES = \ + exitwhen.c \ + $(top_srcdir)/include/nbdkit-filter.h \ + $(NULL) + +nbdkit_exitwhen_filter_la_CPPFLAGS = \ + -I$(top_srcdir)/include \ + -I$(top_srcdir)/common/utils \ + $(NULL) +nbdkit_exitwhen_filter_la_CFLAGS = $(WARNINGS_CFLAGS) +nbdkit_exitwhen_filter_la_LIBADD = \ + $(top_builddir)/common/utils/libutils.la \ + $(IMPORT_LIBRARY_ON_WINDOWS) \ + $(NULL) +nbdkit_exitwhen_filter_la_LDFLAGS = \ + -module -avoid-version -shared $(NO_UNDEFINED_ON_WINDOWS) \ + -Wl,--version-script=$(top_srcdir)/filters/filters.syms \ + $(NULL) + +if HAVE_POD + +man_MANS = nbdkit-exitwhen-filter.1 +CLEANFILES += $(man_MANS) + +nbdkit-exitwhen-filter.1: nbdkit-exitwhen-filter.pod + $(PODWRAPPER) --section=1 --man $@ \ + --html $(top_builddir)/html/$@.html \ + $< + +endif HAVE_POD diff --git a/filters/exitwhen/exitwhen.c b/filters/exitwhen/exitwhen.c new file mode 100644 index 00000000..00881871 --- /dev/null +++ b/filters/exitwhen/exitwhen.c @@ -0,0 +1,406 @@ +/* nbdkit + * Copyright (C) 2019-2020 Red Hat Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of Red Hat nor the names of its contributors may be + * used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY RED HAT AND CONTRIBUTORS ''AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL RED HAT OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <config.h> + +#include <stdio.h> +#include <stdlib.h> +#include <stdbool.h> +#include <stdint.h> +#include <inttypes.h> +#include <unistd.h> +#include <fcntl.h> +#include <poll.h> +#include <errno.h> +#include <sys/types.h> + +#include <pthread.h> + +#include <nbdkit-filter.h> + +#include "cleanup.h" +#include "utils.h" +#include "vector.h" + +static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; +static unsigned connections = 0; +static bool exiting = false; + +/* The list of events generated from command line parameters. */ +struct event { + enum { EVENT_FILE_CREATED = 1, EVENT_FILE_DELETED, EVENT_PROCESS_EXITS, + EVENT_FD_CLOSED, EVENT_SCRIPT } type; + union { + char *filename; /* Filename or script. */ + int fd; /* For PROCESS_EXITS or FD_CLOSED. */ +#ifndef __linux__ + pid_t pid; /* For PROCESS_EXITS on non-Linux. */ +#endif + } u; +}; +DEFINE_VECTOR_TYPE(event_list, struct event); +static event_list events = empty_vector; + +static void +free_event (struct event event) +{ + switch (event.type) { + case EVENT_FILE_CREATED: + case EVENT_FILE_DELETED: + case EVENT_SCRIPT: + free (event.u.filename); + break; + case EVENT_PROCESS_EXITS: +#ifdef __linux__ + case EVENT_FD_CLOSED: +#endif + close (event.u.fd); + break; +#ifndef __linux__ + case EVENT_FD_CLOSED: + break; +#endif + } +} + +static void +exitwhen_unload (void) +{ + event_list_iter (&events, free_event); + free (events.ptr); +} + +/* If exiting is already true, this does nothing and returns true. + * Otherwise it checks if any event in the list has happened. If an + * event has happened, sets exiting to true. It returns the exiting + * flag. + * + * This must only be called with the lock held. + */ +static bool +check_for_event (void) +{ + size_t i; + char c; + struct pollfd fds[1]; + int r; + + if (exiting) + return true; + + for (i = 0; i < events.size; ++i) { + const struct event event = events.ptr[i]; + + switch (event.type) { + case EVENT_FILE_CREATED: + if (access (event.u.filename, R_OK) == 0) { + nbdkit_debug ("exit-when-file-created: detected %s created", + event.u.filename); + exiting = true; + } + break; + + case EVENT_FILE_DELETED: + if (access (event.u.filename, R_OK) == -1) { + if (errno == ENOTDIR || errno == ENOENT) { + nbdkit_debug ("exit-when-file-deleted: detected %s deleted", + event.u.filename); + exiting = true; + } + else { + /* Log the error but continue. */ + nbdkit_error ("exit-when-file-deleted: access: %s: %m", + event.u.filename); + } + } + break; + + case EVENT_PROCESS_EXITS: +#ifdef __linux__ + /* https://gitlab.freedesktop.org/polkit/polkit/-/issues/75 + * event.u.fd holds /proc/PID/stat of the original process open. + * If we can still read a byte from it then the original process + * is still around. If we get ESRCH then the process has + * exited. + */ + lseek (event.u.fd, 0, SEEK_SET); + if (read (event.u.fd, &c, 1) == -1) { + if (errno == ESRCH) { + nbdkit_debug ("exit-when-process-exits: detected process exit"); + exiting = true; + } + else { + /* Log the error but continue. */ + nbdkit_error ("exit-when-process-exits: read: %m"); + } + } +#else /* !__linux__ */ + /* XXX Find a safe way to do this on BSD at least. */ + if (kill (event.u.pid, 0) == -1 && errno == ESRCH) { + nbdkit_debug ("exit-when-process-exits: detected process exit"); + exiting = true; + } +#endif /* !__linux__ */ + break; + + case EVENT_FD_CLOSED: + /* event.u.fd is the read side of a pipe or socket. Check it is + * not closed. We don't actually read anything from the pipe. + */ + fds[0].fd = event.u.fd; + fds[0].events = 0; + r = poll (fds, 1, 0); + if (r == 1) { + if ((fds[0].revents & POLLHUP) != 0) { + nbdkit_debug ("exit-when-pipe-closed: detected pipe closed"); + exiting = true; + } + else if ((fds[0].revents & POLLNVAL) != 0) { + /* If we were passed a bad file descriptor that is user + * error and we should exit with an error early. Because + * check_for_event() is called first in get_ready() this + * should cause this to happen. + */ + nbdkit_error ("exit-when-pipe-closed: invalid file descriptor"); + exiting = true; + } + } + else if (r == -1) { + /* Log the error but continue. */ + nbdkit_error ("exit-when-pipe-closed: poll: %m"); + } + break; + + case EVENT_SCRIPT: + /* event.u.filename is a script filename or command. Exit code + * 88 indicates the event has happened. + */ + r = system (event.u.filename); + if (r == -1) { + /* Log the error but continue. */ + nbdkit_error ("exit-when-script: %m"); + } + else if (WIFEXITED (r) && WEXITSTATUS (r) == 0) { + /* Normal case, do nothing. */ + } + else if (WIFEXITED (r) && WEXITSTATUS (r) == 88) { + nbdkit_debug ("exit-when-script: detected scripted event"); + exiting = true; + } + else { + /* Log the error but continue. */ + exit_status_to_nbd_error (r, "exit-when-script"); + } + break; + } /* switch */ + } /* for */ + + return exiting; +} + +/* The background polling thread. */ +static void * +polling_thread (void *vp) +{ + for (;;) pause (); + return NULL; +} + +static void +pause_polling_thread (void) +{ +} + +static void +resume_polling_thread (void) +{ +} + +static int +exitwhen_config (nbdkit_next_config *next, void *nxdata, + const char *key, const char *value) +{ + struct event event; + + if (strcmp (key, "exit-when-file-created") == 0 || + strcmp (key, "exit-when-file-deleted") == 0) { + event.type = key[15] == 'c' ? EVENT_FILE_CREATED : EVENT_FILE_DELETED; + event.u.filename = nbdkit_absolute_path (value); + if (event.u.filename == NULL) + return -1; + if (event_list_append (&events, event) == -1) + return -1; + return 0; + } + else if (strcmp (key, "exit-when-pipe-closed") == 0 || + strcmp (key, "exit-when-fd-closed") == 0) { + event.type = EVENT_FD_CLOSED; + if (nbdkit_parse_int ("exit-when-pipe-closed", value, &event.u.fd) == -1) + return -1; + if (event_list_append (&events, event) == -1) + return -1; + return 0; + } + else if (strcmp (key, "exit-when-process-exits") == 0 || + strcmp (key, "exit-when-pid-exits") == 0) { + uint64_t pid; + CLEANUP_FREE char *str = NULL; + + event.type = EVENT_PROCESS_EXITS; + if (nbdkit_parse_uint64_t ("exit-when-process-exits", value, &pid) == -1) + return -1; +#ifdef __linux__ + /* See: https://gitlab.freedesktop.org/polkit/polkit/-/issues/75 */ + if (asprintf (&str, "/proc/%" PRIu64 "/stat", pid) == -1) { + nbdkit_error ("asprintf: %m"); + return -1; + } + event.u.fd = open (str, O_RDONLY); + if (event.u.fd == -1) { + nbdkit_error ("exit-when-process-exits: %s: %m", str); + return -1; + } +#else + event.u.pid = (pid_t) pid; +#endif + if (event_list_append (&events, event) == -1) + return -1; + return 0; + } + else if (strcmp (key, "exit-when-script") == 0) { + event.type = EVENT_SCRIPT; + event.u.filename = nbdkit_realpath (value); + if (event.u.filename == NULL) + return -1; + if (event_list_append (&events, event) == -1) + return -1; + return 0; + } + else + return next (nxdata, key, value); +} + +/* Before forking, run the check. If the event has already happened + * then we exit immediately. + */ +static int +exitwhen_get_ready (nbdkit_next_get_ready *next, void *nxdata, + int thread_model) +{ + ACQUIRE_LOCK_FOR_CURRENT_SCOPE (&lock); + + if (check_for_event ()) + exit (EXIT_SUCCESS); + + return next (nxdata); +} + +static int +exitwhen_after_fork (nbdkit_next_after_fork *next, void *nxdata) +{ + int err; + pthread_t thread; + + /* Start background polling thread. Initially it is running. */ + err = pthread_create (&thread, NULL, polling_thread, NULL); + if (err != 0) { + errno = err; + nbdkit_error ("pthread_create: %m"); + return -1; + } + return next (nxdata); +} + +static int +exitwhen_preconnect (nbdkit_next_preconnect *next, void *nxdata, int readonly) +{ + ACQUIRE_LOCK_FOR_CURRENT_SCOPE (&lock); + + if (check_for_event ()) { + nbdkit_error ("exitwhen: nbdkit is exiting: rejecting new connection"); + return -1; + } + + if (next (nxdata, readonly) == -1) + return -1; + + return 0; +} + +static void * +exitwhen_open (nbdkit_next_open *next, nbdkit_backend *nxdata, + int readonly, const char *exportname, int is_tls) +{ + if (next (nxdata, readonly, exportname) == -1) + return NULL; + + ACQUIRE_LOCK_FOR_CURRENT_SCOPE (&lock); + connections++; + if (connections == 1) + pause_polling_thread (); + + return NBDKIT_HANDLE_NOT_NEEDED; +} + +static void +exitwhen_close (void *handle) +{ + ACQUIRE_LOCK_FOR_CURRENT_SCOPE (&lock); + + check_for_event (); + + --connections; + if (connections == 0) { + if (exiting) { + nbdkit_debug ("exitwhen: exiting on last client connection"); + nbdkit_shutdown (); + } + else + resume_polling_thread (); + } +} + +static struct nbdkit_filter filter = { + .name = "exitwhen", + .longname = "nbdkit exitwhen filter", + .unload = exitwhen_unload, + + .config = exitwhen_config, + .get_ready = exitwhen_get_ready, + .after_fork = exitwhen_after_fork, + + .preconnect = exitwhen_preconnect, + .open = exitwhen_open, + .close = exitwhen_close, +}; + +NBDKIT_REGISTER_FILTER(filter) -- 2.29.0.rc2
Reasonably Related Threads
- [PATCH nbdkit] New filter: exitwhen: exit gracefully when an event occurs.
- [PATCH nbdkit 0/4] server: Add nbdkit_shutdown() call and two new filters.
- [PATCH nbdkit v2] New filter: limit: Limit number of clients that can connect.
- [nbdkit PATCH 0/3] Content differentiation during --tls=on
- [nbdkit PATCH 0/4] More .list_exports uses