| author | |
| committer | |
| log | 25a0648176ce0e0061a035fdf602795368a3b40e |
| tree | e5562574fda02d17c3d095baf6b58232d819f381 |
| parent | 7926081ec35761028c87cdb82204f177311866be |
6 files changed, 229 insertions(+), 166 deletions(-)
lib/std/fs/Dir.zig+31-71| ... | ... | @@ -2609,7 +2609,7 @@ pub fn updateFile( |
| 2609 | 2609 | try dest_dir.makePath(dirname); |
| 2610 | 2610 | } |
| 2611 | 2611 | |
| 2612 | var buffer: [2000]u8 = undefined; | |
| 2612 | var buffer: [1000]u8 = undefined; // Used only when direct fd-to-fd is not available. | |
| 2613 | 2613 | var atomic_file = try dest_dir.atomicFile(dest_path, .{ |
| 2614 | 2614 | .mode = actual_mode, |
| 2615 | 2615 | .write_buffer = &buffer, |
| ... | ... | @@ -2619,7 +2619,7 @@ pub fn updateFile( |
| 2619 | 2619 | var src_reader: File.Reader = .initSize(src_file, &.{}, src_stat.size); |
| 2620 | 2620 | const dest_writer = &atomic_file.file_writer.interface; |
| 2621 | 2621 | |
| 2622 | dest_writer.writeFileAll(&src_reader, .{}) catch |err| switch (err) { | |
| 2622 | _ = dest_writer.sendFileAll(&src_reader, .unlimited) catch |err| switch (err) { | |
| 2623 | 2623 | error.ReadFailed => return src_reader.err.?, |
| 2624 | 2624 | error.WriteFailed => return atomic_file.file_writer.err.?, |
| 2625 | 2625 | }; |
| ... | ... | @@ -2628,16 +2628,22 @@ pub fn updateFile( |
| 2628 | 2628 | return .stale; |
| 2629 | 2629 | } |
| 2630 | 2630 | |
| 2631 | pub const CopyFileError = File.OpenError || File.StatError || | |
| 2632 | AtomicFile.InitError || CopyFileRawError || AtomicFile.FinishError; | |
| 2631 | pub const CopyFileError = File.OpenError || File.StatError || File.ReadError || File.WriteError || | |
| 2632 | AtomicFile.InitError || AtomicFile.FinishError; | |
| 2633 | 2633 | |
| 2634 | /// Guaranteed to be atomic. | |
| 2635 | /// On Linux, until https://patchwork.kernel.org/patch/9636735/ is merged and readily available, | |
| 2636 | /// there is a possibility of power loss or application termination leaving temporary files present | |
| 2637 | /// in the same directory as dest_path. | |
| 2638 | /// On Windows, both paths should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). | |
| 2639 | /// On WASI, both paths should be encoded as valid UTF-8. | |
| 2640 | /// On other platforms, both paths are an opaque sequence of bytes with no particular encoding. | |
| 2634 | /// Atomically creates a new file at `dest_path` within `dest_dir` with the | |
| 2635 | /// same contents as `source_path` within `source_dir`, overwriting any already | |
| 2636 | /// existing file. | |
| 2637 | /// | |
| 2638 | /// On Linux, until https://patchwork.kernel.org/patch/9636735/ is merged and | |
| 2639 | /// readily available, there is a possibility of power loss or application | |
| 2640 | /// termination leaving temporary files present in the same directory as | |
| 2641 | /// dest_path. | |
| 2642 | /// | |
| 2643 | /// On Windows, both paths should be encoded as | |
| 2644 | /// [WTF-8](https://simonsapin.github.io/wtf-8/). On WASI, both paths should be | |
| 2645 | /// encoded as valid UTF-8. On other platforms, both paths are an opaque | |
| 2646 | /// sequence of bytes with no particular encoding. | |
| 2641 | 2647 | pub fn copyFile( |
| 2642 | 2648 | source_dir: Dir, |
| 2643 | 2649 | source_path: []const u8, |
| ... | ... | @@ -2645,74 +2651,28 @@ pub fn copyFile( |
| 2645 | 2651 | dest_path: []const u8, |
| 2646 | 2652 | options: CopyFileOptions, |
| 2647 | 2653 | ) CopyFileError!void { |
| 2648 | var in_file = try source_dir.openFile(source_path, .{}); | |
| 2649 | defer in_file.close(); | |
| 2654 | var file_reader: File.Reader = .init(try source_dir.openFile(source_path, .{}), &.{}); | |
| 2655 | defer file_reader.file.close(); | |
| 2650 | 2656 | |
| 2651 | var size: ?u64 = null; | |
| 2652 | 2657 | const mode = options.override_mode orelse blk: { |
| 2653 | const st = try in_file.stat(); | |
| 2654 | size = st.size; | |
| 2658 | const st = try file_reader.file.stat(); | |
| 2659 | file_reader.size = st.size; | |
| 2655 | 2660 | break :blk st.mode; |
| 2656 | 2661 | }; |
| 2657 | 2662 | |
| 2658 | var atomic_file = try dest_dir.atomicFile(dest_path, .{ .mode = mode }); | |
| 2663 | var buffer: [1000]u8 = undefined; // Used only when direct fd-to-fd is not available. | |
| 2664 | var atomic_file = try dest_dir.atomicFile(dest_path, .{ | |
| 2665 | .mode = mode, | |
| 2666 | .write_buffer = &buffer, | |
| 2667 | }); | |
| 2659 | 2668 | defer atomic_file.deinit(); |
| 2660 | 2669 | |
| 2661 | try copy_file(in_file.handle, atomic_file.file_writer.file.handle, size); | |
| 2670 | const size = atomic_file.file_writer.interface.sendFileAll(&file_reader, .unlimited) catch |err| switch (err) { | |
| 2671 | error.ReadFailed => return file_reader.err.?, | |
| 2672 | error.WriteFailed => return atomic_file.file_writer.err.?, | |
| 2673 | }; | |
| 2662 | 2674 | try atomic_file.finish(); |
| 2663 | } | |
| 2664 | ||
| 2665 | const CopyFileRawError = error{SystemResources} || posix.CopyFileRangeError || posix.SendFileError; | |
| 2666 | ||
| 2667 | // Transfer all the data between two file descriptors in the most efficient way. | |
| 2668 | // The copy starts at offset 0, the initial offsets are preserved. | |
| 2669 | // No metadata is transferred over. | |
| 2670 | fn copy_file(fd_in: posix.fd_t, fd_out: posix.fd_t, maybe_size: ?u64) CopyFileRawError!void { | |
| 2671 | if (builtin.target.os.tag.isDarwin()) { | |
| 2672 | const rc = posix.system.fcopyfile(fd_in, fd_out, null, .{ .DATA = true }); | |
| 2673 | switch (posix.errno(rc)) { | |
| 2674 | .SUCCESS => return, | |
| 2675 | .INVAL => unreachable, | |
| 2676 | .NOMEM => return error.SystemResources, | |
| 2677 | // The source file is not a directory, symbolic link, or regular file. | |
| 2678 | // Try with the fallback path before giving up. | |
| 2679 | .OPNOTSUPP => {}, | |
| 2680 | else => |err| return posix.unexpectedErrno(err), | |
| 2681 | } | |
| 2682 | } | |
| 2683 | ||
| 2684 | if (native_os == .linux) { | |
| 2685 | // Try copy_file_range first as that works at the FS level and is the | |
| 2686 | // most efficient method (if available). | |
| 2687 | var offset: u64 = 0; | |
| 2688 | cfr_loop: while (true) { | |
| 2689 | // The kernel checks the u64 value `offset+count` for overflow, use | |
| 2690 | // a 32 bit value so that the syscall won't return EINVAL except for | |
| 2691 | // impossibly large files (> 2^64-1 - 2^32-1). | |
| 2692 | const amt = try posix.copy_file_range(fd_in, offset, fd_out, offset, std.math.maxInt(u32), 0); | |
| 2693 | // Terminate as soon as we have copied size bytes or no bytes | |
| 2694 | if (maybe_size) |s| { | |
| 2695 | if (s == amt) break :cfr_loop; | |
| 2696 | } | |
| 2697 | if (amt == 0) break :cfr_loop; | |
| 2698 | offset += amt; | |
| 2699 | } | |
| 2700 | return; | |
| 2701 | } | |
| 2702 | ||
| 2703 | // Sendfile is a zero-copy mechanism iff the OS supports it, otherwise the | |
| 2704 | // fallback code will copy the contents chunk by chunk. | |
| 2705 | const empty_iovec = [0]posix.iovec_const{}; | |
| 2706 | var offset: u64 = 0; | |
| 2707 | sendfile_loop: while (true) { | |
| 2708 | const amt = try posix.sendfile(fd_out, fd_in, offset, 0, &empty_iovec, &empty_iovec, 0); | |
| 2709 | // Terminate as soon as we have copied size bytes or no bytes | |
| 2710 | if (maybe_size) |s| { | |
| 2711 | if (s == amt) break :sendfile_loop; | |
| 2712 | } | |
| 2713 | if (amt == 0) break :sendfile_loop; | |
| 2714 | offset += amt; | |
| 2715 | } | |
| 2675 | _ = size; | |
| 2716 | 2676 | } |
| 2717 | 2677 | |
| 2718 | 2678 | pub const AtomicFileOptions = struct { |
lib/std/fs/File.zig+82-4| ... | ... | @@ -961,7 +961,7 @@ pub const Reader = struct { |
| 961 | 961 | }; |
| 962 | 962 | } |
| 963 | 963 | |
| 964 | pub fn initSize(file: File, buffer: []u8, size: u64) Reader { | |
| 964 | pub fn initSize(file: File, buffer: []u8, size: ?u64) Reader { | |
| 965 | 965 | return .{ |
| 966 | 966 | .file = file, |
| 967 | 967 | .interface = initInterface(buffer), |
| ... | ... | @@ -1099,7 +1099,6 @@ pub const Reader = struct { |
| 1099 | 1099 | var iovecs_buffer: [max_buffers_len]posix.iovec = undefined; |
| 1100 | 1100 | const dest = try w.writableVectorPosix(&iovecs_buffer, limit); |
| 1101 | 1101 | assert(dest[0].len > 0); |
| 1102 | // TODO also add buffer at the end | |
| 1103 | 1102 | const n = posix.readv(r.file.handle, dest) catch |err| { |
| 1104 | 1103 | r.err = err; |
| 1105 | 1104 | return error.ReadFailed; |
| ... | ... | @@ -1251,6 +1250,8 @@ pub const Writer = struct { |
| 1251 | 1250 | mode: Writer.Mode = .positional, |
| 1252 | 1251 | pos: u64 = 0, |
| 1253 | 1252 | sendfile_err: ?SendfileError = null, |
| 1253 | copy_file_range_err: ?CopyFileRangeError = null, | |
| 1254 | fcopyfile_err: ?FcopyfileError = null, | |
| 1254 | 1255 | seek_err: ?SeekError = null, |
| 1255 | 1256 | interface: std.io.Writer, |
| 1256 | 1257 | |
| ... | ... | @@ -1265,6 +1266,14 @@ pub const Writer = struct { |
| 1265 | 1266 | Unexpected, |
| 1266 | 1267 | }; |
| 1267 | 1268 | |
| 1269 | pub const CopyFileRangeError = std.os.freebsd.CopyFileRangeError || std.os.linux.wrapped.CopyFileRangeError; | |
| 1270 | ||
| 1271 | pub const FcopyfileError = error{ | |
| 1272 | OperationNotSupported, | |
| 1273 | OutOfMemory, | |
| 1274 | Unexpected, | |
| 1275 | }; | |
| 1276 | ||
| 1268 | 1277 | /// Number of slices to store on the stack, when trying to send as many byte |
| 1269 | 1278 | /// vectors through the underlying write calls as possible. |
| 1270 | 1279 | const max_buffers_len = 16; |
| ... | ... | @@ -1408,7 +1417,6 @@ pub const Writer = struct { |
| 1408 | 1417 | const w: *Writer = @fieldParentPtr("interface", io_writer); |
| 1409 | 1418 | const out_fd = w.file.handle; |
| 1410 | 1419 | const in_fd = file_reader.file.handle; |
| 1411 | // TODO try using copy_file_range on Linux | |
| 1412 | 1420 | // TODO try using copy_file_range on FreeBSD |
| 1413 | 1421 | // TODO try using sendfile on macOS |
| 1414 | 1422 | // TODO try using sendfile on FreeBSD |
| ... | ... | @@ -1416,7 +1424,8 @@ pub const Writer = struct { |
| 1416 | 1424 | // Try using sendfile on Linux. |
| 1417 | 1425 | if (w.sendfile_err != null) break :sf; |
| 1418 | 1426 | // Linux sendfile does not support headers. |
| 1419 | if (io_writer.end != 0) return drain(io_writer, &.{""}, 1); | |
| 1427 | const buffered = limit.slice(file_reader.interface.buffer); | |
| 1428 | if (io_writer.end != 0 or buffered.len != 0) return drain(io_writer, &.{buffered}, 1); | |
| 1420 | 1429 | const max_count = 0x7ffff000; // Avoid EINVAL. |
| 1421 | 1430 | var off: std.os.linux.off_t = undefined; |
| 1422 | 1431 | const off_ptr: ?*std.os.linux.off_t, const count: usize = switch (file_reader.mode) { |
| ... | ... | @@ -1455,6 +1464,75 @@ pub const Writer = struct { |
| 1455 | 1464 | w.pos += n; |
| 1456 | 1465 | return n; |
| 1457 | 1466 | } |
| 1467 | const copy_file_range_fn = switch (native_os) { | |
| 1468 | .freebsd => std.os.freebsd.copy_file_range, | |
| 1469 | .linux => if (std.c.versionCheck(.{ .major = 2, .minor = 27, .patch = 0 })) std.os.linux.wrapped.copy_file_range else null, | |
| 1470 | else => null, | |
| 1471 | }; | |
| 1472 | if (copy_file_range_fn) |copy_file_range| cfr: { | |
| 1473 | if (w.copy_file_range_err != null) break :cfr; | |
| 1474 | const buffered = limit.slice(file_reader.interface.buffer); | |
| 1475 | if (io_writer.end != 0 or buffered.len != 0) return drain(io_writer, &.{buffered}, 1); | |
| 1476 | var off_in: i64 = undefined; | |
| 1477 | var off_out: i64 = undefined; | |
| 1478 | const off_in_ptr: ?*i64 = switch (file_reader.mode) { | |
| 1479 | .positional_reading, .streaming_reading => return error.Unimplemented, | |
| 1480 | .positional => p: { | |
| 1481 | off_in = file_reader.pos; | |
| 1482 | break :p &off_in; | |
| 1483 | }, | |
| 1484 | .streaming => null, | |
| 1485 | .failure => return error.WriteFailed, | |
| 1486 | }; | |
| 1487 | const off_out_ptr: ?*i64 = switch (w.mode) { | |
| 1488 | .positional_reading, .streaming_reading => return error.Unimplemented, | |
| 1489 | .positional => p: { | |
| 1490 | off_out = w.pos; | |
| 1491 | break :p &off_out; | |
| 1492 | }, | |
| 1493 | .streaming => null, | |
| 1494 | .failure => return error.WriteFailed, | |
| 1495 | }; | |
| 1496 | const n = copy_file_range(in_fd, off_in_ptr, out_fd, off_out_ptr, @intFromEnum(limit), 0) catch |err| { | |
| 1497 | w.copy_file_range_err = err; | |
| 1498 | return 0; | |
| 1499 | }; | |
| 1500 | file_reader.pos += n; | |
| 1501 | w.pos += n; | |
| 1502 | return n; | |
| 1503 | } | |
| 1504 | ||
| 1505 | if (builtin.os.tag.isDarwin()) fcf: { | |
| 1506 | if (w.fcopyfile_err != null) break :fcf; | |
| 1507 | if (file_reader.pos != 0) break :fcf; | |
| 1508 | if (w.pos != 0) break :fcf; | |
| 1509 | if (limit != .unlimited) break :fcf; | |
| 1510 | const rc = std.c.fcopyfile(in_fd, out_fd, null, .{ .DATA = true }); | |
| 1511 | switch (posix.errno(rc)) { | |
| 1512 | .SUCCESS => {}, | |
| 1513 | .INVAL => if (builtin.mode == .Debug) @panic("invalid API usage") else { | |
| 1514 | w.fcopyfile_err = error.Unexpected; | |
| 1515 | return 0; | |
| 1516 | }, | |
| 1517 | .NOMEM => { | |
| 1518 | w.fcopyfile_err = error.OutOfMemory; | |
| 1519 | return 0; | |
| 1520 | }, | |
| 1521 | .OPNOTSUPP => { | |
| 1522 | w.fcopyfile_err = error.OperationNotSupported; | |
| 1523 | return 0; | |
| 1524 | }, | |
| 1525 | else => |err| { | |
| 1526 | w.fcopyfile_err = posix.unexpectedErrno(err); | |
| 1527 | return 0; | |
| 1528 | }, | |
| 1529 | } | |
| 1530 | const n = if (file_reader.size) |size| size else @panic("TODO figure out how much copied"); | |
| 1531 | file_reader.pos = n; | |
| 1532 | w.pos = n; | |
| 1533 | return n; | |
| 1534 | } | |
| 1535 | ||
| 1458 | 1536 | return error.Unimplemented; |
| 1459 | 1537 | } |
| 1460 | 1538 |
lib/std/os.zig+1| ... | ... | @@ -31,6 +31,7 @@ pub const uefi = @import("os/uefi.zig"); |
| 31 | 31 | pub const wasi = @import("os/wasi.zig"); |
| 32 | 32 | pub const emscripten = @import("os/emscripten.zig"); |
| 33 | 33 | pub const windows = @import("os/windows.zig"); |
| 34 | pub const freebsd = @import("os/freebsd.zig"); | |
| 34 | 35 | |
| 35 | 36 | test { |
| 36 | 37 | _ = linux; |
lib/std/os/freebsd.zig created+49| ... | ... | @@ -0,0 +1,49 @@ |
| 1 | const std = @import("../std.zig"); | |
| 2 | const fd_t = std.c.fd_t; | |
| 3 | const off_t = std.c.off_t; | |
| 4 | const unexpectedErrno = std.posix.unexpectedErrno; | |
| 5 | const errno = std.posix.errno; | |
| 6 | ||
| 7 | pub const CopyFileRangeError = error{ | |
| 8 | /// If infd is not open for reading or outfd is not open for writing, or | |
| 9 | /// opened for writing with O_APPEND, or if infd and outfd refer to the | |
| 10 | /// same file. | |
| 11 | BadFileFlags, | |
| 12 | /// If the copy exceeds the process's file size limit or the maximum | |
| 13 | /// file size for the file system outfd re- sides on. | |
| 14 | FileTooBig, | |
| 15 | /// A signal interrupted the system call before it could be completed. | |
| 16 | /// This may happen for files on some NFS mounts. When this happens, | |
| 17 | /// the values pointed to by inoffp and outoffp are reset to the | |
| 18 | /// initial values for the system call. | |
| 19 | Interrupted, | |
| 20 | /// One of: | |
| 21 | /// * infd and outfd refer to the same file and the byte ranges overlap. | |
| 22 | /// * The flags argument is not zero. | |
| 23 | /// * Either infd or outfd refers to a file object that is not a regular file. | |
| 24 | InvalidArguments, | |
| 25 | /// An I/O error occurred while reading/writing the files. | |
| 26 | InputOutput, | |
| 27 | /// Corrupted data was detected while reading from a file system. | |
| 28 | CorruptedData, | |
| 29 | /// Either infd or outfd refers to a directory. | |
| 30 | IsDir, | |
| 31 | /// File system that stores outfd is full. | |
| 32 | NoSpaceLeft, | |
| 33 | }; | |
| 34 | ||
| 35 | pub fn copy_file_range(fd_in: fd_t, off_in: ?*i64, fd_out: fd_t, off_out: ?*i64, len: usize, flags: u32) CopyFileRangeError!usize { | |
| 36 | const rc = std.c.copy_file_range(fd_in, off_in, fd_out, off_out, len, flags); | |
| 37 | switch (errno(rc)) { | |
| 38 | .SUCCESS => return @intCast(rc), | |
| 39 | .BADF => return error.BadFileFlags, | |
| 40 | .FBIG => return error.FileTooBig, | |
| 41 | .INTR => return error.Interrupted, | |
| 42 | .INVAL => return error.InvalidArguments, | |
| 43 | .IO => return error.InputOutput, | |
| 44 | .INTEGRITY => return error.CorruptedData, | |
| 45 | .ISDIR => return error.IsDir, | |
| 46 | .NOSPC => return error.NoSpaceLeft, | |
| 47 | else => |err| return unexpectedErrno(err), | |
| 48 | } | |
| 49 | } |
lib/std/os/linux.zig+66-1| ... | ... | @@ -9453,7 +9453,7 @@ pub const wrapped = struct { |
| 9453 | 9453 | const sendfileSymbol = if (lfs64_abi) system.sendfile64 else system.sendfile; |
| 9454 | 9454 | const rc = sendfileSymbol(out_fd, in_fd, in_offset, adjusted_len); |
| 9455 | 9455 | switch (errno(rc)) { |
| 9456 | .SUCCESS => return @bitCast(rc), | |
| 9456 | .SUCCESS => return @intCast(rc), | |
| 9457 | 9457 | .BADF => return invalidApiUsage(), // Always a race condition. |
| 9458 | 9458 | .FAULT => return invalidApiUsage(), // Segmentation fault. |
| 9459 | 9459 | .OVERFLOW => return unexpectedErrno(.OVERFLOW), // We avoid passing too large of a `count`. |
| ... | ... | @@ -9469,6 +9469,71 @@ pub const wrapped = struct { |
| 9469 | 9469 | } |
| 9470 | 9470 | } |
| 9471 | 9471 | |
| 9472 | pub const CopyFileRangeError = std.posix.UnexpectedError || error{ | |
| 9473 | /// One of: | |
| 9474 | /// * One or more file descriptors are not valid. | |
| 9475 | /// * fd_in is not open for reading; or fd_out is not open for writing. | |
| 9476 | /// * The O_APPEND flag is set for the open file description referred | |
| 9477 | /// to by the file descriptor fd_out. | |
| 9478 | BadFileFlags, | |
| 9479 | /// One of: | |
| 9480 | /// * An attempt was made to write at a position past the maximum file | |
| 9481 | /// offset the kernel supports. | |
| 9482 | /// * An attempt was made to write a range that exceeds the allowed | |
| 9483 | /// maximum file size. The maximum file size differs between | |
| 9484 | /// filesystem implementations and can be different from the maximum | |
| 9485 | /// allowed file offset. | |
| 9486 | /// * An attempt was made to write beyond the process's file size | |
| 9487 | /// resource limit. This may also result in the process receiving a | |
| 9488 | /// SIGXFSZ signal. | |
| 9489 | FileTooBig, | |
| 9490 | /// One of: | |
| 9491 | /// * either fd_in or fd_out is not a regular file | |
| 9492 | /// * flags argument is not zero | |
| 9493 | /// * fd_in and fd_out refer to the same file and the source and target ranges overlap. | |
| 9494 | InvalidArguments, | |
| 9495 | /// A low-level I/O error occurred while copying. | |
| 9496 | InputOutput, | |
| 9497 | /// Either fd_in or fd_out refers to a directory. | |
| 9498 | IsDir, | |
| 9499 | OutOfMemory, | |
| 9500 | /// There is not enough space on the target filesystem to complete the copy. | |
| 9501 | NoSpaceLeft, | |
| 9502 | /// (since Linux 5.19) the filesystem does not support this operation. | |
| 9503 | OperationNotSupported, | |
| 9504 | /// The requested source or destination range is too large to represent | |
| 9505 | /// in the specified data types. | |
| 9506 | Overflow, | |
| 9507 | /// fd_out refers to an immutable file. | |
| 9508 | PermissionDenied, | |
| 9509 | /// Either fd_in or fd_out refers to an active swap file. | |
| 9510 | SwapFile, | |
| 9511 | /// The files referred to by fd_in and fd_out are not on the same | |
| 9512 | /// filesystem, and the source and target filesystems are not of the | |
| 9513 | /// same type, or do not support cross-filesystem copy. | |
| 9514 | NotSameFileSystem, | |
| 9515 | }; | |
| 9516 | ||
| 9517 | pub fn copy_file_range(fd_in: fd_t, off_in: ?*i64, fd_out: fd_t, off_out: ?*i64, len: usize, flags: u32) CopyFileRangeError!usize { | |
| 9518 | const rc = system.copy_file_range(fd_in, off_in, fd_out, off_out, len, flags); | |
| 9519 | switch (errno(rc)) { | |
| 9520 | .SUCCESS => return @intCast(rc), | |
| 9521 | .BADF => return error.BadFileFlags, | |
| 9522 | .FBIG => return error.FileTooBig, | |
| 9523 | .INVAL => return error.InvalidArguments, | |
| 9524 | .IO => return error.InputOutput, | |
| 9525 | .ISDIR => return error.IsDir, | |
| 9526 | .NOMEM => return error.OutOfMemory, | |
| 9527 | .NOSPC => return error.NoSpaceLeft, | |
| 9528 | .OPNOTSUPP => return error.OperationNotSupported, | |
| 9529 | .OVERFLOW => return error.Overflow, | |
| 9530 | .PERM => return error.PermissionDenied, | |
| 9531 | .TXTBSY => return error.SwapFile, | |
| 9532 | .XDEV => return error.NotSameFileSystem, | |
| 9533 | else => |err| return unexpectedErrno(err), | |
| 9534 | } | |
| 9535 | } | |
| 9536 | ||
| 9472 | 9537 | const unexpectedErrno = std.posix.unexpectedErrno; |
| 9473 | 9538 | |
| 9474 | 9539 | fn invalidApiUsage() error{Unexpected} { |
lib/std/posix.zig-90| ... | ... | @@ -6601,96 +6601,6 @@ fn count_iovec_bytes(iovs: []const iovec_const) usize { |
| 6601 | 6601 | return count; |
| 6602 | 6602 | } |
| 6603 | 6603 | |
| 6604 | pub const CopyFileRangeError = error{ | |
| 6605 | FileTooBig, | |
| 6606 | InputOutput, | |
| 6607 | /// `fd_in` is not open for reading; or `fd_out` is not open for writing; | |
| 6608 | /// or the `APPEND` flag is set for `fd_out`. | |
| 6609 | FilesOpenedWithWrongFlags, | |
| 6610 | IsDir, | |
| 6611 | OutOfMemory, | |
| 6612 | NoSpaceLeft, | |
| 6613 | Unseekable, | |
| 6614 | PermissionDenied, | |
| 6615 | SwapFile, | |
| 6616 | CorruptedData, | |
| 6617 | } || PReadError || PWriteError || UnexpectedError; | |
| 6618 | ||
| 6619 | /// Transfer data between file descriptors at specified offsets. | |
| 6620 | /// | |
| 6621 | /// Returns the number of bytes written, which can less than requested. | |
| 6622 | /// | |
| 6623 | /// The `copy_file_range` call copies `len` bytes from one file descriptor to another. When possible, | |
| 6624 | /// this is done within the operating system kernel, which can provide better performance | |
| 6625 | /// characteristics than transferring data from kernel to user space and back, such as with | |
| 6626 | /// `pread` and `pwrite` calls. | |
| 6627 | /// | |
| 6628 | /// `fd_in` must be a file descriptor opened for reading, and `fd_out` must be a file descriptor | |
| 6629 | /// opened for writing. They may be any kind of file descriptor; however, if `fd_in` is not a regular | |
| 6630 | /// file system file, it may cause this function to fall back to calling `pread` and `pwrite`, in which case | |
| 6631 | /// atomicity guarantees no longer apply. | |
| 6632 | /// | |
| 6633 | /// If `fd_in` and `fd_out` are the same, source and target ranges must not overlap. | |
| 6634 | /// The file descriptor seek positions are ignored and not updated. | |
| 6635 | /// When `off_in` is past the end of the input file, it successfully reads 0 bytes. | |
| 6636 | /// | |
| 6637 | /// `flags` has different meanings per operating system; refer to the respective man pages. | |
| 6638 | /// | |
| 6639 | /// These systems support in-kernel data copying: | |
| 6640 | /// * Linux (cross-filesystem from version 5.3) | |
| 6641 | /// * FreeBSD 13.0 | |
| 6642 | /// | |
| 6643 | /// Other systems fall back to calling `pread` / `pwrite`. | |
| 6644 | /// | |
| 6645 | /// Maximum offsets on Linux and FreeBSD are `maxInt(i64)`. | |
| 6646 | 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 { | |
| 6647 | if (builtin.os.tag == .freebsd or | |
| 6648 | (comptime builtin.os.tag == .linux and std.c.versionCheck(.{ .major = 2, .minor = 27, .patch = 0 }))) | |
| 6649 | { | |
| 6650 | var off_in_copy: i64 = @bitCast(off_in); | |
| 6651 | var off_out_copy: i64 = @bitCast(off_out); | |
| 6652 | ||
| 6653 | while (true) { | |
| 6654 | const rc = system.copy_file_range(fd_in, &off_in_copy, fd_out, &off_out_copy, len, flags); | |
| 6655 | if (native_os == .freebsd) { | |
| 6656 | switch (errno(rc)) { | |
| 6657 | .SUCCESS => return @intCast(rc), | |
| 6658 | .BADF => return error.FilesOpenedWithWrongFlags, | |
| 6659 | .FBIG => return error.FileTooBig, | |
| 6660 | .IO => return error.InputOutput, | |
| 6661 | .ISDIR => return error.IsDir, | |
| 6662 | .NOSPC => return error.NoSpaceLeft, | |
| 6663 | .INVAL => break, // these may not be regular files, try fallback | |
| 6664 | .INTEGRITY => return error.CorruptedData, | |
| 6665 | .INTR => continue, | |
| 6666 | else => |err| return unexpectedErrno(err), | |
| 6667 | } | |
| 6668 | } else { // assume linux | |
| 6669 | switch (errno(rc)) { | |
| 6670 | .SUCCESS => return @intCast(rc), | |
| 6671 | .BADF => return error.FilesOpenedWithWrongFlags, | |
| 6672 | .FBIG => return error.FileTooBig, | |
| 6673 | .IO => return error.InputOutput, | |
| 6674 | .ISDIR => return error.IsDir, | |
| 6675 | .NOSPC => return error.NoSpaceLeft, | |
| 6676 | .INVAL => break, // these may not be regular files, try fallback | |
| 6677 | .NOMEM => return error.OutOfMemory, | |
| 6678 | .OVERFLOW => return error.Unseekable, | |
| 6679 | .PERM => return error.PermissionDenied, | |
| 6680 | .TXTBSY => return error.SwapFile, | |
| 6681 | .XDEV => break, // support for cross-filesystem copy added in Linux 5.3, use fallback | |
| 6682 | else => |err| return unexpectedErrno(err), | |
| 6683 | } | |
| 6684 | } | |
| 6685 | } | |
| 6686 | } | |
| 6687 | ||
| 6688 | var buf: [8 * 4096]u8 = undefined; | |
| 6689 | const amt_read = try pread(fd_in, buf[0..@min(buf.len, len)], off_in); | |
| 6690 | if (amt_read == 0) return 0; | |
| 6691 | return pwrite(fd_out, buf[0..amt_read], off_out); | |
| 6692 | } | |
| 6693 | ||
| 6694 | 6604 | pub const PollError = error{ |
| 6695 | 6605 | /// The network subsystem has failed. |
| 6696 | 6606 | NetworkSubsystemFailed, |