Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Sources/WASI/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ add_wasmkit_library(WASI
Platform/PAL/PALErrnoMapping.swift
Platform/PAL/PALPoll.swift
Platform/PAL/PALSystemClocks.swift
Platform/PAL/PALWindows.swift
Platform/SandboxPrimitives/Open.swift
Platform/SandboxPrimitives/Readlink.swift
Platform/SandboxPrimitives/OpenParent.swift
Expand Down
9 changes: 9 additions & 0 deletions Sources/WASI/FileSystem.swift
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,9 @@ extension WASIEntry {
func removeDirectory(atPath path: String) throws
func removeFile(atPath path: String) throws
func symlink(from sourcePath: String, to destPath: String) throws
/// Creates a hard link at `destPath` in `newDir` to the entry at
/// `sourcePath`, without following a symlink there.
func link(from sourcePath: String, toDir newDir: any WASIDir, to destPath: String) throws
func rename(from sourcePath: String, toDir newDir: any WASIDir, to destPath: String) throws
func readEntries(cookie: WASIAbi.DirCookie) throws -> WASIReaddirEntries
func attributes(path: String, symlinkFollow: Bool) throws -> WASIAbi.Filestat
Expand All @@ -84,6 +87,12 @@ extension WASIEntry {
) throws
}

extension WASIDir {
@_spi(WASIPlatform) public func link(from sourcePath: String, toDir newDir: any WASIDir, to destPath: String) throws {
throw WASIAbi.Errno.ENOTSUP
}
}

/// A single directory entry.
@_spi(WASIPlatform) public typealias WASIReaddirElement = (dirent: WASIAbi.Dirent, name: String)

Expand Down
73 changes: 48 additions & 25 deletions Sources/WASI/Platform/Directory.swift
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,7 @@ extension DirEntry: WASIDir, FdWASIEntry {
options.insert(.directory)
}

if fdflags.contains(.APPEND) {
options.insert(.append)
}
options.formUnion(fdflags.platformOpenOptions)

let mode: FileDescriptor.AccessMode
switch (accessMode.contains(.read), accessMode.contains(.write)) {
Expand Down Expand Up @@ -79,17 +77,17 @@ extension DirEntry: WASIDir, FdWASIEntry {
atim: WASIAbi.Timestamp, mtim: WASIAbi.Timestamp,
fstFlags: WASIAbi.FstFlags, symlinkFollow: Bool
) throws {
let fd = try openFile(
symlinkFollow: symlinkFollow, path: path,
oflags: [], accessMode: .write, fdflags: []
let (access, modification) = try WASIAbi.Timestamp.platformTimeSpec(
atim: atim, mtim: mtim, fstFlags: fstFlags
)
try withThrowing {
let (access, modification) = try WASIAbi.Timestamp.platformTimeSpec(
atim: atim, mtim: mtim, fstFlags: fstFlags
)
try fd.setTimes(access: access, modification: modification)
} defer: {
try fd.close()
// Following happens in the sandbox, and `utimensat` never follows a
// symlink in the last component itself.
let result =
symlinkFollow
? try SandboxPrimitives.openParentFollowingSymlinks(start: fd, path: path)
: try SandboxPrimitives.openParent(start: fd, path: path)
try result.withFields { dir, basename in
try dir.setTimes(at: basename, options: .noFollow, access: access, modification: modification)
}
}

Expand All @@ -113,7 +111,31 @@ extension DirEntry: WASIDir, FdWASIEntry {
start: fd, path: destPath
)
try result.withFields { destDir, destBasename in
try destDir.createSymlink(original: sourcePath, link: destBasename)
try destDir.createSymlink(original: sourcePath, link: destBasename) {
// Look the target up inside the sandbox, relative to the
// directory the link goes in.
guard let (parent, _) = splitParent(path: destPath) else { return false }
let targetPath = parent.isEmpty ? sourcePath : parent.string + "/" + sourcePath
guard
let target = try? SandboxPrimitives.openAt(
start: fd, path: GuestPath(targetPath), mode: .readOnly, options: .directory, permissions: [])
else { return false }
try? target.close()
return true
}
}
}

func link(from sourcePath: String, toDir newDir: any WASIDir, to destPath: String) throws {
guard let newDir = newDir as? Self else {
throw WASIAbi.Errno.EBADF
}
let sourceResult = try SandboxPrimitives.openParent(start: fd, path: sourcePath)
let destResult = try SandboxPrimitives.openParent(start: newDir.fd, path: destPath)
try sourceResult.withFields { sourceDir, sourceBasename in
try destResult.withFields { destDir, destBasename in
try sourceDir.createHardLink(at: sourceBasename, to: destDir, at: destBasename)
}
}
}

Expand Down Expand Up @@ -153,7 +175,6 @@ extension DirEntry: WASIDir, FdWASIEntry {
}

struct HostDirectoryIterator: WASIReaddirIterator {
let fd: FileDescriptor
let stream: FileDescriptor.DirectoryStream
var entryIndex: Int

Expand All @@ -166,16 +187,16 @@ extension DirEntry: WASIDir, FdWASIEntry {
let newFd = try fd.open(at: ".", .readOnly)
let stream = try newFd.contentsOfDirectory()

self.fd = fd
self.entryIndex = 0
self.stream = stream

// The cookie is a `u64` the guest chooses; one past the last entry
// simply skips everything.
let skippedCount = Int(clamping: cookie)
while entryIndex < skippedCount {
guard let entry = next() else { break }
guard let entry = stream.next() else { break }
_ = try entry.get()
entryIndex += 1
}
}

Expand All @@ -187,13 +208,14 @@ extension DirEntry: WASIDir, FdWASIEntry {
return Result(catching: { () -> ReaddirElement in
let entry = try entry.get()
let name = entry.name
let stat = try fd.attributes(at: name, options: [.noFollow])
let dirent = WASIAbi.Dirent(
// We can't use telldir and seekdir because the location data
// is valid for only the same dirp but and there is no way to
// share dirp among fd_readdir calls.
dNext: WASIAbi.DirCookie(entryIndex + 1),
dIno: stat.inode,
// The inode readdir reports, as wasmtime does, rather
// than one `fstatat` per entry.
dIno: entry.inode,
dirNameLen: WASIAbi.DirNameLen(name.utf8.count),
dType: WASIAbi.FileType(platformFileType: entry.fileType)
)
Expand All @@ -219,13 +241,14 @@ extension DirEntry: WASIDir, FdWASIEntry {
}

func attributes(path: String, symlinkFollow: Bool) throws -> WASIAbi.Filestat {
var options: FileDescriptor.AtOptions = []
if !symlinkFollow {
options.insert(.noFollow)
}
let result = try SandboxPrimitives.openParent(start: fd, path: path)
// Following happens in the sandbox, never in the host's `fstatat`,
// which would follow a symlink pointing out of the sandbox.
let result =
symlinkFollow
? try SandboxPrimitives.openParentFollowingSymlinks(start: fd, path: path)
: try SandboxPrimitives.openParent(start: fd, path: path)
return try result.withFields { dir, basename in
let attributes = try dir.attributes(at: basename, options: options)
let attributes = try dir.attributes(at: basename, options: .noFollow)

return WASIAbi.Filestat(stat: attributes)
}
Expand Down
9 changes: 9 additions & 0 deletions Sources/WASI/Platform/PAL/PALErrnoMapping.swift
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,15 @@ private func _mapWindowsError(_ code: UInt32) -> WASIAbi.Errno? {
return .ENOSPC
case ERROR_DIR_NOT_EMPTY:
return .ENOTEMPTY
case ERROR_DIRECTORY:
return .ENOTDIR
case ERROR_INVALID_NAME:
return .ENOENT
case ERROR_CANT_RESOLVE_FILENAME,
ERROR_STOPPED_ON_SYMLINK:
return .ELOOP
case ERROR_PRIVILEGE_NOT_HELD:
return .EPERM
case ERROR_WAIT_NO_CHILDREN,
ERROR_CHILD_NOT_COMPLETE:
return .ECHILD
Expand Down
Loading
Loading