| ... | ... | @@ -4945,6 +4945,7 @@ pub fn sendfile( |
| 4945 | 4945 | pub const CopyFileRangeError = error{ |
| 4946 | 4946 | FileTooBig, |
| 4947 | 4947 | InputOutput, |
| 4948 | InvalidFileDescriptor, |
| 4948 | 4949 | IsDir, |
| 4949 | 4950 | OutOfMemory, |
| 4950 | 4951 | NoSpaceLeft, |
| ... | ... | @@ -4978,6 +4979,11 @@ pub const CopyFileRangeError = error{ |
| 4978 | 4979 | /// Other systems fall back to calling `pread` / `pwrite`. |
| 4979 | 4980 | /// |
| 4980 | 4981 | /// Maximum offsets on Linux are `math.maxInt(i64)`. |
| 4982 | var has_copy_file_range_syscall = init: { |
| 4983 | const kernel_has_syscall = comptime std.Target.current.os.isAtLeast(.linux, .{ .major = 4, .minor = 5 }) orelse true; |
| 4984 | break :init std.atomic.Int(u1).init(@boolToInt(kernel_has_syscall)); |
| 4985 | }; |
| 4986 | |
| 4981 | 4987 | 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 { |
| 4982 | 4988 | const use_c = std.c.versionCheck(.{ .major = 2, .minor = 27, .patch = 0 }).ok; |
| 4983 | 4989 | |
| ... | ... | @@ -4992,7 +4998,7 @@ pub fn copy_file_range(fd_in: fd_t, off_in: u64, fd_out: fd_t, off_out: u64, len |
| 4992 | 4998 | const rc = sys.copy_file_range(fd_in, &off_in_copy, fd_out, &off_out_copy, len, flags); |
| 4993 | 4999 | switch (sys.getErrno(rc)) { |
| 4994 | 5000 | 0 => return @intCast(usize, rc), |
| 4995 | | EBADF => unreachable, |
| 5001 | EBADF => return error.InvalidFileDescriptor, |
| 4996 | 5002 | EFBIG => return error.FileTooBig, |
| 4997 | 5003 | EIO => return error.InputOutput, |
| 4998 | 5004 | 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 |
| 5013 | 5019 | } |
| 5014 | 5020 | } |
| 5015 | 5021 | |
| 5016 | | var buf: [8 * 4096]u8 = undefined; |
| 5017 | | const adjusted_count = math.min(buf.len, len); |
| 5018 | | const amt_read = try pread(fd_in, buf[0..adjusted_count], off_in); |
| 5019 | | // TODO without @as the line below fails to compile for wasm32-wasi: |
| 5020 | | // error: integer value 0 cannot be coerced to type 'os.PWriteError!usize' |
| 5021 | | if (amt_read == 0) return @as(usize, 0); |
| 5022 | | return pwrite(fd_out, buf[0..amt_read], off_out); |
| 5023 | | } |
| 5024 | | |
| 5025 | | var has_copy_file_range_syscall = std.atomic.Int(u1).init(1); |
| 5026 | | |
| 5027 | | pub const CopyFileOptions = struct {}; |
| 5028 | | |
| 5029 | | pub const CopyFileError = error{ |
| 5030 | | BadFileHandle, |
| 5031 | | SystemResources, |
| 5032 | | FileTooBig, |
| 5033 | | InputOutput, |
| 5034 | | IsDir, |
| 5035 | | OutOfMemory, |
| 5036 | | NoSpaceLeft, |
| 5037 | | Unseekable, |
| 5038 | | PermissionDenied, |
| 5039 | | FileBusy, |
| 5040 | | } || FStatError || SendFileError; |
| 5022 | var buf: [2 * 4096]u8 = undefined; |
| 5041 | 5023 | |
| 5042 | | /// Transfer all the data between two file descriptors in the most efficient way. |
| 5043 | | /// No metadata is transferred over. |
| 5044 | | pub fn copy_file(fd_in: fd_t, fd_out: fd_t, options: CopyFileOptions) CopyFileError!void { |
| 5045 | | if (comptime std.Target.current.isDarwin()) { |
| 5046 | | const rc = system.fcopyfile(fd_in, fd_out, null, system.COPYFILE_DATA); |
| 5047 | | switch (errno(rc)) { |
| 5048 | | 0 => return, |
| 5049 | | EINVAL => unreachable, |
| 5050 | | ENOMEM => return error.SystemResources, |
| 5051 | | // The source file was not a directory, symbolic link, or regular file. |
| 5052 | | // Try with the fallback path before giving up. |
| 5053 | | ENOTSUP => {}, |
| 5054 | | else => |err| return unexpectedErrno(err), |
| 5055 | | } |
| 5056 | | } |
| 5057 | | |
| 5058 | | if (std.Target.current.os.tag == .linux) { |
| 5059 | | // Try copy_file_range first as that works at the FS level and is the |
| 5060 | | // most efficient method (if available). |
| 5061 | | if (has_copy_file_range_syscall.get() != 0) { |
| 5062 | | cfr_loop: while (true) { |
| 5063 | | // The kernel checks `file_pos+count` for overflow, use a 32 bit |
| 5064 | | // value so that the syscall won't return EINVAL except for |
| 5065 | | // impossibly large files. |
| 5066 | | const rc = linux.copy_file_range(fd_in, null, fd_out, null, math.maxInt(u32), 0); |
| 5067 | | switch (errno(rc)) { |
| 5068 | | 0 => {}, |
| 5069 | | EBADF => return error.BadFileHandle, |
| 5070 | | EFBIG => return error.FileTooBig, |
| 5071 | | EIO => return error.InputOutput, |
| 5072 | | EISDIR => return error.IsDir, |
| 5073 | | ENOMEM => return error.OutOfMemory, |
| 5074 | | ENOSPC => return error.NoSpaceLeft, |
| 5075 | | EOVERFLOW => return error.Unseekable, |
| 5076 | | EPERM => return error.PermissionDenied, |
| 5077 | | ETXTBSY => return error.FileBusy, |
| 5078 | | // These may not be regular files, try fallback |
| 5079 | | EINVAL => break :cfr_loop, |
| 5080 | | // Support for cross-filesystem copy added in Linux 5.3, use fallback |
| 5081 | | EXDEV => break :cfr_loop, |
| 5082 | | // Syscall added in Linux 4.5, use fallback |
| 5083 | | ENOSYS => { |
| 5084 | | has_copy_file_range_syscall.set(0); |
| 5085 | | break :cfr_loop; |
| 5086 | | }, |
| 5087 | | else => |err| return unexpectedErrno(err), |
| 5088 | | } |
| 5089 | | // Terminate when no data was copied |
| 5090 | | if (rc == 0) return; |
| 5091 | | } |
| 5092 | | // This point is reached when an error occurred, hopefully no data |
| 5093 | | // was transferred yet |
| 5094 | | } |
| 5024 | var total_copied: usize = 0; |
| 5025 | var read_off = off_in; |
| 5026 | var write_off = off_out; |
| 5027 | while (total_copied < len) { |
| 5028 | const adjusted_count = math.min(buf.len, len - total_copied); |
| 5029 | const amt_read = try pread(fd_in, buf[0..adjusted_count], read_off); |
| 5030 | if (amt_read == 0) break; |
| 5031 | const amt_written = try pwrite(fd_out, buf[0..amt_read], write_off); |
| 5032 | // pwrite may write less than the specified amount, handle the remaining |
| 5033 | // chunk of data in the next iteration |
| 5034 | read_off += amt_written; |
| 5035 | write_off += amt_written; |
| 5036 | total_copied += amt_written; |
| 5095 | 5037 | } |
| 5096 | 5038 | |
| 5097 | | // Sendfile is a zero-copy mechanism iff the OS supports it, otherwise the |
| 5098 | | // fallback code will copy the contents chunk by chunk. |
| 5099 | | const empty_iovec = [0]iovec_const{}; |
| 5100 | | var offset: u64 = 0; |
| 5101 | | sendfile_loop: while (true) { |
| 5102 | | const amt = try sendfile(fd_out, fd_in, offset, 0, &empty_iovec, &empty_iovec, 0); |
| 5103 | | if (amt == 0) break :sendfile_loop; |
| 5104 | | offset += amt; |
| 5105 | | } |
| 5039 | return total_copied; |
| 5106 | 5040 | } |
| 5107 | 5041 | |
| 5108 | 5042 | pub const PollError = error{ |