authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-06-24 07:48:52-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-07-01 16:35:30-07:00
log25a0648176ce0e0061a035fdf602795368a3b40e
treee5562574fda02d17c3d095baf6b58232d819f381
parent7926081ec35761028c87cdb82204f177311866be

std: move copy_file_range, fcopyfile impls and usage


6 files changed, 229 insertions(+), 166 deletions(-)

lib/std/fs/Dir.zig+31-71
...@@ -2609,7 +2609,7 @@ pub fn updateFile(...@@ -2609,7 +2609,7 @@ pub fn updateFile(
2609 try dest_dir.makePath(dirname);2609 try dest_dir.makePath(dirname);
2610 }2610 }
26112611
2612 var buffer: [2000]u8 = undefined;2612 var buffer: [1000]u8 = undefined; // Used only when direct fd-to-fd is not available.
2613 var atomic_file = try dest_dir.atomicFile(dest_path, .{2613 var atomic_file = try dest_dir.atomicFile(dest_path, .{
2614 .mode = actual_mode,2614 .mode = actual_mode,
2615 .write_buffer = &buffer,2615 .write_buffer = &buffer,
...@@ -2619,7 +2619,7 @@ pub fn updateFile(...@@ -2619,7 +2619,7 @@ pub fn updateFile(
2619 var src_reader: File.Reader = .initSize(src_file, &.{}, src_stat.size);2619 var src_reader: File.Reader = .initSize(src_file, &.{}, src_stat.size);
2620 const dest_writer = &atomic_file.file_writer.interface;2620 const dest_writer = &atomic_file.file_writer.interface;
26212621
2622 dest_writer.writeFileAll(&src_reader, .{}) catch |err| switch (err) {2622 _ = dest_writer.sendFileAll(&src_reader, .unlimited) catch |err| switch (err) {
2623 error.ReadFailed => return src_reader.err.?,2623 error.ReadFailed => return src_reader.err.?,
2624 error.WriteFailed => return atomic_file.file_writer.err.?,2624 error.WriteFailed => return atomic_file.file_writer.err.?,
2625 };2625 };
...@@ -2628,16 +2628,22 @@ pub fn updateFile(...@@ -2628,16 +2628,22 @@ pub fn updateFile(
2628 return .stale;2628 return .stale;
2629}2629}
26302630
2631pub const CopyFileError = File.OpenError || File.StatError ||2631pub const CopyFileError = File.OpenError || File.StatError || File.ReadError || File.WriteError ||
2632 AtomicFile.InitError || CopyFileRawError || AtomicFile.FinishError;2632 AtomicFile.InitError || AtomicFile.FinishError;
26332633
2634/// Guaranteed to be atomic.2634/// Atomically creates a new file at `dest_path` within `dest_dir` with the
2635/// On Linux, until https://patchwork.kernel.org/patch/9636735/ is merged and readily available,2635/// same contents as `source_path` within `source_dir`, overwriting any already
2636/// there is a possibility of power loss or application termination leaving temporary files present2636/// existing file.
2637/// in the same directory as dest_path.2637///
2638/// On Windows, both paths should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).2638/// On Linux, until https://patchwork.kernel.org/patch/9636735/ is merged and
2639/// On WASI, both paths should be encoded as valid UTF-8.2639/// readily available, there is a possibility of power loss or application
2640/// On other platforms, both paths are an opaque sequence of bytes with no particular encoding.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.
2641pub fn copyFile(2647pub fn copyFile(
2642 source_dir: Dir,2648 source_dir: Dir,
2643 source_path: []const u8,2649 source_path: []const u8,
...@@ -2645,74 +2651,28 @@ pub fn copyFile(...@@ -2645,74 +2651,28 @@ pub fn copyFile(
2645 dest_path: []const u8,2651 dest_path: []const u8,
2646 options: CopyFileOptions,2652 options: CopyFileOptions,
2647) CopyFileError!void {2653) CopyFileError!void {
2648 var in_file = try source_dir.openFile(source_path, .{});2654 var file_reader: File.Reader = .init(try source_dir.openFile(source_path, .{}), &.{});
2649 defer in_file.close();2655 defer file_reader.file.close();
26502656
2651 var size: ?u64 = null;
2652 const mode = options.override_mode orelse blk: {2657 const mode = options.override_mode orelse blk: {
2653 const st = try in_file.stat();2658 const st = try file_reader.file.stat();
2654 size = st.size;2659 file_reader.size = st.size;
2655 break :blk st.mode;2660 break :blk st.mode;
2656 };2661 };
26572662
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 defer atomic_file.deinit();2668 defer atomic_file.deinit();
26602669
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 try atomic_file.finish();2674 try atomic_file.finish();
2663}2675 _ = size;
2664
2665const 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.
2670fn 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 }
2716}2676}
27172677
2718pub const AtomicFileOptions = struct {2678pub const AtomicFileOptions = struct {
lib/std/fs/File.zig+82-4
...@@ -961,7 +961,7 @@ pub const Reader = struct {...@@ -961,7 +961,7 @@ pub const Reader = struct {
961 };961 };
962 }962 }
963963
964 pub fn initSize(file: File, buffer: []u8, size: u64) Reader {964 pub fn initSize(file: File, buffer: []u8, size: ?u64) Reader {
965 return .{965 return .{
966 .file = file,966 .file = file,
967 .interface = initInterface(buffer),967 .interface = initInterface(buffer),
...@@ -1099,7 +1099,6 @@ pub const Reader = struct {...@@ -1099,7 +1099,6 @@ pub const Reader = struct {
1099 var iovecs_buffer: [max_buffers_len]posix.iovec = undefined;1099 var iovecs_buffer: [max_buffers_len]posix.iovec = undefined;
1100 const dest = try w.writableVectorPosix(&iovecs_buffer, limit);1100 const dest = try w.writableVectorPosix(&iovecs_buffer, limit);
1101 assert(dest[0].len > 0);1101 assert(dest[0].len > 0);
1102 // TODO also add buffer at the end
1103 const n = posix.readv(r.file.handle, dest) catch |err| {1102 const n = posix.readv(r.file.handle, dest) catch |err| {
1104 r.err = err;1103 r.err = err;
1105 return error.ReadFailed;1104 return error.ReadFailed;
...@@ -1251,6 +1250,8 @@ pub const Writer = struct {...@@ -1251,6 +1250,8 @@ pub const Writer = struct {
1251 mode: Writer.Mode = .positional,1250 mode: Writer.Mode = .positional,
1252 pos: u64 = 0,1251 pos: u64 = 0,
1253 sendfile_err: ?SendfileError = null,1252 sendfile_err: ?SendfileError = null,
1253 copy_file_range_err: ?CopyFileRangeError = null,
1254 fcopyfile_err: ?FcopyfileError = null,
1254 seek_err: ?SeekError = null,1255 seek_err: ?SeekError = null,
1255 interface: std.io.Writer,1256 interface: std.io.Writer,
12561257
...@@ -1265,6 +1266,14 @@ pub const Writer = struct {...@@ -1265,6 +1266,14 @@ pub const Writer = struct {
1265 Unexpected,1266 Unexpected,
1266 };1267 };
12671268
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 /// Number of slices to store on the stack, when trying to send as many byte1277 /// Number of slices to store on the stack, when trying to send as many byte
1269 /// vectors through the underlying write calls as possible.1278 /// vectors through the underlying write calls as possible.
1270 const max_buffers_len = 16;1279 const max_buffers_len = 16;
...@@ -1408,7 +1417,6 @@ pub const Writer = struct {...@@ -1408,7 +1417,6 @@ pub const Writer = struct {
1408 const w: *Writer = @fieldParentPtr("interface", io_writer);1417 const w: *Writer = @fieldParentPtr("interface", io_writer);
1409 const out_fd = w.file.handle;1418 const out_fd = w.file.handle;
1410 const in_fd = file_reader.file.handle;1419 const in_fd = file_reader.file.handle;
1411 // TODO try using copy_file_range on Linux
1412 // TODO try using copy_file_range on FreeBSD1420 // TODO try using copy_file_range on FreeBSD
1413 // TODO try using sendfile on macOS1421 // TODO try using sendfile on macOS
1414 // TODO try using sendfile on FreeBSD1422 // TODO try using sendfile on FreeBSD
...@@ -1416,7 +1424,8 @@ pub const Writer = struct {...@@ -1416,7 +1424,8 @@ pub const Writer = struct {
1416 // Try using sendfile on Linux.1424 // Try using sendfile on Linux.
1417 if (w.sendfile_err != null) break :sf;1425 if (w.sendfile_err != null) break :sf;
1418 // Linux sendfile does not support headers.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 const max_count = 0x7ffff000; // Avoid EINVAL.1429 const max_count = 0x7ffff000; // Avoid EINVAL.
1421 var off: std.os.linux.off_t = undefined;1430 var off: std.os.linux.off_t = undefined;
1422 const off_ptr: ?*std.os.linux.off_t, const count: usize = switch (file_reader.mode) {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,6 +1464,75 @@ pub const Writer = struct {
1455 w.pos += n;1464 w.pos += n;
1456 return n;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 return error.Unimplemented;1536 return error.Unimplemented;
1459 }1537 }
14601538
lib/std/os.zig+1
...@@ -31,6 +31,7 @@ pub const uefi = @import("os/uefi.zig");...@@ -31,6 +31,7 @@ pub const uefi = @import("os/uefi.zig");
31pub const wasi = @import("os/wasi.zig");31pub const wasi = @import("os/wasi.zig");
32pub const emscripten = @import("os/emscripten.zig");32pub const emscripten = @import("os/emscripten.zig");
33pub const windows = @import("os/windows.zig");33pub const windows = @import("os/windows.zig");
34pub const freebsd = @import("os/freebsd.zig");
3435
35test {36test {
36 _ = linux;37 _ = linux;
lib/std/os/freebsd.zig created+49
...@@ -0,0 +1,49 @@
1const std = @import("../std.zig");
2const fd_t = std.c.fd_t;
3const off_t = std.c.off_t;
4const unexpectedErrno = std.posix.unexpectedErrno;
5const errno = std.posix.errno;
6
7pub 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
35pub 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,7 +9453,7 @@ pub const wrapped = struct {
9453 const sendfileSymbol = if (lfs64_abi) system.sendfile64 else system.sendfile;9453 const sendfileSymbol = if (lfs64_abi) system.sendfile64 else system.sendfile;
9454 const rc = sendfileSymbol(out_fd, in_fd, in_offset, adjusted_len);9454 const rc = sendfileSymbol(out_fd, in_fd, in_offset, adjusted_len);
9455 switch (errno(rc)) {9455 switch (errno(rc)) {
9456 .SUCCESS => return @bitCast(rc),9456 .SUCCESS => return @intCast(rc),
9457 .BADF => return invalidApiUsage(), // Always a race condition.9457 .BADF => return invalidApiUsage(), // Always a race condition.
9458 .FAULT => return invalidApiUsage(), // Segmentation fault.9458 .FAULT => return invalidApiUsage(), // Segmentation fault.
9459 .OVERFLOW => return unexpectedErrno(.OVERFLOW), // We avoid passing too large of a `count`.9459 .OVERFLOW => return unexpectedErrno(.OVERFLOW), // We avoid passing too large of a `count`.
...@@ -9469,6 +9469,71 @@ pub const wrapped = struct {...@@ -9469,6 +9469,71 @@ pub const wrapped = struct {
9469 }9469 }
9470 }9470 }
94719471
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 const unexpectedErrno = std.posix.unexpectedErrno;9537 const unexpectedErrno = std.posix.unexpectedErrno;
94739538
9474 fn invalidApiUsage() error{Unexpected} {9539 fn invalidApiUsage() error{Unexpected} {
lib/std/posix.zig-90
...@@ -6601,96 +6601,6 @@ fn count_iovec_bytes(iovs: []const iovec_const) usize {...@@ -6601,96 +6601,6 @@ fn count_iovec_bytes(iovs: []const iovec_const) usize {
6601 return count;6601 return count;
6602}6602}
66036603
6604pub 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)`.
6646pub 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
6694pub const PollError = error{6604pub const PollError = error{
6695 /// The network subsystem has failed.6605 /// The network subsystem has failed.
6696 NetworkSubsystemFailed,6606 NetworkSubsystemFailed,