authorgravatar for thatlemon@gmail.comLemonBoy <thatlemon@gmail.com> 2020-10-06 09:38:59+02:00
committergravatar for thatlemon@gmail.comLemonBoy <thatlemon@gmail.com> 2020-10-06 09:38:59+02:00
loga419a1aabce63fedcc77a1118d596864fcff46e3
tree1f957a2a663d9a80f3b14583f669262677921475
parent8b4f5f039df65980d0a0ec6add1caf4c9bf2468c

Move copy_file to fs namespace

Now it is a private API. Also handle short writes in copy_file_range fallback implementation.

2 files changed, 69 insertions(+), 89 deletions(-)

lib/std/fs.zig+47-1
...@@ -1823,7 +1823,7 @@ pub const Dir = struct {...@@ -1823,7 +1823,7 @@ pub const Dir = struct {
1823 var atomic_file = try dest_dir.atomicFile(dest_path, .{ .mode = mode });1823 var atomic_file = try dest_dir.atomicFile(dest_path, .{ .mode = mode });
1824 defer atomic_file.deinit();1824 defer atomic_file.deinit();
18251825
1826 try os.copy_file(in_file.handle, atomic_file.file.handle, .{});1826 try copy_file(in_file.handle, atomic_file.file.handle);
1827 return atomic_file.finish();1827 return atomic_file.finish();
1828 }1828 }
18291829
...@@ -2263,6 +2263,52 @@ pub fn realpathAlloc(allocator: *Allocator, pathname: []const u8) ![]u8 {...@@ -2263,6 +2263,52 @@ pub fn realpathAlloc(allocator: *Allocator, pathname: []const u8) ![]u8 {
2263 return allocator.dupe(u8, try os.realpath(pathname, &buf));2263 return allocator.dupe(u8, try os.realpath(pathname, &buf));
2264}2264}
22652265
2266const CopyFileError = error{SystemResources} || os.CopyFileRangeError || os.SendFileError;
2267
2268/// Transfer all the data between two file descriptors in the most efficient way.
2269/// No metadata is transferred over.
2270fn copy_file(fd_in: os.fd_t, fd_out: os.fd_t) CopyFileError!void {
2271 if (comptime std.Target.current.isDarwin()) {
2272 const rc = os.system.fcopyfile(fd_in, fd_out, null, os.system.COPYFILE_DATA);
2273 switch (errno(rc)) {
2274 0 => return,
2275 EINVAL => unreachable,
2276 ENOMEM => return error.SystemResources,
2277 // The source file was not a directory, symbolic link, or regular file.
2278 // Try with the fallback path before giving up.
2279 ENOTSUP => {},
2280 else => |err| return unexpectedErrno(err),
2281 }
2282 }
2283
2284 if (std.Target.current.os.tag == .linux) {
2285 // Try copy_file_range first as that works at the FS level and is the
2286 // most efficient method (if available).
2287 var offset: u64 = 0;
2288 cfr_loop: while (true) {
2289 // The kernel checks `offset+count` for overflow, use a 32 bit
2290 // value so that the syscall won't return EINVAL except for
2291 // impossibly large files.
2292 const amt = try os.copy_file_range(fd_in, offset, fd_out, offset, math.maxInt(u32), 0);
2293 // Terminate when no data was copied
2294 if (amt == 0) break :cfr_loop;
2295 offset += amt;
2296 }
2297 return;
2298 }
2299
2300 // Sendfile is a zero-copy mechanism iff the OS supports it, otherwise the
2301 // fallback code will copy the contents chunk by chunk.
2302 const empty_iovec = [0]os.iovec_const{};
2303 var offset: u64 = 0;
2304 sendfile_loop: while (true) {
2305 const amt = try os.sendfile(fd_out, fd_in, offset, 0, &empty_iovec, &empty_iovec, 0);
2306 // Terminate when no data was copied
2307 if (amt == 0) break :sendfile_loop;
2308 offset += amt;
2309 }
2310}
2311
2266test "" {2312test "" {
2267 if (builtin.os.tag != .wasi) {2313 if (builtin.os.tag != .wasi) {
2268 _ = makeDirAbsolute;2314 _ = makeDirAbsolute;
lib/std/os.zig+22-88
...@@ -4945,6 +4945,7 @@ pub fn sendfile(...@@ -4945,6 +4945,7 @@ pub fn sendfile(
4945pub const CopyFileRangeError = error{4945pub const CopyFileRangeError = error{
4946 FileTooBig,4946 FileTooBig,
4947 InputOutput,4947 InputOutput,
4948 InvalidFileDescriptor,
4948 IsDir,4949 IsDir,
4949 OutOfMemory,4950 OutOfMemory,
4950 NoSpaceLeft,4951 NoSpaceLeft,
...@@ -4978,6 +4979,11 @@ pub const CopyFileRangeError = error{...@@ -4978,6 +4979,11 @@ pub const CopyFileRangeError = error{
4978/// Other systems fall back to calling `pread` / `pwrite`.4979/// Other systems fall back to calling `pread` / `pwrite`.
4979///4980///
4980/// Maximum offsets on Linux are `math.maxInt(i64)`.4981/// Maximum offsets on Linux are `math.maxInt(i64)`.
4982var 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
4981pub fn copy_file_range(fd_in: fd_t, off_in: u64, fd_out: fd_t, off_out: u64, len: usize, flags: u32) CopyFileRangeError!usize {4987pub 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;4988 const use_c = std.c.versionCheck(.{ .major = 2, .minor = 27, .patch = 0 }).ok;
49834989
...@@ -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,7 +4998,7 @@ pub fn copy_file_range(fd_in: fd_t, off_in: u64, fd_out: fd_t, off_out: u64, len
4992 const rc = sys.copy_file_range(fd_in, &off_in_copy, fd_out, &off_out_copy, len, flags);4998 const rc = sys.copy_file_range(fd_in, &off_in_copy, fd_out, &off_out_copy, len, flags);
4993 switch (sys.getErrno(rc)) {4999 switch (sys.getErrno(rc)) {
4994 0 => return @intCast(usize, rc),5000 0 => return @intCast(usize, rc),
4995 EBADF => unreachable,5001 EBADF => return error.InvalidFileDescriptor,
4996 EFBIG => return error.FileTooBig,5002 EFBIG => return error.FileTooBig,
4997 EIO => return error.InputOutput,5003 EIO => return error.InputOutput,
4998 EISDIR => return error.IsDir,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,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 }
50155021
5016 var buf: [8 * 4096]u8 = undefined;5022 var buf: [2 * 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
5025var has_copy_file_range_syscall = std.atomic.Int(u1).init(1);
5026
5027pub const CopyFileOptions = struct {};
5028
5029pub 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;
50415023
5042/// Transfer all the data between two file descriptors in the most efficient way.5024 var total_copied: usize = 0;
5043/// No metadata is transferred over.5025 var read_off = off_in;
5044pub fn copy_file(fd_in: fd_t, fd_out: fd_t, options: CopyFileOptions) CopyFileError!void {5026 var write_off = off_out;
5045 if (comptime std.Target.current.isDarwin()) {5027 while (total_copied < len) {
5046 const rc = system.fcopyfile(fd_in, fd_out, null, system.COPYFILE_DATA);5028 const adjusted_count = math.min(buf.len, len - total_copied);
5047 switch (errno(rc)) {5029 const amt_read = try pread(fd_in, buf[0..adjusted_count], read_off);
5048 0 => return,5030 if (amt_read == 0) break;
5049 EINVAL => unreachable,5031 const amt_written = try pwrite(fd_out, buf[0..amt_read], write_off);
5050 ENOMEM => return error.SystemResources,5032 // pwrite may write less than the specified amount, handle the remaining
5051 // The source file was not a directory, symbolic link, or regular file.5033 // chunk of data in the next iteration
5052 // Try with the fallback path before giving up.5034 read_off += amt_written;
5053 ENOTSUP => {},5035 write_off += amt_written;
5054 else => |err| return unexpectedErrno(err),5036 total_copied += amt_written;
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 }
5095 }5037 }
50965038
5097 // Sendfile is a zero-copy mechanism iff the OS supports it, otherwise the5039 return total_copied;
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 }
5106}5040}
51075041
5108pub const PollError = error{5042pub const PollError = error{