| ... | ... | @@ -21,9 +21,13 @@ const assert = std.debug.assert; |
| 21 | 21 | const math = std.math; |
| 22 | 22 | const mem = std.mem; |
| 23 | 23 | const elf = std.elf; |
| 24 | const fs = std.fs; |
| 24 | 25 | const dl = @import("dynamic_library.zig"); |
| 25 | 26 | const MAX_PATH_BYTES = std.fs.MAX_PATH_BYTES; |
| 26 | 27 | const is_windows = builtin.os.tag == .windows; |
| 28 | const Allocator = std.mem.Allocator; |
| 29 | const Preopen = std.fs.wasi.Preopen; |
| 30 | const PreopenList = std.fs.wasi.PreopenList; |
| 27 | 31 | |
| 28 | 32 | pub const darwin = std.c; |
| 29 | 33 | pub const dragonfly = std.c; |
| ... | ... | @@ -93,7 +97,12 @@ pub const MAX_ADDR_LEN = system.MAX_ADDR_LEN; |
| 93 | 97 | pub const MMAP2_UNIT = system.MMAP2_UNIT; |
| 94 | 98 | pub const MSG = system.MSG; |
| 95 | 99 | pub const NAME_MAX = system.NAME_MAX; |
| 96 | | pub const O = system.O; |
| 100 | pub const O = switch (builtin.os.tag) { |
| 101 | // We want to expose the POSIX-like OFLAGS, so we use std.c.wasi.O instead |
| 102 | // of std.os.wasi.O, which is for non-POSIX-like `wasi.path_open`, etc. |
| 103 | .wasi => std.c.O, |
| 104 | else => system.O, |
| 105 | }; |
| 97 | 106 | pub const PATH_MAX = system.PATH_MAX; |
| 98 | 107 | pub const POLL = system.POLL; |
| 99 | 108 | pub const POSIX_FADV = system.POSIX_FADV; |
| ... | ... | @@ -210,6 +219,13 @@ pub const LOG = struct { |
| 210 | 219 | pub const DEBUG = 7; |
| 211 | 220 | }; |
| 212 | 221 | |
| 222 | pub const RelativePath = struct { |
| 223 | /// Handle to directory |
| 224 | dir_fd: fd_t, |
| 225 | /// Path to resource within `dir_fd`. |
| 226 | relative_path: []const u8, |
| 227 | }; |
| 228 | |
| 213 | 229 | pub const socket_t = if (builtin.os.tag == .windows) windows.ws2_32.SOCKET else fd_t; |
| 214 | 230 | |
| 215 | 231 | /// See also `getenv`. Populated by startup code before main(). |
| ... | ... | @@ -1239,6 +1255,9 @@ pub fn pwritev(fd: fd_t, iov: []const iovec_const, offset: u64) PWriteError!usiz |
| 1239 | 1255 | } |
| 1240 | 1256 | |
| 1241 | 1257 | pub const OpenError = error{ |
| 1258 | /// In WASI, this error may occur when the provided file handle is invalid. |
| 1259 | InvalidHandle, |
| 1260 | |
| 1242 | 1261 | /// In WASI, this error may occur when the file descriptor does |
| 1243 | 1262 | /// not hold the required rights to open a new resource relative to it. |
| 1244 | 1263 | AccessDenied, |
| ... | ... | @@ -1300,6 +1319,8 @@ pub fn open(file_path: []const u8, flags: u32, perm: mode_t) OpenError!fd_t { |
| 1300 | 1319 | if (builtin.os.tag == .windows) { |
| 1301 | 1320 | const file_path_w = try windows.sliceToPrefixedFileW(file_path); |
| 1302 | 1321 | return openW(file_path_w.span(), flags, perm); |
| 1322 | } else if (builtin.os.tag == .wasi and !builtin.link_libc) { |
| 1323 | return openat(wasi.AT.FDCWD, file_path, flags, perm); |
| 1303 | 1324 | } |
| 1304 | 1325 | const file_path_c = try toPosixPath(file_path); |
| 1305 | 1326 | return openZ(&file_path_c, flags, perm); |
| ... | ... | @@ -1311,6 +1332,8 @@ pub fn openZ(file_path: [*:0]const u8, flags: u32, perm: mode_t) OpenError!fd_t |
| 1311 | 1332 | if (builtin.os.tag == .windows) { |
| 1312 | 1333 | const file_path_w = try windows.cStrToPrefixedFileW(file_path); |
| 1313 | 1334 | return openW(file_path_w.span(), flags, perm); |
| 1335 | } else if (builtin.os.tag == .wasi and !builtin.link_libc) { |
| 1336 | return open(mem.sliceTo(file_path, 0), flags, perm); |
| 1314 | 1337 | } |
| 1315 | 1338 | |
| 1316 | 1339 | const open_sym = if (builtin.os.tag == .linux and builtin.link_libc) |
| ... | ... | @@ -1347,7 +1370,7 @@ pub fn openZ(file_path: [*:0]const u8, flags: u32, perm: mode_t) OpenError!fd_t |
| 1347 | 1370 | } |
| 1348 | 1371 | } |
| 1349 | 1372 | |
| 1350 | | fn openOptionsFromFlags(flags: u32) windows.OpenFileOptions { |
| 1373 | fn openOptionsFromFlagsWindows(flags: u32) windows.OpenFileOptions { |
| 1351 | 1374 | const w = windows; |
| 1352 | 1375 | |
| 1353 | 1376 | var access_mask: w.ULONG = w.READ_CONTROL | w.FILE_WRITE_ATTRIBUTES | w.SYNCHRONIZE; |
| ... | ... | @@ -1387,7 +1410,7 @@ fn openOptionsFromFlags(flags: u32) windows.OpenFileOptions { |
| 1387 | 1410 | /// or makes use of perm argument. |
| 1388 | 1411 | pub fn openW(file_path_w: []const u16, flags: u32, perm: mode_t) OpenError!fd_t { |
| 1389 | 1412 | _ = perm; |
| 1390 | | var options = openOptionsFromFlags(flags); |
| 1413 | var options = openOptionsFromFlagsWindows(flags); |
| 1391 | 1414 | options.dir = std.fs.cwd().fd; |
| 1392 | 1415 | return windows.OpenFile(file_path_w, options) catch |err| switch (err) { |
| 1393 | 1416 | error.WouldBlock => unreachable, |
| ... | ... | @@ -1396,21 +1419,187 @@ pub fn openW(file_path_w: []const u16, flags: u32, perm: mode_t) OpenError!fd_t |
| 1396 | 1419 | }; |
| 1397 | 1420 | } |
| 1398 | 1421 | |
| 1422 | var wasi_cwd = if (builtin.os.tag == .wasi and !builtin.link_libc) struct { |
| 1423 | // List of available Preopens |
| 1424 | preopens: ?PreopenList = null, |
| 1425 | // Memory buffer for storing the relative portion of the CWD |
| 1426 | path_buffer: [MAX_PATH_BYTES]u8 = undefined, |
| 1427 | // Current Working Directory, stored as an fd_t and a relative path |
| 1428 | cwd: ?RelativePath = null, |
| 1429 | // Preopen associated with `cwd`, if any |
| 1430 | cwd_preopen: ?Preopen = null, |
| 1431 | }{} else undefined; |
| 1432 | |
| 1433 | /// Initialize the available Preopen list on WASI and set the CWD to `cwd_init`. |
| 1434 | /// |
| 1435 | /// This must be called before using any relative or absolute paths with `std.os` |
| 1436 | /// functions, if you are on WASI without linking libc. |
| 1437 | /// |
| 1438 | /// `alloc` must not be a temporary or leak-detecting allocator, since `std.os` |
| 1439 | /// retains ownership of allocations internally and may never call free(). |
| 1440 | pub fn initPreopensWasi(alloc: Allocator, cwd_init: ?[]const u8) !void { |
| 1441 | if (builtin.os.tag == .wasi) { |
| 1442 | if (!builtin.link_libc) { |
| 1443 | if (wasi_cwd.preopens == null) { |
| 1444 | var preopen_list = PreopenList.init(alloc); |
| 1445 | try preopen_list.populate(); |
| 1446 | wasi_cwd.preopens = preopen_list; |
| 1447 | } |
| 1448 | if (cwd_init) |cwd| { |
| 1449 | const preopen = wasi_cwd.preopens.?.findContaining(.{ .Dir = cwd }); |
| 1450 | if (preopen) |po| { |
| 1451 | wasi_cwd.cwd_preopen = po.base; |
| 1452 | wasi_cwd.cwd = RelativePath{ |
| 1453 | .dir_fd = po.base.fd, |
| 1454 | .relative_path = po.relative_path, |
| 1455 | }; |
| 1456 | } else { |
| 1457 | // No matching preopen found |
| 1458 | return error.FileNotFound; |
| 1459 | } |
| 1460 | } |
| 1461 | } else { |
| 1462 | if (cwd_init) |cwd| try chdir(cwd); |
| 1463 | } |
| 1464 | } |
| 1465 | } |
| 1466 | |
| 1467 | /// Resolve a relative or absolute path to an handle (`fd_t`) and a relative subpath. |
| 1468 | /// |
| 1469 | /// For absolute paths, this automatically searches among available Preopens to find |
| 1470 | /// a match. For relative paths, it uses the "emulated" CWD. |
| 1471 | pub fn resolvePathWasi(path: []const u8, out_buffer: *[MAX_PATH_BYTES]u8) !RelativePath { |
| 1472 | // Note: Due to WASI's "sandboxed" file handles, operations with this RelativePath |
| 1473 | // will fail if the relative path navigates outside of `dir_fd` using ".." |
| 1474 | return resolvePathAndGetWasiPreopen(path, null, out_buffer); |
| 1475 | } |
| 1476 | |
| 1477 | fn resolvePathAndGetWasiPreopen(path: []const u8, preopen: ?*?Preopen, out_buffer: *[MAX_PATH_BYTES]u8) !RelativePath { |
| 1478 | var allocator = std.heap.FixedBufferAllocator.init(out_buffer); |
| 1479 | var alloc = allocator.allocator(); |
| 1480 | |
| 1481 | if (fs.path.isAbsolute(path) or wasi_cwd.cwd == null) { |
| 1482 | if (wasi_cwd.preopens == null) @panic("On WASI, `initPreopensWasi` must be called to initialize preopens " ++ |
| 1483 | "before using any CWD-relative or absolute paths.\n"); |
| 1484 | |
| 1485 | // If the path is absolute, we need to lookup a containing Preopen |
| 1486 | const abs_path = std.fs.path.resolve(alloc, &.{ "/", path }) catch return error.NameTooLong; |
| 1487 | const preopen_uri = wasi_cwd.preopens.?.findContaining(.{ .Dir = abs_path }); |
| 1488 | |
| 1489 | if (preopen_uri) |po| { |
| 1490 | if (preopen) |p| p.* = po.base; |
| 1491 | return RelativePath{ |
| 1492 | .dir_fd = po.base.fd, |
| 1493 | .relative_path = po.relative_path, |
| 1494 | }; |
| 1495 | } else { |
| 1496 | // No matching preopen found |
| 1497 | return error.AccessDenied; |
| 1498 | } |
| 1499 | } else { |
| 1500 | const cwd = wasi_cwd.cwd.?; |
| 1501 | |
| 1502 | // If the path is empty or "." or "./", return CWD |
| 1503 | if (std.mem.eql(u8, path, ".") or std.mem.eql(u8, path, "./")) { |
| 1504 | return cwd; |
| 1505 | } |
| 1506 | |
| 1507 | // First resolve a combined path, where the "/" corresponds to `cwd.dir_fd` |
| 1508 | // not the true filesystem root |
| 1509 | const paths = &.{ "/", cwd.relative_path, path }; |
| 1510 | const resolved_path = std.fs.path.resolve(alloc, paths) catch return error.NameTooLong; |
| 1511 | |
| 1512 | // Strip off the fake root to get the relative path w.r.t. `cwd.dir_fd` |
| 1513 | const resolved_relative_path = resolved_path[1..]; |
| 1514 | |
| 1515 | if (preopen) |p| p.* = wasi_cwd.cwd_preopen; |
| 1516 | return RelativePath{ |
| 1517 | .dir_fd = cwd.dir_fd, |
| 1518 | .relative_path = resolved_relative_path, |
| 1519 | }; |
| 1520 | } |
| 1521 | } |
| 1522 | |
| 1399 | 1523 | /// Open and possibly create a file. Keeps trying if it gets interrupted. |
| 1400 | 1524 | /// `file_path` is relative to the open directory handle `dir_fd`. |
| 1401 | 1525 | /// See also `openatZ`. |
| 1402 | 1526 | pub fn openat(dir_fd: fd_t, file_path: []const u8, flags: u32, mode: mode_t) OpenError!fd_t { |
| 1403 | | if (builtin.os.tag == .wasi and !builtin.link_libc) { |
| 1404 | | @compileError("use openatWasi instead"); |
| 1405 | | } |
| 1406 | 1527 | if (builtin.os.tag == .windows) { |
| 1407 | 1528 | const file_path_w = try windows.sliceToPrefixedFileW(file_path); |
| 1408 | 1529 | return openatW(dir_fd, file_path_w.span(), flags, mode); |
| 1530 | } else if (builtin.os.tag == .wasi and !builtin.link_libc) { |
| 1531 | // `mode` is ignored on WASI, which does not support unix-style file permissions |
| 1532 | const fd = if (dir_fd == wasi.AT.FDCWD or fs.path.isAbsolute(file_path)) blk: { |
| 1533 | // Resolve absolute or CWD-relative paths to a path within a Preopen |
| 1534 | var path_buf: [MAX_PATH_BYTES]u8 = undefined; |
| 1535 | const path = try resolvePathWasi(file_path, &path_buf); |
| 1536 | |
| 1537 | const opts = try openOptionsFromFlagsWasi(path.dir_fd, flags); |
| 1538 | break :blk try openatWasi(path.dir_fd, path.relative_path, opts.lookup_flags, opts.oflags, opts.fs_flags, opts.fs_rights_base, opts.fs_rights_inheriting); |
| 1539 | } else blk: { |
| 1540 | const opts = try openOptionsFromFlagsWasi(dir_fd, flags); |
| 1541 | break :blk try openatWasi(dir_fd, file_path, opts.lookup_flags, opts.oflags, opts.fs_flags, opts.fs_rights_base, opts.fs_rights_inheriting); |
| 1542 | }; |
| 1543 | errdefer close(fd); |
| 1544 | |
| 1545 | const info = try fstat(fd); |
| 1546 | if (flags & O.WRONLY != 0 and info.filetype == .DIRECTORY) |
| 1547 | return error.IsDir; |
| 1548 | |
| 1549 | return fd; |
| 1409 | 1550 | } |
| 1410 | 1551 | const file_path_c = try toPosixPath(file_path); |
| 1411 | 1552 | return openatZ(dir_fd, &file_path_c, flags, mode); |
| 1412 | 1553 | } |
| 1413 | 1554 | |
| 1555 | const WasiOpenOptions = struct { |
| 1556 | oflags: wasi.oflags_t, |
| 1557 | lookup_flags: wasi.lookupflags_t, |
| 1558 | fs_rights_base: wasi.rights_t, |
| 1559 | fs_rights_inheriting: wasi.rights_t, |
| 1560 | fs_flags: wasi.fdflags_t, |
| 1561 | }; |
| 1562 | |
| 1563 | /// Compute rights + flags corresponding to the provided POSIX access mode. |
| 1564 | fn openOptionsFromFlagsWasi(fd: fd_t, oflag: u32) OpenError!WasiOpenOptions { |
| 1565 | const w = std.os.wasi; |
| 1566 | |
| 1567 | // First, discover the rights that we can derive from `fd` |
| 1568 | var fsb_cur: wasi.fdstat_t = undefined; |
| 1569 | _ = switch (w.fd_fdstat_get(fd, &fsb_cur)) { |
| 1570 | .SUCCESS => .{}, |
| 1571 | .BADF => return error.InvalidHandle, |
| 1572 | else => |err| return unexpectedErrno(err), |
| 1573 | }; |
| 1574 | |
| 1575 | // Next, calculate the read/write rights to request, depending on the |
| 1576 | // provided POSIX access mode |
| 1577 | var rights: w.rights_t = 0; |
| 1578 | if (oflag & O.RDONLY != 0) { |
| 1579 | rights |= w.RIGHT.FD_READ | w.RIGHT.FD_READDIR; |
| 1580 | } |
| 1581 | if (oflag & O.WRONLY != 0) { |
| 1582 | rights |= w.RIGHT.FD_DATASYNC | w.RIGHT.FD_WRITE | |
| 1583 | w.RIGHT.FD_ALLOCATE | w.RIGHT.FD_FILESTAT_SET_SIZE; |
| 1584 | } |
| 1585 | |
| 1586 | // Request all other rights unconditionally |
| 1587 | rights |= ~(w.RIGHT.FD_DATASYNC | w.RIGHT.FD_READ | |
| 1588 | w.RIGHT.FD_WRITE | w.RIGHT.FD_ALLOCATE | |
| 1589 | w.RIGHT.FD_READDIR | w.RIGHT.FD_FILESTAT_SET_SIZE); |
| 1590 | |
| 1591 | // But only take rights that we can actually inherit |
| 1592 | rights &= fsb_cur.fs_rights_inheriting; |
| 1593 | |
| 1594 | return WasiOpenOptions{ |
| 1595 | .oflags = @truncate(w.oflags_t, (oflag >> 12)) & 0xfff, |
| 1596 | .lookup_flags = if (oflag & O.NOFOLLOW == 0) w.LOOKUP_SYMLINK_FOLLOW else 0, |
| 1597 | .fs_rights_base = rights, |
| 1598 | .fs_rights_inheriting = fsb_cur.fs_rights_inheriting, |
| 1599 | .fs_flags = @truncate(w.fdflags_t, oflag & 0xfff), |
| 1600 | }; |
| 1601 | } |
| 1602 | |
| 1414 | 1603 | /// Open and possibly create a file in WASI. |
| 1415 | 1604 | pub fn openatWasi(dir_fd: fd_t, file_path: []const u8, lookup_flags: lookupflags_t, oflags: oflags_t, fdflags: fdflags_t, base: rights_t, inheriting: rights_t) OpenError!fd_t { |
| 1416 | 1605 | while (true) { |
| ... | ... | @@ -1450,6 +1639,8 @@ pub fn openatZ(dir_fd: fd_t, file_path: [*:0]const u8, flags: u32, mode: mode_t) |
| 1450 | 1639 | if (builtin.os.tag == .windows) { |
| 1451 | 1640 | const file_path_w = try windows.cStrToPrefixedFileW(file_path); |
| 1452 | 1641 | return openatW(dir_fd, file_path_w.span(), flags, mode); |
| 1642 | } else if (builtin.os.tag == .wasi and !builtin.link_libc) { |
| 1643 | return openat(dir_fd, mem.sliceTo(file_path, 0), flags, mode); |
| 1453 | 1644 | } |
| 1454 | 1645 | |
| 1455 | 1646 | const openat_sym = if (builtin.os.tag == .linux and builtin.link_libc) |
| ... | ... | @@ -1496,7 +1687,7 @@ pub fn openatZ(dir_fd: fd_t, file_path: [*:0]const u8, flags: u32, mode: mode_t) |
| 1496 | 1687 | /// or makes use of perm argument. |
| 1497 | 1688 | pub fn openatW(dir_fd: fd_t, file_path_w: []const u16, flags: u32, mode: mode_t) OpenError!fd_t { |
| 1498 | 1689 | _ = mode; |
| 1499 | | var options = openOptionsFromFlags(flags); |
| 1690 | var options = openOptionsFromFlagsWindows(flags); |
| 1500 | 1691 | options.dir = dir_fd; |
| 1501 | 1692 | return windows.OpenFile(file_path_w, options) catch |err| switch (err) { |
| 1502 | 1693 | error.WouldBlock => unreachable, |
| ... | ... | @@ -1764,9 +1955,29 @@ pub const GetCwdError = error{ |
| 1764 | 1955 | pub fn getcwd(out_buffer: []u8) GetCwdError![]u8 { |
| 1765 | 1956 | if (builtin.os.tag == .windows) { |
| 1766 | 1957 | return windows.GetCurrentDirectory(out_buffer); |
| 1767 | | } |
| 1768 | | if (builtin.os.tag == .wasi and !builtin.link_libc) { |
| 1769 | | @compileError("WASI doesn't have a concept of cwd(); use std.fs.wasi.PreopenList to get available Dir handles instead"); |
| 1958 | } else if (builtin.os.tag == .wasi and !builtin.link_libc) { |
| 1959 | var allocator = std.heap.FixedBufferAllocator.init(out_buffer); |
| 1960 | var alloc = allocator.allocator(); |
| 1961 | if (wasi_cwd.cwd) |cwd| { |
| 1962 | if (wasi_cwd.cwd_preopen) |po| { |
| 1963 | var base_cwd_dir = switch (po.@"type") { |
| 1964 | .Dir => |dir| dir, |
| 1965 | }; |
| 1966 | if (!fs.path.isAbsolute(base_cwd_dir)) { |
| 1967 | // This preopen is not based on an absolute path, so we have |
| 1968 | // no way to know the absolute path of the CWD |
| 1969 | return error.CurrentWorkingDirectoryUnlinked; |
| 1970 | } |
| 1971 | const paths = &.{ base_cwd_dir, cwd.relative_path }; |
| 1972 | return std.fs.path.resolve(alloc, paths) catch return error.NameTooLong; |
| 1973 | } else { |
| 1974 | // The CWD is not rooted to an existing Preopen, |
| 1975 | // so we have no way to know its absolute path |
| 1976 | return error.CurrentWorkingDirectoryUnlinked; |
| 1977 | } |
| 1978 | } else { |
| 1979 | return alloc.dupe(u8, "/") catch return error.NameTooLong; |
| 1980 | } |
| 1770 | 1981 | } |
| 1771 | 1982 | |
| 1772 | 1983 | const err = if (builtin.link_libc) blk: { |
| ... | ... | @@ -1809,11 +2020,10 @@ pub const SymLinkError = error{ |
| 1809 | 2020 | /// If `sym_link_path` exists, it will not be overwritten. |
| 1810 | 2021 | /// See also `symlinkZ. |
| 1811 | 2022 | pub fn symlink(target_path: []const u8, sym_link_path: []const u8) SymLinkError!void { |
| 1812 | | if (builtin.os.tag == .wasi and !builtin.link_libc) { |
| 1813 | | @compileError("symlink is not supported in WASI; use symlinkat instead"); |
| 1814 | | } |
| 1815 | 2023 | if (builtin.os.tag == .windows) { |
| 1816 | 2024 | @compileError("symlink is not supported on Windows; use std.os.windows.CreateSymbolicLink instead"); |
| 2025 | } else if (builtin.os.tag == .wasi and !builtin.link_libc) { |
| 2026 | return symlinkat(target_path, wasi.AT.FDCWD, sym_link_path); |
| 1817 | 2027 | } |
| 1818 | 2028 | const target_path_c = try toPosixPath(target_path); |
| 1819 | 2029 | const sym_link_path_c = try toPosixPath(sym_link_path); |
| ... | ... | @@ -1825,6 +2035,8 @@ pub fn symlink(target_path: []const u8, sym_link_path: []const u8) SymLinkError! |
| 1825 | 2035 | pub fn symlinkZ(target_path: [*:0]const u8, sym_link_path: [*:0]const u8) SymLinkError!void { |
| 1826 | 2036 | if (builtin.os.tag == .windows) { |
| 1827 | 2037 | @compileError("symlink is not supported on Windows; use std.os.windows.CreateSymbolicLink instead"); |
| 2038 | } else if (builtin.os.tag == .wasi and !builtin.link_libc) { |
| 2039 | return symlink(mem.sliceTo(target_path, 0), mem.sliceTo(sym_link_path, 0)); |
| 1828 | 2040 | } |
| 1829 | 2041 | switch (errno(system.symlink(target_path, sym_link_path))) { |
| 1830 | 2042 | .SUCCESS => return, |
| ... | ... | @@ -1853,11 +2065,16 @@ pub fn symlinkZ(target_path: [*:0]const u8, sym_link_path: [*:0]const u8) SymLin |
| 1853 | 2065 | /// If `sym_link_path` exists, it will not be overwritten. |
| 1854 | 2066 | /// See also `symlinkatWasi`, `symlinkatZ` and `symlinkatW`. |
| 1855 | 2067 | pub fn symlinkat(target_path: []const u8, newdirfd: fd_t, sym_link_path: []const u8) SymLinkError!void { |
| 1856 | | if (builtin.os.tag == .wasi and !builtin.link_libc) { |
| 1857 | | return symlinkatWasi(target_path, newdirfd, sym_link_path); |
| 1858 | | } |
| 1859 | 2068 | if (builtin.os.tag == .windows) { |
| 1860 | 2069 | @compileError("symlinkat is not supported on Windows; use std.os.windows.CreateSymbolicLink instead"); |
| 2070 | } else if (builtin.os.tag == .wasi and !builtin.link_libc) { |
| 2071 | if (newdirfd == wasi.AT.FDCWD or fs.path.isAbsolute(target_path)) { |
| 2072 | // Resolve absolute or CWD-relative paths to a path within a Preopen |
| 2073 | var path_buf: [MAX_PATH_BYTES]u8 = undefined; |
| 2074 | const path = try resolvePathWasi(sym_link_path, &path_buf); |
| 2075 | return symlinkatWasi(target_path, path.dir_fd, path.relative_path); |
| 2076 | } |
| 2077 | return symlinkatWasi(target_path, newdirfd, sym_link_path); |
| 1861 | 2078 | } |
| 1862 | 2079 | const target_path_c = try toPosixPath(target_path); |
| 1863 | 2080 | const sym_link_path_c = try toPosixPath(sym_link_path); |
| ... | ... | @@ -1893,6 +2110,8 @@ pub fn symlinkatWasi(target_path: []const u8, newdirfd: fd_t, sym_link_path: []c |
| 1893 | 2110 | pub fn symlinkatZ(target_path: [*:0]const u8, newdirfd: fd_t, sym_link_path: [*:0]const u8) SymLinkError!void { |
| 1894 | 2111 | if (builtin.os.tag == .windows) { |
| 1895 | 2112 | @compileError("symlinkat is not supported on Windows; use std.os.windows.CreateSymbolicLink instead"); |
| 2113 | } else if (builtin.os.tag == .wasi and !builtin.link_libc) { |
| 2114 | return symlinkat(mem.sliceTo(target_path, 0), newdirfd, mem.sliceTo(sym_link_path, 0)); |
| 1896 | 2115 | } |
| 1897 | 2116 | switch (errno(system.symlinkat(target_path, newdirfd, sym_link_path))) { |
| 1898 | 2117 | .SUCCESS => return, |
| ... | ... | @@ -1930,6 +2149,9 @@ pub const LinkError = UnexpectedError || error{ |
| 1930 | 2149 | }; |
| 1931 | 2150 | |
| 1932 | 2151 | pub fn linkZ(oldpath: [*:0]const u8, newpath: [*:0]const u8, flags: i32) LinkError!void { |
| 2152 | if (builtin.os.tag == .wasi and !builtin.link_libc) { |
| 2153 | return link(mem.sliceTo(oldpath, 0), mem.sliceTo(newpath, 0), flags); |
| 2154 | } |
| 1933 | 2155 | switch (errno(system.link(oldpath, newpath, flags))) { |
| 1934 | 2156 | .SUCCESS => return, |
| 1935 | 2157 | .ACCES => return error.AccessDenied, |
| ... | ... | @@ -1952,6 +2174,12 @@ pub fn linkZ(oldpath: [*:0]const u8, newpath: [*:0]const u8, flags: i32) LinkErr |
| 1952 | 2174 | } |
| 1953 | 2175 | |
| 1954 | 2176 | pub fn link(oldpath: []const u8, newpath: []const u8, flags: i32) LinkError!void { |
| 2177 | if (builtin.os.tag == .wasi and !builtin.link_libc) { |
| 2178 | return linkat(wasi.AT.FDCWD, oldpath, wasi.AT.FDCWD, newpath, flags) catch |err| switch (err) { |
| 2179 | error.NotDir => unreachable, // link() does not support directories |
| 2180 | else => |e| return e, |
| 2181 | }; |
| 2182 | } |
| 1955 | 2183 | const old = try toPosixPath(oldpath); |
| 1956 | 2184 | const new = try toPosixPath(newpath); |
| 1957 | 2185 | return try linkZ(&old, &new, flags); |
| ... | ... | @@ -1966,6 +2194,9 @@ pub fn linkatZ( |
| 1966 | 2194 | newpath: [*:0]const u8, |
| 1967 | 2195 | flags: i32, |
| 1968 | 2196 | ) LinkatError!void { |
| 2197 | if (builtin.os.tag == .wasi and !builtin.link_libc) { |
| 2198 | return linkat(olddir, mem.sliceTo(oldpath, 0), newdir, mem.sliceTo(newpath, 0), flags); |
| 2199 | } |
| 1969 | 2200 | switch (errno(system.linkat(olddir, oldpath, newdir, newpath, flags))) { |
| 1970 | 2201 | .SUCCESS => return, |
| 1971 | 2202 | .ACCES => return error.AccessDenied, |
| ... | ... | @@ -1995,11 +2226,62 @@ pub fn linkat( |
| 1995 | 2226 | newpath: []const u8, |
| 1996 | 2227 | flags: i32, |
| 1997 | 2228 | ) LinkatError!void { |
| 2229 | if (builtin.os.tag == .wasi and !builtin.link_libc) { |
| 2230 | var resolve_olddir: bool = (olddir == wasi.AT.FDCWD or fs.path.isAbsolute(oldpath)); |
| 2231 | var resolve_newdir: bool = (newdir == wasi.AT.FDCWD or fs.path.isAbsolute(newpath)); |
| 2232 | |
| 2233 | var old: RelativePath = .{ .dir_fd = olddir, .relative_path = oldpath }; |
| 2234 | var new: RelativePath = .{ .dir_fd = newdir, .relative_path = newpath }; |
| 2235 | |
| 2236 | // Resolve absolute or CWD-relative paths to a path within a Preopen |
| 2237 | if (resolve_olddir or resolve_newdir) { |
| 2238 | var buf_old: [MAX_PATH_BYTES]u8 = undefined; |
| 2239 | var buf_new: [MAX_PATH_BYTES]u8 = undefined; |
| 2240 | |
| 2241 | if (resolve_olddir) |
| 2242 | old = try resolvePathWasi(oldpath, &buf_old); |
| 2243 | |
| 2244 | if (resolve_newdir) |
| 2245 | new = try resolvePathWasi(newpath, &buf_new); |
| 2246 | |
| 2247 | return linkatWasi(old, new, flags); |
| 2248 | } |
| 2249 | return linkatWasi(old, new, flags); |
| 2250 | } |
| 1998 | 2251 | const old = try toPosixPath(oldpath); |
| 1999 | 2252 | const new = try toPosixPath(newpath); |
| 2000 | 2253 | return try linkatZ(olddir, &old, newdir, &new, flags); |
| 2001 | 2254 | } |
| 2002 | 2255 | |
| 2256 | /// WASI-only. The same as `linkat` but targeting WASI. |
| 2257 | /// See also `linkat`. |
| 2258 | pub fn linkatWasi(old: RelativePath, new: RelativePath, flags: i32) LinkatError!void { |
| 2259 | var old_flags: wasi.lookupflags_t = 0; |
| 2260 | // TODO: Why is this not defined in wasi-libc? |
| 2261 | if (flags & linux.AT.SYMLINK_FOLLOW != 0) old_flags |= wasi.LOOKUP_SYMLINK_FOLLOW; |
| 2262 | |
| 2263 | switch (wasi.path_link(old.dir_fd, old_flags, old.relative_path.ptr, old.relative_path.len, new.dir_fd, new.relative_path.ptr, new.relative_path.len)) { |
| 2264 | .SUCCESS => return, |
| 2265 | .ACCES => return error.AccessDenied, |
| 2266 | .DQUOT => return error.DiskQuota, |
| 2267 | .EXIST => return error.PathAlreadyExists, |
| 2268 | .FAULT => unreachable, |
| 2269 | .IO => return error.FileSystem, |
| 2270 | .LOOP => return error.SymLinkLoop, |
| 2271 | .MLINK => return error.LinkQuotaExceeded, |
| 2272 | .NAMETOOLONG => return error.NameTooLong, |
| 2273 | .NOENT => return error.FileNotFound, |
| 2274 | .NOMEM => return error.SystemResources, |
| 2275 | .NOSPC => return error.NoSpaceLeft, |
| 2276 | .NOTDIR => return error.NotDir, |
| 2277 | .PERM => return error.AccessDenied, |
| 2278 | .ROFS => return error.ReadOnlyFileSystem, |
| 2279 | .XDEV => return error.NotSameFileSystem, |
| 2280 | .INVAL => unreachable, |
| 2281 | else => |err| return unexpectedErrno(err), |
| 2282 | } |
| 2283 | } |
| 2284 | |
| 2003 | 2285 | pub const UnlinkError = error{ |
| 2004 | 2286 | FileNotFound, |
| 2005 | 2287 | |
| ... | ... | @@ -2027,7 +2309,10 @@ pub const UnlinkError = error{ |
| 2027 | 2309 | /// See also `unlinkZ`. |
| 2028 | 2310 | pub fn unlink(file_path: []const u8) UnlinkError!void { |
| 2029 | 2311 | if (builtin.os.tag == .wasi and !builtin.link_libc) { |
| 2030 | | @compileError("unlink is not supported in WASI; use unlinkat instead"); |
| 2312 | return unlinkat(wasi.AT.FDCWD, file_path, 0) catch |err| switch (err) { |
| 2313 | error.DirNotEmpty => unreachable, // only occurs when targeting directories |
| 2314 | else => |e| return e, |
| 2315 | }; |
| 2031 | 2316 | } else if (builtin.os.tag == .windows) { |
| 2032 | 2317 | const file_path_w = try windows.sliceToPrefixedFileW(file_path); |
| 2033 | 2318 | return unlinkW(file_path_w.span()); |
| ... | ... | @@ -2042,6 +2327,8 @@ pub fn unlinkZ(file_path: [*:0]const u8) UnlinkError!void { |
| 2042 | 2327 | if (builtin.os.tag == .windows) { |
| 2043 | 2328 | const file_path_w = try windows.cStrToPrefixedFileW(file_path); |
| 2044 | 2329 | return unlinkW(file_path_w.span()); |
| 2330 | } else if (builtin.os.tag == .wasi and !builtin.link_libc) { |
| 2331 | return unlink(mem.sliceTo(file_path, 0)); |
| 2045 | 2332 | } |
| 2046 | 2333 | switch (errno(system.unlink(file_path))) { |
| 2047 | 2334 | .SUCCESS => return, |
| ... | ... | @@ -2079,6 +2366,12 @@ pub fn unlinkat(dirfd: fd_t, file_path: []const u8, flags: u32) UnlinkatError!vo |
| 2079 | 2366 | const file_path_w = try windows.sliceToPrefixedFileW(file_path); |
| 2080 | 2367 | return unlinkatW(dirfd, file_path_w.span(), flags); |
| 2081 | 2368 | } else if (builtin.os.tag == .wasi and !builtin.link_libc) { |
| 2369 | if (dirfd == wasi.AT.FDCWD or fs.path.isAbsolute(file_path)) { |
| 2370 | // Resolve absolute or CWD-relative paths to a path within a Preopen |
| 2371 | var path_buf: [MAX_PATH_BYTES]u8 = undefined; |
| 2372 | const path = try resolvePathWasi(file_path, &path_buf); |
| 2373 | return unlinkatWasi(path.dir_fd, path.relative_path, flags); |
| 2374 | } |
| 2082 | 2375 | return unlinkatWasi(dirfd, file_path, flags); |
| 2083 | 2376 | } else { |
| 2084 | 2377 | const file_path_c = try toPosixPath(file_path); |
| ... | ... | @@ -2123,6 +2416,8 @@ pub fn unlinkatZ(dirfd: fd_t, file_path_c: [*:0]const u8, flags: u32) UnlinkatEr |
| 2123 | 2416 | if (builtin.os.tag == .windows) { |
| 2124 | 2417 | const file_path_w = try windows.cStrToPrefixedFileW(file_path_c); |
| 2125 | 2418 | return unlinkatW(dirfd, file_path_w.span(), flags); |
| 2419 | } else if (builtin.os.tag == .wasi and !builtin.link_libc) { |
| 2420 | return unlinkat(dirfd, mem.sliceTo(file_path_c, 0), flags); |
| 2126 | 2421 | } |
| 2127 | 2422 | switch (errno(system.unlinkat(dirfd, file_path_c, flags))) { |
| 2128 | 2423 | .SUCCESS => return, |
| ... | ... | @@ -2181,7 +2476,7 @@ pub const RenameError = error{ |
| 2181 | 2476 | /// Change the name or location of a file. |
| 2182 | 2477 | pub fn rename(old_path: []const u8, new_path: []const u8) RenameError!void { |
| 2183 | 2478 | if (builtin.os.tag == .wasi and !builtin.link_libc) { |
| 2184 | | @compileError("rename is not supported in WASI; use renameat instead"); |
| 2479 | return renameat(wasi.AT.FDCWD, old_path, wasi.AT.FDCWD, new_path); |
| 2185 | 2480 | } else if (builtin.os.tag == .windows) { |
| 2186 | 2481 | const old_path_w = try windows.sliceToPrefixedFileW(old_path); |
| 2187 | 2482 | const new_path_w = try windows.sliceToPrefixedFileW(new_path); |
| ... | ... | @@ -2199,6 +2494,8 @@ pub fn renameZ(old_path: [*:0]const u8, new_path: [*:0]const u8) RenameError!voi |
| 2199 | 2494 | const old_path_w = try windows.cStrToPrefixedFileW(old_path); |
| 2200 | 2495 | const new_path_w = try windows.cStrToPrefixedFileW(new_path); |
| 2201 | 2496 | return renameW(old_path_w.span().ptr, new_path_w.span().ptr); |
| 2497 | } else if (builtin.os.tag == .wasi and !builtin.link_libc) { |
| 2498 | return rename(mem.sliceTo(old_path, 0), mem.sliceTo(new_path, 0)); |
| 2202 | 2499 | } |
| 2203 | 2500 | switch (errno(system.rename(old_path, new_path))) { |
| 2204 | 2501 | .SUCCESS => return, |
| ... | ... | @@ -2243,7 +2540,25 @@ pub fn renameat( |
| 2243 | 2540 | const new_path_w = try windows.sliceToPrefixedFileW(new_path); |
| 2244 | 2541 | return renameatW(old_dir_fd, old_path_w.span(), new_dir_fd, new_path_w.span(), windows.TRUE); |
| 2245 | 2542 | } else if (builtin.os.tag == .wasi and !builtin.link_libc) { |
| 2246 | | return renameatWasi(old_dir_fd, old_path, new_dir_fd, new_path); |
| 2543 | var resolve_old: bool = (old_dir_fd == wasi.AT.FDCWD or fs.path.isAbsolute(old_path)); |
| 2544 | var resolve_new: bool = (new_dir_fd == wasi.AT.FDCWD or fs.path.isAbsolute(new_path)); |
| 2545 | |
| 2546 | var old: RelativePath = .{ .dir_fd = old_dir_fd, .relative_path = old_path }; |
| 2547 | var new: RelativePath = .{ .dir_fd = new_dir_fd, .relative_path = new_path }; |
| 2548 | |
| 2549 | // Resolve absolute or CWD-relative paths to a path within a Preopen |
| 2550 | if (resolve_old or resolve_new) { |
| 2551 | var buf_old: [MAX_PATH_BYTES]u8 = undefined; |
| 2552 | var buf_new: [MAX_PATH_BYTES]u8 = undefined; |
| 2553 | |
| 2554 | if (resolve_old) |
| 2555 | old = try resolvePathWasi(old_path, &buf_old); |
| 2556 | if (resolve_new) |
| 2557 | new = try resolvePathWasi(new_path, &buf_new); |
| 2558 | |
| 2559 | return renameatWasi(old, new); |
| 2560 | } |
| 2561 | return renameatWasi(old, new); |
| 2247 | 2562 | } else { |
| 2248 | 2563 | const old_path_c = try toPosixPath(old_path); |
| 2249 | 2564 | const new_path_c = try toPosixPath(new_path); |
| ... | ... | @@ -2253,8 +2568,8 @@ pub fn renameat( |
| 2253 | 2568 | |
| 2254 | 2569 | /// WASI-only. Same as `renameat` expect targeting WASI. |
| 2255 | 2570 | /// See also `renameat`. |
| 2256 | | pub fn renameatWasi(old_dir_fd: fd_t, old_path: []const u8, new_dir_fd: fd_t, new_path: []const u8) RenameError!void { |
| 2257 | | switch (wasi.path_rename(old_dir_fd, old_path.ptr, old_path.len, new_dir_fd, new_path.ptr, new_path.len)) { |
| 2571 | pub fn renameatWasi(old: RelativePath, new: RelativePath) RenameError!void { |
| 2572 | switch (wasi.path_rename(old.dir_fd, old.relative_path.ptr, old.relative_path.len, new.dir_fd, new.relative_path.ptr, new.relative_path.len)) { |
| 2258 | 2573 | .SUCCESS => return, |
| 2259 | 2574 | .ACCES => return error.AccessDenied, |
| 2260 | 2575 | .PERM => return error.AccessDenied, |
| ... | ... | @@ -2290,6 +2605,8 @@ pub fn renameatZ( |
| 2290 | 2605 | const old_path_w = try windows.cStrToPrefixedFileW(old_path); |
| 2291 | 2606 | const new_path_w = try windows.cStrToPrefixedFileW(new_path); |
| 2292 | 2607 | return renameatW(old_dir_fd, old_path_w.span(), new_dir_fd, new_path_w.span(), windows.TRUE); |
| 2608 | } else if (builtin.os.tag == .wasi and !builtin.link_libc) { |
| 2609 | return renameat(old_dir_fd, mem.sliceTo(old_path, 0), new_dir_fd, mem.sliceTo(new_path, 0)); |
| 2293 | 2610 | } |
| 2294 | 2611 | |
| 2295 | 2612 | switch (errno(system.renameat(old_dir_fd, old_path, new_dir_fd, new_path))) { |
| ... | ... | @@ -2380,6 +2697,12 @@ pub fn mkdirat(dir_fd: fd_t, sub_dir_path: []const u8, mode: u32) MakeDirError!v |
| 2380 | 2697 | const sub_dir_path_w = try windows.sliceToPrefixedFileW(sub_dir_path); |
| 2381 | 2698 | return mkdiratW(dir_fd, sub_dir_path_w.span(), mode); |
| 2382 | 2699 | } else if (builtin.os.tag == .wasi and !builtin.link_libc) { |
| 2700 | if (dir_fd == wasi.AT.FDCWD or fs.path.isAbsolute(sub_dir_path)) { |
| 2701 | // Resolve absolute or CWD-relative paths to a path within a Preopen |
| 2702 | var path_buf: [MAX_PATH_BYTES]u8 = undefined; |
| 2703 | const path = try resolvePathWasi(sub_dir_path, &path_buf); |
| 2704 | return mkdiratWasi(path.dir_fd, path.relative_path, mode); |
| 2705 | } |
| 2383 | 2706 | return mkdiratWasi(dir_fd, sub_dir_path, mode); |
| 2384 | 2707 | } else { |
| 2385 | 2708 | const sub_dir_path_c = try toPosixPath(sub_dir_path); |
| ... | ... | @@ -2414,6 +2737,8 @@ pub fn mkdiratZ(dir_fd: fd_t, sub_dir_path: [*:0]const u8, mode: u32) MakeDirErr |
| 2414 | 2737 | if (builtin.os.tag == .windows) { |
| 2415 | 2738 | const sub_dir_path_w = try windows.cStrToPrefixedFileW(sub_dir_path); |
| 2416 | 2739 | return mkdiratW(dir_fd, sub_dir_path_w.span().ptr, mode); |
| 2740 | } else if (builtin.os.tag == .wasi and !builtin.link_libc) { |
| 2741 | return mkdirat(dir_fd, mem.sliceTo(sub_dir_path, 0), mode); |
| 2417 | 2742 | } |
| 2418 | 2743 | switch (errno(system.mkdirat(dir_fd, sub_dir_path, mode))) { |
| 2419 | 2744 | .SUCCESS => return, |
| ... | ... | @@ -2472,10 +2797,10 @@ pub const MakeDirError = error{ |
| 2472 | 2797 | } || UnexpectedError; |
| 2473 | 2798 | |
| 2474 | 2799 | /// Create a directory. |
| 2475 | | /// `mode` is ignored on Windows. |
| 2800 | /// `mode` is ignored on Windows and WASI. |
| 2476 | 2801 | pub fn mkdir(dir_path: []const u8, mode: u32) MakeDirError!void { |
| 2477 | 2802 | if (builtin.os.tag == .wasi and !builtin.link_libc) { |
| 2478 | | @compileError("mkdir is not supported in WASI; use mkdirat instead"); |
| 2803 | return mkdirat(wasi.AT.FDCWD, dir_path, mode); |
| 2479 | 2804 | } else if (builtin.os.tag == .windows) { |
| 2480 | 2805 | const dir_path_w = try windows.sliceToPrefixedFileW(dir_path); |
| 2481 | 2806 | return mkdirW(dir_path_w.span(), mode); |
| ... | ... | @@ -2490,6 +2815,8 @@ pub fn mkdirZ(dir_path: [*:0]const u8, mode: u32) MakeDirError!void { |
| 2490 | 2815 | if (builtin.os.tag == .windows) { |
| 2491 | 2816 | const dir_path_w = try windows.cStrToPrefixedFileW(dir_path); |
| 2492 | 2817 | return mkdirW(dir_path_w.span(), mode); |
| 2818 | } else if (builtin.os.tag == .wasi and !builtin.link_libc) { |
| 2819 | return mkdir(mem.sliceTo(dir_path, 0), mode); |
| 2493 | 2820 | } |
| 2494 | 2821 | switch (errno(system.mkdir(dir_path, mode))) { |
| 2495 | 2822 | .SUCCESS => return, |
| ... | ... | @@ -2545,7 +2872,11 @@ pub const DeleteDirError = error{ |
| 2545 | 2872 | /// Deletes an empty directory. |
| 2546 | 2873 | pub fn rmdir(dir_path: []const u8) DeleteDirError!void { |
| 2547 | 2874 | if (builtin.os.tag == .wasi and !builtin.link_libc) { |
| 2548 | | @compileError("rmdir is not supported in WASI; use unlinkat instead"); |
| 2875 | return unlinkat(wasi.AT.FDCWD, dir_path, AT.REMOVEDIR) catch |err| switch (err) { |
| 2876 | error.FileSystem => unreachable, // only occurs when targeting files |
| 2877 | error.IsDir => unreachable, // only occurs when targeting files |
| 2878 | else => |e| return e, |
| 2879 | }; |
| 2549 | 2880 | } else if (builtin.os.tag == .windows) { |
| 2550 | 2881 | const dir_path_w = try windows.sliceToPrefixedFileW(dir_path); |
| 2551 | 2882 | return rmdirW(dir_path_w.span()); |
| ... | ... | @@ -2560,6 +2891,8 @@ pub fn rmdirZ(dir_path: [*:0]const u8) DeleteDirError!void { |
| 2560 | 2891 | if (builtin.os.tag == .windows) { |
| 2561 | 2892 | const dir_path_w = try windows.cStrToPrefixedFileW(dir_path); |
| 2562 | 2893 | return rmdirW(dir_path_w.span()); |
| 2894 | } else if (builtin.os.tag == .wasi and !builtin.link_libc) { |
| 2895 | return rmdir(mem.sliceTo(dir_path, 0)); |
| 2563 | 2896 | } |
| 2564 | 2897 | switch (errno(system.rmdir(dir_path))) { |
| 2565 | 2898 | .SUCCESS => return, |
| ... | ... | @@ -2606,7 +2939,17 @@ pub const ChangeCurDirError = error{ |
| 2606 | 2939 | /// `dir_path` is recommended to be a UTF-8 encoded string. |
| 2607 | 2940 | pub fn chdir(dir_path: []const u8) ChangeCurDirError!void { |
| 2608 | 2941 | if (builtin.os.tag == .wasi and !builtin.link_libc) { |
| 2609 | | @compileError("chdir is not supported in WASI"); |
| 2942 | var preopen: ?Preopen = null; |
| 2943 | const path = try resolvePathAndGetWasiPreopen(dir_path, &preopen, &wasi_cwd.path_buffer); |
| 2944 | |
| 2945 | const dirinfo = try fstatat(path.dir_fd, path.relative_path, 0); |
| 2946 | if (dirinfo.filetype != .DIRECTORY) { |
| 2947 | return error.NotDir; |
| 2948 | } |
| 2949 | |
| 2950 | wasi_cwd.cwd_preopen = preopen; |
| 2951 | wasi_cwd.cwd = path; |
| 2952 | return; |
| 2610 | 2953 | } else if (builtin.os.tag == .windows) { |
| 2611 | 2954 | var utf16_dir_path: [windows.PATH_MAX_WIDE]u16 = undefined; |
| 2612 | 2955 | const len = try std.unicode.utf8ToUtf16Le(utf16_dir_path[0..], dir_path); |
| ... | ... | @@ -2625,6 +2968,8 @@ pub fn chdirZ(dir_path: [*:0]const u8) ChangeCurDirError!void { |
| 2625 | 2968 | const len = try std.unicode.utf8ToUtf16Le(utf16_dir_path[0..], dir_path); |
| 2626 | 2969 | if (len > utf16_dir_path.len) return error.NameTooLong; |
| 2627 | 2970 | return chdirW(utf16_dir_path[0..len]); |
| 2971 | } else if (builtin.os.tag == .wasi and !builtin.link_libc) { |
| 2972 | return chdir(mem.sliceTo(dir_path, 0)); |
| 2628 | 2973 | } |
| 2629 | 2974 | switch (errno(system.chdir(dir_path))) { |
| 2630 | 2975 | .SUCCESS => return, |
| ... | ... | @@ -2655,15 +3000,29 @@ pub const FchdirError = error{ |
| 2655 | 3000 | } || UnexpectedError; |
| 2656 | 3001 | |
| 2657 | 3002 | pub fn fchdir(dirfd: fd_t) FchdirError!void { |
| 2658 | | while (true) { |
| 2659 | | switch (errno(system.fchdir(dirfd))) { |
| 2660 | | .SUCCESS => return, |
| 2661 | | .ACCES => return error.AccessDenied, |
| 2662 | | .BADF => unreachable, |
| 2663 | | .NOTDIR => return error.NotDir, |
| 2664 | | .INTR => continue, |
| 2665 | | .IO => return error.FileSystem, |
| 2666 | | else => |err| return unexpectedErrno(err), |
| 3003 | if (builtin.os.tag == .wasi) { |
| 3004 | // Check that this is a directory |
| 3005 | const dirinfo = fstatat(dirfd, ".", 0) catch unreachable; |
| 3006 | if (dirinfo.filetype != .DIRECTORY) { |
| 3007 | return error.NotDir; |
| 3008 | } |
| 3009 | |
| 3010 | wasi_cwd.cwd = .{ |
| 3011 | .dir_fd = dirfd, |
| 3012 | .relative_path = ".", |
| 3013 | }; |
| 3014 | wasi_cwd.cwd_preopen = null; |
| 3015 | } else { |
| 3016 | while (true) { |
| 3017 | switch (errno(system.fchdir(dirfd))) { |
| 3018 | .SUCCESS => return, |
| 3019 | .ACCES => return error.AccessDenied, |
| 3020 | .BADF => unreachable, |
| 3021 | .NOTDIR => return error.NotDir, |
| 3022 | .INTR => continue, |
| 3023 | .IO => return error.FileSystem, |
| 3024 | else => |err| return unexpectedErrno(err), |
| 3025 | } |
| 2667 | 3026 | } |
| 2668 | 3027 | } |
| 2669 | 3028 | } |
| ... | ... | @@ -2690,7 +3049,7 @@ pub const ReadLinkError = error{ |
| 2690 | 3049 | /// The return value is a slice of `out_buffer` from index 0. |
| 2691 | 3050 | pub fn readlink(file_path: []const u8, out_buffer: []u8) ReadLinkError![]u8 { |
| 2692 | 3051 | if (builtin.os.tag == .wasi and !builtin.link_libc) { |
| 2693 | | @compileError("readlink is not supported in WASI; use readlinkat instead"); |
| 3052 | return readlinkat(wasi.AT.FDCWD, file_path, out_buffer); |
| 2694 | 3053 | } else if (builtin.os.tag == .windows) { |
| 2695 | 3054 | const file_path_w = try windows.sliceToPrefixedFileW(file_path); |
| 2696 | 3055 | return readlinkW(file_path_w.span(), out_buffer); |
| ... | ... | @@ -2711,6 +3070,8 @@ pub fn readlinkZ(file_path: [*:0]const u8, out_buffer: []u8) ReadLinkError![]u8 |
| 2711 | 3070 | if (builtin.os.tag == .windows) { |
| 2712 | 3071 | const file_path_w = try windows.cStrToWin32PrefixedFileW(file_path); |
| 2713 | 3072 | return readlinkW(file_path_w.span(), out_buffer); |
| 3073 | } else if (builtin.os.tag == .wasi and !builtin.link_libc) { |
| 3074 | return readlink(mem.sliceTo(file_path, 0), out_buffer); |
| 2714 | 3075 | } |
| 2715 | 3076 | const rc = system.readlink(file_path, out_buffer.ptr, out_buffer.len); |
| 2716 | 3077 | switch (errno(rc)) { |
| ... | ... | @@ -2733,6 +3094,12 @@ pub fn readlinkZ(file_path: [*:0]const u8, out_buffer: []u8) ReadLinkError![]u8 |
| 2733 | 3094 | /// See also `readlinkatWasi`, `realinkatZ` and `realinkatW`. |
| 2734 | 3095 | pub fn readlinkat(dirfd: fd_t, file_path: []const u8, out_buffer: []u8) ReadLinkError![]u8 { |
| 2735 | 3096 | if (builtin.os.tag == .wasi and !builtin.link_libc) { |
| 3097 | if (dirfd == wasi.AT.FDCWD or fs.path.isAbsolute(file_path)) { |
| 3098 | // Resolve absolute or CWD-relative paths to a path within a Preopen |
| 3099 | var path_buf: [MAX_PATH_BYTES]u8 = undefined; |
| 3100 | var path = try resolvePathWasi(file_path, &path_buf); |
| 3101 | return readlinkatWasi(path.dir_fd, path.relative_path, out_buffer); |
| 3102 | } |
| 2736 | 3103 | return readlinkatWasi(dirfd, file_path, out_buffer); |
| 2737 | 3104 | } |
| 2738 | 3105 | if (builtin.os.tag == .windows) { |
| ... | ... | @@ -2775,6 +3142,8 @@ pub fn readlinkatZ(dirfd: fd_t, file_path: [*:0]const u8, out_buffer: []u8) Read |
| 2775 | 3142 | if (builtin.os.tag == .windows) { |
| 2776 | 3143 | const file_path_w = try windows.cStrToPrefixedFileW(file_path); |
| 2777 | 3144 | return readlinkatW(dirfd, file_path_w.span(), out_buffer); |
| 3145 | } else if (builtin.os.tag == .wasi and !builtin.link_libc) { |
| 3146 | return readlinkat(dirfd, mem.sliceTo(file_path, 0), out_buffer); |
| 2778 | 3147 | } |
| 2779 | 3148 | const rc = system.readlinkat(dirfd, file_path, out_buffer.ptr, out_buffer.len); |
| 2780 | 3149 | switch (errno(rc)) { |
| ... | ... | @@ -3727,7 +4096,14 @@ pub const FStatAtError = FStatError || error{ NameTooLong, FileNotFound, SymLink |
| 3727 | 4096 | /// See also `fstatatZ` and `fstatatWasi`. |
| 3728 | 4097 | pub fn fstatat(dirfd: fd_t, pathname: []const u8, flags: u32) FStatAtError!Stat { |
| 3729 | 4098 | if (builtin.os.tag == .wasi and !builtin.link_libc) { |
| 3730 | | return fstatatWasi(dirfd, pathname, flags); |
| 4099 | const wasi_flags = if (flags & linux.AT.SYMLINK_NOFOLLOW == 0) wasi.LOOKUP_SYMLINK_FOLLOW else 0; |
| 4100 | if (dirfd == wasi.AT.FDCWD or fs.path.isAbsolute(pathname)) { |
| 4101 | // Resolve absolute or CWD-relative paths to a path within a Preopen |
| 4102 | var path_buf: [MAX_PATH_BYTES]u8 = undefined; |
| 4103 | const path = try resolvePathWasi(pathname, &path_buf); |
| 4104 | return fstatatWasi(path.dir_fd, path.relative_path, wasi_flags); |
| 4105 | } |
| 4106 | return fstatatWasi(dirfd, pathname, wasi_flags); |
| 3731 | 4107 | } else if (builtin.os.tag == .windows) { |
| 3732 | 4108 | @compileError("fstatat is not yet implemented on Windows"); |
| 3733 | 4109 | } else { |
| ... | ... | @@ -3758,6 +4134,10 @@ pub fn fstatatWasi(dirfd: fd_t, pathname: []const u8, flags: u32) FStatAtError!S |
| 3758 | 4134 | /// Same as `fstatat` but `pathname` is null-terminated. |
| 3759 | 4135 | /// See also `fstatat`. |
| 3760 | 4136 | pub fn fstatatZ(dirfd: fd_t, pathname: [*:0]const u8, flags: u32) FStatAtError!Stat { |
| 4137 | if (builtin.os.tag == .wasi and !builtin.link_libc) { |
| 4138 | return fstatatWasi(dirfd, mem.sliceTo(pathname), flags); |
| 4139 | } |
| 4140 | |
| 3761 | 4141 | const fstatat_sym = if (builtin.os.tag == .linux and builtin.link_libc) |
| 3762 | 4142 | system.fstatat64 |
| 3763 | 4143 | else |
| ... | ... | @@ -4056,6 +4436,8 @@ pub fn access(path: []const u8, mode: u32) AccessError!void { |
| 4056 | 4436 | const path_w = try windows.sliceToPrefixedFileW(path); |
| 4057 | 4437 | _ = try windows.GetFileAttributesW(path_w.span().ptr); |
| 4058 | 4438 | return; |
| 4439 | } else if (builtin.os.tag == .wasi and !builtin.link_libc) { |
| 4440 | return faccessat(wasi.AT.FDCWD, path, mode, 0); |
| 4059 | 4441 | } |
| 4060 | 4442 | const path_c = try toPosixPath(path); |
| 4061 | 4443 | return accessZ(&path_c, mode); |
| ... | ... | @@ -4067,6 +4449,8 @@ pub fn accessZ(path: [*:0]const u8, mode: u32) AccessError!void { |
| 4067 | 4449 | const path_w = try windows.cStrToPrefixedFileW(path); |
| 4068 | 4450 | _ = try windows.GetFileAttributesW(path_w.span().ptr); |
| 4069 | 4451 | return; |
| 4452 | } else if (builtin.os.tag == .wasi and !builtin.link_libc) { |
| 4453 | return access(mem.sliceTo(path, 0), mode); |
| 4070 | 4454 | } |
| 4071 | 4455 | switch (errno(system.access(path, mode))) { |
| 4072 | 4456 | .SUCCESS => return, |
| ... | ... | @@ -4108,6 +4492,45 @@ pub fn faccessat(dirfd: fd_t, path: []const u8, mode: u32, flags: u32) AccessErr |
| 4108 | 4492 | if (builtin.os.tag == .windows) { |
| 4109 | 4493 | const path_w = try windows.sliceToPrefixedFileW(path); |
| 4110 | 4494 | return faccessatW(dirfd, path_w.span().ptr, mode, flags); |
| 4495 | } else if (builtin.os.tag == .wasi and !builtin.link_libc) { |
| 4496 | var resolved = RelativePath{ .dir_fd = dirfd, .relative_path = path }; |
| 4497 | |
| 4498 | const file = blk: { |
| 4499 | if (dirfd == wasi.AT.FDCWD or fs.path.isAbsolute(path)) { |
| 4500 | // Resolve absolute or CWD-relative paths to a path within a Preopen |
| 4501 | var path_buf: [MAX_PATH_BYTES]u8 = undefined; |
| 4502 | resolved = resolvePathWasi(path, &path_buf) catch |err| break :blk @as(FStatAtError!Stat, err); |
| 4503 | break :blk fstatat(resolved.dir_fd, resolved.relative_path, flags); |
| 4504 | } |
| 4505 | break :blk fstatat(dirfd, path, flags); |
| 4506 | } catch |err| switch (err) { |
| 4507 | error.AccessDenied => return error.PermissionDenied, |
| 4508 | else => |e| return e, |
| 4509 | }; |
| 4510 | |
| 4511 | if (mode != F_OK) { |
| 4512 | var directory: wasi.fdstat_t = undefined; |
| 4513 | if (wasi.fd_fdstat_get(resolved.dir_fd, &directory) != .SUCCESS) { |
| 4514 | return error.PermissionDenied; |
| 4515 | } |
| 4516 | |
| 4517 | var rights: wasi.rights_t = 0; |
| 4518 | if (mode & R_OK != 0) { |
| 4519 | rights |= if (file.filetype == .DIRECTORY) |
| 4520 | wasi.RIGHT.FD_READDIR |
| 4521 | else |
| 4522 | wasi.RIGHT.FD_READ; |
| 4523 | } |
| 4524 | if (mode & W_OK != 0) { |
| 4525 | rights |= wasi.RIGHT.FD_WRITE; |
| 4526 | } |
| 4527 | // No validation for X_OK |
| 4528 | |
| 4529 | if ((rights & directory.fs_rights_inheriting) != rights) { |
| 4530 | return error.PermissionDenied; |
| 4531 | } |
| 4532 | } |
| 4533 | return; |
| 4111 | 4534 | } |
| 4112 | 4535 | const path_c = try toPosixPath(path); |
| 4113 | 4536 | return faccessatZ(dirfd, &path_c, mode, flags); |
| ... | ... | @@ -4118,6 +4541,8 @@ pub fn faccessatZ(dirfd: fd_t, path: [*:0]const u8, mode: u32, flags: u32) Acces |
| 4118 | 4541 | if (builtin.os.tag == .windows) { |
| 4119 | 4542 | const path_w = try windows.cStrToPrefixedFileW(path); |
| 4120 | 4543 | return faccessatW(dirfd, path_w.span().ptr, mode, flags); |
| 4544 | } else if (builtin.os.tag == .wasi and !builtin.link_libc) { |
| 4545 | return faccessat(dirfd, mem.sliceTo(path, 0), mode, flags); |
| 4121 | 4546 | } |
| 4122 | 4547 | switch (errno(system.faccessat(dirfd, path, mode, flags))) { |
| 4123 | 4548 | .SUCCESS => return, |
| ... | ... | @@ -4645,6 +5070,9 @@ pub const RealPathError = error{ |
| 4645 | 5070 | SharingViolation, |
| 4646 | 5071 | PipeBusy, |
| 4647 | 5072 | |
| 5073 | /// On WASI, the current CWD may not be associated with an absolute path. |
| 5074 | InvalidHandle, |
| 5075 | |
| 4648 | 5076 | /// On Windows, file paths must be valid Unicode. |
| 4649 | 5077 | InvalidUtf8, |
| 4650 | 5078 | |
| ... | ... | @@ -4660,9 +5088,33 @@ pub fn realpath(pathname: []const u8, out_buffer: *[MAX_PATH_BYTES]u8) RealPathE |
| 4660 | 5088 | if (builtin.os.tag == .windows) { |
| 4661 | 5089 | const pathname_w = try windows.sliceToPrefixedFileW(pathname); |
| 4662 | 5090 | return realpathW(pathname_w.span(), out_buffer); |
| 4663 | | } |
| 4664 | | if (builtin.os.tag == .wasi and !builtin.link_libc) { |
| 4665 | | @compileError("Use std.fs.wasi.PreopenList to obtain valid Dir handles instead of using absolute paths"); |
| 5091 | } else if (builtin.os.tag == .wasi and !builtin.link_libc) { |
| 5092 | // NOTE: This emulation is incomplete. Symbolic links are not |
| 5093 | // currently expanded during path canonicalization. |
| 5094 | var alloc = std.heap.FixedBufferAllocator.init(out_buffer); |
| 5095 | if (fs.path.isAbsolute(pathname)) |
| 5096 | return try fs.path.resolve(alloc.allocator(), &.{pathname}) catch error.NameTooLong; |
| 5097 | if (wasi_cwd.cwd) |cwd| { |
| 5098 | if (wasi_cwd.cwd_preopen) |po| { |
| 5099 | var base_cwd_dir = switch (po.@"type") { |
| 5100 | .Dir => |dir| dir, |
| 5101 | }; |
| 5102 | if (!fs.path.isAbsolute(base_cwd_dir)) { |
| 5103 | // This preopen is not based on an absolute path, so we have |
| 5104 | // no way to know the absolute path of the CWD |
| 5105 | return error.InvalidHandle; |
| 5106 | } |
| 5107 | |
| 5108 | const paths = &.{ base_cwd_dir, cwd.relative_path, pathname }; |
| 5109 | return fs.path.resolve(alloc.allocator(), paths) catch error.NameTooLong; |
| 5110 | } else { |
| 5111 | // The CWD is not rooted to an existing Preopen, |
| 5112 | // so we have no way to know its absolute path |
| 5113 | return error.InvalidHandle; |
| 5114 | } |
| 5115 | } else { |
| 5116 | return try fs.path.resolve(alloc.allocator(), &.{ "/", pathname }) catch error.NameTooLong; |
| 5117 | } |
| 4666 | 5118 | } |
| 4667 | 5119 | const pathname_c = try toPosixPath(pathname); |
| 4668 | 5120 | return realpathZ(&pathname_c, out_buffer); |
| ... | ... | @@ -4673,6 +5125,8 @@ pub fn realpathZ(pathname: [*:0]const u8, out_buffer: *[MAX_PATH_BYTES]u8) RealP |
| 4673 | 5125 | if (builtin.os.tag == .windows) { |
| 4674 | 5126 | const pathname_w = try windows.cStrToPrefixedFileW(pathname); |
| 4675 | 5127 | return realpathW(pathname_w.span(), out_buffer); |
| 5128 | } else if (builtin.os.tag == .wasi and !builtin.link_libc) { |
| 5129 | return realpath(mem.sliceTo(pathname, 0), out_buffer); |
| 4676 | 5130 | } |
| 4677 | 5131 | if (!builtin.link_libc) { |
| 4678 | 5132 | const flags = if (builtin.os.tag == .linux) O.PATH | O.NONBLOCK | O.CLOEXEC else O.NONBLOCK | O.CLOEXEC; |
| ... | ... | @@ -4680,6 +5134,7 @@ pub fn realpathZ(pathname: [*:0]const u8, out_buffer: *[MAX_PATH_BYTES]u8) RealP |
| 4680 | 5134 | error.FileLocksNotSupported => unreachable, |
| 4681 | 5135 | error.WouldBlock => unreachable, |
| 4682 | 5136 | error.FileBusy => unreachable, // not asking for write permissions |
| 5137 | error.InvalidHandle => unreachable, // WASI-only |
| 4683 | 5138 | else => |e| return e, |
| 4684 | 5139 | }; |
| 4685 | 5140 | defer close(fd); |