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

Richard W.M. Jones rjones at redhat.com
Tue Oct 20 11:23:20 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 | 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




More information about the Libguestfs mailing list