summaryrefslogtreecommitdiff
path: root/src/basic/copy.c
diff options
context:
space:
mode:
authorLennart Poettering <lennart@poettering.net>2018-03-23 17:44:15 +0100
committerLennart Poettering <lennart@poettering.net>2018-04-13 11:32:46 +0200
commite0c5c7d8fa07f44c2b694c809703cb8a59f0dfed (patch)
treeaa772b39dc987f9c054d6e4d99efb5c318119ebc /src/basic/copy.c
parent7a23c7fdfe82754bebecf82cdb7e1ecb44d6f23f (diff)
downloadsystemd-e0c5c7d8fa07f44c2b694c809703cb8a59f0dfed.tar.gz
systemd-e0c5c7d8fa07f44c2b694c809703cb8a59f0dfed.tar.bz2
systemd-e0c5c7d8fa07f44c2b694c809703cb8a59f0dfed.zip
copy: hide in copy_bytes() the strange way splice() handles O_NONBLOCK
splice() ignores O_NONBLOCK on pipes but not on other fds. Let's handle that properly, and query O_ONBLOCK manually in that case, ensuring systematic behaviour in either case.
Diffstat (limited to 'src/basic/copy.c')
-rw-r--r--src/basic/copy.c73
1 files changed, 70 insertions, 3 deletions
diff --git a/src/basic/copy.c b/src/basic/copy.c
index 15b004e1ae..18a245a92c 100644
--- a/src/basic/copy.c
+++ b/src/basic/copy.c
@@ -57,6 +57,31 @@ static ssize_t try_copy_file_range(int fd_in, loff_t *off_in,
return -errno;
}
+enum {
+ FD_IS_NO_PIPE,
+ FD_IS_BLOCKING_PIPE,
+ FD_IS_NONBLOCKING_PIPE,
+};
+
+static int fd_is_nonblock_pipe(int fd) {
+ struct stat st;
+ int flags;
+
+ /* Checks whether the specified file descriptor refers to a pipe, and if so if is has O_NONBLOCK set. */
+
+ if (fstat(fd, &st) < 0)
+ return -errno;
+
+ if (!S_ISFIFO(st.st_mode))
+ return FD_IS_NO_PIPE;
+
+ flags = fcntl(fd, F_GETFL);
+ if (flags < 0)
+ return -errno;
+
+ return (flags & O_NONBLOCK) == O_NONBLOCK ? FD_IS_NONBLOCKING_PIPE : FD_IS_BLOCKING_PIPE;
+}
+
int copy_bytes_full(
int fdf, int fdt,
uint64_t max_bytes,
@@ -65,7 +90,7 @@ int copy_bytes_full(
size_t *ret_remains_size) {
bool try_cfr = true, try_sendfile = true, try_splice = true;
- int r;
+ int r, nonblock_pipe = -1;
size_t m = SSIZE_MAX; /* that is the maximum that sendfile and c_f_r accept */
assert(fdf >= 0);
@@ -182,9 +207,51 @@ int copy_bytes_full(
goto next;
}
- /* Then try splice, unless we already tried */
+ /* Then try splice, unless we already tried. */
+ if (try_splice) {
+
+ /* splice()'s asynchronous I/O support is a bit weird. When it encounters a pipe file
+ * descriptor, then it will ignore its O_NONBLOCK flag and instead only honour the
+ * SPLICE_F_NONBLOCK flag specified in its flag parameter. Let's hide this behaviour here, and
+ * check if either of the specified fds are a pipe, and if so, let's pass the flag
+ * automatically, depending on O_NONBLOCK being set.
+ *
+ * Here's a twist though: when we use it to move data between two pipes of which one has
+ * O_NONBLOCK set and the other has not, then we have no individual control over O_NONBLOCK
+ * behaviour. Hence in that case we can't use splice() and still guarantee systematic
+ * O_NONBLOCK behaviour, hence don't. */
+
+ if (nonblock_pipe < 0) {
+ int a, b;
+
+ /* Check if either of these fds is a pipe, and if so non-blocking or not */
+ a = fd_is_nonblock_pipe(fdf);
+ if (a < 0)
+ return a;
+
+ b = fd_is_nonblock_pipe(fdt);
+ if (b < 0)
+ return b;
+
+ if ((a == FD_IS_NO_PIPE && b == FD_IS_NO_PIPE) ||
+ (a == FD_IS_BLOCKING_PIPE && b == FD_IS_NONBLOCKING_PIPE) ||
+ (a == FD_IS_NONBLOCKING_PIPE && b == FD_IS_BLOCKING_PIPE))
+
+ /* splice() only works if one of the fds is a pipe. If neither is, let's skip
+ * this step right-away. As mentioned above, if one of the two fds refers to a
+ * blocking pipe and the other to a non-blocking pipe, we can't use splice()
+ * either, hence don't try either. This hence means we can only use splice() if
+ * either only one of the two fds is a pipe, or if both are pipes with the same
+ * nonblocking flag setting. */
+
+ try_splice = false;
+ else
+ nonblock_pipe = a == FD_IS_NONBLOCKING_PIPE || b == FD_IS_NONBLOCKING_PIPE;
+ }
+ }
+
if (try_splice) {
- n = splice(fdf, NULL, fdt, NULL, m, 0);
+ n = splice(fdf, NULL, fdt, NULL, m, nonblock_pipe ? SPLICE_F_NONBLOCK : 0);
if (n < 0) {
if (!IN_SET(errno, EINVAL, ENOSYS))
return -errno;