authorgravatar for 14938807+xackus@users.noreply.github.comMaciej Walczak <14938807+xackus@users.noreply.github.com> 2020-08-11 21:49:43+02:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2020-08-11 15:49:43-04:00
log6febe7e977072fea1bf7b6003be2d6c1f3654905
tree77c7d9bae1ec5ea2e7254eefba240f50f6904390
parent2b28cebf644b29543fcb52504b11931a7c797ffb
signaturebadge-question-mark Signed by PGP key 4AEE18F83AFDEB23

copy_file_range linux syscall (#6010)


7 files changed, 157 insertions(+), 7 deletions(-)

lib/std/builtin.zig+8
...@@ -447,6 +447,14 @@ pub const Version = struct {...@@ -447,6 +447,14 @@ pub const Version = struct {
447 if (self.max.order(ver) == .lt) return false;447 if (self.max.order(ver) == .lt) return false;
448 return true;448 return true;
449 }449 }
450
451 /// Checks if system is guaranteed to be at least `version` or older than `version`.
452 /// Returns `null` if a runtime check is required.
453 pub fn isAtLeast(self: Range, ver: Version) ?bool {
454 if (self.min.order(ver) != .lt) return true;
455 if (self.max.order(ver) == .lt) return false;
456 return null;
457 }
450 };458 };
451459
452 pub fn order(lhs: Version, rhs: Version) std.math.Order {460 pub fn order(lhs: Version, rhs: Version) std.math.Order {
lib/std/c/linux.zig+2
...@@ -91,6 +91,8 @@ pub extern "c" fn sendfile(...@@ -91,6 +91,8 @@ pub extern "c" fn sendfile(
91 count: usize,91 count: usize,
92) isize;92) isize;
9393
94pub extern "c" fn copy_file_range(fd_in: fd_t, off_in: ?*i64, fd_out: fd_t, off_out: ?*i64, len: usize, flags: c_uint) isize;
95
94pub const pthread_attr_t = extern struct {96pub const pthread_attr_t = extern struct {
95 __size: [56]u8,97 __size: [56]u8,
96 __align: c_long,98 __align: c_long,
lib/std/fs/file.zig+2-7
...@@ -607,15 +607,10 @@ pub const File = struct {...@@ -607,15 +607,10 @@ pub const File = struct {
607 }607 }
608 }608 }
609609
610 pub const CopyRangeError = PWriteError || PReadError;610 pub const CopyRangeError = os.CopyFileRangeError;
611611
612 pub fn copyRange(in: File, in_offset: u64, out: File, out_offset: u64, len: usize) CopyRangeError!usize {612 pub fn copyRange(in: File, in_offset: u64, out: File, out_offset: u64, len: usize) CopyRangeError!usize {
613 // TODO take advantage of copy_file_range OS APIs613 return os.copy_file_range(in.handle, in_offset, out.handle, out_offset, len, 0);
614 var buf: [8 * 4096]u8 = undefined;
615 const adjusted_count = math.min(buf.len, len);
616 const amt_read = try in.pread(buf[0..adjusted_count], in_offset);
617 if (amt_read == 0) return @as(usize, 0);
618 return out.pwrite(buf[0..amt_read], out_offset);
619 }614 }
620615
621 /// Returns the number of bytes copied. If the number read is smaller than `buffer.len`, it616 /// Returns the number of bytes copied. If the number read is smaller than `buffer.len`, it
lib/std/fs/test.zig+26
...@@ -328,6 +328,32 @@ test "sendfile" {...@@ -328,6 +328,32 @@ test "sendfile" {
328 testing.expect(mem.eql(u8, written_buf[0..amt], "header1\nsecond header\nine1\nsecontrailer1\nsecond trailer\n"));328 testing.expect(mem.eql(u8, written_buf[0..amt], "header1\nsecond header\nine1\nsecontrailer1\nsecond trailer\n"));
329}329}
330330
331test "copyRangeAll" {
332 var tmp = tmpDir(.{});
333 defer tmp.cleanup();
334
335 try tmp.dir.makePath("os_test_tmp");
336 defer tmp.dir.deleteTree("os_test_tmp") catch {};
337
338 var dir = try tmp.dir.openDir("os_test_tmp", .{});
339 defer dir.close();
340
341 var src_file = try dir.createFile("file1.txt", .{ .read = true });
342 defer src_file.close();
343
344 const data = "u6wj+JmdF3qHsFPE BUlH2g4gJCmEz0PP";
345 try src_file.writeAll(data);
346
347 var dest_file = try dir.createFile("file2.txt", .{ .read = true });
348 defer dest_file.close();
349
350 var written_buf: [100]u8 = undefined;
351 _ = try src_file.copyRangeAll(0, dest_file, 0, data.len);
352
353 const amt = try dest_file.preadAll(&written_buf, 0);
354 testing.expect(mem.eql(u8, written_buf[0..amt], data));
355}
356
331test "fs.copyFile" {357test "fs.copyFile" {
332 const data = "u6wj+JmdF3qHsFPE BUlH2g4gJCmEz0PP";358 const data = "u6wj+JmdF3qHsFPE BUlH2g4gJCmEz0PP";
333 const src_file = "tmp_test_copy_file.txt";359 const src_file = "tmp_test_copy_file.txt";
lib/std/os.zig+79
...@@ -4926,6 +4926,85 @@ pub fn sendfile(...@@ -4926,6 +4926,85 @@ pub fn sendfile(
4926 return total_written;4926 return total_written;
4927}4927}
49284928
4929pub const CopyFileRangeError = error{
4930 FileTooBig,
4931 InputOutput,
4932 IsDir,
4933 OutOfMemory,
4934 NoSpaceLeft,
4935 Unseekable,
4936 PermissionDenied,
4937 FileBusy,
4938} || PReadError || PWriteError || UnexpectedError;
4939
4940/// Transfer data between file descriptors at specified offsets.
4941/// Returns the number of bytes written, which can less than requested.
4942///
4943/// The `copy_file_range` call copies `len` bytes from one file descriptor to another. When possible,
4944/// this is done within the operating system kernel, which can provide better performance
4945/// characteristics than transferring data from kernel to user space and back, such as with
4946/// `pread` and `pwrite` calls.
4947///
4948/// `fd_in` must be a file descriptor opened for reading, and `fd_out` must be a file descriptor
4949/// opened for writing. They may be any kind of file descriptor; however, if `fd_in` is not a regular
4950/// file system file, it may cause this function to fall back to calling `pread` and `pwrite`, in which case
4951/// atomicity guarantees no longer apply.
4952///
4953/// If `fd_in` and `fd_out` are the same, source and target ranges must not overlap.
4954/// The file descriptor seek positions are ignored and not updated.
4955/// When `off_in` is past the end of the input file, it successfully reads 0 bytes.
4956///
4957/// `flags` has different meanings per operating system; refer to the respective man pages.
4958///
4959/// These systems support in-kernel data copying:
4960/// * Linux 4.5 (cross-filesystem 5.3)
4961///
4962/// Other systems fall back to calling `pread` / `pwrite`.
4963///
4964/// Maximum offsets on Linux are `math.maxInt(i64)`.
4965pub fn copy_file_range(fd_in: fd_t, off_in: u64, fd_out: fd_t, off_out: u64, len: usize, flags: u32) CopyFileRangeError!usize {
4966 const use_c = std.c.versionCheck(.{ .major = 2, .minor = 27, .patch = 0 }).ok;
4967
4968 // TODO support for other systems than linux
4969 const try_syscall = comptime std.Target.current.os.isAtLeast(.linux, .{ .major = 4, .minor = 5 }) != false;
4970
4971 if (use_c or try_syscall) {
4972 const sys = if (use_c) std.c else linux;
4973
4974 var off_in_copy = @bitCast(i64, off_in);
4975 var off_out_copy = @bitCast(i64, off_out);
4976
4977 const rc = sys.copy_file_range(fd_in, &off_in_copy, fd_out, &off_out_copy, len, flags);
4978
4979 // TODO avoid wasting a syscall every time if kernel is too old and returns ENOSYS https://github.com/ziglang/zig/issues/1018
4980
4981 switch (sys.getErrno(rc)) {
4982 0 => return @intCast(usize, rc),
4983 EBADF => unreachable,
4984 EFBIG => return error.FileTooBig,
4985 EIO => return error.InputOutput,
4986 EISDIR => return error.IsDir,
4987 ENOMEM => return error.OutOfMemory,
4988 ENOSPC => return error.NoSpaceLeft,
4989 EOVERFLOW => return error.Unseekable,
4990 EPERM => return error.PermissionDenied,
4991 ETXTBSY => return error.FileBusy,
4992 EINVAL => {}, // these may not be regular files, try fallback
4993 EXDEV => {}, // support for cross-filesystem copy added in Linux 5.3, use fallback
4994 ENOSYS => {}, // syscall added in Linux 4.5, use fallback
4995 else => |err| return unexpectedErrno(err),
4996 }
4997 }
4998
4999 var buf: [8 * 4096]u8 = undefined;
5000 const adjusted_count = math.min(buf.len, len);
5001 const amt_read = try pread(fd_in, buf[0..adjusted_count], off_in);
5002 // TODO without @as the line below fails to compile for wasm32-wasi:
5003 // error: integer value 0 cannot be coerced to type 'os.PWriteError!usize'
5004 if (amt_read == 0) return @as(usize, 0);
5005 return pwrite(fd_out, buf[0..amt_read], off_out);
5006}
5007
4929pub const PollError = error{5008pub const PollError = error{
4930 /// The kernel had no space to allocate file descriptor tables.5009 /// The kernel had no space to allocate file descriptor tables.
4931 SystemResources,5010 SystemResources,
lib/std/os/linux.zig+12
...@@ -1210,6 +1210,18 @@ pub fn signalfd4(fd: fd_t, mask: *const sigset_t, flags: i32) usize {...@@ -1210,6 +1210,18 @@ pub fn signalfd4(fd: fd_t, mask: *const sigset_t, flags: i32) usize {
1210 );1210 );
1211}1211}
12121212
1213pub fn copy_file_range(fd_in: fd_t, off_in: ?*i64, fd_out: fd_t, off_out: ?*i64, len: usize, flags: u32) usize {
1214 return syscall6(
1215 .copy_file_range,
1216 @bitCast(usize, @as(isize, fd_in)),
1217 @ptrToInt(off_in),
1218 @bitCast(usize, @as(isize, fd_out)),
1219 @ptrToInt(off_out),
1220 len,
1221 flags,
1222 );
1223}
1224
1213test "" {1225test "" {
1214 if (builtin.os.tag == .linux) {1226 if (builtin.os.tag == .linux) {
1215 _ = @import("linux/test.zig");1227 _ = @import("linux/test.zig");
lib/std/target.zig+28
...@@ -100,6 +100,14 @@ pub const Target = struct {...@@ -100,6 +100,14 @@ pub const Target = struct {
100 pub fn includesVersion(self: Range, ver: WindowsVersion) bool {100 pub fn includesVersion(self: Range, ver: WindowsVersion) bool {
101 return @enumToInt(ver) >= @enumToInt(self.min) and @enumToInt(ver) <= @enumToInt(self.max);101 return @enumToInt(ver) >= @enumToInt(self.min) and @enumToInt(ver) <= @enumToInt(self.max);
102 }102 }
103
104 /// Checks if system is guaranteed to be at least `version` or older than `version`.
105 /// Returns `null` if a runtime check is required.
106 pub fn isAtLeast(self: Range, ver: WindowsVersion) ?bool {
107 if (@enumToInt(self.min) >= @enumToInt(ver)) return true;
108 if (@enumToInt(self.max) < @enumToInt(ver)) return false;
109 return null;
110 }
103 };111 };
104112
105 /// This function is defined to serialize a Zig source code representation of this113 /// This function is defined to serialize a Zig source code representation of this
...@@ -135,6 +143,12 @@ pub const Target = struct {...@@ -135,6 +143,12 @@ pub const Target = struct {
135 pub fn includesVersion(self: LinuxVersionRange, ver: Version) bool {143 pub fn includesVersion(self: LinuxVersionRange, ver: Version) bool {
136 return self.range.includesVersion(ver);144 return self.range.includesVersion(ver);
137 }145 }
146
147 /// Checks if system is guaranteed to be at least `version` or older than `version`.
148 /// Returns `null` if a runtime check is required.
149 pub fn isAtLeast(self: LinuxVersionRange, ver: Version) ?bool {
150 return self.range.isAtLeast(ver);
151 }
138 };152 };
139153
140 /// The version ranges here represent the minimum OS version to be supported154 /// The version ranges here represent the minimum OS version to be supported
...@@ -158,6 +172,8 @@ pub const Target = struct {...@@ -158,6 +172,8 @@ pub const Target = struct {
158 ///172 ///
159 /// Binaries built with a given maximum version will continue to function on newer operating system173 /// Binaries built with a given maximum version will continue to function on newer operating system
160 /// versions. However, such a binary may not take full advantage of the newer operating system APIs.174 /// versions. However, such a binary may not take full advantage of the newer operating system APIs.
175 ///
176 /// See `Os.isAtLeast`.
161 pub const VersionRange = union {177 pub const VersionRange = union {
162 none: void,178 none: void,
163 semver: Version.Range,179 semver: Version.Range,
...@@ -273,6 +289,18 @@ pub const Target = struct {...@@ -273,6 +289,18 @@ pub const Target = struct {
273 };289 };
274 }290 }
275291
292 /// Checks if system is guaranteed to be at least `version` or older than `version`.
293 /// Returns `null` if a runtime check is required.
294 pub fn isAtLeast(self: Os, comptime tag: Tag, version: anytype) ?bool {
295 if (self.tag != tag) return false;
296
297 return switch (tag) {
298 .linux => self.version_range.linux.isAtLeast(version),
299 .windows => self.version_range.windows.isAtLeast(version),
300 else => self.version_range.semver.isAtLeast(version),
301 };
302 }
303
276 pub fn requiresLibC(os: Os) bool {304 pub fn requiresLibC(os: Os) bool {
277 return switch (os.tag) {305 return switch (os.tag) {
278 .freebsd,306 .freebsd,