Richard W.M. Jones
2020-Oct-21 14:25 UTC
[Libguestfs] [PATCH nbdkit] New filter: exitwhen: exit gracefully when an event occurs.
--- docs/nbdkit-captive.pod | 6 +- docs/nbdkit-service.pod | 1 + filters/exitlast/nbdkit-exitlast-filter.pod | 3 + filters/exitwhen/nbdkit-exitwhen-filter.pod | 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 (¶m, "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
Possibly Parallel Threads
- [PATCH nbdkit 3/4] New filter: exitlast filter to exit on last client connection.
- [PATCH nbdkit v2] New filter: limit: Limit number of clients that can connect.
- [PATCH nbdkit INCOMPLETE] New filter: exitwhen: exit gracefully when an event occurs.
- [nbdkit PATCH 1/3] server: Implement nbdkit_is_tls for use during .open
- Re: [PATCH nbdkit v2] New filter: limit: Limit number of clients that can connect.