diff options
-rw-r--r-- | src/basic/fd-util.c | 115 | ||||
-rw-r--r-- | src/basic/fd-util.h | 2 | ||||
-rw-r--r-- | src/test/test-fd-util.c | 69 |
3 files changed, 186 insertions, 0 deletions
diff --git a/src/basic/fd-util.c b/src/basic/fd-util.c index 61a93fcb4a7..fdf69476587 100644 --- a/src/basic/fd-util.c +++ b/src/basic/fd-util.c @@ -615,3 +615,118 @@ int fd_move_above_stdio(int fd) { (void) close(fd); return copy; } + +int rearrange_stdio(int original_input_fd, int original_output_fd, int original_error_fd) { + + int fd[3] = { /* Put together an array of fds we work on */ + original_input_fd, + original_output_fd, + original_error_fd + }; + + int r, i, + null_fd = -1, /* if we open /dev/null, we store the fd to it here */ + copy_fd[3] = { -1, -1, -1 }; /* This contains all fds we duplicate here temporarily, and hence need to close at the end */ + bool null_readable, null_writable; + + /* Sets up stdin, stdout, stderr with the three file descriptors passed in. If any of the descriptors is + * specified as -1 it will be connected with /dev/null instead. If any of the file descriptors is passed as + * itself (e.g. stdin as STDIN_FILENO) it is left unmodified, but the O_CLOEXEC bit is turned off should it be + * on. + * + * Note that if any of the passed file descriptors are > 2 they will be closed — both on success and on + * failure! Thus, callers should assume that when this function returns the input fds are invalidated. + * + * Note that when this function fails stdin/stdout/stderr might remain half set up! + * + * O_CLOEXEC is turned off for all three file descriptors (which is how it should be for + * stdin/stdout/stderr). */ + + null_readable = original_input_fd < 0; + null_writable = original_output_fd < 0 || original_error_fd < 0; + + /* First step, open /dev/null once, if we need it */ + if (null_readable || null_writable) { + + /* Let's open this with O_CLOEXEC first, and convert it to non-O_CLOEXEC when we move the fd to the final position. */ + null_fd = open("/dev/null", (null_readable && null_writable ? O_RDWR : + null_readable ? O_RDONLY : O_WRONLY) | O_CLOEXEC); + if (null_fd < 0) { + r = -errno; + goto finish; + } + + /* If this fd is in the 0…2 range, let's move it out of it */ + if (null_fd < 3) { + int copy; + + copy = fcntl(null_fd, F_DUPFD_CLOEXEC, 3); /* Duplicate this with O_CLOEXEC set */ + if (copy < 0) { + r = -errno; + goto finish; + } + + safe_close(null_fd); + null_fd = copy; + } + } + + /* Let's assemble fd[] with the fds to install in place of stdin/stdout/stderr */ + for (i = 0; i < 3; i++) { + + if (fd[i] < 0) + fd[i] = null_fd; /* A negative parameter means: connect this one to /dev/null */ + else if (fd[i] != i && fd[i] < 3) { + /* This fd is in the 0…2 territory, but not at its intended place, move it out of there, so that we can work there. */ + copy_fd[i] = fcntl(fd[i], F_DUPFD_CLOEXEC, 3); /* Duplicate this with O_CLOEXEC set */ + if (copy_fd[i] < 0) { + r = -errno; + goto finish; + } + + fd[i] = copy_fd[i]; + } + } + + /* At this point we now have the fds to use in fd[], and they are all above the stdio range, so that we + * have freedom to move them around. If the fds already were at the right places then the specific fds are + * -1. Let's now move them to the right places. This is the point of no return. */ + for (i = 0; i < 3; i++) { + + if (fd[i] == i) { + + /* fd is already in place, but let's make sure O_CLOEXEC is off */ + r = fd_cloexec(i, false); + if (r < 0) + goto finish; + + } else { + assert(fd[i] > 2); + + if (dup2(fd[i], i) < 0) { /* Turns off O_CLOEXEC on the new fd. */ + r = -errno; + goto finish; + } + } + } + + r = 0; + +finish: + /* Close the original fds, but only if they were outside of the stdio range. Also, properly check for the same + * fd passed in multiple times. */ + safe_close_above_stdio(original_input_fd); + if (original_output_fd != original_input_fd) + safe_close_above_stdio(original_output_fd); + if (original_error_fd != original_input_fd && original_error_fd != original_output_fd) + safe_close_above_stdio(original_error_fd); + + /* Close the copies we moved > 2 */ + for (i = 0; i < 3; i++) + safe_close(copy_fd[i]); + + /* Close our null fd, if it's > 2 */ + safe_close_above_stdio(null_fd); + + return r; +} diff --git a/src/basic/fd-util.h b/src/basic/fd-util.h index 4e8d9bc40a1..b687f1a5559 100644 --- a/src/basic/fd-util.h +++ b/src/basic/fd-util.h @@ -100,3 +100,5 @@ int acquire_data_fd(const void *data, size_t size, unsigned flags); IN_SET(r, ENOTCONN, ECONNRESET, ECONNREFUSED, ECONNABORTED, EPIPE, ENETUNREACH) int fd_move_above_stdio(int fd); + +int rearrange_stdio(int original_input_fd, int original_output_fd, int original_error_fd); diff --git a/src/test/test-fd-util.c b/src/test/test-fd-util.c index db4a7f8fda0..3f94df2eee3 100644 --- a/src/test/test-fd-util.c +++ b/src/test/test-fd-util.c @@ -25,6 +25,8 @@ #include "fd-util.h" #include "fileio.h" #include "macro.h" +#include "path-util.h" +#include "process-util.h" #include "random-util.h" #include "string-util.h" #include "util.h" @@ -173,6 +175,72 @@ static void test_fd_move_above_stdio(void) { assert_se(close_nointr(new_fd) != EBADF); } +static void test_rearrange_stdio(void) { + pid_t pid; + int r; + + r = safe_fork("rearrange", FORK_WAIT|FORK_LOG, &pid); + assert_se(r >= 0); + + if (r == 0) { + _cleanup_free_ char *path = NULL; + char buffer[10]; + + /* Child */ + + safe_close(STDERR_FILENO); /* Let's close an fd < 2, to make it more interesting */ + + assert_se(rearrange_stdio(-1, -1, -1) >= 0); + + assert_se(fd_get_path(STDIN_FILENO, &path) >= 0); + assert_se(path_equal(path, "/dev/null")); + path = mfree(path); + + assert_se(fd_get_path(STDOUT_FILENO, &path) >= 0); + assert_se(path_equal(path, "/dev/null")); + path = mfree(path); + + assert_se(fd_get_path(STDOUT_FILENO, &path) >= 0); + assert_se(path_equal(path, "/dev/null")); + path = mfree(path); + + safe_close(STDIN_FILENO); + safe_close(STDOUT_FILENO); + safe_close(STDERR_FILENO); + + { + int pair[2]; + assert_se(pipe(pair) >= 0); + assert_se(pair[0] == 0); + assert_se(pair[1] == 1); + assert_se(fd_move_above_stdio(0) == 3); + } + assert_se(open("/dev/full", O_WRONLY|O_CLOEXEC) == 0); + assert_se(acquire_data_fd("foobar", 6, 0) == 2); + + assert_se(rearrange_stdio(2, 0, 1) >= 0); + + assert_se(write(1, "x", 1) < 0 && errno == ENOSPC); + assert_se(write(2, "z", 1) == 1); + assert_se(read(3, buffer, sizeof(buffer)) == 1); + assert_se(buffer[0] == 'z'); + assert_se(read(0, buffer, sizeof(buffer)) == 6); + assert_se(memcmp(buffer, "foobar", 6) == 0); + + assert_se(rearrange_stdio(-1, 1, 2) >= 0); + assert_se(write(1, "a", 1) < 0 && errno == ENOSPC); + assert_se(write(2, "y", 1) == 1); + assert_se(read(3, buffer, sizeof(buffer)) == 1); + assert_se(buffer[0] == 'y'); + + assert_se(fd_get_path(0, &path) >= 0); + assert_se(path_equal(path, "/dev/null")); + path = mfree(path); + + _exit(EXIT_SUCCESS); + } +} + int main(int argc, char *argv[]) { test_close_many(); test_close_nointr(); @@ -180,6 +248,7 @@ int main(int argc, char *argv[]) { test_open_serialization_fd(); test_acquire_data_fd(); test_fd_move_above_stdio(); + test_rearrange_stdio(); return 0; } |