From 08af69e0991f1ae8b3a68c8144fab987987d8e63 Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Fri, 26 Jul 2024 07:41:19 +0000 Subject: [PATCH 1/2] Port directory enumeration related code to WASI For now wasi-libc does not include fts(3) implementation, so mark features depending on it as unsupported on WASI. Once wasi-libc includes fts or we decide to implement and maintain our own fts-like API, we can remove these `#if os(WASI)` guards. wasi-libc issue tracking fts support: https://github.com/WebAssembly/wasi-libc/issues/520 Also, wasi-libc defines some constants in a way that ClangImporter can't understand, so we need to grab them manually through _FoundationCShims in function call form. --- .../FileManager/FileManager+Directories.swift | 3 ++ .../FileOperations+Enumeration.swift | 20 +++++++- .../FileManager/FileOperations.swift | 49 ++++++++++++++++++- .../WASILibc+Extensions.swift | 21 ++++++++ .../include/platform_shims.h | 23 +++++++++ 5 files changed, 113 insertions(+), 3 deletions(-) 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..dc51ba55b 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,44 @@ 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, !stat.isDirectory else { + // wasi-libc does not support FTS for now, so we don't support copying/linking + // directories on WASI for now. + throw CocoaError.errorWithFilePath(.featureUnsupported, String(cString: srcPtr)) + } + + // 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 +1061,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 */ From 36fed41f7b5b91e4733b9120c144309aba121c2d Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Mon, 12 Aug 2024 17:32:17 +0000 Subject: [PATCH 2/2] Delegate error throwing decisions to the delegate --- .../FileManager/FileOperations.swift | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/Sources/FoundationEssentials/FileManager/FileOperations.swift b/Sources/FoundationEssentials/FileManager/FileOperations.swift index dc51ba55b..3da9c5022 100644 --- a/Sources/FoundationEssentials/FileManager/FileOperations.swift +++ b/Sources/FoundationEssentials/FileManager/FileOperations.swift @@ -918,10 +918,16 @@ enum _FileOperations { guard delegate.shouldPerformOnItemAtPath(src, to: dst) else { return } var stat = stat() - guard lstat(srcPtr, &stat) == 0, !stat.isDirectory else { + 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. - throw CocoaError.errorWithFilePath(.featureUnsupported, String(cString: srcPtr)) + let error = CocoaError.fileOperationError(.featureUnsupported, src, dst) + try delegate.throwIfNecessary(error, src, dst) + return } // For now, we support only copying regular files and symlinks.