---
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