diff options
| author | Andrew Kelley <andrew@ziglang.org> | 2025-10-17 00:52:33 -0700 |
|---|---|---|
| committer | Andrew Kelley <andrew@ziglang.org> | 2025-10-29 06:20:50 -0700 |
| commit | 81e7e9fdbbb822c413649479dd572ffcd244543a (patch) | |
| tree | bb0b3a9262df12afd28a46fd7326dffe0b2d452b /lib/std | |
| parent | da6b959f647f62aeaf96f380ad2828c16142f23f (diff) | |
| download | zig-81e7e9fdbbb822c413649479dd572ffcd244543a.tar.gz zig-81e7e9fdbbb822c413649479dd572ffcd244543a.zip | |
std.Io: add dirOpenDir and WASI impl
Diffstat (limited to 'lib/std')
| -rw-r--r-- | lib/std/Io.zig | 1 | ||||
| -rw-r--r-- | lib/std/Io/Dir.zig | 24 | ||||
| -rw-r--r-- | lib/std/Io/Threaded.zig | 87 | ||||
| -rw-r--r-- | lib/std/fs/Dir.zig | 89 | ||||
| -rw-r--r-- | lib/std/tar.zig | 10 |
5 files changed, 129 insertions, 82 deletions
diff --git a/lib/std/Io.zig b/lib/std/Io.zig index 2c0fd77dea..859568a15a 100644 --- a/lib/std/Io.zig +++ b/lib/std/Io.zig @@ -666,6 +666,7 @@ pub const VTable = struct { 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, + dirOpenDir: *const fn (?*anyopaque, Dir, sub_path: []const u8, Dir.OpenOptions) Dir.OpenError!Dir, fileStat: *const fn (?*anyopaque, File) File.StatError!File.Stat, fileClose: *const fn (?*anyopaque, File) void, fileWriteStreaming: *const fn (?*anyopaque, File, buffer: [][]const u8) File.WriteStreamingError!usize, diff --git a/lib/std/Io/Dir.zig b/lib/std/Io/Dir.zig index 634c1f9fff..45833dd7f7 100644 --- a/lib/std/Io/Dir.zig +++ b/lib/std/Io/Dir.zig @@ -69,6 +69,30 @@ pub const OpenError = error{ NetworkNotFound, } || PathNameError || Io.Cancelable || Io.UnexpectedError; +pub const OpenOptions = struct { + /// `true` means the opened directory can be used as the `Dir` parameter + /// for functions which operate based on an open directory handle. When `false`, + /// such operations are Illegal Behavior. + access_sub_paths: bool = true, + /// `true` means the opened directory can be scanned for the files and sub-directories + /// of the result. It means the `iterate` function can be called. + iterate: bool = false, + /// `false` means it won't dereference the symlinks. + follow_symlinks: bool = true, +}; + +/// Opens a directory at the given path. The directory is a system resource that remains +/// open until `close` is called on the result. +/// +/// The directory cannot be iterated unless the `iterate` option is set to `true`. +/// +/// 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. +pub fn openDir(dir: Dir, io: Io, sub_path: []const u8, options: OpenOptions) OpenError!Dir { + return io.vtable.dirOpenDir(io.userdata, dir, sub_path, options); +} + pub fn openFile(dir: Dir, io: Io, sub_path: []const u8, flags: File.OpenFlags) File.OpenError!File { return io.vtable.dirOpenFile(io.userdata, dir, sub_path, flags); } diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index 4f2582885b..7f138a25df 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -198,6 +198,11 @@ pub fn io(t: *Threaded) Io { .wasi => dirOpenFileWasi, else => dirOpenFilePosix, }, + .dirOpenDir = switch (builtin.os.tag) { + .windows => @panic("TODO"), + .wasi => dirOpenDirWasi, + else => dirOpenDirPosix, + }, .fileClose = fileClose, .fileWriteStreaming = fileWriteStreaming, .fileWritePositional = fileWritePositional, @@ -1429,7 +1434,6 @@ fn dirCreateFileWasi( .CANCELED => return error.Canceled, .FAULT => |err| return errnoBug(err), - // Provides INVAL with a linux host on a bad path name, but NOENT on Windows .INVAL => return error.BadPathName, .BADF => |err| return errnoBug(err), // File descriptor used after closed. .ACCES => return error.AccessDenied, @@ -1656,6 +1660,87 @@ fn dirOpenFileWasi( } } +fn dirOpenDirPosix( + userdata: ?*anyopaque, + dir: Io.Dir, + sub_path: []const u8, + options: Io.Dir.OpenOptions, +) Io.Dir.OpenError!Io.Dir { + const t: *Threaded = @ptrCast(@alignCast(userdata)); + + _ = t; + _ = dir; + _ = sub_path; + _ = options; + @panic("TODO"); +} + +fn dirOpenDirWasi( + userdata: ?*anyopaque, + dir: Io.Dir, + sub_path: []const u8, + options: Io.Dir.OpenOptions, +) Io.Dir.OpenError!Io.Dir { + if (builtin.link_libc) return dirOpenDirPosix(userdata, dir, sub_path, options); + const t: *Threaded = @ptrCast(@alignCast(userdata)); + const wasi = std.os.wasi; + + var base: std.os.wasi.rights_t = .{ + .FD_FILESTAT_GET = true, + .FD_FDSTAT_SET_FLAGS = true, + .FD_FILESTAT_SET_TIMES = true, + }; + if (options.access_sub_paths) { + base.FD_READDIR = true; + base.PATH_CREATE_DIRECTORY = true; + base.PATH_CREATE_FILE = true; + base.PATH_LINK_SOURCE = true; + base.PATH_LINK_TARGET = true; + base.PATH_OPEN = true; + base.PATH_READLINK = true; + base.PATH_RENAME_SOURCE = true; + base.PATH_RENAME_TARGET = true; + base.PATH_FILESTAT_GET = true; + base.PATH_FILESTAT_SET_SIZE = true; + base.PATH_FILESTAT_SET_TIMES = true; + base.PATH_SYMLINK = true; + base.PATH_REMOVE_DIRECTORY = true; + base.PATH_UNLINK_FILE = true; + } + + const lookup_flags: wasi.lookupflags_t = .{ .SYMLINK_FOLLOW = options.follow_symlinks }; + const oflags: wasi.oflags_t = .{ .DIRECTORY = true }; + const fdflags: wasi.fdflags_t = .{}; + var fd: posix.fd_t = undefined; + + while (true) { + try t.checkCancel(); + switch (wasi.path_open(dir.handle, lookup_flags, sub_path.ptr, sub_path.len, oflags, base, base, fdflags, &fd)) { + .SUCCESS => return .{ .handle = fd }, + .INTR => continue, + .CANCELED => return error.Canceled, + + .FAULT => |err| return errnoBug(err), + .INVAL => return error.BadPathName, + .BADF => |err| return errnoBug(err), // File descriptor used after closed. + .ACCES => return error.AccessDenied, + .LOOP => return error.SymLinkLoop, + .MFILE => return error.ProcessFdQuotaExceeded, + .NAMETOOLONG => return error.NameTooLong, + .NFILE => return error.SystemFdQuotaExceeded, + .NODEV => return error.NoDevice, + .NOENT => return error.FileNotFound, + .NOMEM => return error.SystemResources, + .NOTDIR => return error.NotDir, + .PERM => return error.PermissionDenied, + .BUSY => return error.DeviceBusy, + .NOTCAPABLE => return error.AccessDenied, + .ILSEQ => return error.BadPathName, + else => |err| return posix.unexpectedErrno(err), + } + } +} + fn fileClose(userdata: ?*anyopaque, file: Io.File) void { const t: *Threaded = @ptrCast(@alignCast(userdata)); _ = t; diff --git a/lib/std/fs/Dir.zig b/lib/std/fs/Dir.zig index 615c63efa6..5714d98e39 100644 --- a/lib/std/fs/Dir.zig +++ b/lib/std/fs/Dir.zig @@ -1235,28 +1235,10 @@ pub fn setAsCwd(self: Dir) !void { try posix.fchdir(self.fd); } -pub const OpenOptions = struct { - /// `true` means the opened directory can be used as the `Dir` parameter - /// for functions which operate based on an open directory handle. When `false`, - /// such operations are Illegal Behavior. - access_sub_paths: bool = true, - - /// `true` means the opened directory can be scanned for the files and sub-directories - /// of the result. It means the `iterate` function can be called. - iterate: bool = false, - - /// `true` means it won't dereference the symlinks. - no_follow: bool = false, -}; +/// Deprecated in favor of `Io.Dir.OpenOptions`. +pub const OpenOptions = Io.Dir.OpenOptions; -/// Opens a directory at the given path. The directory is a system resource that remains -/// open until `close` is called on the result. -/// The directory cannot be iterated unless the `iterate` option is set to `true`. -/// -/// 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. -/// Asserts that the path parameter has no null bytes. +/// Deprecated in favor of `Io.Dir.openDir`. pub fn openDir(self: Dir, sub_path: []const u8, args: OpenOptions) OpenError!Dir { switch (native_os) { .windows => { @@ -1264,54 +1246,9 @@ pub fn openDir(self: Dir, sub_path: []const u8, args: OpenOptions) OpenError!Dir return self.openDirW(sub_path_w.span().ptr, args); }, .wasi => if (!builtin.link_libc) { - var base: std.os.wasi.rights_t = .{ - .FD_FILESTAT_GET = true, - .FD_FDSTAT_SET_FLAGS = true, - .FD_FILESTAT_SET_TIMES = true, - }; - if (args.access_sub_paths) { - base.FD_READDIR = true; - base.PATH_CREATE_DIRECTORY = true; - base.PATH_CREATE_FILE = true; - base.PATH_LINK_SOURCE = true; - base.PATH_LINK_TARGET = true; - base.PATH_OPEN = true; - base.PATH_READLINK = true; - base.PATH_RENAME_SOURCE = true; - base.PATH_RENAME_TARGET = true; - base.PATH_FILESTAT_GET = true; - base.PATH_FILESTAT_SET_SIZE = true; - base.PATH_FILESTAT_SET_TIMES = true; - base.PATH_SYMLINK = true; - base.PATH_REMOVE_DIRECTORY = true; - base.PATH_UNLINK_FILE = true; - } - - const result = posix.openatWasi( - self.fd, - sub_path, - .{ .SYMLINK_FOLLOW = !args.no_follow }, - .{ .DIRECTORY = true }, - .{}, - base, - base, - ); - const fd = result catch |err| switch (err) { - error.FileTooBig => unreachable, // can't happen for directories - error.IsDir => unreachable, // we're setting DIRECTORY - error.NoSpaceLeft => unreachable, // not setting CREAT - error.PathAlreadyExists => unreachable, // not setting CREAT - 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, - error.PipeBusy => unreachable, - error.ProcessNotFound => unreachable, - error.AntivirusInterference => unreachable, - - else => |e| return e, - }; - return .{ .fd = fd }; + var threaded: Io.Threaded = .init_single_threaded; + const io = threaded.io(); + return .adaptFromNewApi(try Io.Dir.openDir(.{ .handle = self.fd }, io, sub_path, args)); }, else => {}, } @@ -1358,12 +1295,12 @@ pub fn openDirZ(self: Dir, sub_path_c: [*:0]const u8, args: OpenOptions) OpenErr var symlink_flags: posix.O = switch (native_os) { .wasi => .{ .read = true, - .NOFOLLOW = args.no_follow, + .NOFOLLOW = !args.follow_symlinks, .DIRECTORY = true, }, else => .{ .ACCMODE = .RDONLY, - .NOFOLLOW = args.no_follow, + .NOFOLLOW = !args.follow_symlinks, .DIRECTORY = true, .CLOEXEC = true, }, @@ -1384,7 +1321,7 @@ pub fn openDirW(self: Dir, sub_path_w: [*:0]const u16, args: OpenOptions) OpenEr w.SYNCHRONIZE | w.FILE_TRAVERSE; const flags: u32 = if (args.iterate) base_flags | w.FILE_LIST_DIRECTORY else base_flags; const dir = self.makeOpenDirAccessMaskW(sub_path_w, flags, .{ - .no_follow = args.no_follow, + .no_follow = !args.follow_symlinks, .create_disposition = w.FILE_OPEN, }) catch |err| switch (err) { error.ReadOnlyFileSystem => unreachable, @@ -1923,7 +1860,7 @@ pub fn deleteTree(self: Dir, sub_path: []const u8) DeleteTreeError!void { if (treat_as_dir) { if (stack.unusedCapacitySlice().len >= 1) { var iterable_dir = top.iter.dir.openDir(entry.name, .{ - .no_follow = true, + .follow_symlinks = false, .iterate = true, }) catch |err| switch (err) { error.NotDir => { @@ -2019,7 +1956,7 @@ pub fn deleteTree(self: Dir, sub_path: []const u8) DeleteTreeError!void { handle_entry: while (true) { if (treat_as_dir) { break :iterable_dir parent_dir.openDir(name, .{ - .no_follow = true, + .follow_symlinks = false, .iterate = true, }) catch |err| switch (err) { error.NotDir => { @@ -2125,7 +2062,7 @@ fn deleteTreeMinStackSizeWithKindHint(self: Dir, sub_path: []const u8, kind_hint handle_entry: while (true) { if (treat_as_dir) { const new_dir = dir.openDir(entry.name, .{ - .no_follow = true, + .follow_symlinks = false, .iterate = true, }) catch |err| switch (err) { error.NotDir => { @@ -2224,7 +2161,7 @@ fn deleteTreeOpenInitialSubpath(self: Dir, sub_path: []const u8, kind_hint: File handle_entry: while (true) { if (treat_as_dir) { break :iterable_dir self.openDir(sub_path, .{ - .no_follow = true, + .follow_symlinks = false, .iterate = true, }) catch |err| switch (err) { error.NotDir => { diff --git a/lib/std/tar.zig b/lib/std/tar.zig index e397677cf3..12f9c837a2 100644 --- a/lib/std/tar.zig +++ b/lib/std/tar.zig @@ -977,7 +977,7 @@ test pipeToFileSystem { const data = @embedFile("tar/testdata/example.tar"); var reader: std.Io.Reader = .fixed(data); - var tmp = testing.tmpDir(.{ .no_follow = true }); + var tmp = testing.tmpDir(.{ .follow_symlinks = false }); defer tmp.cleanup(); const dir = tmp.dir; @@ -1010,7 +1010,7 @@ test "pipeToFileSystem root_dir" { // with strip_components = 1 { - var tmp = testing.tmpDir(.{ .no_follow = true }); + var tmp = testing.tmpDir(.{ .follow_symlinks = false }); defer tmp.cleanup(); var diagnostics: Diagnostics = .{ .allocator = testing.allocator }; defer diagnostics.deinit(); @@ -1032,7 +1032,7 @@ test "pipeToFileSystem root_dir" { // with strip_components = 0 { reader = .fixed(data); - var tmp = testing.tmpDir(.{ .no_follow = true }); + var tmp = testing.tmpDir(.{ .follow_symlinks = false }); defer tmp.cleanup(); var diagnostics: Diagnostics = .{ .allocator = testing.allocator }; defer diagnostics.deinit(); @@ -1084,7 +1084,7 @@ test "pipeToFileSystem strip_components" { const data = @embedFile("tar/testdata/example.tar"); var reader: std.Io.Reader = .fixed(data); - var tmp = testing.tmpDir(.{ .no_follow = true }); + var tmp = testing.tmpDir(.{ .follow_symlinks = false }); defer tmp.cleanup(); var diagnostics: Diagnostics = .{ .allocator = testing.allocator }; defer diagnostics.deinit(); @@ -1145,7 +1145,7 @@ test "executable bit" { for ([_]PipeOptions.ModeMode{ .ignore, .executable_bit_only }) |opt| { var reader: std.Io.Reader = .fixed(data); - var tmp = testing.tmpDir(.{ .no_follow = true }); + var tmp = testing.tmpDir(.{ .follow_symlinks = false }); //defer tmp.cleanup(); pipeToFileSystem(tmp.dir, &reader, .{ |
