aboutsummaryrefslogtreecommitdiff
path: root/src/copy_file.zig
diff options
context:
space:
mode:
Diffstat (limited to 'src/copy_file.zig')
-rw-r--r--src/copy_file.zig50
1 files changed, 50 insertions, 0 deletions
diff --git a/src/copy_file.zig b/src/copy_file.zig
new file mode 100644
index 000000000..57738363f
--- /dev/null
+++ b/src/copy_file.zig
@@ -0,0 +1,50 @@
+const std = @import("std");
+const os = std.os;
+const math = std.math;
+
+const CopyFileError = error{SystemResources} || os.CopyFileRangeError || os.SendFileError;
+
+// Transfer all the data between two file descriptors in the most efficient way.
+// The copy starts at offset 0, the initial offsets are preserved.
+// No metadata is transferred over.
+pub fn copy(fd_in: os.fd_t, fd_out: os.fd_t) CopyFileError!void {
+ if (comptime std.Target.current.isDarwin()) {
+ const rc = os.system.fcopyfile(fd_in, fd_out, null, os.system.COPYFILE_DATA);
+ switch (os.errno(rc)) {
+ .SUCCESS => return,
+ .INVAL => unreachable,
+ .NOMEM => return error.SystemResources,
+ // The source file is not a directory, symbolic link, or regular file.
+ // Try with the fallback path before giving up.
+ .OPNOTSUPP => {},
+ else => |err| return os.unexpectedErrno(err),
+ }
+ }
+
+ if (std.Target.current.os.tag == .linux) {
+ // Try copy_file_range first as that works at the FS level and is the
+ // most efficient method (if available).
+ var offset: u64 = 0;
+ cfr_loop: while (true) {
+ // The kernel checks the u64 value `offset+count` for overflow, use
+ // a 32 bit value so that the syscall won't return EINVAL except for
+ // impossibly large files (> 2^64-1 - 2^32-1).
+ const amt = try os.copy_file_range(fd_in, offset, fd_out, offset, math.maxInt(u32), 0);
+ // Terminate when no data was copied
+ if (amt == 0) break :cfr_loop;
+ offset += amt;
+ }
+ return;
+ }
+
+ // Sendfile is a zero-copy mechanism iff the OS supports it, otherwise the
+ // fallback code will copy the contents chunk by chunk.
+ const empty_iovec = [0]os.iovec_const{};
+ var offset: u64 = 0;
+ sendfile_loop: while (true) {
+ const amt = try os.sendfile(fd_out, fd_in, offset, 0, &empty_iovec, &empty_iovec, 0);
+ // Terminate when no data was copied
+ if (amt == 0) break :sendfile_loop;
+ offset += amt;
+ }
+}