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 <eblake(a)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