Commit a419a1aabc

LemonBoy <thatlemon@gmail.com>
2020-10-06 09:38:59
Move copy_file to fs namespace
Now it is a private API. Also handle short writes in copy_file_range fallback implementation.
1 parent 8b4f5f0
Changed files (2)
lib
lib/std/fs.zig
@@ -1823,7 +1823,7 @@ pub const Dir = struct {
         var atomic_file = try dest_dir.atomicFile(dest_path, .{ .mode = mode });
         defer atomic_file.deinit();
 
-        try os.copy_file(in_file.handle, atomic_file.file.handle, .{});
+        try copy_file(in_file.handle, atomic_file.file.handle);
         return atomic_file.finish();
     }
 
@@ -2263,6 +2263,52 @@ pub fn realpathAlloc(allocator: *Allocator, pathname: []const u8) ![]u8 {
     return allocator.dupe(u8, try os.realpath(pathname, &buf));
 }
 
+const CopyFileError = error{SystemResources} || os.CopyFileRangeError || os.SendFileError;
+
+/// Transfer all the data between two file descriptors in the most efficient way.
+/// No metadata is transferred over.
+fn copy_file(fd_in: os.fd_t, fd_out: os.fd_t) CopyFileError!void {
+    if (comptime std.Target.current.isDarwin()) {
+        const rc = os.system.fcopyfile(fd_in, fd_out, null, os.system.COPYFILE_DATA);
+        switch (errno(rc)) {
+            0 => return,
+            EINVAL => unreachable,
+            ENOMEM => return error.SystemResources,
+            // The source file was not a directory, symbolic link, or regular file.
+            // Try with the fallback path before giving up.
+            ENOTSUP => {},
+            else => |err| return unexpectedErrno(err),
+        }
+    }
+
+    if (std.Target.current.os.tag == .linux) {
+        // Try copy_file_range first as that works at the FS level and is the
+        // most efficient method (if available).
+        var offset: u64 = 0;
+        cfr_loop: while (true) {
+            // The kernel checks `offset+count` for overflow, use a 32 bit
+            // value so that the syscall won't return EINVAL except for
+            // impossibly large files.
+            const amt = try os.copy_file_range(fd_in, offset, fd_out, offset, math.maxInt(u32), 0);
+            // Terminate when no data was copied
+            if (amt == 0) break :cfr_loop;
+            offset += amt;
+        }
+        return;
+    }
+
+    // Sendfile is a zero-copy mechanism iff the OS supports it, otherwise the
+    // fallback code will copy the contents chunk by chunk.
+    const empty_iovec = [0]os.iovec_const{};
+    var offset: u64 = 0;
+    sendfile_loop: while (true) {
+        const amt = try os.sendfile(fd_out, fd_in, offset, 0, &empty_iovec, &empty_iovec, 0);
+        // Terminate when no data was copied
+        if (amt == 0) break :sendfile_loop;
+        offset += amt;
+    }
+}
+
 test "" {
     if (builtin.os.tag != .wasi) {
         _ = makeDirAbsolute;
lib/std/os.zig
@@ -4945,6 +4945,7 @@ pub fn sendfile(
 pub const CopyFileRangeError = error{
     FileTooBig,
     InputOutput,
+    InvalidFileDescriptor,
     IsDir,
     OutOfMemory,
     NoSpaceLeft,
@@ -4978,6 +4979,11 @@ pub const CopyFileRangeError = error{
 /// Other systems fall back to calling `pread` / `pwrite`.
 ///
 /// Maximum offsets on Linux are `math.maxInt(i64)`.
+var has_copy_file_range_syscall = init: {
+    const kernel_has_syscall = comptime std.Target.current.os.isAtLeast(.linux, .{ .major = 4, .minor = 5 }) orelse true;
+    break :init std.atomic.Int(u1).init(@boolToInt(kernel_has_syscall));
+};
+
 pub fn copy_file_range(fd_in: fd_t, off_in: u64, fd_out: fd_t, off_out: u64, len: usize, flags: u32) CopyFileRangeError!usize {
     const use_c = std.c.versionCheck(.{ .major = 2, .minor = 27, .patch = 0 }).ok;
 
@@ -4992,7 +4998,7 @@ pub fn copy_file_range(fd_in: fd_t, off_in: u64, fd_out: fd_t, off_out: u64, len
         const rc = sys.copy_file_range(fd_in, &off_in_copy, fd_out, &off_out_copy, len, flags);
         switch (sys.getErrno(rc)) {
             0 => return @intCast(usize, rc),
-            EBADF => unreachable,
+            EBADF => return error.InvalidFileDescriptor,
             EFBIG => return error.FileTooBig,
             EIO => return error.InputOutput,
             EISDIR => return error.IsDir,
@@ -5013,96 +5019,24 @@ pub fn copy_file_range(fd_in: fd_t, off_in: u64, fd_out: fd_t, off_out: u64, len
         }
     }
 
-    var buf: [8 * 4096]u8 = undefined;
-    const adjusted_count = math.min(buf.len, len);
-    const amt_read = try pread(fd_in, buf[0..adjusted_count], off_in);
-    // TODO without @as the line below fails to compile for wasm32-wasi:
-    // error: integer value 0 cannot be coerced to type 'os.PWriteError!usize'
-    if (amt_read == 0) return @as(usize, 0);
-    return pwrite(fd_out, buf[0..amt_read], off_out);
-}
-
-var has_copy_file_range_syscall = std.atomic.Int(u1).init(1);
-
-pub const CopyFileOptions = struct {};
-
-pub const CopyFileError = error{
-    BadFileHandle,
-    SystemResources,
-    FileTooBig,
-    InputOutput,
-    IsDir,
-    OutOfMemory,
-    NoSpaceLeft,
-    Unseekable,
-    PermissionDenied,
-    FileBusy,
-} || FStatError || SendFileError;
+    var buf: [2 * 4096]u8 = undefined;
 
-/// Transfer all the data between two file descriptors in the most efficient way.
-/// No metadata is transferred over.
-pub fn copy_file(fd_in: fd_t, fd_out: fd_t, options: CopyFileOptions) CopyFileError!void {
-    if (comptime std.Target.current.isDarwin()) {
-        const rc = system.fcopyfile(fd_in, fd_out, null, system.COPYFILE_DATA);
-        switch (errno(rc)) {
-            0 => return,
-            EINVAL => unreachable,
-            ENOMEM => return error.SystemResources,
-            // The source file was not a directory, symbolic link, or regular file.
-            // Try with the fallback path before giving up.
-            ENOTSUP => {},
-            else => |err| return unexpectedErrno(err),
-        }
-    }
-
-    if (std.Target.current.os.tag == .linux) {
-        // Try copy_file_range first as that works at the FS level and is the
-        // most efficient method (if available).
-        if (has_copy_file_range_syscall.get() != 0) {
-            cfr_loop: while (true) {
-                // The kernel checks `file_pos+count` for overflow, use a 32 bit
-                // 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)) {
-                    0 => {},
-                    EBADF => return error.BadFileHandle,
-                    EFBIG => return error.FileTooBig,
-                    EIO => return error.InputOutput,
-                    EISDIR => return error.IsDir,
-                    ENOMEM => return error.OutOfMemory,
-                    ENOSPC => return error.NoSpaceLeft,
-                    EOVERFLOW => return error.Unseekable,
-                    EPERM => return error.PermissionDenied,
-                    ETXTBSY => return error.FileBusy,
-                    // These may not be regular files, try fallback
-                    EINVAL => break :cfr_loop,
-                    // Support for cross-filesystem copy added in Linux 5.3, use fallback
-                    EXDEV => break :cfr_loop,
-                    // Syscall added in Linux 4.5, use fallback
-                    ENOSYS => {
-                        has_copy_file_range_syscall.set(0);
-                        break :cfr_loop;
-                    },
-                    else => |err| return unexpectedErrno(err),
-                }
-                // Terminate when no data was copied
-                if (rc == 0) return;
-            }
-            // This point is reached when an error occurred, hopefully no data
-            // was transferred yet
-        }
+    var total_copied: usize = 0;
+    var read_off = off_in;
+    var write_off = off_out;
+    while (total_copied < len) {
+        const adjusted_count = math.min(buf.len, len - total_copied);
+        const amt_read = try pread(fd_in, buf[0..adjusted_count], read_off);
+        if (amt_read == 0) break;
+        const amt_written = try pwrite(fd_out, buf[0..amt_read], write_off);
+        // pwrite may write less than the specified amount, handle the remaining
+        // chunk of data in the next iteration
+        read_off += amt_written;
+        write_off += amt_written;
+        total_copied += amt_written;
     }
 
-    // Sendfile is a zero-copy mechanism iff the OS supports it, otherwise the
-    // fallback code will copy the contents chunk by chunk.
-    const empty_iovec = [0]iovec_const{};
-    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;
-    }
+    return total_copied;
 }
 
 pub const PollError = error{