| ... | ... | @@ -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,17 @@ pub const LOG = struct { |
| 210 | 219 | pub const DEBUG = 7; |
| 211 | 220 | }; |
| 212 | 221 | |
| 222 | /// An fd-relative file path |
| 223 | /// |
| 224 | /// This is currently only used for WASI-specific functionality, but the concept |
| 225 | /// is the same as the dirfd/pathname pairs in the `*at(...)` POSIX functions. |
| 226 | pub const RelativePathWasi = struct { |
| 227 | /// Handle to directory |
| 228 | dir_fd: fd_t, |
| 229 | /// Path to resource within `dir_fd`. |
| 230 | relative_path: []const u8, |
| 231 | }; |
| 232 | |
| 213 | 233 | pub const socket_t = if (builtin.os.tag == .windows) windows.ws2_32.SOCKET else fd_t; |
| 214 | 234 | |
| 215 | 235 | /// See also `getenv`. Populated by startup code before main(). |
| ... | ... | @@ -1239,6 +1259,9 @@ pub fn pwritev(fd: fd_t, iov: []const iovec_const, offset: u64) PWriteError!usiz |
| 1239 | 1259 | } |
| 1240 | 1260 | |
| 1241 | 1261 | pub const OpenError = error{ |
| 1262 | /// In WASI, this error may occur when the provided file handle is invalid. |
| 1263 | InvalidHandle, |
| 1264 | |
| 1242 | 1265 | /// In WASI, this error may occur when the file descriptor does |
| 1243 | 1266 | /// not hold the required rights to open a new resource relative to it. |
| 1244 | 1267 | AccessDenied, |
| ... | ... | @@ -1300,6 +1323,8 @@ pub fn open(file_path: []const u8, flags: u32, perm: mode_t) OpenError!fd_t { |
| 1300 | 1323 | if (builtin.os.tag == .windows) { |
| 1301 | 1324 | const file_path_w = try windows.sliceToPrefixedFileW(file_path); |
| 1302 | 1325 | return openW(file_path_w.span(), flags, perm); |
| 1326 | } else if (builtin.os.tag == .wasi and !builtin.link_libc) { |
| 1327 | return openat(wasi.AT.FDCWD, file_path, flags, perm); |
| 1303 | 1328 | } |
| 1304 | 1329 | const file_path_c = try toPosixPath(file_path); |
| 1305 | 1330 | return openZ(&file_path_c, flags, perm); |
| ... | ... | @@ -1311,6 +1336,8 @@ pub fn openZ(file_path: [*:0]const u8, flags: u32, perm: mode_t) OpenError!fd_t |
| 1311 | 1336 | if (builtin.os.tag == .windows) { |
| 1312 | 1337 | const file_path_w = try windows.cStrToPrefixedFileW(file_path); |
| 1313 | 1338 | return openW(file_path_w.span(), flags, perm); |
| 1339 | } else if (builtin.os.tag == .wasi and !builtin.link_libc) { |
| 1340 | return open(mem.sliceTo(file_path, 0), flags, perm); |
| 1314 | 1341 | } |
| 1315 | 1342 | |
| 1316 | 1343 | const open_sym = if (builtin.os.tag == .linux and builtin.link_libc) |
| ... | ... | @@ -1347,7 +1374,7 @@ pub fn openZ(file_path: [*:0]const u8, flags: u32, perm: mode_t) OpenError!fd_t |
| 1347 | 1374 | } |
| 1348 | 1375 | } |
| 1349 | 1376 | |
| 1350 | | fn openOptionsFromFlags(flags: u32) windows.OpenFileOptions { |
| 1377 | fn openOptionsFromFlagsWindows(flags: u32) windows.OpenFileOptions { |
| 1351 | 1378 | const w = windows; |
| 1352 | 1379 | |
| 1353 | 1380 | var access_mask: w.ULONG = w.READ_CONTROL | w.FILE_WRITE_ATTRIBUTES | w.SYNCHRONIZE; |
| ... | ... | @@ -1387,7 +1414,7 @@ fn openOptionsFromFlags(flags: u32) windows.OpenFileOptions { |
| 1387 | 1414 | /// or makes use of perm argument. |
| 1388 | 1415 | pub fn openW(file_path_w: []const u16, flags: u32, perm: mode_t) OpenError!fd_t { |
| 1389 | 1416 | _ = perm; |
| 1390 | | var options = openOptionsFromFlags(flags); |
| 1417 | var options = openOptionsFromFlagsWindows(flags); |
| 1391 | 1418 | options.dir = std.fs.cwd().fd; |
| 1392 | 1419 | return windows.OpenFile(file_path_w, options) catch |err| switch (err) { |
| 1393 | 1420 | error.WouldBlock => unreachable, |
| ... | ... | @@ -1396,21 +1423,204 @@ pub fn openW(file_path_w: []const u16, flags: u32, perm: mode_t) OpenError!fd_t |
| 1396 | 1423 | }; |
| 1397 | 1424 | } |
| 1398 | 1425 | |
| 1426 | var wasi_cwd = if (builtin.os.tag == .wasi and !builtin.link_libc) struct { |
| 1427 | // List of available Preopens |
| 1428 | preopens: ?PreopenList = null, |
| 1429 | // Memory buffer for storing the relative portion of the CWD |
| 1430 | path_buffer: [MAX_PATH_BYTES]u8 = undefined, |
| 1431 | // Current Working Directory, stored as an fd_t and a relative path |
| 1432 | cwd: ?RelativePathWasi = null, |
| 1433 | // Preopen associated with `cwd`, if any |
| 1434 | cwd_preopen: ?Preopen = null, |
| 1435 | }{} else undefined; |
| 1436 | |
| 1437 | /// Initialize the available Preopen list on WASI and set the CWD to `cwd_init`. |
| 1438 | /// Note that `cwd_init` corresponds to a Preopen directory, not necessarily |
| 1439 | /// a POSIX path. For example, "." matches a Preopen provided with `--dir=.` |
| 1440 | /// |
| 1441 | /// This must be called before using any relative or absolute paths with `std.os` |
| 1442 | /// functions, if you are on WASI without linking libc. |
| 1443 | /// |
| 1444 | /// `alloc` must not be a temporary or leak-detecting allocator, since `std.os` |
| 1445 | /// retains ownership of allocations internally and may never call free(). |
| 1446 | pub fn initPreopensWasi(alloc: Allocator, cwd_init: ?[]const u8) !void { |
| 1447 | if (builtin.os.tag == .wasi) { |
| 1448 | if (!builtin.link_libc) { |
| 1449 | if (wasi_cwd.preopens == null) { |
| 1450 | var preopen_list = PreopenList.init(alloc); |
| 1451 | try preopen_list.populate(); |
| 1452 | wasi_cwd.preopens = preopen_list; |
| 1453 | } |
| 1454 | if (cwd_init) |cwd| { |
| 1455 | const preopen = wasi_cwd.preopens.?.findContaining(.{ .Dir = cwd }); |
| 1456 | if (preopen) |po| { |
| 1457 | wasi_cwd.cwd_preopen = po.base; |
| 1458 | wasi_cwd.cwd = RelativePathWasi{ |
| 1459 | .dir_fd = po.base.fd, |
| 1460 | .relative_path = po.relative_path, |
| 1461 | }; |
| 1462 | } else { |
| 1463 | // No matching preopen found |
| 1464 | return error.FileNotFound; |
| 1465 | } |
| 1466 | } |
| 1467 | } else { |
| 1468 | if (cwd_init) |cwd| try chdir(cwd); |
| 1469 | } |
| 1470 | } |
| 1471 | } |
| 1472 | |
| 1473 | /// Resolve a relative or absolute path to an handle (`fd_t`) and a relative subpath. |
| 1474 | /// |
| 1475 | /// For absolute paths, this automatically searches among available Preopens to find |
| 1476 | /// a match. For relative paths, it uses the "emulated" CWD. |
| 1477 | pub fn resolvePathWasi(path: []const u8, out_buffer: *[MAX_PATH_BYTES]u8) !RelativePathWasi { |
| 1478 | // Note: Due to WASI's "sandboxed" file handles, operations with this RelativePathWasi |
| 1479 | // will fail if the relative path navigates outside of `dir_fd` using ".." |
| 1480 | return resolvePathAndGetWasiPreopen(path, null, out_buffer); |
| 1481 | } |
| 1482 | |
| 1483 | fn resolvePathAndGetWasiPreopen(path: []const u8, preopen: ?*?Preopen, out_buffer: *[MAX_PATH_BYTES]u8) !RelativePathWasi { |
| 1484 | var allocator = std.heap.FixedBufferAllocator.init(out_buffer); |
| 1485 | var alloc = allocator.allocator(); |
| 1486 | |
| 1487 | if (fs.path.isAbsolute(path) or wasi_cwd.cwd == null) { |
| 1488 | if (wasi_cwd.preopens == null) @panic("On WASI, `initPreopensWasi` must be called to initialize preopens " ++ |
| 1489 | "before using any CWD-relative or absolute paths.\n"); |
| 1490 | |
| 1491 | if (mem.startsWith(u8, path, "/preopens/fd/")) { |
| 1492 | // "/preopens/fd/<N>" is a special prefix, which refers to a Preopen directly by fd |
| 1493 | const fd_start = "/preopens/fd/".len; |
| 1494 | const fd_end = mem.indexOfScalarPos(u8, path, fd_start, '/') orelse path.len; |
| 1495 | const fd = std.fmt.parseUnsigned(fd_t, path[fd_start..fd_end], 10) catch unreachable; |
| 1496 | const rel_path = if (path.len > fd_end + 1) path[fd_end + 1 ..] else "."; |
| 1497 | |
| 1498 | if (preopen) |p| p.* = wasi_cwd.preopens.?.findByFd(fd); |
| 1499 | return RelativePathWasi{ |
| 1500 | .dir_fd = fd, |
| 1501 | .relative_path = alloc.dupe(u8, rel_path) catch return error.NameTooLong, |
| 1502 | }; |
| 1503 | } |
| 1504 | |
| 1505 | // For any other absolute path, we need to lookup a containing Preopen |
| 1506 | const abs_path = fs.path.resolve(alloc, &.{ "/", path }) catch return error.NameTooLong; |
| 1507 | const preopen_uri = wasi_cwd.preopens.?.findContaining(.{ .Dir = abs_path }); |
| 1508 | |
| 1509 | if (preopen_uri) |po| { |
| 1510 | if (preopen) |p| p.* = po.base; |
| 1511 | return RelativePathWasi{ |
| 1512 | .dir_fd = po.base.fd, |
| 1513 | .relative_path = po.relative_path, |
| 1514 | }; |
| 1515 | } else { |
| 1516 | // No matching preopen found |
| 1517 | return error.AccessDenied; |
| 1518 | } |
| 1519 | } else { |
| 1520 | const cwd = wasi_cwd.cwd.?; |
| 1521 | |
| 1522 | // If the path is empty or "." or "./", return CWD |
| 1523 | if (std.mem.eql(u8, path, ".") or std.mem.eql(u8, path, "./")) { |
| 1524 | return cwd; |
| 1525 | } |
| 1526 | |
| 1527 | // First resolve a combined path, where the "/" corresponds to `cwd.dir_fd` |
| 1528 | // not the true filesystem root |
| 1529 | const paths = &.{ "/", cwd.relative_path, path }; |
| 1530 | const resolved_path = fs.path.resolve(alloc, paths) catch return error.NameTooLong; |
| 1531 | |
| 1532 | // Strip off the fake root to get the relative path w.r.t. `cwd.dir_fd` |
| 1533 | const resolved_relative_path = resolved_path[1..]; |
| 1534 | |
| 1535 | if (preopen) |p| p.* = wasi_cwd.cwd_preopen; |
| 1536 | return RelativePathWasi{ |
| 1537 | .dir_fd = cwd.dir_fd, |
| 1538 | .relative_path = resolved_relative_path, |
| 1539 | }; |
| 1540 | } |
| 1541 | } |
| 1542 | |
| 1399 | 1543 | /// Open and possibly create a file. Keeps trying if it gets interrupted. |
| 1400 | 1544 | /// `file_path` is relative to the open directory handle `dir_fd`. |
| 1401 | 1545 | /// See also `openatZ`. |
| 1402 | 1546 | 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 | 1547 | if (builtin.os.tag == .windows) { |
| 1407 | 1548 | const file_path_w = try windows.sliceToPrefixedFileW(file_path); |
| 1408 | 1549 | return openatW(dir_fd, file_path_w.span(), flags, mode); |
| 1550 | } else if (builtin.os.tag == .wasi and !builtin.link_libc) { |
| 1551 | // `mode` is ignored on WASI, which does not support unix-style file permissions |
| 1552 | const fd = if (dir_fd == wasi.AT.FDCWD or fs.path.isAbsolute(file_path)) blk: { |
| 1553 | // Resolve absolute or CWD-relative paths to a path within a Preopen |
| 1554 | var path_buf: [MAX_PATH_BYTES]u8 = undefined; |
| 1555 | const path = try resolvePathWasi(file_path, &path_buf); |
| 1556 | |
| 1557 | const opts = try openOptionsFromFlagsWasi(path.dir_fd, flags); |
| 1558 | 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); |
| 1559 | } else blk: { |
| 1560 | const opts = try openOptionsFromFlagsWasi(dir_fd, flags); |
| 1561 | break :blk try openatWasi(dir_fd, file_path, opts.lookup_flags, opts.oflags, opts.fs_flags, opts.fs_rights_base, opts.fs_rights_inheriting); |
| 1562 | }; |
| 1563 | errdefer close(fd); |
| 1564 | |
| 1565 | const info = try fstat(fd); |
| 1566 | if (flags & O.WRONLY != 0 and info.filetype == .DIRECTORY) |
| 1567 | return error.IsDir; |
| 1568 | |
| 1569 | return fd; |
| 1409 | 1570 | } |
| 1410 | 1571 | const file_path_c = try toPosixPath(file_path); |
| 1411 | 1572 | return openatZ(dir_fd, &file_path_c, flags, mode); |
| 1412 | 1573 | } |
| 1413 | 1574 | |
| 1575 | /// A struct to contain all lookup/rights flags accepted by `wasi.path_open` |
| 1576 | const WasiOpenOptions = struct { |
| 1577 | oflags: wasi.oflags_t, |
| 1578 | lookup_flags: wasi.lookupflags_t, |
| 1579 | fs_rights_base: wasi.rights_t, |
| 1580 | fs_rights_inheriting: wasi.rights_t, |
| 1581 | fs_flags: wasi.fdflags_t, |
| 1582 | }; |
| 1583 | |
| 1584 | /// Compute rights + flags corresponding to the provided POSIX access mode. |
| 1585 | fn openOptionsFromFlagsWasi(fd: fd_t, oflag: u32) OpenError!WasiOpenOptions { |
| 1586 | const w = std.os.wasi; |
| 1587 | |
| 1588 | // First, discover the rights that we can derive from `fd` |
| 1589 | var fsb_cur: wasi.fdstat_t = undefined; |
| 1590 | _ = switch (w.fd_fdstat_get(fd, &fsb_cur)) { |
| 1591 | .SUCCESS => .{}, |
| 1592 | .BADF => return error.InvalidHandle, |
| 1593 | else => |err| return unexpectedErrno(err), |
| 1594 | }; |
| 1595 | |
| 1596 | // Next, calculate the read/write rights to request, depending on the |
| 1597 | // provided POSIX access mode |
| 1598 | var rights: w.rights_t = 0; |
| 1599 | if (oflag & O.RDONLY != 0) { |
| 1600 | rights |= w.RIGHT.FD_READ | w.RIGHT.FD_READDIR; |
| 1601 | } |
| 1602 | if (oflag & O.WRONLY != 0) { |
| 1603 | rights |= w.RIGHT.FD_DATASYNC | w.RIGHT.FD_WRITE | |
| 1604 | w.RIGHT.FD_ALLOCATE | w.RIGHT.FD_FILESTAT_SET_SIZE; |
| 1605 | } |
| 1606 | |
| 1607 | // Request all other rights unconditionally |
| 1608 | rights |= ~(w.RIGHT.FD_DATASYNC | w.RIGHT.FD_READ | |
| 1609 | w.RIGHT.FD_WRITE | w.RIGHT.FD_ALLOCATE | |
| 1610 | w.RIGHT.FD_READDIR | w.RIGHT.FD_FILESTAT_SET_SIZE); |
| 1611 | |
| 1612 | // But only take rights that we can actually inherit |
| 1613 | rights &= fsb_cur.fs_rights_inheriting; |
| 1614 | |
| 1615 | return WasiOpenOptions{ |
| 1616 | .oflags = @truncate(w.oflags_t, (oflag >> 12)) & 0xfff, |
| 1617 | .lookup_flags = if (oflag & O.NOFOLLOW == 0) w.LOOKUP_SYMLINK_FOLLOW else 0, |
| 1618 | .fs_rights_base = rights, |
| 1619 | .fs_rights_inheriting = fsb_cur.fs_rights_inheriting, |
| 1620 | .fs_flags = @truncate(w.fdflags_t, oflag & 0xfff), |
| 1621 | }; |
| 1622 | } |
| 1623 | |
| 1414 | 1624 | /// Open and possibly create a file in WASI. |
| 1415 | 1625 | 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 | 1626 | while (true) { |
| ... | ... | @@ -1450,6 +1660,8 @@ pub fn openatZ(dir_fd: fd_t, file_path: [*:0]const u8, flags: u32, mode: mode_t) |
| 1450 | 1660 | if (builtin.os.tag == .windows) { |
| 1451 | 1661 | const file_path_w = try windows.cStrToPrefixedFileW(file_path); |
| 1452 | 1662 | return openatW(dir_fd, file_path_w.span(), flags, mode); |
| 1663 | } else if (builtin.os.tag == .wasi and !builtin.link_libc) { |
| 1664 | return openat(dir_fd, mem.sliceTo(file_path, 0), flags, mode); |
| 1453 | 1665 | } |
| 1454 | 1666 | |
| 1455 | 1667 | const openat_sym = if (builtin.os.tag == .linux and builtin.link_libc) |
| ... | ... | @@ -1496,7 +1708,7 @@ pub fn openatZ(dir_fd: fd_t, file_path: [*:0]const u8, flags: u32, mode: mode_t) |
| 1496 | 1708 | /// or makes use of perm argument. |
| 1497 | 1709 | pub fn openatW(dir_fd: fd_t, file_path_w: []const u16, flags: u32, mode: mode_t) OpenError!fd_t { |
| 1498 | 1710 | _ = mode; |
| 1499 | | var options = openOptionsFromFlags(flags); |
| 1711 | var options = openOptionsFromFlagsWindows(flags); |
| 1500 | 1712 | options.dir = dir_fd; |
| 1501 | 1713 | return windows.OpenFile(file_path_w, options) catch |err| switch (err) { |
| 1502 | 1714 | error.WouldBlock => unreachable, |
| ... | ... | @@ -1764,9 +1976,15 @@ pub const GetCwdError = error{ |
| 1764 | 1976 | pub fn getcwd(out_buffer: []u8) GetCwdError![]u8 { |
| 1765 | 1977 | if (builtin.os.tag == .windows) { |
| 1766 | 1978 | 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"); |
| 1979 | } else if (builtin.os.tag == .wasi and !builtin.link_libc) { |
| 1980 | var buf: [MAX_PATH_BYTES]u8 = undefined; |
| 1981 | const path = realpathWasi(".", &buf) catch |err| switch (err) { |
| 1982 | error.NameTooLong => return error.NameTooLong, |
| 1983 | error.InvalidHandle => return error.CurrentWorkingDirectoryUnlinked, |
| 1984 | }; |
| 1985 | if (out_buffer.len < path.len) return error.NameTooLong; |
| 1986 | std.mem.copy(u8, out_buffer, path); |
| 1987 | return out_buffer[0..path.len]; |
| 1770 | 1988 | } |
| 1771 | 1989 | |
| 1772 | 1990 | const err = if (builtin.link_libc) blk: { |
| ... | ... | @@ -1809,11 +2027,10 @@ pub const SymLinkError = error{ |
| 1809 | 2027 | /// If `sym_link_path` exists, it will not be overwritten. |
| 1810 | 2028 | /// See also `symlinkZ. |
| 1811 | 2029 | 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 | 2030 | if (builtin.os.tag == .windows) { |
| 1816 | 2031 | @compileError("symlink is not supported on Windows; use std.os.windows.CreateSymbolicLink instead"); |
| 2032 | } else if (builtin.os.tag == .wasi and !builtin.link_libc) { |
| 2033 | return symlinkat(target_path, wasi.AT.FDCWD, sym_link_path); |
| 1817 | 2034 | } |
| 1818 | 2035 | const target_path_c = try toPosixPath(target_path); |
| 1819 | 2036 | const sym_link_path_c = try toPosixPath(sym_link_path); |
| ... | ... | @@ -1825,6 +2042,8 @@ pub fn symlink(target_path: []const u8, sym_link_path: []const u8) SymLinkError! |
| 1825 | 2042 | pub fn symlinkZ(target_path: [*:0]const u8, sym_link_path: [*:0]const u8) SymLinkError!void { |
| 1826 | 2043 | if (builtin.os.tag == .windows) { |
| 1827 | 2044 | @compileError("symlink is not supported on Windows; use std.os.windows.CreateSymbolicLink instead"); |
| 2045 | } else if (builtin.os.tag == .wasi and !builtin.link_libc) { |
| 2046 | return symlink(mem.sliceTo(target_path, 0), mem.sliceTo(sym_link_path, 0)); |
| 1828 | 2047 | } |
| 1829 | 2048 | switch (errno(system.symlink(target_path, sym_link_path))) { |
| 1830 | 2049 | .SUCCESS => return, |
| ... | ... | @@ -1853,11 +2072,16 @@ pub fn symlinkZ(target_path: [*:0]const u8, sym_link_path: [*:0]const u8) SymLin |
| 1853 | 2072 | /// If `sym_link_path` exists, it will not be overwritten. |
| 1854 | 2073 | /// See also `symlinkatWasi`, `symlinkatZ` and `symlinkatW`. |
| 1855 | 2074 | 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 | 2075 | if (builtin.os.tag == .windows) { |
| 1860 | 2076 | @compileError("symlinkat is not supported on Windows; use std.os.windows.CreateSymbolicLink instead"); |
| 2077 | } else if (builtin.os.tag == .wasi and !builtin.link_libc) { |
| 2078 | if (newdirfd == wasi.AT.FDCWD or fs.path.isAbsolute(target_path)) { |
| 2079 | // Resolve absolute or CWD-relative paths to a path within a Preopen |
| 2080 | var path_buf: [MAX_PATH_BYTES]u8 = undefined; |
| 2081 | const path = try resolvePathWasi(sym_link_path, &path_buf); |
| 2082 | return symlinkatWasi(target_path, path.dir_fd, path.relative_path); |
| 2083 | } |
| 2084 | return symlinkatWasi(target_path, newdirfd, sym_link_path); |
| 1861 | 2085 | } |
| 1862 | 2086 | const target_path_c = try toPosixPath(target_path); |
| 1863 | 2087 | const sym_link_path_c = try toPosixPath(sym_link_path); |
| ... | ... | @@ -1893,6 +2117,8 @@ pub fn symlinkatWasi(target_path: []const u8, newdirfd: fd_t, sym_link_path: []c |
| 1893 | 2117 | pub fn symlinkatZ(target_path: [*:0]const u8, newdirfd: fd_t, sym_link_path: [*:0]const u8) SymLinkError!void { |
| 1894 | 2118 | if (builtin.os.tag == .windows) { |
| 1895 | 2119 | @compileError("symlinkat is not supported on Windows; use std.os.windows.CreateSymbolicLink instead"); |
| 2120 | } else if (builtin.os.tag == .wasi and !builtin.link_libc) { |
| 2121 | return symlinkat(mem.sliceTo(target_path, 0), newdirfd, mem.sliceTo(sym_link_path, 0)); |
| 1896 | 2122 | } |
| 1897 | 2123 | switch (errno(system.symlinkat(target_path, newdirfd, sym_link_path))) { |
| 1898 | 2124 | .SUCCESS => return, |
| ... | ... | @@ -1930,6 +2156,9 @@ pub const LinkError = UnexpectedError || error{ |
| 1930 | 2156 | }; |
| 1931 | 2157 | |
| 1932 | 2158 | pub fn linkZ(oldpath: [*:0]const u8, newpath: [*:0]const u8, flags: i32) LinkError!void { |
| 2159 | if (builtin.os.tag == .wasi and !builtin.link_libc) { |
| 2160 | return link(mem.sliceTo(oldpath, 0), mem.sliceTo(newpath, 0), flags); |
| 2161 | } |
| 1933 | 2162 | switch (errno(system.link(oldpath, newpath, flags))) { |
| 1934 | 2163 | .SUCCESS => return, |
| 1935 | 2164 | .ACCES => return error.AccessDenied, |
| ... | ... | @@ -1952,6 +2181,12 @@ pub fn linkZ(oldpath: [*:0]const u8, newpath: [*:0]const u8, flags: i32) LinkErr |
| 1952 | 2181 | } |
| 1953 | 2182 | |
| 1954 | 2183 | pub fn link(oldpath: []const u8, newpath: []const u8, flags: i32) LinkError!void { |
| 2184 | if (builtin.os.tag == .wasi and !builtin.link_libc) { |
| 2185 | return linkat(wasi.AT.FDCWD, oldpath, wasi.AT.FDCWD, newpath, flags) catch |err| switch (err) { |
| 2186 | error.NotDir => unreachable, // link() does not support directories |
| 2187 | else => |e| return e, |
| 2188 | }; |
| 2189 | } |
| 1955 | 2190 | const old = try toPosixPath(oldpath); |
| 1956 | 2191 | const new = try toPosixPath(newpath); |
| 1957 | 2192 | return try linkZ(&old, &new, flags); |
| ... | ... | @@ -1966,6 +2201,9 @@ pub fn linkatZ( |
| 1966 | 2201 | newpath: [*:0]const u8, |
| 1967 | 2202 | flags: i32, |
| 1968 | 2203 | ) LinkatError!void { |
| 2204 | if (builtin.os.tag == .wasi and !builtin.link_libc) { |
| 2205 | return linkat(olddir, mem.sliceTo(oldpath, 0), newdir, mem.sliceTo(newpath, 0), flags); |
| 2206 | } |
| 1969 | 2207 | switch (errno(system.linkat(olddir, oldpath, newdir, newpath, flags))) { |
| 1970 | 2208 | .SUCCESS => return, |
| 1971 | 2209 | .ACCES => return error.AccessDenied, |
| ... | ... | @@ -1995,11 +2233,62 @@ pub fn linkat( |
| 1995 | 2233 | newpath: []const u8, |
| 1996 | 2234 | flags: i32, |
| 1997 | 2235 | ) LinkatError!void { |
| 2236 | if (builtin.os.tag == .wasi and !builtin.link_libc) { |
| 2237 | var resolve_olddir: bool = (olddir == wasi.AT.FDCWD or fs.path.isAbsolute(oldpath)); |
| 2238 | var resolve_newdir: bool = (newdir == wasi.AT.FDCWD or fs.path.isAbsolute(newpath)); |
| 2239 | |
| 2240 | var old: RelativePathWasi = .{ .dir_fd = olddir, .relative_path = oldpath }; |
| 2241 | var new: RelativePathWasi = .{ .dir_fd = newdir, .relative_path = newpath }; |
| 2242 | |
| 2243 | // Resolve absolute or CWD-relative paths to a path within a Preopen |
| 2244 | if (resolve_olddir or resolve_newdir) { |
| 2245 | var buf_old: [MAX_PATH_BYTES]u8 = undefined; |
| 2246 | var buf_new: [MAX_PATH_BYTES]u8 = undefined; |
| 2247 | |
| 2248 | if (resolve_olddir) |
| 2249 | old = try resolvePathWasi(oldpath, &buf_old); |
| 2250 | |
| 2251 | if (resolve_newdir) |
| 2252 | new = try resolvePathWasi(newpath, &buf_new); |
| 2253 | |
| 2254 | return linkatWasi(old, new, flags); |
| 2255 | } |
| 2256 | return linkatWasi(old, new, flags); |
| 2257 | } |
| 1998 | 2258 | const old = try toPosixPath(oldpath); |
| 1999 | 2259 | const new = try toPosixPath(newpath); |
| 2000 | 2260 | return try linkatZ(olddir, &old, newdir, &new, flags); |
| 2001 | 2261 | } |
| 2002 | 2262 | |
| 2263 | /// WASI-only. The same as `linkat` but targeting WASI. |
| 2264 | /// See also `linkat`. |
| 2265 | pub fn linkatWasi(old: RelativePathWasi, new: RelativePathWasi, flags: i32) LinkatError!void { |
| 2266 | var old_flags: wasi.lookupflags_t = 0; |
| 2267 | // TODO: Why is this not defined in wasi-libc? |
| 2268 | if (flags & linux.AT.SYMLINK_FOLLOW != 0) old_flags |= wasi.LOOKUP_SYMLINK_FOLLOW; |
| 2269 | |
| 2270 | 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)) { |
| 2271 | .SUCCESS => return, |
| 2272 | .ACCES => return error.AccessDenied, |
| 2273 | .DQUOT => return error.DiskQuota, |
| 2274 | .EXIST => return error.PathAlreadyExists, |
| 2275 | .FAULT => unreachable, |
| 2276 | .IO => return error.FileSystem, |
| 2277 | .LOOP => return error.SymLinkLoop, |
| 2278 | .MLINK => return error.LinkQuotaExceeded, |
| 2279 | .NAMETOOLONG => return error.NameTooLong, |
| 2280 | .NOENT => return error.FileNotFound, |
| 2281 | .NOMEM => return error.SystemResources, |
| 2282 | .NOSPC => return error.NoSpaceLeft, |
| 2283 | .NOTDIR => return error.NotDir, |
| 2284 | .PERM => return error.AccessDenied, |
| 2285 | .ROFS => return error.ReadOnlyFileSystem, |
| 2286 | .XDEV => return error.NotSameFileSystem, |
| 2287 | .INVAL => unreachable, |
| 2288 | else => |err| return unexpectedErrno(err), |
| 2289 | } |
| 2290 | } |
| 2291 | |
| 2003 | 2292 | pub const UnlinkError = error{ |
| 2004 | 2293 | FileNotFound, |
| 2005 | 2294 | |
| ... | ... | @@ -2027,7 +2316,10 @@ pub const UnlinkError = error{ |
| 2027 | 2316 | /// See also `unlinkZ`. |
| 2028 | 2317 | pub fn unlink(file_path: []const u8) UnlinkError!void { |
| 2029 | 2318 | if (builtin.os.tag == .wasi and !builtin.link_libc) { |
| 2030 | | @compileError("unlink is not supported in WASI; use unlinkat instead"); |
| 2319 | return unlinkat(wasi.AT.FDCWD, file_path, 0) catch |err| switch (err) { |
| 2320 | error.DirNotEmpty => unreachable, // only occurs when targeting directories |
| 2321 | else => |e| return e, |
| 2322 | }; |
| 2031 | 2323 | } else if (builtin.os.tag == .windows) { |
| 2032 | 2324 | const file_path_w = try windows.sliceToPrefixedFileW(file_path); |
| 2033 | 2325 | return unlinkW(file_path_w.span()); |
| ... | ... | @@ -2042,6 +2334,8 @@ pub fn unlinkZ(file_path: [*:0]const u8) UnlinkError!void { |
| 2042 | 2334 | if (builtin.os.tag == .windows) { |
| 2043 | 2335 | const file_path_w = try windows.cStrToPrefixedFileW(file_path); |
| 2044 | 2336 | return unlinkW(file_path_w.span()); |
| 2337 | } else if (builtin.os.tag == .wasi and !builtin.link_libc) { |
| 2338 | return unlink(mem.sliceTo(file_path, 0)); |
| 2045 | 2339 | } |
| 2046 | 2340 | switch (errno(system.unlink(file_path))) { |
| 2047 | 2341 | .SUCCESS => return, |
| ... | ... | @@ -2079,6 +2373,12 @@ pub fn unlinkat(dirfd: fd_t, file_path: []const u8, flags: u32) UnlinkatError!vo |
| 2079 | 2373 | const file_path_w = try windows.sliceToPrefixedFileW(file_path); |
| 2080 | 2374 | return unlinkatW(dirfd, file_path_w.span(), flags); |
| 2081 | 2375 | } else if (builtin.os.tag == .wasi and !builtin.link_libc) { |
| 2376 | if (dirfd == wasi.AT.FDCWD or fs.path.isAbsolute(file_path)) { |
| 2377 | // Resolve absolute or CWD-relative paths to a path within a Preopen |
| 2378 | var path_buf: [MAX_PATH_BYTES]u8 = undefined; |
| 2379 | const path = try resolvePathWasi(file_path, &path_buf); |
| 2380 | return unlinkatWasi(path.dir_fd, path.relative_path, flags); |
| 2381 | } |
| 2082 | 2382 | return unlinkatWasi(dirfd, file_path, flags); |
| 2083 | 2383 | } else { |
| 2084 | 2384 | const file_path_c = try toPosixPath(file_path); |
| ... | ... | @@ -2123,6 +2423,8 @@ pub fn unlinkatZ(dirfd: fd_t, file_path_c: [*:0]const u8, flags: u32) UnlinkatEr |
| 2123 | 2423 | if (builtin.os.tag == .windows) { |
| 2124 | 2424 | const file_path_w = try windows.cStrToPrefixedFileW(file_path_c); |
| 2125 | 2425 | return unlinkatW(dirfd, file_path_w.span(), flags); |
| 2426 | } else if (builtin.os.tag == .wasi and !builtin.link_libc) { |
| 2427 | return unlinkat(dirfd, mem.sliceTo(file_path_c, 0), flags); |
| 2126 | 2428 | } |
| 2127 | 2429 | switch (errno(system.unlinkat(dirfd, file_path_c, flags))) { |
| 2128 | 2430 | .SUCCESS => return, |
| ... | ... | @@ -2181,7 +2483,7 @@ pub const RenameError = error{ |
| 2181 | 2483 | /// Change the name or location of a file. |
| 2182 | 2484 | pub fn rename(old_path: []const u8, new_path: []const u8) RenameError!void { |
| 2183 | 2485 | if (builtin.os.tag == .wasi and !builtin.link_libc) { |
| 2184 | | @compileError("rename is not supported in WASI; use renameat instead"); |
| 2486 | return renameat(wasi.AT.FDCWD, old_path, wasi.AT.FDCWD, new_path); |
| 2185 | 2487 | } else if (builtin.os.tag == .windows) { |
| 2186 | 2488 | const old_path_w = try windows.sliceToPrefixedFileW(old_path); |
| 2187 | 2489 | const new_path_w = try windows.sliceToPrefixedFileW(new_path); |
| ... | ... | @@ -2199,6 +2501,8 @@ pub fn renameZ(old_path: [*:0]const u8, new_path: [*:0]const u8) RenameError!voi |
| 2199 | 2501 | const old_path_w = try windows.cStrToPrefixedFileW(old_path); |
| 2200 | 2502 | const new_path_w = try windows.cStrToPrefixedFileW(new_path); |
| 2201 | 2503 | return renameW(old_path_w.span().ptr, new_path_w.span().ptr); |
| 2504 | } else if (builtin.os.tag == .wasi and !builtin.link_libc) { |
| 2505 | return rename(mem.sliceTo(old_path, 0), mem.sliceTo(new_path, 0)); |
| 2202 | 2506 | } |
| 2203 | 2507 | switch (errno(system.rename(old_path, new_path))) { |
| 2204 | 2508 | .SUCCESS => return, |
| ... | ... | @@ -2243,7 +2547,25 @@ pub fn renameat( |
| 2243 | 2547 | const new_path_w = try windows.sliceToPrefixedFileW(new_path); |
| 2244 | 2548 | return renameatW(old_dir_fd, old_path_w.span(), new_dir_fd, new_path_w.span(), windows.TRUE); |
| 2245 | 2549 | } else if (builtin.os.tag == .wasi and !builtin.link_libc) { |
| 2246 | | return renameatWasi(old_dir_fd, old_path, new_dir_fd, new_path); |
| 2550 | var resolve_old: bool = (old_dir_fd == wasi.AT.FDCWD or fs.path.isAbsolute(old_path)); |
| 2551 | var resolve_new: bool = (new_dir_fd == wasi.AT.FDCWD or fs.path.isAbsolute(new_path)); |
| 2552 | |
| 2553 | var old: RelativePathWasi = .{ .dir_fd = old_dir_fd, .relative_path = old_path }; |
| 2554 | var new: RelativePathWasi = .{ .dir_fd = new_dir_fd, .relative_path = new_path }; |
| 2555 | |
| 2556 | // Resolve absolute or CWD-relative paths to a path within a Preopen |
| 2557 | if (resolve_old or resolve_new) { |
| 2558 | var buf_old: [MAX_PATH_BYTES]u8 = undefined; |
| 2559 | var buf_new: [MAX_PATH_BYTES]u8 = undefined; |
| 2560 | |
| 2561 | if (resolve_old) |
| 2562 | old = try resolvePathWasi(old_path, &buf_old); |
| 2563 | if (resolve_new) |
| 2564 | new = try resolvePathWasi(new_path, &buf_new); |
| 2565 | |
| 2566 | return renameatWasi(old, new); |
| 2567 | } |
| 2568 | return renameatWasi(old, new); |
| 2247 | 2569 | } else { |
| 2248 | 2570 | const old_path_c = try toPosixPath(old_path); |
| 2249 | 2571 | const new_path_c = try toPosixPath(new_path); |
| ... | ... | @@ -2253,8 +2575,8 @@ pub fn renameat( |
| 2253 | 2575 | |
| 2254 | 2576 | /// WASI-only. Same as `renameat` expect targeting WASI. |
| 2255 | 2577 | /// 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)) { |
| 2578 | pub fn renameatWasi(old: RelativePathWasi, new: RelativePathWasi) RenameError!void { |
| 2579 | 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 | 2580 | .SUCCESS => return, |
| 2259 | 2581 | .ACCES => return error.AccessDenied, |
| 2260 | 2582 | .PERM => return error.AccessDenied, |
| ... | ... | @@ -2290,6 +2612,8 @@ pub fn renameatZ( |
| 2290 | 2612 | const old_path_w = try windows.cStrToPrefixedFileW(old_path); |
| 2291 | 2613 | const new_path_w = try windows.cStrToPrefixedFileW(new_path); |
| 2292 | 2614 | return renameatW(old_dir_fd, old_path_w.span(), new_dir_fd, new_path_w.span(), windows.TRUE); |
| 2615 | } else if (builtin.os.tag == .wasi and !builtin.link_libc) { |
| 2616 | return renameat(old_dir_fd, mem.sliceTo(old_path, 0), new_dir_fd, mem.sliceTo(new_path, 0)); |
| 2293 | 2617 | } |
| 2294 | 2618 | |
| 2295 | 2619 | switch (errno(system.renameat(old_dir_fd, old_path, new_dir_fd, new_path))) { |
| ... | ... | @@ -2380,6 +2704,12 @@ pub fn mkdirat(dir_fd: fd_t, sub_dir_path: []const u8, mode: u32) MakeDirError!v |
| 2380 | 2704 | const sub_dir_path_w = try windows.sliceToPrefixedFileW(sub_dir_path); |
| 2381 | 2705 | return mkdiratW(dir_fd, sub_dir_path_w.span(), mode); |
| 2382 | 2706 | } else if (builtin.os.tag == .wasi and !builtin.link_libc) { |
| 2707 | if (dir_fd == wasi.AT.FDCWD or fs.path.isAbsolute(sub_dir_path)) { |
| 2708 | // Resolve absolute or CWD-relative paths to a path within a Preopen |
| 2709 | var path_buf: [MAX_PATH_BYTES]u8 = undefined; |
| 2710 | const path = try resolvePathWasi(sub_dir_path, &path_buf); |
| 2711 | return mkdiratWasi(path.dir_fd, path.relative_path, mode); |
| 2712 | } |
| 2383 | 2713 | return mkdiratWasi(dir_fd, sub_dir_path, mode); |
| 2384 | 2714 | } else { |
| 2385 | 2715 | const sub_dir_path_c = try toPosixPath(sub_dir_path); |
| ... | ... | @@ -2414,6 +2744,8 @@ pub fn mkdiratZ(dir_fd: fd_t, sub_dir_path: [*:0]const u8, mode: u32) MakeDirErr |
| 2414 | 2744 | if (builtin.os.tag == .windows) { |
| 2415 | 2745 | const sub_dir_path_w = try windows.cStrToPrefixedFileW(sub_dir_path); |
| 2416 | 2746 | return mkdiratW(dir_fd, sub_dir_path_w.span().ptr, mode); |
| 2747 | } else if (builtin.os.tag == .wasi and !builtin.link_libc) { |
| 2748 | return mkdirat(dir_fd, mem.sliceTo(sub_dir_path, 0), mode); |
| 2417 | 2749 | } |
| 2418 | 2750 | switch (errno(system.mkdirat(dir_fd, sub_dir_path, mode))) { |
| 2419 | 2751 | .SUCCESS => return, |
| ... | ... | @@ -2472,10 +2804,10 @@ pub const MakeDirError = error{ |
| 2472 | 2804 | } || UnexpectedError; |
| 2473 | 2805 | |
| 2474 | 2806 | /// Create a directory. |
| 2475 | | /// `mode` is ignored on Windows. |
| 2807 | /// `mode` is ignored on Windows and WASI. |
| 2476 | 2808 | pub fn mkdir(dir_path: []const u8, mode: u32) MakeDirError!void { |
| 2477 | 2809 | if (builtin.os.tag == .wasi and !builtin.link_libc) { |
| 2478 | | @compileError("mkdir is not supported in WASI; use mkdirat instead"); |
| 2810 | return mkdirat(wasi.AT.FDCWD, dir_path, mode); |
| 2479 | 2811 | } else if (builtin.os.tag == .windows) { |
| 2480 | 2812 | const dir_path_w = try windows.sliceToPrefixedFileW(dir_path); |
| 2481 | 2813 | return mkdirW(dir_path_w.span(), mode); |
| ... | ... | @@ -2490,6 +2822,8 @@ pub fn mkdirZ(dir_path: [*:0]const u8, mode: u32) MakeDirError!void { |
| 2490 | 2822 | if (builtin.os.tag == .windows) { |
| 2491 | 2823 | const dir_path_w = try windows.cStrToPrefixedFileW(dir_path); |
| 2492 | 2824 | return mkdirW(dir_path_w.span(), mode); |
| 2825 | } else if (builtin.os.tag == .wasi and !builtin.link_libc) { |
| 2826 | return mkdir(mem.sliceTo(dir_path, 0), mode); |
| 2493 | 2827 | } |
| 2494 | 2828 | switch (errno(system.mkdir(dir_path, mode))) { |
| 2495 | 2829 | .SUCCESS => return, |
| ... | ... | @@ -2545,7 +2879,11 @@ pub const DeleteDirError = error{ |
| 2545 | 2879 | /// Deletes an empty directory. |
| 2546 | 2880 | pub fn rmdir(dir_path: []const u8) DeleteDirError!void { |
| 2547 | 2881 | if (builtin.os.tag == .wasi and !builtin.link_libc) { |
| 2548 | | @compileError("rmdir is not supported in WASI; use unlinkat instead"); |
| 2882 | return unlinkat(wasi.AT.FDCWD, dir_path, AT.REMOVEDIR) catch |err| switch (err) { |
| 2883 | error.FileSystem => unreachable, // only occurs when targeting files |
| 2884 | error.IsDir => unreachable, // only occurs when targeting files |
| 2885 | else => |e| return e, |
| 2886 | }; |
| 2549 | 2887 | } else if (builtin.os.tag == .windows) { |
| 2550 | 2888 | const dir_path_w = try windows.sliceToPrefixedFileW(dir_path); |
| 2551 | 2889 | return rmdirW(dir_path_w.span()); |
| ... | ... | @@ -2560,6 +2898,8 @@ pub fn rmdirZ(dir_path: [*:0]const u8) DeleteDirError!void { |
| 2560 | 2898 | if (builtin.os.tag == .windows) { |
| 2561 | 2899 | const dir_path_w = try windows.cStrToPrefixedFileW(dir_path); |
| 2562 | 2900 | return rmdirW(dir_path_w.span()); |
| 2901 | } else if (builtin.os.tag == .wasi and !builtin.link_libc) { |
| 2902 | return rmdir(mem.sliceTo(dir_path, 0)); |
| 2563 | 2903 | } |
| 2564 | 2904 | switch (errno(system.rmdir(dir_path))) { |
| 2565 | 2905 | .SUCCESS => return, |
| ... | ... | @@ -2606,7 +2946,17 @@ pub const ChangeCurDirError = error{ |
| 2606 | 2946 | /// `dir_path` is recommended to be a UTF-8 encoded string. |
| 2607 | 2947 | pub fn chdir(dir_path: []const u8) ChangeCurDirError!void { |
| 2608 | 2948 | if (builtin.os.tag == .wasi and !builtin.link_libc) { |
| 2609 | | @compileError("chdir is not supported in WASI"); |
| 2949 | var preopen: ?Preopen = null; |
| 2950 | const path = try resolvePathAndGetWasiPreopen(dir_path, &preopen, &wasi_cwd.path_buffer); |
| 2951 | |
| 2952 | const dirinfo = try fstatat(path.dir_fd, path.relative_path, 0); |
| 2953 | if (dirinfo.filetype != .DIRECTORY) { |
| 2954 | return error.NotDir; |
| 2955 | } |
| 2956 | |
| 2957 | wasi_cwd.cwd_preopen = preopen; |
| 2958 | wasi_cwd.cwd = path; |
| 2959 | return; |
| 2610 | 2960 | } else if (builtin.os.tag == .windows) { |
| 2611 | 2961 | var utf16_dir_path: [windows.PATH_MAX_WIDE]u16 = undefined; |
| 2612 | 2962 | const len = try std.unicode.utf8ToUtf16Le(utf16_dir_path[0..], dir_path); |
| ... | ... | @@ -2625,6 +2975,8 @@ pub fn chdirZ(dir_path: [*:0]const u8) ChangeCurDirError!void { |
| 2625 | 2975 | const len = try std.unicode.utf8ToUtf16Le(utf16_dir_path[0..], dir_path); |
| 2626 | 2976 | if (len > utf16_dir_path.len) return error.NameTooLong; |
| 2627 | 2977 | return chdirW(utf16_dir_path[0..len]); |
| 2978 | } else if (builtin.os.tag == .wasi and !builtin.link_libc) { |
| 2979 | return chdir(mem.sliceTo(dir_path, 0)); |
| 2628 | 2980 | } |
| 2629 | 2981 | switch (errno(system.chdir(dir_path))) { |
| 2630 | 2982 | .SUCCESS => return, |
| ... | ... | @@ -2655,15 +3007,29 @@ pub const FchdirError = error{ |
| 2655 | 3007 | } || UnexpectedError; |
| 2656 | 3008 | |
| 2657 | 3009 | 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), |
| 3010 | if (builtin.os.tag == .wasi) { |
| 3011 | // Check that this is a directory |
| 3012 | const dirinfo = fstatat(dirfd, ".", 0) catch unreachable; |
| 3013 | if (dirinfo.filetype != .DIRECTORY) { |
| 3014 | return error.NotDir; |
| 3015 | } |
| 3016 | |
| 3017 | wasi_cwd.cwd = .{ |
| 3018 | .dir_fd = dirfd, |
| 3019 | .relative_path = ".", |
| 3020 | }; |
| 3021 | wasi_cwd.cwd_preopen = null; |
| 3022 | } else { |
| 3023 | while (true) { |
| 3024 | switch (errno(system.fchdir(dirfd))) { |
| 3025 | .SUCCESS => return, |
| 3026 | .ACCES => return error.AccessDenied, |
| 3027 | .BADF => unreachable, |
| 3028 | .NOTDIR => return error.NotDir, |
| 3029 | .INTR => continue, |
| 3030 | .IO => return error.FileSystem, |
| 3031 | else => |err| return unexpectedErrno(err), |
| 3032 | } |
| 2667 | 3033 | } |
| 2668 | 3034 | } |
| 2669 | 3035 | } |
| ... | ... | @@ -2690,7 +3056,7 @@ pub const ReadLinkError = error{ |
| 2690 | 3056 | /// The return value is a slice of `out_buffer` from index 0. |
| 2691 | 3057 | pub fn readlink(file_path: []const u8, out_buffer: []u8) ReadLinkError![]u8 { |
| 2692 | 3058 | if (builtin.os.tag == .wasi and !builtin.link_libc) { |
| 2693 | | @compileError("readlink is not supported in WASI; use readlinkat instead"); |
| 3059 | return readlinkat(wasi.AT.FDCWD, file_path, out_buffer); |
| 2694 | 3060 | } else if (builtin.os.tag == .windows) { |
| 2695 | 3061 | const file_path_w = try windows.sliceToPrefixedFileW(file_path); |
| 2696 | 3062 | return readlinkW(file_path_w.span(), out_buffer); |
| ... | ... | @@ -2711,6 +3077,8 @@ pub fn readlinkZ(file_path: [*:0]const u8, out_buffer: []u8) ReadLinkError![]u8 |
| 2711 | 3077 | if (builtin.os.tag == .windows) { |
| 2712 | 3078 | const file_path_w = try windows.cStrToWin32PrefixedFileW(file_path); |
| 2713 | 3079 | return readlinkW(file_path_w.span(), out_buffer); |
| 3080 | } else if (builtin.os.tag == .wasi and !builtin.link_libc) { |
| 3081 | return readlink(mem.sliceTo(file_path, 0), out_buffer); |
| 2714 | 3082 | } |
| 2715 | 3083 | const rc = system.readlink(file_path, out_buffer.ptr, out_buffer.len); |
| 2716 | 3084 | switch (errno(rc)) { |
| ... | ... | @@ -2733,6 +3101,12 @@ pub fn readlinkZ(file_path: [*:0]const u8, out_buffer: []u8) ReadLinkError![]u8 |
| 2733 | 3101 | /// See also `readlinkatWasi`, `realinkatZ` and `realinkatW`. |
| 2734 | 3102 | pub fn readlinkat(dirfd: fd_t, file_path: []const u8, out_buffer: []u8) ReadLinkError![]u8 { |
| 2735 | 3103 | if (builtin.os.tag == .wasi and !builtin.link_libc) { |
| 3104 | if (dirfd == wasi.AT.FDCWD or fs.path.isAbsolute(file_path)) { |
| 3105 | // Resolve absolute or CWD-relative paths to a path within a Preopen |
| 3106 | var path_buf: [MAX_PATH_BYTES]u8 = undefined; |
| 3107 | var path = try resolvePathWasi(file_path, &path_buf); |
| 3108 | return readlinkatWasi(path.dir_fd, path.relative_path, out_buffer); |
| 3109 | } |
| 2736 | 3110 | return readlinkatWasi(dirfd, file_path, out_buffer); |
| 2737 | 3111 | } |
| 2738 | 3112 | if (builtin.os.tag == .windows) { |
| ... | ... | @@ -2775,6 +3149,8 @@ pub fn readlinkatZ(dirfd: fd_t, file_path: [*:0]const u8, out_buffer: []u8) Read |
| 2775 | 3149 | if (builtin.os.tag == .windows) { |
| 2776 | 3150 | const file_path_w = try windows.cStrToPrefixedFileW(file_path); |
| 2777 | 3151 | return readlinkatW(dirfd, file_path_w.span(), out_buffer); |
| 3152 | } else if (builtin.os.tag == .wasi and !builtin.link_libc) { |
| 3153 | return readlinkat(dirfd, mem.sliceTo(file_path, 0), out_buffer); |
| 2778 | 3154 | } |
| 2779 | 3155 | const rc = system.readlinkat(dirfd, file_path, out_buffer.ptr, out_buffer.len); |
| 2780 | 3156 | switch (errno(rc)) { |
| ... | ... | @@ -3727,7 +4103,14 @@ pub const FStatAtError = FStatError || error{ NameTooLong, FileNotFound, SymLink |
| 3727 | 4103 | /// See also `fstatatZ` and `fstatatWasi`. |
| 3728 | 4104 | pub fn fstatat(dirfd: fd_t, pathname: []const u8, flags: u32) FStatAtError!Stat { |
| 3729 | 4105 | if (builtin.os.tag == .wasi and !builtin.link_libc) { |
| 3730 | | return fstatatWasi(dirfd, pathname, flags); |
| 4106 | const wasi_flags = if (flags & linux.AT.SYMLINK_NOFOLLOW == 0) wasi.LOOKUP_SYMLINK_FOLLOW else 0; |
| 4107 | if (dirfd == wasi.AT.FDCWD or fs.path.isAbsolute(pathname)) { |
| 4108 | // Resolve absolute or CWD-relative paths to a path within a Preopen |
| 4109 | var path_buf: [MAX_PATH_BYTES]u8 = undefined; |
| 4110 | const path = try resolvePathWasi(pathname, &path_buf); |
| 4111 | return fstatatWasi(path.dir_fd, path.relative_path, wasi_flags); |
| 4112 | } |
| 4113 | return fstatatWasi(dirfd, pathname, wasi_flags); |
| 3731 | 4114 | } else if (builtin.os.tag == .windows) { |
| 3732 | 4115 | @compileError("fstatat is not yet implemented on Windows"); |
| 3733 | 4116 | } else { |
| ... | ... | @@ -3758,6 +4141,10 @@ pub fn fstatatWasi(dirfd: fd_t, pathname: []const u8, flags: u32) FStatAtError!S |
| 3758 | 4141 | /// Same as `fstatat` but `pathname` is null-terminated. |
| 3759 | 4142 | /// See also `fstatat`. |
| 3760 | 4143 | pub fn fstatatZ(dirfd: fd_t, pathname: [*:0]const u8, flags: u32) FStatAtError!Stat { |
| 4144 | if (builtin.os.tag == .wasi and !builtin.link_libc) { |
| 4145 | return fstatatWasi(dirfd, mem.sliceTo(pathname), flags); |
| 4146 | } |
| 4147 | |
| 3761 | 4148 | const fstatat_sym = if (builtin.os.tag == .linux and builtin.link_libc) |
| 3762 | 4149 | system.fstatat64 |
| 3763 | 4150 | else |
| ... | ... | @@ -4056,6 +4443,8 @@ pub fn access(path: []const u8, mode: u32) AccessError!void { |
| 4056 | 4443 | const path_w = try windows.sliceToPrefixedFileW(path); |
| 4057 | 4444 | _ = try windows.GetFileAttributesW(path_w.span().ptr); |
| 4058 | 4445 | return; |
| 4446 | } else if (builtin.os.tag == .wasi and !builtin.link_libc) { |
| 4447 | return faccessat(wasi.AT.FDCWD, path, mode, 0); |
| 4059 | 4448 | } |
| 4060 | 4449 | const path_c = try toPosixPath(path); |
| 4061 | 4450 | return accessZ(&path_c, mode); |
| ... | ... | @@ -4067,6 +4456,8 @@ pub fn accessZ(path: [*:0]const u8, mode: u32) AccessError!void { |
| 4067 | 4456 | const path_w = try windows.cStrToPrefixedFileW(path); |
| 4068 | 4457 | _ = try windows.GetFileAttributesW(path_w.span().ptr); |
| 4069 | 4458 | return; |
| 4459 | } else if (builtin.os.tag == .wasi and !builtin.link_libc) { |
| 4460 | return access(mem.sliceTo(path, 0), mode); |
| 4070 | 4461 | } |
| 4071 | 4462 | switch (errno(system.access(path, mode))) { |
| 4072 | 4463 | .SUCCESS => return, |
| ... | ... | @@ -4108,6 +4499,45 @@ pub fn faccessat(dirfd: fd_t, path: []const u8, mode: u32, flags: u32) AccessErr |
| 4108 | 4499 | if (builtin.os.tag == .windows) { |
| 4109 | 4500 | const path_w = try windows.sliceToPrefixedFileW(path); |
| 4110 | 4501 | return faccessatW(dirfd, path_w.span().ptr, mode, flags); |
| 4502 | } else if (builtin.os.tag == .wasi and !builtin.link_libc) { |
| 4503 | var resolved = RelativePathWasi{ .dir_fd = dirfd, .relative_path = path }; |
| 4504 | |
| 4505 | const file = blk: { |
| 4506 | if (dirfd == wasi.AT.FDCWD or fs.path.isAbsolute(path)) { |
| 4507 | // Resolve absolute or CWD-relative paths to a path within a Preopen |
| 4508 | var path_buf: [MAX_PATH_BYTES]u8 = undefined; |
| 4509 | resolved = resolvePathWasi(path, &path_buf) catch |err| break :blk @as(FStatAtError!Stat, err); |
| 4510 | break :blk fstatat(resolved.dir_fd, resolved.relative_path, flags); |
| 4511 | } |
| 4512 | break :blk fstatat(dirfd, path, flags); |
| 4513 | } catch |err| switch (err) { |
| 4514 | error.AccessDenied => return error.PermissionDenied, |
| 4515 | else => |e| return e, |
| 4516 | }; |
| 4517 | |
| 4518 | if (mode != F_OK) { |
| 4519 | var directory: wasi.fdstat_t = undefined; |
| 4520 | if (wasi.fd_fdstat_get(resolved.dir_fd, &directory) != .SUCCESS) { |
| 4521 | return error.PermissionDenied; |
| 4522 | } |
| 4523 | |
| 4524 | var rights: wasi.rights_t = 0; |
| 4525 | if (mode & R_OK != 0) { |
| 4526 | rights |= if (file.filetype == .DIRECTORY) |
| 4527 | wasi.RIGHT.FD_READDIR |
| 4528 | else |
| 4529 | wasi.RIGHT.FD_READ; |
| 4530 | } |
| 4531 | if (mode & W_OK != 0) { |
| 4532 | rights |= wasi.RIGHT.FD_WRITE; |
| 4533 | } |
| 4534 | // No validation for X_OK |
| 4535 | |
| 4536 | if ((rights & directory.fs_rights_inheriting) != rights) { |
| 4537 | return error.PermissionDenied; |
| 4538 | } |
| 4539 | } |
| 4540 | return; |
| 4111 | 4541 | } |
| 4112 | 4542 | const path_c = try toPosixPath(path); |
| 4113 | 4543 | return faccessatZ(dirfd, &path_c, mode, flags); |
| ... | ... | @@ -4118,6 +4548,8 @@ pub fn faccessatZ(dirfd: fd_t, path: [*:0]const u8, mode: u32, flags: u32) Acces |
| 4118 | 4548 | if (builtin.os.tag == .windows) { |
| 4119 | 4549 | const path_w = try windows.cStrToPrefixedFileW(path); |
| 4120 | 4550 | return faccessatW(dirfd, path_w.span().ptr, mode, flags); |
| 4551 | } else if (builtin.os.tag == .wasi and !builtin.link_libc) { |
| 4552 | return faccessat(dirfd, mem.sliceTo(path, 0), mode, flags); |
| 4121 | 4553 | } |
| 4122 | 4554 | switch (errno(system.faccessat(dirfd, path, mode, flags))) { |
| 4123 | 4555 | .SUCCESS => return, |
| ... | ... | @@ -4645,6 +5077,9 @@ pub const RealPathError = error{ |
| 4645 | 5077 | SharingViolation, |
| 4646 | 5078 | PipeBusy, |
| 4647 | 5079 | |
| 5080 | /// On WASI, the current CWD may not be associated with an absolute path. |
| 5081 | InvalidHandle, |
| 5082 | |
| 4648 | 5083 | /// On Windows, file paths must be valid Unicode. |
| 4649 | 5084 | InvalidUtf8, |
| 4650 | 5085 | |
| ... | ... | @@ -4660,19 +5095,55 @@ pub fn realpath(pathname: []const u8, out_buffer: *[MAX_PATH_BYTES]u8) RealPathE |
| 4660 | 5095 | if (builtin.os.tag == .windows) { |
| 4661 | 5096 | const pathname_w = try windows.sliceToPrefixedFileW(pathname); |
| 4662 | 5097 | 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"); |
| 5098 | } else if (builtin.os.tag == .wasi and !builtin.link_libc) { |
| 5099 | return realpathWasi(pathname, out_buffer); |
| 4666 | 5100 | } |
| 4667 | 5101 | const pathname_c = try toPosixPath(pathname); |
| 4668 | 5102 | return realpathZ(&pathname_c, out_buffer); |
| 4669 | 5103 | } |
| 4670 | 5104 | |
| 5105 | /// Return an emulated canonicalized absolute pathname on WASI. |
| 5106 | /// |
| 5107 | /// NOTE: This emulation is incomplete. Symbolic links are not |
| 5108 | /// currently expanded during path canonicalization. |
| 5109 | fn realpathWasi(pathname: []const u8, out_buffer: []u8) ![]u8 { |
| 5110 | var alloc = std.heap.FixedBufferAllocator.init(out_buffer); |
| 5111 | if (fs.path.isAbsolute(pathname)) |
| 5112 | return try fs.path.resolve(alloc.allocator(), &.{pathname}) catch error.NameTooLong; |
| 5113 | if (wasi_cwd.cwd) |cwd| { |
| 5114 | if (wasi_cwd.cwd_preopen) |po| { |
| 5115 | var base_cwd_dir = switch (po.@"type") { |
| 5116 | .Dir => |dir| dir, |
| 5117 | }; |
| 5118 | const paths: [][]const u8 = if (fs.path.isAbsolute(base_cwd_dir)) blk: { |
| 5119 | break :blk &.{ base_cwd_dir, cwd.relative_path, pathname }; |
| 5120 | } else blk: { |
| 5121 | // No absolute path is associated with this preopen, so |
| 5122 | // instead we use a special "/preopens/fd/<N>/" prefix |
| 5123 | var buf: [16]u8 = undefined; |
| 5124 | var fbs = std.io.fixedBufferStream(&buf); |
| 5125 | std.fmt.formatInt(po.fd, 10, .lower, .{}, fbs.writer()) catch return error.NameTooLong; |
| 5126 | break :blk &.{ "/preopens/fd/", fbs.getWritten(), cwd.relative_path, pathname }; |
| 5127 | }; |
| 5128 | |
| 5129 | return fs.path.resolve(alloc.allocator(), paths) catch error.NameTooLong; |
| 5130 | } else { |
| 5131 | // The CWD is not rooted to an existing Preopen, |
| 5132 | // so we have no way to know its absolute path |
| 5133 | return error.InvalidHandle; |
| 5134 | } |
| 5135 | } else { |
| 5136 | return try fs.path.resolve(alloc.allocator(), &.{ "/", pathname }) catch error.NameTooLong; |
| 5137 | } |
| 5138 | } |
| 5139 | |
| 4671 | 5140 | /// Same as `realpath` except `pathname` is null-terminated. |
| 4672 | 5141 | pub fn realpathZ(pathname: [*:0]const u8, out_buffer: *[MAX_PATH_BYTES]u8) RealPathError![]u8 { |
| 4673 | 5142 | if (builtin.os.tag == .windows) { |
| 4674 | 5143 | const pathname_w = try windows.cStrToPrefixedFileW(pathname); |
| 4675 | 5144 | return realpathW(pathname_w.span(), out_buffer); |
| 5145 | } else if (builtin.os.tag == .wasi and !builtin.link_libc) { |
| 5146 | return realpath(mem.sliceTo(pathname, 0), out_buffer); |
| 4676 | 5147 | } |
| 4677 | 5148 | if (!builtin.link_libc) { |
| 4678 | 5149 | const flags = if (builtin.os.tag == .linux) O.PATH | O.NONBLOCK | O.CLOEXEC else O.NONBLOCK | O.CLOEXEC; |
| ... | ... | @@ -4680,6 +5151,7 @@ pub fn realpathZ(pathname: [*:0]const u8, out_buffer: *[MAX_PATH_BYTES]u8) RealP |
| 4680 | 5151 | error.FileLocksNotSupported => unreachable, |
| 4681 | 5152 | error.WouldBlock => unreachable, |
| 4682 | 5153 | error.FileBusy => unreachable, // not asking for write permissions |
| 5154 | error.InvalidHandle => unreachable, // WASI-only |
| 4683 | 5155 | else => |e| return e, |
| 4684 | 5156 | }; |
| 4685 | 5157 | defer close(fd); |