|
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 = std.builtin.Os; const maxInt = std.math.maxInt; const is_windows = builtin.os.tag == .windows; |
File |
pub const File = struct { handle: Handle, capable_io_mode: io.ModeOverride = io.default_mode, intended_io_mode: io.ModeOverride = io.default_mode, pub const Handle = os.fd_t; pub const Mode = os.mode_t; pub const INode = os.ino_t; pub const Uid = os.uid_t; pub const Gid = os.gid_t; pub const Kind = enum { block_device, character_device, directory, named_pipe, sym_link, file, unix_domain_socket, whiteout, door, event_port, 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, InvalidUtf8, BadPathName, Unexpected, NetworkNotFound, } || os.OpenError || os.FlockError; pub const OpenMode = enum { read_only, write_only, read_write, }; pub const Lock = enum { none, shared, exclusive, }; pub const OpenFlags = struct { mode: OpenMode = .read_only, lock: Lock = .none, lock_nonblocking: bool = false, intended_io_mode: io.ModeOverride = io.default_mode, allow_ctty: bool = false, |
isRead() The OS-specific file descriptor or file handle. 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, |
pub fn isRead(self: OpenFlags) bool { return self.mode != .write_only; } |
isWrite() |
pub fn isWrite(self: OpenFlags) bool { return self.mode != .read_only; } }; pub const CreateFlags = struct { read: bool = false, truncate: bool = true, exclusive: bool = false, lock: Lock = .none, lock_nonblocking: bool = false, mode: Mode = default_mode, intended_io_mode: io.ModeOverride = io.default_mode, }; |
close() Whether the file will be created with read access. If the file already exists, and is a regular file, and the access mode allows writing, it will be truncated to length 0. Ensures that this open call creates the file, otherwise causes |
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); } } pub const SyncError = os.SyncError; |
sync() Blocks until all pending file contents and metadata modifications for the file have been synchronized with the underlying filesystem. |
pub fn sync(self: File) SyncError!void { return os.fsync(self.handle); } |
isTty() Test whether the file refers to a terminal. See also |
pub fn isTty(self: File) bool { return os.isatty(self.handle); } |
supportsAnsiEscapeCodes()Test whether ANSI escape codes will be treated as such. |
pub fn supportsAnsiEscapeCodes(self: File) bool { if (builtin.os.tag == .windows) { var console_mode: os.windows.DWORD = 0; if (os.windows.kernel32.GetConsoleMode(self.handle, &console_mode) != 0) { if (console_mode & os.windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING != 0) return true; } 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) { if (os.getenvZ("TERM")) |term| { if (std.mem.eql(u8, term, "dumb")) return false; } } return true; } return false; } pub const SetEndPosError = os.TruncateError; |
setEndPos()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; |
seekBy()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); } |
seekFromEnd()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); } |
seekTo()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 GetSeekPosError = os.SeekError || os.FStatError; |
getPos()TODO: integrate with async I/O |
pub fn getPos(self: File) GetSeekPosError!u64 { return os.lseek_CUR_get(self.handle); } |
getEndPos()TODO: integrate with async I/O |
pub fn getEndPos(self: File) GetSeekPosError!u64 { if (builtin.os.tag == .windows) { return windows.GetFileSizeEx(self.handle); } return (try self.stat()).size; } pub const ModeError = os.FStatError; |
mode()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 { inode: INode, size: u64, mode: Mode, kind: Kind, atime: i128, mtime: i128, ctime: i128, |
fromSystem() 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. |
pub fn fromSystem(st: os.system.Stat) Stat { const atime = st.atime(); const mtime = st.mtime(); const ctime = st.ctime(); const kind: Kind = if (builtin.os.tag == .wasi and !builtin.link_libc) switch (st.filetype) { .BLOCK_DEVICE => .block_device, .CHARACTER_DEVICE => .character_device, .DIRECTORY => .directory, .SYMBOLIC_LINK => .sym_link, .REGULAR_FILE => .file, .SOCKET_STREAM, .SOCKET_DGRAM => .unix_domain_socket, else => .unknown, } else blk: { const m = st.mode & os.S.IFMT; switch (m) { os.S.IFBLK => break :blk .block_device, os.S.IFCHR => break :blk .character_device, os.S.IFDIR => break :blk .directory, os.S.IFIFO => break :blk .named_pipe, os.S.IFLNK => break :blk .sym_link, os.S.IFREG => break :blk .file, os.S.IFSOCK => break :blk .unix_domain_socket, else => {}, } if (builtin.os.tag.isSolarish()) switch (m) { os.S.IFDOOR => break :blk .door, os.S.IFPORT => break :blk .event_port, else => {}, }; break :blk .unknown; }; return Stat{ .inode = st.ino, .size = @as(u64, @bitCast(st.size)), .mode = st.mode, .kind = kind, .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 StatError = os.FStatError; |
stat()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 here indicates that there is more information available than was able to be stored in the buffer // size provided. This is treated as success because the type of variable-length information that this would be relevant for // (name, volume name, etc) we don't care about. .BUFFER_OVERFLOW => {}, .INVALID_PARAMETER => unreachable, .ACCESS_DENIED => return error.AccessDenied, else => return windows.unexpectedStatus(rc), } return Stat{ .inode = info.InternalInformation.IndexNumber, .size = @as(u64, @bitCast(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); return Stat.fromSystem(st); } pub const ChmodError = std.os.FChmodError; |
chmod()Changes the mode of the file. The process must have the correct privileges in order to do this successfully, or must have the effective user ID matching the owner of the file. |
pub fn chmod(self: File, new_mode: Mode) ChmodError!void { try os.fchmod(self.handle, new_mode); } pub const ChownError = std.os.FChownError; |
chown() Changes the owner and group of the file. The process must have the correct privileges in order to do this successfully. The group may be changed by the owner of the file to any group of which the owner is a member. If the owner or group is specified as |
pub fn chown(self: File, owner: ?Uid, group: ?Gid) ChownError!void { try os.fchown(self.handle, owner, group); } pub const Permissions = struct { inner: switch (builtin.os.tag) { .windows => PermissionsWindows, else => PermissionsUnix, }, const Self = @This(); |
readOnly() Cross-platform representation of permissions on a file. The |
pub fn readOnly(self: Self) bool { return self.inner.readOnly(); } |
setReadOnly() Sets whether write permissions are provided. On Unix, this affects *all* classes. If this is undesired, use |
pub fn setReadOnly(self: *Self, read_only: bool) void { self.inner.setReadOnly(read_only); } }; pub const PermissionsWindows = struct { attributes: os.windows.DWORD, const Self = @This(); |
readOnly() Returns |
pub fn readOnly(self: Self) bool { return self.attributes & os.windows.FILE_ATTRIBUTE_READONLY != 0; } |
setReadOnly() Sets whether write permissions are provided. This method *DOES NOT* set permissions on the filesystem: use |
pub fn setReadOnly(self: *Self, read_only: bool) void { if (read_only) { self.attributes |= os.windows.FILE_ATTRIBUTE_READONLY; } else { self.attributes &= ~@as(os.windows.DWORD, os.windows.FILE_ATTRIBUTE_READONLY); } } }; pub const PermissionsUnix = struct { mode: Mode, const Self = @This(); |
readOnly() Returns |
pub fn readOnly(self: Self) bool { return self.mode & 0o222 == 0; } |
setReadOnly() Sets whether write permissions are provided. This affects *all* classes. If this is undesired, use |
pub fn setReadOnly(self: *Self, read_only: bool) void { if (read_only) { self.mode &= ~@as(Mode, 0o222); } else { self.mode |= @as(Mode, 0o222); } } pub const Class = enum(u2) { user = 2, group = 1, other = 0, }; pub const Permission = enum(u3) { read = 0o4, write = 0o2, execute = 0o1, }; |
unixHas() Returns |
pub fn unixHas(self: Self, class: Class, permission: Permission) bool { const mask = @as(Mode, @intFromEnum(permission)) << @as(u3, @intFromEnum(class)) * 3; return self.mode & mask != 0; } |
unixSet() Sets the permissions for the chosen class. Any permissions set to |
pub fn unixSet(self: *Self, class: Class, permissions: struct { read: ?bool = null, write: ?bool = null, execute: ?bool = null, }) void { const shift = @as(u3, @intFromEnum(class)) * 3; if (permissions.read) |r| { if (r) { self.mode |= @as(Mode, 0o4) << shift; } else { self.mode &= ~(@as(Mode, 0o4) << shift); } } if (permissions.write) |w| { if (w) { self.mode |= @as(Mode, 0o2) << shift; } else { self.mode &= ~(@as(Mode, 0o2) << shift); } } if (permissions.execute) |x| { if (x) { self.mode |= @as(Mode, 0o1) << shift; } else { self.mode &= ~(@as(Mode, 0o1) << shift); } } } |
unixNew() Returns a |
pub fn unixNew(new_mode: Mode) Self { return Self{ .mode = new_mode, }; } }; pub const SetPermissionsError = ChmodError; |
setPermissions() Sets permissions according to the provided |
pub fn setPermissions(self: File, permissions: Permissions) SetPermissionsError!void { switch (builtin.os.tag) { .windows => { var io_status_block: windows.IO_STATUS_BLOCK = undefined; var info = windows.FILE_BASIC_INFORMATION{ .CreationTime = 0, .LastAccessTime = 0, .LastWriteTime = 0, .ChangeTime = 0, .FileAttributes = permissions.inner.attributes, }; const rc = windows.ntdll.NtSetInformationFile( self.handle, &io_status_block, &info, @sizeOf(windows.FILE_BASIC_INFORMATION), .FileBasicInformation, ); switch (rc) { .SUCCESS => return, .INVALID_HANDLE => unreachable, .ACCESS_DENIED => return error.AccessDenied, else => return windows.unexpectedStatus(rc), } }, .wasi => @compileError("Unsupported OS"), // Wasi filesystem does not *yet* support chmod else => { try self.chmod(permissions.inner.mode); }, } } pub const Metadata = struct { inner: switch (builtin.os.tag) { .windows => MetadataWindows, .linux => MetadataLinux, else => MetadataUnix, }, const Self = @This(); |
size() Cross-platform representation of file metadata. Platform-specific functionality is available through the |
pub fn size(self: Self) u64 { return self.inner.size(); } |
permissions() Returns a |
pub fn permissions(self: Self) Permissions { return self.inner.permissions(); } |
kind() Returns the |
pub fn kind(self: Self) Kind { return self.inner.kind(); } |
accessed()Returns the last time the file was accessed in nanoseconds since UTC 1970-01-01 |
pub fn accessed(self: Self) i128 { return self.inner.accessed(); } |
modified()Returns the time the file was modified in nanoseconds since UTC 1970-01-01 |
pub fn modified(self: Self) i128 { return self.inner.modified(); } |
created()Returns the time the file was created in nanoseconds since UTC 1970-01-01 On Windows, this cannot return null On Linux, this returns null if the filesystem does not support creation times, or if the kernel is older than 4.11 On Unices, this returns null if the filesystem or OS does not support creation times On MacOS, this returns the ctime if the filesystem does not support creation times; this is insanity, and yet another reason to hate on Apple |
pub fn created(self: Self) ?i128 { return self.inner.created(); } }; pub const MetadataUnix = struct { stat: os.Stat, const Self = @This(); |
size()Returns the size of the file |
pub fn size(self: Self) u64 { return @as(u64, @intCast(self.stat.size)); } |
permissions() Returns a |
pub fn permissions(self: Self) Permissions { return Permissions{ .inner = PermissionsUnix{ .mode = self.stat.mode } }; } |
kind() Returns the |
pub fn kind(self: Self) Kind { if (builtin.os.tag == .wasi and !builtin.link_libc) return switch (self.stat.filetype) { .BLOCK_DEVICE => .block_device, .CHARACTER_DEVICE => .character_device, .DIRECTORY => .directory, .SYMBOLIC_LINK => .sym_link, .REGULAR_FILE => .file, .SOCKET_STREAM, .SOCKET_DGRAM => .unix_domain_socket, else => .unknown, }; const m = self.stat.mode & os.S.IFMT; switch (m) { os.S.IFBLK => return .block_device, os.S.IFCHR => return .character_device, os.S.IFDIR => return .directory, os.S.IFIFO => return .named_pipe, os.S.IFLNK => return .sym_link, os.S.IFREG => return .file, os.S.IFSOCK => return .unix_domain_socket, else => {}, } if (builtin.os.tag.isSolarish()) switch (m) { os.S.IFDOOR => return .door, os.S.IFPORT => return .event_port, else => {}, }; return .unknown; } |
accessed()Returns the last time the file was accessed in nanoseconds since UTC 1970-01-01 |
pub fn accessed(self: Self) i128 { const atime = self.stat.atime(); return @as(i128, atime.tv_sec) * std.time.ns_per_s + atime.tv_nsec; } |
modified()Returns the last time the file was modified in nanoseconds since UTC 1970-01-01 |
pub fn modified(self: Self) i128 { const mtime = self.stat.mtime(); return @as(i128, mtime.tv_sec) * std.time.ns_per_s + mtime.tv_nsec; } |
created()Returns the time the file was created in nanoseconds since UTC 1970-01-01. Returns null if this is not supported by the OS or filesystem |
pub fn created(self: Self) ?i128 { if (!@hasDecl(@TypeOf(self.stat), "birthtime")) return null; const birthtime = self.stat.birthtime(); // If the filesystem doesn't support this the value *should* be: // On FreeBSD: tv_nsec = 0, tv_sec = -1 // On NetBSD and OpenBSD: tv_nsec = 0, tv_sec = 0 // On MacOS, it is set to ctime -- we cannot detect this!! switch (builtin.os.tag) { .freebsd => if (birthtime.tv_sec == -1 and birthtime.tv_nsec == 0) return null, .netbsd, .openbsd => if (birthtime.tv_sec == 0 and birthtime.tv_nsec == 0) return null, .macos => {}, else => @compileError("Creation time detection not implemented for OS"), } return @as(i128, birthtime.tv_sec) * std.time.ns_per_s + birthtime.tv_nsec; } }; pub const MetadataLinux = struct { statx: os.linux.Statx, const Self = @This(); |
size() |
pub fn size(self: Self) u64 { return self.statx.size; } |
permissions() Returns a |
pub fn permissions(self: Self) Permissions { return Permissions{ .inner = PermissionsUnix{ .mode = self.statx.mode } }; } |
kind() Returns the |
pub fn kind(self: Self) Kind { const m = self.statx.mode & os.S.IFMT; switch (m) { os.S.IFBLK => return .block_device, os.S.IFCHR => return .character_device, os.S.IFDIR => return .directory, os.S.IFIFO => return .named_pipe, os.S.IFLNK => return .sym_link, os.S.IFREG => return .file, os.S.IFSOCK => return .unix_domain_socket, else => {}, } return .unknown; } |
accessed()Returns the last time the file was accessed in nanoseconds since UTC 1970-01-01 |
pub fn accessed(self: Self) i128 { return @as(i128, self.statx.atime.tv_sec) * std.time.ns_per_s + self.statx.atime.tv_nsec; } |
modified()Returns the last time the file was modified in nanoseconds since UTC 1970-01-01 |
pub fn modified(self: Self) i128 { return @as(i128, self.statx.mtime.tv_sec) * std.time.ns_per_s + self.statx.mtime.tv_nsec; } |
created()Returns the time the file was created in nanoseconds since UTC 1970-01-01. Returns null if this is not supported by the filesystem, or on kernels before than version 4.11 |
pub fn created(self: Self) ?i128 { if (self.statx.mask & os.linux.STATX_BTIME == 0) return null; return @as(i128, self.statx.btime.tv_sec) * std.time.ns_per_s + self.statx.btime.tv_nsec; } }; pub const MetadataWindows = struct { attributes: windows.DWORD, reparse_tag: windows.DWORD, _size: u64, access_time: i128, modified_time: i128, creation_time: i128, const Self = @This(); |
size()Returns the size of the file |
pub fn size(self: Self) u64 { return self._size; } |
permissions() Returns a |
pub fn permissions(self: Self) Permissions { return Permissions{ .inner = PermissionsWindows{ .attributes = self.attributes } }; } |
kind() Returns the |
pub fn kind(self: Self) Kind { if (self.attributes & windows.FILE_ATTRIBUTE_REPARSE_POINT != 0) { if (self.reparse_tag & 0x20000000 != 0) { return .sym_link; } } else if (self.attributes & windows.FILE_ATTRIBUTE_DIRECTORY != 0) { return .directory; } else { return .file; } return .unknown; } |
accessed()Returns the last time the file was accessed in nanoseconds since UTC 1970-01-01 |
pub fn accessed(self: Self) i128 { return self.access_time; } |
modified()Returns the time the file was modified in nanoseconds since UTC 1970-01-01 |
pub fn modified(self: Self) i128 { return self.modified_time; } |
created()Returns the time the file was created in nanoseconds since UTC 1970-01-01. This never returns null, only returning an optional for compatibility with other OSes |
pub fn created(self: Self) ?i128 { return self.creation_time; } }; pub const MetadataError = os.FStatError; |
metadata() |
pub fn metadata(self: File) MetadataError!Metadata { return Metadata{ .inner = switch (builtin.os.tag) { .windows => blk: { 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 here indicates that there is more information available than was able to be stored in the buffer // size provided. This is treated as success because the type of variable-length information that this would be relevant for // (name, volume name, etc) we don't care about. .BUFFER_OVERFLOW => {}, .INVALID_PARAMETER => unreachable, .ACCESS_DENIED => return error.AccessDenied, else => return windows.unexpectedStatus(rc), } const reparse_tag: windows.DWORD = reparse_blk: { if (info.BasicInformation.FileAttributes & windows.FILE_ATTRIBUTE_REPARSE_POINT != 0) { var reparse_buf: [windows.MAXIMUM_REPARSE_DATA_BUFFER_SIZE]u8 = undefined; try windows.DeviceIoControl(self.handle, windows.FSCTL_GET_REPARSE_POINT, null, reparse_buf[0..]); const reparse_struct: *const windows.REPARSE_DATA_BUFFER = @ptrCast(@alignCast(&reparse_buf[0])); break :reparse_blk reparse_struct.ReparseTag; } break :reparse_blk 0; }; break :blk MetadataWindows{ .attributes = info.BasicInformation.FileAttributes, .reparse_tag = reparse_tag, ._size = @as(u64, @bitCast(info.StandardInformation.EndOfFile)), .access_time = windows.fromSysTime(info.BasicInformation.LastAccessTime), .modified_time = windows.fromSysTime(info.BasicInformation.LastWriteTime), .creation_time = windows.fromSysTime(info.BasicInformation.CreationTime), }; }, .linux => blk: { var stx = mem.zeroes(os.linux.Statx); const rcx = os.linux.statx(self.handle, "\x00", os.linux.AT.EMPTY_PATH, os.linux.STATX_TYPE | os.linux.STATX_MODE | os.linux.STATX_ATIME | os.linux.STATX_MTIME | os.linux.STATX_BTIME, &stx); switch (os.errno(rcx)) { .SUCCESS => {}, // NOSYS happens when `statx` is unsupported, which is the case on kernel versions before 4.11 // Here, we call `fstat` and fill `stx` with the data we need .NOSYS => { const st = try os.fstat(self.handle); stx.mode = @as(u16, @intCast(st.mode)); // Hacky conversion from timespec to statx_timestamp stx.atime = std.mem.zeroes(os.linux.statx_timestamp); stx.atime.tv_sec = st.atim.tv_sec; stx.atime.tv_nsec = @as(u32, @intCast(st.atim.tv_nsec)); // Guaranteed to succeed (tv_nsec is always below 10^9) stx.mtime = std.mem.zeroes(os.linux.statx_timestamp); stx.mtime.tv_sec = st.mtim.tv_sec; stx.mtime.tv_nsec = @as(u32, @intCast(st.mtim.tv_nsec)); stx.mask = os.linux.STATX_BASIC_STATS | os.linux.STATX_MTIME; }, .BADF => unreachable, .FAULT => unreachable, .NOMEM => return error.SystemResources, else => |err| return os.unexpectedErrno(err), } break :blk MetadataLinux{ .statx = stx, }; }, else => blk: { const st = try os.fstat(self.handle); break :blk MetadataUnix{ .stat = st, }; }, }, }; } pub const UpdateTimesError = os.FutimensError || windows.SetFileTimeError; |
updateTimes()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, atime: i128, 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)) orelse maxInt(isize), .tv_nsec = math.cast(isize, @mod(atime, std.time.ns_per_s)) orelse maxInt(isize), }, os.timespec{ .tv_sec = math.cast(isize, @divFloor(mtime, std.time.ns_per_s)) orelse maxInt(isize), .tv_nsec = math.cast(isize, @mod(mtime, std.time.ns_per_s)) orelse maxInt(isize), }, }; try os.futimens(self.handle, ×); } |
readToEndAlloc() access timestamp in nanoseconds last modification timestamp in nanoseconds 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 |
pub fn readToEndAlloc(self: File, allocator: mem.Allocator, max_bytes: usize) ![]u8 { return self.readToEndAllocOptions(allocator, max_bytes, null, @alignOf(u8), null); } |
readToEndAllocOptions() 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 |
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) + @intFromBool(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| { return try array_list.toOwnedSliceSentinel(sentinel); } else { return try array_list.toOwnedSlice(); } } pub const ReadError = os.ReadError; pub const PReadError = os.PReadError; |
read() |
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); } } |
readAll() Returns the number of bytes read. If the number read is smaller than |
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; } |
pread()On Windows, this function currently does alter the file pointer. https://github.com/ziglang/zig/issues/12783 |
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); } } |
preadAll() Returns the number of bytes read. If the number read is smaller than |
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; } |
readv()See https://github.com/ziglang/zig/issues/7699 |
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); } } |
readvAll() 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. |
pub fn readvAll(self: File, iovecs: []os.iovec) ReadError!usize { if (iovecs.len == 0) return 0; // We use the address of this local variable for all zero-length // vectors so that the OS does not complain that we are giving it // addresses outside the application's address space. var garbage: [1]u8 = undefined; for (iovecs) |*v| { if (v.iov_len == 0) v.iov_base = &garbage; } 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; } } |
preadv()See https://github.com/ziglang/zig/issues/7699 On Windows, this function currently does alter the file pointer. https://github.com/ziglang/zig/issues/12783 |
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); } } |
preadvAll() 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 |
pub fn preadvAll(self: File, iovecs: []os.iovec, offset: u64) PReadError!usize { if (iovecs.len == 0) return 0; 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; |
write() |
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); } } |
writeAll() |
pub fn writeAll(self: File, bytes: []const u8) WriteError!void { var index: usize = 0; while (index < bytes.len) { index += try self.write(bytes[index..]); } } |
pwrite()On Windows, this function currently does alter the file pointer. https://github.com/ziglang/zig/issues/12783 |
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); } } |
pwriteAll()On Windows, this function currently does alter the file pointer. https://github.com/ziglang/zig/issues/12783 |
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); } } |
writev() See https://github.com/ziglang/zig/issues/7699 See equivalent function: |
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); } } |
writevAll() The |
pub fn writevAll(self: File, iovecs: []os.iovec_const) WriteError!void { if (iovecs.len == 0) return; // We use the address of this local variable for all zero-length // vectors so that the OS does not complain that we are giving it // addresses outside the application's address space. var garbage: [1]u8 = undefined; for (iovecs) |*v| { if (v.iov_len == 0) v.iov_base = &garbage; } 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; } } |
pwritev()See https://github.com/ziglang/zig/issues/7699 On Windows, this function currently does alter the file pointer. https://github.com/ziglang/zig/issues/12783 |
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); } } |
pwritevAll() The |
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; |
copyRange() |
pub fn copyRange(in: File, in_offset: u64, out: File, out_offset: u64, len: u64) CopyRangeError!u64 { const adjusted_len = math.cast(usize, len) orelse math.maxInt(usize); const result = try os.copy_file_range(in.handle, in_offset, out.handle, out_offset, adjusted_len, 0); return result; } |
copyRangeAll() Returns the number of bytes copied. If the number read is smaller than |
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, in_len: ?u64 = null, headers_and_trailers: []os.iovec_const = &[0]os.iovec_const{}, header_count: usize = 0, }; pub const WriteFileError = ReadError || error{EndOfStream} || WriteError; |
writeFileAll() |
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.NetworkUnreachable, error.NetworkSubsystemFailed, => return self.writeFileAllUnseekable(in_file, args), else => |e| return e, }; } |
writeFileAllUnseekable() Does not try seeking in either of the File parameters. See |
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); try in_file.reader().skipBytes(args.in_offset, .{ .buf_size = 4096 }); var fifo = std.fifo.LinearFifo(u8, .{ .Static = 4096 }).init(); if (args.in_len) |len| { var stream = std.io.limitedReader(in_file.reader(), len); try fifo.pump(stream.reader(), self.writer()); } else { try fifo.pump(in_file.reader(), self.writer()); } try self.writevAll(trailers); } 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 = @as(usize, @intCast(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); |
reader() Low level function which can fail for OS-specific reasons. See |
pub fn reader(file: File) Reader { return .{ .context = file }; } pub const Writer = io.Writer(File, WriteError, write); |
writer() |
pub fn writer(file: File) Writer { return .{ .context = file }; } pub const SeekableStream = io.SeekableStream( File, SeekError, GetSeekPosError, seekTo, seekBy, getPos, getEndPos, ); |
seekableStream() |
pub fn seekableStream(file: File) SeekableStream { return .{ .context = file }; } const range_off: windows.LARGE_INTEGER = 0; const range_len: windows.LARGE_INTEGER = 1; pub const LockError = error{ SystemResources, FileLocksNotSupported, } || os.UnexpectedError; |
lock() Blocks when an incompatible lock is held by another process. A process may hold only one type of lock (shared or exclusive) on a file. When a process terminates in any way, the lock is released. |
pub fn lock(file: File, l: Lock) LockError!void { if (is_windows) { var io_status_block: windows.IO_STATUS_BLOCK = undefined; const exclusive = switch (l) { .none => return, .shared => false, .exclusive => true, }; return windows.LockFile( file.handle, null, null, null, &io_status_block, &range_off, &range_len, null, windows.FALSE, // non-blocking=false @intFromBool(exclusive), ) catch |err| switch (err) { error.WouldBlock => unreachable, // non-blocking=false else => |e| return e, }; } else { return os.flock(file.handle, switch (l) { .none => os.LOCK.UN, .shared => os.LOCK.SH, .exclusive => os.LOCK.EX, }) catch |err| switch (err) { error.WouldBlock => unreachable, // non-blocking=false else => |e| return e, }; } } |
unlock()Assumes the file is locked. |
pub fn unlock(file: File) void { if (is_windows) { var io_status_block: windows.IO_STATUS_BLOCK = undefined; return windows.UnlockFile( file.handle, &io_status_block, &range_off, &range_len, null, ) catch |err| switch (err) { error.RangeNotLocked => unreachable, // Function assumes unlocked. error.Unexpected => unreachable, // Resource deallocation must succeed. }; } else { return os.flock(file.handle, os.LOCK.UN) catch |err| switch (err) { error.WouldBlock => unreachable, // unlocking can't block error.SystemResources => unreachable, // We are deallocating resources. error.FileLocksNotSupported => unreachable, // We already got the lock. error.Unexpected => unreachable, // Resource deallocation must succeed. }; } } |
tryLock() Attempts to obtain a lock, returning |
pub fn tryLock(file: File, l: Lock) LockError!bool { if (is_windows) { var io_status_block: windows.IO_STATUS_BLOCK = undefined; const exclusive = switch (l) { .none => return, .shared => false, .exclusive => true, }; windows.LockFile( file.handle, null, null, null, &io_status_block, &range_off, &range_len, null, windows.TRUE, // non-blocking=true @intFromBool(exclusive), ) catch |err| switch (err) { error.WouldBlock => return false, else => |e| return e, }; } else { os.flock(file.handle, switch (l) { .none => os.LOCK.UN, .shared => os.LOCK.SH | os.LOCK.NB, .exclusive => os.LOCK.EX | os.LOCK.NB, }) catch |err| switch (err) { error.WouldBlock => return false, else => |e| return e, }; } return true; } |
downgradeLock() Assumes the file is already locked in exclusive mode. Atomically modifies the lock to be in shared mode, without releasing it. |
pub fn downgradeLock(file: File) LockError!void { if (is_windows) { // On Windows it works like a semaphore + exclusivity flag. To implement this // function, we first obtain another lock in shared mode. This changes the // exclusivity flag, but increments the semaphore to 2. So we follow up with // an NtUnlockFile which decrements the semaphore but does not modify the // exclusivity flag. var io_status_block: windows.IO_STATUS_BLOCK = undefined; windows.LockFile( file.handle, null, null, null, &io_status_block, &range_off, &range_len, null, windows.TRUE, // non-blocking=true windows.FALSE, // exclusive=false ) catch |err| switch (err) { error.WouldBlock => unreachable, // File was not locked in exclusive mode. else => |e| return e, }; return windows.UnlockFile( file.handle, &io_status_block, &range_off, &range_len, null, ) catch |err| switch (err) { error.RangeNotLocked => unreachable, // File was not locked. error.Unexpected => unreachable, // Resource deallocation must succeed. }; } else { return os.flock(file.handle, os.LOCK.SH | os.LOCK.NB) catch |err| switch (err) { error.WouldBlock => unreachable, // File was not locked in exclusive mode. else => |e| return e, }; } } }; |
Generated by zstd-browse2 on 2023-11-04 14:12:27 -0400. |