From: Zbigniew Jędrzejewski-Szmek Date: Mon, 21 Jun 2021 20:54:12 +0000 (+0200) Subject: basic: move acquire_data_fd() and fd_duplicate_data_fd() to new data-fd-util.c X-Git-Tag: v249-rc2~23^2~9 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=6a818c3cb4cba768dc42efa84db2fbd938f5def0;p=thirdparty%2Fsystemd.git basic: move acquire_data_fd() and fd_duplicate_data_fd() to new data-fd-util.c fd_duplicate_data_fd() is renamed to copy_data_fd(). This makes the two functions have nicely similar names. Now fd-util.[ch] is again about low-level file descriptor manipulations. copy_data_fd() is a complex function that internally wraps the other functions in copy.c. I want to move copy.c and the whole cluster of related code from basic/ to shared/ later on, and this is a preparatory step for that. --- diff --git a/src/basic/data-fd-util.c b/src/basic/data-fd-util.c new file mode 100644 index 00000000000..a827ef5a8bc --- /dev/null +++ b/src/basic/data-fd-util.c @@ -0,0 +1,350 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include +#include +#include +#include + +#include "alloc-util.h" +#include "copy.h" +#include "data-fd-util.h" +#include "fd-util.h" +#include "fs-util.h" +#include "io-util.h" +#include "memfd-util.h" +#include "tmpfile-util.h" + +/* When the data is smaller or equal to 64K, try to place the copy in a memfd/pipe */ +#define DATA_FD_MEMORY_LIMIT (64U*1024U) + +/* If memfd/pipe didn't work out, then let's use a file in /tmp up to a size of 1M. If it's large than that use /var/tmp instead. */ +#define DATA_FD_TMP_LIMIT (1024U*1024U) + +int acquire_data_fd(const void *data, size_t size, unsigned flags) { + _cleanup_close_pair_ int pipefds[2] = { -1, -1 }; + char pattern[] = "/dev/shm/data-fd-XXXXXX"; + _cleanup_close_ int fd = -1; + int isz = 0, r; + ssize_t n; + off_t f; + + assert(data || size == 0); + + /* Acquire a read-only file descriptor that when read from returns the specified data. This is much more + * complex than I wish it was. But here's why: + * + * a) First we try to use memfds. They are the best option, as we can seal them nicely to make them + * read-only. Unfortunately they require kernel 3.17, and – at the time of writing – we still support 3.14. + * + * b) Then, we try classic pipes. They are the second best options, as we can close the writing side, retaining + * a nicely read-only fd in the reading side. However, they are by default quite small, and unprivileged + * clients can only bump their size to a system-wide limit, which might be quite low. + * + * c) Then, we try an O_TMPFILE file in /dev/shm (that dir is the only suitable one known to exist from + * earliest boot on). To make it read-only we open the fd a second time with O_RDONLY via + * /proc/self/. Unfortunately O_TMPFILE is not available on older kernels on tmpfs. + * + * d) Finally, we try creating a regular file in /dev/shm, which we then delete. + * + * It sucks a bit that depending on the situation we return very different objects here, but that's Linux I + * figure. */ + + if (size == 0 && ((flags & ACQUIRE_NO_DEV_NULL) == 0)) { + /* As a special case, return /dev/null if we have been called for an empty data block */ + r = open("/dev/null", O_RDONLY|O_CLOEXEC|O_NOCTTY); + if (r < 0) + return -errno; + + return r; + } + + if ((flags & ACQUIRE_NO_MEMFD) == 0) { + fd = memfd_new("data-fd"); + if (fd < 0) + goto try_pipe; + + n = write(fd, data, size); + if (n < 0) + return -errno; + if ((size_t) n != size) + return -EIO; + + f = lseek(fd, 0, SEEK_SET); + if (f != 0) + return -errno; + + r = memfd_set_sealed(fd); + if (r < 0) + return r; + + return TAKE_FD(fd); + } + +try_pipe: + if ((flags & ACQUIRE_NO_PIPE) == 0) { + if (pipe2(pipefds, O_CLOEXEC|O_NONBLOCK) < 0) + return -errno; + + isz = fcntl(pipefds[1], F_GETPIPE_SZ, 0); + if (isz < 0) + return -errno; + + if ((size_t) isz < size) { + isz = (int) size; + if (isz < 0 || (size_t) isz != size) + return -E2BIG; + + /* Try to bump the pipe size */ + (void) fcntl(pipefds[1], F_SETPIPE_SZ, isz); + + /* See if that worked */ + isz = fcntl(pipefds[1], F_GETPIPE_SZ, 0); + if (isz < 0) + return -errno; + + if ((size_t) isz < size) + goto try_dev_shm; + } + + n = write(pipefds[1], data, size); + if (n < 0) + return -errno; + if ((size_t) n != size) + return -EIO; + + (void) fd_nonblock(pipefds[0], false); + + return TAKE_FD(pipefds[0]); + } + +try_dev_shm: + if ((flags & ACQUIRE_NO_TMPFILE) == 0) { + fd = open("/dev/shm", O_RDWR|O_TMPFILE|O_CLOEXEC, 0500); + if (fd < 0) + goto try_dev_shm_without_o_tmpfile; + + n = write(fd, data, size); + if (n < 0) + return -errno; + if ((size_t) n != size) + return -EIO; + + /* Let's reopen the thing, in order to get an O_RDONLY fd for the original O_RDWR one */ + return fd_reopen(fd, O_RDONLY|O_CLOEXEC); + } + +try_dev_shm_without_o_tmpfile: + if ((flags & ACQUIRE_NO_REGULAR) == 0) { + fd = mkostemp_safe(pattern); + if (fd < 0) + return fd; + + n = write(fd, data, size); + if (n < 0) { + r = -errno; + goto unlink_and_return; + } + if ((size_t) n != size) { + r = -EIO; + goto unlink_and_return; + } + + /* Let's reopen the thing, in order to get an O_RDONLY fd for the original O_RDWR one */ + r = open(pattern, O_RDONLY|O_CLOEXEC); + if (r < 0) + r = -errno; + + unlink_and_return: + (void) unlink(pattern); + return r; + } + + return -EOPNOTSUPP; +} + +int copy_data_fd(int fd) { + _cleanup_close_ int copy_fd = -1, tmp_fd = -1; + _cleanup_free_ void *remains = NULL; + size_t remains_size = 0; + const char *td; + struct stat st; + int r; + + /* Creates a 'data' fd from the specified source fd, containing all the same data in a read-only fashion, but + * independent of it (i.e. the source fd can be closed and unmounted after this call succeeded). Tries to be + * somewhat smart about where to place the data. In the best case uses a memfd(). If memfd() are not supported + * uses a pipe instead. For larger data will use an unlinked file in /tmp, and for even larger data one in + * /var/tmp. */ + + if (fstat(fd, &st) < 0) + return -errno; + + /* For now, let's only accept regular files, sockets, pipes and char devices */ + if (S_ISDIR(st.st_mode)) + return -EISDIR; + if (S_ISLNK(st.st_mode)) + return -ELOOP; + if (!S_ISREG(st.st_mode) && !S_ISSOCK(st.st_mode) && !S_ISFIFO(st.st_mode) && !S_ISCHR(st.st_mode)) + return -EBADFD; + + /* If we have reason to believe the data is bounded in size, then let's use memfds or pipes as backing fd. Note + * that we use the reported regular file size only as a hint, given that there are plenty special files in + * /proc and /sys which report a zero file size but can be read from. */ + + if (!S_ISREG(st.st_mode) || st.st_size < DATA_FD_MEMORY_LIMIT) { + + /* Try a memfd first */ + copy_fd = memfd_new("data-fd"); + if (copy_fd >= 0) { + off_t f; + + r = copy_bytes(fd, copy_fd, DATA_FD_MEMORY_LIMIT, 0); + if (r < 0) + return r; + + f = lseek(copy_fd, 0, SEEK_SET); + if (f != 0) + return -errno; + + if (r == 0) { + /* Did it fit into the limit? If so, we are done. */ + r = memfd_set_sealed(copy_fd); + if (r < 0) + return r; + + return TAKE_FD(copy_fd); + } + + /* Hmm, pity, this didn't fit. Let's fall back to /tmp then, see below */ + + } else { + _cleanup_(close_pairp) int pipefds[2] = { -1, -1 }; + int isz; + + /* If memfds aren't available, use a pipe. Set O_NONBLOCK so that we will get EAGAIN rather + * then block indefinitely when we hit the pipe size limit */ + + if (pipe2(pipefds, O_CLOEXEC|O_NONBLOCK) < 0) + return -errno; + + isz = fcntl(pipefds[1], F_GETPIPE_SZ, 0); + if (isz < 0) + return -errno; + + /* Try to enlarge the pipe size if necessary */ + if ((size_t) isz < DATA_FD_MEMORY_LIMIT) { + + (void) fcntl(pipefds[1], F_SETPIPE_SZ, DATA_FD_MEMORY_LIMIT); + + isz = fcntl(pipefds[1], F_GETPIPE_SZ, 0); + if (isz < 0) + return -errno; + } + + if ((size_t) isz >= DATA_FD_MEMORY_LIMIT) { + + r = copy_bytes_full(fd, pipefds[1], DATA_FD_MEMORY_LIMIT, 0, &remains, &remains_size, NULL, NULL); + if (r < 0 && r != -EAGAIN) + return r; /* If we get EAGAIN it could be because of the source or because of + * the destination fd, we can't know, as sendfile() and friends won't + * tell us. Hence, treat this as reason to fall back, just to be + * sure. */ + if (r == 0) { + /* Everything fit in, yay! */ + (void) fd_nonblock(pipefds[0], false); + + return TAKE_FD(pipefds[0]); + } + + /* Things didn't fit in. But we read data into the pipe, let's remember that, so that + * when writing the new file we incorporate this first. */ + copy_fd = TAKE_FD(pipefds[0]); + } + } + } + + /* If we have reason to believe this will fit fine in /tmp, then use that as first fallback. */ + if ((!S_ISREG(st.st_mode) || st.st_size < DATA_FD_TMP_LIMIT) && + (DATA_FD_MEMORY_LIMIT + remains_size) < DATA_FD_TMP_LIMIT) { + off_t f; + + tmp_fd = open_tmpfile_unlinkable(NULL /* NULL as directory means /tmp */, O_RDWR|O_CLOEXEC); + if (tmp_fd < 0) + return tmp_fd; + + if (copy_fd >= 0) { + /* If we tried a memfd/pipe first and it ended up being too large, then copy this into the + * temporary file first. */ + + r = copy_bytes(copy_fd, tmp_fd, UINT64_MAX, 0); + if (r < 0) + return r; + + assert(r == 0); + } + + if (remains_size > 0) { + /* If there were remaining bytes (i.e. read into memory, but not written out yet) from the + * failed copy operation, let's flush them out next. */ + + r = loop_write(tmp_fd, remains, remains_size, false); + if (r < 0) + return r; + } + + r = copy_bytes(fd, tmp_fd, DATA_FD_TMP_LIMIT - DATA_FD_MEMORY_LIMIT - remains_size, COPY_REFLINK); + if (r < 0) + return r; + if (r == 0) + goto finish; /* Yay, it fit in */ + + /* It didn't fit in. Let's not forget to use what we already used */ + f = lseek(tmp_fd, 0, SEEK_SET); + if (f != 0) + return -errno; + + CLOSE_AND_REPLACE(copy_fd, tmp_fd); + + remains = mfree(remains); + remains_size = 0; + } + + /* As last fallback use /var/tmp */ + r = var_tmp_dir(&td); + if (r < 0) + return r; + + tmp_fd = open_tmpfile_unlinkable(td, O_RDWR|O_CLOEXEC); + if (tmp_fd < 0) + return tmp_fd; + + if (copy_fd >= 0) { + /* If we tried a memfd/pipe first, or a file in /tmp, and it ended up being too large, than copy this + * into the temporary file first. */ + r = copy_bytes(copy_fd, tmp_fd, UINT64_MAX, COPY_REFLINK); + if (r < 0) + return r; + + assert(r == 0); + } + + if (remains_size > 0) { + /* Then, copy in any read but not yet written bytes. */ + r = loop_write(tmp_fd, remains, remains_size, false); + if (r < 0) + return r; + } + + /* Copy in the rest */ + r = copy_bytes(fd, tmp_fd, UINT64_MAX, COPY_REFLINK); + if (r < 0) + return r; + + assert(r == 0); + +finish: + /* Now convert the O_RDWR file descriptor into an O_RDONLY one (and as side effect seek to the beginning of the + * file again */ + + return fd_reopen(tmp_fd, O_RDONLY|O_CLOEXEC); +} diff --git a/src/basic/data-fd-util.h b/src/basic/data-fd-util.h new file mode 100644 index 00000000000..827e149662d --- /dev/null +++ b/src/basic/data-fd-util.h @@ -0,0 +1,7 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include + +int acquire_data_fd(const void *data, size_t size, unsigned flags); +int copy_data_fd(int fd); diff --git a/src/basic/fd-util.c b/src/basic/fd-util.c index ac6a37b5678..008f474344d 100644 --- a/src/basic/fd-util.c +++ b/src/basic/fd-util.c @@ -10,14 +10,12 @@ #include #include "alloc-util.h" -#include "copy.h" #include "dirent-util.h" #include "fd-util.h" #include "fileio.h" #include "fs-util.h" #include "io-util.h" #include "macro.h" -#include "memfd-util.h" #include "missing_fcntl.h" #include "missing_syscall.h" #include "parse-util.h" @@ -523,343 +521,6 @@ int move_fd(int from, int to, int cloexec) { return to; } -int acquire_data_fd(const void *data, size_t size, unsigned flags) { - - _cleanup_close_pair_ int pipefds[2] = { -1, -1 }; - char pattern[] = "/dev/shm/data-fd-XXXXXX"; - _cleanup_close_ int fd = -1; - int isz = 0, r; - ssize_t n; - off_t f; - - assert(data || size == 0); - - /* Acquire a read-only file descriptor that when read from returns the specified data. This is much more - * complex than I wish it was. But here's why: - * - * a) First we try to use memfds. They are the best option, as we can seal them nicely to make them - * read-only. Unfortunately they require kernel 3.17, and – at the time of writing – we still support 3.14. - * - * b) Then, we try classic pipes. They are the second best options, as we can close the writing side, retaining - * a nicely read-only fd in the reading side. However, they are by default quite small, and unprivileged - * clients can only bump their size to a system-wide limit, which might be quite low. - * - * c) Then, we try an O_TMPFILE file in /dev/shm (that dir is the only suitable one known to exist from - * earliest boot on). To make it read-only we open the fd a second time with O_RDONLY via - * /proc/self/. Unfortunately O_TMPFILE is not available on older kernels on tmpfs. - * - * d) Finally, we try creating a regular file in /dev/shm, which we then delete. - * - * It sucks a bit that depending on the situation we return very different objects here, but that's Linux I - * figure. */ - - if (size == 0 && ((flags & ACQUIRE_NO_DEV_NULL) == 0)) { - /* As a special case, return /dev/null if we have been called for an empty data block */ - r = open("/dev/null", O_RDONLY|O_CLOEXEC|O_NOCTTY); - if (r < 0) - return -errno; - - return r; - } - - if ((flags & ACQUIRE_NO_MEMFD) == 0) { - fd = memfd_new("data-fd"); - if (fd < 0) - goto try_pipe; - - n = write(fd, data, size); - if (n < 0) - return -errno; - if ((size_t) n != size) - return -EIO; - - f = lseek(fd, 0, SEEK_SET); - if (f != 0) - return -errno; - - r = memfd_set_sealed(fd); - if (r < 0) - return r; - - return TAKE_FD(fd); - } - -try_pipe: - if ((flags & ACQUIRE_NO_PIPE) == 0) { - if (pipe2(pipefds, O_CLOEXEC|O_NONBLOCK) < 0) - return -errno; - - isz = fcntl(pipefds[1], F_GETPIPE_SZ, 0); - if (isz < 0) - return -errno; - - if ((size_t) isz < size) { - isz = (int) size; - if (isz < 0 || (size_t) isz != size) - return -E2BIG; - - /* Try to bump the pipe size */ - (void) fcntl(pipefds[1], F_SETPIPE_SZ, isz); - - /* See if that worked */ - isz = fcntl(pipefds[1], F_GETPIPE_SZ, 0); - if (isz < 0) - return -errno; - - if ((size_t) isz < size) - goto try_dev_shm; - } - - n = write(pipefds[1], data, size); - if (n < 0) - return -errno; - if ((size_t) n != size) - return -EIO; - - (void) fd_nonblock(pipefds[0], false); - - return TAKE_FD(pipefds[0]); - } - -try_dev_shm: - if ((flags & ACQUIRE_NO_TMPFILE) == 0) { - fd = open("/dev/shm", O_RDWR|O_TMPFILE|O_CLOEXEC, 0500); - if (fd < 0) - goto try_dev_shm_without_o_tmpfile; - - n = write(fd, data, size); - if (n < 0) - return -errno; - if ((size_t) n != size) - return -EIO; - - /* Let's reopen the thing, in order to get an O_RDONLY fd for the original O_RDWR one */ - return fd_reopen(fd, O_RDONLY|O_CLOEXEC); - } - -try_dev_shm_without_o_tmpfile: - if ((flags & ACQUIRE_NO_REGULAR) == 0) { - fd = mkostemp_safe(pattern); - if (fd < 0) - return fd; - - n = write(fd, data, size); - if (n < 0) { - r = -errno; - goto unlink_and_return; - } - if ((size_t) n != size) { - r = -EIO; - goto unlink_and_return; - } - - /* Let's reopen the thing, in order to get an O_RDONLY fd for the original O_RDWR one */ - r = open(pattern, O_RDONLY|O_CLOEXEC); - if (r < 0) - r = -errno; - - unlink_and_return: - (void) unlink(pattern); - return r; - } - - return -EOPNOTSUPP; -} - -/* When the data is smaller or equal to 64K, try to place the copy in a memfd/pipe */ -#define DATA_FD_MEMORY_LIMIT (64U*1024U) - -/* If memfd/pipe didn't work out, then let's use a file in /tmp up to a size of 1M. If it's large than that use /var/tmp instead. */ -#define DATA_FD_TMP_LIMIT (1024U*1024U) - -int fd_duplicate_data_fd(int fd) { - - _cleanup_close_ int copy_fd = -1, tmp_fd = -1; - _cleanup_free_ void *remains = NULL; - size_t remains_size = 0; - const char *td; - struct stat st; - int r; - - /* Creates a 'data' fd from the specified source fd, containing all the same data in a read-only fashion, but - * independent of it (i.e. the source fd can be closed and unmounted after this call succeeded). Tries to be - * somewhat smart about where to place the data. In the best case uses a memfd(). If memfd() are not supported - * uses a pipe instead. For larger data will use an unlinked file in /tmp, and for even larger data one in - * /var/tmp. */ - - if (fstat(fd, &st) < 0) - return -errno; - - /* For now, let's only accept regular files, sockets, pipes and char devices */ - if (S_ISDIR(st.st_mode)) - return -EISDIR; - if (S_ISLNK(st.st_mode)) - return -ELOOP; - if (!S_ISREG(st.st_mode) && !S_ISSOCK(st.st_mode) && !S_ISFIFO(st.st_mode) && !S_ISCHR(st.st_mode)) - return -EBADFD; - - /* If we have reason to believe the data is bounded in size, then let's use memfds or pipes as backing fd. Note - * that we use the reported regular file size only as a hint, given that there are plenty special files in - * /proc and /sys which report a zero file size but can be read from. */ - - if (!S_ISREG(st.st_mode) || st.st_size < DATA_FD_MEMORY_LIMIT) { - - /* Try a memfd first */ - copy_fd = memfd_new("data-fd"); - if (copy_fd >= 0) { - off_t f; - - r = copy_bytes(fd, copy_fd, DATA_FD_MEMORY_LIMIT, 0); - if (r < 0) - return r; - - f = lseek(copy_fd, 0, SEEK_SET); - if (f != 0) - return -errno; - - if (r == 0) { - /* Did it fit into the limit? If so, we are done. */ - r = memfd_set_sealed(copy_fd); - if (r < 0) - return r; - - return TAKE_FD(copy_fd); - } - - /* Hmm, pity, this didn't fit. Let's fall back to /tmp then, see below */ - - } else { - _cleanup_(close_pairp) int pipefds[2] = { -1, -1 }; - int isz; - - /* If memfds aren't available, use a pipe. Set O_NONBLOCK so that we will get EAGAIN rather - * then block indefinitely when we hit the pipe size limit */ - - if (pipe2(pipefds, O_CLOEXEC|O_NONBLOCK) < 0) - return -errno; - - isz = fcntl(pipefds[1], F_GETPIPE_SZ, 0); - if (isz < 0) - return -errno; - - /* Try to enlarge the pipe size if necessary */ - if ((size_t) isz < DATA_FD_MEMORY_LIMIT) { - - (void) fcntl(pipefds[1], F_SETPIPE_SZ, DATA_FD_MEMORY_LIMIT); - - isz = fcntl(pipefds[1], F_GETPIPE_SZ, 0); - if (isz < 0) - return -errno; - } - - if ((size_t) isz >= DATA_FD_MEMORY_LIMIT) { - - r = copy_bytes_full(fd, pipefds[1], DATA_FD_MEMORY_LIMIT, 0, &remains, &remains_size, NULL, NULL); - if (r < 0 && r != -EAGAIN) - return r; /* If we get EAGAIN it could be because of the source or because of - * the destination fd, we can't know, as sendfile() and friends won't - * tell us. Hence, treat this as reason to fall back, just to be - * sure. */ - if (r == 0) { - /* Everything fit in, yay! */ - (void) fd_nonblock(pipefds[0], false); - - return TAKE_FD(pipefds[0]); - } - - /* Things didn't fit in. But we read data into the pipe, let's remember that, so that - * when writing the new file we incorporate this first. */ - copy_fd = TAKE_FD(pipefds[0]); - } - } - } - - /* If we have reason to believe this will fit fine in /tmp, then use that as first fallback. */ - if ((!S_ISREG(st.st_mode) || st.st_size < DATA_FD_TMP_LIMIT) && - (DATA_FD_MEMORY_LIMIT + remains_size) < DATA_FD_TMP_LIMIT) { - off_t f; - - tmp_fd = open_tmpfile_unlinkable(NULL /* NULL as directory means /tmp */, O_RDWR|O_CLOEXEC); - if (tmp_fd < 0) - return tmp_fd; - - if (copy_fd >= 0) { - /* If we tried a memfd/pipe first and it ended up being too large, then copy this into the - * temporary file first. */ - - r = copy_bytes(copy_fd, tmp_fd, UINT64_MAX, 0); - if (r < 0) - return r; - - assert(r == 0); - } - - if (remains_size > 0) { - /* If there were remaining bytes (i.e. read into memory, but not written out yet) from the - * failed copy operation, let's flush them out next. */ - - r = loop_write(tmp_fd, remains, remains_size, false); - if (r < 0) - return r; - } - - r = copy_bytes(fd, tmp_fd, DATA_FD_TMP_LIMIT - DATA_FD_MEMORY_LIMIT - remains_size, COPY_REFLINK); - if (r < 0) - return r; - if (r == 0) - goto finish; /* Yay, it fit in */ - - /* It didn't fit in. Let's not forget to use what we already used */ - f = lseek(tmp_fd, 0, SEEK_SET); - if (f != 0) - return -errno; - - CLOSE_AND_REPLACE(copy_fd, tmp_fd); - - remains = mfree(remains); - remains_size = 0; - } - - /* As last fallback use /var/tmp */ - r = var_tmp_dir(&td); - if (r < 0) - return r; - - tmp_fd = open_tmpfile_unlinkable(td, O_RDWR|O_CLOEXEC); - if (tmp_fd < 0) - return tmp_fd; - - if (copy_fd >= 0) { - /* If we tried a memfd/pipe first, or a file in /tmp, and it ended up being too large, than copy this - * into the temporary file first. */ - r = copy_bytes(copy_fd, tmp_fd, UINT64_MAX, COPY_REFLINK); - if (r < 0) - return r; - - assert(r == 0); - } - - if (remains_size > 0) { - /* Then, copy in any read but not yet written bytes. */ - r = loop_write(tmp_fd, remains, remains_size, false); - if (r < 0) - return r; - } - - /* Copy in the rest */ - r = copy_bytes(fd, tmp_fd, UINT64_MAX, COPY_REFLINK); - if (r < 0) - return r; - - assert(r == 0); - -finish: - /* Now convert the O_RDWR file descriptor into an O_RDONLY one (and as side effect seek to the beginning of the - * file again */ - - return fd_reopen(tmp_fd, O_RDONLY|O_CLOEXEC); -} - int fd_move_above_stdio(int fd) { int flags, copy; PROTECT_ERRNO; diff --git a/src/basic/fd-util.h b/src/basic/fd-util.h index eb696762b6a..9529a4723d3 100644 --- a/src/basic/fd-util.h +++ b/src/basic/fd-util.h @@ -76,10 +76,6 @@ enum { ACQUIRE_NO_REGULAR = 1 << 4, }; -int acquire_data_fd(const void *data, size_t size, unsigned flags); - -int fd_duplicate_data_fd(int fd); - 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/basic/meson.build b/src/basic/meson.build index 3685b639ae5..7cf2ff09ca6 100644 --- a/src/basic/meson.build +++ b/src/basic/meson.build @@ -37,6 +37,8 @@ basic_sources = files(''' copy.h creds-util.c creds-util.h + data-fd-util.c + data-fd-util.h def.h dirent-util.c dirent-util.h diff --git a/src/basic/terminal-util.c b/src/basic/terminal-util.c index ed0632b02b6..d769423d6e9 100644 --- a/src/basic/terminal-util.c +++ b/src/basic/terminal-util.c @@ -21,7 +21,6 @@ #include #include "alloc-util.h" -#include "copy.h" #include "def.h" #include "env-util.h" #include "fd-util.h" diff --git a/src/core/dbus-manager.c b/src/core/dbus-manager.c index 758dd8f1b8b..de057a02452 100644 --- a/src/core/dbus-manager.c +++ b/src/core/dbus-manager.c @@ -11,6 +11,7 @@ #include "bus-common-errors.h" #include "bus-get-properties.h" #include "bus-log-control-api.h" +#include "data-fd-util.h" #include "dbus-cgroup.h" #include "dbus-execute.h" #include "dbus-job.h" diff --git a/src/core/execute.c b/src/core/execute.c index f9d23272c4f..42d76a346db 100644 --- a/src/core/execute.c +++ b/src/core/execute.c @@ -46,6 +46,7 @@ #include "cgroup-setup.h" #include "chown-recursive.h" #include "cpu-set-util.h" +#include "data-fd-util.h" #include "def.h" #include "env-file.h" #include "env-util.h" diff --git a/src/home/homed-home.c b/src/home/homed-home.c index 39dd501a32e..104427dabe8 100644 --- a/src/home/homed-home.c +++ b/src/home/homed-home.c @@ -11,6 +11,7 @@ #include "blockdev-util.h" #include "btrfs-util.h" #include "bus-common-errors.h" +#include "data-fd-util.h" #include "env-util.h" #include "errno-list.h" #include "errno-util.h" diff --git a/src/oom/oomd-manager-bus.c b/src/oom/oomd-manager-bus.c index 4ea2a338fc8..b41e3663098 100644 --- a/src/oom/oomd-manager-bus.c +++ b/src/oom/oomd-manager-bus.c @@ -4,6 +4,7 @@ #include "bus-common-errors.h" #include "bus-polkit.h" +#include "data-fd-util.h" #include "fd-util.h" #include "oomd-manager-bus.h" #include "oomd-manager.h" diff --git a/src/portable/portable.c b/src/portable/portable.c index 9e33299ed51..a9ced3c865e 100644 --- a/src/portable/portable.c +++ b/src/portable/portable.c @@ -6,6 +6,7 @@ #include "bus-error.h" #include "conf-files.h" #include "copy.h" +#include "data-fd-util.h" #include "def.h" #include "dirent-util.h" #include "discover-image.h" @@ -153,7 +154,7 @@ static int send_item( assert(name); assert(fd >= 0); - data_fd = fd_duplicate_data_fd(fd); + data_fd = copy_data_fd(fd); if (data_fd < 0) return data_fd; diff --git a/src/test/meson.build b/src/test/meson.build index 29f488f4d86..03f08673bb9 100644 --- a/src/test/meson.build +++ b/src/test/meson.build @@ -159,6 +159,8 @@ tests += [ [['src/test/test-copy.c']], + [['src/test/test-data-fd-util.c']], + [['src/test/test-static-destruct.c']], [['src/test/test-sigbus.c']], diff --git a/src/test/test-data-fd-util.c b/src/test/test-data-fd-util.c new file mode 100644 index 00000000000..22a57b51559 --- /dev/null +++ b/src/test/test-data-fd-util.c @@ -0,0 +1,154 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include +#include +#include + +#include "data-fd-util.h" +#include "fd-util.h" +#include "memory-util.h" +#include "process-util.h" +#include "tests.h" +#include "random-util.h" + +static void test_acquire_data_fd_one(unsigned flags) { + char wbuffer[196*1024 - 7]; + char rbuffer[sizeof(wbuffer)]; + int fd; + + fd = acquire_data_fd("foo", 3, flags); + assert_se(fd >= 0); + + zero(rbuffer); + assert_se(read(fd, rbuffer, sizeof(rbuffer)) == 3); + assert_se(streq(rbuffer, "foo")); + + fd = safe_close(fd); + + fd = acquire_data_fd("", 0, flags); + assert_se(fd >= 0); + + zero(rbuffer); + assert_se(read(fd, rbuffer, sizeof(rbuffer)) == 0); + assert_se(streq(rbuffer, "")); + + fd = safe_close(fd); + + random_bytes(wbuffer, sizeof(wbuffer)); + + fd = acquire_data_fd(wbuffer, sizeof(wbuffer), flags); + assert_se(fd >= 0); + + zero(rbuffer); + assert_se(read(fd, rbuffer, sizeof(rbuffer)) == sizeof(rbuffer)); + assert_se(memcmp(rbuffer, wbuffer, sizeof(rbuffer)) == 0); + + fd = safe_close(fd); +} + +static void test_acquire_data_fd(void) { + test_acquire_data_fd_one(0); + test_acquire_data_fd_one(ACQUIRE_NO_DEV_NULL); + test_acquire_data_fd_one(ACQUIRE_NO_MEMFD); + test_acquire_data_fd_one(ACQUIRE_NO_DEV_NULL|ACQUIRE_NO_MEMFD); + test_acquire_data_fd_one(ACQUIRE_NO_PIPE); + test_acquire_data_fd_one(ACQUIRE_NO_DEV_NULL|ACQUIRE_NO_PIPE); + test_acquire_data_fd_one(ACQUIRE_NO_MEMFD|ACQUIRE_NO_PIPE); + test_acquire_data_fd_one(ACQUIRE_NO_DEV_NULL|ACQUIRE_NO_MEMFD|ACQUIRE_NO_PIPE); + test_acquire_data_fd_one(ACQUIRE_NO_DEV_NULL|ACQUIRE_NO_MEMFD|ACQUIRE_NO_PIPE|ACQUIRE_NO_TMPFILE); +} + +static void assert_equal_fd(int fd1, int fd2) { + for (;;) { + uint8_t a[4096], b[4096]; + ssize_t x, y; + + x = read(fd1, a, sizeof(a)); + assert_se(x >= 0); + + y = read(fd2, b, sizeof(b)); + assert_se(y >= 0); + + assert_se(x == y); + + if (x == 0) + break; + + assert_se(memcmp(a, b, x) == 0); + } +} + +static void test_copy_data_fd(void) { + _cleanup_close_ int fd1 = -1, fd2 = -1; + _cleanup_(close_pairp) int sfd[2] = { -1, -1 }; + _cleanup_(sigkill_waitp) pid_t pid = -1; + int r; + + fd1 = open("/etc/fstab", O_RDONLY|O_CLOEXEC); + if (fd1 >= 0) { + + fd2 = copy_data_fd(fd1); + assert_se(fd2 >= 0); + + assert_se(lseek(fd1, 0, SEEK_SET) == 0); + assert_equal_fd(fd1, fd2); + } + + fd1 = safe_close(fd1); + fd2 = safe_close(fd2); + + fd1 = acquire_data_fd("hallo", 6, 0); + assert_se(fd1 >= 0); + + fd2 = copy_data_fd(fd1); + assert_se(fd2 >= 0); + + safe_close(fd1); + fd1 = acquire_data_fd("hallo", 6, 0); + assert_se(fd1 >= 0); + + assert_equal_fd(fd1, fd2); + + fd1 = safe_close(fd1); + fd2 = safe_close(fd2); + + assert_se(socketpair(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0, sfd) >= 0); + + r = safe_fork("(sd-pipe)", FORK_RESET_SIGNALS|FORK_DEATHSIG|FORK_LOG, &pid); + assert_se(r >= 0); + + if (r == 0) { + /* child */ + + sfd[0] = safe_close(sfd[0]); + + for (uint64_t i = 0; i < 1536*1024 / sizeof(uint64_t); i++) + assert_se(write(sfd[1], &i, sizeof(i)) == sizeof(i)); + + sfd[1] = safe_close(sfd[1]); + + _exit(EXIT_SUCCESS); + } + + sfd[1] = safe_close(sfd[1]); + + fd2 = copy_data_fd(sfd[0]); + assert_se(fd2 >= 0); + + uint64_t j; + for (uint64_t i = 0; i < 1536*1024 / sizeof(uint64_t); i++) { + assert_se(read(fd2, &j, sizeof(j)) == sizeof(j)); + assert_se(i == j); + } + + assert_se(read(fd2, &j, sizeof(j)) == 0); +} + +int main(int argc, char *argv[]) { + test_setup_logging(LOG_DEBUG); + + test_acquire_data_fd(); + test_copy_data_fd(); + + return 0; +} diff --git a/src/test/test-fd-util.c b/src/test/test-fd-util.c index bece89aef25..1cd3bdfbbec 100644 --- a/src/test/test-fd-util.c +++ b/src/test/test-fd-util.c @@ -4,6 +4,7 @@ #include #include "alloc-util.h" +#include "data-fd-util.h" #include "fd-util.h" #include "fileio.h" #include "macro.h" @@ -95,54 +96,6 @@ static void test_open_serialization_fd(void) { assert_se(write(fd, "test\n", 5) == 5); } -static void test_acquire_data_fd_one(unsigned flags) { - char wbuffer[196*1024 - 7]; - char rbuffer[sizeof(wbuffer)]; - int fd; - - fd = acquire_data_fd("foo", 3, flags); - assert_se(fd >= 0); - - zero(rbuffer); - assert_se(read(fd, rbuffer, sizeof(rbuffer)) == 3); - assert_se(streq(rbuffer, "foo")); - - fd = safe_close(fd); - - fd = acquire_data_fd("", 0, flags); - assert_se(fd >= 0); - - zero(rbuffer); - assert_se(read(fd, rbuffer, sizeof(rbuffer)) == 0); - assert_se(streq(rbuffer, "")); - - fd = safe_close(fd); - - random_bytes(wbuffer, sizeof(wbuffer)); - - fd = acquire_data_fd(wbuffer, sizeof(wbuffer), flags); - assert_se(fd >= 0); - - zero(rbuffer); - assert_se(read(fd, rbuffer, sizeof(rbuffer)) == sizeof(rbuffer)); - assert_se(memcmp(rbuffer, wbuffer, sizeof(rbuffer)) == 0); - - fd = safe_close(fd); -} - -static void test_acquire_data_fd(void) { - - test_acquire_data_fd_one(0); - test_acquire_data_fd_one(ACQUIRE_NO_DEV_NULL); - test_acquire_data_fd_one(ACQUIRE_NO_MEMFD); - test_acquire_data_fd_one(ACQUIRE_NO_DEV_NULL|ACQUIRE_NO_MEMFD); - test_acquire_data_fd_one(ACQUIRE_NO_PIPE); - test_acquire_data_fd_one(ACQUIRE_NO_DEV_NULL|ACQUIRE_NO_PIPE); - test_acquire_data_fd_one(ACQUIRE_NO_MEMFD|ACQUIRE_NO_PIPE); - test_acquire_data_fd_one(ACQUIRE_NO_DEV_NULL|ACQUIRE_NO_MEMFD|ACQUIRE_NO_PIPE); - test_acquire_data_fd_one(ACQUIRE_NO_DEV_NULL|ACQUIRE_NO_MEMFD|ACQUIRE_NO_PIPE|ACQUIRE_NO_TMPFILE); -} - static void test_fd_move_above_stdio(void) { int original_stdin, new_fd; @@ -227,93 +180,6 @@ static void test_rearrange_stdio(void) { } } -static void assert_equal_fd(int fd1, int fd2) { - - for (;;) { - uint8_t a[4096], b[4096]; - ssize_t x, y; - - x = read(fd1, a, sizeof(a)); - assert_se(x >= 0); - - y = read(fd2, b, sizeof(b)); - assert_se(y >= 0); - - assert_se(x == y); - - if (x == 0) - break; - - assert_se(memcmp(a, b, x) == 0); - } -} - -static void test_fd_duplicate_data_fd(void) { - _cleanup_close_ int fd1 = -1, fd2 = -1; - _cleanup_(close_pairp) int sfd[2] = { -1, -1 }; - _cleanup_(sigkill_waitp) pid_t pid = -1; - uint64_t i, j; - int r; - - fd1 = open("/etc/fstab", O_RDONLY|O_CLOEXEC); - if (fd1 >= 0) { - - fd2 = fd_duplicate_data_fd(fd1); - assert_se(fd2 >= 0); - - assert_se(lseek(fd1, 0, SEEK_SET) == 0); - assert_equal_fd(fd1, fd2); - } - - fd1 = safe_close(fd1); - fd2 = safe_close(fd2); - - fd1 = acquire_data_fd("hallo", 6, 0); - assert_se(fd1 >= 0); - - fd2 = fd_duplicate_data_fd(fd1); - assert_se(fd2 >= 0); - - safe_close(fd1); - fd1 = acquire_data_fd("hallo", 6, 0); - assert_se(fd1 >= 0); - - assert_equal_fd(fd1, fd2); - - fd1 = safe_close(fd1); - fd2 = safe_close(fd2); - - assert_se(socketpair(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0, sfd) >= 0); - - r = safe_fork("(sd-pipe)", FORK_RESET_SIGNALS|FORK_DEATHSIG|FORK_LOG, &pid); - assert_se(r >= 0); - - if (r == 0) { - /* child */ - - sfd[0] = safe_close(sfd[0]); - - for (i = 0; i < 1536*1024 / sizeof(uint64_t); i++) - assert_se(write(sfd[1], &i, sizeof(i)) == sizeof(i)); - - sfd[1] = safe_close(sfd[1]); - - _exit(EXIT_SUCCESS); - } - - sfd[1] = safe_close(sfd[1]); - - fd2 = fd_duplicate_data_fd(sfd[0]); - assert_se(fd2 >= 0); - - for (i = 0; i < 1536*1024 / sizeof(uint64_t); i++) { - assert_se(read(fd2, &j, sizeof(j)) == sizeof(j)); - assert_se(i == j); - } - - assert_se(read(fd2, &j, sizeof(j)) == 0); -} - static void test_read_nr_open(void) { log_info("nr-open: %i", read_nr_open()); } @@ -420,10 +286,8 @@ int main(int argc, char *argv[]) { test_close_nointr(); test_same_fd(); test_open_serialization_fd(); - test_acquire_data_fd(); test_fd_move_above_stdio(); test_rearrange_stdio(); - test_fd_duplicate_data_fd(); test_read_nr_open(); test_close_all_fds();