Skip to content

Commit

Permalink
Merge pull request #4898 from kateinoigakukun/pr-232e89b439816fe1ab82…
Browse files Browse the repository at this point in the history
…ea5d1efc020e9adcc700

[wasm] Port FileManager for WASI platform
  • Loading branch information
kateinoigakukun committed Mar 2, 2024
2 parents 294988f + e80fcc5 commit fbae263
Show file tree
Hide file tree
Showing 5 changed files with 105 additions and 3 deletions.
16 changes: 13 additions & 3 deletions CoreFoundation/Base.subproj/ForSwiftFoundationOnly.h
Expand Up @@ -45,7 +45,7 @@
#if _POSIX_THREADS
#include <pthread.h>
#endif
#if defined(__unix__) || (defined(__APPLE__) && defined(__MACH__))
#if defined(__unix__) || (defined(__APPLE__) && defined(__MACH__)) || defined(__wasi__)
#include <dirent.h>
#endif

Expand Down Expand Up @@ -565,11 +565,11 @@ CF_CROSS_PLATFORM_EXPORT int _CFOpenFileWithMode(const char *path, int opts, mod
CF_CROSS_PLATFORM_EXPORT void *_CFReallocf(void *ptr, size_t size);
CF_CROSS_PLATFORM_EXPORT int _CFOpenFile(const char *path, int opts);

#if defined(__unix__) || (defined(__APPLE__) && defined(__MACH__))
#if defined(__unix__) || (defined(__APPLE__) && defined(__MACH__)) || defined(__wasi__)
static inline int _direntNameLength(struct dirent *entry) {
#ifdef _D_EXACT_NAMLEN // defined on Linux
return _D_EXACT_NAMLEN(entry);
#elif TARGET_OS_LINUX || TARGET_OS_ANDROID
#elif TARGET_OS_LINUX || TARGET_OS_ANDROID || TARGET_OS_WASI
return strlen(entry->d_name);
#else
return entry->d_namlen;
Expand All @@ -578,11 +578,21 @@ static inline int _direntNameLength(struct dirent *entry) {

// major() and minor() might be implemented as macros or functions.
static inline unsigned int _dev_major(dev_t rdev) {
#if !TARGET_OS_WASI
return major(rdev);
#else
// WASI does not have device numbers
return 0;
#endif
}

static inline unsigned int _dev_minor(dev_t rdev) {
#if !TARGET_OS_WASI
return minor(rdev);
#else
// WASI does not have device numbers
return 0;
#endif
}

#endif
Expand Down
6 changes: 6 additions & 0 deletions Sources/Foundation/FileHandle.swift
Expand Up @@ -27,6 +27,12 @@ import Musl
fileprivate let _read = Musl.read(_:_:_:)
fileprivate let _write = Musl.write(_:_:_:)
fileprivate let _close = Musl.close(_:)
#elseif canImport(WASILibc)
import WASILibc
@_implementationOnly import wasi_emulated_mman
fileprivate let _read = WASILibc.read(_:_:_:)
fileprivate let _write = WASILibc.write(_:_:_:)
fileprivate let _close = WASILibc.close(_:)
#endif

#if canImport(WinSDK)
Expand Down
73 changes: 73 additions & 0 deletions Sources/Foundation/FileManager+POSIX.swift
Expand Up @@ -96,6 +96,32 @@ extension FileManager {
return nil
}
urls = mountPoints(statBuf, Int(fsCount))
#elseif os(WASI)
// Skip the first three file descriptors, which are reserved for stdin, stdout, and stderr.
var fd: __wasi_fd_t = 3
let __WASI_PREOPENTYPE_DIR: UInt8 = 0
while true {
var prestat = __wasi_prestat_t()
guard __wasi_fd_prestat_get(fd, &prestat) == 0 else {
break
}

if prestat.tag == __WASI_PREOPENTYPE_DIR {
var buf = [UInt8](repeating: 0, count: Int(prestat.u.dir.pr_name_len))
guard __wasi_fd_prestat_dir_name(fd, &buf, prestat.u.dir.pr_name_len) == 0 else {
break
}
let path = buf.withUnsafeBufferPointer { buf in
guard let baseAddress = buf.baseAddress else {
return ""
}
let base = UnsafeRawPointer(baseAddress).assumingMemoryBound(to: Int8.self)
return string(withFileSystemRepresentation: base, length: buf.count)
}
urls.append(URL(fileURLWithPath: path, isDirectory: true))
}
fd += 1
}
#else
#error("Requires a platform-specific implementation")
#endif
Expand Down Expand Up @@ -446,6 +472,10 @@ extension FileManager {
}

internal func _attributesOfFileSystemIncludingBlockSize(forPath path: String) throws -> (attributes: [FileAttributeKey : Any], blockSize: UInt64?) {
#if os(WASI)
// WASI doesn't have statvfs
throw _NSErrorWithErrno(ENOTSUP, reading: true, path: path)
#else
var result: [FileAttributeKey:Any] = [:]
var finalBlockSize: UInt64?

Expand Down Expand Up @@ -478,6 +508,7 @@ extension FileManager {
finalBlockSize = blockSize
}
return (attributes: result, blockSize: finalBlockSize)
#endif // os(WASI)
}

internal func _createSymbolicLink(atPath path: String, withDestinationPath destPath: String) throws {
Expand Down Expand Up @@ -507,6 +538,11 @@ extension FileManager {
}

internal func _recursiveDestinationOfSymbolicLink(atPath path: String) throws -> String {
#if os(WASI)
// TODO: Remove this guard when realpath implementation will be released
// See https://github.com/WebAssembly/wasi-libc/pull/473
throw _NSErrorWithErrno(ENOTSUP, reading: true, path: path)
#else
// Throw error if path is not a symbolic link:
let path = try _destinationOfSymbolicLink(atPath: path)

Expand All @@ -520,10 +556,16 @@ extension FileManager {
}

return String(cString: resolvedPath)
#endif
}

/* Returns a String with a canonicalized path for the element at the specified path. */
internal func _canonicalizedPath(toFileAtPath path: String) throws -> String {
#if os(WASI)
// TODO: Remove this guard when realpath implementation will be released
// See https://github.com/WebAssembly/wasi-libc/pull/473
throw _NSErrorWithErrno(ENOTSUP, reading: true, path: path)
#else
let bufSize = Int(PATH_MAX + 1)
var buf = [Int8](repeating: 0, count: bufSize)
let done = try _fileSystemRepresentation(withPath: path) {
Expand All @@ -534,6 +576,7 @@ extension FileManager {
}

return self.string(withFileSystemRepresentation: buf, length: strlen(buf))
#endif
}

internal func _readFrom(fd: Int32, toBuffer buffer: UnsafeMutablePointer<UInt8>, length bytesToRead: Int, filename: String) throws -> Int {
Expand Down Expand Up @@ -591,12 +634,14 @@ extension FileManager {
}
defer { close(dstfd) }

#if !os(WASI) // WASI doesn't have ownership concept
// Set the file permissions using fchmod() instead of when open()ing to avoid umask() issues
let permissions = fileInfo.st_mode & ~S_IFMT
guard fchmod(dstfd, permissions) == 0 else {
throw _NSErrorWithErrno(errno, reading: false, path: dstPath,
extraUserInfo: extraErrorInfo(srcPath: srcPath, dstPath: dstPath, userVariant: variant))
}
#endif

if fileInfo.st_size == 0 {
// no copying required
Expand Down Expand Up @@ -741,6 +786,10 @@ extension FileManager {
if rmdir(fsRep) == 0 {
return
} else if errno == ENOTEMPTY {
#if os(WASI)
// wasi-libc, which is based on musl, does not provide fts(3)
throw _NSErrorWithErrno(ENOTSUP, reading: false, path: path)
#else
let ps = UnsafeMutablePointer<UnsafeMutablePointer<Int8>?>.allocate(capacity: 2)
ps.initialize(to: UnsafeMutablePointer(mutating: fsRep))
ps.advanced(by: 1).initialize(to: nil)
Expand Down Expand Up @@ -783,6 +832,7 @@ extension FileManager {
} else {
let _ = _NSErrorWithErrno(ENOTEMPTY, reading: false, path: path)
}
#endif
} else if errno != ENOTDIR {
throw _NSErrorWithErrno(errno, reading: false, path: path)
} else if unlink(fsRep) != 0 {
Expand Down Expand Up @@ -885,6 +935,7 @@ extension FileManager {
return false
}

#if !os(WASI) // WASI doesn't have ownership concept
// Stat the parent directory, if that fails, return false.
let parentS = try _lstatFile(atPath: path, withFileSystemRepresentation: parentFsRep)

Expand All @@ -895,6 +946,7 @@ extension FileManager {
// If the current user owns the file, return true.
return s.st_uid == getuid()
}
#endif

// Return true as the best guess.
return true
Expand Down Expand Up @@ -1065,6 +1117,26 @@ extension FileManager {
return temp._bridgeToObjectiveC().appendingPathComponent(dest)
}

#if os(WASI)
// For platforms that don't support FTS, we just throw an error for now.
// TODO: Provide readdir(2) based implementation here or FTS in wasi-libc?
internal class NSURLDirectoryEnumerator : DirectoryEnumerator {
var _url : URL
var _errorHandler : ((URL, Error) -> Bool)?

init(url: URL, options: FileManager.DirectoryEnumerationOptions, errorHandler: ((URL, Error) -> Bool)?) {
_url = url
_errorHandler = errorHandler
}

override func nextObject() -> Any? {
if let handler = _errorHandler {
_ = handler(_url, _NSErrorWithErrno(ENOTSUP, reading: true, url: _url))
}
return nil
}
}
#else
internal class NSURLDirectoryEnumerator : DirectoryEnumerator {
var _url : URL
var _options : FileManager.DirectoryEnumerationOptions
Expand Down Expand Up @@ -1198,6 +1270,7 @@ extension FileManager {
return nil
}
}
#endif

internal func _updateTimes(atPath path: String, withFileSystemRepresentation fsr: UnsafePointer<Int8>, creationTime: Date? = nil, accessTime: Date? = nil, modificationTime: Date? = nil) throws {
let stat = try _lstatFile(atPath: path, withFileSystemRepresentation: fsr)
Expand Down
11 changes: 11 additions & 0 deletions Sources/Foundation/FileManager.swift
Expand Up @@ -21,6 +21,10 @@ import CRT
import WinSDK
#endif

#if os(WASI)
import WASILibc
#endif

#if os(Windows)
internal typealias NativeFSRCharType = WCHAR
internal let NativeFSREncoding = String.Encoding.utf16LittleEndian.rawValue
Expand Down Expand Up @@ -384,6 +388,10 @@ open class FileManager : NSObject {

switch attribute {
case .posixPermissions:
#if os(WASI)
// WASI does not have permission concept
throw _NSErrorWithErrno(ENOTSUP, reading: false, path: path)
#else
guard let number = attributeValues[attribute] as? NSNumber else {
fatalError("Can't set file permissions to \(attributeValues[attribute] as Any?)")
}
Expand All @@ -400,6 +408,7 @@ open class FileManager : NSObject {
guard result == 0 else {
throw _NSErrorWithErrno(errno, reading: false, path: path)
}
#endif // os(WASI)

case .modificationDate: fallthrough
case ._accessDate:
Expand Down Expand Up @@ -567,6 +576,8 @@ open class FileManager : NSObject {
result[.deviceIdentifier] = NSNumber(value: UInt64(s.st_rdev))
let attributes = try windowsFileAttributes(atPath: path)
let type = FileAttributeType(attributes: attributes, atPath: path)
#elseif os(WASI)
let type = FileAttributeType(statMode: mode_t(s.st_mode))
#else
if let pwd = getpwuid(s.st_uid), pwd.pointee.pw_name != nil {
let name = String(cString: pwd.pointee.pw_name)
Expand Down
2 changes: 2 additions & 0 deletions Sources/Foundation/NSData.swift
Expand Up @@ -498,6 +498,8 @@ open class NSData : NSObject, NSCopying, NSMutableCopying, NSSecureCoding {
let createMode = Int(Glibc.S_IRUSR) | Int(Glibc.S_IWUSR) | Int(Glibc.S_IRGRP) | Int(Glibc.S_IWGRP) | Int(Glibc.S_IROTH) | Int(Glibc.S_IWOTH)
#elseif canImport(Musl)
let createMode = Int(Musl.S_IRUSR) | Int(Musl.S_IWUSR) | Int(Musl.S_IRGRP) | Int(Musl.S_IWGRP) | Int(Musl.S_IROTH) | Int(Musl.S_IWOTH)
#elseif canImport(WASILibc)
let createMode = Int(WASILibc.S_IRUSR) | Int(WASILibc.S_IWUSR) | Int(WASILibc.S_IRGRP) | Int(WASILibc.S_IWGRP) | Int(WASILibc.S_IROTH) | Int(WASILibc.S_IWOTH)
#endif
guard let fh = FileHandle(path: path, flags: flags, createMode: createMode) else {
throw _NSErrorWithErrno(errno, reading: false, path: path)
Expand Down

0 comments on commit fbae263

Please sign in to comment.