Alternative strategy to avoid calling stat()

This is an optimization as it avoids an extra syscall, but it's also a
workaround for fstat being not available on Windows.
This commit is contained in:
LemonBoy 2020-10-03 19:51:22 +02:00
parent 0f248e0988
commit 8b4f5f039d
2 changed files with 22 additions and 19 deletions

View File

@ -1823,7 +1823,7 @@ pub const Dir = struct {
var atomic_file = try dest_dir.atomicFile(dest_path, .{ .mode = mode }); var atomic_file = try dest_dir.atomicFile(dest_path, .{ .mode = mode });
defer atomic_file.deinit(); defer atomic_file.deinit();
try os.copy_file(in_file.handle, atomic_file.file.handle, .{ .file_size = size }); try os.copy_file(in_file.handle, atomic_file.file.handle, .{});
return atomic_file.finish(); return atomic_file.finish();
} }

View File

@ -5024,12 +5024,10 @@ pub fn copy_file_range(fd_in: fd_t, off_in: u64, fd_out: fd_t, off_out: u64, len
var has_copy_file_range_syscall = std.atomic.Int(u1).init(1); var has_copy_file_range_syscall = std.atomic.Int(u1).init(1);
pub const CopyFileOptions = struct { pub const CopyFileOptions = struct {};
/// Size in bytes of the source files, if available saves a call to stat().
file_size: ?u64 = null,
};
pub const CopyFileError = error{ pub const CopyFileError = error{
BadFileHandle,
SystemResources, SystemResources,
FileTooBig, FileTooBig,
InputOutput, InputOutput,
@ -5057,20 +5055,18 @@ pub fn copy_file(fd_in: fd_t, fd_out: fd_t, options: CopyFileOptions) CopyFileEr
} }
} }
const src_file_size = options.file_size orelse
@bitCast(u64, (try fstat(fd_in)).size);
var remaining = src_file_size;
if (std.Target.current.os.tag == .linux) { if (std.Target.current.os.tag == .linux) {
// Try copy_file_range first as that works at the FS level and is the // Try copy_file_range first as that works at the FS level and is the
// most efficient method (if available). // most efficient method (if available).
if (has_copy_file_range_syscall.get() != 0) { if (has_copy_file_range_syscall.get() != 0) {
cfr_loop: while (remaining > 0) { cfr_loop: while (true) {
const copy_amt = math.cast(usize, remaining) catch math.maxInt(usize); // The kernel checks `file_pos+count` for overflow, use a 32 bit
const rc = linux.copy_file_range(fd_in, null, fd_out, null, copy_amt, 0); // value so that the syscall won't return EINVAL except for
// impossibly large files.
const rc = linux.copy_file_range(fd_in, null, fd_out, null, math.maxInt(u32), 0);
switch (errno(rc)) { switch (errno(rc)) {
0 => {}, 0 => {},
EBADF => unreachable, EBADF => return error.BadFileHandle,
EFBIG => return error.FileTooBig, EFBIG => return error.FileTooBig,
EIO => return error.InputOutput, EIO => return error.InputOutput,
EISDIR => return error.IsDir, EISDIR => return error.IsDir,
@ -5079,27 +5075,34 @@ pub fn copy_file(fd_in: fd_t, fd_out: fd_t, options: CopyFileOptions) CopyFileEr
EOVERFLOW => return error.Unseekable, EOVERFLOW => return error.Unseekable,
EPERM => return error.PermissionDenied, EPERM => return error.PermissionDenied,
ETXTBSY => return error.FileBusy, ETXTBSY => return error.FileBusy,
// these may not be regular files, try fallback // These may not be regular files, try fallback
EINVAL => break :cfr_loop, EINVAL => break :cfr_loop,
// support for cross-filesystem copy added in Linux 5.3, use fallback // Support for cross-filesystem copy added in Linux 5.3, use fallback
EXDEV => break :cfr_loop, EXDEV => break :cfr_loop,
// syscall added in Linux 4.5, use fallback // Syscall added in Linux 4.5, use fallback
ENOSYS => { ENOSYS => {
has_copy_file_range_syscall.set(0); has_copy_file_range_syscall.set(0);
break :cfr_loop; break :cfr_loop;
}, },
else => |err| return unexpectedErrno(err), else => |err| return unexpectedErrno(err),
} }
remaining -= rc; // Terminate when no data was copied
if (rc == 0) return;
} }
return; // This point is reached when an error occurred, hopefully no data
// was transferred yet
} }
} }
// Sendfile is a zero-copy mechanism iff the OS supports it, otherwise the // Sendfile is a zero-copy mechanism iff the OS supports it, otherwise the
// fallback code will copy the contents chunk by chunk. // fallback code will copy the contents chunk by chunk.
const empty_iovec = [0]iovec_const{}; const empty_iovec = [0]iovec_const{};
_ = try sendfile(fd_out, fd_in, 0, remaining, &empty_iovec, &empty_iovec, 0); var offset: u64 = 0;
sendfile_loop: while (true) {
const amt = try sendfile(fd_out, fd_in, offset, 0, &empty_iovec, &empty_iovec, 0);
if (amt == 0) break :sendfile_loop;
offset += amt;
}
} }
pub const PollError = error{ pub const PollError = error{