Skip to content

Commit fbae263

Browse files
Merge pull request #4898 from kateinoigakukun/pr-232e89b439816fe1ab82ea5d1efc020e9adcc700
[wasm] Port FileManager for WASI platform
2 parents 294988f + e80fcc5 commit fbae263

File tree

5 files changed

+105
-3
lines changed

5 files changed

+105
-3
lines changed

CoreFoundation/Base.subproj/ForSwiftFoundationOnly.h

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@
4545
#if _POSIX_THREADS
4646
#include <pthread.h>
4747
#endif
48-
#if defined(__unix__) || (defined(__APPLE__) && defined(__MACH__))
48+
#if defined(__unix__) || (defined(__APPLE__) && defined(__MACH__)) || defined(__wasi__)
4949
#include <dirent.h>
5050
#endif
5151

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

568-
#if defined(__unix__) || (defined(__APPLE__) && defined(__MACH__))
568+
#if defined(__unix__) || (defined(__APPLE__) && defined(__MACH__)) || defined(__wasi__)
569569
static inline int _direntNameLength(struct dirent *entry) {
570570
#ifdef _D_EXACT_NAMLEN // defined on Linux
571571
return _D_EXACT_NAMLEN(entry);
572-
#elif TARGET_OS_LINUX || TARGET_OS_ANDROID
572+
#elif TARGET_OS_LINUX || TARGET_OS_ANDROID || TARGET_OS_WASI
573573
return strlen(entry->d_name);
574574
#else
575575
return entry->d_namlen;
@@ -578,11 +578,21 @@ static inline int _direntNameLength(struct dirent *entry) {
578578

579579
// major() and minor() might be implemented as macros or functions.
580580
static inline unsigned int _dev_major(dev_t rdev) {
581+
#if !TARGET_OS_WASI
581582
return major(rdev);
583+
#else
584+
// WASI does not have device numbers
585+
return 0;
586+
#endif
582587
}
583588

584589
static inline unsigned int _dev_minor(dev_t rdev) {
590+
#if !TARGET_OS_WASI
585591
return minor(rdev);
592+
#else
593+
// WASI does not have device numbers
594+
return 0;
595+
#endif
586596
}
587597

588598
#endif

Sources/Foundation/FileHandle.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,12 @@ import Musl
2727
fileprivate let _read = Musl.read(_:_:_:)
2828
fileprivate let _write = Musl.write(_:_:_:)
2929
fileprivate let _close = Musl.close(_:)
30+
#elseif canImport(WASILibc)
31+
import WASILibc
32+
@_implementationOnly import wasi_emulated_mman
33+
fileprivate let _read = WASILibc.read(_:_:_:)
34+
fileprivate let _write = WASILibc.write(_:_:_:)
35+
fileprivate let _close = WASILibc.close(_:)
3036
#endif
3137

3238
#if canImport(WinSDK)

Sources/Foundation/FileManager+POSIX.swift

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,32 @@ extension FileManager {
9696
return nil
9797
}
9898
urls = mountPoints(statBuf, Int(fsCount))
99+
#elseif os(WASI)
100+
// Skip the first three file descriptors, which are reserved for stdin, stdout, and stderr.
101+
var fd: __wasi_fd_t = 3
102+
let __WASI_PREOPENTYPE_DIR: UInt8 = 0
103+
while true {
104+
var prestat = __wasi_prestat_t()
105+
guard __wasi_fd_prestat_get(fd, &prestat) == 0 else {
106+
break
107+
}
108+
109+
if prestat.tag == __WASI_PREOPENTYPE_DIR {
110+
var buf = [UInt8](repeating: 0, count: Int(prestat.u.dir.pr_name_len))
111+
guard __wasi_fd_prestat_dir_name(fd, &buf, prestat.u.dir.pr_name_len) == 0 else {
112+
break
113+
}
114+
let path = buf.withUnsafeBufferPointer { buf in
115+
guard let baseAddress = buf.baseAddress else {
116+
return ""
117+
}
118+
let base = UnsafeRawPointer(baseAddress).assumingMemoryBound(to: Int8.self)
119+
return string(withFileSystemRepresentation: base, length: buf.count)
120+
}
121+
urls.append(URL(fileURLWithPath: path, isDirectory: true))
122+
}
123+
fd += 1
124+
}
99125
#else
100126
#error("Requires a platform-specific implementation")
101127
#endif
@@ -446,6 +472,10 @@ extension FileManager {
446472
}
447473

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

@@ -478,6 +508,7 @@ extension FileManager {
478508
finalBlockSize = blockSize
479509
}
480510
return (attributes: result, blockSize: finalBlockSize)
511+
#endif // os(WASI)
481512
}
482513

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

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

@@ -520,10 +556,16 @@ extension FileManager {
520556
}
521557

522558
return String(cString: resolvedPath)
559+
#endif
523560
}
524561

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

536578
return self.string(withFileSystemRepresentation: buf, length: strlen(buf))
579+
#endif
537580
}
538581

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

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

601646
if fileInfo.st_size == 0 {
602647
// no copying required
@@ -741,6 +786,10 @@ extension FileManager {
741786
if rmdir(fsRep) == 0 {
742787
return
743788
} else if errno == ENOTEMPTY {
789+
#if os(WASI)
790+
// wasi-libc, which is based on musl, does not provide fts(3)
791+
throw _NSErrorWithErrno(ENOTSUP, reading: false, path: path)
792+
#else
744793
let ps = UnsafeMutablePointer<UnsafeMutablePointer<Int8>?>.allocate(capacity: 2)
745794
ps.initialize(to: UnsafeMutablePointer(mutating: fsRep))
746795
ps.advanced(by: 1).initialize(to: nil)
@@ -783,6 +832,7 @@ extension FileManager {
783832
} else {
784833
let _ = _NSErrorWithErrno(ENOTEMPTY, reading: false, path: path)
785834
}
835+
#endif
786836
} else if errno != ENOTDIR {
787837
throw _NSErrorWithErrno(errno, reading: false, path: path)
788838
} else if unlink(fsRep) != 0 {
@@ -885,6 +935,7 @@ extension FileManager {
885935
return false
886936
}
887937

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

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

899951
// Return true as the best guess.
900952
return true
@@ -1065,6 +1117,26 @@ extension FileManager {
10651117
return temp._bridgeToObjectiveC().appendingPathComponent(dest)
10661118
}
10671119

1120+
#if os(WASI)
1121+
// For platforms that don't support FTS, we just throw an error for now.
1122+
// TODO: Provide readdir(2) based implementation here or FTS in wasi-libc?
1123+
internal class NSURLDirectoryEnumerator : DirectoryEnumerator {
1124+
var _url : URL
1125+
var _errorHandler : ((URL, Error) -> Bool)?
1126+
1127+
init(url: URL, options: FileManager.DirectoryEnumerationOptions, errorHandler: ((URL, Error) -> Bool)?) {
1128+
_url = url
1129+
_errorHandler = errorHandler
1130+
}
1131+
1132+
override func nextObject() -> Any? {
1133+
if let handler = _errorHandler {
1134+
_ = handler(_url, _NSErrorWithErrno(ENOTSUP, reading: true, url: _url))
1135+
}
1136+
return nil
1137+
}
1138+
}
1139+
#else
10681140
internal class NSURLDirectoryEnumerator : DirectoryEnumerator {
10691141
var _url : URL
10701142
var _options : FileManager.DirectoryEnumerationOptions
@@ -1198,6 +1270,7 @@ extension FileManager {
11981270
return nil
11991271
}
12001272
}
1273+
#endif
12011274

12021275
internal func _updateTimes(atPath path: String, withFileSystemRepresentation fsr: UnsafePointer<Int8>, creationTime: Date? = nil, accessTime: Date? = nil, modificationTime: Date? = nil) throws {
12031276
let stat = try _lstatFile(atPath: path, withFileSystemRepresentation: fsr)

Sources/Foundation/FileManager.swift

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,10 @@ import CRT
2121
import WinSDK
2222
#endif
2323

24+
#if os(WASI)
25+
import WASILibc
26+
#endif
27+
2428
#if os(Windows)
2529
internal typealias NativeFSRCharType = WCHAR
2630
internal let NativeFSREncoding = String.Encoding.utf16LittleEndian.rawValue
@@ -384,6 +388,10 @@ open class FileManager : NSObject {
384388

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

404413
case .modificationDate: fallthrough
405414
case ._accessDate:
@@ -567,6 +576,8 @@ open class FileManager : NSObject {
567576
result[.deviceIdentifier] = NSNumber(value: UInt64(s.st_rdev))
568577
let attributes = try windowsFileAttributes(atPath: path)
569578
let type = FileAttributeType(attributes: attributes, atPath: path)
579+
#elseif os(WASI)
580+
let type = FileAttributeType(statMode: mode_t(s.st_mode))
570581
#else
571582
if let pwd = getpwuid(s.st_uid), pwd.pointee.pw_name != nil {
572583
let name = String(cString: pwd.pointee.pw_name)

Sources/Foundation/NSData.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -498,6 +498,8 @@ open class NSData : NSObject, NSCopying, NSMutableCopying, NSSecureCoding {
498498
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)
499499
#elseif canImport(Musl)
500500
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)
501+
#elseif canImport(WASILibc)
502+
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)
501503
#endif
502504
guard let fh = FileHandle(path: path, flags: flags, createMode: createMode) else {
503505
throw _NSErrorWithErrno(errno, reading: false, path: path)

0 commit comments

Comments
 (0)