authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-10-16 20:53:28-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-10-29 06:20:50-07:00
logf8ea00bd6dccba678901b50904972c017c9e66a1
tree1a5f6f077f1442325896b1f48e827f72a268e9ff
parent3bf0ce65a514e6f86241364c4a11089e32b3ba57

std.Io: add dirAccess


8 files changed, 225 insertions(+), 226 deletions(-)

lib/std/Io.zig+1
...@@ -663,6 +663,7 @@ pub const VTable = struct {...@@ -663,6 +663,7 @@ pub const VTable = struct {
663 dirMake: *const fn (?*anyopaque, Dir, sub_path: []const u8, mode: Dir.Mode) Dir.MakeError!void,663 dirMake: *const fn (?*anyopaque, Dir, sub_path: []const u8, mode: Dir.Mode) Dir.MakeError!void,
664 dirStat: *const fn (?*anyopaque, Dir) Dir.StatError!Dir.Stat,664 dirStat: *const fn (?*anyopaque, Dir) Dir.StatError!Dir.Stat,
665 dirStatPath: *const fn (?*anyopaque, Dir, sub_path: []const u8, Dir.StatPathOptions) Dir.StatPathError!File.Stat,665 dirStatPath: *const fn (?*anyopaque, Dir, sub_path: []const u8, Dir.StatPathOptions) Dir.StatPathError!File.Stat,
666 dirAccess: *const fn (?*anyopaque, Dir, sub_path: []const u8, Dir.AccessOptions) Dir.AccessError!void,
666 dirCreateFile: *const fn (?*anyopaque, Dir, sub_path: []const u8, File.CreateFlags) File.OpenError!File,667 dirCreateFile: *const fn (?*anyopaque, Dir, sub_path: []const u8, File.CreateFlags) File.OpenError!File,
667 dirOpenFile: *const fn (?*anyopaque, Dir, sub_path: []const u8, File.OpenFlags) File.OpenError!File,668 dirOpenFile: *const fn (?*anyopaque, Dir, sub_path: []const u8, File.OpenFlags) File.OpenError!File,
668 fileStat: *const fn (?*anyopaque, File) File.StatError!File.Stat,669 fileStat: *const fn (?*anyopaque, File) File.StatError!File.Stat,
lib/std/Io/Dir.zig+31
...@@ -23,6 +23,37 @@ pub const PathNameError = error{...@@ -23,6 +23,37 @@ pub const PathNameError = error{
23 BadPathName,23 BadPathName,
24};24};
2525
26pub const AccessError = error{
27 AccessDenied,
28 PermissionDenied,
29 FileNotFound,
30 InputOutput,
31 SystemResources,
32 FileBusy,
33 SymLinkLoop,
34 ReadOnlyFileSystem,
35} || PathNameError || Io.Cancelable || Io.UnexpectedError;
36
37pub const AccessOptions = packed struct {
38 follow_symlinks: bool = true,
39 read: bool = false,
40 write: bool = false,
41 execute: bool = false,
42};
43
44/// Test accessing `sub_path`.
45///
46/// On Windows, `sub_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
47/// On WASI, `sub_path` should be encoded as valid UTF-8.
48/// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
49///
50/// Be careful of Time-Of-Check-Time-Of-Use race conditions when using this
51/// function. For example, instead of testing if a file exists and then opening
52/// it, just open it and handle the error for file not found.
53pub fn access(dir: Dir, io: Io, sub_path: []const u8, options: AccessOptions) AccessError!void {
54 return io.vtable.dirAccess(io.userdata, dir, sub_path, options);
55}
56
26pub const OpenError = error{57pub const OpenError = error{
27 FileNotFound,58 FileNotFound,
28 NotDir,59 NotDir,
lib/std/Io/File.zig+61-1
...@@ -80,7 +80,67 @@ pub fn stat(file: File, io: Io) StatError!Stat {...@@ -80,7 +80,67 @@ pub fn stat(file: File, io: Io) StatError!Stat {
80 return io.vtable.fileStat(io.userdata, file);80 return io.vtable.fileStat(io.userdata, file);
81}81}
8282
83pub const OpenFlags = std.fs.File.OpenFlags;83pub const OpenMode = enum {
84 read_only,
85 write_only,
86 read_write,
87};
88
89pub const Lock = enum {
90 none,
91 shared,
92 exclusive,
93};
94
95pub const OpenFlags = struct {
96 mode: OpenMode = .read_only,
97
98 /// Open the file with an advisory lock to coordinate with other processes
99 /// accessing it at the same time. An exclusive lock will prevent other
100 /// processes from acquiring a lock. A shared lock will prevent other
101 /// processes from acquiring a exclusive lock, but does not prevent
102 /// other process from getting their own shared locks.
103 ///
104 /// The lock is advisory, except on Linux in very specific circumstances[1].
105 /// This means that a process that does not respect the locking API can still get access
106 /// to the file, despite the lock.
107 ///
108 /// On these operating systems, the lock is acquired atomically with
109 /// opening the file:
110 /// * Darwin
111 /// * DragonFlyBSD
112 /// * FreeBSD
113 /// * Haiku
114 /// * NetBSD
115 /// * OpenBSD
116 /// On these operating systems, the lock is acquired via a separate syscall
117 /// after opening the file:
118 /// * Linux
119 /// * Windows
120 ///
121 /// [1]: https://www.kernel.org/doc/Documentation/filesystems/mandatory-locking.txt
122 lock: Lock = .none,
123
124 /// Sets whether or not to wait until the file is locked to return. If set to true,
125 /// `error.WouldBlock` will be returned. Otherwise, the file will wait until the file
126 /// is available to proceed.
127 lock_nonblocking: bool = false,
128
129 /// Set this to allow the opened file to automatically become the
130 /// controlling TTY for the current process.
131 allow_ctty: bool = false,
132
133 follow_symlinks: bool = true,
134
135 pub fn isRead(self: OpenFlags) bool {
136 return self.mode != .write_only;
137 }
138
139 pub fn isWrite(self: OpenFlags) bool {
140 return self.mode != .read_only;
141 }
142};
143
84pub const CreateFlags = std.fs.File.CreateFlags;144pub const CreateFlags = std.fs.File.CreateFlags;
85145
86pub const OpenError = error{146pub const OpenError = error{
lib/std/Io/Threaded.zig+113-5
...@@ -183,6 +183,11 @@ pub fn io(t: *Threaded) Io {...@@ -183,6 +183,11 @@ pub fn io(t: *Threaded) Io {
183 .wasi => fileStatWasi,183 .wasi => fileStatWasi,
184 else => fileStatPosix,184 else => fileStatPosix,
185 },185 },
186 .dirAccess = switch (builtin.os.tag) {
187 .windows => @panic("TODO"),
188 .wasi => dirAccessWasi,
189 else => dirAccessPosix,
190 },
186 .dirCreateFile = switch (builtin.os.tag) {191 .dirCreateFile = switch (builtin.os.tag) {
187 .windows => @panic("TODO"),192 .windows => @panic("TODO"),
188 .wasi => @panic("TODO"),193 .wasi => @panic("TODO"),
...@@ -992,7 +997,6 @@ fn dirStatPathWasi(...@@ -992,7 +997,6 @@ fn dirStatPathWasi(
992) Io.Dir.StatPathError!Io.File.Stat {997) Io.Dir.StatPathError!Io.File.Stat {
993 if (builtin.link_libc) return dirStatPathPosix(userdata, dir, sub_path, options);998 if (builtin.link_libc) return dirStatPathPosix(userdata, dir, sub_path, options);
994 const t: *Threaded = @ptrCast(@alignCast(userdata));999 const t: *Threaded = @ptrCast(@alignCast(userdata));
995 const dir_fd = dir.handle;
996 const wasi = std.os.wasi;1000 const wasi = std.os.wasi;
997 const flags: wasi.lookupflags_t = .{1001 const flags: wasi.lookupflags_t = .{
998 .SYMLINK_FOLLOW = @intFromBool(options.follow_symlinks),1002 .SYMLINK_FOLLOW = @intFromBool(options.follow_symlinks),
...@@ -1000,16 +1004,16 @@ fn dirStatPathWasi(...@@ -1000,16 +1004,16 @@ fn dirStatPathWasi(
1000 var stat: wasi.filestat_t = undefined;1004 var stat: wasi.filestat_t = undefined;
1001 while (true) {1005 while (true) {
1002 try t.checkCancel();1006 try t.checkCancel();
1003 switch (wasi.path_filestat_get(dir_fd, flags, sub_path.ptr, sub_path.len, &stat)) {1007 switch (wasi.path_filestat_get(dir.handle, flags, sub_path.ptr, sub_path.len, &stat)) {
1004 .SUCCESS => return statFromWasi(stat),1008 .SUCCESS => return statFromWasi(stat),
1005 .INTR => continue,1009 .INTR => continue,
1006 .CANCELED => return error.Canceled,1010 .CANCELED => return error.Canceled,
10071011
1008 .INVAL => |err| errnoBug(err),1012 .INVAL => |err| return errnoBug(err),
1009 .BADF => |err| errnoBug(err), // Always a race condition.1013 .BADF => |err| return errnoBug(err), // Always a race condition.
1010 .NOMEM => return error.SystemResources,1014 .NOMEM => return error.SystemResources,
1011 .ACCES => return error.AccessDenied,1015 .ACCES => return error.AccessDenied,
1012 .FAULT => |err| errnoBug(err),1016 .FAULT => |err| return errnoBug(err),
1013 .NAMETOOLONG => return error.NameTooLong,1017 .NAMETOOLONG => return error.NameTooLong,
1014 .NOENT => return error.FileNotFound,1018 .NOENT => return error.FileNotFound,
1015 .NOTDIR => return error.FileNotFound,1019 .NOTDIR => return error.FileNotFound,
...@@ -1103,6 +1107,110 @@ const fstatat_sym = if (posix.lfs64_abi) posix.system.fstatat64 else posix.syste...@@ -1103,6 +1107,110 @@ const fstatat_sym = if (posix.lfs64_abi) posix.system.fstatat64 else posix.syste
1103const lseek_sym = if (posix.lfs64_abi) posix.system.lseek64 else posix.system.lseek;1107const lseek_sym = if (posix.lfs64_abi) posix.system.lseek64 else posix.system.lseek;
1104const preadv_sym = if (posix.lfs64_abi) posix.system.preadv64 else posix.system.preadv;1108const preadv_sym = if (posix.lfs64_abi) posix.system.preadv64 else posix.system.preadv;
11051109
1110fn dirAccessPosix(
1111 userdata: ?*anyopaque,
1112 dir: Io.Dir,
1113 sub_path: []const u8,
1114 options: Io.Dir.AccessOptions,
1115) Io.Dir.AccessError!void {
1116 const t: *Threaded = @ptrCast(@alignCast(userdata));
1117
1118 var path_buffer: [posix.PATH_MAX]u8 = undefined;
1119 const sub_path_posix = try pathToPosix(sub_path, &path_buffer);
1120
1121 const flags: u32 = @as(u32, if (!options.follow_symlinks) posix.AT.SYMLINK_NOFOLLOW else 0);
1122
1123 const mode: u32 =
1124 @as(u32, if (options.read) posix.R_OK else 0) |
1125 @as(u32, if (options.write) posix.W_OK else 0) |
1126 @as(u32, if (options.execute) posix.X_OK else 0);
1127
1128 while (true) {
1129 try t.checkCancel();
1130 switch (posix.errno(posix.system.faccessat(dir.handle, sub_path_posix, mode, flags))) {
1131 .SUCCESS => return,
1132 .INTR => continue,
1133 .CANCELED => return error.Canceled,
1134
1135 .ACCES => return error.AccessDenied,
1136 .PERM => return error.PermissionDenied,
1137 .ROFS => return error.ReadOnlyFileSystem,
1138 .LOOP => return error.SymLinkLoop,
1139 .TXTBSY => return error.FileBusy,
1140 .NOTDIR => return error.FileNotFound,
1141 .NOENT => return error.FileNotFound,
1142 .NAMETOOLONG => return error.NameTooLong,
1143 .INVAL => |err| return errnoBug(err),
1144 .FAULT => |err| return errnoBug(err),
1145 .IO => return error.InputOutput,
1146 .NOMEM => return error.SystemResources,
1147 .ILSEQ => return error.BadPathName, // TODO move to wasi
1148 else => |err| return posix.unexpectedErrno(err),
1149 }
1150 }
1151}
1152
1153fn dirAccessWasi(
1154 userdata: ?*anyopaque,
1155 dir: Io.Dir,
1156 sub_path: []const u8,
1157 options: Io.File.OpenFlags,
1158) Io.File.AccessError!void {
1159 if (builtin.link_libc) return dirAccessPosix(userdata, dir, sub_path, options);
1160 const t: *Threaded = @ptrCast(@alignCast(userdata));
1161 const wasi = std.os.wasi;
1162 const flags: wasi.lookupflags_t = .{
1163 .SYMLINK_FOLLOW = @intFromBool(options.follow_symlinks),
1164 };
1165 const stat = while (true) {
1166 var stat: wasi.filestat_t = undefined;
1167 try t.checkCancel();
1168 switch (wasi.path_filestat_get(dir.handle, flags, sub_path.ptr, sub_path.len, &stat)) {
1169 .SUCCESS => break statFromWasi(stat),
1170 .INTR => continue,
1171 .CANCELED => return error.Canceled,
1172
1173 .INVAL => |err| return errnoBug(err),
1174 .BADF => |err| return errnoBug(err), // Always a race condition.
1175 .NOMEM => return error.SystemResources,
1176 .ACCES => return error.AccessDenied,
1177 .FAULT => |err| return errnoBug(err),
1178 .NAMETOOLONG => return error.NameTooLong,
1179 .NOENT => return error.FileNotFound,
1180 .NOTDIR => return error.FileNotFound,
1181 .NOTCAPABLE => return error.AccessDenied,
1182 .ILSEQ => return error.BadPathName,
1183 else => |err| return posix.unexpectedErrno(err),
1184 }
1185 };
1186
1187 if (!options.mode.read and !options.mode.write and !options.mode.execute)
1188 return;
1189
1190 var directory: wasi.fdstat_t = undefined;
1191 if (wasi.fd_fdstat_get(dir.handle, &directory) != .SUCCESS)
1192 return error.AccessDenied;
1193
1194 var rights: wasi.rights_t = .{};
1195 if (options.mode.read) {
1196 if (stat.filetype == .DIRECTORY) {
1197 rights.FD_READDIR = true;
1198 } else {
1199 rights.FD_READ = true;
1200 }
1201 }
1202 if (options.mode.write)
1203 rights.FD_WRITE = true;
1204
1205 // No validation for execution.
1206
1207 // https://github.com/ziglang/zig/issues/18882
1208 const rights_int: u64 = @bitCast(rights);
1209 const inheriting_int: u64 = @bitCast(directory.fs_rights_inheriting);
1210 if ((rights_int & inheriting_int) != rights_int)
1211 return error.AccessDenied;
1212}
1213
1106fn dirCreateFilePosix(1214fn dirCreateFilePosix(
1107 userdata: ?*anyopaque,1215 userdata: ?*anyopaque,
1108 dir: Io.Dir,1216 dir: Io.Dir,
lib/std/fs.zig+4-3
...@@ -1,14 +1,15 @@...@@ -1,14 +1,15 @@
1//! File System.1//! File System.
2const builtin = @import("builtin");
3const native_os = builtin.os.tag;
24
3const std = @import("std.zig");5const std = @import("std.zig");
4const builtin = @import("builtin");6const Io = std.Io;
5const root = @import("root");7const root = @import("root");
6const mem = std.mem;8const mem = std.mem;
7const base64 = std.base64;9const base64 = std.base64;
8const crypto = std.crypto;10const crypto = std.crypto;
9const Allocator = std.mem.Allocator;11const Allocator = std.mem.Allocator;
10const assert = std.debug.assert;12const assert = std.debug.assert;
11const native_os = builtin.os.tag;
12const posix = std.posix;13const posix = std.posix;
13const windows = std.os.windows;14const windows = std.os.windows;
1415
...@@ -274,7 +275,7 @@ pub fn openFileAbsoluteW(absolute_path_w: []const u16, flags: File.OpenFlags) Fi...@@ -274,7 +275,7 @@ pub fn openFileAbsoluteW(absolute_path_w: []const u16, flags: File.OpenFlags) Fi
274/// On Windows, `absolute_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).275/// On Windows, `absolute_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
275/// On WASI, `absolute_path` should be encoded as valid UTF-8.276/// On WASI, `absolute_path` should be encoded as valid UTF-8.
276/// On other platforms, `absolute_path` is an opaque sequence of bytes with no particular encoding.277/// On other platforms, `absolute_path` is an opaque sequence of bytes with no particular encoding.
277pub fn accessAbsolute(absolute_path: []const u8, flags: File.OpenFlags) Dir.AccessError!void {278pub fn accessAbsolute(absolute_path: []const u8, flags: Io.Dir.AccessOptions) Dir.AccessError!void {
278 assert(path.isAbsolute(absolute_path));279 assert(path.isAbsolute(absolute_path));
279 try cwd().access(absolute_path, flags);280 try cwd().access(absolute_path, flags);
280}281}
lib/std/fs/Dir.zig+7-40
...@@ -2353,47 +2353,14 @@ pub fn writeFile(self: Dir, options: WriteFileOptions) WriteFileError!void {...@@ -2353,47 +2353,14 @@ pub fn writeFile(self: Dir, options: WriteFileOptions) WriteFileError!void {
2353 try file.writeAll(options.data);2353 try file.writeAll(options.data);
2354}2354}
23552355
2356pub const AccessError = posix.AccessError;2356/// Deprecated in favor of `Io.Dir.AccessError`.
2357pub const AccessError = Io.Dir.AccessError;
23572358
2358/// Test accessing `sub_path`.2359/// Deprecated in favor of `Io.Dir.access`.
2359/// On Windows, `sub_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).2360pub fn access(self: Dir, sub_path: []const u8, options: Io.Dir.AccessOptions) AccessError!void {
2360/// On WASI, `sub_path` should be encoded as valid UTF-8.2361 var threaded: Io.Threaded = .init_single_threaded;
2361/// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.2362 const io = threaded.io();
2362/// Be careful of Time-Of-Check-Time-Of-Use race conditions when using this function.2363 return Io.Dir.access(self.adaptToNewApi(), io, sub_path, options);
2363/// For example, instead of testing if a file exists and then opening it, just
2364/// open it and handle the error for file not found.
2365pub fn access(self: Dir, sub_path: []const u8, flags: File.OpenFlags) AccessError!void {
2366 if (native_os == .windows) {
2367 const sub_path_w = try windows.sliceToPrefixedFileW(self.fd, sub_path);
2368 return self.accessW(sub_path_w.span().ptr, flags);
2369 }
2370 const path_c = try posix.toPosixPath(sub_path);
2371 return self.accessZ(&path_c, flags);
2372}
2373
2374/// Same as `access` except the path parameter is null-terminated.
2375pub fn accessZ(self: Dir, sub_path: [*:0]const u8, flags: File.OpenFlags) AccessError!void {
2376 if (native_os == .windows) {
2377 const sub_path_w = try windows.cStrToPrefixedFileW(self.fd, sub_path);
2378 return self.accessW(sub_path_w.span().ptr, flags);
2379 }
2380 const os_mode = switch (flags.mode) {
2381 .read_only => @as(u32, posix.F_OK),
2382 .write_only => @as(u32, posix.W_OK),
2383 .read_write => @as(u32, posix.R_OK | posix.W_OK),
2384 };
2385 const result = posix.faccessatZ(self.fd, sub_path, os_mode, 0);
2386 return result;
2387}
2388
2389/// Same as `access` except asserts the target OS is Windows and the path parameter is
2390/// * WTF-16 LE encoded
2391/// * null-terminated
2392/// * relative or has the NT namespace prefix
2393/// TODO currently this ignores `flags`.
2394pub fn accessW(self: Dir, sub_path_w: [*:0]const u16, flags: File.OpenFlags) AccessError!void {
2395 _ = flags;
2396 return posix.faccessatW(self.fd, sub_path_w);
2397}2364}
23982365
2399pub const CopyFileOptions = struct {2366pub const CopyFileOptions = struct {
lib/std/fs/File.zig+6-59
...@@ -40,65 +40,12 @@ pub const default_mode = switch (builtin.os.tag) {...@@ -40,65 +40,12 @@ pub const default_mode = switch (builtin.os.tag) {
4040
41/// Deprecated in favor of `Io.File.OpenError`.41/// Deprecated in favor of `Io.File.OpenError`.
42pub const OpenError = Io.File.OpenError || error{WouldBlock};42pub const OpenError = Io.File.OpenError || error{WouldBlock};
4343/// Deprecated in favor of `Io.File.OpenMode`.
44pub const OpenMode = enum {44pub const OpenMode = Io.File.OpenMode;
45 read_only,45/// Deprecated in favor of `Io.File.Lock`.
46 write_only,46pub const Lock = Io.File.Lock;
47 read_write,47/// Deprecated in favor of `Io.File.OpenFlags`.
48};48pub const OpenFlags = Io.File.OpenFlags;
49
50pub const Lock = enum {
51 none,
52 shared,
53 exclusive,
54};
55
56pub const OpenFlags = struct {
57 mode: OpenMode = .read_only,
58
59 /// Open the file with an advisory lock to coordinate with other processes
60 /// accessing it at the same time. An exclusive lock will prevent other
61 /// processes from acquiring a lock. A shared lock will prevent other
62 /// processes from acquiring a exclusive lock, but does not prevent
63 /// other process from getting their own shared locks.
64 ///
65 /// The lock is advisory, except on Linux in very specific circumstances[1].
66 /// This means that a process that does not respect the locking API can still get access
67 /// to the file, despite the lock.
68 ///
69 /// On these operating systems, the lock is acquired atomically with
70 /// opening the file:
71 /// * Darwin
72 /// * DragonFlyBSD
73 /// * FreeBSD
74 /// * Haiku
75 /// * NetBSD
76 /// * OpenBSD
77 /// On these operating systems, the lock is acquired via a separate syscall
78 /// after opening the file:
79 /// * Linux
80 /// * Windows
81 ///
82 /// [1]: https://www.kernel.org/doc/Documentation/filesystems/mandatory-locking.txt
83 lock: Lock = .none,
84
85 /// Sets whether or not to wait until the file is locked to return. If set to true,
86 /// `error.WouldBlock` will be returned. Otherwise, the file will wait until the file
87 /// is available to proceed.
88 lock_nonblocking: bool = false,
89
90 /// Set this to allow the opened file to automatically become the
91 /// controlling TTY for the current process.
92 allow_ctty: bool = false,
93
94 pub fn isRead(self: OpenFlags) bool {
95 return self.mode != .write_only;
96 }
97
98 pub fn isWrite(self: OpenFlags) bool {
99 return self.mode != .read_only;
100 }
101};
10249
103pub const CreateFlags = struct {50pub const CreateFlags = struct {
104 /// Whether the file will be created with read access.51 /// Whether the file will be created with read access.
lib/std/posix.zig+2-118
...@@ -4360,8 +4360,7 @@ pub const FStatAtError = FStatError || error{...@@ -4360,8 +4360,7 @@ pub const FStatAtError = FStatError || error{
4360 NameTooLong,4360 NameTooLong,
4361 FileNotFound,4361 FileNotFound,
4362 SymLinkLoop,4362 SymLinkLoop,
4363 /// WASI-only; file paths must be valid UTF-8.4363 BadPathName,
4364 InvalidUtf8,
4365};4364};
43664365
4367/// Similar to `fstat`, but returns stat of a resource pointed to by `pathname`4366/// Similar to `fstat`, but returns stat of a resource pointed to by `pathname`
...@@ -4900,7 +4899,7 @@ pub fn access(path: []const u8, mode: u32) AccessError!void {...@@ -4900,7 +4899,7 @@ pub fn access(path: []const u8, mode: u32) AccessError!void {
4900 _ = try windows.GetFileAttributesW(path_w.span().ptr);4899 _ = try windows.GetFileAttributesW(path_w.span().ptr);
4901 return;4900 return;
4902 } else if (native_os == .wasi and !builtin.link_libc) {4901 } else if (native_os == .wasi and !builtin.link_libc) {
4903 return faccessat(AT.FDCWD, path, mode, 0);4902 @compileError("wasi doesn't support absolute paths");
4904 }4903 }
4905 const path_c = try toPosixPath(path);4904 const path_c = try toPosixPath(path);
4906 return accessZ(&path_c, mode);4905 return accessZ(&path_c, mode);
...@@ -4934,121 +4933,6 @@ pub fn accessZ(path: [*:0]const u8, mode: u32) AccessError!void {...@@ -4934,121 +4933,6 @@ pub fn accessZ(path: [*:0]const u8, mode: u32) AccessError!void {
4934 }4933 }
4935}4934}
49364935
4937/// Check user's permissions for a file, based on an open directory handle.
4938///
4939/// * On Windows, asserts `path` is valid [WTF-8](https://wtf-8.codeberg.page/).
4940/// * On WASI, invalid UTF-8 passed to `path` causes `error.InvalidUtf8`.
4941/// * On other platforms, `path` is an opaque sequence of bytes with no particular encoding.
4942///
4943/// On Windows, `mode` is ignored. This is a POSIX API that is only partially supported by
4944/// Windows. See `fs` for the cross-platform file system API.
4945pub fn faccessat(dirfd: fd_t, path: []const u8, mode: u32, flags: u32) AccessError!void {
4946 if (native_os == .windows) {
4947 const path_w = try windows.sliceToPrefixedFileW(dirfd, path);
4948 return faccessatW(dirfd, path_w.span().ptr);
4949 } else if (native_os == .wasi and !builtin.link_libc) {
4950 const resolved: RelativePathWasi = .{ .dir_fd = dirfd, .relative_path = path };
4951
4952 const st = try std.os.fstatat_wasi(dirfd, path, .{
4953 .SYMLINK_FOLLOW = (flags & AT.SYMLINK_NOFOLLOW) == 0,
4954 });
4955
4956 if (mode != F_OK) {
4957 var directory: wasi.fdstat_t = undefined;
4958 if (wasi.fd_fdstat_get(resolved.dir_fd, &directory) != .SUCCESS) {
4959 return error.AccessDenied;
4960 }
4961
4962 var rights: wasi.rights_t = .{};
4963 if (mode & R_OK != 0) {
4964 if (st.filetype == .DIRECTORY) {
4965 rights.FD_READDIR = true;
4966 } else {
4967 rights.FD_READ = true;
4968 }
4969 }
4970 if (mode & W_OK != 0) {
4971 rights.FD_WRITE = true;
4972 }
4973 // No validation for X_OK
4974
4975 // https://github.com/ziglang/zig/issues/18882
4976 const rights_int: u64 = @bitCast(rights);
4977 const inheriting_int: u64 = @bitCast(directory.fs_rights_inheriting);
4978 if ((rights_int & inheriting_int) != rights_int) {
4979 return error.AccessDenied;
4980 }
4981 }
4982 return;
4983 }
4984 const path_c = try toPosixPath(path);
4985 return faccessatZ(dirfd, &path_c, mode, flags);
4986}
4987
4988/// Same as `faccessat` except the path parameter is null-terminated.
4989pub fn faccessatZ(dirfd: fd_t, path: [*:0]const u8, mode: u32, flags: u32) AccessError!void {
4990 if (native_os == .windows) {
4991 const path_w = try windows.cStrToPrefixedFileW(dirfd, path);
4992 return faccessatW(dirfd, path_w.span().ptr);
4993 } else if (native_os == .wasi and !builtin.link_libc) {
4994 return faccessat(dirfd, mem.sliceTo(path, 0), mode, flags);
4995 }
4996 switch (errno(system.faccessat(dirfd, path, mode, flags))) {
4997 .SUCCESS => return,
4998 .ACCES => return error.AccessDenied,
4999 .PERM => return error.PermissionDenied,
5000 .ROFS => return error.ReadOnlyFileSystem,
5001 .LOOP => return error.SymLinkLoop,
5002 .TXTBSY => return error.FileBusy,
5003 .NOTDIR => return error.FileNotFound,
5004 .NOENT => return error.FileNotFound,
5005 .NAMETOOLONG => return error.NameTooLong,
5006 .INVAL => unreachable,
5007 .FAULT => unreachable,
5008 .IO => return error.InputOutput,
5009 .NOMEM => return error.SystemResources,
5010 .ILSEQ => return error.BadPathName,
5011 else => |err| return unexpectedErrno(err),
5012 }
5013}
5014
5015/// Same as `faccessat` except asserts the target is Windows and the path parameter
5016/// is NtDll-prefixed, null-terminated, WTF-16 encoded.
5017pub fn faccessatW(dirfd: fd_t, sub_path_w: [*:0]const u16) AccessError!void {
5018 if (sub_path_w[0] == '.' and sub_path_w[1] == 0) {
5019 return;
5020 }
5021 if (sub_path_w[0] == '.' and sub_path_w[1] == '.' and sub_path_w[2] == 0) {
5022 return;
5023 }
5024
5025 const path_len_bytes = cast(u16, mem.sliceTo(sub_path_w, 0).len * 2) orelse return error.NameTooLong;
5026 var nt_name = windows.UNICODE_STRING{
5027 .Length = path_len_bytes,
5028 .MaximumLength = path_len_bytes,
5029 .Buffer = @constCast(sub_path_w),
5030 };
5031 var attr = windows.OBJECT_ATTRIBUTES{
5032 .Length = @sizeOf(windows.OBJECT_ATTRIBUTES),
5033 .RootDirectory = if (fs.path.isAbsoluteWindowsW(sub_path_w)) null else dirfd,
5034 .Attributes = 0, // Note we do not use OBJ_CASE_INSENSITIVE here.
5035 .ObjectName = &nt_name,
5036 .SecurityDescriptor = null,
5037 .SecurityQualityOfService = null,
5038 };
5039 var basic_info: windows.FILE_BASIC_INFORMATION = undefined;
5040 switch (windows.ntdll.NtQueryAttributesFile(&attr, &basic_info)) {
5041 .SUCCESS => return,
5042 .OBJECT_NAME_NOT_FOUND => return error.FileNotFound,
5043 .OBJECT_PATH_NOT_FOUND => return error.FileNotFound,
5044 .OBJECT_NAME_INVALID => unreachable,
5045 .INVALID_PARAMETER => unreachable,
5046 .ACCESS_DENIED => return error.AccessDenied,
5047 .OBJECT_PATH_SYNTAX_BAD => unreachable,
5048 else => |rc| return windows.unexpectedStatus(rc),
5049 }
5050}
5051
5052pub const PipeError = error{4936pub const PipeError = error{
5053 SystemFdQuotaExceeded,4937 SystemFdQuotaExceeded,
5054 ProcessFdQuotaExceeded,4938 ProcessFdQuotaExceeded,