Commit 0a536a7c90

Andrew Kelley <andrew@ziglang.org>
2023-11-22 22:12:53
std.fs.File: flatten struct
1 parent e00e9c0
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, &times);
+}
+
+/// 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, &times);
-    }
-
-    /// 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 {
         _ = &copyFileAbsolute;
         _ = &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"