[Libguestfs] [PATCH nbdkit] New filter: exitwhen: exit gracefully when an event occurs.

Richard W.M. Jones rjones at redhat.com
Wed Oct 21 14:25:09 UTC 2020


---
 docs/nbdkit-captive.pod                       |   6 +-
 docs/nbdkit-service.pod                       |   1 +
 filters/exitlast/nbdkit-exitlast-filter.pod   |   3 +
 filters/exitwhen/nbdkit-exitwhen-filter.pod   | 156 ++++++
 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 +++
 tests/Makefile.am                             |  24 +
 filters/exitwhen/exitwhen.c                   | 473 ++++++++++++++++++
 tests/test-exitwhen-file-already-created.sh   |  45 ++
 .../test-exitwhen-file-created-reject-new.sh  |  88 ++++
 tests/test-exitwhen-file-created-when-idle.sh |  63 +++
 tests/test-exitwhen-file-created.sh           |  91 ++++
 tests/test-exitwhen-file-deleted.sh           |  68 +++
 tests/test-exitwhen-process-exits.sh          |  69 +++
 tests/test-exitwhen-script.sh                 |  70 +++
 tests/test-exitwhen-pipe-closed.c             |  81 +++
 .gitignore                                    |   1 +
 20 files changed, 1309 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..b9c51561
--- /dev/null
+++ b/filters/exitwhen/nbdkit-exitwhen-filter.pod
@@ -0,0 +1,156 @@
+=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 are able to modify the other
+process.
+
+=item B<exit-when-script=">SCRIPTB<">
+
+Create a custom event using the script or command C<SCRIPT>.  The
+C<SCRIPT> can be a program, shell script or a command with optional
+parameters.  Note if using a separate program or script that you may
+need to use the absolute path because nbdkit changes directory when it
+daemonizes.
+
+The script should exit with code 88 if the event is detected.  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
+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/tests/Makefile.am b/tests/Makefile.am
index ee342cc4..071ac3b5 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -1382,6 +1382,30 @@ EXTRA_DIST += \
 TESTS += test-exitlast.sh
 EXTRA_DIST += test-exitlast.sh
 
+# exitwhen filter test.
+check_PROGRAMS += test-exitwhen-pipe-closed
+TESTS += \
+	test-exitwhen-file-already-created.sh \
+	test-exitwhen-file-created.sh \
+	test-exitwhen-file-created-reject-new.sh \
+	test-exitwhen-file-created-when-idle.sh \
+	test-exitwhen-file-deleted.sh \
+	test-exitwhen-pipe-closed \
+	test-exitwhen-process-exits.sh \
+	test-exitwhen-script.sh \
+	$(NULL)
+EXTRA_DIST += \
+	test-exitwhen-file-already-created.sh \
+	test-exitwhen-file-created.sh \
+	test-exitwhen-file-created-reject-new.sh \
+	test-exitwhen-file-created-when-idle.sh \
+	test-exitwhen-file-deleted.sh \
+	test-exitwhen-process-exits.sh \
+	test-exitwhen-script.sh \
+	$(NULL)
+
+test_exitwhen_pipe_closed_CFLAGS = $(WARNINGS_CFLAGS)
+
 # exportname filter test.
 TESTS += test-exportname.sh
 EXTRA_DIST += test-exportname.sh
diff --git a/filters/exitwhen/exitwhen.c b/filters/exitwhen/exitwhen.c
new file mode 100644
index 00000000..801c355a
--- /dev/null
+++ b/filters/exitwhen/exitwhen.c
@@ -0,0 +1,473 @@
+/* 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 unsigned pollsecs = 60;
+
+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 void check_for_event_file_created (const struct event *);
+static void check_for_event_file_deleted (const struct event *);
+static void check_for_event_process_exits (const struct event *);
+static void check_for_event_fd_closed (const struct event *);
+static void check_for_event_script (const struct event *);
+
+static bool
+check_for_event (void)
+{
+  size_t i;
+
+  if (!exiting) {
+    for (i = 0; i < events.size; ++i) {
+      const struct event *event = &events.ptr[i];
+
+      switch (event->type) {
+      case EVENT_FILE_CREATED:
+        check_for_event_file_created (event);
+        break;
+      case EVENT_FILE_DELETED:
+        check_for_event_file_deleted (event);
+        break;
+      case EVENT_PROCESS_EXITS:
+        check_for_event_process_exits (event);
+        break;
+      case EVENT_FD_CLOSED:
+        check_for_event_fd_closed (event);
+        break;
+      case EVENT_SCRIPT:
+        check_for_event_script (event);
+        break;
+      }
+    }
+  }
+
+  return exiting;
+}
+
+static void
+check_for_event_file_created (const struct event *event)
+{
+  if (access (event->u.filename, R_OK) == 0) {
+    nbdkit_debug ("exit-when-file-created: detected %s created",
+                  event->u.filename);
+    exiting = true;
+  }
+}
+
+static void
+check_for_event_file_deleted (const struct event *event)
+{
+  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);
+    }
+  }
+}
+
+static void
+check_for_event_process_exits (const struct event *event)
+{
+  char c;
+
+#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__ */
+}
+
+static void
+check_for_event_fd_closed (const struct event *event)
+{
+  int r;
+  struct pollfd fds[1];
+
+  /* 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");
+  }
+}
+
+static void
+check_for_event_script (const struct event *event)
+{
+  int r;
+
+  /* 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");
+  }
+}
+
+/* The background polling thread.
+ *
+ * This runs continuously in the background, but you can pause it by
+ * grabbing the "pause_lock" (use the pause/resume_polling_thread()
+ * wrappers).
+ */
+static pthread_mutex_t pause_lock = PTHREAD_MUTEX_INITIALIZER;
+
+static void *
+polling_thread (void *vp)
+{
+  for (;;) {
+    {
+      /* Note the order here is chosen to avoid possible deadlock
+       * because the callers of pause/resume_polling_thread() always
+       * acquire &lock before &pause_lock.
+       */
+      ACQUIRE_LOCK_FOR_CURRENT_SCOPE (&lock);
+      ACQUIRE_LOCK_FOR_CURRENT_SCOPE (&pause_lock);
+      if (check_for_event ()) {
+        nbdkit_debug ("exitwhen: shutdown from polling thread");
+        nbdkit_shutdown ();
+      }
+    }
+
+    sleep (pollsecs);
+  }
+}
+
+static void
+pause_polling_thread (void)
+{
+  pthread_mutex_lock (&pause_lock);
+}
+
+static void
+resume_polling_thread (void)
+{
+  pthread_mutex_unlock (&pause_lock);
+}
+
+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 = strdup (value);
+    if (event.u.filename == NULL) {
+      nbdkit_error ("strdup: %m");
+      return -1;
+    }
+    if (event_list_append (&events, event) == -1)
+      return -1;
+    return 0;
+  }
+  else if (strcmp (key, "exit-when-poll") == 0) {
+    if (nbdkit_parse_unsigned ("exit-when-poll", value, &pollsecs) == -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)
diff --git a/tests/test-exitwhen-file-already-created.sh b/tests/test-exitwhen-file-already-created.sh
new file mode 100755
index 00000000..145864b5
--- /dev/null
+++ b/tests/test-exitwhen-file-already-created.sh
@@ -0,0 +1,45 @@
+#!/usr/bin/env bash
+# nbdkit
+# Copyright (C) 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.
+
+source ./functions.sh
+set -x
+
+requires_filter exitwhen
+
+eventfile=exitwhen-file-already-created.event
+rm -f $eventfile
+cleanup_fn rm -f $eventfile
+
+# nbdkit should exit immediately if the event file already exists.
+
+touch $eventfile
+nbdkit -fv -U - --filter=exitwhen null exit-when-file-created=$eventfile
diff --git a/tests/test-exitwhen-file-created-reject-new.sh b/tests/test-exitwhen-file-created-reject-new.sh
new file mode 100755
index 00000000..7736e067
--- /dev/null
+++ b/tests/test-exitwhen-file-created-reject-new.sh
@@ -0,0 +1,88 @@
+#!/usr/bin/env bash
+# nbdkit
+# Copyright (C) 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.
+
+source ./functions.sh
+set -x
+
+requires_filter exitwhen
+requires nbdsh --version
+
+sock=$(mktemp -u /tmp/nbdkit-test-sock.XXXXXX)
+pidfile=exitwhen-file-created-reject-new.pid
+eventfile=exitwhen-file-created-reject-new.event
+files="$pidfile $sock $eventfile"
+cleanup_fn rm -f $files
+
+# Start nbdkit with the exitwhen filter.
+start_nbdkit -P $pidfile -U $sock \
+             --filter=exitwhen memory size=1M \
+             exit-when-file-created=$eventfile
+
+# Connect and test.
+export eventfile sock
+nbdsh -c - <<'EOF'
+import os
+from pathlib import Path
+
+eventfile = os.environ['eventfile']
+sock = os.environ['sock']
+
+# Open a single connection.
+h.connect_unix(sock)
+
+# Check the connection is functional.
+buf = h.pread(512, 0)
+
+# Create the event.
+Path(eventfile).touch()
+
+# A new connection should be rejected.
+h2 = nbd.NBD()
+try:
+    h2.connect_unix(sock)
+    exit(1)
+except nbd.Error:
+    pass
+EOF
+
+# Now nbdkit should exit automatically.  Wait for it to do so.
+pid=`cat $pidfile`
+for i in {1..60}; do
+    if ! kill -s 0 $pid; then
+        break
+    fi
+    sleep 1
+done
+if kill -s 0 $pid; then
+    echo "$0: nbdkit did not exit after last client connection"
+    exit 1
+fi
diff --git a/tests/test-exitwhen-file-created-when-idle.sh b/tests/test-exitwhen-file-created-when-idle.sh
new file mode 100755
index 00000000..ff8a106b
--- /dev/null
+++ b/tests/test-exitwhen-file-created-when-idle.sh
@@ -0,0 +1,63 @@
+#!/usr/bin/env bash
+# nbdkit
+# Copyright (C) 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.
+
+source ./functions.sh
+set -x
+
+requires_filter exitwhen
+
+pidfile=exitwhen-file-created-when-idle.pid
+eventfile=exitwhen-file-created-when-idle.event
+rm -f $eventfile $pidfile
+cleanup_fn rm -f $eventfile $pidfile
+
+start_nbdkit -P $pidfile -U - \
+             --filter=exitwhen null \
+             exit-when-file-created=$eventfile \
+             exit-when-poll=1
+
+# Creating the file should cause nbdkit to exit after a short time.
+sleep 1
+touch $eventfile
+sleep 1
+
+pid=`cat $pidfile`
+for i in {1..60}; do
+    if ! kill -s 0 $pid; then
+        break
+    fi
+    sleep 1
+done
+if kill -s 0 $pid; then
+    echo "$0: nbdkit did not exit when idle"
+    exit 1
+fi
diff --git a/tests/test-exitwhen-file-created.sh b/tests/test-exitwhen-file-created.sh
new file mode 100755
index 00000000..ff62604d
--- /dev/null
+++ b/tests/test-exitwhen-file-created.sh
@@ -0,0 +1,91 @@
+#!/usr/bin/env bash
+# nbdkit
+# Copyright (C) 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.
+
+source ./functions.sh
+set -x
+
+requires_filter exitwhen
+requires nbdsh --version
+
+sock=$(mktemp -u /tmp/nbdkit-test-sock.XXXXXX)
+pidfile=exitwhen-file-created.pid
+eventfile=exitwhen-file-created.event
+files="$pidfile $sock $eventfile"
+cleanup_fn rm -f $files
+
+# Start nbdkit with the exitwhen filter.
+start_nbdkit -P $pidfile -U $sock \
+             --filter=exitwhen memory size=1M \
+             exit-when-file-created=$eventfile
+
+# Connect and test.
+export eventfile sock
+nbdsh -c - <<'EOF'
+import os
+from pathlib import Path
+
+eventfile = os.environ['eventfile']
+sock = os.environ['sock']
+
+# Open a couple of connections.
+h.connect_unix(sock)
+h2 = nbd.NBD()
+h2.connect_unix(sock)
+
+# The connections should both be functional.
+buf = h.pread(512, 0)
+buf = h2.pread(512, 0)
+
+# Create the event.
+Path(eventfile).touch()
+
+# The connections should still be functional.
+buf = h.pread(512, 0)
+h.shutdown()
+del h
+buf = h2.pread(512, 0)
+h2.shutdown()
+del h2
+EOF
+
+# Now nbdkit should exit automatically.  Wait for it to do so.
+pid=`cat $pidfile`
+for i in {1..60}; do
+    if ! kill -s 0 $pid; then
+        break
+    fi
+    sleep 1
+done
+if kill -s 0 $pid; then
+    echo "$0: nbdkit did not exit after last client connection"
+    exit 1
+fi
diff --git a/tests/test-exitwhen-file-deleted.sh b/tests/test-exitwhen-file-deleted.sh
new file mode 100755
index 00000000..2c0cafe2
--- /dev/null
+++ b/tests/test-exitwhen-file-deleted.sh
@@ -0,0 +1,68 @@
+#!/usr/bin/env bash
+# nbdkit
+# Copyright (C) 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.
+
+source ./functions.sh
+set -x
+
+requires_filter exitwhen
+requires nbdsh --version
+
+sock=$(mktemp -u /tmp/nbdkit-test-sock.XXXXXX)
+pidfile=exitwhen-file-deleted.pid
+eventfile=exitwhen-file-deleted.event
+files="$pidfile $sock $eventfile"
+cleanup_fn rm -f $files
+
+touch $eventfile
+
+# Start nbdkit with the exitwhen filter.
+start_nbdkit -P $pidfile -U $sock \
+             --filter=exitwhen memory size=1M \
+             exit-when-file-deleted=$eventfile \
+             exit-when-poll=1
+
+sleep 1
+rm $eventfile
+sleep 1
+
+# Now nbdkit should exit automatically.  Wait for it to do so.
+pid=`cat $pidfile`
+for i in {1..60}; do
+    if ! kill -s 0 $pid; then
+        break
+    fi
+    sleep 1
+done
+if kill -s 0 $pid; then
+    echo "$0: nbdkit did not exit after last client connection"
+    exit 1
+fi
diff --git a/tests/test-exitwhen-process-exits.sh b/tests/test-exitwhen-process-exits.sh
new file mode 100755
index 00000000..22a17b58
--- /dev/null
+++ b/tests/test-exitwhen-process-exits.sh
@@ -0,0 +1,69 @@
+#!/usr/bin/env bash
+# nbdkit
+# Copyright (C) 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.
+
+source ./functions.sh
+set -x
+
+requires_filter exitwhen
+requires nbdsh --version
+
+sock=$(mktemp -u /tmp/nbdkit-test-sock.XXXXXX)
+pidfile=exitwhen-process-exits.pid
+files="$pidfile $sock"
+cleanup_fn rm -f $files
+
+# Start the unrelated process.
+sleep 1h &
+sleeppid=$!
+
+# Start nbdkit with the exitwhen filter.
+start_nbdkit -P $pidfile -U $sock \
+             --filter=exitwhen memory size=1M \
+             exit-when-process-exits=$sleeppid \
+             exit-when-poll=1
+
+# Killing the sleep process should cause nbdkit to exit after a few
+# seconds.
+kill $sleeppid
+
+# Now nbdkit should exit automatically.  Wait for it to do so.
+pid=`cat $pidfile`
+for i in {1..60}; do
+    if ! kill -s 0 $pid; then
+        break
+    fi
+    sleep 1
+done
+if kill -s 0 $pid; then
+    echo "$0: nbdkit did not exit after last client connection"
+    exit 1
+fi
diff --git a/tests/test-exitwhen-script.sh b/tests/test-exitwhen-script.sh
new file mode 100755
index 00000000..43eeb58a
--- /dev/null
+++ b/tests/test-exitwhen-script.sh
@@ -0,0 +1,70 @@
+#!/usr/bin/env bash
+# nbdkit
+# Copyright (C) 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.
+
+source ./functions.sh
+set -x
+
+requires_filter exitwhen
+requires nbdsh --version
+
+sock=$(mktemp -u /tmp/nbdkit-test-sock.XXXXXX)
+pidfile=exitwhen-script.pid
+files="$pidfile $sock"
+cleanup_fn rm -f $files
+
+# Start the unrelated process.
+sleep 1h &
+sleeppid=$!
+
+# Start nbdkit with the exitwhen filter.
+# The command tests if the sleep process is still running.
+start_nbdkit -P $pidfile -U $sock \
+             --filter=exitwhen memory size=1M \
+             exit-when-script="kill -0 $sleeppid || exit 88" \
+             exit-when-poll=1
+
+# Killing the sleep process should cause nbdkit to exit after a few
+# seconds.
+kill $sleeppid
+
+# Now nbdkit should exit automatically.  Wait for it to do so.
+pid=`cat $pidfile`
+for i in {1..60}; do
+    if ! kill -s 0 $pid; then
+        break
+    fi
+    sleep 1
+done
+if kill -s 0 $pid; then
+    echo "$0: nbdkit did not exit after last client connection"
+    exit 1
+fi
diff --git a/tests/test-exitwhen-pipe-closed.c b/tests/test-exitwhen-pipe-closed.c
new file mode 100644
index 00000000..b14bacc4
--- /dev/null
+++ b/tests/test-exitwhen-pipe-closed.c
@@ -0,0 +1,81 @@
+/* nbdkit
+ * Copyright (C) 2017-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 <unistd.h>
+
+int
+main (int argc, char *argv[])
+{
+  char *param;
+  int fd[2];
+  pid_t pid;
+
+  if (pipe (fd) == -1) {
+    perror ("pipe");
+    exit (EXIT_FAILURE);
+  }
+  if (asprintf (&param, "exit-when-pipe-closed=%d", fd[0]) == -1) {
+    perror ("asprintf");
+    exit (EXIT_FAILURE);
+  }
+
+  /* Run nbdkit. */
+  pid = fork ();
+  if (pid == -1) {
+    perror ("fork");
+    exit (EXIT_FAILURE);
+  }
+  if (pid == 0) {               /* Child - run nbdkit. */
+    /* Close the write side of the pipe. */
+    close (fd[1]);
+
+    /* Run nbdkit. */
+    execlp ("nbdkit", "nbdkit", "-v", "--filter=exitwhen",
+            "null", "1M", param, "exit-when-poll=1",
+            NULL);
+    perror ("execvp");
+    _exit (EXIT_FAILURE);
+  }
+
+  /* Close the read side of the pipe. */
+  close (fd[0]);
+
+  /* The test here is simply that nbdkit exits because we exit and our
+   * side of the pipe is closed.
+   */
+  free (param);
+  exit (EXIT_SUCCESS);
+}
diff --git a/.gitignore b/.gitignore
index 6434f83d..f98c3e9a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -127,6 +127,7 @@ plugins/*/*.3
 /tests/test-data
 /tests/test-delay
 /tests/test-exit-with-parent
+/tests/test-exitwhen-pipe-closed
 /tests/test-ext2
 /tests/test-file-block
 /tests/test-golang
-- 
2.29.0.rc2




More information about the Libguestfs mailing list