Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Port directory enumeration related code to WASI #836

Merged
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
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
55 changes: 54 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,50 @@ enum _FileOperations {
}
#endif

#if os(WASI)
private static func _linkOrCopyFile(_ srcPtr: UnsafePointer<CChar>, _ dstPtr: UnsafePointer<CChar>, with fileManager: FileManager, delegate: some LinkOrCopyDelegate) throws {
MaxDesiatov marked this conversation as resolved.
Show resolved Hide resolved
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<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 +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
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.
Copy link
Contributor

Choose a reason for hiding this comment

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

For things like this, is there any hope for us resolving this somehow beyond updating the ClangImporter (some sort of API notes or something at the toolchain layer? Not sure if that's possible today, but I'm curious what our options are since it seems like WASI requires a large amount of shims

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes, we have three directions, 1. provide those definitions in WASILibc overlay module, 2. change the upstream wasi-libc headers to be compatible with ClangImporter, 3. "fix" ClangImporter to import those constants from wasi-libc headers directly.

1 is what we are doing for some frequently used constants now. Still, it's quite ad-hoc and not a scalable solution. 2 is an option worth considering to align with other platform's headers, but not sure if it's acceptable for the upstream and if they can maintain the compatibility with ClangImporter.
I think 3 is the way we should go, and there are several open tickets for this kind of ClangImporter enhancement around macros (swiftlang/swift#46679, swiftlang/swift#45008, swiftlang/swift#43102, swiftlang/swift#45009). We need to carefully do it since it can break source compatibilities, but I have a plan to work on this.


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