Skip to content
Open
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
5 changes: 4 additions & 1 deletion Sources/SystemExtras/FileOperations.swift
Original file line number Diff line number Diff line change
Expand Up @@ -249,7 +249,10 @@ extension FileDescriptor {

@_alwaysEmitIntoClient
public var device: UInt64 {
UInt64(rawValue.st_dev)
if (rawValue.st_dev < 0) {
return UInt64(bitPattern: Int64(rawValue.st_dev))
}
return UInt64(rawValue.st_dev)
}

@_alwaysEmitIntoClient
Expand Down
8 changes: 7 additions & 1 deletion Sources/WASI/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@ add_wasmkit_library(WASI
Platform/File.swift
Platform/PlatformTypes.swift
Platform/SandboxPrimitives.swift
Platform/HostFileSystem.swift
MemoryFileSystem/MemoryFileSystem.swift
MemoryFileSystem/MemoryFSNodes.swift
MemoryFileSystem/MemoryDirEntry.swift
MemoryFileSystem/MemoryFileEntry.swift
MemoryFileSystem/MemoryStdioFile.swift
FileSystem.swift
GuestMemorySupport.swift
Clock.swift
Expand All @@ -14,4 +20,4 @@ add_wasmkit_library(WASI
)

target_link_wasmkit_libraries(WASI PUBLIC
WasmTypes SystemExtras)
WasmTypes SystemExtras)
59 changes: 59 additions & 0 deletions Sources/WASI/FileSystem.swift
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,13 @@ enum FdEntry {
return directory
}
}

func asFile() -> (any WASIFile)? {
if case .file(let entry) = self {
return entry
}
return nil
}
}

/// A table that maps file descriptor to actual resource in host environment
Expand Down Expand Up @@ -120,3 +127,55 @@ struct FdTable {
}
}
}

/// Content of a file that can be retrieved from the file system.
public enum FileContent {
case bytes([UInt8])
case handle(FileDescriptor)
}

/// Public protocol for file system providers that users interact with.
///
/// This protocol exposes only user-facing methods for managing files and directories.
public protocol FileSystemProvider: ~Copyable {
/// Adds a file to the file system with the given byte content.
func addFile(at path: String, content: some Sequence<UInt8>) throws

/// Adds a file to the file system with the given string content.
func addFile(at path: String, content: String) throws

/// Adds a file to the file system backed by a file descriptor handle.
func addFile(at path: String, handle: FileDescriptor) throws

/// Gets the content of a file at the specified path.
func getFile(at path: String) throws -> FileContent

/// Removes a file from the file system.
func removeFile(at path: String) throws
}

/// Internal protocol for file system implementations used by WASI.
///
/// This protocol contains WASI-specific implementation details that should not
/// be exposed to library users.
protocol FileSystemImplementation: ~Copyable {
/// Returns the list of pre-opened directory paths.
var preopenPaths: [String] { get }

/// Opens a directory and returns a WASIDir implementation.
func openDirectory(at path: String) throws -> any WASIDir
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if the use of existentials is justified here? Could we use opaque types instead?

Suggested change
func openDirectory(at path: String) throws -> any WASIDir
func openDirectory(at path: String) throws -> some WASIDir

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WASIDir seems to be defined as a protocol so I believe an existential is how its used elsewhere (i.e in Directory.swift)


/// Opens a file or directory from a directory file descriptor.
func openAt(
dirFd: any WASIDir,
path: String,
oflags: WASIAbi.Oflags,
fsRightsBase: WASIAbi.Rights,
fsRightsInheriting: WASIAbi.Rights,
fdflags: WASIAbi.Fdflags,
symlinkFollow: Bool
) throws -> FdEntry

/// Creates a standard I/O file entry for stdin/stdout/stderr.
func createStdioFile(fd: FileDescriptor, accessMode: FileAccessMode) -> any WASIFile
}
153 changes: 153 additions & 0 deletions Sources/WASI/MemoryFileSystem/MemoryDirEntry.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
import SystemPackage

/// A WASIDir implementation backed by an in-memory directory node.
struct MemoryDirEntry: WASIDir {
let preopenPath: String?
let dirNode: MemoryDirectoryNode
let path: String
let fileSystem: MemoryFileSystem

func attributes() throws -> WASIAbi.Filestat {
return WASIAbi.Filestat(
dev: 0, ino: 0, filetype: .DIRECTORY,
nlink: 1, size: 0,
atim: 0, mtim: 0, ctim: 0
)
}

func fileType() throws -> WASIAbi.FileType {
return .DIRECTORY
}

func status() throws -> WASIAbi.Fdflags {
return []
}

func setTimes(
atim: WASIAbi.Timestamp, mtim: WASIAbi.Timestamp,
fstFlags: WASIAbi.FstFlags
) throws {
// No-op for memory filesystem - timestamps not tracked
}

func advise(
offset: WASIAbi.FileSize, length: WASIAbi.FileSize, advice: WASIAbi.Advice
) throws {
// No-op for memory filesystem
}

func close() throws {
// No-op for memory filesystem - no resources to release
}

func openFile(
symlinkFollow: Bool,
path: String,
oflags: WASIAbi.Oflags,
accessMode: FileAccessMode,
fdflags: WASIAbi.Fdflags
) throws -> FileDescriptor {
// Memory filesystem doesn't return real file descriptors for this method
// File opening is handled through the WASI bridge's path_open implementation
throw WASIAbi.Errno.ENOTSUP
}

func createDirectory(atPath path: String) throws {
let fullPath = self.path.hasSuffix("/") ? self.path + path : self.path + "/" + path
try fileSystem.ensureDirectory(at: fullPath)
}

func removeDirectory(atPath path: String) throws {
try fileSystem.removeNode(in: dirNode, at: path, mustBeDirectory: true)
}

func removeFile(atPath path: String) throws {
try fileSystem.removeNode(in: dirNode, at: path, mustBeDirectory: false)
}

func symlink(from sourcePath: String, to destPath: String) throws {
// Symlinks not supported in memory filesystem
throw WASIAbi.Errno.ENOTSUP
}

func rename(from sourcePath: String, toDir newDir: any WASIDir, to destPath: String) throws {
guard let newMemoryDir = newDir as? MemoryDirEntry else {
throw WASIAbi.Errno.EXDEV
}

try fileSystem.rename(
from: sourcePath, in: dirNode,
to: destPath, in: newMemoryDir.dirNode
)
}

func readEntries(cookie: WASIAbi.DirCookie) throws -> AnyIterator<Result<ReaddirElement, any Error>> {
let children = dirNode.listChildren()

let iterator = children.enumerated()
.dropFirst(Int(cookie))
.map { (index, name) -> Result<ReaddirElement, any Error> in
return Result(catching: {
let childPath = self.path.hasSuffix("/") ? self.path + name : self.path + "/" + name
guard let childNode = fileSystem.lookup(at: childPath) else {
throw WASIAbi.Errno.ENOENT
}

let fileType: WASIAbi.FileType
switch childNode.type {
case .directory: fileType = .DIRECTORY
case .file: fileType = .REGULAR_FILE
case .characterDevice: fileType = .CHARACTER_DEVICE
}

let dirent = WASIAbi.Dirent(
dNext: WASIAbi.DirCookie(index + 1),
dIno: 0,
dirNameLen: WASIAbi.DirNameLen(name.utf8.count),
dType: fileType
)

return (dirent, name)
})
}
.makeIterator()

return AnyIterator(iterator)
}

func attributes(path: String, symlinkFollow: Bool) throws -> WASIAbi.Filestat {
let fullPath = self.path.hasSuffix("/") ? self.path + path : self.path + "/" + path
guard let node = fileSystem.lookup(at: fullPath) else {
throw WASIAbi.Errno.ENOENT
}

let fileType: WASIAbi.FileType
var size: WASIAbi.FileSize = 0

switch node.type {
case .directory:
fileType = .DIRECTORY
case .file:
fileType = .REGULAR_FILE
if let fileNode = node as? MemoryFileNode {
size = WASIAbi.FileSize(fileNode.size)
}
case .characterDevice:
fileType = .CHARACTER_DEVICE
}

return WASIAbi.Filestat(
dev: 0, ino: 0, filetype: fileType,
nlink: 1, size: size,
atim: 0, mtim: 0, ctim: 0
)
}

func setFilestatTimes(
path: String,
atim: WASIAbi.Timestamp, mtim: WASIAbi.Timestamp,
fstFlags: WASIAbi.FstFlags, symlinkFollow: Bool
) throws {
// No-op for memory filesystem - timestamps not tracked
}
}
Loading
Loading