diff --git a/CoreFoundation/Base.subproj/ForSwiftFoundationOnly.h b/CoreFoundation/Base.subproj/ForSwiftFoundationOnly.h index d545b9e21e..d78eeddf68 100644 --- a/CoreFoundation/Base.subproj/ForSwiftFoundationOnly.h +++ b/CoreFoundation/Base.subproj/ForSwiftFoundationOnly.h @@ -45,7 +45,7 @@ #if _POSIX_THREADS #include #endif -#if defined(__unix__) || (defined(__APPLE__) && defined(__MACH__)) +#if defined(__unix__) || (defined(__APPLE__) && defined(__MACH__)) || defined(__wasi__) #include #endif @@ -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; @@ -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 diff --git a/Sources/Foundation/FileHandle.swift b/Sources/Foundation/FileHandle.swift index 285bed7059..5fb7258ca8 100644 --- a/Sources/Foundation/FileHandle.swift +++ b/Sources/Foundation/FileHandle.swift @@ -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) diff --git a/Sources/Foundation/FileManager+POSIX.swift b/Sources/Foundation/FileManager+POSIX.swift index d90ece91e6..a69d71e9f7 100644 --- a/Sources/Foundation/FileManager+POSIX.swift +++ b/Sources/Foundation/FileManager+POSIX.swift @@ -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 @@ -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? @@ -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 { @@ -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) @@ -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) { @@ -534,6 +576,7 @@ extension FileManager { } return self.string(withFileSystemRepresentation: buf, length: strlen(buf)) + #endif } internal func _readFrom(fd: Int32, toBuffer buffer: UnsafeMutablePointer, length bytesToRead: Int, filename: String) throws -> Int { @@ -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 @@ -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?>.allocate(capacity: 2) ps.initialize(to: UnsafeMutablePointer(mutating: fsRep)) ps.advanced(by: 1).initialize(to: nil) @@ -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 { @@ -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) @@ -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 @@ -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 @@ -1198,6 +1270,7 @@ extension FileManager { return nil } } + #endif internal func _updateTimes(atPath path: String, withFileSystemRepresentation fsr: UnsafePointer, creationTime: Date? = nil, accessTime: Date? = nil, modificationTime: Date? = nil) throws { let stat = try _lstatFile(atPath: path, withFileSystemRepresentation: fsr) diff --git a/Sources/Foundation/FileManager.swift b/Sources/Foundation/FileManager.swift index 1aa3038a01..fdd8411511 100644 --- a/Sources/Foundation/FileManager.swift +++ b/Sources/Foundation/FileManager.swift @@ -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 @@ -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?)") } @@ -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: @@ -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) diff --git a/Sources/Foundation/NSData.swift b/Sources/Foundation/NSData.swift index e004c9502a..5f3640300d 100644 --- a/Sources/Foundation/NSData.swift +++ b/Sources/Foundation/NSData.swift @@ -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)