Commit 0a536a7c90
Changed files (6)
lib/std/fs/AtomicFile.zig
@@ -1,6 +1,6 @@
file: File,
// TODO either replace this with rand_buf or use []u16 on Windows
-tmp_path_buf: [TMP_PATH_LEN:0]u8,
+tmp_path_buf: [tmp_path_len:0]u8,
dest_basename: []const u8,
file_open: bool,
file_exists: bool,
@@ -9,8 +9,8 @@ dir: Dir,
pub const InitError = File.OpenError;
-const RANDOM_BYTES = 12;
-const TMP_PATH_LEN = fs.base64_encoder.calcSize(RANDOM_BYTES);
+pub const random_bytes_len = 12;
+const tmp_path_len = fs.base64_encoder.calcSize(random_bytes_len);
/// Note that the `Dir.atomicFile` API may be more handy than this lower-level function.
pub fn init(
@@ -19,8 +19,8 @@ pub fn init(
dir: Dir,
close_dir_on_deinit: bool,
) InitError!AtomicFile {
- var rand_buf: [RANDOM_BYTES]u8 = undefined;
- var tmp_path_buf: [TMP_PATH_LEN:0]u8 = undefined;
+ var rand_buf: [random_bytes_len]u8 = undefined;
+ var tmp_path_buf: [tmp_path_len:0]u8 = undefined;
while (true) {
std.crypto.random.bytes(rand_buf[0..]);
@@ -81,4 +81,5 @@ const File = std.fs.File;
const Dir = std.fs.Dir;
const fs = std.fs;
const assert = std.debug.assert;
+// https://github.com/ziglang/zig/issues/5019
const posix = std.os;
lib/std/fs/Dir.zig
@@ -2527,6 +2527,7 @@ const builtin = @import("builtin");
const std = @import("../std.zig");
const File = std.fs.File;
const AtomicFile = std.fs.AtomicFile;
+// https://github.com/ziglang/zig/issues/5019
const posix = std.os;
const mem = std.mem;
const fs = std.fs;
lib/std/fs/File.zig
@@ -0,0 +1,1624 @@
+/// The OS-specific file descriptor or file handle.
+handle: 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, `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.options.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 Handle = posix.fd_t;
+pub const Mode = posix.mode_t;
+pub const INode = posix.ino_t;
+pub const Uid = posix.uid_t;
+pub const Gid = posix.gid_t;
+
+pub const Kind = enum {
+ block_device,
+ character_device,
+ directory,
+ named_pipe,
+ sym_link,
+ file,
+ unix_domain_socket,
+ whiteout,
+ door,
+ event_port,
+ unknown,
+};
+
+/// This is the default mode given to POSIX operating systems for creating
+/// files. `0o666` is "-rw-rw-rw-" which is counter-intuitive at first,
+/// since most people would expect "-rw-r--r--", for example, when using
+/// the `touch` command, which would correspond to `0o644`. However, POSIX
+/// libc implementations use `0o666` inside `fopen` and then rely on the
+/// process-scoped "umask" setting to adjust this number for file creation.
+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,
+ /// On Windows, `\\server` or `\\server\share` was not found.
+ NetworkNotFound,
+} || posix.OpenError || posix.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,
+
+ /// Open the file with an advisory lock to coordinate with other processes
+ /// 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 does not prevent
+ /// other process from getting their own shared locks.
+ ///
+ /// The lock is advisory, except on Linux in very specific circumstances[1].
+ /// This means that a process that does not respect the locking API can still get access
+ /// to the file, despite the lock.
+ ///
+ /// On these operating systems, the lock is acquired atomically with
+ /// opening the file:
+ /// * Darwin
+ /// * DragonFlyBSD
+ /// * FreeBSD
+ /// * Haiku
+ /// * NetBSD
+ /// * OpenBSD
+ /// On these operating systems, the lock is acquired via a separate syscall
+ /// after opening the file:
+ /// * Linux
+ /// * Windows
+ ///
+ /// [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,
+
+ pub fn isRead(self: OpenFlags) bool {
+ return self.mode != .write_only;
+ }
+
+ pub fn isWrite(self: OpenFlags) bool {
+ return self.mode != .read_only;
+ }
+};
+
+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.PathAlreadyExists` to be returned.
+ exclusive: bool = false,
+
+ /// Open the file with an advisory lock to coordinate with other processes
+ /// 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 does not prevent
+ /// other process from getting their own shared locks.
+ ///
+ /// The lock is advisory, except on Linux in very specific circumstances[1].
+ /// This means that a process that does not respect the locking API can still get access
+ /// to the file, despite the lock.
+ ///
+ /// On these operating systems, the lock is acquired atomically with
+ /// opening the file:
+ /// * Darwin
+ /// * DragonFlyBSD
+ /// * FreeBSD
+ /// * Haiku
+ /// * NetBSD
+ /// * OpenBSD
+ /// On these operating systems, the lock is acquired via a separate syscall
+ /// after opening the file:
+ /// * Linux
+ /// * Windows
+ ///
+ /// [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. On other systems this is always 0.
+ 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 {
+ posix.close(self.handle);
+ }
+}
+
+pub const SyncError = posix.SyncError;
+
+/// Blocks until all pending file contents and metadata modifications
+/// for the file have been synchronized with the underlying filesystem.
+///
+/// Note that this does not ensure that metadata for the
+/// directory containing the file has also reached disk.
+pub fn sync(self: File) SyncError!void {
+ return posix.fsync(self.handle);
+}
+
+/// Test whether the file refers to a terminal.
+/// See also `supportsAnsiEscapeCodes`.
+pub fn isTty(self: File) bool {
+ return posix.isatty(self.handle);
+}
+
+/// Test whether ANSI escape codes will be treated as such.
+pub fn supportsAnsiEscapeCodes(self: File) bool {
+ if (builtin.os.tag == .windows) {
+ var console_mode: windows.DWORD = 0;
+ if (windows.kernel32.GetConsoleMode(self.handle, &console_mode) != 0) {
+ if (console_mode & windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING != 0) return true;
+ }
+
+ return posix.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 == posix.STDOUT_FILENO or self.handle == posix.STDERR_FILENO) {
+ if (posix.getenvZ("TERM")) |term| {
+ if (std.mem.eql(u8, term, "dumb"))
+ return false;
+ }
+ }
+ return true;
+ }
+ return false;
+}
+
+pub const SetEndPosError = posix.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 posix.ftruncate(self.handle, length);
+}
+
+pub const SeekError = posix.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 posix.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 posix.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 posix.lseek_SET(self.handle, offset);
+}
+
+pub const GetSeekPosError = posix.SeekError || posix.FStatError;
+
+/// TODO: integrate with async I/O
+pub fn getPos(self: File) GetSeekPosError!u64 {
+ return posix.lseek_CUR_get(self.handle);
+}
+
+/// 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 = posix.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,
+ /// This is available on POSIX systems and is always 0 otherwise.
+ 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 fn fromSystem(st: posix.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 & posix.S.IFMT;
+ switch (m) {
+ posix.S.IFBLK => break :blk .block_device,
+ posix.S.IFCHR => break :blk .character_device,
+ posix.S.IFDIR => break :blk .directory,
+ posix.S.IFIFO => break :blk .named_pipe,
+ posix.S.IFLNK => break :blk .sym_link,
+ posix.S.IFREG => break :blk .file,
+ posix.S.IFSOCK => break :blk .unix_domain_socket,
+ else => {},
+ }
+ if (builtin.os.tag.isSolarish()) switch (m) {
+ posix.S.IFDOOR => break :blk .door,
+ posix.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 = posix.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 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 posix.fstat(self.handle);
+ return Stat.fromSystem(st);
+}
+
+pub const ChmodError = posix.FChmodError;
+
+/// 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 posix.fchmod(self.handle, new_mode);
+}
+
+pub const ChownError = posix.FChownError;
+
+/// 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 `null`, the ID is not changed.
+pub fn chown(self: File, owner: ?Uid, group: ?Gid) ChownError!void {
+ try posix.fchown(self.handle, owner, group);
+}
+
+/// Cross-platform representation of permissions on a file.
+/// The `readonly` and `setReadonly` are the only methods available across all platforms.
+/// Platform-specific functionality is available through the `inner` field.
+pub const Permissions = struct {
+ /// You may use the `inner` field to use platform-specific functionality
+ inner: switch (builtin.os.tag) {
+ .windows => PermissionsWindows,
+ else => PermissionsUnix,
+ },
+
+ const Self = @This();
+
+ /// Returns `true` if permissions represent an unwritable file.
+ /// On Unix, `true` is returned only if no class has write permissions.
+ pub fn readOnly(self: Self) bool {
+ return self.inner.readOnly();
+ }
+
+ /// Sets whether write permissions are provided.
+ /// On Unix, this affects *all* classes. If this is undesired, use `unixSet`.
+ /// This method *DOES NOT* set permissions on the filesystem: use `File.setPermissions(permissions)`
+ pub fn setReadOnly(self: *Self, read_only: bool) void {
+ self.inner.setReadOnly(read_only);
+ }
+};
+
+pub const PermissionsWindows = struct {
+ attributes: windows.DWORD,
+
+ const Self = @This();
+
+ /// Returns `true` if permissions represent an unwritable file.
+ pub fn readOnly(self: Self) bool {
+ return self.attributes & windows.FILE_ATTRIBUTE_READONLY != 0;
+ }
+
+ /// Sets whether write permissions are provided.
+ /// This method *DOES NOT* set permissions on the filesystem: use `File.setPermissions(permissions)`
+ pub fn setReadOnly(self: *Self, read_only: bool) void {
+ if (read_only) {
+ self.attributes |= windows.FILE_ATTRIBUTE_READONLY;
+ } else {
+ self.attributes &= ~@as(windows.DWORD, windows.FILE_ATTRIBUTE_READONLY);
+ }
+ }
+};
+
+pub const PermissionsUnix = struct {
+ mode: Mode,
+
+ const Self = @This();
+
+ /// Returns `true` if permissions represent an unwritable file.
+ /// `true` is returned only if no class has write permissions.
+ pub fn readOnly(self: Self) bool {
+ return self.mode & 0o222 == 0;
+ }
+
+ /// Sets whether write permissions are provided.
+ /// This affects *all* classes. If this is undesired, use `unixSet`.
+ /// This method *DOES NOT* set permissions on the filesystem: use `File.setPermissions(permissions)`
+ 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,
+ };
+
+ /// Returns `true` if the chosen class has the selected permission.
+ /// This method is only available on Unix platforms.
+ 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;
+ }
+
+ /// Sets the permissions for the chosen class. Any permissions set to `null` are left unchanged.
+ /// This method *DOES NOT* set permissions on the filesystem: use `File.setPermissions(permissions)`
+ 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);
+ }
+ }
+ }
+
+ /// Returns a `Permissions` struct representing the permissions from the passed mode.
+ pub fn unixNew(new_mode: Mode) Self {
+ return Self{
+ .mode = new_mode,
+ };
+ }
+};
+
+pub const SetPermissionsError = ChmodError;
+
+/// Sets permissions according to the provided `Permissions` struct.
+/// This method is *NOT* available on WASI
+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);
+ },
+ }
+}
+
+/// Cross-platform representation of file metadata.
+/// Platform-specific functionality is available through the `inner` field.
+pub const Metadata = struct {
+ /// You may use the `inner` field to use platform-specific functionality
+ inner: switch (builtin.os.tag) {
+ .windows => MetadataWindows,
+ .linux => MetadataLinux,
+ else => MetadataUnix,
+ },
+
+ const Self = @This();
+
+ /// Returns the size of the file
+ pub fn size(self: Self) u64 {
+ return self.inner.size();
+ }
+
+ /// Returns a `Permissions` struct, representing the permissions on the file
+ pub fn permissions(self: Self) Permissions {
+ return self.inner.permissions();
+ }
+
+ /// Returns the `Kind` of file.
+ /// On Windows, can only return: `.file`, `.directory`, `.sym_link` or `.unknown`
+ pub fn kind(self: Self) Kind {
+ return self.inner.kind();
+ }
+
+ /// 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();
+ }
+
+ /// Returns the time the file was modified in nanoseconds since UTC 1970-01-01
+ pub fn modified(self: Self) i128 {
+ return self.inner.modified();
+ }
+
+ /// 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: posix.Stat,
+
+ const Self = @This();
+
+ /// Returns the size of the file
+ pub fn size(self: Self) u64 {
+ return @as(u64, @intCast(self.stat.size));
+ }
+
+ /// Returns a `Permissions` struct, representing the permissions on the file
+ pub fn permissions(self: Self) Permissions {
+ return Permissions{ .inner = PermissionsUnix{ .mode = self.stat.mode } };
+ }
+
+ /// Returns the `Kind` of the file
+ 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 & posix.S.IFMT;
+
+ switch (m) {
+ posix.S.IFBLK => return .block_device,
+ posix.S.IFCHR => return .character_device,
+ posix.S.IFDIR => return .directory,
+ posix.S.IFIFO => return .named_pipe,
+ posix.S.IFLNK => return .sym_link,
+ posix.S.IFREG => return .file,
+ posix.S.IFSOCK => return .unix_domain_socket,
+ else => {},
+ }
+
+ if (builtin.os.tag.isSolarish()) switch (m) {
+ posix.S.IFDOOR => return .door,
+ posix.S.IFPORT => return .event_port,
+ else => {},
+ };
+
+ return .unknown;
+ }
+
+ /// 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;
+ }
+
+ /// 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;
+ }
+
+ /// 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;
+ }
+};
+
+/// `MetadataUnix`, but using Linux's `statx` syscall.
+/// On Linux versions below 4.11, `statx` will be filled with data from stat.
+pub const MetadataLinux = struct {
+ statx: std.os.linux.Statx,
+
+ const Self = @This();
+
+ /// Returns the size of the file
+ pub fn size(self: Self) u64 {
+ return self.statx.size;
+ }
+
+ /// Returns a `Permissions` struct, representing the permissions on the file
+ pub fn permissions(self: Self) Permissions {
+ return Permissions{ .inner = PermissionsUnix{ .mode = self.statx.mode } };
+ }
+
+ /// Returns the `Kind` of the file
+ pub fn kind(self: Self) Kind {
+ const m = self.statx.mode & posix.S.IFMT;
+
+ switch (m) {
+ posix.S.IFBLK => return .block_device,
+ posix.S.IFCHR => return .character_device,
+ posix.S.IFDIR => return .directory,
+ posix.S.IFIFO => return .named_pipe,
+ posix.S.IFLNK => return .sym_link,
+ posix.S.IFREG => return .file,
+ posix.S.IFSOCK => return .unix_domain_socket,
+ else => {},
+ }
+
+ return .unknown;
+ }
+
+ /// 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;
+ }
+
+ /// 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;
+ }
+
+ /// 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 & std.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();
+
+ /// Returns the size of the file
+ pub fn size(self: Self) u64 {
+ return self._size;
+ }
+
+ /// Returns a `Permissions` struct, representing the permissions on the file
+ pub fn permissions(self: Self) Permissions {
+ return Permissions{ .inner = PermissionsWindows{ .attributes = self.attributes } };
+ }
+
+ /// Returns the `Kind` of the file.
+ /// Can only return: `.file`, `.directory`, `.sym_link` or `.unknown`
+ 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;
+ }
+
+ /// 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;
+ }
+
+ /// Returns the time the file was modified in nanoseconds since UTC 1970-01-01
+ pub fn modified(self: Self) i128 {
+ return self.modified_time;
+ }
+
+ /// 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 = posix.FStatError;
+
+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: {
+ const l = std.os.linux;
+ var stx = std.mem.zeroes(l.Statx);
+ const rcx = l.statx(self.handle, "\x00", l.AT.EMPTY_PATH, l.STATX_TYPE |
+ l.STATX_MODE | l.STATX_ATIME | l.STATX_MTIME | l.STATX_BTIME, &stx);
+
+ switch (posix.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 posix.fstat(self.handle);
+
+ stx.mode = @as(u16, @intCast(st.mode));
+
+ // Hacky conversion from timespec to statx_timestamp
+ stx.atime = std.mem.zeroes(l.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(l.statx_timestamp);
+ stx.mtime.tv_sec = st.mtim.tv_sec;
+ stx.mtime.tv_nsec = @as(u32, @intCast(st.mtim.tv_nsec));
+
+ stx.mask = l.STATX_BASIC_STATS | l.STATX_MTIME;
+ },
+ .BADF => unreachable,
+ .FAULT => unreachable,
+ .NOMEM => return error.SystemResources,
+ else => |err| return posix.unexpectedErrno(err),
+ }
+
+ break :blk MetadataLinux{
+ .statx = stx,
+ };
+ },
+ else => blk: {
+ const st = try posix.fstat(self.handle);
+ break :blk MetadataUnix{
+ .stat = st,
+ };
+ },
+ },
+ };
+}
+
+pub const UpdateTimesError = posix.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]posix.timespec{
+ posix.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),
+ },
+ posix.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 posix.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: 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: 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 = posix.ReadError;
+pub const PReadError = posix.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 posix.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;
+}
+
+/// 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 posix.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.
+/// On Windows, this function currently does alter the file pointer.
+/// https://github.com/ziglang/zig/issues/12783
+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;
+}
+
+/// See https://github.com/ziglang/zig/issues/7699
+pub fn readv(self: File, iovecs: []const posix.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 posix.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.
+/// * The OS layer expects pointer addresses to be inside the application's address space
+/// even if the length is zero. Meanwhile, in Zig, slices may have undefined pointer
+/// addresses when the length is zero. So this function modifies the iov_base fields
+/// when the length is zero.
+///
+/// Related open issue: https://github.com/ziglang/zig/issues/7699
+pub fn readvAll(self: File, iovecs: []posix.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;
+ }
+}
+
+/// 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 posix.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 posix.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.
+/// 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 preadvAll(self: File, iovecs: []posix.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 = posix.WriteError;
+pub const PWriteError = posix.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 posix.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..]);
+ }
+}
+
+/// 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 posix.pwrite(self.handle, bytes, offset);
+ } else {
+ return std.event.Loop.instance.?.pwrite(self.handle, bytes, offset, self.capable_io_mode != self.intended_io_mode);
+ }
+}
+
+/// 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);
+ }
+}
+
+/// See https://github.com/ziglang/zig/issues/7699
+/// See equivalent function: `std.net.Stream.writev`.
+pub fn writev(self: File, iovecs: []const posix.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 posix.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.
+/// * The OS layer expects pointer addresses to be inside the application's address space
+/// even if the length is zero. Meanwhile, in Zig, slices may have undefined pointer
+/// addresses when the length is zero. So this function modifies the iov_base fields
+/// when the length is zero.
+/// See https://github.com/ziglang/zig/issues/7699
+/// See equivalent function: `std.net.Stream.writevAll`.
+pub fn writevAll(self: File, iovecs: []posix.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;
+ }
+}
+
+/// 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: []posix.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 posix.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.
+/// 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 pwritevAll(self: File, iovecs: []posix.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 = posix.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) orelse maxInt(usize);
+ const result = try posix.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: []posix.iovec_const = &[0]posix.iovec_const{},
+
+ /// The trailer count is inferred from `headers_and_trailers.len - header_count`
+ header_count: usize = 0,
+};
+
+pub const WriteFileError = ReadError || error{EndOfStream} || 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.NetworkUnreachable,
+ error.NetworkSubsystemFailed,
+ => 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);
+
+ 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);
+}
+
+/// 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) posix.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]posix.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 posix.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 posix.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 posix.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 posix.writev(self.handle, trailers[i..]);
+ }
+}
+
+pub const Reader = io.Reader(File, ReadError, read);
+
+pub fn reader(file: File) Reader {
+ return .{ .context = file };
+}
+
+pub const Writer = io.Writer(File, WriteError, write);
+
+pub fn writer(file: File) Writer {
+ return .{ .context = file };
+}
+
+pub const SeekableStream = io.SeekableStream(
+ File,
+ SeekError,
+ GetSeekPosError,
+ seekTo,
+ seekBy,
+ getPos,
+ getEndPos,
+);
+
+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,
+} || posix.UnexpectedError;
+
+/// 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.
+///
+/// Assumes the file is unlocked.
+///
+/// TODO: integrate with async I/O
+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 posix.flock(file.handle, switch (l) {
+ .none => posix.LOCK.UN,
+ .shared => posix.LOCK.SH,
+ .exclusive => posix.LOCK.EX,
+ }) catch |err| switch (err) {
+ error.WouldBlock => unreachable, // non-blocking=false
+ else => |e| return e,
+ };
+ }
+}
+
+/// 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 posix.flock(file.handle, posix.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.
+ };
+ }
+}
+
+/// Attempts to obtain a lock, returning `true` if the lock is
+/// obtained, and `false` if there was an existing incompatible lock held.
+/// 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.
+///
+/// Assumes the file is unlocked.
+///
+/// TODO: integrate with async I/O
+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 {
+ posix.flock(file.handle, switch (l) {
+ .none => posix.LOCK.UN,
+ .shared => posix.LOCK.SH | posix.LOCK.NB,
+ .exclusive => posix.LOCK.EX | posix.LOCK.NB,
+ }) catch |err| switch (err) {
+ error.WouldBlock => return false,
+ else => |e| return e,
+ };
+ }
+ return true;
+}
+
+/// Assumes the file is already locked in exclusive mode.
+/// Atomically modifies the lock to be in shared mode, without releasing it.
+///
+/// TODO: integrate with async I/O
+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 posix.flock(file.handle, posix.LOCK.SH | posix.LOCK.NB) catch |err| switch (err) {
+ error.WouldBlock => unreachable, // File was not locked in exclusive mode.
+ else => |e| return e,
+ };
+ }
+}
+
+const File = @This();
+const std = @import("../std.zig");
+const builtin = @import("builtin");
+const Allocator = std.mem.Allocator;
+// https://github.com/ziglang/zig/issues/5019
+const posix = std.os;
+const io = std.io;
+const math = std.math;
+const assert = std.debug.assert;
+const windows = std.os.windows;
+const Os = std.builtin.Os;
+const maxInt = std.math.maxInt;
+const is_windows = builtin.os.tag == .windows;
lib/std/fs/file.zig
@@ -1,1622 +0,0 @@
-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;
-
-pub const File = struct {
- /// The OS-specific file descriptor or file handle.
- handle: 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, `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.options.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 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,
- };
-
- /// This is the default mode given to POSIX operating systems for creating
- /// files. `0o666` is "-rw-rw-rw-" which is counter-intuitive at first,
- /// since most people would expect "-rw-r--r--", for example, when using
- /// the `touch` command, which would correspond to `0o644`. However, POSIX
- /// libc implementations use `0o666` inside `fopen` and then rely on the
- /// process-scoped "umask" setting to adjust this number for file creation.
- 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,
- /// On Windows, `\\server` or `\\server\share` was not found.
- 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,
-
- /// Open the file with an advisory lock to coordinate with other processes
- /// 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 does not prevent
- /// other process from getting their own shared locks.
- ///
- /// The lock is advisory, except on Linux in very specific circumstances[1].
- /// This means that a process that does not respect the locking API can still get access
- /// to the file, despite the lock.
- ///
- /// On these operating systems, the lock is acquired atomically with
- /// opening the file:
- /// * Darwin
- /// * DragonFlyBSD
- /// * FreeBSD
- /// * Haiku
- /// * NetBSD
- /// * OpenBSD
- /// On these operating systems, the lock is acquired via a separate syscall
- /// after opening the file:
- /// * Linux
- /// * Windows
- ///
- /// [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,
-
- pub fn isRead(self: OpenFlags) bool {
- return self.mode != .write_only;
- }
-
- pub fn isWrite(self: OpenFlags) bool {
- return self.mode != .read_only;
- }
- };
-
- 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.PathAlreadyExists` to be returned.
- exclusive: bool = false,
-
- /// Open the file with an advisory lock to coordinate with other processes
- /// 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 does not prevent
- /// other process from getting their own shared locks.
- ///
- /// The lock is advisory, except on Linux in very specific circumstances[1].
- /// This means that a process that does not respect the locking API can still get access
- /// to the file, despite the lock.
- ///
- /// On these operating systems, the lock is acquired atomically with
- /// opening the file:
- /// * Darwin
- /// * DragonFlyBSD
- /// * FreeBSD
- /// * Haiku
- /// * NetBSD
- /// * OpenBSD
- /// On these operating systems, the lock is acquired via a separate syscall
- /// after opening the file:
- /// * Linux
- /// * Windows
- ///
- /// [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. On other systems this is always 0.
- 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);
- }
- }
-
- pub const SyncError = os.SyncError;
-
- /// Blocks until all pending file contents and metadata modifications
- /// for the file have been synchronized with the underlying filesystem.
- ///
- /// Note that this does not ensure that metadata for the
- /// directory containing the file has also reached disk.
- pub fn sync(self: File) SyncError!void {
- return os.fsync(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) {
- 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;
-
- /// 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 GetSeekPosError = os.SeekError || os.FStatError;
-
- /// TODO: integrate with async I/O
- pub fn getPos(self: File) GetSeekPosError!u64 {
- return os.lseek_CUR_get(self.handle);
- }
-
- /// 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;
-
- /// 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,
- /// This is available on POSIX systems and is always 0 otherwise.
- 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 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;
-
- /// 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;
-
- /// 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;
-
- /// 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 `null`, the ID is not changed.
- pub fn chown(self: File, owner: ?Uid, group: ?Gid) ChownError!void {
- try os.fchown(self.handle, owner, group);
- }
-
- /// Cross-platform representation of permissions on a file.
- /// The `readonly` and `setReadonly` are the only methods available across all platforms.
- /// Platform-specific functionality is available through the `inner` field.
- pub const Permissions = struct {
- /// You may use the `inner` field to use platform-specific functionality
- inner: switch (builtin.os.tag) {
- .windows => PermissionsWindows,
- else => PermissionsUnix,
- },
-
- const Self = @This();
-
- /// Returns `true` if permissions represent an unwritable file.
- /// On Unix, `true` is returned only if no class has write permissions.
- pub fn readOnly(self: Self) bool {
- return self.inner.readOnly();
- }
-
- /// Sets whether write permissions are provided.
- /// On Unix, this affects *all* classes. If this is undesired, use `unixSet`.
- /// This method *DOES NOT* set permissions on the filesystem: use `File.setPermissions(permissions)`
- 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();
-
- /// Returns `true` if permissions represent an unwritable file.
- pub fn readOnly(self: Self) bool {
- return self.attributes & os.windows.FILE_ATTRIBUTE_READONLY != 0;
- }
-
- /// Sets whether write permissions are provided.
- /// This method *DOES NOT* set permissions on the filesystem: use `File.setPermissions(permissions)`
- 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();
-
- /// Returns `true` if permissions represent an unwritable file.
- /// `true` is returned only if no class has write permissions.
- pub fn readOnly(self: Self) bool {
- return self.mode & 0o222 == 0;
- }
-
- /// Sets whether write permissions are provided.
- /// This affects *all* classes. If this is undesired, use `unixSet`.
- /// This method *DOES NOT* set permissions on the filesystem: use `File.setPermissions(permissions)`
- 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,
- };
-
- /// Returns `true` if the chosen class has the selected permission.
- /// This method is only available on Unix platforms.
- 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;
- }
-
- /// Sets the permissions for the chosen class. Any permissions set to `null` are left unchanged.
- /// This method *DOES NOT* set permissions on the filesystem: use `File.setPermissions(permissions)`
- 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);
- }
- }
- }
-
- /// Returns a `Permissions` struct representing the permissions from the passed mode.
- pub fn unixNew(new_mode: Mode) Self {
- return Self{
- .mode = new_mode,
- };
- }
- };
-
- pub const SetPermissionsError = ChmodError;
-
- /// Sets permissions according to the provided `Permissions` struct.
- /// This method is *NOT* available on WASI
- 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);
- },
- }
- }
-
- /// Cross-platform representation of file metadata.
- /// Platform-specific functionality is available through the `inner` field.
- pub const Metadata = struct {
- /// You may use the `inner` field to use platform-specific functionality
- inner: switch (builtin.os.tag) {
- .windows => MetadataWindows,
- .linux => MetadataLinux,
- else => MetadataUnix,
- },
-
- const Self = @This();
-
- /// Returns the size of the file
- pub fn size(self: Self) u64 {
- return self.inner.size();
- }
-
- /// Returns a `Permissions` struct, representing the permissions on the file
- pub fn permissions(self: Self) Permissions {
- return self.inner.permissions();
- }
-
- /// Returns the `Kind` of file.
- /// On Windows, can only return: `.file`, `.directory`, `.sym_link` or `.unknown`
- pub fn kind(self: Self) Kind {
- return self.inner.kind();
- }
-
- /// 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();
- }
-
- /// Returns the time the file was modified in nanoseconds since UTC 1970-01-01
- pub fn modified(self: Self) i128 {
- return self.inner.modified();
- }
-
- /// 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();
-
- /// Returns the size of the file
- pub fn size(self: Self) u64 {
- return @as(u64, @intCast(self.stat.size));
- }
-
- /// Returns a `Permissions` struct, representing the permissions on the file
- pub fn permissions(self: Self) Permissions {
- return Permissions{ .inner = PermissionsUnix{ .mode = self.stat.mode } };
- }
-
- /// Returns the `Kind` of the file
- 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;
- }
-
- /// 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;
- }
-
- /// 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;
- }
-
- /// 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;
- }
- };
-
- /// `MetadataUnix`, but using Linux's `statx` syscall.
- /// On Linux versions below 4.11, `statx` will be filled with data from stat.
- pub const MetadataLinux = struct {
- statx: os.linux.Statx,
-
- const Self = @This();
-
- /// Returns the size of the file
- pub fn size(self: Self) u64 {
- return self.statx.size;
- }
-
- /// Returns a `Permissions` struct, representing the permissions on the file
- pub fn permissions(self: Self) Permissions {
- return Permissions{ .inner = PermissionsUnix{ .mode = self.statx.mode } };
- }
-
- /// Returns the `Kind` of the file
- 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;
- }
-
- /// 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;
- }
-
- /// 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;
- }
-
- /// 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();
-
- /// Returns the size of the file
- pub fn size(self: Self) u64 {
- return self._size;
- }
-
- /// Returns a `Permissions` struct, representing the permissions on the file
- pub fn permissions(self: Self) Permissions {
- return Permissions{ .inner = PermissionsWindows{ .attributes = self.attributes } };
- }
-
- /// Returns the `Kind` of the file.
- /// Can only return: `.file`, `.directory`, `.sym_link` or `.unknown`
- 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;
- }
-
- /// 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;
- }
-
- /// Returns the time the file was modified in nanoseconds since UTC 1970-01-01
- pub fn modified(self: Self) i128 {
- return self.modified_time;
- }
-
- /// 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;
-
- 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;
-
- /// 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)) 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, ×);
- }
-
- /// 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) + @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;
-
- 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;
- }
-
- /// 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);
- }
- }
-
- /// 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.
- /// On Windows, this function currently does alter the file pointer.
- /// https://github.com/ziglang/zig/issues/12783
- 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;
- }
-
- /// 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);
- }
- }
-
- /// 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.
- /// * The OS layer expects pointer addresses to be inside the application's address space
- /// even if the length is zero. Meanwhile, in Zig, slices may have undefined pointer
- /// addresses when the length is zero. So this function modifies the iov_base fields
- /// when the length is zero.
- ///
- /// Related open issue: https://github.com/ziglang/zig/issues/7699
- 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;
- }
- }
-
- /// 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);
- }
- }
-
- /// 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.
- /// 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 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;
-
- 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..]);
- }
- }
-
- /// 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);
- }
- }
-
- /// 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);
- }
- }
-
- /// See https://github.com/ziglang/zig/issues/7699
- /// See equivalent function: `std.net.Stream.writev`.
- 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.
- /// * The OS layer expects pointer addresses to be inside the application's address space
- /// even if the length is zero. Meanwhile, in Zig, slices may have undefined pointer
- /// addresses when the length is zero. So this function modifies the iov_base fields
- /// when the length is zero.
- /// See https://github.com/ziglang/zig/issues/7699
- /// See equivalent function: `std.net.Stream.writevAll`.
- 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;
- }
- }
-
- /// 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);
- }
- }
-
- /// The `iovecs` parameter is mutable because this function needs to mutate the fields in
- /// order to handle partial writes from the underlying OS layer.
- /// 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 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) orelse 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 || error{EndOfStream} || 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.NetworkUnreachable,
- error.NetworkSubsystemFailed,
- => 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);
-
- 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);
- }
-
- /// 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 = @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);
-
- pub fn reader(file: File) Reader {
- return .{ .context = file };
- }
-
- pub const Writer = io.Writer(File, WriteError, write);
-
- pub fn writer(file: File) Writer {
- return .{ .context = file };
- }
-
- pub const SeekableStream = io.SeekableStream(
- File,
- SeekError,
- GetSeekPosError,
- seekTo,
- seekBy,
- getPos,
- getEndPos,
- );
-
- 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;
-
- /// 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.
- ///
- /// Assumes the file is unlocked.
- ///
- /// TODO: integrate with async I/O
- 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,
- };
- }
- }
-
- /// 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.
- };
- }
- }
-
- /// Attempts to obtain a lock, returning `true` if the lock is
- /// obtained, and `false` if there was an existing incompatible lock held.
- /// 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.
- ///
- /// Assumes the file is unlocked.
- ///
- /// TODO: integrate with async I/O
- 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;
- }
-
- /// Assumes the file is already locked in exclusive mode.
- /// Atomically modifies the lock to be in shared mode, without releasing it.
- ///
- /// TODO: integrate with async I/O
- 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,
- };
- }
- }
-};
lib/std/fs.zig
@@ -10,16 +10,16 @@ const assert = std.debug.assert;
const is_darwin = builtin.os.tag.isDarwin();
-pub const Dir = @import("fs/Dir.zig");
pub const AtomicFile = @import("fs/AtomicFile.zig");
+pub const Dir = @import("fs/Dir.zig");
+pub const File = @import("fs/File.zig");
+pub const path = @import("fs/path.zig");
pub const has_executable_bit = switch (builtin.os.tag) {
.windows, .wasi => false,
else => true,
};
-pub const path = @import("fs/path.zig");
-pub const File = @import("fs/file.zig").File;
pub const wasi = @import("fs/wasi.zig");
// TODO audit these APIs with respect to Dir and absolute paths
@@ -94,6 +94,7 @@ pub const need_async_thread = std.io.is_async and switch (builtin.os.tag) {
};
/// TODO remove the allocator requirement from this API
+/// TODO move to Dir
pub fn atomicSymLink(allocator: Allocator, existing_path: []const u8, new_path: []const u8) !void {
if (cwd().symLink(existing_path, new_path, .{})) {
return;
@@ -104,7 +105,7 @@ pub fn atomicSymLink(allocator: Allocator, existing_path: []const u8, new_path:
const dirname = path.dirname(new_path) orelse ".";
- var rand_buf: [AtomicFile.RANDOM_BYTES]u8 = undefined;
+ var rand_buf: [AtomicFile.random_bytes_len]u8 = undefined;
const tmp_path = try allocator.alloc(u8, dirname.len + 1 + base64_encoder.calcSize(rand_buf.len));
defer allocator.free(tmp_path);
@memcpy(tmp_path[0..dirname.len], dirname);
@@ -634,8 +635,9 @@ test {
_ = ©FileAbsolute;
_ = &updateFileAbsolute;
}
- _ = &File;
+ _ = &AtomicFile;
_ = &Dir;
+ _ = &File;
_ = &path;
_ = @import("fs/test.zig");
_ = @import("fs/get_app_data_dir.zig");
CMakeLists.txt
@@ -249,7 +249,7 @@ set(ZIG_STAGE2_SOURCES
"${CMAKE_SOURCE_DIR}/lib/std/fs.zig"
"${CMAKE_SOURCE_DIR}/lib/std/fs/AtomicFile.zig"
"${CMAKE_SOURCE_DIR}/lib/std/fs/Dir.zig"
- "${CMAKE_SOURCE_DIR}/lib/std/fs/file.zig"
+ "${CMAKE_SOURCE_DIR}/lib/std/fs/File.zig"
"${CMAKE_SOURCE_DIR}/lib/std/fs/get_app_data_dir.zig"
"${CMAKE_SOURCE_DIR}/lib/std/fs/path.zig"
"${CMAKE_SOURCE_DIR}/lib/std/hash.zig"