diff --git a/Sources/FoundationEssentials/FileManager/FileManager+Directories.swift b/Sources/FoundationEssentials/FileManager/FileManager+Directories.swift
index a29b31195..bcbb5e998 100644
--- a/Sources/FoundationEssentials/FileManager/FileManager+Directories.swift
+++ b/Sources/FoundationEssentials/FileManager/FileManager+Directories.swift
@@ -193,6 +193,9 @@ extension _FileManagerImpl {
}
}
return results
+#elseif os(WASI)
+ // wasi-libc does not support FTS for now
+ throw CocoaError.errorWithFilePath(.featureUnsupported, path)
#else
return try path.withFileSystemRepresentation { fileSystemRep in
guard let fileSystemRep else {
diff --git a/Sources/FoundationEssentials/FileManager/FileOperations+Enumeration.swift b/Sources/FoundationEssentials/FileManager/FileOperations+Enumeration.swift
index 22111f52c..904f5fb75 100644
--- a/Sources/FoundationEssentials/FileManager/FileOperations+Enumeration.swift
+++ b/Sources/FoundationEssentials/FileManager/FileOperations+Enumeration.swift
@@ -115,10 +115,16 @@ import posix_filesystem.dirent
#elseif canImport(Glibc)
import Glibc
internal import _FoundationCShims
+#elseif os(WASI)
+import WASILibc
+internal import _FoundationCShims
#endif
// MARK: Directory Iteration
+// No FTS support in wasi-libc for now (https://github.com/WebAssembly/wasi-libc/issues/520)
+#if !os(WASI)
+
struct _FTSSequence: Sequence {
enum Element {
struct SwiftFTSENT {
@@ -315,10 +321,12 @@ extension Sequence<_FTSSequence.Element> {
}
}
+#endif // !os(WASI)
+
struct _POSIXDirectoryContentsSequence: Sequence {
#if canImport(Darwin)
typealias DirectoryEntryPtr = UnsafeMutablePointer
- #elseif os(Android) || canImport(Glibc)
+ #elseif os(Android) || canImport(Glibc) || os(WASI)
typealias DirectoryEntryPtr = OpaquePointer
#endif
@@ -343,10 +351,18 @@ struct _POSIXDirectoryContentsSequence: Sequence {
continue
}
// Use name
- let fileName = withUnsafeBytes(of: &dent.pointee.d_name) { buf in
+ let fileName: String
+ #if os(WASI)
+ // Use shim on WASI because wasi-libc defines `d_name` as
+ // "flexible array member" which is not supported by
+ // ClangImporter yet.
+ fileName = String(cString: _platform_shims_dirent_d_name(dent))
+ #else
+ fileName = withUnsafeBytes(of: &dent.pointee.d_name) { buf in
let ptr = buf.baseAddress!.assumingMemoryBound(to: CChar.self)
return String(cString: ptr)
}
+ #endif
if fileName == "." || fileName == ".." || fileName == "._" {
continue
diff --git a/Sources/FoundationEssentials/FileManager/FileOperations.swift b/Sources/FoundationEssentials/FileManager/FileOperations.swift
index d3fc7b2d5..3da9c5022 100644
--- a/Sources/FoundationEssentials/FileManager/FileOperations.swift
+++ b/Sources/FoundationEssentials/FileManager/FileOperations.swift
@@ -512,7 +512,14 @@ enum _FileOperations {
// We failed for a reason other than the directory not being empty, so throw
throw CocoaError.removeFileError(errno, resolve(path: pathStr))
}
-
+
+ #if os(WASI)
+
+ // wasi-libc does not support FTS, so we don't support removing non-empty directories on WASI for now.
+ throw CocoaError.errorWithFilePath(.featureUnsupported, pathStr)
+
+ #else
+
let seq = _FTSSequence(path, FTS_PHYSICAL | FTS_XDEV | FTS_NOCHDIR | FTS_NOSTAT)
let iterator = seq.makeIterator()
var isFirst = true
@@ -561,6 +568,7 @@ enum _FileOperations {
}
}
}
+ #endif
}
#endif
@@ -903,6 +911,50 @@ enum _FileOperations {
}
#endif
+ #if os(WASI)
+ private static func _linkOrCopyFile(_ srcPtr: UnsafePointer, _ dstPtr: UnsafePointer, with fileManager: FileManager, delegate: some LinkOrCopyDelegate) throws {
+ let src = String(cString: srcPtr)
+ let dst = String(cString: dstPtr)
+ guard delegate.shouldPerformOnItemAtPath(src, to: dst) else { return }
+
+ var stat = stat()
+ guard lstat(srcPtr, &stat) == 0 else {
+ try delegate.throwIfNecessary(errno, src, dst)
+ return
+ }
+ guard !stat.isDirectory else {
+ // wasi-libc does not support FTS for now, so we don't support copying/linking
+ // directories on WASI for now.
+ let error = CocoaError.fileOperationError(.featureUnsupported, src, dst)
+ try delegate.throwIfNecessary(error, src, dst)
+ return
+ }
+
+ // For now, we support only copying regular files and symlinks.
+ // After we get FTS support (https://github.com/WebAssembly/wasi-libc/pull/522),
+ // we can remove this method and use the below FTS-based implementation.
+
+ if stat.isSymbolicLink {
+ try withUnsafeTemporaryAllocation(of: CChar.self, capacity: FileManager.MAX_PATH_SIZE) { tempBuff in
+ tempBuff.initialize(repeating: 0)
+ defer { tempBuff.deinitialize() }
+ let len = readlink(srcPtr, tempBuff.baseAddress!, FileManager.MAX_PATH_SIZE - 1)
+ if len >= 0, symlink(tempBuff.baseAddress!, dstPtr) != -1 {
+ return
+ }
+ try delegate.throwIfNecessary(errno, src, dst)
+ }
+ } else {
+ if delegate.copyData {
+ try _copyRegularFile(srcPtr, dstPtr, delegate: delegate)
+ } else {
+ if link(srcPtr, dstPtr) != 0 {
+ try delegate.throwIfNecessary(errno, src, dst)
+ }
+ }
+ }
+ }
+ #else
private static func _linkOrCopyFile(_ srcPtr: UnsafePointer, _ dstPtr: UnsafePointer, with fileManager: FileManager, delegate: some LinkOrCopyDelegate) throws {
try withUnsafeTemporaryAllocation(of: CChar.self, capacity: FileManager.MAX_PATH_SIZE) { buffer in
let dstLen = Platform.copyCString(dst: buffer.baseAddress!, src: dstPtr, size: FileManager.MAX_PATH_SIZE)
@@ -1015,6 +1067,7 @@ enum _FileOperations {
}
}
}
+ #endif
private static func linkOrCopyFile(_ src: String, dst: String, with fileManager: FileManager, delegate: some LinkOrCopyDelegate) throws {
try src.withFileSystemRepresentation { srcPtr in
diff --git a/Sources/FoundationEssentials/WASILibc+Extensions.swift b/Sources/FoundationEssentials/WASILibc+Extensions.swift
index 1c05f9958..351fe19f2 100644
--- a/Sources/FoundationEssentials/WASILibc+Extensions.swift
+++ b/Sources/FoundationEssentials/WASILibc+Extensions.swift
@@ -29,4 +29,25 @@ internal var CLOCK_MONOTONIC_RAW: clockid_t {
return CLOCK_MONOTONIC
}
+// MARK: - File Operations
+
+internal var DT_DIR: UInt8 {
+ return _platform_shims_DT_DIR()
+}
+internal var DT_UNKNOWN: UInt8 {
+ return _platform_shims_DT_UNKNOWN()
+}
+internal var O_CREAT: Int32 {
+ return _platform_shims_O_CREAT()
+}
+internal var O_EXCL: Int32 {
+ return _platform_shims_O_EXCL()
+}
+internal var O_TRUNC: Int32 {
+ return _platform_shims_O_TRUNC()
+}
+internal var O_WRONLY: Int32 {
+ return _platform_shims_O_WRONLY()
+}
+
#endif // os(WASI)
diff --git a/Sources/_FoundationCShims/include/platform_shims.h b/Sources/_FoundationCShims/include/platform_shims.h
index f45f5fd55..6bc0a0e15 100644
--- a/Sources/_FoundationCShims/include/platform_shims.h
+++ b/Sources/_FoundationCShims/include/platform_shims.h
@@ -79,6 +79,29 @@ static inline _Nonnull clockid_t _platform_shims_clock_monotonic(void) {
static inline _Nonnull clockid_t _platform_shims_clock_realtime(void) {
return CLOCK_REALTIME;
}
+
+// Define dirent shims so that we can use them in Swift because wasi-libc defines
+// `d_name` as "flexible array member" which is not supported by ClangImporter yet.
+
+#include
+
+static inline char * _Nonnull _platform_shims_dirent_d_name(struct dirent * _Nonnull entry) {
+ return entry->d_name;
+}
+
+// Define getter shims for constants because wasi-libc defines them as function-like macros
+// which are not supported by ClangImporter yet.
+
+#include
+#include
+#include
+
+static inline uint8_t _platform_shims_DT_DIR(void) { return DT_DIR; }
+static inline uint8_t _platform_shims_DT_UNKNOWN(void) { return DT_UNKNOWN; }
+static inline int32_t _platform_shims_O_CREAT(void) { return O_CREAT; }
+static inline int32_t _platform_shims_O_EXCL(void) { return O_EXCL; }
+static inline int32_t _platform_shims_O_TRUNC(void) { return O_TRUNC; }
+static inline int32_t _platform_shims_O_WRONLY(void) { return O_WRONLY; }
#endif
#endif /* CSHIMS_PLATFORM_SHIMS */