Commit 8a1e6c8c39

Andrew Kelley <andrew@ziglang.org>
2025-10-09 09:19:44
std.Io: implement dirStatPath
1 parent 750b143
lib/std/Build/Cache.zig
@@ -1305,7 +1305,7 @@ fn hashFile(file: fs.File, bin_digest: *[Hasher.mac_length]u8) fs.File.PReadErro
 }
 
 // Create/Write a file, close it, then grab its stat.mtime timestamp.
-fn testGetCurrentFileTimestamp(dir: fs.Dir) !i128 {
+fn testGetCurrentFileTimestamp(dir: fs.Dir) !Io.Timestamp {
     const test_out_file = "test-filetimestamp.tmp";
 
     var file = try dir.createFile(test_out_file, .{
@@ -1333,8 +1333,8 @@ test "cache file and then recall it" {
 
     // Wait for file timestamps to tick
     const initial_time = try testGetCurrentFileTimestamp(tmp.dir);
-    while ((try testGetCurrentFileTimestamp(tmp.dir)) == initial_time) {
-        try std.Io.Duration.sleep(.fromNanoseconds(1), io);
+    while ((try testGetCurrentFileTimestamp(tmp.dir)).nanoseconds == initial_time.nanoseconds) {
+        try std.Io.Clock.Duration.sleep(.{ .clock = .boot, .raw = .fromNanoseconds(1) }, io);
     }
 
     var digest1: HexDigest = undefined;
@@ -1399,8 +1399,8 @@ test "check that changing a file makes cache fail" {
 
     // Wait for file timestamps to tick
     const initial_time = try testGetCurrentFileTimestamp(tmp.dir);
-    while ((try testGetCurrentFileTimestamp(tmp.dir)) == initial_time) {
-        try std.Io.Duration.sleep(.fromNanoseconds(1), io);
+    while ((try testGetCurrentFileTimestamp(tmp.dir)).nanoseconds == initial_time.nanoseconds) {
+        try std.Io.Clock.Duration.sleep(.{ .clock = .boot, .raw = .fromNanoseconds(1) }, io);
     }
 
     var digest1: HexDigest = undefined;
@@ -1517,8 +1517,8 @@ test "Manifest with files added after initial hash work" {
 
     // Wait for file timestamps to tick
     const initial_time = try testGetCurrentFileTimestamp(tmp.dir);
-    while ((try testGetCurrentFileTimestamp(tmp.dir)) == initial_time) {
-        try std.Io.Duration.sleep(.fromNanoseconds(1), io);
+    while ((try testGetCurrentFileTimestamp(tmp.dir)).nanoseconds == initial_time.nanoseconds) {
+        try std.Io.Clock.Duration.sleep(.{ .clock = .boot, .raw = .fromNanoseconds(1) }, io);
     }
 
     var digest1: HexDigest = undefined;
@@ -1568,8 +1568,8 @@ test "Manifest with files added after initial hash work" {
 
         // Wait for file timestamps to tick
         const initial_time2 = try testGetCurrentFileTimestamp(tmp.dir);
-        while ((try testGetCurrentFileTimestamp(tmp.dir)) == initial_time2) {
-            try std.Io.Duration.sleep(.fromNanoseconds(1), io);
+        while ((try testGetCurrentFileTimestamp(tmp.dir)).nanoseconds == initial_time2.nanoseconds) {
+            try std.Io.Clock.Duration.sleep(.{ .clock = .boot, .raw = .fromNanoseconds(1) }, io);
         }
 
         {
lib/std/fs/Dir.zig
@@ -36,10 +36,6 @@ const IteratorError = error{
     AccessDenied,
     PermissionDenied,
     SystemResources,
-    /// WASI-only. The path of an entry could not be encoded as valid UTF-8.
-    /// WASI is unable to handle paths that cannot be encoded as well-formed UTF-8.
-    /// https://github.com/WebAssembly/wasi-filesystem/issues/17#issuecomment-1430639353
-    InvalidUtf8,
 } || posix.UnexpectedError;
 
 pub const Iterator = switch (native_os) {
@@ -553,7 +549,6 @@ pub const Iterator = switch (native_os) {
                         .INVAL => unreachable,
                         .NOENT => return error.DirNotFound, // The directory being iterated was deleted during iteration.
                         .NOTCAPABLE => return error.AccessDenied,
-                        .ILSEQ => return error.InvalidUtf8, // An entry's name cannot be encoded as UTF-8.
                         else => |err| return posix.unexpectedErrno(err),
                     }
                     if (bufused == 0) return null;
@@ -844,28 +839,7 @@ pub fn walk(self: Dir, allocator: Allocator) Allocator.Error!Walker {
     };
 }
 
-pub const OpenError = error{
-    FileNotFound,
-    NotDir,
-    AccessDenied,
-    PermissionDenied,
-    SymLinkLoop,
-    ProcessFdQuotaExceeded,
-    NameTooLong,
-    SystemFdQuotaExceeded,
-    NoDevice,
-    SystemResources,
-    /// WASI-only; file paths must be valid UTF-8.
-    InvalidUtf8,
-    /// Windows-only; file paths provided by the user must be valid WTF-8.
-    /// https://wtf-8.codeberg.page/
-    InvalidWtf8,
-    BadPathName,
-    DeviceBusy,
-    /// On Windows, `\\server` or `\\server\share` was not found.
-    NetworkNotFound,
-    ProcessNotFound,
-} || posix.UnexpectedError;
+pub const OpenError = Io.Dir.OpenError;
 
 pub fn close(self: *Dir) void {
     posix.close(self.fd);
@@ -1311,7 +1285,7 @@ pub fn makeOpenPath(self: Dir, sub_path: []const u8, open_dir_options: OpenOptio
     };
 }
 
-pub const RealPathError = posix.RealPathError;
+pub const RealPathError = posix.RealPathError || error{Canceled};
 
 ///  This function returns the canonicalized absolute pathname of
 /// `pathname` relative to this `Dir`. If `pathname` is absolute, ignores this
@@ -1369,7 +1343,6 @@ pub fn realpathZ(self: Dir, pathname: [*:0]const u8, out_buffer: []u8) RealPathE
         error.FileLocksNotSupported => return error.Unexpected,
         error.FileBusy => return error.Unexpected,
         error.WouldBlock => return error.Unexpected,
-        error.InvalidUtf8 => unreachable, // WASI-only
         else => |e| return e,
     };
     defer posix.close(fd);
@@ -1639,6 +1612,10 @@ fn openDirFlagsZ(self: Dir, sub_path_c: [*:0]const u8, flags: posix.O) OpenError
         error.FileLocksNotSupported => unreachable, // locking folders is not supported
         error.WouldBlock => unreachable, // can't happen for directories
         error.FileBusy => unreachable, // can't happen for directories
+        error.SharingViolation => unreachable, // can't happen for directories
+        error.PipeBusy => unreachable, // can't happen for directories
+        error.AntivirusInterference => unreachable, // can't happen for directories
+        error.ProcessNotFound => unreachable, // can't happen for directories
         else => |e| return e,
     };
     return Dir{ .fd = fd };
@@ -2095,24 +2072,21 @@ pub const DeleteTreeError = error{
     FileBusy,
     DeviceBusy,
     ProcessNotFound,
-
     /// One of the path components was not a directory.
     /// This error is unreachable if `sub_path` does not contain a path separator.
     NotDir,
-
     /// WASI-only; file paths must be valid UTF-8.
     InvalidUtf8,
-
     /// Windows-only; file paths provided by the user must be valid WTF-8.
     /// https://wtf-8.codeberg.page/
     InvalidWtf8,
-
     /// On Windows, file paths cannot contain these characters:
     /// '/', '*', '?', '"', '<', '>', '|'
     BadPathName,
-
     /// On Windows, `\\server` or `\\server\share` was not found.
     NetworkNotFound,
+
+    Canceled,
 } || posix.UnexpectedError;
 
 /// Whether `sub_path` describes a symlink, file, or directory, this function
@@ -2169,17 +2143,15 @@ pub fn deleteTree(self: Dir, sub_path: []const u8) DeleteTreeError!void {
                             error.PermissionDenied,
                             error.SymLinkLoop,
                             error.ProcessFdQuotaExceeded,
-                            error.ProcessNotFound,
                             error.NameTooLong,
                             error.SystemFdQuotaExceeded,
                             error.NoDevice,
                             error.SystemResources,
                             error.Unexpected,
-                            error.InvalidUtf8,
-                            error.InvalidWtf8,
                             error.BadPathName,
                             error.NetworkNotFound,
                             error.DeviceBusy,
+                            error.Canceled,
                             => |e| return e,
                         };
                         stack.appendAssumeCapacity(.{
@@ -2266,18 +2238,16 @@ pub fn deleteTree(self: Dir, sub_path: []const u8) DeleteTreeError!void {
                             error.AccessDenied,
                             error.PermissionDenied,
                             error.SymLinkLoop,
-                            error.ProcessNotFound,
                             error.ProcessFdQuotaExceeded,
                             error.NameTooLong,
                             error.SystemFdQuotaExceeded,
                             error.NoDevice,
                             error.SystemResources,
                             error.Unexpected,
-                            error.InvalidUtf8,
-                            error.InvalidWtf8,
                             error.BadPathName,
                             error.NetworkNotFound,
                             error.DeviceBusy,
+                            error.Canceled,
                             => |e| return e,
                         };
                     } else {
@@ -2374,18 +2344,16 @@ fn deleteTreeMinStackSizeWithKindHint(self: Dir, sub_path: []const u8, kind_hint
                             error.AccessDenied,
                             error.PermissionDenied,
                             error.SymLinkLoop,
-                            error.ProcessNotFound,
                             error.ProcessFdQuotaExceeded,
                             error.NameTooLong,
                             error.SystemFdQuotaExceeded,
                             error.NoDevice,
                             error.SystemResources,
                             error.Unexpected,
-                            error.InvalidUtf8,
-                            error.InvalidWtf8,
                             error.BadPathName,
                             error.NetworkNotFound,
                             error.DeviceBusy,
+                            error.Canceled,
                             => |e| return e,
                         };
                         if (cleanup_dir_parent) |*d| d.close();
@@ -2476,17 +2444,15 @@ fn deleteTreeOpenInitialSubpath(self: Dir, sub_path: []const u8, kind_hint: File
                     error.PermissionDenied,
                     error.SymLinkLoop,
                     error.ProcessFdQuotaExceeded,
-                    error.ProcessNotFound,
                     error.NameTooLong,
                     error.SystemFdQuotaExceeded,
                     error.NoDevice,
                     error.SystemResources,
                     error.Unexpected,
-                    error.InvalidUtf8,
-                    error.InvalidWtf8,
                     error.BadPathName,
                     error.DeviceBusy,
                     error.NetworkNotFound,
+                    error.Canceled,
                     => |e| return e,
                 };
             } else {
@@ -2589,7 +2555,7 @@ pub const CopyFileOptions = struct {
 
 pub const CopyFileError = File.OpenError || File.StatError ||
     AtomicFile.InitError || AtomicFile.FinishError ||
-    File.ReadError || File.WriteError;
+    File.ReadError || File.WriteError || error{InvalidFileName};
 
 /// Atomically creates a new file at `dest_path` within `dest_dir` with the
 /// same contents as `source_path` within `source_dir`, overwriting any already
@@ -2690,33 +2656,9 @@ pub fn statFile(self: Dir, sub_path: []const u8) StatFileError!Stat {
         const st = try std.os.fstatat_wasi(self.fd, sub_path, .{ .SYMLINK_FOLLOW = true });
         return Stat.fromWasi(st);
     }
-    if (native_os == .linux) {
-        const sub_path_c = try posix.toPosixPath(sub_path);
-        var stx = std.mem.zeroes(linux.Statx);
-
-        const rc = linux.statx(
-            self.fd,
-            &sub_path_c,
-            linux.AT.NO_AUTOMOUNT,
-            linux.STATX_TYPE | linux.STATX_MODE | linux.STATX_ATIME | linux.STATX_MTIME | linux.STATX_CTIME,
-            &stx,
-        );
-
-        return switch (linux.E.init(rc)) {
-            .SUCCESS => Stat.fromLinux(stx),
-            .ACCES => error.AccessDenied,
-            .BADF => unreachable,
-            .FAULT => unreachable,
-            .INVAL => unreachable,
-            .LOOP => error.SymLinkLoop,
-            .NAMETOOLONG => unreachable, // Handled by posix.toPosixPath() above.
-            .NOENT, .NOTDIR => error.FileNotFound,
-            .NOMEM => error.SystemResources,
-            else => |err| posix.unexpectedErrno(err),
-        };
-    }
-    const st = try posix.fstatat(self.fd, sub_path, 0);
-    return Stat.fromPosix(st);
+    var threaded: Io.Threaded = .init_single_threaded;
+    const io = threaded.io();
+    return Io.Dir.statPath(.{ .handle = self.fd }, io, sub_path, .{});
 }
 
 pub const ChmodError = File.ChmodError;
lib/std/fs/File.zig
@@ -38,33 +38,8 @@ pub const default_mode = switch (builtin.os.tag) {
     else => 0o666,
 };
 
-pub const OpenError = error{
-    SharingViolation,
-    PathAlreadyExists,
-    FileNotFound,
-    AccessDenied,
-    PipeBusy,
-    NoDevice,
-    NameTooLong,
-    /// WASI-only; file paths must be valid UTF-8.
-    InvalidUtf8,
-    /// Windows-only; file paths provided by the user must be valid WTF-8.
-    /// https://wtf-8.codeberg.page/
-    InvalidWtf8,
-    /// On Windows, file paths cannot contain these characters:
-    /// '/', '*', '?', '"', '<', '>', '|'
-    BadPathName,
-    Unexpected,
-    /// On Windows, `\\server` or `\\server\share` was not found.
-    NetworkNotFound,
-    ProcessNotFound,
-    /// On Windows, antivirus software is enabled by default. It can be
-    /// disabled, but Windows Update sometimes ignores the user's preference
-    /// and re-enables it. When enabled, antivirus software on Windows
-    /// intercepts file system operations and makes them significantly slower
-    /// in addition to possibly failing with this error code.
-    AntivirusInterference,
-} || posix.OpenError || posix.FlockError;
+/// Deprecated in favor of `Io.File.OpenError`.
+pub const OpenError = Io.File.OpenError || error{WouldBlock};
 
 pub const OpenMode = enum {
     read_only,
lib/std/Io/Dir.zig
@@ -15,6 +15,29 @@ pub fn cwd() Dir {
 
 pub const Handle = std.posix.fd_t;
 
+pub const PathNameError = error{
+    NameTooLong,
+    /// File system cannot encode the requested file name bytes.
+    /// Could be due to invalid WTF-8 on Windows, invalid UTF-8 on WASI,
+    /// invalid characters on Windows, etc. Filesystem and operating specific.
+    BadPathName,
+};
+
+pub const OpenError = error{
+    FileNotFound,
+    NotDir,
+    AccessDenied,
+    PermissionDenied,
+    SymLinkLoop,
+    ProcessFdQuotaExceeded,
+    SystemFdQuotaExceeded,
+    NoDevice,
+    SystemResources,
+    DeviceBusy,
+    /// On Windows, `\\server` or `\\server\share` was not found.
+    NetworkNotFound,
+} || PathNameError || Io.Cancelable || Io.UnexpectedError;
+
 pub fn openFile(dir: Dir, io: Io, sub_path: []const u8, flags: File.OpenFlags) File.OpenError!File {
     return io.vtable.fileOpen(io.userdata, dir, sub_path, flags);
 }
@@ -149,22 +172,15 @@ pub const MakeError = error{
     PathAlreadyExists,
     SymLinkLoop,
     LinkQuotaExceeded,
-    NameTooLong,
     FileNotFound,
     SystemResources,
     NoSpaceLeft,
     NotDir,
     ReadOnlyFileSystem,
-    /// Windows-only; file paths provided by the user must be valid WTF-8.
-    /// https://simonsapin.github.io/wtf-8/
-    InvalidWtf8,
-    BadPathName,
     NoDevice,
     /// On Windows, `\\server` or `\\server\share` was not found.
     NetworkNotFound,
-    /// File system cannot encode the requested file name bytes.
-    InvalidFileName,
-} || Io.Cancelable || Io.UnexpectedError;
+} || PathNameError || Io.Cancelable || Io.UnexpectedError;
 
 /// Creates a single directory with a relative or absolute path.
 ///
@@ -225,7 +241,7 @@ pub fn makePathStatus(dir: Dir, io: Io, sub_path: []const u8) MakePathError!Make
                 // could cause an infinite loop
                 check_dir: {
                     // workaround for windows, see https://github.com/ziglang/zig/issues/16738
-                    const fstat = statPath(dir, io, component.path) catch |stat_err| switch (stat_err) {
+                    const fstat = statPath(dir, io, component.path, .{}) catch |stat_err| switch (stat_err) {
                         error.IsDir => break :check_dir,
                         else => |e| return e,
                     };
@@ -251,6 +267,10 @@ pub fn stat(dir: Dir, io: Io) StatError!Stat {
 
 pub const StatPathError = File.OpenError || File.StatError;
 
+pub const StatPathOptions = struct {
+    follow_symlinks: bool = true,
+};
+
 /// Returns metadata for a file inside the directory.
 ///
 /// On Windows, this requires three syscalls. On other operating systems, it
@@ -263,6 +283,6 @@ pub const StatPathError = File.OpenError || File.StatError;
 /// * On Windows, `sub_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
 /// * On WASI, `sub_path` should be encoded as valid UTF-8.
 /// * On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
-pub fn statPath(dir: Dir, io: Io, sub_path: []const u8) StatPathError!Stat {
-    return io.vtable.dirStatPath(io.userdata, dir, sub_path);
+pub fn statPath(dir: Dir, io: Io, sub_path: []const u8, options: StatPathOptions) StatPathError!Stat {
+    return io.vtable.dirStatPath(io.userdata, dir, sub_path, options);
 }
lib/std/Io/File.zig
@@ -81,7 +81,63 @@ pub fn stat(file: File, io: Io) StatError!Stat {
 pub const OpenFlags = std.fs.File.OpenFlags;
 pub const CreateFlags = std.fs.File.CreateFlags;
 
-pub const OpenError = std.fs.File.OpenError || Io.Cancelable;
+pub const OpenError = error{
+    SharingViolation,
+    PipeBusy,
+    NoDevice,
+    /// On Windows, `\\server` or `\\server\share` was not found.
+    NetworkNotFound,
+    ProcessNotFound,
+    /// On Windows, antivirus software is enabled by default. It can be
+    /// disabled, but Windows Update sometimes ignores the user's preference
+    /// and re-enables it. When enabled, antivirus software on Windows
+    /// intercepts file system operations and makes them significantly slower
+    /// in addition to possibly failing with this error code.
+    AntivirusInterference,
+    /// In WASI, this error may occur when the file descriptor does
+    /// not hold the required rights to open a new resource relative to it.
+    AccessDenied,
+    PermissionDenied,
+    SymLinkLoop,
+    ProcessFdQuotaExceeded,
+    SystemFdQuotaExceeded,
+    /// Either:
+    /// * One of the path components does not exist.
+    /// * Cwd was used, but cwd has been deleted.
+    /// * The path associated with the open directory handle has been deleted.
+    /// * On macOS, multiple processes or threads raced to create the same file
+    ///   with `O.EXCL` set to `false`.
+    FileNotFound,
+    /// The path exceeded `max_path_bytes` bytes.
+    /// Insufficient kernel memory was available, or
+    /// the named file is a FIFO and per-user hard limit on
+    /// memory allocation for pipes has been reached.
+    SystemResources,
+    /// The file is too large to be opened. This error is unreachable
+    /// for 64-bit targets, as well as when opening directories.
+    FileTooBig,
+    /// The path refers to directory but the `DIRECTORY` flag was not provided.
+    IsDir,
+    /// A new path cannot be created because the device has no room for the new file.
+    /// This error is only reachable when the `CREAT` flag is provided.
+    NoSpaceLeft,
+    /// A component used as a directory in the path was not, in fact, a directory, or
+    /// `DIRECTORY` was specified and the path was not a directory.
+    NotDir,
+    /// The path already exists and the `CREAT` and `EXCL` flags were provided.
+    PathAlreadyExists,
+    DeviceBusy,
+    FileLocksNotSupported,
+    /// One of these three things:
+    /// * pathname  refers to an executable image which is currently being
+    ///   executed and write access was requested.
+    /// * pathname refers to a file that is currently in  use  as  a  swap
+    ///   file, and the O_TRUNC flag was specified.
+    /// * pathname  refers  to  a file that is currently being read by the
+    ///   kernel (e.g., for module/firmware loading), and write access was
+    ///   requested.
+    FileBusy,
+} || Io.Dir.PathNameError || Io.Cancelable || Io.UnexpectedError;
 
 pub fn close(file: File, io: Io) void {
     return io.vtable.fileClose(io.userdata, file);
lib/std/Io/test.zig
@@ -11,6 +11,8 @@ const native_endian = @import("builtin").target.cpu.arch.endian();
 const tmpDir = std.testing.tmpDir;
 
 test "write a file, read it, then delete it" {
+    const io = std.testing.io;
+
     var tmp = tmpDir(.{});
     defer tmp.cleanup();
 
@@ -45,7 +47,7 @@ test "write a file, read it, then delete it" {
         try expectEqual(expected_file_size, file_size);
 
         var file_buffer: [1024]u8 = undefined;
-        var file_reader = file.reader(&file_buffer);
+        var file_reader = file.reader(io, &file_buffer);
         const contents = try file_reader.interface.allocRemaining(std.testing.allocator, .limited(2 * 1024));
         defer std.testing.allocator.free(contents);
 
@@ -114,10 +116,10 @@ test "updateTimes" {
     const stat_old = try file.stat();
     // Set atime and mtime to 5s before
     try file.updateTimes(
-        stat_old.atime - 5 * std.time.ns_per_s,
-        stat_old.mtime - 5 * std.time.ns_per_s,
+        stat_old.atime.subDuration(.fromSeconds(5)),
+        stat_old.mtime.subDuration(.fromSeconds(5)),
     );
     const stat_new = try file.stat();
-    try expect(stat_new.atime < stat_old.atime);
-    try expect(stat_new.mtime < stat_old.mtime);
+    try expect(stat_new.atime.nanoseconds < stat_old.atime.nanoseconds);
+    try expect(stat_new.mtime.nanoseconds < stat_old.mtime.nanoseconds);
 }
lib/std/Io/Threaded.zig
@@ -166,14 +166,23 @@ pub fn io(pool: *Pool) Io {
                 else => dirMakePosix,
             },
             .dirStat = dirStat,
-            .dirStatPath = dirStatPath,
+            .dirStatPath = switch (builtin.os.tag) {
+                .linux => dirStatPathLinux,
+                .windows => @panic("TODO"),
+                .wasi => @panic("TODO"),
+                else => dirStatPathPosix,
+            },
             .fileStat = switch (builtin.os.tag) {
                 .linux => fileStatLinux,
                 .windows => fileStatWindows,
                 .wasi => fileStatWasi,
                 else => fileStatPosix,
             },
-            .createFile = createFile,
+            .createFile = switch (builtin.os.tag) {
+                .windows => @panic("TODO"),
+                .wasi => @panic("TODO"),
+                else => createFilePosix,
+            },
             .fileOpen = fileOpen,
             .fileClose = fileClose,
             .pwrite = pwrite,
@@ -765,8 +774,10 @@ fn conditionWake(userdata: ?*anyopaque, cond: *Io.Condition, wake: Io.Condition.
 
 fn dirMakePosix(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8, mode: Io.Dir.Mode) Io.Dir.MakeError!void {
     const pool: *Pool = @ptrCast(@alignCast(userdata));
+
     var path_buffer: [posix.PATH_MAX]u8 = undefined;
-    const sub_path_posix = try toPosixPath(sub_path, &path_buffer);
+    const sub_path_posix = try pathToPosix(sub_path, &path_buffer);
+
     while (true) {
         try pool.checkCancel();
         switch (posix.errno(posix.system.mkdirat(dir.handle, sub_path_posix, mode))) {
@@ -788,7 +799,7 @@ fn dirMakePosix(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8, mode:
             .ROFS => return error.ReadOnlyFileSystem,
             // dragonfly: when dir_fd is unlinked from filesystem
             .NOTCONN => return error.FileNotFound,
-            .ILSEQ => return error.InvalidFileName,
+            .ILSEQ => return error.BadPathName,
             else => |err| return posix.unexpectedErrno(err),
         }
     }
@@ -802,13 +813,82 @@ fn dirStat(userdata: ?*anyopaque, dir: Io.Dir) Io.Dir.StatError!Io.Dir.Stat {
     @panic("TODO");
 }
 
-fn dirStatPath(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8) Io.Dir.StatError!Io.File.Stat {
+fn dirStatPathLinux(
+    userdata: ?*anyopaque,
+    dir: Io.Dir,
+    sub_path: []const u8,
+    options: Io.Dir.StatPathOptions,
+) Io.Dir.StatPathError!Io.File.Stat {
     const pool: *Pool = @ptrCast(@alignCast(userdata));
-    try pool.checkCancel();
+    const linux = std.os.linux;
 
-    _ = dir;
-    _ = sub_path;
-    @panic("TODO");
+    var path_buffer: [posix.PATH_MAX]u8 = undefined;
+    const sub_path_posix = try pathToPosix(sub_path, &path_buffer);
+
+    const flags: u32 = linux.AT.NO_AUTOMOUNT |
+        @as(u32, if (!options.follow_symlinks) linux.AT.SYMLINK_NOFOLLOW else 0);
+
+    while (true) {
+        try pool.checkCancel();
+        var statx = std.mem.zeroes(linux.Statx);
+        const rc = linux.statx(
+            dir.handle,
+            sub_path_posix,
+            flags,
+            linux.STATX_TYPE | linux.STATX_MODE | linux.STATX_ATIME | linux.STATX_MTIME | linux.STATX_CTIME,
+            &statx,
+        );
+        switch (linux.E.init(rc)) {
+            .SUCCESS => return statFromLinux(&statx),
+            .INTR => continue,
+            .ACCES => return error.AccessDenied,
+            .BADF => |err| return errnoBug(err),
+            .FAULT => |err| return errnoBug(err),
+            .INVAL => |err| return errnoBug(err),
+            .LOOP => return error.SymLinkLoop,
+            .NAMETOOLONG => |err| return errnoBug(err), // Handled by pathToPosix() above.
+            .NOENT => return error.FileNotFound,
+            .NOTDIR => return error.NotDir,
+            .NOMEM => return error.SystemResources,
+            else => |err| return posix.unexpectedErrno(err),
+        }
+    }
+}
+
+fn dirStatPathPosix(
+    userdata: ?*anyopaque,
+    dir: Io.Dir,
+    sub_path: []const u8,
+    options: Io.Dir.StatPathOptions,
+) Io.Dir.StatPathError!Io.File.Stat {
+    const pool: *Pool = @ptrCast(@alignCast(userdata));
+
+    var path_buffer: [posix.PATH_MAX]u8 = undefined;
+    const sub_path_posix = try pathToPosix(sub_path, &path_buffer);
+
+    const flags: u32 = if (!options.follow_symlinks) posix.AT.SYMLINK_NOFOLLOW else 0;
+    const fstatat_sym = if (posix.lfs64_abi) posix.system.fstatat64 else posix.system.fstatat;
+
+    while (true) {
+        try pool.checkCancel();
+        var stat = std.mem.zeroes(posix.Stat);
+        switch (posix.errno(fstatat_sym(dir.handle, sub_path_posix, &stat, flags))) {
+            .SUCCESS => return statFromPosix(stat),
+            .INTR => continue,
+            .INVAL => |err| return errnoBug(err),
+            .BADF => |err| return errnoBug(err), // Always a race condition.
+            .NOMEM => return error.SystemResources,
+            .ACCES => return error.AccessDenied,
+            .PERM => return error.PermissionDenied,
+            .FAULT => |err| return errnoBug(err),
+            .NAMETOOLONG => return error.NameTooLong,
+            .LOOP => return error.SymLinkLoop,
+            .NOENT => return error.FileNotFound,
+            .NOTDIR => return error.FileNotFound,
+            .ILSEQ => return error.BadPathName,
+            else => |err| return posix.unexpectedErrno(err),
+        }
+    }
 }
 
 fn fileStatPosix(userdata: ?*anyopaque, file: Io.File) Io.File.StatError!Io.File.Stat {
@@ -885,17 +965,127 @@ fn fileStatWasi(userdata: ?*anyopaque, file: Io.File) Io.File.StatError!Io.File.
     }
 }
 
-fn createFile(
+const have_flock = @TypeOf(posix.system.flock) != void;
+
+fn createFilePosix(
     userdata: ?*anyopaque,
     dir: Io.Dir,
     sub_path: []const u8,
     flags: Io.File.CreateFlags,
 ) Io.File.OpenError!Io.File {
     const pool: *Pool = @ptrCast(@alignCast(userdata));
-    try pool.checkCancel();
-    const fs_dir: std.fs.Dir = .{ .fd = dir.handle };
-    const fs_file = try fs_dir.createFile(sub_path, flags);
-    return .{ .handle = fs_file.handle };
+
+    var path_buffer: [posix.PATH_MAX]u8 = undefined;
+    const sub_path_posix = try pathToPosix(sub_path, &path_buffer);
+
+    var os_flags: posix.O = .{
+        .ACCMODE = if (flags.read) .RDWR else .WRONLY,
+        .CREAT = true,
+        .TRUNC = flags.truncate,
+        .EXCL = flags.exclusive,
+    };
+    if (@hasField(posix.O, "LARGEFILE")) os_flags.LARGEFILE = true;
+    if (@hasField(posix.O, "CLOEXEC")) os_flags.CLOEXEC = true;
+
+    // Use the O locking flags if the os supports them to acquire the lock
+    // atomically. Note that the NONBLOCK flag is removed after the openat()
+    // call is successful.
+    const has_flock_open_flags = @hasField(posix.O, "EXLOCK");
+    if (has_flock_open_flags) switch (flags.lock) {
+        .none => {},
+        .shared => {
+            os_flags.SHLOCK = true;
+            os_flags.NONBLOCK = flags.lock_nonblocking;
+        },
+        .exclusive => {
+            os_flags.EXLOCK = true;
+            os_flags.NONBLOCK = flags.lock_nonblocking;
+        },
+    };
+
+    const openat_sym = if (posix.lfs64_abi) posix.system.openat64 else posix.system.openat;
+
+    const fd: posix.fd_t = while (true) {
+        try pool.checkCancel();
+        const rc = openat_sym(dir.handle, sub_path_posix, os_flags, flags.mode);
+        switch (posix.errno(rc)) {
+            .SUCCESS => break @intCast(rc),
+            .INTR => continue,
+
+            .FAULT => |err| return errnoBug(err),
+            .INVAL => return error.BadPathName,
+            .BADF => |err| return errnoBug(err),
+            .ACCES => return error.AccessDenied,
+            .FBIG => return error.FileTooBig,
+            .OVERFLOW => return error.FileTooBig,
+            .ISDIR => return error.IsDir,
+            .LOOP => return error.SymLinkLoop,
+            .MFILE => return error.ProcessFdQuotaExceeded,
+            .NAMETOOLONG => return error.NameTooLong,
+            .NFILE => return error.SystemFdQuotaExceeded,
+            .NODEV => return error.NoDevice,
+            .NOENT => return error.FileNotFound,
+            .SRCH => return error.ProcessNotFound,
+            .NOMEM => return error.SystemResources,
+            .NOSPC => return error.NoSpaceLeft,
+            .NOTDIR => return error.NotDir,
+            .PERM => return error.PermissionDenied,
+            .EXIST => return error.PathAlreadyExists,
+            .BUSY => return error.DeviceBusy,
+            .OPNOTSUPP => return error.FileLocksNotSupported,
+            //.AGAIN => return error.WouldBlock,
+            .TXTBSY => return error.FileBusy,
+            .NXIO => return error.NoDevice,
+            .ILSEQ => return error.BadPathName,
+            else => |err| return posix.unexpectedErrno(err),
+        }
+    };
+    errdefer posix.close(fd);
+
+    if (have_flock and !has_flock_open_flags and flags.lock != .none) {
+        const lock_nonblocking: i32 = if (flags.lock_nonblocking) posix.LOCK.NB else 0;
+        const lock_flags = switch (flags.lock) {
+            .none => unreachable,
+            .shared => posix.LOCK.SH | lock_nonblocking,
+            .exclusive => posix.LOCK.EX | lock_nonblocking,
+        };
+        while (true) {
+            try pool.checkCancel();
+            switch (posix.errno(posix.system.flock(fd, lock_flags))) {
+                .SUCCESS => break,
+                .INTR => continue,
+
+                .BADF => |err| return errnoBug(err),
+                .INVAL => |err| return errnoBug(err), // invalid parameters
+                .NOLCK => return error.SystemResources,
+                //.AGAIN => return error.WouldBlock,
+                .OPNOTSUPP => return error.FileLocksNotSupported,
+                else => |err| return posix.unexpectedErrno(err),
+            }
+        }
+    }
+
+    if (has_flock_open_flags and flags.lock_nonblocking) {
+        var fl_flags: usize = while (true) {
+            try pool.checkCancel();
+            switch (posix.errno(posix.system.fcntl(fd, posix.F.GETFL, 0))) {
+                .SUCCESS => break,
+                .INTR => continue,
+                else => |err| return posix.unexpectedErrno(err),
+            }
+        };
+        fl_flags &= ~@as(usize, 1 << @bitOffsetOf(posix.O, "NONBLOCK"));
+        while (true) {
+            try pool.checkCancel();
+            switch (posix.errno(posix.fcntl(fd, posix.F.SETFL, fl_flags))) {
+                .SUCCESS => break,
+                .INTR => continue,
+                else => |err| return posix.unexpectedErrno(err),
+            }
+        }
+    }
+
+    return .{ .handle = fd };
 }
 
 fn fileOpen(
@@ -2293,8 +2483,8 @@ fn timestampToPosix(nanoseconds: i96) std.posix.timespec {
     };
 }
 
-fn toPosixPath(file_path: []const u8, buffer: *[posix.PATH_MAX]u8) error{ NameTooLong, InvalidFileName }![:0]u8 {
-    if (std.mem.containsAtLeastScalar2(u8, file_path, 0, 1)) return error.InvalidFileName;
+fn pathToPosix(file_path: []const u8, buffer: *[posix.PATH_MAX]u8) Io.Dir.PathNameError![:0]u8 {
+    if (std.mem.containsAtLeastScalar2(u8, file_path, 0, 1)) return error.BadPathName;
     // >= rather than > to make room for the null byte
     if (file_path.len >= buffer.len) return error.NameTooLong;
     @memcpy(buffer[0..file_path.len], file_path);
lib/std/Thread/Condition.zig
@@ -123,14 +123,9 @@ const SingleThreadedImpl = struct {
     fn wait(self: *Impl, mutex: *Mutex, timeout: ?u64) error{Timeout}!void {
         _ = self;
         _ = mutex;
-
         // There are no other threads to wake us up.
         // So if we wait without a timeout we would never wake up.
-        const timeout_ns = timeout orelse {
-            unreachable; // deadlock detected
-        };
-
-        std.Thread.sleep(timeout_ns);
+        assert(timeout != null); // Deadlock detected.
         return error.Timeout;
     }
 
@@ -323,6 +318,8 @@ test "wait and signal" {
         return error.SkipZigTest;
     }
 
+    const io = testing.io;
+
     const num_threads = 4;
 
     const MultiWait = struct {
@@ -348,7 +345,7 @@ test "wait and signal" {
     }
 
     while (true) {
-        std.Thread.sleep(100 * std.time.ns_per_ms);
+        try std.Io.Clock.Duration.sleep(.{ .clock = .awake, .raw = .fromMilliseconds(100) }, io);
 
         multi_wait.mutex.lock();
         defer multi_wait.mutex.unlock();
@@ -368,6 +365,8 @@ test signal {
         return error.SkipZigTest;
     }
 
+    const io = testing.io;
+
     const num_threads = 4;
 
     const SignalTest = struct {
@@ -405,7 +404,7 @@ test signal {
     }
 
     while (true) {
-        std.Thread.sleep(10 * std.time.ns_per_ms);
+        try std.Io.Clock.Duration.sleep(.{ .clock = .awake, .raw = .fromMilliseconds(10) }, io);
 
         signal_test.mutex.lock();
         defer signal_test.mutex.unlock();
lib/std/zig/system.zig
@@ -766,27 +766,23 @@ test glibcVerFromLinkName {
 
 fn glibcVerFromRPath(io: Io, rpath: []const u8) !std.SemanticVersion {
     var dir = fs.cwd().openDir(rpath, .{}) catch |err| switch (err) {
-        error.NameTooLong => unreachable,
-        error.InvalidUtf8 => unreachable, // WASI only
-        error.InvalidWtf8 => unreachable, // Windows-only
-        error.BadPathName => unreachable,
-        error.DeviceBusy => unreachable,
-        error.NetworkNotFound => unreachable, // Windows-only
-
-        error.FileNotFound,
-        error.NotDir,
-        error.AccessDenied,
-        error.PermissionDenied,
-        error.NoDevice,
-        => return error.GLibCNotFound,
-
-        error.ProcessNotFound,
-        error.ProcessFdQuotaExceeded,
-        error.SystemFdQuotaExceeded,
-        error.SystemResources,
-        error.SymLinkLoop,
-        error.Unexpected,
-        => |e| return e,
+        error.NameTooLong => return error.Unexpected,
+        error.BadPathName => return error.Unexpected,
+        error.DeviceBusy => return error.Unexpected,
+        error.NetworkNotFound => return error.Unexpected, // Windows-only
+
+        error.FileNotFound => return error.GLibCNotFound,
+        error.NotDir => return error.GLibCNotFound,
+        error.AccessDenied => return error.GLibCNotFound,
+        error.PermissionDenied => return error.GLibCNotFound,
+        error.NoDevice => return error.GLibCNotFound,
+
+        error.ProcessFdQuotaExceeded => |e| return e,
+        error.SystemFdQuotaExceeded => |e| return e,
+        error.SystemResources => |e| return e,
+        error.SymLinkLoop => |e| return e,
+        error.Unexpected => |e| return e,
+        error.Canceled => |e| return e,
     };
     defer dir.close();
 
@@ -799,38 +795,34 @@ fn glibcVerFromRPath(io: Io, rpath: []const u8) !std.SemanticVersion {
     // that start with "GLIBC_2.".
     const glibc_so_basename = "libc.so.6";
     var file = dir.openFile(glibc_so_basename, .{}) catch |err| switch (err) {
-        error.NameTooLong => unreachable,
-        error.InvalidUtf8 => unreachable, // WASI only
-        error.InvalidWtf8 => unreachable, // Windows only
-        error.BadPathName => unreachable, // Windows only
-        error.PipeBusy => unreachable, // Windows-only
-        error.SharingViolation => unreachable, // Windows-only
-        error.NetworkNotFound => unreachable, // Windows-only
-        error.AntivirusInterference => unreachable, // Windows-only
-        error.FileLocksNotSupported => unreachable, // No lock requested.
-        error.NoSpaceLeft => unreachable, // read-only
-        error.PathAlreadyExists => unreachable, // read-only
-        error.DeviceBusy => unreachable, // read-only
-        error.FileBusy => unreachable, // read-only
-        error.WouldBlock => unreachable, // not using O_NONBLOCK
-        error.NoDevice => unreachable, // not asking for a special device
-
-        error.AccessDenied,
-        error.PermissionDenied,
-        error.FileNotFound,
-        error.NotDir,
-        error.IsDir,
-        => return error.GLibCNotFound,
-
+        error.NameTooLong => return error.Unexpected,
+        error.BadPathName => return error.Unexpected,
+        error.PipeBusy => return error.Unexpected, // Windows-only
+        error.SharingViolation => return error.Unexpected, // Windows-only
+        error.NetworkNotFound => return error.Unexpected, // Windows-only
+        error.AntivirusInterference => return error.Unexpected, // Windows-only
+        error.FileLocksNotSupported => return error.Unexpected, // No lock requested.
+        error.NoSpaceLeft => return error.Unexpected, // read-only
+        error.PathAlreadyExists => return error.Unexpected, // read-only
+        error.DeviceBusy => return error.Unexpected, // read-only
+        error.FileBusy => return error.Unexpected, // read-only
+        error.NoDevice => return error.Unexpected, // not asking for a special device
         error.FileTooBig => return error.Unexpected,
-
-        error.ProcessNotFound,
-        error.ProcessFdQuotaExceeded,
-        error.SystemFdQuotaExceeded,
-        error.SystemResources,
-        error.SymLinkLoop,
-        error.Unexpected,
-        => |e| return e,
+        error.WouldBlock => return error.Unexpected, // not opened in non-blocking
+
+        error.AccessDenied => return error.GLibCNotFound,
+        error.PermissionDenied => return error.GLibCNotFound,
+        error.FileNotFound => return error.GLibCNotFound,
+        error.NotDir => return error.GLibCNotFound,
+        error.IsDir => return error.GLibCNotFound,
+
+        error.ProcessNotFound => |e| return e,
+        error.ProcessFdQuotaExceeded => |e| return e,
+        error.SystemFdQuotaExceeded => |e| return e,
+        error.SystemResources => |e| return e,
+        error.SymLinkLoop => |e| return e,
+        error.Unexpected => |e| return e,
+        error.Canceled => |e| return e,
     };
     defer file.close();
 
@@ -1016,12 +1008,9 @@ fn detectAbiAndDynamicLinker(io: Io, cpu: Target.Cpu, os: Target.Os, query: Targ
                 error.NameTooLong => return error.Unexpected,
                 error.PathAlreadyExists => return error.Unexpected,
                 error.SharingViolation => return error.Unexpected,
-                error.InvalidUtf8 => return error.Unexpected, // WASI only
-                error.InvalidWtf8 => return error.Unexpected, // Windows only
                 error.BadPathName => return error.Unexpected,
                 error.PipeBusy => return error.Unexpected,
                 error.FileLocksNotSupported => return error.Unexpected,
-                error.WouldBlock => return error.Unexpected,
                 error.FileBusy => return error.Unexpected, // opened without write permissions
                 error.AntivirusInterference => return error.Unexpected, // Windows-only error
 
lib/std/dynamic_library.zig
@@ -137,6 +137,7 @@ const ElfDynLibError = error{
     ElfStringSectionNotFound,
     ElfSymSectionNotFound,
     ElfHashTableNotFound,
+    Canceled,
 } || posix.OpenError || posix.MMapError;
 
 pub const ElfDynLib = struct {
lib/std/Io.zig
@@ -654,20 +654,20 @@ pub const VTable = struct {
     conditionWait: *const fn (?*anyopaque, cond: *Condition, mutex: *Mutex) Cancelable!void,
     conditionWake: *const fn (?*anyopaque, cond: *Condition, wake: Condition.Wake) void,
 
-    dirMake: *const fn (?*anyopaque, dir: Dir, sub_path: []const u8, mode: Dir.Mode) Dir.MakeError!void,
-    dirStat: *const fn (?*anyopaque, dir: Dir) Dir.StatError!Dir.Stat,
-    dirStatPath: *const fn (?*anyopaque, dir: Dir, sub_path: []const u8) Dir.StatError!File.Stat,
-    fileStat: *const fn (?*anyopaque, file: File) File.StatError!File.Stat,
-    createFile: *const fn (?*anyopaque, dir: Dir, sub_path: []const u8, flags: File.CreateFlags) File.OpenError!File,
-    fileOpen: *const fn (?*anyopaque, dir: Dir, sub_path: []const u8, flags: File.OpenFlags) File.OpenError!File,
+    dirMake: *const fn (?*anyopaque, Dir, sub_path: []const u8, mode: Dir.Mode) Dir.MakeError!void,
+    dirStat: *const fn (?*anyopaque, Dir) Dir.StatError!Dir.Stat,
+    dirStatPath: *const fn (?*anyopaque, Dir, sub_path: []const u8, Dir.StatPathOptions) Dir.StatPathError!File.Stat,
+    fileStat: *const fn (?*anyopaque, File) File.StatError!File.Stat,
+    createFile: *const fn (?*anyopaque, Dir, sub_path: []const u8, File.CreateFlags) File.OpenError!File,
+    fileOpen: *const fn (?*anyopaque, Dir, sub_path: []const u8, File.OpenFlags) File.OpenError!File,
     fileClose: *const fn (?*anyopaque, File) void,
-    pwrite: *const fn (?*anyopaque, file: File, buffer: []const u8, offset: std.posix.off_t) File.PWriteError!usize,
+    pwrite: *const fn (?*anyopaque, File, buffer: []const u8, offset: std.posix.off_t) File.PWriteError!usize,
     /// Returns 0 on end of stream.
-    fileReadStreaming: *const fn (?*anyopaque, file: File, data: [][]u8) File.ReadStreamingError!usize,
+    fileReadStreaming: *const fn (?*anyopaque, File, data: [][]u8) File.ReadStreamingError!usize,
     /// Returns 0 on end of stream.
-    fileReadPositional: *const fn (?*anyopaque, file: File, data: [][]u8, offset: u64) File.ReadPositionalError!usize,
-    fileSeekBy: *const fn (?*anyopaque, file: File, offset: i64) File.SeekError!void,
-    fileSeekTo: *const fn (?*anyopaque, file: File, offset: u64) File.SeekError!void,
+    fileReadPositional: *const fn (?*anyopaque, File, data: [][]u8, offset: u64) File.ReadPositionalError!usize,
+    fileSeekBy: *const fn (?*anyopaque, File, offset: i64) File.SeekError!void,
+    fileSeekTo: *const fn (?*anyopaque, File, offset: u64) File.SeekError!void,
 
     now: *const fn (?*anyopaque, Clock) Clock.Error!Timestamp,
     sleep: *const fn (?*anyopaque, Timeout) SleepError!void,
@@ -795,6 +795,14 @@ pub const Clock = enum {
             };
         }
 
+        pub fn subDuration(from: Clock.Timestamp, duration: Clock.Duration) Clock.Timestamp {
+            assert(from.clock == duration.clock);
+            return .{
+                .raw = from.raw.subDuration(duration.raw),
+                .clock = from.clock,
+            };
+        }
+
         pub fn fromNow(io: Io, duration: Clock.Duration) Error!Clock.Timestamp {
             return .{
                 .clock = duration.clock,
@@ -855,6 +863,10 @@ pub const Timestamp = struct {
         return .{ .nanoseconds = from.nanoseconds + duration.nanoseconds };
     }
 
+    pub fn subDuration(from: Timestamp, duration: Duration) Timestamp {
+        return .{ .nanoseconds = from.nanoseconds - duration.nanoseconds };
+    }
+
     pub fn withClock(t: Timestamp, clock: Clock) Clock.Timestamp {
         return .{ .nanoseconds = t.nanoseconds, .clock = clock };
     }
lib/std/posix.zig
@@ -1542,81 +1542,7 @@ pub fn pwritev(fd: fd_t, iov: []const iovec_const, offset: u64) PWriteError!usiz
     }
 }
 
-pub const OpenError = error{
-    /// In WASI, this error may occur when the file descriptor does
-    /// not hold the required rights to open a new resource relative to it.
-    AccessDenied,
-    PermissionDenied,
-    SymLinkLoop,
-    ProcessFdQuotaExceeded,
-    SystemFdQuotaExceeded,
-    NoDevice,
-    /// Either:
-    /// * One of the path components does not exist.
-    /// * Cwd was used, but cwd has been deleted.
-    /// * The path associated with the open directory handle has been deleted.
-    /// * On macOS, multiple processes or threads raced to create the same file
-    ///   with `O.EXCL` set to `false`.
-    FileNotFound,
-
-    /// The path exceeded `max_path_bytes` bytes.
-    NameTooLong,
-
-    /// Insufficient kernel memory was available, or
-    /// the named file is a FIFO and per-user hard limit on
-    /// memory allocation for pipes has been reached.
-    SystemResources,
-
-    /// The file is too large to be opened. This error is unreachable
-    /// for 64-bit targets, as well as when opening directories.
-    FileTooBig,
-
-    /// The path refers to directory but the `DIRECTORY` flag was not provided.
-    IsDir,
-
-    /// A new path cannot be created because the device has no room for the new file.
-    /// This error is only reachable when the `CREAT` flag is provided.
-    NoSpaceLeft,
-
-    /// A component used as a directory in the path was not, in fact, a directory, or
-    /// `DIRECTORY` was specified and the path was not a directory.
-    NotDir,
-
-    /// The path already exists and the `CREAT` and `EXCL` flags were provided.
-    PathAlreadyExists,
-    DeviceBusy,
-
-    /// The underlying filesystem does not support file locks
-    FileLocksNotSupported,
-
-    /// Path contains characters that are disallowed by the underlying filesystem.
-    BadPathName,
-
-    /// WASI-only; file paths must be valid UTF-8.
-    InvalidUtf8,
-
-    /// Windows-only; file paths provided by the user must be valid WTF-8.
-    /// https://wtf-8.codeberg.page/
-    InvalidWtf8,
-
-    /// On Windows, `\\server` or `\\server\share` was not found.
-    NetworkNotFound,
-
-    /// This error occurs in Linux if the process to be open was not found.
-    ProcessNotFound,
-
-    /// One of these three things:
-    /// * pathname  refers to an executable image which is currently being
-    ///   executed and write access was requested.
-    /// * pathname refers to a file that is currently in  use  as  a  swap
-    ///   file, and the O_TRUNC flag was specified.
-    /// * pathname  refers  to  a file that is currently being read by the
-    ///   kernel (e.g., for module/firmware loading), and write access was
-    ///   requested.
-    FileBusy,
-
-    WouldBlock,
-} || UnexpectedError;
+pub const OpenError = std.Io.File.OpenError || error{WouldBlock};
 
 /// Open and possibly create a file. Keeps trying if it gets interrupted.
 /// On Windows, `file_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
lib/std/Thread.zig
@@ -318,10 +318,13 @@ pub fn getName(self: Thread, buffer_ptr: *[max_name_len:0]u8) GetNameError!?[]co
             var buf: [32]u8 = undefined;
             const path = try std.fmt.bufPrint(&buf, "/proc/self/task/{d}/comm", .{self.getHandle()});
 
+            var threaded: std.Io.Threaded = .init_single_threaded;
+            const io = threaded.io();
+
             const file = try std.fs.cwd().openFile(path, .{});
             defer file.close();
 
-            var file_reader = file.readerStreaming(&.{});
+            var file_reader = file.readerStreaming(io, &.{});
             const data_len = file_reader.interface.readSliceShort(buffer_ptr[0 .. max_name_len + 1]) catch |err| switch (err) {
                 error.ReadFailed => return file_reader.err.?,
             };
lib/std/Uri.zig
@@ -441,7 +441,7 @@ pub fn resolveInPlace(base: Uri, new_len: usize, aux_buf: *[]u8) ResolveInPlaceE
     };
 }
 
-fn validateHost(bytes: []const u8) []const u8 {
+fn validateHost(bytes: []const u8) HostName.ValidateError![]const u8 {
     try HostName.validate(bytes);
     return bytes;
 }