// SPDX-License-Identifier: MIT // Copyright (c) 2015-2020 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. const std = @import("../std.zig"); const builtin = @import("builtin"); const os = std.os; const io = std.io; const mem = std.mem; const math = std.math; const assert = std.debug.assert; const windows = os.windows; const Os = builtin.Os; const maxInt = std.math.maxInt; const is_windows = std.Target.current.os.tag == .windows; pub const File = struct { /// The OS-specific file descriptor or file handle. handle: os.fd_t, /// On some systems, such as Linux, file system file descriptors are incapable of non-blocking I/O. /// This forces us to perform asynchronous I/O on a dedicated thread, to achieve non-blocking /// file-system I/O. To do this, `File` must be aware of whether it is a file system file descriptor, /// or, more specifically, whether the I/O is always blocking. capable_io_mode: io.ModeOverride = io.default_mode, /// Furthermore, even when `std.io.mode` is async, it is still sometimes desirable to perform blocking I/O, /// although not by default. For example, when printing a stack trace to stderr. /// This field tracks both by acting as an overriding I/O mode. When not building in async I/O mode, /// the type only has the `.blocking` tag, making it a zero-bit type. intended_io_mode: io.ModeOverride = io.default_mode, pub const Mode = os.mode_t; pub const INode = os.ino_t; pub const Kind = enum { BlockDevice, CharacterDevice, Directory, NamedPipe, SymLink, File, UnixDomainSocket, Whiteout, Unknown, }; pub const default_mode = switch (builtin.os.tag) { .windows => 0, .wasi => 0, else => 0o666, }; pub const OpenError = error{ SharingViolation, PathAlreadyExists, FileNotFound, AccessDenied, PipeBusy, NameTooLong, /// On Windows, file paths must be valid Unicode. InvalidUtf8, /// On Windows, file paths cannot contain these characters: /// '/', '*', '?', '"', '<', '>', '|' BadPathName, Unexpected, } || os.OpenError || os.FlockError; pub const Lock = enum { None, Shared, Exclusive }; /// TODO https://github.com/ziglang/zig/issues/3802 pub const OpenFlags = struct { read: bool = true, write: bool = false, /// Open the file with a lock to prevent other processes from accessing it at the /// same time. An exclusive lock will prevent other processes from acquiring a lock. /// A shared lock will prevent other processes from acquiring a exclusive lock, but /// doesn't prevent other process from getting their own shared locks. /// /// Note that the lock is only advisory on Linux, except in very specific cirsumstances[1]. /// This means that a process that does not respect the locking API can still get access /// to the file, despite the lock. /// /// Windows' file locks are mandatory, and any process attempting to access the file will /// receive an error. /// /// [1]: https://www.kernel.org/doc/Documentation/filesystems/mandatory-locking.txt lock: Lock = .None, /// Sets whether or not to wait until the file is locked to return. If set to true, /// `error.WouldBlock` will be returned. Otherwise, the file will wait until the file /// is available to proceed. /// In async I/O mode, non-blocking at the OS level is /// determined by `intended_io_mode`, and `true` means `error.WouldBlock` is returned, /// and `false` means `error.WouldBlock` is handled by the event loop. lock_nonblocking: bool = false, /// Setting this to `.blocking` prevents `O_NONBLOCK` from being passed even /// if `std.io.is_async`. It allows the use of `nosuspend` when calling functions /// related to opening the file, reading, writing, and locking. intended_io_mode: io.ModeOverride = io.default_mode, /// Set this to allow the opened file to automatically become the /// controlling TTY for the current process. allow_ctty: bool = false, }; /// TODO https://github.com/ziglang/zig/issues/3802 pub const CreateFlags = struct { /// Whether the file will be created with read access. read: bool = false, /// If the file already exists, and is a regular file, and the access /// mode allows writing, it will be truncated to length 0. truncate: bool = true, /// Ensures that this open call creates the file, otherwise causes /// `error.FileAlreadyExists` to be returned. exclusive: bool = false, /// Open the file with a lock to prevent other processes from accessing it at the /// same time. An exclusive lock will prevent other processes from acquiring a lock. /// A shared lock will prevent other processes from acquiring a exclusive lock, but /// doesn't prevent other process from getting their own shared locks. /// /// Note that the lock is only advisory on Linux, except in very specific cirsumstances[1]. /// This means that a process that does not respect the locking API can still get access /// to the file, despite the lock. /// /// Windows's file locks are mandatory, and any process attempting to access the file will /// receive an error. /// /// [1]: https://www.kernel.org/doc/Documentation/filesystems/mandatory-locking.txt lock: Lock = .None, /// Sets whether or not to wait until the file is locked to return. If set to true, /// `error.WouldBlock` will be returned. Otherwise, the file will wait until the file /// is available to proceed. /// In async I/O mode, non-blocking at the OS level is /// determined by `intended_io_mode`, and `true` means `error.WouldBlock` is returned, /// and `false` means `error.WouldBlock` is handled by the event loop. lock_nonblocking: bool = false, /// For POSIX systems this is the file system mode the file will /// be created with. mode: Mode = default_mode, /// Setting this to `.blocking` prevents `O_NONBLOCK` from being passed even /// if `std.io.is_async`. It allows the use of `nosuspend` when calling functions /// related to opening the file, reading, writing, and locking. intended_io_mode: io.ModeOverride = io.default_mode, }; /// Upon success, the stream is in an uninitialized state. To continue using it, /// you must use the open() function. pub fn close(self: File) void { if (is_windows) { windows.CloseHandle(self.handle); } else if (self.capable_io_mode != self.intended_io_mode) { std.event.Loop.instance.?.close(self.handle); } else { os.close(self.handle); } } /// Test whether the file refers to a terminal. /// See also `supportsAnsiEscapeCodes`. pub fn isTty(self: File) bool { return os.isatty(self.handle); } /// Test whether ANSI escape codes will be treated as such. pub fn supportsAnsiEscapeCodes(self: File) bool { if (builtin.os.tag == .windows) { return os.isCygwinPty(self.handle); } if (builtin.os.tag == .wasi) { // WASI sanitizes stdout when fd is a tty so ANSI escape codes // will not be interpreted as actual cursor commands, and // stderr is always sanitized. return false; } if (self.isTty()) { if (self.handle == os.STDOUT_FILENO or self.handle == os.STDERR_FILENO) { // Use getenvC to workaround https://github.com/ziglang/zig/issues/3511 if (os.getenvZ("TERM")) |term| { if (std.mem.eql(u8, term, "dumb")) return false; } } return true; } return false; } pub const SetEndPosError = os.TruncateError; /// Shrinks or expands the file. /// The file offset after this call is left unchanged. pub fn setEndPos(self: File, length: u64) SetEndPosError!void { try os.ftruncate(self.handle, length); } pub const SeekError = os.SeekError; /// Repositions read/write file offset relative to the current offset. /// TODO: integrate with async I/O pub fn seekBy(self: File, offset: i64) SeekError!void { return os.lseek_CUR(self.handle, offset); } /// Repositions read/write file offset relative to the end. /// TODO: integrate with async I/O pub fn seekFromEnd(self: File, offset: i64) SeekError!void { return os.lseek_END(self.handle, offset); } /// Repositions read/write file offset relative to the beginning. /// TODO: integrate with async I/O pub fn seekTo(self: File, offset: u64) SeekError!void { return os.lseek_SET(self.handle, offset); } pub const GetPosError = os.SeekError || os.FStatError; /// TODO: integrate with async I/O pub fn getPos(self: File) GetPosError!u64 { return os.lseek_CUR_get(self.handle); } /// TODO: integrate with async I/O pub fn getEndPos(self: File) GetPosError!u64 { if (builtin.os.tag == .windows) { return windows.GetFileSizeEx(self.handle); } return (try self.stat()).size; } pub const ModeError = os.FStatError; /// TODO: integrate with async I/O pub fn mode(self: File) ModeError!Mode { if (builtin.os.tag == .windows) { return 0; } return (try self.stat()).mode; } pub const Stat = struct { /// A number that the system uses to point to the file metadata. This number is not guaranteed to be /// unique across time, as some file systems may reuse an inode after its file has been deleted. /// Some systems may change the inode of a file over time. /// /// On Linux, the inode is a structure that stores the metadata, and the inode _number_ is what /// you see here: the index number of the inode. /// /// The FileIndex on Windows is similar. It is a number for a file that is unique to each filesystem. inode: INode, size: u64, mode: Mode, kind: Kind, /// Access time in nanoseconds, relative to UTC 1970-01-01. atime: i128, /// Last modification time in nanoseconds, relative to UTC 1970-01-01. mtime: i128, /// Creation time in nanoseconds, relative to UTC 1970-01-01. ctime: i128, }; pub const StatError = os.FStatError; /// TODO: integrate with async I/O pub fn stat(self: File) StatError!Stat { if (builtin.os.tag == .windows) { var io_status_block: windows.IO_STATUS_BLOCK = undefined; var info: windows.FILE_ALL_INFORMATION = undefined; const rc = windows.ntdll.NtQueryInformationFile(self.handle, &io_status_block, &info, @sizeOf(windows.FILE_ALL_INFORMATION), .FileAllInformation); switch (rc) { .SUCCESS => {}, .BUFFER_OVERFLOW => {}, .INVALID_PARAMETER => unreachable, .ACCESS_DENIED => return error.AccessDenied, else => return windows.unexpectedStatus(rc), } return Stat{ .inode = info.InternalInformation.IndexNumber, .size = @bitCast(u64, info.StandardInformation.EndOfFile), .mode = 0, .kind = if (info.StandardInformation.Directory == 0) .File else .Directory, .atime = windows.fromSysTime(info.BasicInformation.LastAccessTime), .mtime = windows.fromSysTime(info.BasicInformation.LastWriteTime), .ctime = windows.fromSysTime(info.BasicInformation.CreationTime), }; } const st = try os.fstat(self.handle); const atime = st.atime(); const mtime = st.mtime(); const ctime = st.ctime(); return Stat{ .inode = st.ino, .size = @bitCast(u64, st.size), .mode = st.mode, .kind = switch (builtin.os.tag) { .wasi => switch (st.filetype) { os.FILETYPE_BLOCK_DEVICE => Kind.BlockDevice, os.FILETYPE_CHARACTER_DEVICE => Kind.CharacterDevice, os.FILETYPE_DIRECTORY => Kind.Directory, os.FILETYPE_SYMBOLIC_LINK => Kind.SymLink, os.FILETYPE_REGULAR_FILE => Kind.File, os.FILETYPE_SOCKET_STREAM, os.FILETYPE_SOCKET_DGRAM => Kind.UnixDomainSocket, else => Kind.Unknown, }, else => switch (st.mode & os.S_IFMT) { os.S_IFBLK => Kind.BlockDevice, os.S_IFCHR => Kind.CharacterDevice, os.S_IFDIR => Kind.Directory, os.S_IFIFO => Kind.NamedPipe, os.S_IFLNK => Kind.SymLink, os.S_IFREG => Kind.File, os.S_IFSOCK => Kind.UnixDomainSocket, else => Kind.Unknown, }, }, .atime = @as(i128, atime.tv_sec) * std.time.ns_per_s + atime.tv_nsec, .mtime = @as(i128, mtime.tv_sec) * std.time.ns_per_s + mtime.tv_nsec, .ctime = @as(i128, ctime.tv_sec) * std.time.ns_per_s + ctime.tv_nsec, }; } pub const UpdateTimesError = os.FutimensError || windows.SetFileTimeError; /// The underlying file system may have a different granularity than nanoseconds, /// and therefore this function cannot guarantee any precision will be stored. /// Further, the maximum value is limited by the system ABI. When a value is provided /// that exceeds this range, the value is clamped to the maximum. /// TODO: integrate with async I/O pub fn updateTimes( self: File, /// access timestamp in nanoseconds atime: i128, /// last modification timestamp in nanoseconds mtime: i128, ) UpdateTimesError!void { if (builtin.os.tag == .windows) { const atime_ft = windows.nanoSecondsToFileTime(atime); const mtime_ft = windows.nanoSecondsToFileTime(mtime); return windows.SetFileTime(self.handle, null, &atime_ft, &mtime_ft); } const times = [2]os.timespec{ os.timespec{ .tv_sec = math.cast(isize, @divFloor(atime, std.time.ns_per_s)) catch maxInt(isize), .tv_nsec = math.cast(isize, @mod(atime, std.time.ns_per_s)) catch maxInt(isize), }, os.timespec{ .tv_sec = math.cast(isize, @divFloor(mtime, std.time.ns_per_s)) catch maxInt(isize), .tv_nsec = math.cast(isize, @mod(mtime, std.time.ns_per_s)) catch maxInt(isize), }, }; try os.futimens(self.handle, ×); } /// Reads all the bytes from the current position to the end of the file. /// On success, caller owns returned buffer. /// If the file is larger than `max_bytes`, returns `error.FileTooBig`. pub fn readToEndAlloc(self: File, allocator: *mem.Allocator, max_bytes: usize) ![]u8 { return self.readToEndAllocOptions(allocator, max_bytes, null, @alignOf(u8), null); } /// Reads all the bytes from the current position to the end of the file. /// On success, caller owns returned buffer. /// If the file is larger than `max_bytes`, returns `error.FileTooBig`. /// If `size_hint` is specified the initial buffer size is calculated using /// that value, otherwise an arbitrary value is used instead. /// Allows specifying alignment and a sentinel value. pub fn readToEndAllocOptions( self: File, allocator: *mem.Allocator, max_bytes: usize, size_hint: ?usize, comptime alignment: u29, comptime optional_sentinel: ?u8, ) !(if (optional_sentinel) |s| [:s]align(alignment) u8 else []align(alignment) u8) { // If no size hint is provided fall back to the size=0 code path const size = size_hint orelse 0; // The file size returned by stat is used as hint to set the buffer // size. If the reported size is zero, as it happens on Linux for files // in /proc, a small buffer is allocated instead. const initial_cap = (if (size > 0) size else 1024) + @boolToInt(optional_sentinel != null); var array_list = try std.ArrayListAligned(u8, alignment).initCapacity(allocator, initial_cap); defer array_list.deinit(); self.reader().readAllArrayListAligned(alignment, &array_list, max_bytes) catch |err| switch (err) { error.StreamTooLong => return error.FileTooBig, else => |e| return e, }; if (optional_sentinel) |sentinel| { try array_list.append(sentinel); const buf = array_list.toOwnedSlice(); return buf[0 .. buf.len - 1 :sentinel]; } else { return array_list.toOwnedSlice(); } } pub const ReadError = os.ReadError; pub const PReadError = os.PReadError; pub fn read(self: File, buffer: []u8) ReadError!usize { if (is_windows) { return windows.ReadFile(self.handle, buffer, null, self.intended_io_mode); } if (self.intended_io_mode == .blocking) { return os.read(self.handle, buffer); } else { return std.event.Loop.instance.?.read(self.handle, buffer, self.capable_io_mode != self.intended_io_mode); } } /// Returns the number of bytes read. If the number read is smaller than `buffer.len`, it /// means the file reached the end. Reaching the end of a file is not an error condition. pub fn readAll(self: File, buffer: []u8) ReadError!usize { var index: usize = 0; while (index != buffer.len) { const amt = try self.read(buffer[index..]); if (amt == 0) break; index += amt; } return index; } pub fn pread(self: File, buffer: []u8, offset: u64) PReadError!usize { if (is_windows) { return windows.ReadFile(self.handle, buffer, offset, self.intended_io_mode); } if (self.intended_io_mode == .blocking) { return os.pread(self.handle, buffer, offset); } else { return std.event.Loop.instance.?.pread(self.handle, buffer, offset, self.capable_io_mode != self.intended_io_mode); } } /// Returns the number of bytes read. If the number read is smaller than `buffer.len`, it /// means the file reached the end. Reaching the end of a file is not an error condition. pub fn preadAll(self: File, buffer: []u8, offset: u64) PReadError!usize { var index: usize = 0; while (index != buffer.len) { const amt = try self.pread(buffer[index..], offset + index); if (amt == 0) break; index += amt; } return index; } pub fn readv(self: File, iovecs: []const os.iovec) ReadError!usize { if (is_windows) { // TODO improve this to use ReadFileScatter if (iovecs.len == 0) return @as(usize, 0); const first = iovecs[0]; return windows.ReadFile(self.handle, first.iov_base[0..first.iov_len], null, self.intended_io_mode); } if (self.intended_io_mode == .blocking) { return os.readv(self.handle, iovecs); } else { return std.event.Loop.instance.?.readv(self.handle, iovecs, self.capable_io_mode != self.intended_io_mode); } } /// Returns the number of bytes read. If the number read is smaller than the total bytes /// from all the buffers, it means the file reached the end. Reaching the end of a file /// is not an error condition. /// The `iovecs` parameter is mutable because this function needs to mutate the fields in /// order to handle partial reads from the underlying OS layer. pub fn readvAll(self: File, iovecs: []os.iovec) ReadError!usize { if (iovecs.len == 0) return; var i: usize = 0; var off: usize = 0; while (true) { var amt = try self.readv(iovecs[i..]); var eof = amt == 0; off += amt; while (amt >= iovecs[i].iov_len) { amt -= iovecs[i].iov_len; i += 1; if (i >= iovecs.len) return off; eof = false; } if (eof) return off; iovecs[i].iov_base += amt; iovecs[i].iov_len -= amt; } } pub fn preadv(self: File, iovecs: []const os.iovec, offset: u64) PReadError!usize { if (is_windows) { // TODO improve this to use ReadFileScatter if (iovecs.len == 0) return @as(usize, 0); const first = iovecs[0]; return windows.ReadFile(self.handle, first.iov_base[0..first.iov_len], offset, self.intended_io_mode); } if (self.intended_io_mode == .blocking) { return os.preadv(self.handle, iovecs, offset); } else { return std.event.Loop.instance.?.preadv(self.handle, iovecs, offset, self.capable_io_mode != self.intended_io_mode); } } /// Returns the number of bytes read. If the number read is smaller than the total bytes /// from all the buffers, it means the file reached the end. Reaching the end of a file /// is not an error condition. /// The `iovecs` parameter is mutable because this function needs to mutate the fields in /// order to handle partial reads from the underlying OS layer. pub fn preadvAll(self: File, iovecs: []const os.iovec, offset: u64) PReadError!void { if (iovecs.len == 0) return; var i: usize = 0; var off: usize = 0; while (true) { var amt = try self.preadv(iovecs[i..], offset + off); var eof = amt == 0; off += amt; while (amt >= iovecs[i].iov_len) { amt -= iovecs[i].iov_len; i += 1; if (i >= iovecs.len) return off; eof = false; } if (eof) return off; iovecs[i].iov_base += amt; iovecs[i].iov_len -= amt; } } pub const WriteError = os.WriteError; pub const PWriteError = os.PWriteError; pub fn write(self: File, bytes: []const u8) WriteError!usize { if (is_windows) { return windows.WriteFile(self.handle, bytes, null, self.intended_io_mode); } if (self.intended_io_mode == .blocking) { return os.write(self.handle, bytes); } else { return std.event.Loop.instance.?.write(self.handle, bytes, self.capable_io_mode != self.intended_io_mode); } } pub fn writeAll(self: File, bytes: []const u8) WriteError!void { var index: usize = 0; while (index < bytes.len) { index += try self.write(bytes[index..]); } } pub fn pwrite(self: File, bytes: []const u8, offset: u64) PWriteError!usize { if (is_windows) { return windows.WriteFile(self.handle, bytes, offset, self.intended_io_mode); } if (self.intended_io_mode == .blocking) { return os.pwrite(self.handle, bytes, offset); } else { return std.event.Loop.instance.?.pwrite(self.handle, bytes, offset, self.capable_io_mode != self.intended_io_mode); } } pub fn pwriteAll(self: File, bytes: []const u8, offset: u64) PWriteError!void { var index: usize = 0; while (index < bytes.len) { index += try self.pwrite(bytes[index..], offset + index); } } pub fn writev(self: File, iovecs: []const os.iovec_const) WriteError!usize { if (is_windows) { // TODO improve this to use WriteFileScatter if (iovecs.len == 0) return @as(usize, 0); const first = iovecs[0]; return windows.WriteFile(self.handle, first.iov_base[0..first.iov_len], null, self.intended_io_mode); } if (self.intended_io_mode == .blocking) { return os.writev(self.handle, iovecs); } else { return std.event.Loop.instance.?.writev(self.handle, iovecs, self.capable_io_mode != self.intended_io_mode); } } /// The `iovecs` parameter is mutable because this function needs to mutate the fields in /// order to handle partial writes from the underlying OS layer. pub fn writevAll(self: File, iovecs: []os.iovec_const) WriteError!void { if (iovecs.len == 0) return; var i: usize = 0; while (true) { var amt = try self.writev(iovecs[i..]); while (amt >= iovecs[i].iov_len) { amt -= iovecs[i].iov_len; i += 1; if (i >= iovecs.len) return; } iovecs[i].iov_base += amt; iovecs[i].iov_len -= amt; } } pub fn pwritev(self: File, iovecs: []os.iovec_const, offset: u64) PWriteError!usize { if (is_windows) { // TODO improve this to use WriteFileScatter if (iovecs.len == 0) return @as(usize, 0); const first = iovecs[0]; return windows.WriteFile(self.handle, first.iov_base[0..first.iov_len], offset, self.intended_io_mode); } if (self.intended_io_mode == .blocking) { return os.pwritev(self.handle, iovecs, offset); } else { return std.event.Loop.instance.?.pwritev(self.handle, iovecs, offset, self.capable_io_mode != self.intended_io_mode); } } /// The `iovecs` parameter is mutable because this function needs to mutate the fields in /// order to handle partial writes from the underlying OS layer. pub fn pwritevAll(self: File, iovecs: []os.iovec_const, offset: u64) PWriteError!void { if (iovecs.len == 0) return; var i: usize = 0; var off: u64 = 0; while (true) { var amt = try self.pwritev(iovecs[i..], offset + off); off += amt; while (amt >= iovecs[i].iov_len) { amt -= iovecs[i].iov_len; i += 1; if (i >= iovecs.len) return; } iovecs[i].iov_base += amt; iovecs[i].iov_len -= amt; } } pub const CopyRangeError = os.CopyFileRangeError; pub fn copyRange(in: File, in_offset: u64, out: File, out_offset: u64, len: u64) CopyRangeError!u64 { const adjusted_len = math.cast(usize, len) catch math.maxInt(usize); const result = try os.copy_file_range(in.handle, in_offset, out.handle, out_offset, adjusted_len, 0); return result; } /// Returns the number of bytes copied. If the number read is smaller than `buffer.len`, it /// means the in file reached the end. Reaching the end of a file is not an error condition. pub fn copyRangeAll(in: File, in_offset: u64, out: File, out_offset: u64, len: u64) CopyRangeError!u64 { var total_bytes_copied: u64 = 0; var in_off = in_offset; var out_off = out_offset; while (total_bytes_copied < len) { const amt_copied = try copyRange(in, in_off, out, out_off, len - total_bytes_copied); if (amt_copied == 0) return total_bytes_copied; total_bytes_copied += amt_copied; in_off += amt_copied; out_off += amt_copied; } return total_bytes_copied; } pub const WriteFileOptions = struct { in_offset: u64 = 0, /// `null` means the entire file. `0` means no bytes from the file. /// When this is `null`, trailers must be sent in a separate writev() call /// due to a flaw in the BSD sendfile API. Other operating systems, such as /// Linux, already do this anyway due to API limitations. /// If the size of the source file is known, passing the size here will save one syscall. in_len: ?u64 = null, headers_and_trailers: []os.iovec_const = &[0]os.iovec_const{}, /// The trailer count is inferred from `headers_and_trailers.len - header_count` header_count: usize = 0, }; pub const WriteFileError = ReadError || WriteError; pub fn writeFileAll(self: File, in_file: File, args: WriteFileOptions) WriteFileError!void { return self.writeFileAllSendfile(in_file, args) catch |err| switch (err) { error.Unseekable, error.FastOpenAlreadyInProgress, error.MessageTooBig, error.FileDescriptorNotASocket, error.AddressFamilyNotSupported, => return self.writeFileAllUnseekable(in_file, args), else => |e| return e, }; } /// Does not try seeking in either of the File parameters. /// See `writeFileAll` as an alternative to calling this. pub fn writeFileAllUnseekable(self: File, in_file: File, args: WriteFileOptions) WriteFileError!void { const headers = args.headers_and_trailers[0..args.header_count]; const trailers = args.headers_and_trailers[args.header_count..]; try self.writevAll(headers); var buffer: [4096]u8 = undefined; { var index: usize = 0; // Skip in_offset bytes. while (index < args.in_offset) { const ask = math.min(buffer.len, args.in_offset - index); const amt = try in_file.read(buffer[0..ask]); index += amt; } } const in_len = args.in_len orelse math.maxInt(u64); var index: usize = 0; while (index < in_len) { const ask = math.min(buffer.len, in_len - index); const amt = try in_file.read(buffer[0..ask]); if (amt == 0) break; index += try self.write(buffer[0..amt]); } try self.writevAll(trailers); } /// Low level function which can fail for OS-specific reasons. /// See `writeFileAll` as an alternative to calling this. /// TODO integrate with async I/O fn writeFileAllSendfile(self: File, in_file: File, args: WriteFileOptions) os.SendFileError!void { const count = blk: { if (args.in_len) |l| { if (l == 0) { return self.writevAll(args.headers_and_trailers); } else { break :blk l; } } else { break :blk 0; } }; const headers = args.headers_and_trailers[0..args.header_count]; const trailers = args.headers_and_trailers[args.header_count..]; const zero_iovec = &[0]os.iovec_const{}; // When reading the whole file, we cannot put the trailers in the sendfile() syscall, // because we have no way to determine whether a partial write is past the end of the file or not. const trls = if (count == 0) zero_iovec else trailers; const offset = args.in_offset; const out_fd = self.handle; const in_fd = in_file.handle; const flags = 0; var amt: usize = 0; hdrs: { var i: usize = 0; while (i < headers.len) { amt = try os.sendfile(out_fd, in_fd, offset, count, headers[i..], trls, flags); while (amt >= headers[i].iov_len) { amt -= headers[i].iov_len; i += 1; if (i >= headers.len) break :hdrs; } headers[i].iov_base += amt; headers[i].iov_len -= amt; } } if (count == 0) { var off: u64 = amt; while (true) { amt = try os.sendfile(out_fd, in_fd, offset + off, 0, zero_iovec, zero_iovec, flags); if (amt == 0) break; off += amt; } } else { var off: u64 = amt; while (off < count) { amt = try os.sendfile(out_fd, in_fd, offset + off, count - off, zero_iovec, trailers, flags); off += amt; } amt = @intCast(usize, off - count); } var i: usize = 0; while (i < trailers.len) { while (amt >= trailers[i].iov_len) { amt -= trailers[i].iov_len; i += 1; if (i >= trailers.len) return; } trailers[i].iov_base += amt; trailers[i].iov_len -= amt; amt = try os.writev(self.handle, trailers[i..]); } } pub const Reader = io.Reader(File, ReadError, read); /// Deprecated: use `Reader` pub const InStream = Reader; pub fn reader(file: File) Reader { return .{ .context = file }; } /// Deprecated: use `reader` pub fn inStream(file: File) Reader { return .{ .context = file }; } pub const Writer = io.Writer(File, WriteError, write); /// Deprecated: use `Writer` pub const OutStream = Writer; pub fn writer(file: File) Writer { return .{ .context = file }; } /// Deprecated: use `writer` pub fn outStream(file: File) Writer { return .{ .context = file }; } pub const SeekableStream = io.SeekableStream( File, SeekError, GetPosError, seekTo, seekBy, getPos, getEndPos, ); pub fn seekableStream(file: File) SeekableStream { return .{ .context = file }; } };