Skip to content

Commit

Permalink
Port directory enumeration related code to WASI
Browse files Browse the repository at this point in the history
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:
WebAssembly/wasi-libc#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.
  • Loading branch information
kateinoigakukun committed Aug 10, 2024
1 parent 4440638 commit 08af69e
Show file tree
Hide file tree
Showing 5 changed files with 113 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -315,10 +321,12 @@ extension Sequence<_FTSSequence.Element> {
}
}

#endif // !os(WASI)

struct _POSIXDirectoryContentsSequence: Sequence {
#if canImport(Darwin)
typealias DirectoryEntryPtr = UnsafeMutablePointer<DIR>
#elseif os(Android) || canImport(Glibc)
#elseif os(Android) || canImport(Glibc) || os(WASI)
typealias DirectoryEntryPtr = OpaquePointer
#endif

Expand All @@ -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
Expand Down
49 changes: 48 additions & 1 deletion Sources/FoundationEssentials/FileManager/FileOperations.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -561,6 +568,7 @@ enum _FileOperations {
}
}
}
#endif

}
#endif
Expand Down Expand Up @@ -903,6 +911,44 @@ enum _FileOperations {
}
#endif

#if os(WASI)
private static func _linkOrCopyFile(_ srcPtr: UnsafePointer<CChar>, _ dstPtr: UnsafePointer<CChar>, 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<CChar>, _ dstPtr: UnsafePointer<CChar>, 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)
Expand Down Expand Up @@ -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
Expand Down
21 changes: 21 additions & 0 deletions Sources/FoundationEssentials/WASILibc+Extensions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
23 changes: 23 additions & 0 deletions Sources/_FoundationCShims/include/platform_shims.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 <dirent.h>

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 <stdint.h>
#include <fcntl.h>
#include <dirent.h>

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 */

0 comments on commit 08af69e

Please sign in to comment.