| author | |
| committer | |
| log | 6febe7e977072fea1bf7b6003be2d6c1f3654905 |
| tree | 77c7d9bae1ec5ea2e7254eefba240f50f6904390 |
| parent | 2b28cebf644b29543fcb52504b11931a7c797ffb |
| signature | Signed by PGP key 4AEE18F83AFDEB23 |
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 | }; |
| 451 | 459 | ||
| 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; |
| 93 | 93 | ||
| 94 | pub 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 | |||
| 94 | pub const pthread_attr_t = extern struct { | 96 | pub 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 | } |
| 609 | 609 | ||
| 610 | pub const CopyRangeError = PWriteError || PReadError; | 610 | pub const CopyRangeError = os.CopyFileRangeError; |
| 611 | 611 | ||
| 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 APIs | 613 | 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 | } |
| 620 | 615 | ||
| 621 | /// Returns the number of bytes copied. If the number read is smaller than `buffer.len`, it | 616 | /// 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 | } |
| 330 | 330 | ||
| 331 | test "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 | |||
| 331 | test "fs.copyFile" { | 357 | test "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 | } |
| 4928 | 4928 | ||
| 4929 | pub 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)`. | ||
| 4965 | 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 { | ||
| 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 | |||
| 4929 | pub const PollError = error{ | 5008 | pub 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 | } |
| 1212 | 1212 | ||
| 1213 | pub 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 | |||
| 1213 | test "" { | 1225 | test "" { |
| 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 | }; |
| 104 | 112 | ||
| 105 | /// This function is defined to serialize a Zig source code representation of this | 113 | /// 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 | }; |
| 139 | 153 | ||
| 140 | /// The version ranges here represent the minimum OS version to be supported | 154 | /// 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 system | 173 | /// 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 | } |
| 275 | 291 | ||
| 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, |