From 5119cf6ffd0cf92586bc26f3f2d6e1e623fd17a8 Mon Sep 17 00:00:00 2001 From: eshom Date: Wed, 18 Mar 2026 22:36:57 +0200 Subject: [PATCH 1/3] std.Io.Threaded: syscall with O_TMPFILE flag can return OPNOTSUPP --- lib/std/Io/Threaded.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index b8deda5845..3f47272c12 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -4704,7 +4704,7 @@ fn dirCreateFileAtomic( try syscall.checkCancel(); continue; }, - .ISDIR, .NOENT => { + .ISDIR, .NOENT, .OPNOTSUPP => { // Ambiguous error code. It might mean the file system // does not support O_TMPFILE. Therefore, we must fall // back to not using O_TMPFILE. From 2054a257c288c44df52510ac2e7646a08df8a4bb Mon Sep 17 00:00:00 2001 From: eshom Date: Wed, 18 Mar 2026 22:38:22 +0200 Subject: [PATCH 2/3] std.Io.Uring: handle UnsupportedOperation for O_TMPFILE case Having `std.Io.Uring` contain OpenError error set allows handling of OperationUnsupported specifically for `.openat`, while avoiding propagating this error to the more general `File.OpenError`. This more specific subset also eliminated 2 unreachable prongs that previously inherited from `File.OpenError`. Added comments when handling OperationUnsupported, making it clear it is an unexpected error when TMPFILE bit is not set. --- lib/std/Io/Uring.zig | 52 ++++++++++++++++++++++++++++++++++---------- 1 file changed, 40 insertions(+), 12 deletions(-) diff --git a/lib/std/Io/Uring.zig b/lib/std/Io/Uring.zig index 69a8a28aae..bce1d9d54f 100644 --- a/lib/std/Io/Uring.zig +++ b/lib/std/Io/Uring.zig @@ -593,7 +593,10 @@ const CachedFd = struct { @atomicStore(Once, &cached_fd.once, .uninitialized, .monotonic); futexWake(ev, @ptrCast(&cached_fd.once), 1); } - const fd = try ev.openat(cancel_region, linux.AT.FDCWD, path, flags, 0); + const fd = ev.openat(cancel_region, linux.AT.FDCWD, path, flags, 0) catch |err| switch (err) { + error.OperationUnsupported => return error.Unexpected, // Not expecting O_TMPFILE flag + else => |e| return e, + }; @atomicStore(Once, &cached_fd.once, .fromFd(fd), .monotonic); futexWake(ev, @ptrCast(&cached_fd.once), std.math.maxInt(u32)); return fd; @@ -2725,9 +2728,8 @@ fn dirOpenDir( error.DeviceBusy => return errnoBug(.BUSY), // O_EXCL not passed error.FileBusy => return errnoBug(.TXTBSY), error.PathAlreadyExists => return errnoBug(.EXIST), // Not creating. - error.PipeBusy => return error.Unexpected, // Not opening a pipe. - error.AntivirusInterference => unreachable, // Windows-only error.FileLocksUnsupported => return errnoBug(.OPNOTSUPP), // Not asking for locks. + error.OperationUnsupported => return errnoBug(.OPNOTSUPP), // Not asking for O_TMPFILE. else => |e| return e, }, }; @@ -2810,13 +2812,16 @@ fn dirCreateFile( var maybe_sync: CancelRegion.Sync.Maybe = .{ .cancel_region = .init() }; defer maybe_sync.deinit(ev); - const fd = try ev.openat(&maybe_sync.cancel_region, dir.handle, sub_path_posix, .{ + const fd = ev.openat(&maybe_sync.cancel_region, dir.handle, sub_path_posix, .{ .ACCMODE = if (flags.read) .RDWR else .WRONLY, .CREAT = true, .TRUNC = flags.truncate, .EXCL = flags.exclusive, .CLOEXEC = true, - }, flags.permissions.toMode()); + }, flags.permissions.toMode()) catch |err| switch (err) { + error.OperationUnsupported => return error.Unexpected, // TMPFILE bit not set. + else => |e| return e, + }; errdefer ev.closeAsync(fd); switch (flags.lock) { @@ -2892,7 +2897,7 @@ fn dirCreateFileAtomic( flags, options.permissions.toMode(), ) catch |err| switch (err) { - error.IsDir, error.FileNotFound => { + error.IsDir, error.FileNotFound, error.OperationUnsupported => { // Ambiguous error code. It might mean the file system // does not support O_TMPFILE. Therefore, we must fall // back to not using O_TMPFILE. @@ -2901,8 +2906,6 @@ fn dirCreateFileAtomic( error.FileTooBig => return errnoBug(.FBIG), error.DeviceBusy => return errnoBug(.BUSY), // O_EXCL not passed error.PathAlreadyExists => return errnoBug(.EXIST), // Not creating. - error.PipeBusy => return error.Unexpected, // Not opening a pipe. - error.AntivirusInterference => unreachable, // Windows-only error.FileLocksUnsupported => return errnoBug(.OPNOTSUPP), // Not asking for locks. else => |e| return e, }, @@ -2994,7 +2997,7 @@ fn dirOpenFile( var maybe_sync: CancelRegion.Sync.Maybe = .{ .cancel_region = .init() }; defer maybe_sync.deinit(ev); - const fd = try ev.openat(&maybe_sync.cancel_region, dir.handle, sub_path_posix, .{ + const fd = ev.openat(&maybe_sync.cancel_region, dir.handle, sub_path_posix, .{ .ACCMODE = switch (flags.mode) { .read_only => .RDONLY, .write_only => .WRONLY, @@ -3004,7 +3007,10 @@ fn dirOpenFile( .NOFOLLOW = !flags.follow_symlinks, .CLOEXEC = true, .PATH = flags.path_only, - }, 0); + }, 0) catch |err| switch (err) { + error.OperationUnsupported => return error.Unexpected, // TMPFILE bit not set. + else => |e| return e, + }; errdefer ev.closeAsync(fd); if (!flags.allow_directory) { @@ -5609,6 +5615,27 @@ fn lseek( } } +const OpenError = error{ + AccessDenied, + FileTooBig, + IsDir, + SymLinkLoop, + ProcessFdQuotaExceeded, + SystemFdQuotaExceeded, + NoDevice, + FileNotFound, + SystemResources, + NoSpaceLeft, + NotDir, + PermissionDenied, + PathAlreadyExists, + DeviceBusy, + OperationUnsupported, + FileLocksUnsupported, + WouldBlock, + FileBusy, +} || Dir.PathNameError || Io.Cancelable || Io.UnexpectedError; + fn openat( ev: *Evented, cancel_region: *CancelRegion, @@ -5616,7 +5643,7 @@ fn openat( path: [*:0]const u8, flags: linux.O, mode: linux.mode_t, -) File.OpenError!fd_t { +) OpenError!fd_t { var mut_flags = flags; if (@hasField(linux.O, "LARGEFILE")) mut_flags.LARGEFILE = true; while (true) { @@ -5662,7 +5689,8 @@ fn openat( .PERM => return error.PermissionDenied, .EXIST => return error.PathAlreadyExists, .BUSY => return error.DeviceBusy, - .OPNOTSUPP => return error.FileLocksUnsupported, + // File locking and TMPFILE are mutually exclusive + .OPNOTSUPP => return if (flags.TMPFILE) error.OperationUnsupported else error.FileLocksUnsupported, .AGAIN => return error.WouldBlock, .TXTBSY => return error.FileBusy, .NXIO => return error.NoDevice, From a9e5c72aa885c67348277ca982fb964fd686254e Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 20 Mar 2026 12:18:25 -0700 Subject: [PATCH 3/3] Io.Uring: simplify openat error handling --- lib/std/Io/Uring.zig | 42 ++++++++++-------------------------------- 1 file changed, 10 insertions(+), 32 deletions(-) diff --git a/lib/std/Io/Uring.zig b/lib/std/Io/Uring.zig index bce1d9d54f..c3330463db 100644 --- a/lib/std/Io/Uring.zig +++ b/lib/std/Io/Uring.zig @@ -594,7 +594,7 @@ const CachedFd = struct { futexWake(ev, @ptrCast(&cached_fd.once), 1); } const fd = ev.openat(cancel_region, linux.AT.FDCWD, path, flags, 0) catch |err| switch (err) { - error.OperationUnsupported => return error.Unexpected, // Not expecting O_TMPFILE flag + error.OperationUnsupported => return error.Unexpected, // TMPFILE unset. else => |e| return e, }; @atomicStore(Once, &cached_fd.once, .fromFd(fd), .monotonic); @@ -2725,11 +2725,10 @@ fn dirOpenDir( error.WouldBlock => return errnoBug(.AGAIN), error.FileTooBig => return errnoBug(.FBIG), error.NoSpaceLeft => return errnoBug(.NOSPC), - error.DeviceBusy => return errnoBug(.BUSY), // O_EXCL not passed + error.DeviceBusy => return errnoBug(.BUSY), // EXCL unset. error.FileBusy => return errnoBug(.TXTBSY), error.PathAlreadyExists => return errnoBug(.EXIST), // Not creating. - error.FileLocksUnsupported => return errnoBug(.OPNOTSUPP), // Not asking for locks. - error.OperationUnsupported => return errnoBug(.OPNOTSUPP), // Not asking for O_TMPFILE. + error.OperationUnsupported => return errnoBug(.OPNOTSUPP), // No TMPFILE, no locks. else => |e| return e, }, }; @@ -2819,7 +2818,7 @@ fn dirCreateFile( .EXCL = flags.exclusive, .CLOEXEC = true, }, flags.permissions.toMode()) catch |err| switch (err) { - error.OperationUnsupported => return error.Unexpected, // TMPFILE bit not set. + error.OperationUnsupported => return error.Unexpected, // TMPFILE unset. else => |e| return e, }; errdefer ev.closeAsync(fd); @@ -2906,7 +2905,6 @@ fn dirCreateFileAtomic( error.FileTooBig => return errnoBug(.FBIG), error.DeviceBusy => return errnoBug(.BUSY), // O_EXCL not passed error.PathAlreadyExists => return errnoBug(.EXIST), // Not creating. - error.FileLocksUnsupported => return errnoBug(.OPNOTSUPP), // Not asking for locks. else => |e| return e, }, .flags = .{ .nonblocking = false }, @@ -3008,7 +3006,7 @@ fn dirOpenFile( .CLOEXEC = true, .PATH = flags.path_only, }, 0) catch |err| switch (err) { - error.OperationUnsupported => return error.Unexpected, // TMPFILE bit not set. + error.OperationUnsupported => return error.Unexpected, // TMPFILE unset. else => |e| return e, }; errdefer ev.closeAsync(fd); @@ -3155,7 +3153,7 @@ fn dirRealPathFile( .PATH = true, }, 0) catch |err| switch (err) { error.WouldBlock => return errnoBug(.AGAIN), - error.FileLocksUnsupported => return errnoBug(.OPNOTSUPP), // Not asking for locks. + error.OperationUnsupported => return errnoBug(.OPNOTSUPP), // Not asking for locks. else => |e| return e, }; defer ev.closeAsync(fd); @@ -5615,27 +5613,6 @@ fn lseek( } } -const OpenError = error{ - AccessDenied, - FileTooBig, - IsDir, - SymLinkLoop, - ProcessFdQuotaExceeded, - SystemFdQuotaExceeded, - NoDevice, - FileNotFound, - SystemResources, - NoSpaceLeft, - NotDir, - PermissionDenied, - PathAlreadyExists, - DeviceBusy, - OperationUnsupported, - FileLocksUnsupported, - WouldBlock, - FileBusy, -} || Dir.PathNameError || Io.Cancelable || Io.UnexpectedError; - fn openat( ev: *Evented, cancel_region: *CancelRegion, @@ -5643,7 +5620,7 @@ fn openat( path: [*:0]const u8, flags: linux.O, mode: linux.mode_t, -) OpenError!fd_t { +) !fd_t { var mut_flags = flags; if (@hasField(linux.O, "LARGEFILE")) mut_flags.LARGEFILE = true; while (true) { @@ -5689,8 +5666,9 @@ fn openat( .PERM => return error.PermissionDenied, .EXIST => return error.PathAlreadyExists, .BUSY => return error.DeviceBusy, - // File locking and TMPFILE are mutually exclusive - .OPNOTSUPP => return if (flags.TMPFILE) error.OperationUnsupported else error.FileLocksUnsupported, + // This can be triggered by file locking and TMPFILE, but those + // flags are mutually exclusive. + .OPNOTSUPP => return error.OperationUnsupported, .AGAIN => return error.WouldBlock, .TXTBSY => return error.FileBusy, .NXIO => return error.NoDevice,