As shown in the previous patch, plugins may choose to use stdin or stdout during .config. But from .get_ready onwards, well-written plugins shouldn't be needing any further use of stdin/out. We already swapped stdin/out to /dev/null while daemonizing, but did not do do during -f or --run, which leads to some surprising inconsistency when trying to debug a plugin that works in the foreground but fails when daemonized. What's more, while the task executed by --run should use the original stdin/out (and we even have tests that would fail without this), we don't want the plugin to interfere with whatever --run is doing. So it's time to move the swap over to /dev/null into a central location, right before .get_ready.
Note that if a plugin consumes some, but not all, input during .config, then we want --run to pick up with reading the rest of the input. For that to happen, we need to flush any buffered read-ahead data from stdin. POSIX says fflush(NULL) is supposed to be sufficient for the task, but in practice, glibc still has a bug that requires an additional fflush(stdin). Signed-off-by: Eric Blake <ebl...@redhat.com> --- server/internal.h | 2 ++ server/background.c | 14 +++++--------- server/captive.c | 10 ++++++++-- server/connections.c | 12 ------------ server/main.c | 38 +++++++++++++++++++++++++++++++++++++- 5 files changed, 52 insertions(+), 24 deletions(-) diff --git a/server/internal.h b/server/internal.h index 67eb6a32..79e1906c 100644 --- a/server/internal.h +++ b/server/internal.h @@ -132,6 +132,8 @@ extern bool tls_verify_peer; extern char *unixsocket; extern const char *user, *group; extern bool verbose; +extern int orig_in; +extern int orig_out; /* Linked list of backends. Each backend struct is followed by either * a filter or plugin struct. "top" points to the first one. They diff --git a/server/background.c b/server/background.c index 531ba470..72ab1ef6 100644 --- a/server/background.c +++ b/server/background.c @@ -1,5 +1,5 @@ /* nbdkit - * Copyright (C) 2019 Red Hat Inc. + * 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 @@ -70,15 +70,11 @@ fork_into_background (void) chdir ("/"); #pragma GCC diagnostic pop - /* Close stdin/stdout and redirect to /dev/null. */ - close (0); - close (1); - open ("/dev/null", O_RDONLY); - open ("/dev/null", O_WRONLY); - - /* If not verbose, set stderr to the same as stdout as well. */ + /* By this point, stdin/out have been redirected to /dev/null. + * If not verbose, set stderr to the same as stdout as well. + */ if (!verbose) - dup2 (1, 2); + dup2 (STDOUT_FILENO, STDERR_FILENO); forked_into_background = true; debug ("forked into background (new pid = %d)", getpid ()); diff --git a/server/captive.c b/server/captive.c index 0a243d2b..1ff10192 100644 --- a/server/captive.c +++ b/server/captive.c @@ -1,5 +1,5 @@ /* nbdkit - * Copyright (C) 2019 Red Hat Inc. + * 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 @@ -151,7 +151,13 @@ run_command (void) } if (pid > 0) { /* Parent process is the run command. */ - r = system (cmd); + /* Restore original stdin/out */ + if (dup2 (orig_in, STDIN_FILENO) == -1 || + dup2 (orig_out, STDOUT_FILENO) == -1) { + r = -1; + } + else + r = system (cmd); if (r == -1) { nbdkit_error ("failure to execute external command: %m"); r = EXIT_FAILURE; diff --git a/server/connections.c b/server/connections.c index c54c71c1..c7b55ca1 100644 --- a/server/connections.c +++ b/server/connections.c @@ -335,18 +335,6 @@ free_connection (struct connection *conn) return; conn->close (); - if (listen_stdin) { - int fd; - - /* Restore something to stdin/out so the rest of our code can - * continue to assume that all new fds will be above stderr. - * Swap directions to get EBADF on improper use of stdin/out. - */ - fd = open ("/dev/null", O_WRONLY | O_CLOEXEC); - assert (fd == 0); - fd = open ("/dev/null", O_RDONLY | O_CLOEXEC); - assert (fd == 1); - } /* Don't call the plugin again if quit has been set because the main * thread will be in the process of unloading it. The plugin.unload diff --git a/server/main.c b/server/main.c index 054e60b9..bbb416c3 100644 --- a/server/main.c +++ b/server/main.c @@ -101,6 +101,8 @@ const char *user, *group; /* -u & -g */ bool verbose; /* -v */ bool vsock; /* --vsock */ unsigned int socket_activation /* $LISTEN_FDS and $LISTEN_PID set */; +int orig_in = -1; /* dup'd stdin during -s/--run */ +int orig_out = -1; /* dup'd stdout during -s/--run */ /* The linked list of zero or more filters, and one plugin. */ struct backend *top; @@ -694,6 +696,40 @@ main (int argc, char *argv[]) top->config_complete (top); + /* Sanitize stdin/stdout to /dev/null, after saving the originals + * when needed. We are still single-threaded at this point, and + * already checked that stdin/out were open, so we don't have to + * worry about other threads accidentally grabbing our intended fds, + * or races on FD_CLOEXEC. POSIX says that 'fflush(NULL)' is + * supposed to reset the underlying offset of seekable stdin, but + * glibc is buggy and requires an explicit fflush(stdin) as + * well. https://sourceware.org/bugzilla/show_bug.cgi?id=12799 + */ + fflush (stdin); + fflush (NULL); + if (listen_stdin || run) { +#ifndef F_DUPFD_CLOEXEC +#define F_DUPFD_CLOEXEC F_DUPFD +#endif + orig_in = fcntl (STDIN_FILENO, F_DUPFD_CLOEXEC, STDERR_FILENO + 1); + orig_out = fcntl (STDOUT_FILENO, F_DUPFD_CLOEXEC, STDERR_FILENO + 1); +#if F_DUPFD == F_DUPFD_CLOEXEC + orig_in = set_cloexec (orig_in); + orig_out = set_cloexec (orig_out); +#endif + if (orig_in == -1 || orig_out == -1) { + perror ("fcntl"); + exit (EXIT_FAILURE); + } + } + close (STDIN_FILENO); + close (STDOUT_FILENO); + if (open ("/dev/null", O_RDONLY) != STDIN_FILENO || + open ("/dev/null", O_WRONLY) != STDOUT_FILENO) { + perror ("open"); + exit (EXIT_FAILURE); + } + /* Select the correct thread model based on config. */ lock_init_thread_model (); @@ -918,7 +954,7 @@ start_serving (void) change_user (); write_pidfile (); threadlocal_new_server_thread (); - handle_single_connection (0, 1); + handle_single_connection (orig_in, orig_out); return; } -- 2.26.0 _______________________________________________ Libguestfs mailing list Libguestfs@redhat.com https://www.redhat.com/mailman/listinfo/libguestfs