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

[wasm] Port FileManager for WASI platform #4898

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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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