Skip to content
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
37 changes: 36 additions & 1 deletion Sources/CSystem/include/CSystemWASI.h
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/*
This source file is part of the Swift System open source project

Copyright (c) 2024 Apple Inc. and the Swift System project authors
Copyright (c) 2024 - 2025 Apple Inc. and the Swift System project authors
Licensed under Apache License v2.0 with Runtime Library Exception

See https://swift.org/LICENSE.txt for license information
Expand All @@ -11,8 +11,10 @@

#if __wasi__

#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h> // For NAME_MAX

// wasi-libc defines the following constants in a way that Clang Importer can't
// understand, so we need to expose them manually.
Expand All @@ -28,4 +30,37 @@ static inline int32_t _getConst_O_WRONLY(void) { return O_WRONLY; }
static inline int32_t _getConst_EWOULDBLOCK(void) { return EWOULDBLOCK; }
static inline int32_t _getConst_EOPNOTSUPP(void) { return EOPNOTSUPP; }

static inline uint8_t _getConst_DT_DIR(void) { return DT_DIR; }

// Modified dirent struct that can be imported to Swift
struct _system_dirent {
ino_t d_ino;
unsigned char d_type;
// char d_name[] cannot be imported to Swift
char d_name[NAME_MAX + 1];
};

// Convert WASI dirent with d_name[] to _system_dirent
static inline
struct _system_dirent *
_system_dirent_from_wasi_dirent(const struct dirent *wasi_dirent) {

// Match readdir behavior and use thread-local storage for the converted dirent
static __thread struct _system_dirent _converted_dirent;

if (wasi_dirent == NULL) {
return NULL;
}

memset(&_converted_dirent, 0, sizeof(struct _system_dirent));

_converted_dirent.d_ino = wasi_dirent->d_ino;
_converted_dirent.d_type = wasi_dirent->d_type;

strncpy(_converted_dirent.d_name, wasi_dirent->d_name, NAME_MAX);
_converted_dirent.d_name[NAME_MAX] = '\0';

return &_converted_dirent;
}

#endif
4 changes: 3 additions & 1 deletion Sources/System/FileOperations.swift
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/*
This source file is part of the Swift System open source project

Copyright (c) 2020 - 2024 Apple Inc. and the Swift System project authors
Copyright (c) 2020 - 2025 Apple Inc. and the Swift System project authors
Licensed under Apache License v2.0 with Runtime Library Exception

See https://swift.org/LICENSE.txt for license information
Expand Down Expand Up @@ -509,6 +509,7 @@ extension FileDescriptor {
}
}

#if !os(WASI) // WASI has no umask
extension FilePermissions {
/// The file creation permission mask (aka "umask").
///
Expand Down Expand Up @@ -549,3 +550,4 @@ extension FilePermissions {
return system_umask(mode)
}
}
#endif
18 changes: 15 additions & 3 deletions Sources/System/Internals/Syscalls.swift
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/*
This source file is part of the Swift System open source project

Copyright (c) 2020 - 2024 Apple Inc. and the Swift System project authors
Copyright (c) 2020 - 2025 Apple Inc. and the Swift System project authors
Licensed under Apache License v2.0 with Runtime Library Exception

See https://swift.org/LICENSE.txt for license information
Expand All @@ -14,6 +14,7 @@ import Glibc
#elseif canImport(Musl)
import Musl
#elseif canImport(WASILibc)
import CSystem
import WASILibc
#elseif os(Windows)
import ucrt
Expand Down Expand Up @@ -189,9 +190,14 @@ internal func system_confstr(

#if !os(Windows)
internal let SYSTEM_AT_REMOVE_DIR = AT_REMOVEDIR
#if os(WASI)
internal let SYSTEM_DT_DIR = _getConst_DT_DIR()
internal typealias system_dirent = _system_dirent
#else
internal let SYSTEM_DT_DIR = DT_DIR
internal typealias system_dirent = dirent
#if os(Linux) || os(Android) || os(FreeBSD) || os(OpenBSD)
#endif
#if os(Linux) || os(Android) || os(FreeBSD) || os(OpenBSD) || os(WASI)
internal typealias system_DIRPtr = OpaquePointer
#else
internal typealias system_DIRPtr = UnsafeMutablePointer<DIR>
Expand All @@ -216,8 +222,12 @@ internal func system_fdopendir(

internal func system_readdir(
_ dir: system_DIRPtr
) -> UnsafeMutablePointer<dirent>? {
) -> UnsafeMutablePointer<system_dirent>? {
#if os(WASI)
return _system_dirent_from_wasi_dirent(readdir(dir))
#else
return readdir(dir)
#endif
}

internal func system_rewinddir(
Expand Down Expand Up @@ -246,11 +256,13 @@ internal func system_openat(
}
#endif

#if !os(WASI) // WASI has no umask
internal func system_umask(
_ mode: CInterop.Mode
) -> CInterop.Mode {
return umask(mode)
}
#endif

internal func system_getenv(
_ name: UnsafePointer<CChar>
Expand Down
20 changes: 13 additions & 7 deletions Tests/SystemTests/ErrnoTest.swift
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/*
This source file is part of the Swift System open source project

Copyright (c) 2020 Apple Inc. and the Swift System project authors
Copyright (c) 2020 - 2025 Apple Inc. and the Swift System project authors
Licensed under Apache License v2.0 with Runtime Library Exception

See https://swift.org/LICENSE.txt for license information
Expand Down Expand Up @@ -36,7 +36,7 @@ final class ErrnoTest: XCTestCase {
XCTAssert(ENOMEM == Errno.noMemory.rawValue)
XCTAssert(EACCES == Errno.permissionDenied.rawValue)
XCTAssert(EFAULT == Errno.badAddress.rawValue)
#if !os(Windows)
#if !os(Windows) && !os(WASI)
XCTAssert(ENOTBLK == Errno.notBlockDevice.rawValue)
#endif
XCTAssert(EBUSY == Errno.resourceBusy.rawValue)
Expand Down Expand Up @@ -74,9 +74,11 @@ final class ErrnoTest: XCTestCase {
XCTAssert(WSAEOPNOTSUPP == Errno.notSupported.rawValue)
XCTAssert(WSAEPFNOSUPPORT == Errno.protocolFamilyNotSupported.rawValue)
#else
XCTAssert(ESOCKTNOSUPPORT == Errno.socketTypeNotSupported.rawValue)
XCTAssert(ENOTSUP == Errno.notSupported.rawValue)
#if !os(WASI)
XCTAssert(ESOCKTNOSUPPORT == Errno.socketTypeNotSupported.rawValue)
XCTAssert(EPFNOSUPPORT == Errno.protocolFamilyNotSupported.rawValue)
#endif
#endif
XCTAssert(EAFNOSUPPORT == Errno.addressFamilyNotSupported.rawValue)
XCTAssert(EADDRINUSE == Errno.addressInUse.rawValue)
Expand All @@ -91,7 +93,7 @@ final class ErrnoTest: XCTestCase {
XCTAssert(ENOTCONN == Errno.socketNotConnected.rawValue)
#if os(Windows)
XCTAssert(WSAESHUTDOWN == Errno.socketShutdown.rawValue)
#else
#elseif !os(WASI)
XCTAssert(ESHUTDOWN == Errno.socketShutdown.rawValue)
#endif
XCTAssert(ETIMEDOUT == Errno.timedOut.rawValue)
Expand All @@ -100,7 +102,7 @@ final class ErrnoTest: XCTestCase {
XCTAssert(ENAMETOOLONG == Errno.fileNameTooLong.rawValue)
#if os(Windows)
XCTAssert(WSAEHOSTDOWN == Errno.hostIsDown.rawValue)
#else
#elseif !os(WASI)
XCTAssert(EHOSTDOWN == Errno.hostIsDown.rawValue)
#endif
XCTAssert(EHOSTUNREACH == Errno.noRouteToHost.rawValue)
Expand All @@ -115,7 +117,9 @@ final class ErrnoTest: XCTestCase {
XCTAssert(WSAEDQUOT == Errno.diskQuotaExceeded.rawValue)
XCTAssert(WSAESTALE == Errno.staleNFSFileHandle.rawValue)
#else
#if !os(WASI)
XCTAssert(EUSERS == Errno.tooManyUsers.rawValue)
#endif
XCTAssert(EDQUOT == Errno.diskQuotaExceeded.rawValue)
XCTAssert(ESTALE == Errno.staleNFSFileHandle.rawValue)
#endif
Expand Down Expand Up @@ -171,7 +175,7 @@ final class ErrnoTest: XCTestCase {
XCTAssert(EPROTO == Errno.protocolError.rawValue)
#endif

#if !os(Windows) && !os(FreeBSD)
#if !os(Windows) && !os(FreeBSD) && !os(WASI)
XCTAssert(ENODATA == Errno.noData.rawValue)
XCTAssert(ENOSR == Errno.noStreamResources.rawValue)
XCTAssert(ENOSTR == Errno.notStream.rawValue)
Expand All @@ -181,11 +185,13 @@ final class ErrnoTest: XCTestCase {
XCTAssert(EOPNOTSUPP == Errno.notSupportedOnSocket.rawValue)

// From headers but not man page
#if !os(WASI) // Would need to use _getConst func from CSystem
XCTAssert(EWOULDBLOCK == Errno.wouldBlock.rawValue)
#endif
#if os(Windows)
XCTAssert(WSAETOOMANYREFS == Errno.tooManyReferences.rawValue)
XCTAssert(WSAEREMOTE == Errno.tooManyRemoteLevels.rawValue)
#else
#elseif !os(WASI)
XCTAssert(ETOOMANYREFS == Errno.tooManyReferences.rawValue)
XCTAssert(EREMOTE == Errno.tooManyRemoteLevels.rawValue)
#endif
Expand Down
16 changes: 13 additions & 3 deletions Tests/SystemTests/FileOperationsTest.swift
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/*
This source file is part of the Swift System open source project

Copyright (c) 2020 Apple Inc. and the Swift System project authors
Copyright (c) 2020 - 2025 Apple Inc. and the Swift System project authors
Licensed under Apache License v2.0 with Runtime Library Exception

See https://swift.org/LICENSE.txt for license information
Expand All @@ -16,10 +16,13 @@ import XCTest
#endif
#if canImport(Android)
import Android
#elseif os(WASI)
import CSystem
#endif

@available(/*System 0.0.1: macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0*/iOS 8, *)
final class FileOperationsTest: XCTestCase {
#if !os(WASI) // Would need to use _getConst funcs from CSystem
func testSyscalls() {
let fd = FileDescriptor(rawValue: 1)

Expand Down Expand Up @@ -88,6 +91,7 @@ final class FileOperationsTest: XCTestCase {

for test in syscallTestCases { test.runAllTests() }
}
#endif // !os(WASI)

func testWriteFromEmptyBuffer() throws {
#if os(Windows)
Expand Down Expand Up @@ -153,6 +157,7 @@ final class FileOperationsTest: XCTestCase {
// TODO: Test writeAll, writeAll(toAbsoluteOffset), closeAfter
}

#if !os(WASI) // WASI has no pipe
func testAdHocPipe() throws {
// Ad-hoc test testing `Pipe` functionality.
// We cannot test `Pipe` using `MockTestCase` because it calls `pipe` with a pointer to an array local to the `Pipe`, the address of which we do not know prior to invoking `Pipe`.
Expand All @@ -171,6 +176,7 @@ final class FileOperationsTest: XCTestCase {
}
}
}
#endif

func testAdHocOpen() {
// Ad-hoc test touching a file system.
Expand Down Expand Up @@ -211,8 +217,13 @@ final class FileOperationsTest: XCTestCase {

func testGithubIssues() {
// https://github.com/apple/swift-system/issues/26
#if os(WASI)
let openOptions = _getConst_O_WRONLY() | _getConst_O_CREAT()
#else
let openOptions = O_WRONLY | O_CREAT
#endif
let issue26 = MockTestCase(
name: "open", .interruptable, "a path", O_WRONLY | O_CREAT, 0o020
name: "open", .interruptable, "a path", openOptions, 0o020
) {
retryOnInterrupt in
_ = try FileDescriptor.open(
Expand All @@ -221,7 +232,6 @@ final class FileOperationsTest: XCTestCase {
retryOnInterrupt: retryOnInterrupt)
}
issue26.runAllTests()

}

func testResizeFile() throws {
Expand Down
4 changes: 3 additions & 1 deletion Tests/SystemTests/FileTypesTest.swift
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/*
This source file is part of the Swift System open source project

Copyright (c) 2020 - 2021 Apple Inc. and the Swift System project authors
Copyright (c) 2020 - 2025 Apple Inc. and the Swift System project authors
Licensed under Apache License v2.0 with Runtime Library Exception

See https://swift.org/LICENSE.txt for license information
Expand Down Expand Up @@ -32,13 +32,15 @@ final class FileDescriptorTest: XCTestCase {
XCTAssertEqual(O_WRONLY, FileDescriptor.AccessMode.writeOnly.rawValue)
XCTAssertEqual(O_RDWR, FileDescriptor.AccessMode.readWrite.rawValue)

#if !os(WASI) // Would need to use _getConst funcs from CSystem
#if !os(Windows)
XCTAssertEqual(O_NONBLOCK, FileDescriptor.OpenOptions.nonBlocking.rawValue)
#endif
XCTAssertEqual(O_APPEND, FileDescriptor.OpenOptions.append.rawValue)
XCTAssertEqual(O_CREAT, FileDescriptor.OpenOptions.create.rawValue)
XCTAssertEqual(O_TRUNC, FileDescriptor.OpenOptions.truncate.rawValue)
XCTAssertEqual(O_EXCL, FileDescriptor.OpenOptions.exclusiveCreate.rawValue)
#endif // !os(WASI
#if !os(Windows)
XCTAssertEqual(O_NOFOLLOW, FileDescriptor.OpenOptions.noFollow.rawValue)
XCTAssertEqual(O_CLOEXEC, FileDescriptor.OpenOptions.closeOnExec.rawValue)
Expand Down