authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-10-16 22:50:30-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-10-29 06:20:50-07:00
logd4215ffaa04b976400bd597cca0cca8182068bf6
treeb0047c72d94c145fc0abc2eb77c30a10247acfa8
parent143127529b109971c931cce18cc7a328f6031df3

std.Io.Threaded: implement dirCreateFile for WASI


3 files changed, 70 insertions(+), 134 deletions(-)

lib/std/Io/Threaded.zig+69-1
...@@ -190,7 +190,7 @@ pub fn io(t: *Threaded) Io {...@@ -190,7 +190,7 @@ pub fn io(t: *Threaded) Io {
190 },190 },
191 .dirCreateFile = switch (builtin.os.tag) {191 .dirCreateFile = switch (builtin.os.tag) {
192 .windows => @panic("TODO"),192 .windows => @panic("TODO"),
193 .wasi => @panic("TODO"),193 .wasi => dirCreateFileWasi,
194 else => dirCreateFilePosix,194 else => dirCreateFilePosix,
195 },195 },
196 .dirOpenFile = dirOpenFile,196 .dirOpenFile = dirOpenFile,
...@@ -1378,6 +1378,74 @@ fn dirCreateFilePosix(...@@ -1378,6 +1378,74 @@ fn dirCreateFilePosix(
1378 return .{ .handle = fd };1378 return .{ .handle = fd };
1379}1379}
13801380
1381fn dirCreateFileWasi(
1382 userdata: ?*anyopaque,
1383 dir: Io.Dir,
1384 sub_path: []const u8,
1385 flags: Io.File.CreateFlags,
1386) Io.File.OpenError!Io.File {
1387 const t: *Threaded = @ptrCast(@alignCast(userdata));
1388 const wasi = std.os.wasi;
1389 const lookup_flags: wasi.lookupflags_t = .{};
1390 const oflags: wasi.oflags_t = .{
1391 .CREAT = true,
1392 .TRUNC = flags.truncate,
1393 .EXCL = flags.exclusive,
1394 };
1395 const fdflags: wasi.fdflags_t = .{};
1396 const base: wasi.rights_t = .{
1397 .FD_READ = flags.read,
1398 .FD_WRITE = true,
1399 .FD_DATASYNC = true,
1400 .FD_SEEK = true,
1401 .FD_TELL = true,
1402 .FD_FDSTAT_SET_FLAGS = true,
1403 .FD_SYNC = true,
1404 .FD_ALLOCATE = true,
1405 .FD_ADVISE = true,
1406 .FD_FILESTAT_SET_TIMES = true,
1407 .FD_FILESTAT_SET_SIZE = true,
1408 .FD_FILESTAT_GET = true,
1409 // POLL_FD_READWRITE only grants extra rights if the corresponding FD_READ and/or
1410 // FD_WRITE is also set.
1411 .POLL_FD_READWRITE = true,
1412 };
1413 const inheriting: wasi.rights_t = .{};
1414 var fd: posix.fd_t = undefined;
1415 while (true) {
1416 try t.checkCancel();
1417 switch (wasi.path_open(dir.handle, lookup_flags, sub_path.ptr, sub_path.len, oflags, base, inheriting, fdflags, &fd)) {
1418 .SUCCESS => return .{ .handle = fd },
1419 .INTR => continue,
1420 .CANCELED => return error.Canceled,
1421
1422 .FAULT => |err| return errnoBug(err),
1423 // Provides INVAL with a linux host on a bad path name, but NOENT on Windows
1424 .INVAL => return error.BadPathName,
1425 .BADF => |err| return errnoBug(err),
1426 .ACCES => return error.AccessDenied,
1427 .FBIG => return error.FileTooBig,
1428 .OVERFLOW => return error.FileTooBig,
1429 .ISDIR => return error.IsDir,
1430 .LOOP => return error.SymLinkLoop,
1431 .MFILE => return error.ProcessFdQuotaExceeded,
1432 .NAMETOOLONG => return error.NameTooLong,
1433 .NFILE => return error.SystemFdQuotaExceeded,
1434 .NODEV => return error.NoDevice,
1435 .NOENT => return error.FileNotFound,
1436 .NOMEM => return error.SystemResources,
1437 .NOSPC => return error.NoSpaceLeft,
1438 .NOTDIR => return error.NotDir,
1439 .PERM => return error.PermissionDenied,
1440 .EXIST => return error.PathAlreadyExists,
1441 .BUSY => return error.DeviceBusy,
1442 .NOTCAPABLE => return error.AccessDenied,
1443 .ILSEQ => return error.BadPathName,
1444 else => |err| return posix.unexpectedErrno(err),
1445 }
1446 }
1447}
1448
1381fn dirOpenFile(1449fn dirOpenFile(
1382 userdata: ?*anyopaque,1450 userdata: ?*anyopaque,
1383 dir: Io.Dir,1451 dir: Io.Dir,
lib/std/fs/Dir.zig-25
...@@ -938,31 +938,6 @@ pub fn createFile(self: Dir, sub_path: []const u8, flags: File.CreateFlags) File...@@ -938,31 +938,6 @@ pub fn createFile(self: Dir, sub_path: []const u8, flags: File.CreateFlags) File
938 const path_w = try windows.sliceToPrefixedFileW(self.fd, sub_path);938 const path_w = try windows.sliceToPrefixedFileW(self.fd, sub_path);
939 return self.createFileW(path_w.span(), flags);939 return self.createFileW(path_w.span(), flags);
940 }940 }
941 if (native_os == .wasi) {
942 return .{
943 .handle = try posix.openatWasi(self.fd, sub_path, .{}, .{
944 .CREAT = true,
945 .TRUNC = flags.truncate,
946 .EXCL = flags.exclusive,
947 }, .{}, .{
948 .FD_READ = flags.read,
949 .FD_WRITE = true,
950 .FD_DATASYNC = true,
951 .FD_SEEK = true,
952 .FD_TELL = true,
953 .FD_FDSTAT_SET_FLAGS = true,
954 .FD_SYNC = true,
955 .FD_ALLOCATE = true,
956 .FD_ADVISE = true,
957 .FD_FILESTAT_SET_TIMES = true,
958 .FD_FILESTAT_SET_SIZE = true,
959 .FD_FILESTAT_GET = true,
960 // POLL_FD_READWRITE only grants extra rights if the corresponding FD_READ and/or
961 // FD_WRITE is also set.
962 .POLL_FD_READWRITE = true,
963 }, .{}),
964 };
965 }
966 var threaded: Io.Threaded = .init_single_threaded;941 var threaded: Io.Threaded = .init_single_threaded;
967 const io = threaded.io();942 const io = threaded.io();
968 const new_file = try Io.Dir.createFile(self.adaptToNewApi(), io, sub_path, flags);943 const new_file = try Io.Dir.createFile(self.adaptToNewApi(), io, sub_path, flags);
lib/std/posix.zig+1-108
...@@ -1610,119 +1610,12 @@ pub fn openat(dir_fd: fd_t, file_path: []const u8, flags: O, mode: mode_t) OpenE...@@ -1610,119 +1610,12 @@ pub fn openat(dir_fd: fd_t, file_path: []const u8, flags: O, mode: mode_t) OpenE
1610 if (native_os == .windows) {1610 if (native_os == .windows) {
1611 @compileError("Windows does not support POSIX; use Windows-specific API or cross-platform std.fs API");1611 @compileError("Windows does not support POSIX; use Windows-specific API or cross-platform std.fs API");
1612 } else if (native_os == .wasi and !builtin.link_libc) {1612 } else if (native_os == .wasi and !builtin.link_libc) {
1613 // `mode` is ignored on WASI, which does not support unix-style file permissions1613 @compileError("use std.Io instead");
1614 const opts = try openOptionsFromFlagsWasi(flags);
1615 const fd = try openatWasi(
1616 dir_fd,
1617 file_path,
1618 opts.lookup_flags,
1619 opts.oflags,
1620 opts.fs_flags,
1621 opts.fs_rights_base,
1622 opts.fs_rights_inheriting,
1623 );
1624 errdefer close(fd);
1625
1626 if (flags.write) {
1627 const info = try std.os.fstat_wasi(fd);
1628 if (info.filetype == .DIRECTORY)
1629 return error.IsDir;
1630 }
1631
1632 return fd;
1633 }1614 }
1634 const file_path_c = try toPosixPath(file_path);1615 const file_path_c = try toPosixPath(file_path);
1635 return openatZ(dir_fd, &file_path_c, flags, mode);1616 return openatZ(dir_fd, &file_path_c, flags, mode);
1636}1617}
16371618
1638/// Open and possibly create a file in WASI.
1639pub fn openatWasi(
1640 dir_fd: fd_t,
1641 file_path: []const u8,
1642 lookup_flags: wasi.lookupflags_t,
1643 oflags: wasi.oflags_t,
1644 fdflags: wasi.fdflags_t,
1645 base: wasi.rights_t,
1646 inheriting: wasi.rights_t,
1647) OpenError!fd_t {
1648 while (true) {
1649 var fd: fd_t = undefined;
1650 switch (wasi.path_open(dir_fd, lookup_flags, file_path.ptr, file_path.len, oflags, base, inheriting, fdflags, &fd)) {
1651 .SUCCESS => return fd,
1652 .INTR => continue,
1653
1654 .FAULT => unreachable,
1655 // Provides INVAL with a linux host on a bad path name, but NOENT on Windows
1656 .INVAL => return error.BadPathName,
1657 .BADF => unreachable,
1658 .ACCES => return error.AccessDenied,
1659 .FBIG => return error.FileTooBig,
1660 .OVERFLOW => return error.FileTooBig,
1661 .ISDIR => return error.IsDir,
1662 .LOOP => return error.SymLinkLoop,
1663 .MFILE => return error.ProcessFdQuotaExceeded,
1664 .NAMETOOLONG => return error.NameTooLong,
1665 .NFILE => return error.SystemFdQuotaExceeded,
1666 .NODEV => return error.NoDevice,
1667 .NOENT => return error.FileNotFound,
1668 .NOMEM => return error.SystemResources,
1669 .NOSPC => return error.NoSpaceLeft,
1670 .NOTDIR => return error.NotDir,
1671 .PERM => return error.PermissionDenied,
1672 .EXIST => return error.PathAlreadyExists,
1673 .BUSY => return error.DeviceBusy,
1674 .NOTCAPABLE => return error.AccessDenied,
1675 .ILSEQ => return error.BadPathName,
1676 else => |err| return unexpectedErrno(err),
1677 }
1678 }
1679}
1680
1681/// A struct to contain all lookup/rights flags accepted by `wasi.path_open`
1682const WasiOpenOptions = struct {
1683 oflags: wasi.oflags_t,
1684 lookup_flags: wasi.lookupflags_t,
1685 fs_rights_base: wasi.rights_t,
1686 fs_rights_inheriting: wasi.rights_t,
1687 fs_flags: wasi.fdflags_t,
1688};
1689
1690/// Compute rights + flags corresponding to the provided POSIX access mode.
1691fn openOptionsFromFlagsWasi(oflag: O) OpenError!WasiOpenOptions {
1692 const w = std.os.wasi;
1693
1694 // Next, calculate the read/write rights to request, depending on the
1695 // provided POSIX access mode
1696 var rights: w.rights_t = .{};
1697 if (oflag.read) {
1698 rights.FD_READ = true;
1699 rights.FD_READDIR = true;
1700 }
1701 if (oflag.write) {
1702 rights.FD_DATASYNC = true;
1703 rights.FD_WRITE = true;
1704 rights.FD_ALLOCATE = true;
1705 rights.FD_FILESTAT_SET_SIZE = true;
1706 }
1707
1708 // https://github.com/ziglang/zig/issues/18882
1709 const flag_bits: u32 = @bitCast(oflag);
1710 const oflags_int: u16 = @as(u12, @truncate(flag_bits >> 12));
1711 const fs_flags_int: u16 = @as(u12, @truncate(flag_bits));
1712
1713 return .{
1714 // https://github.com/ziglang/zig/issues/18882
1715 .oflags = @bitCast(oflags_int),
1716 .lookup_flags = .{
1717 .SYMLINK_FOLLOW = !oflag.NOFOLLOW,
1718 },
1719 .fs_rights_base = rights,
1720 .fs_rights_inheriting = rights,
1721 // https://github.com/ziglang/zig/issues/18882
1722 .fs_flags = @bitCast(fs_flags_int),
1723 };
1724}
1725
1726/// Open and possibly create a file. Keeps trying if it gets interrupted.1619/// Open and possibly create a file. Keeps trying if it gets interrupted.
1727/// `file_path` is relative to the open directory handle `dir_fd`.1620/// `file_path` is relative to the open directory handle `dir_fd`.
1728/// On Windows, `file_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).1621/// On Windows, `file_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).