| author | |
| committer | |
| log | f8ea00bd6dccba678901b50904972c017c9e66a1 |
| tree | 1a5f6f077f1442325896b1f48e827f72a268e9ff |
| parent | 3bf0ce65a514e6f86241364c4a11089e32b3ba57 |
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 | }; |
| 25 | 25 | ||
| 26 | pub 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 | |||
| 37 | pub 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. | ||
| 53 | pub 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 | |||
| 26 | pub const OpenError = error{ | 57 | pub 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 | } |
| 82 | 82 | ||
| 83 | pub const OpenFlags = std.fs.File.OpenFlags; | 83 | pub const OpenMode = enum { |
| 84 | read_only, | ||
| 85 | write_only, | ||
| 86 | read_write, | ||
| 87 | }; | ||
| 88 | |||
| 89 | pub const Lock = enum { | ||
| 90 | none, | ||
| 91 | shared, | ||
| 92 | exclusive, | ||
| 93 | }; | ||
| 94 | |||
| 95 | pub 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 | |||
| 84 | pub const CreateFlags = std.fs.File.CreateFlags; | 144 | pub const CreateFlags = std.fs.File.CreateFlags; |
| 85 | 145 | ||
| 86 | pub const OpenError = error{ | 146 | pub 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, |
| 1007 | 1011 | ||
| 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 |
| 1103 | const lseek_sym = if (posix.lfs64_abi) posix.system.lseek64 else posix.system.lseek; | 1107 | const lseek_sym = if (posix.lfs64_abi) posix.system.lseek64 else posix.system.lseek; |
| 1104 | const preadv_sym = if (posix.lfs64_abi) posix.system.preadv64 else posix.system.preadv; | 1108 | const preadv_sym = if (posix.lfs64_abi) posix.system.preadv64 else posix.system.preadv; |
| 1105 | 1109 | ||
| 1110 | fn 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 | |||
| 1153 | fn 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 | |||
| 1106 | fn dirCreateFilePosix( | 1214 | fn 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. |
| 2 | const builtin = @import("builtin"); | ||
| 3 | const native_os = builtin.os.tag; | ||
| 2 | 4 | ||
| 3 | const std = @import("std.zig"); | 5 | const std = @import("std.zig"); |
| 4 | const builtin = @import("builtin"); | 6 | const Io = std.Io; |
| 5 | const root = @import("root"); | 7 | const root = @import("root"); |
| 6 | const mem = std.mem; | 8 | const mem = std.mem; |
| 7 | const base64 = std.base64; | 9 | const base64 = std.base64; |
| 8 | const crypto = std.crypto; | 10 | const crypto = std.crypto; |
| 9 | const Allocator = std.mem.Allocator; | 11 | const Allocator = std.mem.Allocator; |
| 10 | const assert = std.debug.assert; | 12 | const assert = std.debug.assert; |
| 11 | const native_os = builtin.os.tag; | ||
| 12 | const posix = std.posix; | 13 | const posix = std.posix; |
| 13 | const windows = std.os.windows; | 14 | const windows = std.os.windows; |
| 14 | 15 | ||
| ... | @@ -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. |
| 277 | pub fn accessAbsolute(absolute_path: []const u8, flags: File.OpenFlags) Dir.AccessError!void { | 278 | pub 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 | } |
| 2355 | 2355 | ||
| 2356 | pub const AccessError = posix.AccessError; | 2356 | /// Deprecated in favor of `Io.Dir.AccessError`. |
| 2357 | pub const AccessError = Io.Dir.AccessError; | ||
| 2357 | 2358 | ||
| 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/). | 2360 | pub 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. | ||
| 2365 | pub 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. | ||
| 2375 | pub 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`. | ||
| 2394 | pub 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 | } |
| 2398 | 2365 | ||
| 2399 | pub const CopyFileOptions = struct { | 2366 | pub 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) { |
| 40 | 40 | ||
| 41 | /// Deprecated in favor of `Io.File.OpenError`. | 41 | /// Deprecated in favor of `Io.File.OpenError`. |
| 42 | pub const OpenError = Io.File.OpenError || error{WouldBlock}; | 42 | pub const OpenError = Io.File.OpenError || error{WouldBlock}; |
| 43 | 43 | /// Deprecated in favor of `Io.File.OpenMode`. | |
| 44 | pub const OpenMode = enum { | 44 | pub const OpenMode = Io.File.OpenMode; |
| 45 | read_only, | 45 | /// Deprecated in favor of `Io.File.Lock`. |
| 46 | write_only, | 46 | pub const Lock = Io.File.Lock; |
| 47 | read_write, | 47 | /// Deprecated in favor of `Io.File.OpenFlags`. |
| 48 | }; | 48 | pub const OpenFlags = Io.File.OpenFlags; |
| 49 | |||
| 50 | pub const Lock = enum { | ||
| 51 | none, | ||
| 52 | shared, | ||
| 53 | exclusive, | ||
| 54 | }; | ||
| 55 | |||
| 56 | pub 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 | }; | ||
| 102 | 49 | ||
| 103 | pub const CreateFlags = struct { | 50 | pub 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 | }; |
| 4366 | 4365 | ||
| 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 | } |
| 4936 | 4935 | ||
| 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. | ||
| 4945 | pub 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. | ||
| 4989 | pub 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. | ||
| 5017 | pub 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 | |||
| 5052 | pub const PipeError = error{ | 4936 | pub const PipeError = error{ |
| 5053 | SystemFdQuotaExceeded, | 4937 | SystemFdQuotaExceeded, |
| 5054 | ProcessFdQuotaExceeded, | 4938 | ProcessFdQuotaExceeded, |