mirror of
https://github.com/ziglang/zig.git
synced 2025-12-06 06:13:07 +00:00
std.Io: add dirOpenDir and WASI impl
This commit is contained in:
parent
da6b959f64
commit
81e7e9fdbb
@ -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,
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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,
|
||||
/// Deprecated in favor of `Io.Dir.OpenOptions`.
|
||||
pub const OpenOptions = Io.Dir.OpenOptions;
|
||||
|
||||
/// `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,
|
||||
};
|
||||
|
||||
/// 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 => {
|
||||
|
||||
@ -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, .{
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user