Commit f8ea00bd6d
lib/std/fs/Dir.zig
@@ -2353,47 +2353,14 @@ pub fn writeFile(self: Dir, options: WriteFileOptions) WriteFileError!void {
try file.writeAll(options.data);
}
-pub const AccessError = posix.AccessError;
+/// Deprecated in favor of `Io.Dir.AccessError`.
+pub const AccessError = Io.Dir.AccessError;
-/// Test accessing `sub_path`.
-/// On Windows, `sub_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
-/// 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.
-/// Be careful of Time-Of-Check-Time-Of-Use race conditions when using this function.
-/// For example, instead of testing if a file exists and then opening it, just
-/// open it and handle the error for file not found.
-pub fn access(self: Dir, sub_path: []const u8, flags: File.OpenFlags) AccessError!void {
- if (native_os == .windows) {
- const sub_path_w = try windows.sliceToPrefixedFileW(self.fd, sub_path);
- return self.accessW(sub_path_w.span().ptr, flags);
- }
- const path_c = try posix.toPosixPath(sub_path);
- return self.accessZ(&path_c, flags);
-}
-
-/// Same as `access` except the path parameter is null-terminated.
-pub fn accessZ(self: Dir, sub_path: [*:0]const u8, flags: File.OpenFlags) AccessError!void {
- if (native_os == .windows) {
- const sub_path_w = try windows.cStrToPrefixedFileW(self.fd, sub_path);
- return self.accessW(sub_path_w.span().ptr, flags);
- }
- const os_mode = switch (flags.mode) {
- .read_only => @as(u32, posix.F_OK),
- .write_only => @as(u32, posix.W_OK),
- .read_write => @as(u32, posix.R_OK | posix.W_OK),
- };
- const result = posix.faccessatZ(self.fd, sub_path, os_mode, 0);
- return result;
-}
-
-/// Same as `access` except asserts the target OS is Windows and the path parameter is
-/// * WTF-16 LE encoded
-/// * null-terminated
-/// * relative or has the NT namespace prefix
-/// TODO currently this ignores `flags`.
-pub fn accessW(self: Dir, sub_path_w: [*:0]const u16, flags: File.OpenFlags) AccessError!void {
- _ = flags;
- return posix.faccessatW(self.fd, sub_path_w);
+/// Deprecated in favor of `Io.Dir.access`.
+pub fn access(self: Dir, sub_path: []const u8, options: Io.Dir.AccessOptions) AccessError!void {
+ var threaded: Io.Threaded = .init_single_threaded;
+ const io = threaded.io();
+ return Io.Dir.access(self.adaptToNewApi(), io, sub_path, options);
}
pub const CopyFileOptions = struct {
lib/std/fs/File.zig
@@ -40,65 +40,12 @@ pub const default_mode = switch (builtin.os.tag) {
/// Deprecated in favor of `Io.File.OpenError`.
pub const OpenError = Io.File.OpenError || error{WouldBlock};
-
-pub const OpenMode = enum {
- read_only,
- write_only,
- read_write,
-};
-
-pub const Lock = enum {
- none,
- shared,
- exclusive,
-};
-
-pub const OpenFlags = struct {
- mode: OpenMode = .read_only,
-
- /// Open the file with an advisory lock to coordinate with other processes
- /// accessing it at the same time. An exclusive lock will prevent other
- /// processes from acquiring a lock. A shared lock will prevent other
- /// processes from acquiring a exclusive lock, but does not prevent
- /// other process from getting their own shared locks.
- ///
- /// The lock is advisory, except on Linux in very specific circumstances[1].
- /// This means that a process that does not respect the locking API can still get access
- /// to the file, despite the lock.
- ///
- /// On these operating systems, the lock is acquired atomically with
- /// opening the file:
- /// * Darwin
- /// * DragonFlyBSD
- /// * FreeBSD
- /// * Haiku
- /// * NetBSD
- /// * OpenBSD
- /// On these operating systems, the lock is acquired via a separate syscall
- /// after opening the file:
- /// * Linux
- /// * Windows
- ///
- /// [1]: https://www.kernel.org/doc/Documentation/filesystems/mandatory-locking.txt
- lock: Lock = .none,
-
- /// Sets whether or not to wait until the file is locked to return. If set to true,
- /// `error.WouldBlock` will be returned. Otherwise, the file will wait until the file
- /// is available to proceed.
- lock_nonblocking: bool = false,
-
- /// Set this to allow the opened file to automatically become the
- /// controlling TTY for the current process.
- allow_ctty: bool = false,
-
- pub fn isRead(self: OpenFlags) bool {
- return self.mode != .write_only;
- }
-
- pub fn isWrite(self: OpenFlags) bool {
- return self.mode != .read_only;
- }
-};
+/// Deprecated in favor of `Io.File.OpenMode`.
+pub const OpenMode = Io.File.OpenMode;
+/// Deprecated in favor of `Io.File.Lock`.
+pub const Lock = Io.File.Lock;
+/// Deprecated in favor of `Io.File.OpenFlags`.
+pub const OpenFlags = Io.File.OpenFlags;
pub const CreateFlags = struct {
/// Whether the file will be created with read access.
lib/std/Io/Dir.zig
@@ -23,6 +23,37 @@ pub const PathNameError = error{
BadPathName,
};
+pub const AccessError = error{
+ AccessDenied,
+ PermissionDenied,
+ FileNotFound,
+ InputOutput,
+ SystemResources,
+ FileBusy,
+ SymLinkLoop,
+ ReadOnlyFileSystem,
+} || PathNameError || Io.Cancelable || Io.UnexpectedError;
+
+pub const AccessOptions = packed struct {
+ follow_symlinks: bool = true,
+ read: bool = false,
+ write: bool = false,
+ execute: bool = false,
+};
+
+/// Test accessing `sub_path`.
+///
+/// On Windows, `sub_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
+/// 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.
+///
+/// Be careful of Time-Of-Check-Time-Of-Use race conditions when using this
+/// function. For example, instead of testing if a file exists and then opening
+/// it, just open it and handle the error for file not found.
+pub fn access(dir: Dir, io: Io, sub_path: []const u8, options: AccessOptions) AccessError!void {
+ return io.vtable.dirAccess(io.userdata, dir, sub_path, options);
+}
+
pub const OpenError = error{
FileNotFound,
NotDir,
lib/std/Io/File.zig
@@ -80,7 +80,67 @@ pub fn stat(file: File, io: Io) StatError!Stat {
return io.vtable.fileStat(io.userdata, file);
}
-pub const OpenFlags = std.fs.File.OpenFlags;
+pub const OpenMode = enum {
+ read_only,
+ write_only,
+ read_write,
+};
+
+pub const Lock = enum {
+ none,
+ shared,
+ exclusive,
+};
+
+pub const OpenFlags = struct {
+ mode: OpenMode = .read_only,
+
+ /// Open the file with an advisory lock to coordinate with other processes
+ /// accessing it at the same time. An exclusive lock will prevent other
+ /// processes from acquiring a lock. A shared lock will prevent other
+ /// processes from acquiring a exclusive lock, but does not prevent
+ /// other process from getting their own shared locks.
+ ///
+ /// The lock is advisory, except on Linux in very specific circumstances[1].
+ /// This means that a process that does not respect the locking API can still get access
+ /// to the file, despite the lock.
+ ///
+ /// On these operating systems, the lock is acquired atomically with
+ /// opening the file:
+ /// * Darwin
+ /// * DragonFlyBSD
+ /// * FreeBSD
+ /// * Haiku
+ /// * NetBSD
+ /// * OpenBSD
+ /// On these operating systems, the lock is acquired via a separate syscall
+ /// after opening the file:
+ /// * Linux
+ /// * Windows
+ ///
+ /// [1]: https://www.kernel.org/doc/Documentation/filesystems/mandatory-locking.txt
+ lock: Lock = .none,
+
+ /// Sets whether or not to wait until the file is locked to return. If set to true,
+ /// `error.WouldBlock` will be returned. Otherwise, the file will wait until the file
+ /// is available to proceed.
+ lock_nonblocking: bool = false,
+
+ /// Set this to allow the opened file to automatically become the
+ /// controlling TTY for the current process.
+ allow_ctty: bool = false,
+
+ follow_symlinks: bool = true,
+
+ pub fn isRead(self: OpenFlags) bool {
+ return self.mode != .write_only;
+ }
+
+ pub fn isWrite(self: OpenFlags) bool {
+ return self.mode != .read_only;
+ }
+};
+
pub const CreateFlags = std.fs.File.CreateFlags;
pub const OpenError = error{
lib/std/Io/Threaded.zig
@@ -183,6 +183,11 @@ pub fn io(t: *Threaded) Io {
.wasi => fileStatWasi,
else => fileStatPosix,
},
+ .dirAccess = switch (builtin.os.tag) {
+ .windows => @panic("TODO"),
+ .wasi => dirAccessWasi,
+ else => dirAccessPosix,
+ },
.dirCreateFile = switch (builtin.os.tag) {
.windows => @panic("TODO"),
.wasi => @panic("TODO"),
@@ -992,7 +997,6 @@ fn dirStatPathWasi(
) Io.Dir.StatPathError!Io.File.Stat {
if (builtin.link_libc) return dirStatPathPosix(userdata, dir, sub_path, options);
const t: *Threaded = @ptrCast(@alignCast(userdata));
- const dir_fd = dir.handle;
const wasi = std.os.wasi;
const flags: wasi.lookupflags_t = .{
.SYMLINK_FOLLOW = @intFromBool(options.follow_symlinks),
@@ -1000,16 +1004,16 @@ fn dirStatPathWasi(
var stat: wasi.filestat_t = undefined;
while (true) {
try t.checkCancel();
- switch (wasi.path_filestat_get(dir_fd, flags, sub_path.ptr, sub_path.len, &stat)) {
+ switch (wasi.path_filestat_get(dir.handle, flags, sub_path.ptr, sub_path.len, &stat)) {
.SUCCESS => return statFromWasi(stat),
.INTR => continue,
.CANCELED => return error.Canceled,
- .INVAL => |err| errnoBug(err),
- .BADF => |err| errnoBug(err), // Always a race condition.
+ .INVAL => |err| return errnoBug(err),
+ .BADF => |err| return errnoBug(err), // Always a race condition.
.NOMEM => return error.SystemResources,
.ACCES => return error.AccessDenied,
- .FAULT => |err| errnoBug(err),
+ .FAULT => |err| return errnoBug(err),
.NAMETOOLONG => return error.NameTooLong,
.NOENT => return error.FileNotFound,
.NOTDIR => return error.FileNotFound,
@@ -1103,6 +1107,110 @@ const fstatat_sym = if (posix.lfs64_abi) posix.system.fstatat64 else posix.syste
const lseek_sym = if (posix.lfs64_abi) posix.system.lseek64 else posix.system.lseek;
const preadv_sym = if (posix.lfs64_abi) posix.system.preadv64 else posix.system.preadv;
+fn dirAccessPosix(
+ userdata: ?*anyopaque,
+ dir: Io.Dir,
+ sub_path: []const u8,
+ options: Io.Dir.AccessOptions,
+) Io.Dir.AccessError!void {
+ const t: *Threaded = @ptrCast(@alignCast(userdata));
+
+ var path_buffer: [posix.PATH_MAX]u8 = undefined;
+ const sub_path_posix = try pathToPosix(sub_path, &path_buffer);
+
+ const flags: u32 = @as(u32, if (!options.follow_symlinks) posix.AT.SYMLINK_NOFOLLOW else 0);
+
+ const mode: u32 =
+ @as(u32, if (options.read) posix.R_OK else 0) |
+ @as(u32, if (options.write) posix.W_OK else 0) |
+ @as(u32, if (options.execute) posix.X_OK else 0);
+
+ while (true) {
+ try t.checkCancel();
+ switch (posix.errno(posix.system.faccessat(dir.handle, sub_path_posix, mode, flags))) {
+ .SUCCESS => return,
+ .INTR => continue,
+ .CANCELED => return error.Canceled,
+
+ .ACCES => return error.AccessDenied,
+ .PERM => return error.PermissionDenied,
+ .ROFS => return error.ReadOnlyFileSystem,
+ .LOOP => return error.SymLinkLoop,
+ .TXTBSY => return error.FileBusy,
+ .NOTDIR => return error.FileNotFound,
+ .NOENT => return error.FileNotFound,
+ .NAMETOOLONG => return error.NameTooLong,
+ .INVAL => |err| return errnoBug(err),
+ .FAULT => |err| return errnoBug(err),
+ .IO => return error.InputOutput,
+ .NOMEM => return error.SystemResources,
+ .ILSEQ => return error.BadPathName, // TODO move to wasi
+ else => |err| return posix.unexpectedErrno(err),
+ }
+ }
+}
+
+fn dirAccessWasi(
+ userdata: ?*anyopaque,
+ dir: Io.Dir,
+ sub_path: []const u8,
+ options: Io.File.OpenFlags,
+) Io.File.AccessError!void {
+ if (builtin.link_libc) return dirAccessPosix(userdata, dir, sub_path, options);
+ const t: *Threaded = @ptrCast(@alignCast(userdata));
+ const wasi = std.os.wasi;
+ const flags: wasi.lookupflags_t = .{
+ .SYMLINK_FOLLOW = @intFromBool(options.follow_symlinks),
+ };
+ const stat = while (true) {
+ var stat: wasi.filestat_t = undefined;
+ try t.checkCancel();
+ switch (wasi.path_filestat_get(dir.handle, flags, sub_path.ptr, sub_path.len, &stat)) {
+ .SUCCESS => break statFromWasi(stat),
+ .INTR => continue,
+ .CANCELED => return error.Canceled,
+
+ .INVAL => |err| return errnoBug(err),
+ .BADF => |err| return errnoBug(err), // Always a race condition.
+ .NOMEM => return error.SystemResources,
+ .ACCES => return error.AccessDenied,
+ .FAULT => |err| return errnoBug(err),
+ .NAMETOOLONG => return error.NameTooLong,
+ .NOENT => return error.FileNotFound,
+ .NOTDIR => return error.FileNotFound,
+ .NOTCAPABLE => return error.AccessDenied,
+ .ILSEQ => return error.BadPathName,
+ else => |err| return posix.unexpectedErrno(err),
+ }
+ };
+
+ if (!options.mode.read and !options.mode.write and !options.mode.execute)
+ return;
+
+ var directory: wasi.fdstat_t = undefined;
+ if (wasi.fd_fdstat_get(dir.handle, &directory) != .SUCCESS)
+ return error.AccessDenied;
+
+ var rights: wasi.rights_t = .{};
+ if (options.mode.read) {
+ if (stat.filetype == .DIRECTORY) {
+ rights.FD_READDIR = true;
+ } else {
+ rights.FD_READ = true;
+ }
+ }
+ if (options.mode.write)
+ rights.FD_WRITE = true;
+
+ // No validation for execution.
+
+ // https://github.com/ziglang/zig/issues/18882
+ const rights_int: u64 = @bitCast(rights);
+ const inheriting_int: u64 = @bitCast(directory.fs_rights_inheriting);
+ if ((rights_int & inheriting_int) != rights_int)
+ return error.AccessDenied;
+}
+
fn dirCreateFilePosix(
userdata: ?*anyopaque,
dir: Io.Dir,
lib/std/fs.zig
@@ -1,14 +1,15 @@
//! File System.
+const builtin = @import("builtin");
+const native_os = builtin.os.tag;
const std = @import("std.zig");
-const builtin = @import("builtin");
+const Io = std.Io;
const root = @import("root");
const mem = std.mem;
const base64 = std.base64;
const crypto = std.crypto;
const Allocator = std.mem.Allocator;
const assert = std.debug.assert;
-const native_os = builtin.os.tag;
const posix = std.posix;
const windows = std.os.windows;
@@ -274,7 +275,7 @@ pub fn openFileAbsoluteW(absolute_path_w: []const u16, flags: File.OpenFlags) Fi
/// On Windows, `absolute_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
/// On WASI, `absolute_path` should be encoded as valid UTF-8.
/// On other platforms, `absolute_path` is an opaque sequence of bytes with no particular encoding.
-pub fn accessAbsolute(absolute_path: []const u8, flags: File.OpenFlags) Dir.AccessError!void {
+pub fn accessAbsolute(absolute_path: []const u8, flags: Io.Dir.AccessOptions) Dir.AccessError!void {
assert(path.isAbsolute(absolute_path));
try cwd().access(absolute_path, flags);
}
lib/std/Io.zig
@@ -663,6 +663,7 @@ pub const VTable = struct {
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,
+ dirAccess: *const fn (?*anyopaque, Dir, sub_path: []const u8, Dir.AccessOptions) Dir.AccessError!void,
dirCreateFile: *const fn (?*anyopaque, Dir, sub_path: []const u8, File.CreateFlags) File.OpenError!File,
dirOpenFile: *const fn (?*anyopaque, Dir, sub_path: []const u8, File.OpenFlags) File.OpenError!File,
fileStat: *const fn (?*anyopaque, File) File.StatError!File.Stat,
lib/std/posix.zig
@@ -4360,8 +4360,7 @@ pub const FStatAtError = FStatError || error{
NameTooLong,
FileNotFound,
SymLinkLoop,
- /// WASI-only; file paths must be valid UTF-8.
- InvalidUtf8,
+ BadPathName,
};
/// 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 {
_ = try windows.GetFileAttributesW(path_w.span().ptr);
return;
} else if (native_os == .wasi and !builtin.link_libc) {
- return faccessat(AT.FDCWD, path, mode, 0);
+ @compileError("wasi doesn't support absolute paths");
}
const path_c = try toPosixPath(path);
return accessZ(&path_c, mode);
@@ -4934,121 +4933,6 @@ pub fn accessZ(path: [*:0]const u8, mode: u32) AccessError!void {
}
}
-/// Check user's permissions for a file, based on an open directory handle.
-///
-/// * On Windows, asserts `path` is valid [WTF-8](https://wtf-8.codeberg.page/).
-/// * On WASI, invalid UTF-8 passed to `path` causes `error.InvalidUtf8`.
-/// * On other platforms, `path` is an opaque sequence of bytes with no particular encoding.
-///
-/// On Windows, `mode` is ignored. This is a POSIX API that is only partially supported by
-/// Windows. See `fs` for the cross-platform file system API.
-pub fn faccessat(dirfd: fd_t, path: []const u8, mode: u32, flags: u32) AccessError!void {
- if (native_os == .windows) {
- const path_w = try windows.sliceToPrefixedFileW(dirfd, path);
- return faccessatW(dirfd, path_w.span().ptr);
- } else if (native_os == .wasi and !builtin.link_libc) {
- const resolved: RelativePathWasi = .{ .dir_fd = dirfd, .relative_path = path };
-
- const st = try std.os.fstatat_wasi(dirfd, path, .{
- .SYMLINK_FOLLOW = (flags & AT.SYMLINK_NOFOLLOW) == 0,
- });
-
- if (mode != F_OK) {
- var directory: wasi.fdstat_t = undefined;
- if (wasi.fd_fdstat_get(resolved.dir_fd, &directory) != .SUCCESS) {
- return error.AccessDenied;
- }
-
- var rights: wasi.rights_t = .{};
- if (mode & R_OK != 0) {
- if (st.filetype == .DIRECTORY) {
- rights.FD_READDIR = true;
- } else {
- rights.FD_READ = true;
- }
- }
- if (mode & W_OK != 0) {
- rights.FD_WRITE = true;
- }
- // No validation for X_OK
-
- // https://github.com/ziglang/zig/issues/18882
- const rights_int: u64 = @bitCast(rights);
- const inheriting_int: u64 = @bitCast(directory.fs_rights_inheriting);
- if ((rights_int & inheriting_int) != rights_int) {
- return error.AccessDenied;
- }
- }
- return;
- }
- const path_c = try toPosixPath(path);
- return faccessatZ(dirfd, &path_c, mode, flags);
-}
-
-/// Same as `faccessat` except the path parameter is null-terminated.
-pub fn faccessatZ(dirfd: fd_t, path: [*:0]const u8, mode: u32, flags: u32) AccessError!void {
- if (native_os == .windows) {
- const path_w = try windows.cStrToPrefixedFileW(dirfd, path);
- return faccessatW(dirfd, path_w.span().ptr);
- } else if (native_os == .wasi and !builtin.link_libc) {
- return faccessat(dirfd, mem.sliceTo(path, 0), mode, flags);
- }
- switch (errno(system.faccessat(dirfd, path, mode, flags))) {
- .SUCCESS => return,
- .ACCES => return error.AccessDenied,
- .PERM => return error.PermissionDenied,
- .ROFS => return error.ReadOnlyFileSystem,
- .LOOP => return error.SymLinkLoop,
- .TXTBSY => return error.FileBusy,
- .NOTDIR => return error.FileNotFound,
- .NOENT => return error.FileNotFound,
- .NAMETOOLONG => return error.NameTooLong,
- .INVAL => unreachable,
- .FAULT => unreachable,
- .IO => return error.InputOutput,
- .NOMEM => return error.SystemResources,
- .ILSEQ => return error.BadPathName,
- else => |err| return unexpectedErrno(err),
- }
-}
-
-/// Same as `faccessat` except asserts the target is Windows and the path parameter
-/// is NtDll-prefixed, null-terminated, WTF-16 encoded.
-pub fn faccessatW(dirfd: fd_t, sub_path_w: [*:0]const u16) AccessError!void {
- if (sub_path_w[0] == '.' and sub_path_w[1] == 0) {
- return;
- }
- if (sub_path_w[0] == '.' and sub_path_w[1] == '.' and sub_path_w[2] == 0) {
- return;
- }
-
- const path_len_bytes = cast(u16, mem.sliceTo(sub_path_w, 0).len * 2) orelse return error.NameTooLong;
- var nt_name = windows.UNICODE_STRING{
- .Length = path_len_bytes,
- .MaximumLength = path_len_bytes,
- .Buffer = @constCast(sub_path_w),
- };
- var attr = windows.OBJECT_ATTRIBUTES{
- .Length = @sizeOf(windows.OBJECT_ATTRIBUTES),
- .RootDirectory = if (fs.path.isAbsoluteWindowsW(sub_path_w)) null else dirfd,
- .Attributes = 0, // Note we do not use OBJ_CASE_INSENSITIVE here.
- .ObjectName = &nt_name,
- .SecurityDescriptor = null,
- .SecurityQualityOfService = null,
- };
- var basic_info: windows.FILE_BASIC_INFORMATION = undefined;
- switch (windows.ntdll.NtQueryAttributesFile(&attr, &basic_info)) {
- .SUCCESS => return,
- .OBJECT_NAME_NOT_FOUND => return error.FileNotFound,
- .OBJECT_PATH_NOT_FOUND => return error.FileNotFound,
- .OBJECT_NAME_INVALID => unreachable,
- .INVALID_PARAMETER => unreachable,
- .ACCESS_DENIED => return error.AccessDenied,
- .OBJECT_PATH_SYNTAX_BAD => unreachable,
- else => |rc| return windows.unexpectedStatus(rc),
- }
-}
-
pub const PipeError = error{
SystemFdQuotaExceeded,
ProcessFdQuotaExceeded,