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.
This commit is contained in:
@@ -0,0 +1,350 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#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/<fd>. 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);
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
#pragma once
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
int acquire_data_fd(const void *data, size_t size, unsigned flags);
|
||||
int copy_data_fd(int fd);
|
||||
@@ -10,14 +10,12 @@
|
||||
#include <unistd.h>
|
||||
|
||||
#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/<fd>. 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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -21,7 +21,6 @@
|
||||
#include <unistd.h>
|
||||
|
||||
#include "alloc-util.h"
|
||||
#include "copy.h"
|
||||
#include "def.h"
|
||||
#include "env-util.h"
|
||||
#include "fd-util.h"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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']],
|
||||
|
||||
@@ -0,0 +1,154 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
|
||||
#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;
|
||||
}
|
||||
+1
-137
@@ -4,6 +4,7 @@
|
||||
#include <unistd.h>
|
||||
|
||||
#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();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user