Merge pull request #8707 from poettering/copy-tweaks

tweaks to fd copying code
This commit is contained in:
Zbigniew Jędrzejewski-Szmek
2018-04-13 12:10:17 +02:00
committed by GitHub
2 changed files with 194 additions and 35 deletions
+187 -31
View File
@@ -38,54 +38,142 @@
#define COPY_BUFFER_SIZE (16*1024u)
static ssize_t try_copy_file_range(int fd_in, loff_t *off_in,
int fd_out, loff_t *off_out,
size_t len,
unsigned int flags) {
static ssize_t try_copy_file_range(
int fd_in, loff_t *off_in,
int fd_out, loff_t *off_out,
size_t len,
unsigned int flags) {
static int have = -1;
ssize_t r;
if (have == false)
if (have == 0)
return -ENOSYS;
r = copy_file_range(fd_in, off_in, fd_out, off_out, len, flags);
if (_unlikely_(have < 0))
if (have < 0)
have = r >= 0 || errno != ENOSYS;
if (r >= 0)
return r;
else
if (r < 0)
return -errno;
return r;
}
int copy_bytes(int fdf, int fdt, uint64_t max_bytes, CopyFlags copy_flags) {
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,
CopyFlags copy_flags,
void **ret_remains,
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);
assert(fdt >= 0);
/* Try btrfs reflinks first. */
if ((copy_flags & COPY_REFLINK) &&
max_bytes == (uint64_t) -1 &&
lseek(fdf, 0, SEEK_CUR) == 0 &&
lseek(fdt, 0, SEEK_CUR) == 0) {
/* Tries to copy bytes from the file descriptor 'fdf' to 'fdt' in the smartest possible way. Copies a maximum
* of 'max_bytes', which may be specified as UINT64_MAX, in which no maximum is applied. Returns negative on
* error, zero if EOF is hit before the bytes limit is hit and positive otherwise. If the copy fails for some
* reason but we read but didn't yet write some data an ret_remains/ret_remains_size is not NULL, then it will
* be initialized with an allocated buffer containing this "remaining" data. Note that these two parameters are
* initialized with a valid buffer only on failure and only if there's actually data already read. Otherwise
* these parameters if non-NULL are set to NULL. */
r = btrfs_reflink(fdf, fdt);
if (r >= 0)
return 0; /* we copied the whole thing, hence hit EOF, return 0 */
if (ret_remains)
*ret_remains = NULL;
if (ret_remains_size)
*ret_remains_size = 0;
/* Try btrfs reflinks first. This only works on regular, seekable files, hence let's check the file offsets of
* source and destination first. */
if ((copy_flags & COPY_REFLINK)) {
off_t foffset;
foffset = lseek(fdf, 0, SEEK_CUR);
if (foffset >= 0) {
off_t toffset;
toffset = lseek(fdt, 0, SEEK_CUR);
if (toffset >= 0) {
if (foffset == 0 && toffset == 0 && max_bytes == UINT64_MAX)
r = btrfs_reflink(fdf, fdt); /* full file reflink */
else
r = btrfs_clone_range(fdf, foffset, fdt, toffset, max_bytes == UINT64_MAX ? 0 : max_bytes); /* partial reflink */
if (r >= 0) {
off_t t;
/* This worked, yay! Now — to be fully correct — let's adjust the file pointers */
if (max_bytes == UINT64_MAX) {
/* We cloned to the end of the source file, let's position the read
* pointer there, and query it at the same time. */
t = lseek(fdf, 0, SEEK_END);
if (t < 0)
return -errno;
if (t < foffset)
return -ESPIPE;
/* Let's adjust the destination file write pointer by the same number
* of bytes. */
t = lseek(fdt, toffset + (t - foffset), SEEK_SET);
if (t < 0)
return -errno;
return 0; /* we copied the whole thing, hence hit EOF, return 0 */
} else {
t = lseek(fdf, foffset + max_bytes, SEEK_SET);
if (t < 0)
return -errno;
t = lseek(fdt, toffset + max_bytes, SEEK_SET);
if (t < 0)
return -errno;
return 1; /* we copied only some number of bytes, which worked, but this means we didn't hit EOF, return 1 */
}
}
log_debug_errno(r, "Reflinking didn't work, falling back to non-reflink copying: %m");
}
}
}
for (;;) {
ssize_t n;
if (max_bytes != (uint64_t) -1) {
if (max_bytes <= 0)
return 1; /* return > 0 if we hit the max_bytes limit */
if (max_bytes <= 0)
return 1; /* return > 0 if we hit the max_bytes limit */
if (m > max_bytes)
m = max_bytes;
}
if (max_bytes != UINT64_MAX && m > max_bytes)
m = max_bytes;
/* First try copy_file_range(), unless we already tried */
if (try_cfr) {
@@ -119,9 +207,51 @@ int copy_bytes(int fdf, int fdt, uint64_t max_bytes, CopyFlags copy_flags) {
goto next;
}
/* Then try splice, unless we already tried */
/* Then try splice, unless we already tried. */
if (try_splice) {
n = splice(fdf, NULL, fdt, NULL, m, 0);
/* 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, nonblock_pipe ? SPLICE_F_NONBLOCK : 0);
if (n < 0) {
if (!IN_SET(errno, EINVAL, ENOSYS))
return -errno;
@@ -137,7 +267,8 @@ int copy_bytes(int fdf, int fdt, uint64_t max_bytes, CopyFlags copy_flags) {
/* As a fallback just copy bits by hand */
{
uint8_t buf[MIN(m, COPY_BUFFER_SIZE)];
uint8_t buf[MIN(m, COPY_BUFFER_SIZE)], *p = buf;
ssize_t z;
n = read(fdf, buf, sizeof buf);
if (n < 0)
@@ -145,9 +276,34 @@ int copy_bytes(int fdf, int fdt, uint64_t max_bytes, CopyFlags copy_flags) {
if (n == 0) /* EOF */
break;
r = loop_write(fdt, buf, (size_t) n, false);
if (r < 0)
return r;
z = (size_t) n;
do {
ssize_t k;
k = write(fdt, p, z);
if (k < 0) {
r = -errno;
if (ret_remains) {
void *copy;
copy = memdup(p, z);
if (!copy)
return -ENOMEM;
*ret_remains = copy;
}
if (ret_remains_size)
*ret_remains_size = z;
return r;
}
assert(k <= z);
z -= k;
p += k;
} while (z > 0);
}
next:
+7 -4
View File
@@ -13,9 +13,9 @@
#include <sys/types.h>
typedef enum CopyFlags {
COPY_REFLINK = 0x1, /* try to reflink */
COPY_MERGE = 0x2, /* merge existing trees with our new one to copy */
COPY_REPLACE = 0x4, /* replace an existing file if there's one */
COPY_REFLINK = 1U << 0, /* Try to reflink */
COPY_MERGE = 1U << 1, /* Merge existing trees with our new one to copy */
COPY_REPLACE = 1U << 2, /* Replace an existing file if there's one */
} CopyFlags;
int copy_file_fd(const char *from, int to, CopyFlags copy_flags);
@@ -25,6 +25,9 @@ int copy_tree(const char *from, const char *to, uid_t override_uid, gid_t overri
int copy_tree_at(int fdf, const char *from, int fdt, const char *to, uid_t override_uid, gid_t override_gid, CopyFlags copy_flags);
int copy_directory_fd(int dirfd, const char *to, CopyFlags copy_flags);
int copy_directory(const char *from, const char *to, CopyFlags copy_flags);
int copy_bytes(int fdf, int fdt, uint64_t max_bytes, CopyFlags copy_flags);
int copy_bytes_full(int fdf, int fdt, uint64_t max_bytes, CopyFlags copy_flags, void **ret_remains, size_t *ret_remains_size);
static inline int copy_bytes(int fdf, int fdt, uint64_t max_bytes, CopyFlags copy_flags) {
return copy_bytes_full(fdf, fdt, max_bytes, copy_flags, NULL, NULL);
}
int copy_times(int fdf, int fdt);
int copy_xattr(int fdf, int fdt);