| ... | @@ -4981,19 +4981,15 @@ pub const CopyFileRangeError = error{ | ... | @@ -4981,19 +4981,15 @@ pub const CopyFileRangeError = error{ |
| 4981 | 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 { | 4981 | 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 | const use_c = std.c.versionCheck(.{ .major = 2, .minor = 27, .patch = 0 }).ok; | 4982 | const use_c = std.c.versionCheck(.{ .major = 2, .minor = 27, .patch = 0 }).ok; |
| 4983 | | 4983 | |
| 4984 | // TODO support for other systems than linux | 4984 | if (std.Target.current.os.tag == .linux and |
| 4985 | const try_syscall = comptime std.Target.current.os.isAtLeast(.linux, .{ .major = 4, .minor = 5 }) != false; | 4985 | (use_c or has_copy_file_range_syscall.get() != 0)) |
| 4986 | | 4986 | { |
| 4987 | if (use_c or try_syscall) { | | |
| 4988 | const sys = if (use_c) std.c else linux; | 4987 | const sys = if (use_c) std.c else linux; |
| 4989 | | 4988 | |
| 4990 | var off_in_copy = @bitCast(i64, off_in); | 4989 | var off_in_copy = @bitCast(i64, off_in); |
| 4991 | var off_out_copy = @bitCast(i64, off_out); | 4990 | var off_out_copy = @bitCast(i64, off_out); |
| 4992 | | 4991 | |
| 4993 | const rc = sys.copy_file_range(fd_in, &off_in_copy, fd_out, &off_out_copy, len, flags); | 4992 | const rc = sys.copy_file_range(fd_in, &off_in_copy, fd_out, &off_out_copy, len, flags); |
| 4994 | | | |
| 4995 | // TODO avoid wasting a syscall every time if kernel is too old and returns ENOSYS https://github.com/ziglang/zig/issues/1018 | | |
| 4996 | | | |
| 4997 | switch (sys.getErrno(rc)) { | 4993 | switch (sys.getErrno(rc)) { |
| 4998 | 0 => return @intCast(usize, rc), | 4994 | 0 => return @intCast(usize, rc), |
| 4999 | EBADF => unreachable, | 4995 | EBADF => unreachable, |
| ... | @@ -5005,9 +5001,14 @@ pub fn copy_file_range(fd_in: fd_t, off_in: u64, fd_out: fd_t, off_out: u64, len | ... | @@ -5005,9 +5001,14 @@ pub fn copy_file_range(fd_in: fd_t, off_in: u64, fd_out: fd_t, off_out: u64, len |
| 5005 | EOVERFLOW => return error.Unseekable, | 5001 | EOVERFLOW => return error.Unseekable, |
| 5006 | EPERM => return error.PermissionDenied, | 5002 | EPERM => return error.PermissionDenied, |
| 5007 | ETXTBSY => return error.FileBusy, | 5003 | ETXTBSY => return error.FileBusy, |
| 5008 | EINVAL => {}, // these may not be regular files, try fallback | 5004 | // these may not be regular files, try fallback |
| 5009 | EXDEV => {}, // support for cross-filesystem copy added in Linux 5.3, use fallback | 5005 | EINVAL => {}, |
| 5010 | ENOSYS => {}, // syscall added in Linux 4.5, use fallback | 5006 | // support for cross-filesystem copy added in Linux 5.3, use fallback |
| | 5007 | EXDEV => {}, |
| | 5008 | // syscall added in Linux 4.5, use fallback |
| | 5009 | ENOSYS => { |
| | 5010 | has_copy_file_range_syscall.set(0); |
| | 5011 | }, |
| 5011 | else => |err| return unexpectedErrno(err), | 5012 | else => |err| return unexpectedErrno(err), |
| 5012 | } | 5013 | } |
| 5013 | } | 5014 | } |
| ... | @@ -5021,6 +5022,86 @@ pub fn copy_file_range(fd_in: fd_t, off_in: u64, fd_out: fd_t, off_out: u64, len | ... | @@ -5021,6 +5022,86 @@ pub fn copy_file_range(fd_in: fd_t, off_in: u64, fd_out: fd_t, off_out: u64, len |
| 5021 | return pwrite(fd_out, buf[0..amt_read], off_out); | 5022 | return pwrite(fd_out, buf[0..amt_read], off_out); |
| 5022 | } | 5023 | } |
| 5023 | | 5024 | |
| | 5025 | var has_copy_file_range_syscall = std.atomic.Int(u1).init(1); |
| | 5026 | |
| | 5027 | pub const CopyFileOptions = struct { |
| | 5028 | /// Size in bytes of the source files, if available saves a call to stat(). |
| | 5029 | file_size: ?u64 = null, |
| | 5030 | }; |
| | 5031 | |
| | 5032 | pub const CopyFileError = error{ |
| | 5033 | SystemResources, |
| | 5034 | FileTooBig, |
| | 5035 | InputOutput, |
| | 5036 | IsDir, |
| | 5037 | OutOfMemory, |
| | 5038 | NoSpaceLeft, |
| | 5039 | Unseekable, |
| | 5040 | PermissionDenied, |
| | 5041 | FileBusy, |
| | 5042 | } || FStatError || SendFileError; |
| | 5043 | |
| | 5044 | /// Transfer all the data between two file descriptors in the most efficient way. |
| | 5045 | /// No metadata is transferred over. |
| | 5046 | pub fn copy_file(fd_in: fd_t, fd_out: fd_t, options: CopyFileOptions) CopyFileError!void { |
| | 5047 | if (comptime std.Target.current.isDarwin()) { |
| | 5048 | const rc = system.fcopyfile(fd_in, fd_out, null, system.COPYFILE_DATA); |
| | 5049 | switch (errno(rc)) { |
| | 5050 | 0 => return, |
| | 5051 | EINVAL => unreachable, |
| | 5052 | ENOMEM => return error.SystemResources, |
| | 5053 | // The source file was not a directory, symbolic link, or regular file. |
| | 5054 | // Try with the fallback path before giving up. |
| | 5055 | ENOTSUP => {}, |
| | 5056 | else => |err| return unexpectedErrno(err), |
| | 5057 | } |
| | 5058 | } |
| | 5059 | |
| | 5060 | const src_file_size = options.file_size orelse |
| | 5061 | @bitCast(u64, (try fstat(fd_in)).size); |
| | 5062 | var remaining = src_file_size; |
| | 5063 | |
| | 5064 | if (std.Target.current.os.tag == .linux) { |
| | 5065 | // Try copy_file_range first as that works at the FS level and is the |
| | 5066 | // most efficient method (if available). |
| | 5067 | if (has_copy_file_range_syscall.get() != 0) { |
| | 5068 | cfr_loop: while (remaining > 0) { |
| | 5069 | const copy_amt = math.cast(usize, remaining) catch math.maxInt(usize); |
| | 5070 | const rc = linux.copy_file_range(fd_in, null, fd_out, null, copy_amt, 0); |
| | 5071 | switch (errno(rc)) { |
| | 5072 | 0 => {}, |
| | 5073 | EBADF => unreachable, |
| | 5074 | EFBIG => return error.FileTooBig, |
| | 5075 | EIO => return error.InputOutput, |
| | 5076 | EISDIR => return error.IsDir, |
| | 5077 | ENOMEM => return error.OutOfMemory, |
| | 5078 | ENOSPC => return error.NoSpaceLeft, |
| | 5079 | EOVERFLOW => return error.Unseekable, |
| | 5080 | EPERM => return error.PermissionDenied, |
| | 5081 | ETXTBSY => return error.FileBusy, |
| | 5082 | // these may not be regular files, try fallback |
| | 5083 | EINVAL => break :cfr_loop, |
| | 5084 | // support for cross-filesystem copy added in Linux 5.3, use fallback |
| | 5085 | EXDEV => break :cfr_loop, |
| | 5086 | // syscall added in Linux 4.5, use fallback |
| | 5087 | ENOSYS => { |
| | 5088 | has_copy_file_range_syscall.set(0); |
| | 5089 | break :cfr_loop; |
| | 5090 | }, |
| | 5091 | else => |err| return unexpectedErrno(err), |
| | 5092 | } |
| | 5093 | remaining -= rc; |
| | 5094 | } |
| | 5095 | return; |
| | 5096 | } |
| | 5097 | } |
| | 5098 | |
| | 5099 | // Sendfile is a zero-copy mechanism iff the OS supports it, otherwise the |
| | 5100 | // fallback code will copy the contents chunk by chunk. |
| | 5101 | const empty_iovec = [0]iovec_const{}; |
| | 5102 | _ = try sendfile(fd_out, fd_in, 0, remaining, &empty_iovec, &empty_iovec, 0); |
| | 5103 | } |
| | 5104 | |
| 5024 | pub const PollError = error{ | 5105 | pub const PollError = error{ |
| 5025 | /// The kernel had no space to allocate file descriptor tables. | 5106 | /// The kernel had no space to allocate file descriptor tables. |
| 5026 | SystemResources, | 5107 | SystemResources, |