From 5d1b2fb985c22819f9e409e4d3ef21d8179ec132 Mon Sep 17 00:00:00 2001 From: Michael Ilseman Date: Fri, 13 Nov 2020 16:57:25 -0800 Subject: [PATCH 001/427] Folders for FilePath and Platform sources --- Sources/System/{ => FilePath}/FilePath.swift | 0 Sources/System/{ => Platform}/DarwinPlatformConstants.swift | 0 Sources/System/{ => Platform}/LinuxPlatformConstants.swift | 0 Sources/System/{ => Platform}/Platform.swift | 0 Sources/System/{ => Platform}/WindowsPlatformConstants.swift | 0 Tests/SystemTests/{ => FilePathTests}/FilePathTest.swift | 0 6 files changed, 0 insertions(+), 0 deletions(-) rename Sources/System/{ => FilePath}/FilePath.swift (100%) rename Sources/System/{ => Platform}/DarwinPlatformConstants.swift (100%) rename Sources/System/{ => Platform}/LinuxPlatformConstants.swift (100%) rename Sources/System/{ => Platform}/Platform.swift (100%) rename Sources/System/{ => Platform}/WindowsPlatformConstants.swift (100%) rename Tests/SystemTests/{ => FilePathTests}/FilePathTest.swift (100%) diff --git a/Sources/System/FilePath.swift b/Sources/System/FilePath/FilePath.swift similarity index 100% rename from Sources/System/FilePath.swift rename to Sources/System/FilePath/FilePath.swift diff --git a/Sources/System/DarwinPlatformConstants.swift b/Sources/System/Platform/DarwinPlatformConstants.swift similarity index 100% rename from Sources/System/DarwinPlatformConstants.swift rename to Sources/System/Platform/DarwinPlatformConstants.swift diff --git a/Sources/System/LinuxPlatformConstants.swift b/Sources/System/Platform/LinuxPlatformConstants.swift similarity index 100% rename from Sources/System/LinuxPlatformConstants.swift rename to Sources/System/Platform/LinuxPlatformConstants.swift diff --git a/Sources/System/Platform.swift b/Sources/System/Platform/Platform.swift similarity index 100% rename from Sources/System/Platform.swift rename to Sources/System/Platform/Platform.swift diff --git a/Sources/System/WindowsPlatformConstants.swift b/Sources/System/Platform/WindowsPlatformConstants.swift similarity index 100% rename from Sources/System/WindowsPlatformConstants.swift rename to Sources/System/Platform/WindowsPlatformConstants.swift diff --git a/Tests/SystemTests/FilePathTest.swift b/Tests/SystemTests/FilePathTests/FilePathTest.swift similarity index 100% rename from Tests/SystemTests/FilePathTest.swift rename to Tests/SystemTests/FilePathTests/FilePathTest.swift From 3367b485e1ef80e554e86f0d90c66ebe3a78e059 Mon Sep 17 00:00:00 2001 From: Michael Ilseman Date: Fri, 13 Nov 2020 17:00:33 -0800 Subject: [PATCH 002/427] Follow test convention of expected as first parameter --- Tests/SystemTests/TestingInfrastructure.swift | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/Tests/SystemTests/TestingInfrastructure.swift b/Tests/SystemTests/TestingInfrastructure.swift index 6c088d47..53367cad 100644 --- a/Tests/SystemTests/TestingInfrastructure.swift +++ b/Tests/SystemTests/TestingInfrastructure.swift @@ -21,7 +21,7 @@ internal protocol TestCase { } extension TestCase { func expectEqualSequence( - _ actual: S1, _ expected: S2, + _ expected: S1, _ actual: S2, _ message: String? = nil ) where S1.Element: Equatable, S1.Element == S2.Element { if !actual.elementsEqual(expected) { @@ -29,7 +29,7 @@ extension TestCase { } } func expectEqual( - _ actual: E, _ expected: E, + _ expected: E, _ actual: E, _ message: String? = nil ) { if actual != expected { @@ -88,7 +88,7 @@ internal struct MockTestCase: TestCase { // Test our API mappings to the lower-level syscall invocation do { try body(true) - self.expectEqual(mocking.trace.dequeue(), self.expected) + self.expectEqual(self.expected, mocking.trace.dequeue()) } catch { self.fail() } @@ -103,7 +103,7 @@ internal struct MockTestCase: TestCase { self.fail() } catch Errno.interrupted { // Success! - self.expectEqual(mocking.trace.dequeue(), self.expected) + self.expectEqual(self.expected, mocking.trace.dequeue()) } catch { self.fail() } @@ -114,13 +114,13 @@ internal struct MockTestCase: TestCase { mocking.forceErrno = .counted(errno: EINTR, count: 3) try body(interruptable) - self.expectEqual(mocking.trace.dequeue(), self.expected) // EINTR - self.expectEqual(mocking.trace.dequeue(), self.expected) // EINTR - self.expectEqual(mocking.trace.dequeue(), self.expected) // EINTR - self.expectEqual(mocking.trace.dequeue(), self.expected) // Success + self.expectEqual(self.expected, mocking.trace.dequeue()) // EINTR + self.expectEqual(self.expected, mocking.trace.dequeue()) // EINTR + self.expectEqual(self.expected, mocking.trace.dequeue()) // EINTR + self.expectEqual(self.expected, mocking.trace.dequeue()) // Success } catch Errno.interrupted { self.expectFalse(interruptable) - self.expectEqual(mocking.trace.dequeue(), self.expected) // EINTR + self.expectEqual(self.expected, mocking.trace.dequeue()) // EINTR } catch { self.fail() } From 79eed1f99d64ffe5c8452e2a0ca1c010b198c1ee Mon Sep 17 00:00:00 2001 From: Michael Ilseman Date: Thu, 5 Nov 2020 17:07:08 -0800 Subject: [PATCH 003/427] Enforce at construction/mutation that FilePath's separators are normalized --- Sources/System/FilePath/FilePath.swift | 76 ++++++------ Sources/System/FilePath/FilePathParsing.swift | 112 ++++++++++++++++++ Sources/System/Util.swift | 24 ++++ 3 files changed, 174 insertions(+), 38 deletions(-) create mode 100644 Sources/System/FilePath/FilePathParsing.swift diff --git a/Sources/System/FilePath/FilePath.swift b/Sources/System/FilePath/FilePath.swift index 2fa7cc83..37d52cab 100644 --- a/Sources/System/FilePath/FilePath.swift +++ b/Sources/System/FilePath/FilePath.swift @@ -7,31 +7,22 @@ See https://swift.org/LICENSE.txt for license information */ -extension UnsafePointer where Pointee == UInt8 { - internal var _asCChar: UnsafePointer { - UnsafeRawPointer(self).assumingMemoryBound(to: CChar.self) - } -} -extension UnsafePointer where Pointee == CChar { - internal var _asUInt8: UnsafePointer { - UnsafeRawPointer(self).assumingMemoryBound(to: UInt8.self) - } -} -extension UnsafeBufferPointer where Element == UInt8 { - internal var _asCChar: UnsafeBufferPointer { - let base = baseAddress?._asCChar - return UnsafeBufferPointer(start: base, count: self.count) - } -} -extension UnsafeBufferPointer where Element == CChar { - internal var _asUInt8: UnsafeBufferPointer { - let base = baseAddress?._asUInt8 - return UnsafeBufferPointer(start: base, count: self.count) - } -} - // NOTE: FilePath not frozen for ABI flexibility +// A platform-native string representation, for file paths +#if os(Windows) + internal typealias SystemString = [UInt16] +#else + internal typealias SystemString = [CChar] +#endif + +// TODO: Adjust comment header to de-emphasize the null-terminated +// bytes part. This is a type that represents a location in the file +// system, and can vend null-terminated bytes. Also, we will be +// normalizing the separator representation, so it needs to pretty +// much be re-written. + + /// A null-terminated sequence of bytes /// that represents a location in the file system. /// @@ -59,13 +50,24 @@ extension UnsafeBufferPointer where Element == CChar { /// like case insensitivity, Unicode normalization, and symbolic links. // @available(macOS 10.16, iOS 14.0, watchOS 7.0, tvOS 14.0, *) public struct FilePath { - internal var bytes: [CChar] + internal typealias Storage = SystemString + + internal var bytes: Storage /// Creates an empty, null-terminated path. public init() { self.bytes = [0] _invariantCheck() } + + // In addition to the empty init, this init will properly normalize + // separators. All other initializers should be implemented by + // ultimately deferring to a normalizing init. + internal init(nullTerminatedBytes: C) where C.Element == CChar { + self.bytes = Array(nullTerminatedBytes) + self._normalizeSeparators() + _invariantCheck() + } } // @@ -79,12 +81,7 @@ extension FilePath { // @available(macOS 10.16, iOS 14.0, watchOS 7.0, tvOS 14.0, *) extension FilePath { - internal init(nullTerminatedBytes: C) where C.Element == CChar { - self.bytes = Array(nullTerminatedBytes) - _invariantCheck() - } - - internal init(byteContents bytes: C) where C.Element == CChar { + fileprivate init(byteContents bytes: C) where C.Element == CChar { var nulTermBytes = Array(bytes) nulTermBytes.append(0) self.init(nullTerminatedBytes: nulTermBytes) @@ -129,6 +126,11 @@ extension FilePath { // byte contents and unterminated byte contents. } +#if os(Windows) + // TODO: wchar_t* initializers, interfaces, etc. + // TODO: Consider eventually doing OSString +#endif + // // MARK: - String interfaces // @@ -186,11 +188,16 @@ extension String { } self = str } + + // TODO: Consider a init?(validating:), keeping the encoding agnostic in API and + // dependent on file system. } // @available(macOS 10.16, iOS 14.0, watchOS 7.0, tvOS 14.0, *) extension FilePath: CustomStringConvertible, CustomDebugStringConvertible { - /// A textual representation of the file path. + /// A textual representation of the file path using the platform's preferred separator. + /// + /// On Unix, the preferred separator is `/`. On Windows, the preferred separator is `\` @inline(never) public var description: String { String(decoding: self) } @@ -201,10 +208,3 @@ extension FilePath: CustomStringConvertible, CustomDebugStringConvertible { // @available(macOS 10.16, iOS 14.0, watchOS 7.0, tvOS 14.0, *) extension FilePath: Hashable, Codable {} -// @available(macOS 10.16, iOS 14.0, watchOS 7.0, tvOS 14.0, *) -extension FilePath { - fileprivate func _invariantCheck() { - precondition(bytes.last! == 0) - _debugPrecondition(bytes.firstIndex(of: 0) == bytes.count - 1) - } -} diff --git a/Sources/System/FilePath/FilePathParsing.swift b/Sources/System/FilePath/FilePathParsing.swift new file mode 100644 index 00000000..43bc4ecd --- /dev/null +++ b/Sources/System/FilePath/FilePathParsing.swift @@ -0,0 +1,112 @@ +/* + This source file is part of the Swift System open source project + + Copyright (c) 2020 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 +*/ + +private typealias SystemChar = SystemString.Element + +// The separator we use internally +private var genericSeparator: SystemChar { + numericCast(UInt8(ascii: "/")) +} + +// The platform preferred separator +// +// TODO: Make private +private var platformSeparator: SystemChar { +#if os(Windows) + return numericCast(UInt8(ascii: "\\")) +#else + return genericSeparator +#endif +} + +// TODO: Make private +private func isSeparator(_ c: SystemChar) -> Bool { + c == genericSeparator || c == platformSeparator +} + +// @available(macOS 10.16, iOS 14.0, watchOS 7.0, tvOS 14.0, *) +extension FilePath { + // For invariant enforcing/checking. Should always return `nil` on + // a fully-formed path + private func _trailingSepIdx() -> Storage.Index? { + guard bytes.count > 2 else { return nil } + let idx = bytes.index(bytes.endIndex, offsetBy: -2) + return isSeparator(bytes[idx]) ? idx : nil + } + + // Enforce invariants by removing a trailing separator. + // + // Precondition: There is exactly zero or one trailing slashes + // + // Postcondition: Path is root, or has no trailing separator + private mutating func _removeTrailingSeparator() { + assert(bytes.last == 0, "even empty paths have null termiantor") + defer { assert(_trailingSepIdx() == nil) } + + guard let sepIdx = _trailingSepIdx() else { return } + bytes.remove(at: sepIdx) + + } + + // Enforce invariants by normalizing the internal separator representation. + // + // 1) Normalize all separators to platform-preferred separator + // 2) Drop redundant separators + // 3) Drop trailing separators + // + // On Windows, UNC and device paths are allowed to begin with two separators + // + // The POSIX standard does allow two leading separators to + // denote implementation-specific handling, but Darwin and Linux + // do not treat these differently. + // + internal mutating func _normalizeSeparators() { + var (writeIdx, readIdx) = (bytes.startIndex, bytes.startIndex) + #if os(Windows) + // TODO: skip over two leading backslashes + #endif + + while readIdx < bytes.endIndex { + assert(writeIdx <= readIdx) + + let wasSeparator = isSeparator(bytes[readIdx]) + if wasSeparator { + bytes[readIdx] = platformSeparator + } + bytes.swapAt(writeIdx, readIdx) + bytes.formIndex(after: &writeIdx) + bytes.formIndex(after: &readIdx) + + if readIdx == bytes.endIndex { break } + + while wasSeparator && isSeparator(bytes[readIdx]) { + bytes.formIndex(after: &readIdx) + if readIdx == bytes.endIndex { break } + } + } + bytes.removeLast(bytes.distance(from: writeIdx, to: readIdx)) + self._removeTrailingSeparator() + } + + internal func _invariantCheck() { + #if DEBUG + precondition(bytes.last! == 0) + + // TODO: Should this be a hard trap unconditionally?? + precondition(bytes.firstIndex(of: 0) == bytes.count - 1) + + var normal = self + normal._normalizeSeparators() + precondition(self == normal) + precondition(self._trailingSepIdx() == nil) + + #endif // DEBUG + } +} + diff --git a/Sources/System/Util.swift b/Sources/System/Util.swift index 2d966784..00bcd7d7 100644 --- a/Sources/System/Util.swift +++ b/Sources/System/Util.swift @@ -94,3 +94,27 @@ extension OptionSet { return result } } + +extension UnsafePointer where Pointee == UInt8 { + internal var _asCChar: UnsafePointer { + UnsafeRawPointer(self).assumingMemoryBound(to: CChar.self) + } +} +extension UnsafePointer where Pointee == CChar { + internal var _asUInt8: UnsafePointer { + UnsafeRawPointer(self).assumingMemoryBound(to: UInt8.self) + } +} +extension UnsafeBufferPointer where Element == UInt8 { + internal var _asCChar: UnsafeBufferPointer { + let base = baseAddress?._asCChar + return UnsafeBufferPointer(start: base, count: self.count) + } +} +extension UnsafeBufferPointer where Element == CChar { + internal var _asUInt8: UnsafeBufferPointer { + let base = baseAddress?._asUInt8 + return UnsafeBufferPointer(start: base, count: self.count) + } +} + From 920ef3085d4c0a3abad12bdaa265e0670db98fc0 Mon Sep 17 00:00:00 2001 From: Michael Ilseman Date: Mon, 1 Feb 2021 08:03:10 -0700 Subject: [PATCH 004/427] Add PlatformString and Windows support Add CInterop namespace and PlatformChar/PlatformEncoding typealiases for narrow/wide characters. Change Windows syscalls to use wide-character variants. Add full Windows root parser to error-correct malformed roots and correctly normalize roots and post-root portions of the path. Add platformString APIs as replacements for cString APIs. Add internal SystemString as backing storage type for FilePath. Add Windows path mocking support. This change includes API/ABI additions and deprecations. --- Sources/System/FileOperations.swift | 6 +- Sources/System/FilePath/FilePath.swift | 179 +------ Sources/System/FilePath/FilePathParsing.swift | 259 ++++++++-- Sources/System/FilePath/FilePathString.swift | 184 +++++++ Sources/System/FilePath/FilePathWindows.swift | 465 ++++++++++++++++++ Sources/System/FilePermissions.swift | 6 +- Sources/System/Platform/Platform.swift | 26 +- Sources/System/Platform/PlatformString.swift | 114 +++++ Sources/System/SystemString.swift | 289 +++++++++++ Sources/System/Util.swift | 46 +- Sources/System/UtilConsumers.swift | 73 +++ Sources/SystemInternals/Exports.swift | 79 +++ Sources/SystemInternals/Mocking.swift | 16 + Sources/SystemInternals/Syscalls.swift | 14 +- .../WindowsSyscallAdapters.swift | 10 +- Tests/SystemTests/FileOperationsTest.swift | 2 +- .../FilePathTests/FilePathTest.swift | 17 +- Tests/SystemTests/SystemStringTests.swift | 244 +++++++++ Tests/SystemTests/TestingInfrastructure.swift | 63 ++- 19 files changed, 1822 insertions(+), 270 deletions(-) create mode 100644 Sources/System/FilePath/FilePathString.swift create mode 100644 Sources/System/FilePath/FilePathWindows.swift create mode 100644 Sources/System/Platform/PlatformString.swift create mode 100644 Sources/System/SystemString.swift create mode 100644 Sources/System/UtilConsumers.swift create mode 100644 Tests/SystemTests/SystemStringTests.swift diff --git a/Sources/System/FileOperations.swift b/Sources/System/FileOperations.swift index b1ce2358..83ce9bfa 100644 --- a/Sources/System/FileOperations.swift +++ b/Sources/System/FileOperations.swift @@ -33,7 +33,7 @@ extension FileDescriptor { permissions: FilePermissions? = nil, retryOnInterrupt: Bool = true ) throws -> FileDescriptor { - try path.withCString { + try path.withPlatformString { try FileDescriptor.open( $0, mode, options: options, permissions: permissions, retryOnInterrupt: retryOnInterrupt) } @@ -55,7 +55,7 @@ extension FileDescriptor { /// The corresponding C function is `open`. @_alwaysEmitIntoClient public static func open( - _ path: UnsafePointer, + _ path: UnsafePointer, _ mode: FileDescriptor.AccessMode, options: FileDescriptor.OpenOptions = FileDescriptor.OpenOptions(), permissions: FilePermissions? = nil, @@ -68,7 +68,7 @@ extension FileDescriptor { @usableFromInline internal static func _open( - _ path: UnsafePointer, + _ path: UnsafePointer, _ mode: FileDescriptor.AccessMode, options: FileDescriptor.OpenOptions, permissions: FilePermissions?, diff --git a/Sources/System/FilePath/FilePath.swift b/Sources/System/FilePath/FilePath.swift index 37d52cab..a678ff59 100644 --- a/Sources/System/FilePath/FilePath.swift +++ b/Sources/System/FilePath/FilePath.swift @@ -7,29 +7,18 @@ See https://swift.org/LICENSE.txt for license information */ -// NOTE: FilePath not frozen for ABI flexibility - -// A platform-native string representation, for file paths -#if os(Windows) - internal typealias SystemString = [UInt16] -#else - internal typealias SystemString = [CChar] -#endif - -// TODO: Adjust comment header to de-emphasize the null-terminated -// bytes part. This is a type that represents a location in the file -// system, and can vend null-terminated bytes. Also, we will be -// normalizing the separator representation, so it needs to pretty -// much be re-written. - - -/// A null-terminated sequence of bytes -/// that represents a location in the file system. +/// Represents a location in the file system. +/// +/// This structure recognizes directory separators (e.g. `/`), roots, and +/// requires that the content terminates in a NUL (`0x0`). Beyond that, it +/// does not give any meaning to the bytes that it contains. The file system +/// defines how the content is interpreted; for example, by its choice of string +/// encoding. /// -/// This structure doesn't give any meaning to the bytes that it contains, -/// except for the requirement that the last byte is a NUL (`0x0`). -/// The file system defines how this string is interpreted; -/// for example, by its choice of string encoding. +/// On construction, `FilePath` will normalize separators by removing +/// reduncant intermediary separators and stripping any trailing separators. +/// On Windows, `FilePath` will also normalize forward slashes `/` into +/// backslashes `\`, as preferred by the platform. /// /// The code below creates a file path from a string literal, /// and then uses it to open and append to a log file: @@ -39,6 +28,9 @@ /// let fd = try FileDescriptor.open(path, .writeOnly, options: .append) /// try fd.closeAfter { try fd.writeAll(message.utf8) } /// +/// TODO(docs): Section on all the new syntactic operations, lexical normalization, decomposition, +/// components, etc. +/// /// File paths conform to the /// and /// and protocols @@ -50,159 +42,28 @@ /// like case insensitivity, Unicode normalization, and symbolic links. // @available(macOS 10.16, iOS 14.0, watchOS 7.0, tvOS 14.0, *) public struct FilePath { - internal typealias Storage = SystemString - - internal var bytes: Storage + internal var _storage: SystemString /// Creates an empty, null-terminated path. public init() { - self.bytes = [0] + self._storage = SystemString() _invariantCheck() } // In addition to the empty init, this init will properly normalize // separators. All other initializers should be implemented by // ultimately deferring to a normalizing init. - internal init(nullTerminatedBytes: C) where C.Element == CChar { - self.bytes = Array(nullTerminatedBytes) + internal init(_ str: SystemString) { + self._storage = str self._normalizeSeparators() _invariantCheck() } } -// -// MARK: - Public Interfaces -// -// @available(macOS 10.16, iOS 14.0, watchOS 7.0, tvOS 14.0, *) -extension FilePath { - /// The length of the file path, excluding the null termination. - public var length: Int { bytes.count - 1 } -} - // @available(macOS 10.16, iOS 14.0, watchOS 7.0, tvOS 14.0, *) extension FilePath { - fileprivate init(byteContents bytes: C) where C.Element == CChar { - var nulTermBytes = Array(bytes) - nulTermBytes.append(0) - self.init(nullTerminatedBytes: nulTermBytes) - } -} - -@_implementationOnly import SystemInternals - -// -// MARK: - CString interfaces -// -// @available(macOS 10.16, iOS 14.0, watchOS 7.0, tvOS 14.0, *) -extension FilePath { - /// Creates a file path by copying bytes from a null-terminated C string. - /// - /// - Parameter cString: A pointer to a null-terminated C string. - public init(cString: UnsafePointer) { - self.init(nullTerminatedBytes: - UnsafeBufferPointer(start: cString, count: 1 + system_strlen(cString))) - } - - /// Calls the given closure with a pointer to the contents of the file path, - /// represented as a null-terminated C string. - /// - /// - Parameter body: A closure with a pointer parameter - /// that points to a null-terminated C string. - /// If `body` has a return value, - /// that value is also used as the return value for this method. - /// - Returns: The return value, if any, of the `body` closure parameter. - /// - /// The pointer passed as an argument to `body` is valid - /// only during the execution of this method. - /// Don't try to store the pointer for later use. - public func withCString( - _ body: (UnsafePointer) throws -> Result - ) rethrows -> Result { - try bytes.withUnsafeBufferPointer { try body($0.baseAddress!) } - } - - // TODO: in the future, with opaque result types with associated - // type constraints, we want to provide a RAC for terminated - // byte contents and unterminated byte contents. -} - -#if os(Windows) - // TODO: wchar_t* initializers, interfaces, etc. - // TODO: Consider eventually doing OSString -#endif - -// -// MARK: - String interfaces -// -// @available(macOS 10.16, iOS 14.0, watchOS 7.0, tvOS 14.0, *) -extension FilePath: ExpressibleByStringLiteral { - /// Creates a file path from a string literal. - /// - /// - Parameter stringLiteral: A string literal - /// whose UTF-8 contents to use as the contents of the path. - public init(stringLiteral: String) { - self.init(stringLiteral) - } - - /// Creates a file path from a string. - /// - /// - Parameter string: A string - /// whose UTF-8 contents to use as the contents of the path. - public init(_ string: String) { - var str = string - self = str.withUTF8 { FilePath(byteContents: $0._asCChar) } - } -} - -// @available(macOS 10.16, iOS 14.0, watchOS 7.0, tvOS 14.0, *) -extension String { - /// Creates a string by interpreting the file path's content as UTF-8. - /// - /// - Parameter path: The file path to be interpreted as UTF-8. - /// - /// If the content of the file path - /// isn't a well-formed UTF-8 string, - /// this initializer removes invalid bytes or replaces them with U+FFFD. - /// This means that, depending on the semantics of the specific file system, - /// conversion to a string and back to a path - /// might result in a value that's different from the original path. - public init(decoding path: FilePath) { - self = path.withCString { String(cString: $0) } - } - - @available(*, deprecated, renamed: "String.init(decoding:)") - public init(_ path: FilePath) { - self.init(decoding: path) - } - - /// Creates a string from a file path, validating its UTF-8 contents. - /// - /// - Parameter path: The file path be interpreted as UTF-8. - /// - /// If the contents of the file path - /// isn't a well-formed UTF-8 string, - /// this initializer returns `nil`. - public init?(validatingUTF8 path: FilePath) { - guard let str = path.withCString({ String(validatingUTF8: $0) }) else { - return nil - } - self = str - } - - // TODO: Consider a init?(validating:), keeping the encoding agnostic in API and - // dependent on file system. -} - -// @available(macOS 10.16, iOS 14.0, watchOS 7.0, tvOS 14.0, *) -extension FilePath: CustomStringConvertible, CustomDebugStringConvertible { - /// A textual representation of the file path using the platform's preferred separator. - /// - /// On Unix, the preferred separator is `/`. On Windows, the preferred separator is `\` - @inline(never) - public var description: String { String(decoding: self) } - - /// A textual representation of the file path, suitable for debugging. - public var debugDescription: String { self.description.debugDescription } + /// The length of the file path, excluding the null terminator. + public var length: Int { _storage.length } } // @available(macOS 10.16, iOS 14.0, watchOS 7.0, tvOS 14.0, *) diff --git a/Sources/System/FilePath/FilePathParsing.swift b/Sources/System/FilePath/FilePathParsing.swift index 43bc4ecd..a097c740 100644 --- a/Sources/System/FilePath/FilePathParsing.swift +++ b/Sources/System/FilePath/FilePathParsing.swift @@ -7,37 +7,42 @@ See https://swift.org/LICENSE.txt for license information */ -private typealias SystemChar = SystemString.Element +// FIXME: Need to rewrite and simplify this code now that SystemString +// manages (and hides) the null terminator // The separator we use internally -private var genericSeparator: SystemChar { - numericCast(UInt8(ascii: "/")) -} +private var genericSeparator: SystemChar { .slash } // The platform preferred separator // // TODO: Make private -private var platformSeparator: SystemChar { -#if os(Windows) - return numericCast(UInt8(ascii: "\\")) -#else - return genericSeparator -#endif +internal var platformSeparator: SystemChar { + _windowsPaths ? .backslash : genericSeparator } +// Whether the character is the canonical separator // TODO: Make private -private func isSeparator(_ c: SystemChar) -> Bool { +internal func isSeparator(_ c: SystemChar) -> Bool { + c == platformSeparator +} + +// Whether the character is a pre-normalized separator +internal func isPrenormalSeparator(_ c: SystemChar) -> Bool { c == genericSeparator || c == platformSeparator } -// @available(macOS 10.16, iOS 14.0, watchOS 7.0, tvOS 14.0, *) -extension FilePath { - // For invariant enforcing/checking. Should always return `nil` on +// Separator normalization, checking, and root parsing is internally hosted +// on SystemString for ease of unit testing. + +extension SystemString { + // For invariant enforcing/checking. Should always return false on // a fully-formed path - private func _trailingSepIdx() -> Storage.Index? { - guard bytes.count > 2 else { return nil } - let idx = bytes.index(bytes.endIndex, offsetBy: -2) - return isSeparator(bytes[idx]) ? idx : nil + fileprivate func _hasTrailingSeparator() -> Bool { + // Just a root: do nothing + guard _relativePathStart != endIndex else { return false } + assert(!isEmpty) + + return isSeparator(self.last!) } // Enforce invariants by removing a trailing separator. @@ -45,13 +50,11 @@ extension FilePath { // Precondition: There is exactly zero or one trailing slashes // // Postcondition: Path is root, or has no trailing separator - private mutating func _removeTrailingSeparator() { - assert(bytes.last == 0, "even empty paths have null termiantor") - defer { assert(_trailingSepIdx() == nil) } - - guard let sepIdx = _trailingSepIdx() else { return } - bytes.remove(at: sepIdx) - + internal mutating func _removeTrailingSeparator() { + if _hasTrailingSeparator() { + self.removeLast() + assert(!_hasTrailingSeparator()) + } } // Enforce invariants by normalizing the internal separator representation. @@ -60,53 +63,205 @@ extension FilePath { // 2) Drop redundant separators // 3) Drop trailing separators // - // On Windows, UNC and device paths are allowed to begin with two separators + // On Windows, UNC and device paths are allowed to begin with two separators, + // and partial or mal-formed roots are completed. // // The POSIX standard does allow two leading separators to // denote implementation-specific handling, but Darwin and Linux // do not treat these differently. // internal mutating func _normalizeSeparators() { - var (writeIdx, readIdx) = (bytes.startIndex, bytes.startIndex) - #if os(Windows) - // TODO: skip over two leading backslashes - #endif + guard !isEmpty else { return } + var (writeIdx, readIdx) = (startIndex, startIndex) - while readIdx < bytes.endIndex { + if _windowsPaths { + // Normalize forwards slashes to backslashes. + // + // NOTE: Ideally this would be done as part of separator coalescing + // below. However, prenormalizing roots such as UNC paths requires + // parsing and (potentially) fixing up semi-formed roots. This + // normalization reduces the complexity of the task by allowing us to + // use a read-only lexer. + self._replaceAll(genericSeparator, with: platformSeparator) + + // Windows roots can have meaningful repeated backslashes or may + // need backslashes inserted for partially-formed roots. Delegate that to + // `_prenormalizeWindowsRoots` and resume. + readIdx = _prenormalizeWindowsRoots() + writeIdx = readIdx + } else { + assert(genericSeparator == platformSeparator) + } + + while readIdx < endIndex { assert(writeIdx <= readIdx) - let wasSeparator = isSeparator(bytes[readIdx]) - if wasSeparator { - bytes[readIdx] = platformSeparator + // Swap and advance our indices. + let wasSeparator = isSeparator(self[readIdx]) + self.swapAt(writeIdx, readIdx) + self.formIndex(after: &writeIdx) + self.formIndex(after: &readIdx) + + while wasSeparator, readIdx < endIndex, isSeparator(self[readIdx]) { + self.formIndex(after: &readIdx) } - bytes.swapAt(writeIdx, readIdx) - bytes.formIndex(after: &writeIdx) - bytes.formIndex(after: &readIdx) + } + self.removeLast(self.distance(from: writeIdx, to: readIdx)) + self._removeTrailingSeparator() + } +} + +// @available(macOS 10.16, iOS 14.0, watchOS 7.0, tvOS 14.0, *) +extension FilePath { + internal mutating func _removeTrailingSeparator() { + _storage._removeTrailingSeparator() + } + + internal mutating func _normalizeSeparators() { + _storage._normalizeSeparators() + } + +} - if readIdx == bytes.endIndex { break } +extension SystemString { + internal var _relativePathStart: Index { + _parseRoot().relativeBegin + } +} - while wasSeparator && isSeparator(bytes[readIdx]) { - bytes.formIndex(after: &readIdx) - if readIdx == bytes.endIndex { break } +extension FilePath { + internal var _relativeStart: SystemString.Index { + _storage._relativePathStart + } + internal var _hasRoot: Bool { + _relativeStart != _storage.startIndex + } +} + +// Parse separators + +extension FilePath { + internal typealias _Index = SystemString.Index + + // Parse a component that starts at `i`. Returns the end + // of the component and the start of the next. Parsing terminates + // at the index of the null byte. + internal func _parseComponent( + startingAt i: _Index + ) -> (componentEnd: _Index, nextStart: _Index) { + assert(i < _storage.endIndex) + // Parse the root + if i == _storage.startIndex { + let relativeStart = _relativeStart + if i != relativeStart { + return (relativeStart, relativeStart) } } - bytes.removeLast(bytes.distance(from: writeIdx, to: readIdx)) - self._removeTrailingSeparator() + + assert(!isSeparator(_storage[i])) + guard let nextSep = _storage[i...].firstIndex(where: isSeparator) else { + return (_storage.endIndex, _storage.endIndex) + } + return (nextSep, _storage.index(after: nextSep)) } - internal func _invariantCheck() { - #if DEBUG - precondition(bytes.last! == 0) + // Parse a component prior to the one that starts at `i`. Returns + // the start of the prior component. If `i` is the index of null, + // returns the last component. + internal func _parseComponent( + priorTo i: _Index + ) -> Range<_Index> { + precondition(i > _storage.startIndex) + let relStart = _relativeStart + + if i == relStart { return _storage.startIndex.. relStart) + + var slice = _storage[..) -> Bool { + _storage[component].elementsEqual([.dot]) + } + + internal func _isParentDirectory(_ component: Range<_Index>) -> Bool { + _storage[component].elementsEqual([.dot, .dot]) + } + + internal func _isSpecialDirectory(_ component: Range<_Index>) -> Bool { + _isCurrentDirectory(component) || _isParentDirectory(component) + } +} + +extension SystemString { + internal func _parseRoot() -> ( + rootEnd: Index, relativeBegin: Index + ) { + guard !isEmpty else { return (startIndex, startIndex) } + + // Windows roots are more complex + if _windowsPaths { return _parseWindowsRoot() } + + // A leading `/` is a root + guard isSeparator(self.first!) else { return (startIndex, startIndex) } - // TODO: Should this be a hard trap unconditionally?? - precondition(bytes.firstIndex(of: 0) == bytes.count - 1) + let next = self.index(after: startIndex) + return (next, next) + } +} + +extension FilePath { + internal var _portableDescription: String { + guard _windowsPaths else { return description } + let utf8 = description.utf8.map { $0 == UInt8(ascii: #"\"#) ? UInt8(ascii: "/") : $0 } + return String(decoding: utf8, as: UTF8.self) + } +} + +// Whether we are providing Windows paths +@_implementationOnly import var SystemInternals.forceWindowsPaths +@inline(__always) +internal var _windowsPaths: Bool { + #if os(Windows) + return true + #else + return forceWindowsPaths + #endif +} + +extension FilePath { + // Whether we should add a separator when doing an append + internal var _needsSeparatorForAppend: Bool { + guard let last = _storage.last, !isSeparator(last) else { return false } + // On Windows, we can have a path of the form `C:` which is a root and + // does not need a separator after it + if _windowsPaths && _relativeStart == _storage.endIndex { + return false + } + + return true + } +} + +// MARK: - Invariants +extension FilePath { + internal func _invariantCheck() { + #if DEBUG var normal = self normal._normalizeSeparators() precondition(self == normal) - precondition(self._trailingSepIdx() == nil) - - #endif // DEBUG + precondition(!self._storage._hasTrailingSeparator()) + #endif // DEBUG } } - diff --git a/Sources/System/FilePath/FilePathString.swift b/Sources/System/FilePath/FilePathString.swift new file mode 100644 index 00000000..5607b6f0 --- /dev/null +++ b/Sources/System/FilePath/FilePathString.swift @@ -0,0 +1,184 @@ +/* + This source file is part of the Swift System open source project + + Copyright (c) 2020 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 +*/ + +// MARK: - Platform string + +// @available(macOS 10.16, iOS 14.0, watchOS 7.0, tvOS 14.0, *) +extension FilePath { + /// Creates a file path by copying bytes from a null-terminated platform + /// string. + /// + /// - Parameter platformString: A pointer to a null-terminated platform + /// string. + public init(platformString: UnsafePointer) { + self.init(_platformString: platformString) + } + + /// Calls the given closure with a pointer to the contents of the file path, + /// represented as a null-terminated platform string. + /// + /// - Parameter body: A closure with a pointer parameter + /// that points to a null-terminated platform string. + /// If `body` has a return value, + /// that value is also used as the return value for this method. + /// - Returns: The return value, if any, of the `body` closure parameter. + /// + /// The pointer passed as an argument to `body` is valid + /// only during the execution of this method. + /// Don't try to store the pointer for later use. + public func withPlatformString( + _ body: (UnsafePointer) throws -> Result + ) rethrows -> Result { + try _withPlatformString(body) + } +} + +// MARK: - String literals + +// @available(macOS 10.16, iOS 14.0, watchOS 7.0, tvOS 14.0, *) +extension FilePath: ExpressibleByStringLiteral { + /// Creates a file path from a string literal. + /// + /// - Parameter stringLiteral: A string literal + /// whose Unicode encoded contents to use as the contents of the path. + public init(stringLiteral: String) { + self.init(stringLiteral) + } + + /// Creates a file path from a string. + /// + /// - Parameter string: A string + /// whose Unicode encoded contents to use as the contents of the path. + public init(_ string: String) { + self.init(SystemString(string)) + } +} + +// MARK: - Printing and dumping + +// @available(macOS 10.16, iOS 14.0, watchOS 7.0, tvOS 14.0, *) +extension FilePath: CustomStringConvertible, CustomDebugStringConvertible { + /// A textual representation of the file path. + /// + /// If the content of the path isn't a well-formed Unicode string, + /// this replaces invalid bytes with U+FFFD. See `String.init(decoding:)` + @inline(never) + public var description: String { String(decoding: self) } + + /// A textual representation of the file path, suitable for debugging. + /// + /// If the content of the path isn't a well-formed Unicode string, + /// this replaces invalid bytes with U+FFFD. See `String.init(decoding:)` + public var debugDescription: String { description.debugDescription } +} + +// MARK: - Convenience helpers + +// Convenience helpers +extension FilePath { + /// Creates a string by interpreting the path’s content as UTF-8 on Unix + /// and UTF-16 on Windows. + /// + /// This property is equivalent to calling `String(decoding: path)` + public var string: String { + String(decoding: self) + } +} + +// MARK: - Decoding and validating + +// @available(macOS 10.16, iOS 14.0, watchOS 7.0, tvOS 14.0, *) +extension String { + /// Creates a string by interpreting the file path's content as UTF-8 on Unix + /// and UTF-16 on Windows. + /// + /// - Parameter path: The file path to be interpreted as + /// `CInterop.PlatformUnicodeEncoding`. + /// + /// If the content of the file path isn't a well-formed Unicode string, + /// this initializer replaces invalid bytes with U+FFFD. + /// This means that, depending on the semantics of the specific file system, + /// conversion to a string and back to a path + /// might result in a value that's different from the original path. + public init(decoding path: FilePath) { + self.init(_decoding: path) + } + + /// Creates a string from a file path, validating its contents as UTF-8 on + /// Unix and UTF-16 on Windows. + /// + /// - Parameter path: The file path to be interpreted as + /// `CInterop.PlatformUnicodeEncoding`. + /// + /// If the contents of the file path isn't a well-formed Unicode string, + /// this initializer returns `nil`. + public init?(validating path: FilePath) { + self.init(_validating: path) + } +} + +// MARK: - Internal helpers + +extension String { + fileprivate init(_decoding ps: PS) { + self = ps._withPlatformString { String(platformString: $0) } + } + + fileprivate init?(_validating ps: PS) { + guard let str = ps._withPlatformString( + String.init(validatingPlatformString:) + ) else { + return nil + } + self = str + } +} + +extension FilePath: _PlatformStringable { + func _withPlatformString(_ body: (UnsafePointer) throws -> Result) rethrows -> Result { + try _storage.withPlatformString(body) + } + + init(_platformString: UnsafePointer) { + self.init(SystemString(platformString: _platformString)) + } + + } + +// MARK: - Deprecations + +extension String { + @available(*, deprecated, renamed: "init(decoding:)") + public init(_ path: FilePath) { self.init(decoding: path) } + + @available(*, deprecated, renamed: "init(validating:)") + public init?(validatingUTF8 path: FilePath) { self.init(validating: path) } +} + +extension FilePath { + @available(*, deprecated, renamed: "init(platformString:)") + public init(cString: UnsafePointer) { + #if os(Windows) + fatalError("FilePath.init(cString:) unsupported on Windows ") + #else + self.init(platformString: cString) + #endif + } + + @available(*, deprecated, renamed: "withPlatformString(_:)") + public func withCString( + _ body: (UnsafePointer) throws -> Result + ) rethrows -> Result { + #if os(Windows) + fatalError("FilePath.withCString() unsupported on Windows ") + #else + return try withPlatformString(body) + #endif + } +} diff --git a/Sources/System/FilePath/FilePathWindows.swift b/Sources/System/FilePath/FilePathWindows.swift new file mode 100644 index 00000000..0d3216f6 --- /dev/null +++ b/Sources/System/FilePath/FilePathWindows.swift @@ -0,0 +1,465 @@ +/* + This source file is part of the Swift System open source project + + Copyright (c) 2020 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 +*/ + +internal struct _ParsedWindowsRoot { + var rootEnd: SystemString.Index + + // TODO: Remove when I normalize to always (except `C:`) + // have trailing separator + var relativeBegin: SystemString.Index + + var drive: SystemChar? + var fullyQualified: Bool + + var deviceSigil: SystemChar? + + var host: Range? + var volume: Range? +} + +extension _ParsedWindowsRoot { + static func traditional( + drive: SystemChar?, fullQualified: Bool, endingAt idx: SystemString.Index + ) -> _ParsedWindowsRoot { + _ParsedWindowsRoot( + rootEnd: idx, + relativeBegin: idx, + drive: drive, + fullyQualified: fullQualified, + deviceSigil: nil, + host: nil, + volume: nil) + } + + static func unc( + deviceSigil: SystemChar?, + server: Range, + share: Range, + endingAt end: SystemString.Index, + relativeBegin relBegin: SystemString.Index + ) -> _ParsedWindowsRoot { + _ParsedWindowsRoot( + rootEnd: end, + relativeBegin: relBegin, + drive: nil, + fullyQualified: true, + deviceSigil: deviceSigil, + host: server, + volume: share) + } + + static func device( + deviceSigil: SystemChar, + volume: Range, + endingAt end: SystemString.Index, + relativeBegin relBegin: SystemString.Index + ) -> _ParsedWindowsRoot { + _ParsedWindowsRoot( + rootEnd: end, + relativeBegin: relBegin, + drive: nil, + fullyQualified: true, + deviceSigil: deviceSigil, + host: nil, + volume: volume) + } +} + +struct _Lexer { + var slice: Slice + + init(_ str: SystemString) { + self.slice = str[...] + } + + var backslash: SystemChar { .backslash } + + // Try to eat a backslash, returns false if nothing happened + mutating func eatBackslash() -> Bool { + slice._eat(.backslash) != nil + } + + // Try to consume a drive letter and subsequent `:`. + mutating func eatDrive() -> SystemChar? { + let copy = slice + if let d = slice._eat(if: { $0.isLetter }), slice._eat(.colon) != nil { + return d + } + // Restore slice + slice = copy + return nil + } + + // Try to consume a device sigil (stand-alone . or ?) + mutating func eatSigil() -> SystemChar? { + let copy = slice + guard let sigil = slice._eat(.question) ?? slice._eat(.dot) else { + return nil + } + + // Check for something like .hidden or ?question + guard isEmpty || slice.first == backslash else { + slice = copy + return nil + } + + return sigil + } + + // Try to consume an explicit "UNC" directory + mutating func eatUNC() -> Bool { + slice._eatSequence("UNC".unicodeScalars.lazy.map { SystemChar(ascii: $0) }) != nil + } + + // Eat everything up to but not including a backslash or null + mutating func eatComponent() -> Range { + let backslash = self.backslash + let component = slice._eatWhile({ $0 != backslash }) + ?? slice[slice.startIndex ..< slice.startIndex] + return component.indices + } + + var isEmpty: Bool { + return slice.isEmpty + } + + var current: SystemString.Index { slice.startIndex } + + mutating func clear() { + // TODO: Intern empty system string + self = _Lexer(SystemString()) + } + + mutating func reset(to: SystemString, at: SystemString.Index) { + self.slice = to[at...] + } +} + +internal struct WindowsRootInfo { + // The "volume" of a root. For UNC paths, this is also known as the "share". + internal enum Volume: Equatable { + /// No volume specified + /// + /// * Traditional root relative to the current drive: `\`, + /// * Omitted volume from other forms: `\\.\`, `\\.\UNC\server\\`, `\\server\\` + case empty + + /// A specified drive. + /// + /// * Traditional disk: `C:\`, `C:` + /// * Device disk: `\\.\C:\`, `\\?\C:\` + /// * UNC: `\\server\e:\`, `\\?\UNC\server\e:\` + /// + /// TODO: NT paths? Admin paths using `$`? + case drive(Character) + + /// A volume with a GUID in a non-traditional path + /// + /// * UNC: `\\host\Volume{0000-...}\`, `\\.\UNC\host\Volume{0000-...}\` + /// * Device roots: `\\.\Volume{0000-...}\`, `\\?\Volume{000-...}\` + /// + /// TODO: GUID type? + case guid(String) + + // TODO: Legacy DOS devices, such as COM1? + + /// Device object or share name + /// + /// * Device roots: `\\.\BootPartition\` + /// * UNC: `\\host\volume\` + case volume(String) + + // TODO: Should legacy DOS devices be detected and/or converted at construction time? + // TODO: What about NT paths: `\??\` + } + + /// Represents the syntactic form of the path + internal enum Form: Equatable { + /// Traditional DOS roots: `C:\`, `C:`, and `\` + case traditional(fullyQualified: Bool) // `C:\`, `C:`, `\` + + /// UNC syntactic form: `\\server\share\` + case unc + + /// DOS device syntactic form: `\\?\BootPartition`, `\\.\C:\`, `\\?\UNC\server\share` + case device(sigil: Character) + + // TODO: NT? + } + + /// The host for UNC paths, else `nil`. + internal var host: String? + + /// The specified volume (or UNC share) for the root + internal var volume: Volume + + /// The syntactic form the root is in + internal var form: Form + + init(host: String?, volume: Volume, form: Form) { + self.host = host + self.volume = volume + self.form = form + checkInvariants() + } +} + +extension _ParsedWindowsRoot { + fileprivate func volumeInfo(_ root: SystemString) -> WindowsRootInfo.Volume { + if let d = self.drive { + return .drive(Character(d.asciiScalar!)) + } + + guard let vol = self.volume, !vol.isEmpty else { return .empty } + + // TODO: check for GUID + // TODO: check for drive + return .volume(root[vol].string) + } +} + +extension WindowsRootInfo { + internal init(_ root: SystemString, _ parsed: _ParsedWindowsRoot) { + self.volume = parsed.volumeInfo(root) + + if let host = parsed.host { + self.host = root[host].string + } else { + self.host = nil + } + + if let sig = parsed.deviceSigil { + self.form = .device(sigil: Character(sig.asciiScalar!)) + } else if parsed.host != nil { + assert(parsed.volume != nil) + self.form = .unc + } else { + self.form = .traditional(fullyQualified: parsed.fullyQualified) + } + } +} + +extension WindowsRootInfo { + /// NOT `\foo\bar` nor `C:foo\bar` + internal var isFullyQualified: Bool { + return form != .traditional(fullyQualified: false) + } + + /// + /// `\\server\share\foo\bar.exe`, `\\.\UNC\server\share\foo\bar.exe` + internal var isUNC: Bool { + host != nil + } + + /// + /// `\foo\bar.exe` + internal var isTraditionalRooted: Bool { + form == .traditional(fullyQualified: false) && volume == .empty + } + + /// + /// `C:foo\bar.exe` + internal var isTraditionalDriveRelative: Bool { + switch (form, volume) { + case (.traditional(fullyQualified: false), .drive(_)): return true + default: return false + } + } + + // TODO: Should this be component? + func formPath() -> FilePath { + fatalError("Unimplemented") + } + + // static func traditional( + // drive: Character?, fullyQualified: Bool + // ) -> WindowsRootInfo { + // let vol: Volume + // if let d = Character { + // vol = .drive(d) + // } else { + // vol = .relative + // } + // + // return WindowsRootInfo( + // volume: .relative, form: .traditional(fullyQualified: false)) + // } + + internal func checkInvariants() { + switch form { + case .traditional(let qual): + precondition(host == nil) + switch volume { + case .empty: + precondition(!qual) + break + case .drive(_): break + default: preconditionFailure() + } + case .unc: + precondition(host != nil) + case .device(_): break + } + } + +} + +extension SystemString { + // TODO: Or, should I always inline this to remove some of the bookeeping? + private func _parseWindowsRootInternal() -> _ParsedWindowsRoot? { + assert(_windowsPaths) + + /* + Windows root: device or UNC or DOS + device: (`\\.` or `\\?`) `\` (drive or guid or UNC-link) + drive: letter `:` + guid: `Volume{` (hex-digit or `-`)* `}` + UNC-link: `UNC\` UNC-volume + UNC: `\\` UNC-volume + UNC-volume: server `\` share + DOS: fully-qualified or legacy-device or drive or `\` + full-qualified: drive `\` + + TODO: What is \\?\server1\e:\utilities\\filecomparer\ from the docs? + TODO: What about admin use of `$` instead of `:`? E.g. \\system07\C$\ + + NOTE: Legacy devices are not handled by System at a library level, but + are deferred to the relevant syscalls. + */ + + var lexer = _Lexer(self) + + // Helper to parse a UNC root + func parseUNC(deviceSigil: SystemChar?) -> _ParsedWindowsRoot { + let serverRange = lexer.eatComponent() + guard lexer.eatBackslash() else { + assert(false, "expected normalized root to contain backslash") + } + let shareRange = lexer.eatComponent() + let rootEnd = lexer.current + _ = lexer.eatBackslash() + return .unc( + deviceSigil: deviceSigil, + server: serverRange, share: shareRange, + endingAt: rootEnd, relativeBegin: lexer.current) + } + + + // `C:` or `C:\` + if let d = lexer.eatDrive() { + // `C:\` - fully qualified + let fullyQualified = lexer.eatBackslash() + return .traditional( + drive: d, fullQualified: fullyQualified, endingAt: lexer.current) + } + + // `\` or else it's just a rootless relative path + guard lexer.eatBackslash() else { return nil } + + // `\\` or else it's just a current-drive rooted traditional path + guard lexer.eatBackslash() else { + return .traditional( + drive: nil, fullQualified: false, endingAt: lexer.current) + } + + // `\\.` or `\\?` (device paths) or else it's just UNC + guard let sigil = lexer.eatSigil() else { + return parseUNC(deviceSigil: nil) + } + _ = sigil // suppress warnings + + guard lexer.eatBackslash() else { + assert(false, "expected normalized root to contain backslash") + } + + if lexer.eatUNC() { + guard lexer.eatBackslash() else { + assert(false, "expected normalized root to contain backslash") + } + return parseUNC(deviceSigil: sigil) + } + + let device = lexer.eatComponent() + let rootEnd = lexer.current + _ = lexer.eatBackslash() + + return .device( + deviceSigil: sigil, volume: device, + endingAt: rootEnd, relativeBegin: lexer.current) + } + + @inline(never) + internal func _parseWindowsRoot() -> ( + rootEnd: SystemString.Index, relativeBegin: SystemString.Index + ) { + guard let parsed = _parseWindowsRootInternal() else { + return (startIndex, startIndex) + } + return (parsed.rootEnd, parsed.relativeBegin) + } +} + +extension SystemString { + // UNC and device roots can have multiple repeated roots that are meaningful, + // and extra backslashes may need to be inserted for partial roots (e.g. empty + // volume). + // + // Returns the point where `_normalizeSeparators` should resume. + internal mutating func _prenormalizeWindowsRoots() -> Index { + assert(_windowsPaths) + assert(!self.contains(.slash), "only valid after separator conversion") + + var lexer = _Lexer(self) + + // Only relevant for UNC or device paths + guard lexer.eatBackslash(), lexer.eatBackslash() else { + return lexer.current + } + + // Parse a backslash, inserting one if needed + func expectBackslash() { + if lexer.eatBackslash() { return } + + // A little gross, but we reset the lexer because the lexer + // holds a strong reference to `self`. + // + // TODO: Intern the empty SystemString. Right now, this is + // along an uncommon/pathological case, but we want to in + // general make empty strings without allocation + let idx = lexer.current + lexer.clear() + self.insert(.backslash, at: idx) + lexer.reset(to: self, at: idx) + let p = lexer.eatBackslash() + assert(p) + } + // Parse a component and subsequent backslash, insering one if needed + func expectComponent() { + _ = lexer.eatComponent() + expectBackslash() + } + + // Check for `\\.` style paths + if lexer.eatSigil() != nil { + expectBackslash() + if lexer.eatUNC() { + expectBackslash() + expectComponent() + expectComponent() + return lexer.current + } + expectComponent() + return lexer.current + } + + expectComponent() + expectComponent() + return lexer.current + } +} diff --git a/Sources/System/FilePermissions.swift b/Sources/System/FilePermissions.swift index 0b3217b1..1ad9434a 100644 --- a/Sources/System/FilePermissions.swift +++ b/Sources/System/FilePermissions.swift @@ -21,14 +21,14 @@ public struct FilePermissions: OptionSet, Hashable, Codable { /// The raw C file permissions. @_alwaysEmitIntoClient - public let rawValue: CModeT + public let rawValue: CInterop.Mode /// Create a strongly-typed file permission from a raw C value. @_alwaysEmitIntoClient - public init(rawValue: CModeT) { self.rawValue = rawValue } + public init(rawValue: CInterop.Mode) { self.rawValue = rawValue } @_alwaysEmitIntoClient - private init(_ raw: CModeT) { self.init(rawValue: raw) } + private init(_ raw: CInterop.Mode) { self.init(rawValue: raw) } /// Indicates that other users have read-only permission. @_alwaysEmitIntoClient diff --git a/Sources/System/Platform/Platform.swift b/Sources/System/Platform/Platform.swift index 73e436dc..167bc61e 100644 --- a/Sources/System/Platform/Platform.swift +++ b/Sources/System/Platform/Platform.swift @@ -12,11 +12,23 @@ // Public typealiases that can't be reexported from SystemInternals /// The C `mode_t` type. -#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) // @available(macOS 10.16, iOS 14.0, watchOS 7.0, tvOS 14.0, *) -public typealias CModeT = UInt16 -#elseif os(Windows) -public typealias CModeT = Int32 -#else -public typealias CModeT = UInt32 -#endif +@available(*, deprecated, renamed: "CInterop.Mode") +public typealias CModeT = CInterop.Mode + +/// A namespace for C and platform types +public enum CInterop { + #if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) + /// The C `mode_t` type. + public typealias Mode = UInt16 + #elseif os(Windows) + /// The C `mode_t` type. + public typealias Mode = Int32 + #else + /// The C `mode_t` type. + public typealias Mode = UInt32 + #endif + + /// The C `char` type + public typealias Char = CChar +} diff --git a/Sources/System/Platform/PlatformString.swift b/Sources/System/Platform/PlatformString.swift new file mode 100644 index 00000000..af1bee73 --- /dev/null +++ b/Sources/System/Platform/PlatformString.swift @@ -0,0 +1,114 @@ +/* + This source file is part of the Swift System open source project + + Copyright (c) 2020 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 +*/ + +extension CInterop { + #if os(Windows) + /// The platform's preferred character type. On Unix, this is an 8-bit C + /// `char` (which may be signed or unsigned, depending on platform). On + /// Windows, this is `UInt16` (a "wide" character). + public typealias PlatformChar = UInt16 + #else + /// The platform's preferred character type. On Unix, this is an 8-bit C + /// `char` (which may be signed or unsigned, depending on platform). On + /// Windows, this is `UInt16` (a "wide" character). + public typealias PlatformChar = CInterop.Char + #endif + + #if os(Windows) + /// The platform's preferred Unicode encoding. On Unix this is UTF-8 and on + /// Windows it is UTF-16. Native strings may contain invalid Unicode, + /// which will be handled by either error-correction or failing, depending + /// on API. + public typealias PlatformUnicodeEncoding = UTF16 + #else + /// The platform's preferred Unicode encoding. On Unix this is UTF-8 and on + /// Windows it is UTF-16. Native strings may contain invalid Unicode, + /// which will be handled by either error-correction or failing, depending + /// on API. + public typealias PlatformUnicodeEncoding = UTF8 + #endif +} + +extension CInterop.PlatformChar { + internal var _platformCodeUnit: CInterop.PlatformUnicodeEncoding.CodeUnit { + #if os(Windows) + return self + #else + return CInterop.PlatformUnicodeEncoding.CodeUnit(bitPattern: self) + #endif + } +} +extension CInterop.PlatformUnicodeEncoding.CodeUnit { + internal var _platformChar: CInterop.PlatformChar { + #if os(Windows) + return self + #else + return CInterop.PlatformChar(bitPattern: self) + #endif + } +} + +extension String { + /// Creates a string by interpreting the null-terminated platform string as + /// UTF-8 on Unix and UTF-16 on Windows. + /// + /// - Parameter platformString: The null-terminated platform string to be + /// interpreted as `CInterop.PlatformUnicodeEncoding`. + /// + /// If the content of the platform string isn't well-formed Unicode, + /// this initializer replaces invalid bytes with U+FFFD. + /// This means that, depending on the semantics of the specific platform, + /// conversion to a string and back might result in a value that's different + /// from the original platform string. + public init(platformString: UnsafePointer) { + self.init(_errorCorrectingPlatformString: platformString) + } + + /// Creates a string by interpreting the null-terminated platform string as + /// UTF-8 on Unix and UTF-16 on Windows. + /// + /// - Parameter platformString: The null-terminated platform string to be + /// interpreted as `CInterop.PlatformUnicodeEncoding`. + /// + /// If the contents of the platform string isn't well-formed Unicode, + /// this initializer returns `nil`. + public init?( + validatingPlatformString platformString: UnsafePointer + ) { + self.init(_platformString: platformString) + } + + /// Calls the given closure with a pointer to the contents of the string, + /// represented as a null-terminated platform string. + /// + /// - Parameter body: A closure with a pointer parameter + /// that points to a null-terminated platform string. + /// If `body` has a return value, + /// that value is also used as the return value for this method. + /// - Returns: The return value, if any, of the `body` closure parameter. + /// + /// The pointer passed as an argument to `body` is valid + /// only during the execution of this method. + /// Don't try to store the pointer for later use. + public func withPlatformString( + _ body: (UnsafePointer) throws -> Result + ) rethrows -> Result { + try _withPlatformString(body) + } + +} + +internal protocol _PlatformStringable { + func _withPlatformString( + _ body: (UnsafePointer) throws -> Result + ) rethrows -> Result + + init?(_platformString: UnsafePointer) +} +extension String: _PlatformStringable {} diff --git a/Sources/System/SystemString.swift b/Sources/System/SystemString.swift new file mode 100644 index 00000000..4ab93099 --- /dev/null +++ b/Sources/System/SystemString.swift @@ -0,0 +1,289 @@ +/* + This source file is part of the Swift System open source project + + Copyright (c) 2020 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 +*/ + +// A platform-native character representation, currently used for file paths +internal struct SystemChar: RawRepresentable, Comparable, Hashable, Codable { + internal typealias RawValue = CInterop.PlatformChar + + internal var rawValue: RawValue + + internal init(rawValue: RawValue) { self.rawValue = rawValue } + + internal init(_ rawValue: RawValue) { self.init(rawValue: rawValue) } + + static func < (lhs: SystemChar, rhs: SystemChar) -> Bool { + lhs.rawValue < rhs.rawValue + } +} + +extension SystemChar { + internal init(ascii: Unicode.Scalar) { + self.init(rawValue: numericCast(UInt8(ascii: ascii))) + } + internal init(codeUnit: CInterop.PlatformUnicodeEncoding.CodeUnit) { + self.init(rawValue: codeUnit._platformChar) + } + + internal static var null: SystemChar { SystemChar(0x0) } + internal static var slash: SystemChar { SystemChar(ascii: "/") } + internal static var backslash: SystemChar { SystemChar(ascii: #"\"#) } + internal static var dot: SystemChar { SystemChar(ascii: ".") } + internal static var colon: SystemChar { SystemChar(ascii: ":") } + internal static var question: SystemChar { SystemChar(ascii: "?") } + + internal var codeUnit: CInterop.PlatformUnicodeEncoding.CodeUnit { + rawValue._platformCodeUnit + } + + internal var asciiScalar: Unicode.Scalar? { + guard isASCII else { return nil } + return Unicode.Scalar(UInt8(truncatingIfNeeded: rawValue)) + } + + internal var isASCII: Bool { + (0...0x7F).contains(rawValue) + } + + internal var isLetter: Bool { + guard isASCII else { return false } + let asciiRaw: UInt8 = numericCast(rawValue) + return (UInt8(ascii: "a") ..< UInt8(ascii: "z")).contains(asciiRaw) || + (UInt8(ascii: "A") ..< UInt8(ascii: "Z")).contains(asciiRaw) + } +} + +// A platform-native string representation, currently for file paths +// +// Always null-terminated. +internal struct SystemString { + internal typealias Storage = [SystemChar] + internal var nullTerminatedStorage: Storage +} + +extension SystemString { + internal init() { + self.nullTerminatedStorage = [.null] + _invariantCheck() + } + + internal var length: Int { + let len = nullTerminatedStorage.count - 1 + assert(len == self.count) + return len + } + + // Common funnel point. Ensure all non-empty inits go here. + internal init(nullTerminated storage: Storage) { + self.nullTerminatedStorage = storage + _invariantCheck() + } + + // Ensures that result is null-terminated + internal init(_ chars: C) where C.Element == SystemChar { + var rawChars = Storage(chars) + if rawChars.last != .null { + rawChars.append(.null) + } + self.init(nullTerminated: rawChars) + } +} + +extension SystemString { + fileprivate func _invariantCheck() { + #if DEBUG + precondition(nullTerminatedStorage.last! == .null) + precondition(nullTerminatedStorage.firstIndex(of: .null) == length) + #endif // DEBUG + } +} + +extension SystemString: RandomAccessCollection, MutableCollection { + internal typealias Element = SystemChar + internal typealias Index = Storage.Index + internal typealias Indices = Range + + internal var startIndex: Index { + nullTerminatedStorage.startIndex + } + + internal var endIndex: Index { + nullTerminatedStorage.index(before: nullTerminatedStorage.endIndex) + } + + internal subscript(position: Index) -> SystemChar { + _read { + precondition(position >= startIndex && position <= endIndex) + yield nullTerminatedStorage[position] + } + set(newValue) { + precondition(position >= startIndex && position <= endIndex) + nullTerminatedStorage[position] = newValue + _invariantCheck() + } + } +} +extension SystemString: RangeReplaceableCollection { + internal mutating func replaceSubrange( + _ subrange: Range, with newElements: C + ) where C.Element == SystemChar { + defer { _invariantCheck() } + nullTerminatedStorage.replaceSubrange(subrange, with: newElements) + } + + internal mutating func reserveCapacity(_ n: Int) { + defer { _invariantCheck() } + nullTerminatedStorage.reserveCapacity(1 + n) + } + + // TODO: Below include null terminator, is this desired? + + internal func withContiguousStorageIfAvailable( + _ body: (UnsafeBufferPointer) throws -> R + ) rethrows -> R? { + try nullTerminatedStorage.withContiguousStorageIfAvailable(body) + } + + internal mutating func withContiguousMutableStorageIfAvailable( + _ body: (inout UnsafeMutableBufferPointer) throws -> R + ) rethrows -> R? { + defer { _invariantCheck() } + return try nullTerminatedStorage.withContiguousMutableStorageIfAvailable(body) + } +} + +extension SystemString: Hashable, Codable {} + +extension SystemString { + // TODO: Below include null terminator, is this desired? + + internal func withSystemChars( + _ f: (UnsafeBufferPointer) throws -> T + ) rethrows -> T { + try withContiguousStorageIfAvailable(f)! + } + + internal func withCodeUnits( + _ f: (UnsafeBufferPointer) throws -> T + ) rethrows -> T { + try withSystemChars { + // TODO: Is this the right way? + let base = UnsafeRawPointer( + $0.baseAddress + )!.assumingMemoryBound(to: CInterop.PlatformUnicodeEncoding.CodeUnit.self) + return try f(UnsafeBufferPointer(start: base, count: $0.count)) + } + } +} + +extension Slice where Base == SystemString { + internal func withCodeUnits( + _ f: (UnsafeBufferPointer) throws -> T + ) rethrows -> T { + try base.withCodeUnits { + try f(UnsafeBufferPointer(rebasing: $0[indices])) + } + } + + internal var string: String { + withCodeUnits { String(decoding: $0, as: CInterop.PlatformUnicodeEncoding.self) } + } + + internal func withPlatformString( + _ f: (UnsafePointer) throws -> T + ) rethrows -> T { + // FIXME: avoid allocation if we're at the end + return try SystemString(self).withPlatformString(f) + } + +} + +extension String { + internal init(decoding str: SystemString) { + // TODO: Can avoid extra strlen + self = str.withPlatformString { + String(platformString: $0) + } + } + internal init?(validating str: SystemString) { + // TODO: Can avoid extra strlen + guard let str = str.withPlatformString(String.init(validatingPlatformString:)) + else { return nil } + + self = str + } +} + +extension SystemString: ExpressibleByStringLiteral { + internal init(stringLiteral: String) { + self.init(stringLiteral) + } + + internal init(_ string: String) { + // TODO: can avoid extra strlen + self = string.withPlatformString { + SystemString(platformString: $0) + } + } +} + +extension SystemString: CustomStringConvertible, CustomDebugStringConvertible { + internal var string: String { String(decoding: self) } + + internal var description: String { string } + internal var debugDescription: String { description.debugDescription } +} + +@_implementationOnly import func SystemInternals.system_platform_strlen +extension SystemString { + /// Creates a system string by copying bytes from a null-terminated platform string. + /// + /// - Parameter platformString: A pointer to a null-terminated platform string. + internal init(platformString: UnsafePointer) { + let count = 1 + system_platform_strlen(platformString) + + // TODO: Is this the right way? + let chars: Array = platformString.withMemoryRebound( + to: SystemChar.self, capacity: count + ) { + let bufPtr = UnsafeBufferPointer(start: $0, count: count) + return Array(bufPtr) + } + + self.init(nullTerminated: chars) + } + + /// Calls the given closure with a pointer to the contents of the sytem string, + /// represented as a null-terminated platform string. + /// + /// - Parameter body: A closure with a pointer parameter + /// that points to a null-terminated platform string. + /// If `body` has a return value, + /// that value is also used as the return value for this method. + /// - Returns: The return value, if any, of the `body` closure parameter. + /// + /// The pointer passed as an argument to `body` is valid + /// only during the execution of this method. + /// Don't try to store the pointer for later use. + internal func withPlatformString( + _ f: (UnsafePointer) throws -> T + ) rethrows -> T { + try withSystemChars { + // TODO: Is this the right way? + let base = UnsafeRawPointer( + $0.baseAddress + )!.assumingMemoryBound(to: CInterop.PlatformChar.self) + assert(base[self.count] == 0) + return try f(base) + } + } +} + +// TODO: SystemString should use a COW-interchangable storage form rather +// than array, so you could "borrow" the storage from a non-bridged String +// or Data or whatever diff --git a/Sources/System/Util.swift b/Sources/System/Util.swift index 00bcd7d7..d70c2892 100644 --- a/Sources/System/Util.swift +++ b/Sources/System/Util.swift @@ -38,15 +38,21 @@ internal func valueOrErrno( // Run a precondition for debug client builds internal func _debugPrecondition( - _ condition: @autoclosure () -> Bool, _ message: StaticString = StaticString(), + _ condition: @autoclosure () -> Bool, + _ message: StaticString = StaticString(), file: StaticString = #file, line: UInt = #line ) { // Only check in debug mode. - if _slowPath(_isDebugAssertConfiguration()) { precondition(condition()) } + if _slowPath(_isDebugAssertConfiguration()) { + precondition( + condition(), String(describing: message), file: file, line: line) + } } extension OpaquePointer { - internal var _isNULL: Bool { OpaquePointer(bitPattern: Int(bitPattern: self)) == nil } + internal var _isNULL: Bool { + OpaquePointer(bitPattern: Int(bitPattern: self)) == nil + } } extension Sequence { @@ -95,26 +101,22 @@ extension OptionSet { } } -extension UnsafePointer where Pointee == UInt8 { - internal var _asCChar: UnsafePointer { - UnsafeRawPointer(self).assumingMemoryBound(to: CChar.self) +internal func _dropCommonPrefix( + _ lhs: C, _ rhs: C +) -> (C.SubSequence, C.SubSequence) +where C.Element: Equatable { + var (lhs, rhs) = (lhs[...], rhs[...]) + while lhs.first != nil && lhs.first == rhs.first { + lhs.removeFirst() + rhs.removeFirst() } + return (lhs, rhs) } -extension UnsafePointer where Pointee == CChar { - internal var _asUInt8: UnsafePointer { - UnsafeRawPointer(self).assumingMemoryBound(to: UInt8.self) - } -} -extension UnsafeBufferPointer where Element == UInt8 { - internal var _asCChar: UnsafeBufferPointer { - let base = baseAddress?._asCChar - return UnsafeBufferPointer(start: base, count: self.count) - } -} -extension UnsafeBufferPointer where Element == CChar { - internal var _asUInt8: UnsafeBufferPointer { - let base = baseAddress?._asUInt8 - return UnsafeBufferPointer(start: base, count: self.count) + +extension MutableCollection where Element: Equatable { + mutating func _replaceAll(_ e: Element, with new: Element) { + for idx in self.indices { + if self[idx] == e { self[idx] = new } + } } } - diff --git a/Sources/System/UtilConsumers.swift b/Sources/System/UtilConsumers.swift new file mode 100644 index 00000000..183f5f7d --- /dev/null +++ b/Sources/System/UtilConsumers.swift @@ -0,0 +1,73 @@ +/* + This source file is part of the Swift System open source project + + Copyright (c) 2020 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 +*/ + +// TODO: Below should return an optional of what was eaten + +extension Slice where Element: Equatable { + internal mutating func _eat(if p: (Element) -> Bool) -> Element? { + guard let s = self.first, p(s) else { return nil } + self = self.dropFirst() + return s + } + internal mutating func _eat(_ e: Element) -> Element? { + _eat(if: { $0 == e }) + } + + internal mutating func _eat(asserting e: Element) { + let p = _eat(e) + assert(p != nil) + } + + internal mutating func _eat(count c: Int) -> Slice { + defer { self = self.dropFirst(c) } + return self.prefix(c) + } + + internal mutating func _eatSequence(_ es: C) -> Slice? + where C.Element == Element + { + guard self.starts(with: es) else { return nil } + return _eat(count: es.count) + } + + internal mutating func _eatUntil(_ idx: Index) -> Slice { + precondition(idx >= startIndex && idx <= endIndex) + defer { self = self[idx...] } + return self[.. Slice { + precondition(idx >= startIndex && idx <= endIndex) + guard idx != endIndex else { + defer { self = self[endIndex ..< endIndex] } + return self + } + defer { self = self[index(after: idx)...] } + return self[...idx] + } + + // If `e` is present, eat up to first occurence of `e` + internal mutating func _eatUntil(_ e: Element) -> Slice? { + guard let idx = self.firstIndex(of: e) else { return nil } + return _eatUntil(idx) + } + + // If `e` is present, eat up to and through first occurence of `e` + internal mutating func _eatThrough(_ e: Element) -> Slice? { + guard let idx = self.firstIndex(of: e) else { return nil } + return _eatThrough(idx) + } + + // Eat any elements from the front matching the predicate + internal mutating func _eatWhile(_ p: (Element) -> Bool) -> Slice? { + let idx = firstIndex(where: { !p($0) }) ?? endIndex + guard idx != startIndex else { return nil } + return _eatUntil(idx) + } +} diff --git a/Sources/SystemInternals/Exports.swift b/Sources/SystemInternals/Exports.swift index 6162b8b4..868fe2b4 100644 --- a/Sources/SystemInternals/Exports.swift +++ b/Sources/SystemInternals/Exports.swift @@ -9,6 +9,9 @@ import CSystem +// Internal wrappers and typedefs which help reduce #if littering in System's +// code base. + // TODO: Should CSystem just include all the header files we need? #if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) @@ -57,6 +60,8 @@ public var system_errno: CInt { // MARK: C stdlib decls +// Convention: `system_foo` is system's wrapper for `foo`. + public func system_strerror(_ __errnum: Int32) -> UnsafeMutablePointer! { strerror(__errnum) } @@ -65,3 +70,77 @@ public func system_strlen(_ s: UnsafePointer) -> Int { strlen(s) } +#if os(Windows) +public typealias _PlatformChar = UInt16 +#else +public typealias _PlatformChar = CChar +#endif +#if os(Windows) +public typealias _PlatformUnicodeEncoding = UTF16 +#else +public typealias _PlatformUnicodeEncoding = UTF8 +#endif + + +// Convention: `system_platform_foo` is a +// platform-representation-abstracted wrapper around `foo`-like functionality. +// Type and layout differences such as the `char` vs `wchar` are abstracted. +// + +// strlen for the platform string +public func system_platform_strlen(_ s: UnsafePointer<_PlatformChar>) -> Int { + #if os(Windows) + return wcslen(s) + #else + return strlen(s) + #endif +} + +// Interop between String and platfrom string +extension String { + public func _withPlatformString( + _ body: (UnsafePointer<_PlatformChar>) throws -> Result + ) rethrows -> Result { + // Need to #if because CChar may be signed + #if os(Windows) + return try withCString(encodedAs: _PlatformUnicodeEncoding.self, body) + #else + return try withCString(body) + #endif + } + + public init?(_platformString platformString: UnsafePointer<_PlatformChar>) { + // Need to #if because CChar may be signed + #if os(Windows) + guard let strRes = String.decodeCString( + platformString, + as: _PlatformUnicodeEncoding.self, + repairingInvalidCodeUnits: false + ) else { return nil } + assert(strRes.repairsMade == false) + self = strRes.result + return + + #else + self.init(validatingUTF8: platformString) + #endif + } + + public init( + _errorCorrectingPlatformString platformString: UnsafePointer<_PlatformChar> + ) { + // Need to #if because CChar may be signed + #if os(Windows) + let strRes = String.decodeCString( + platformString, + as: _PlatformUnicodeEncoding.self, + repairingInvalidCodeUnits: true) + self = strRes!.result + return + #else + self.init(cString: platformString) + #endif + } + + +} diff --git a/Sources/SystemInternals/Mocking.swift b/Sources/SystemInternals/Mocking.swift index aa4bdbb9..118575ef 100644 --- a/Sources/SystemInternals/Mocking.swift +++ b/Sources/SystemInternals/Mocking.swift @@ -81,6 +81,10 @@ public class MockingDriver { // A buffer to put `write` bytes into public var writeBuffer = WriteBuffer() + + // Whether we should pretend to be Windows for syntactic operations + // inside FilePath + public var forceWindowsSyntaxForPaths = false } #if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) @@ -176,6 +180,10 @@ private var contextualMockingEnabled: Bool { extension MockingDriver { public static var enabled: Bool { mockingEnabled } + + public static var forceWindowsPaths: Bool { + currentMockingDriver?.forceWindowsSyntaxForPaths ?? false + } } #endif // ENABLE_MOCKING @@ -190,3 +198,11 @@ internal var mockingEnabled: Bool { #endif } +@inlinable @inline(__always) +public var forceWindowsPaths: Bool { + #if !ENABLE_MOCKING + return false + #else + return MockingDriver.forceWindowsPaths + #endif +} diff --git a/Sources/SystemInternals/Syscalls.swift b/Sources/SystemInternals/Syscalls.swift index 694018c5..4c95f114 100644 --- a/Sources/SystemInternals/Syscalls.swift +++ b/Sources/SystemInternals/Syscalls.swift @@ -72,18 +72,24 @@ private func mockOffT( // amount of code size, so we hand outline that code for every syscall // open -public func system_open(_ path: UnsafePointer, _ oflag: Int32) -> CInt { +public func system_open( + _ path: UnsafePointer<_PlatformChar>, _ oflag: Int32 +) -> CInt { #if ENABLE_MOCKING - if mockingEnabled { return mock(String(cString: path), oflag) } + if mockingEnabled { + return mock(String(_errorCorrectingPlatformString: path), oflag) + } #endif return open(path, oflag) } public func system_open( - _ path: UnsafePointer, _ oflag: Int32, _ mode: CModeT + _ path: UnsafePointer<_PlatformChar>, _ oflag: Int32, _ mode: CModeT ) -> CInt { #if ENABLE_MOCKING - if mockingEnabled { return mock(String(cString: path), oflag, mode) } + if mockingEnabled { + return mock(String(_errorCorrectingPlatformString: path), oflag, mode) + } #endif return open(path, oflag, mode) } diff --git a/Sources/SystemInternals/WindowsSyscallAdapters.swift b/Sources/SystemInternals/WindowsSyscallAdapters.swift index 2cc46d20..f1d74f06 100644 --- a/Sources/SystemInternals/WindowsSyscallAdapters.swift +++ b/Sources/SystemInternals/WindowsSyscallAdapters.swift @@ -13,19 +13,21 @@ import ucrt import WinSDK @inline(__always) -internal func open(_ path: UnsafePointer, _ oflag: Int32) -> CInt { +internal func open( + _ path: UnsafePointer<_PlatformChar>, _ oflag: Int32 +) -> CInt { var fh: CInt = -1 - _ = _sopen_s(&fh, path, oflag, _SH_DENYNO, _S_IREAD | _S_IWRITE) + _ = _wsopen_s(&fh, path, oflag, _SH_DENYNO, _S_IREAD | _S_IWRITE) return fh } @inline(__always) internal func open( - _ path: UnsafePointer, _ oflag: Int32, _ mode: CModeT + _ path: UnsafePointer<_PlatformChar>, _ oflag: Int32, _ mode: CModeT ) -> CInt { // TODO(compnerd): Apply read/write permissions var fh: CInt = -1 - _ = _sopen_s(&fh, path, oflag, _SH_DENYNO, _S_IREAD | _S_IWRITE) + _ = _wsopen_s(&fh, path, oflag, _SH_DENYNO, _S_IREAD | _S_IWRITE) return fh } diff --git a/Tests/SystemTests/FileOperationsTest.swift b/Tests/SystemTests/FileOperationsTest.swift index b3932ddf..57149eae 100644 --- a/Tests/SystemTests/FileOperationsTest.swift +++ b/Tests/SystemTests/FileOperationsTest.swift @@ -108,7 +108,7 @@ final class FileOperationsTest: XCTestCase { // Should we assert? I'd be interested in knowing if this happened XCTAssert(false) } catch { - fatalError() + fatalError("FATAL: `testAdHocOpen`") } } } diff --git a/Tests/SystemTests/FilePathTests/FilePathTest.swift b/Tests/SystemTests/FilePathTests/FilePathTest.swift index 441d9a0a..ae4f8755 100644 --- a/Tests/SystemTests/FilePathTests/FilePathTest.swift +++ b/Tests/SystemTests/FilePathTests/FilePathTest.swift @@ -10,12 +10,6 @@ import XCTest import SystemPackage -extension UnsafePointer where Pointee == UInt8 { - internal var _asCChar: UnsafePointer { - UnsafeRawPointer(self).assumingMemoryBound(to: CChar.self) - } -} - // @available(macOS 10.16, iOS 14.0, watchOS 7.0, tvOS 14.0, *) func filePathFromUnterminatedBytes(_ bytes: S) -> FilePath where S.Element == UInt8 { var array = Array(bytes) @@ -23,7 +17,7 @@ func filePathFromUnterminatedBytes(_ bytes: S) -> FilePath where S. array += [0] return array.withUnsafeBufferPointer { - FilePath(cString: $0.baseAddress!._asCChar) + FilePath(platformString: $0.baseAddress!._asCChar) } } let invalidBytes: [UInt8] = [0x2F, 0x61, 0x2F, 0x62, 0x2F, 0x83] @@ -58,17 +52,18 @@ final class FilePathTest: XCTestCase { XCTAssertEqual(testPath.string, String(decoding: testPath.filePath)) + // TODO: test component UTF8 validation if testPath.validUTF8 { XCTAssertEqual(testPath.filePath, FilePath(testPath.string)) - XCTAssertEqual(testPath.string, String(validatingUTF8: testPath.filePath)) + XCTAssertEqual(testPath.string, String(validating: testPath.filePath)) } else { XCTAssertNotEqual(testPath.filePath, FilePath(testPath.string)) - XCTAssertNil(String(validatingUTF8: testPath.filePath)) + XCTAssertNil(String(validating: testPath.filePath)) } - testPath.filePath.withCString { + testPath.filePath.withPlatformString { XCTAssertEqual(testPath.string, String(cString: $0)) - XCTAssertEqual(testPath.filePath, FilePath(cString: $0)) + XCTAssertEqual(testPath.filePath, FilePath(platformString: $0)) } } } diff --git a/Tests/SystemTests/SystemStringTests.swift b/Tests/SystemTests/SystemStringTests.swift new file mode 100644 index 00000000..96cfa6e6 --- /dev/null +++ b/Tests/SystemTests/SystemStringTests.swift @@ -0,0 +1,244 @@ +/* + This source file is part of the Swift System open source project + + Copyright (c) 2020 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 +*/ + +// Tests for PlatformString, SystemString, and FilePath's forwarding APIs + +// TODO: Adapt test to Windows +extension UnsafePointer where Pointee == UInt8 { + internal var _asCChar: UnsafePointer { + UnsafeRawPointer(self).assumingMemoryBound(to: CChar.self) + } +} +extension UnsafePointer where Pointee == CChar { + internal var _asUInt8: UnsafePointer { + UnsafeRawPointer(self).assumingMemoryBound(to: UInt8.self) + } +} +extension UnsafeBufferPointer where Element == UInt8 { + internal var _asCChar: UnsafeBufferPointer { + let base = baseAddress?._asCChar + return UnsafeBufferPointer(start: base, count: self.count) + } +} +extension UnsafeBufferPointer where Element == CChar { + internal var _asUInt8: UnsafeBufferPointer { + let base = baseAddress?._asUInt8 + return UnsafeBufferPointer(start: base, count: self.count) + } +} + + +import XCTest +import SystemPackage + +@testable import SystemPackage +import SystemInternals + +private func makeRaw( + _ str: String +) -> [CInterop.PlatformChar] { + var str = str + var array = str.withUTF8 { Array($0._asCChar) } + array.append(0) + return array +} +private func makeStr( + _ raw: [CInterop.PlatformChar] +) -> String { + precondition((raw.last ?? 0) == 0) + // FIXME: Unfortunately, this relies on some of the same mechanisms tested... + return raw.withUnsafeBufferPointer { + String(platformString: $0.baseAddress!) + } +} + +struct StringTest: TestCase { + // The error-corrected string + var string: String + + // The raw contents + var raw: [CInterop.PlatformChar] + + var isValid: Bool + + var file: StaticString + var line: UInt + + func runAllTests() { + precondition((raw.last ?? 0) == 0, "invalid test case") + + // Test idempotence post-validation + string.withPlatformString { + if isValid { + let len = system_platform_strlen($0) + expectEqual(raw.count, 1+len, "Validation idempotence") + expectEqualSequence(raw, UnsafeBufferPointer(start: $0, count: 1+len), + "Validation idempotence") + } + + let str = String(platformString: $0) + expectEqualSequence( + string.unicodeScalars, str.unicodeScalars, "Validation idempotence") + + let validStr = String(validatingPlatformString: $0) + expectEqual(string, validStr, "Validation idempotence") + } + + // Test String, SystemString, FilePath construction + let sysStr = SystemString(string) + expectEqualSequence(string.unicodeScalars, sysStr.string.unicodeScalars) + expectEqual(string, String(decoding: sysStr)) + expectEqual(string, String(validating: sysStr)) + + let sysRaw = raw.withUnsafeBufferPointer { + SystemString(platformString: $0.baseAddress!) + } + expectEqual(string, String(decoding: sysRaw)) + expectEqual(isValid, nil != String(validating: sysRaw)) + expectEqual(isValid, sysStr == sysRaw) + + let strDecoded = raw.withUnsafeBufferPointer { + String(platformString: $0.baseAddress!) + } + expectEqualSequence(string.unicodeScalars, strDecoded.unicodeScalars) + + let strValidated = raw.withUnsafeBufferPointer { + String(validatingPlatformString: $0.baseAddress!) + } + expectEqual(isValid, strValidated != nil, "String(validatingPlatformString:)") + expectEqual(isValid, strDecoded == strValidated, + "String(validatingPlatformString:)") + + // Test null insertion + let rawChars = raw.lazy.map { SystemChar($0) } + expectEqual(sysRaw, SystemString(rawChars.dropLast())) + sysRaw.withSystemChars { // TODO: assuming we want null in withSysChars + expectEqualSequence(rawChars, $0, "rawChars") + } + + // Whether the content has normalized separators + let hasNormalSeparators: Bool = { + var normalSys = sysRaw + normalSys._normalizeSeparators() + return normalSys == sysRaw + }() + + let fpStr = FilePath(string) + if hasNormalSeparators { + expectEqualSequence( + string.unicodeScalars, fpStr.string.unicodeScalars, "FilePath from string") + expectEqual(string, String(decoding: fpStr), "FilePath from string") + expectEqual(string, String(validating: fpStr), "FilePath from string") + expectEqual(sysStr, fpStr._storage, "FilePath from string") + } + + let fpRaw = FilePath(sysRaw) + if hasNormalSeparators { + expectEqual(string, String(decoding: fpRaw), "raw FilePath") + expectEqual(isValid, nil != String(validating: fpRaw), "raw FilePath") + expectEqual(sysRaw, fpRaw._storage, "raw FilePath") + expectEqual(isValid, fpStr == fpRaw, "raw FilePath") + } + fpRaw.withPlatformString { fp0 in + fpRaw._storage.withPlatformString { storage0 in + expectEqual(fp0, storage0, + "FilePath withPlatformString address forwarding") + } + } + + sysRaw.withPlatformString { + let len = system_platform_strlen($0) + expectEqual(raw.count, 1+len, "SystemString.withPlatformString") + expectEqualSequence(raw, UnsafeBufferPointer(start: $0, count: 1+len), + "SystemString.withPlatformString") + if hasNormalSeparators { + expectEqual(sysRaw, FilePath(platformString: $0)._storage) + } + } + + } +} + +extension StringTest { + + static func valid( + _ str: String, + file: StaticString = #file, line: UInt = #line + ) -> StringTest { + StringTest( + string: str, + raw: makeRaw(str), + isValid: true, + file: file, line: line) + } + + static func invalid( + _ raw: [CInterop.PlatformChar], + file: StaticString = #file, line: UInt = #line + ) -> StringTest { + StringTest( + string: makeStr(raw), + raw: raw, + isValid: false, + file: file, line: line) + } +} + +final class SystemStringTest: XCTestCase { + + let validCases: Array = [ + .valid(""), + .valid("a"), + .valid("abc"), + .valid("/あ/🧟‍♀️"), + .valid("/あ\\🧟‍♀️"), + .valid("/あ\\🧟‍♀️///"), + .valid("あ_🧟‍♀️"), + .valid("/a/b/c"), + .valid("/a/b/c//"), + .valid(#"\a\b\c\\"#), + .valid("_a_b_c"), + ] + + #if os(Windows) + let invalidCases: Array = [ + // TODO: Unpaired surrogates + ] + #else + let invalidCases: Array = [ + .invalid([CChar(bitPattern: 0x80), 0]), + .invalid([CChar(bitPattern: 0xFF), 0]), + .invalid([0x2F, 0x61, 0x2F, 0x62, 0x2F, CChar(bitPattern: 0x83), 0]), + ] + #endif + + func testPlatformString() { + for test in validCases { + test.runAllTests() + } + for test in invalidCases { + test.runAllTests() + } + } + + // TODO: More exhaustive RAC+RRC SystemString tests + + func testAdHoc() { + var str: SystemString = "abc" + str.append(SystemChar(ascii: "d")) + XCTAssert(str == "abcd") + XCTAssert(str.count == 4) + XCTAssert(str.count == str.length) + + str.reserveCapacity(100) + XCTAssert(str == "abcd") + } + +} + diff --git a/Tests/SystemTests/TestingInfrastructure.swift b/Tests/SystemTests/TestingInfrastructure.swift index 53367cad..8aded96d 100644 --- a/Tests/SystemTests/TestingInfrastructure.swift +++ b/Tests/SystemTests/TestingInfrastructure.swift @@ -11,6 +11,9 @@ import XCTest import SystemInternals import SystemPackage +// To aid debugging, force failures to fatal error +internal var forceFatalFailures = false + internal protocol TestCase { // TODO: want a source location stack, more fidelity, kinds of stack entries, etc var file: StaticString { get } @@ -18,13 +21,22 @@ internal protocol TestCase { // TODO: Instead have an attribute to register a test in a allTests var, similar to the argument parser. func runAllTests() + + // Customization hook: add adornment to reported failure reason + // Defaut: reason or empty + func failureMessage(_ reason: String?) -> String } + extension TestCase { + // Default implementation + func failureMessage(_ reason: String?) -> String { reason ?? "" } + func expectEqualSequence( _ expected: S1, _ actual: S2, _ message: String? = nil ) where S1.Element: Equatable, S1.Element == S2.Element { - if !actual.elementsEqual(expected) { + if !expected.elementsEqual(actual) { + defer { print("expected: \(expected), actual: \(actual)") } fail(message) } } @@ -33,6 +45,34 @@ extension TestCase { _ message: String? = nil ) { if actual != expected { + defer { print("expected: \(expected), actual: \(actual)") } + fail(message) + } + } + func expectNotEqual( + _ expected: E, _ actual: E, + _ message: String? = nil + ) { + if actual == expected { + defer { print("expected not equal: \(expected) and \(actual)") } + fail(message) + } + } + func expectNil( + _ actual: T?, + _ message: String? = nil + ) { + if actual != nil { + defer { print("expected nil: \(actual!)") } + fail(message) + } + } + func expectNotNil( + _ actual: T?, + _ message: String? = nil + ) { + if actual == nil { + defer { print("expected non-nil") } fail(message) } } @@ -40,18 +80,22 @@ extension TestCase { _ actual: Bool, _ message: String? = nil ) { - expectEqual(true, actual, message) + if !actual { fail(message) } } func expectFalse( _ actual: Bool, _ message: String? = nil ) { - expectEqual(false, actual, message) + if actual { fail(message) } } func fail(_ reason: String? = nil) { - XCTAssert(false, reason ?? "", file: file, line: line) + XCTAssert(false, failureMessage(reason), file: file, line: line) + if forceFatalFailures { + fatalError(reason ?? "") + } } + } internal struct MockTestCase: TestCase { @@ -127,3 +171,14 @@ internal struct MockTestCase: TestCase { } } } + +// Force paths to be treated as Windows syntactically if `enabled` is +// true. +internal func withWindowsPaths(enabled: Bool, _ body: () -> ()) { + guard enabled else { return body() } + MockingDriver.withMockingEnabled { driver in + driver.forceWindowsSyntaxForPaths = true + body() + } +} + From 2167575e66d7db8839524a3c6fe9f12ec58528f9 Mon Sep 17 00:00:00 2001 From: Michael Ilseman Date: Mon, 1 Feb 2021 08:33:22 -0700 Subject: [PATCH 005/427] Add FilePath Syntactic Operations API Add the proposed non-file-system-interacting FilePath operations, Component, Root, and ComponentView. Proposal: https://forums.swift.org/t/api-review-filepath-syntactic-apis-version-2/44197 This change includes API/ABI additions and deprecations. --- .../FilePath/FilePathComponentView.swift | 220 +++ .../System/FilePath/FilePathComponents.swift | 281 ++++ Sources/System/FilePath/FilePathParsing.swift | 128 ++ Sources/System/FilePath/FilePathString.swift | 249 +++- Sources/System/FilePath/FilePathSyntax.swift | 616 +++++++++ .../FilePathComponentsTest.swift | 388 ++++++ .../FilePathTests/FilePathExtras.swift | 101 ++ .../FilePathTests/FilePathParsingTest.swift | 97 ++ .../FilePathTests/FilePathSyntaxTest.swift | 1231 +++++++++++++++++ Tests/SystemTests/SystemStringTests.swift | 31 + Tests/SystemTests/XCTestManifests.swift | 49 + 11 files changed, 3380 insertions(+), 11 deletions(-) create mode 100644 Sources/System/FilePath/FilePathComponentView.swift create mode 100644 Sources/System/FilePath/FilePathComponents.swift create mode 100644 Sources/System/FilePath/FilePathSyntax.swift create mode 100644 Tests/SystemTests/FilePathTests/FilePathComponentsTest.swift create mode 100644 Tests/SystemTests/FilePathTests/FilePathExtras.swift create mode 100644 Tests/SystemTests/FilePathTests/FilePathParsingTest.swift create mode 100644 Tests/SystemTests/FilePathTests/FilePathSyntaxTest.swift diff --git a/Sources/System/FilePath/FilePathComponentView.swift b/Sources/System/FilePath/FilePathComponentView.swift new file mode 100644 index 00000000..d281708d --- /dev/null +++ b/Sources/System/FilePath/FilePathComponentView.swift @@ -0,0 +1,220 @@ +/* + This source file is part of the Swift System open source project + + Copyright (c) 2020 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 +*/ + +// MARK: - API + +extension FilePath { + /// A bidirectional, range replaceable collection of the non-root components + /// that make up a file path. + /// + /// ComponentView provides access to standard `BidirectionalCollection` + /// algorithms for accessing components from the front or back, as well as + /// standard `RangeReplaceableCollection` algorithms for modifying the + /// file path using component or range of components granularity. + /// + /// Example: + /// + /// var path: FilePath = "/./home/./username/scripts/./tree" + /// let scriptIdx = path.components.lastIndex(of: "scripts")! + /// path.components.insert("bin", at: scriptIdx) + /// // path is "/./home/./username/bin/scripts/./tree" + /// + /// path.components.removeAll { $0.kind == .currentDirectory } + /// // path is "/home/username/bin/scripts/tree" + /// + public struct ComponentView { + internal var _path: FilePath + internal var _start: SystemString.Index + + internal init(_ path: FilePath) { + self._path = path + self._start = path._relativeStart + _invariantCheck() + } + } + + /// View the non-root components that make up this path. + public var components: ComponentView { + __consuming get { ComponentView(self) } + _modify { + // RRC's empty init means that we cann't guarantee that the yielded + // view will restore our root. So copy it out first. + // + // TODO(perf): Small-form root (especially on Unix). Have Root + // always copy out (not worth ref counting). Make sure that we're + // not needlessly sliding values around or triggering a COW + let rootStr = self.root?._systemString ?? SystemString() + var comp = ComponentView(self) + self = FilePath() + defer { + self = comp._path + + if !rootStr.isEmpty { + if let r = self.root { + // Roots can be forgotten but never altered + assert(r._slice.elementsEqual(rootStr)) + } + // TODO: conditional on it having changed? + self.root = Root(rootStr) + } + } + yield &comp + } + } +} + +// @available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *) +extension FilePath.ComponentView: BidirectionalCollection { + public typealias Element = FilePath.Component + public struct Index: Comparable, Hashable { + internal typealias Storage = SystemString.Index + + internal var _storage: Storage + + public static func < (lhs: Self, rhs: Self) -> Bool { + lhs._storage < rhs._storage + } + + fileprivate init(_ idx: Storage) { + self._storage = idx + } + } + + public var startIndex: Index { Index(_start) } + public var endIndex: Index { Index(_path._storage.endIndex) } + + public func index(after i: Index) -> Index { + return Index(_path._parseComponent(startingAt: i._storage).nextStart) + } + + public func index(before i: Index) -> Index { + Index(_path._parseComponent(priorTo: i._storage).lowerBound) + } + + public subscript(position: Index) -> FilePath.Component { + let end = _path._parseComponent(startingAt: position._storage).componentEnd + return FilePath.Component(_path, position._storage ..< end) + } +} + +extension FilePath.ComponentView: RangeReplaceableCollection { + public init() { + // FIXME: but what about the root? + self.init(FilePath()) + } + + // TODO(perf): We probably want to have concrete overrides or generic + // specializations taking FP.ComponentView and + // FP.ComponentView.SubSequence because we + // can just memcpy in those cases. We + // probably want to do that for all RRC operations. + + public mutating func replaceSubrange( + _ subrange: Range, with newElements: C + ) where C : Collection, Self.Element == C.Element { + defer { + _path._invariantCheck() + _invariantCheck() + } + if isEmpty { + _path = FilePath(root: _path.root, newElements) + return + } + let range = subrange.lowerBound._storage ..< subrange.upperBound._storage + if newElements.isEmpty { + let fromEnd = subrange.upperBound == endIndex + _path._storage.removeSubrange(range) + if fromEnd { + _path._removeTrailingSeparator() + } + return + } + + // TODO(perf): Avoid extra allocation by sliding elements down and + // filling in the bytes ourselves. + + // If we're inserting at the end, we need a leading separator. + var str = SystemString() + let atEnd = subrange.lowerBound == endIndex + if atEnd { + str.append(platformSeparator) + } + str.appendComponents(components: newElements) + if !atEnd { + str.append(platformSeparator) + } + _path._storage.replaceSubrange(range, with: str) + } +} + +// @available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *) +extension FilePath { + /// Create a file path from a root and a collection of components. + public init( + root: Root?, _ components: C + ) where C.Element == Component { + var str = root?._systemString ?? SystemString() + str.appendComponents(components: components) + self.init(str) + } + + /// Create a file path from a root and any number of components. + public init(root: Root?, components: Component...) { + self.init(root: root, components) + } + + /// Create a file path from an optional root and a slice of another path's + /// components. + public init(root: Root?, _ components: ComponentView.SubSequence) { + var str = root?._systemString ?? SystemString() + let (start, end) = + (components.startIndex._storage, components.endIndex._storage) + str.append(contentsOf: components.base._slice[start.. { + _start ..< _path._storage.endIndex + } + + internal init(_ str: SystemString) { + fatalError("TODO: consider dropping proto req") + } +} + +// MARK: - Invariants + +extension FilePath.ComponentView { + internal func _invariantCheck() { + #if DEBUG + if isEmpty { + precondition(_path.isEmpty == (_path.root == nil)) + return + } + + // If path has a root, + if _path.root != nil { + precondition(first!._slice.startIndex > _path._storage.startIndex) + precondition(first!._slice.startIndex == _path._relativeStart) + } + + self.forEach { $0._invariantCheck() } + + if let base = last { + precondition(base._slice.endIndex == _path._storage.endIndex) + } + + precondition(FilePath(root: _path.root, self) == _path) + #endif // DEBUG + } +} diff --git a/Sources/System/FilePath/FilePathComponents.swift b/Sources/System/FilePath/FilePathComponents.swift new file mode 100644 index 00000000..22fbe10c --- /dev/null +++ b/Sources/System/FilePath/FilePathComponents.swift @@ -0,0 +1,281 @@ +/* + This source file is part of the Swift System open source project + + Copyright (c) 2020 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 +*/ + +// MARK: - API + +// @available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *) +extension FilePath { + /// Represents a root of a file path. + /// + /// On Unix, a root is simply the directory separator `/`. + /// + /// On Windows, a root contains the entire path prefix up to and including + /// the final separator. + /// + /// Examples: + /// * Unix: + /// * `/` + /// * Windows: + /// * `C:\` + /// * `C:` + /// * `\` + /// * `\\server\share\` + /// * `\\?\UNC\server\share\` + /// * `\\?\Volume{12345678-abcd-1111-2222-123445789abc}\` + public struct Root { + internal var _path: FilePath + internal var _rootEnd: SystemString.Index + + internal init(_ path: FilePath, rootEnd: SystemString.Index) { + self._path = path + self._rootEnd = rootEnd + _invariantCheck() + } + // TODO: Definitely want a small form for this on Windows, + // and intern "/" for Unix. + } + + /// Represents an individual, non-root component of a file path. + /// + /// Components can be one of the special directory components (`.` or `..`) + /// or a file or directory name. Components are never empty and never + /// contain the directory separator. + /// + /// Example: + /// + /// var path: FilePath = "/tmp" + /// let file: FilePath.Component = "foo.txt" + /// file.kind == .regular // true + /// file.extension // "txt" + /// path.append(file) // path is "/tmp/foo.txt" + /// + public struct Component { + internal var _path: FilePath + internal var _range: Range + + // TODO: Make a small-component form to save on ARC overhead when + // extracted from a path, and especially to save on allocation overhead + // when constructing one from a String literal. + + internal init(_ path: FilePath, _ range: RE) + where RE.Bound == SystemString.Index { + self._path = path + self._range = range.relative(to: path._storage) + precondition(!self._range.isEmpty, "FilePath components cannot be empty") + self._invariantCheck() + } + } +} + +extension FilePath.Component { + + /// Whether a component is a regular file or directory name, or a special + /// directory `.` or `..` + public enum Kind { + /// The special directory `.`, representing the current directory. + case currentDirectory + + /// The special directory `..`, representing the parent directory. + case parentDirectory + + /// A file or directory name + case regular + } + + /// The kind of this component + public var kind: Kind { + if _path._isCurrentDirectory(_range) { return .currentDirectory } + if _path._isParentDirectory(_range) { return .parentDirectory } + return .regular + } + + /// Whether this component is either special directory `.` or `..`. + public var isSpecialDirectory: Bool { _path._isSpecialDirectory(_range) } +} + +extension FilePath.Root { + // TODO: Windows analysis APIs +} + +// MARK: - Internals + +extension SystemString { + // TODO: take insertLeadingSlash: Bool + // TODO: turn into an insert operation with slide + internal mutating func appendComponents( + components: C + ) where C.Element == FilePath.Component { + // TODO(perf): Consider pre-pass to count capacity, slide + + defer { + _removeTrailingSeparator() + FilePath(self)._invariantCheck() + } + + for idx in components.indices { + let component = components[idx] + component._withSystemChars { self.append(contentsOf: $0) } + self.append(platformSeparator) + } + } +} + +// Unifying protocol for common functionality between roots, components, +// and views onto SystemString and FilePath. +internal protocol _StrSlice: _PlatformStringable, Hashable, Codable { + var _storage: SystemString { get } + var _range: Range { get } + + init?(_ str: SystemString) + + func _invariantCheck() +} +extension _StrSlice { + internal var _slice: Slice { + Slice(base: _storage, bounds: _range) + } + + internal func _withSystemChars( + _ f: (UnsafeBufferPointer) throws -> T + ) rethrows -> T { + try _storage.withSystemChars { + try f(UnsafeBufferPointer(rebasing: $0[_range])) + } + } + internal func _withCodeUnits( + _ f: (UnsafeBufferPointer) throws -> T + ) rethrows -> T { + try _slice.withCodeUnits(f) + } + + internal init?(_platformString s: UnsafePointer) { + self.init(SystemString(platformString: s)) + } + + internal func _withPlatformString( + _ body: (UnsafePointer) throws -> Result + ) rethrows -> Result { + try _slice.withPlatformString(body) + } + + internal var _systemString: SystemString { SystemString(_slice) } +} +extension _StrSlice { + public static func == (lhs: Self, rhs: Self) -> Bool { + lhs._slice.elementsEqual(rhs._slice) + } + public func hash(into hasher: inout Hasher) { + hasher.combine(_slice.count) // discriminator + for element in _slice { + hasher.combine(element) + } + } +} +internal protocol _PathSlice: _StrSlice { + var _path: FilePath { get } +} +extension _PathSlice { + internal var _storage: SystemString { _path._storage } +} + +extension FilePath.Component: _PathSlice { +} +extension FilePath.Root: _PathSlice { + internal var _range: Range { + (..<_rootEnd).relative(to: _path._storage) + } +} +extension FilePath: _PlatformStringable { + func _withPlatformString(_ body: (UnsafePointer) throws -> Result) rethrows -> Result { + try _storage.withPlatformString(body) + } + + init(_platformString: UnsafePointer) { + self.init(SystemString(platformString: _platformString)) + } + +} + +// TODO(root): Re-think these initializers. We want them to be conv + +extension FilePath.Component { + // The index of the `.` denoting an extension + internal func _extensionIndex() -> SystemString.Index? { + guard !isSpecialDirectory, + let idx = _slice.lastIndex(of: .dot), + idx != _slice.startIndex + else { return nil } + + return idx + } + + internal func _extensionRange() -> Range? { + guard let idx = _extensionIndex() else { return nil } + return _slice.index(after: idx) ..< _slice.endIndex + } + + internal func _stemRange() -> Range { + _slice.startIndex ..< (_extensionIndex() ?? _slice.endIndex) + } +} + +internal func _makeExtension(_ ext: String) -> SystemString { + var result = SystemString() + result.append(.dot) + result.append(contentsOf: ext.unicodeScalars.lazy.map(SystemChar.init)) + return result +} + +extension FilePath.Component { + internal init?(_ str: SystemString) { + // FIXME: explicit null root? Or something else? + let path = FilePath(str) + guard path.root == nil, path.components.count == 1 else { + return nil + } + self = path.components.first! + self._invariantCheck() + } +} + +extension FilePath.Root { + internal init?(_ str: SystemString) { + // FIXME: explicit null root? Or something else? + let path = FilePath(str) + guard path.root != nil, path.components.isEmpty else { + return nil + } + self = path.root! + self._invariantCheck() + } +} + +// MARK: - Invariants + +// @available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *) +extension FilePath.Component { + // TODO: ensure this all gets easily optimized away in release... + internal func _invariantCheck() { + #if DEBUG + precondition(!_slice.isEmpty) + precondition(_slice.last != .null) + precondition(_slice.allSatisfy { !isSeparator($0) } ) + precondition(_path._relativeStart <= _slice.startIndex) + #endif // DEBUG + } +} +extension FilePath.Root { + internal func _invariantCheck() { + #if DEBUG + precondition(self._rootEnd > _path._storage.startIndex) + + // TODO: Windows root invariants + #endif + } +} diff --git a/Sources/System/FilePath/FilePathParsing.swift b/Sources/System/FilePath/FilePathParsing.swift index a097c740..b4ef51fa 100644 --- a/Sources/System/FilePath/FilePathParsing.swift +++ b/Sources/System/FilePath/FilePathParsing.swift @@ -121,6 +121,73 @@ extension FilePath { _storage._normalizeSeparators() } + // Remove any `.` and `..` components + internal mutating func _normalizeSpecialDirectories() { + guard !isLexicallyNormal else { return } + defer { assert(isLexicallyNormal) } + + let relStart = _relativeStart + let hasRoot = relStart != _storage.startIndex + + // TODO: all this logic might be nicer if _parseComponent considered + // the null character index to be the next start... + + var (writeIdx, readIdx) = (relStart, relStart) + while readIdx < _storage.endIndex { + let (compEnd, nextStart) = _parseComponent(startingAt: readIdx) + assert(readIdx < nextStart && compEnd <= nextStart) + let component = readIdx..= writeIdx) + if readIdx != writeIdx { + // TODO: Why was it everything after writeIdx? +// storage.removeSubrange(storage.index(after: writeIdx)...) + _storage.removeSubrange(writeIdx...) + _removeTrailingSeparator() + } + } } extension SystemString { @@ -203,6 +270,31 @@ extension FilePath { } } +extension FilePath.ComponentView { + // TODO: Store this... + internal var _relativeStart: SystemString.Index { + _path._relativeStart + } + + internal func parseComponentStart( + endingAt i: SystemString.Index + ) -> SystemString.Index { + if i == _relativeStart, i != _path._storage.startIndex { + return _path._storage.startIndex + } + var i = i + if i != _path._storage.endIndex { + assert(isSeparator(_path._storage[i])) + i = _path._storage.index(before: i) + } + var slice = _path._storage[.. ( rootEnd: Index, relativeBegin: Index @@ -220,6 +312,30 @@ extension SystemString { } } +extension FilePath.Root { + // Asserting self is a root, returns whether this is an + // absolute root. + // + // On Unix, all roots are absolute. On Windows, `\` and `X:` are + // relative roots + // + // TODO: public + internal var isAbsolute: Bool { + assert(FilePath(SystemString(self._slice)).root == self, "not a root") + + guard _windowsPaths else { return true } + + // `\` or `C:` are the only form of relative roots, and all + // absolute roots are at least 3 chars long. + let slice = self._slice + guard slice.count < 3 else { return true } + assert( + (slice.count == 1 && slice.first == .backslash) || + (slice.count == 2 && slice.last == .colon)) + return false + } +} + extension FilePath { internal var _portableDescription: String { guard _windowsPaths else { return description } @@ -252,6 +368,17 @@ extension FilePath { return true } + + // Perform an append, inseting a separator if needed. + // Note tha this will not check whether `content` is a root + internal mutating func _append(unchecked content: Slice) { + assert(FilePath(SystemString(content)).root == nil) + if content.isEmpty { return } + if _needsSeparatorForAppend { + _storage.append(platformSeparator) + } + _storage.append(contentsOf: content) + } } // MARK: - Invariants @@ -262,6 +389,7 @@ extension FilePath { normal._normalizeSeparators() precondition(self == normal) precondition(!self._storage._hasTrailingSeparator()) + precondition(_hasRoot == (self.root != nil)) #endif // DEBUG } } diff --git a/Sources/System/FilePath/FilePathString.swift b/Sources/System/FilePath/FilePathString.swift index 5607b6f0..eb9b27b3 100644 --- a/Sources/System/FilePath/FilePathString.swift +++ b/Sources/System/FilePath/FilePathString.swift @@ -39,6 +39,77 @@ extension FilePath { } } +extension FilePath.Component { + /// Creates a file path component by copying bytes from a null-terminated + /// platform string. + /// + /// Returns `nil` if `platformString` is empty, is a root, or has more than + /// one component in it. + /// + /// - Parameter platformString: A pointer to a null-terminated platform + /// string. + /// + public init?(platformString: UnsafePointer) { + self.init(_platformString: platformString) + } + + /// Calls the given closure with a pointer to the contents of the file path + /// component, represented as a null-terminated platform string. + /// + /// If this is not the last component of a path, an allocation will occur in + /// order to add the null terminator. + /// + /// - Parameter body: A closure with a pointer parameter + /// that points to a null-terminated platform string. + /// If `body` has a return value, + /// that value is also used as the return value for this method. + /// - Returns: The return value, if any, of the `body` closure parameter. + /// + /// The pointer passed as an argument to `body` is valid + /// only during the execution of this method. + /// Don't try to store the pointer for later use. + public func withPlatformString( + _ body: (UnsafePointer) throws -> Result + ) rethrows -> Result { + try _withPlatformString(body) + } +} + +extension FilePath.Root { + /// Creates a file path root by copying bytes from a null-terminated platform + /// string. + /// + /// Returns `nil` if `platformString` is empty or is not a root. + /// + /// - Parameter platformString: A pointer to a null-terminated platform + /// string. + /// + public init?(platformString: UnsafePointer) { + self.init(_platformString: platformString) + } + + /// Calls the given closure with a pointer to the contents of the file path + /// root, represented as a null-terminated platform string. + /// + /// If the path has a relative portion, an allocation will occur in order to + /// add the null terminator. + /// + /// - Parameter body: A closure with a pointer parameter + /// that points to a null-terminated platform string. + /// If `body` has a return value, + /// that value is also used as the return value for this method. + /// - Returns: The return value, if any, of the `body` closure parameter. + /// + /// The pointer passed as an argument to `body` is valid + /// only during the execution of this method. + /// Don't try to store the pointer for later use. + public func withPlatformString( + _ body: (UnsafePointer) throws -> Result + ) rethrows -> Result { + try _withPlatformString(body) + } +} + // MARK: - String literals // @available(macOS 10.16, iOS 14.0, watchOS 7.0, tvOS 14.0, *) @@ -60,6 +131,54 @@ extension FilePath: ExpressibleByStringLiteral { } } +// @available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *) +extension FilePath.Component: ExpressibleByStringLiteral { + /// Create a file path component from a string literal. + /// + /// Precondition: `stringLiteral` is non-empty, is not a root, + /// and has only one component in it. + public init(stringLiteral: String) { + guard let s = FilePath.Component(stringLiteral) else { + // TODO: static assert + preconditionFailure(""" + FilePath.Component must be created from exactly one non-root component + """) + } + self = s + } + + /// Create a file path component from a string. + /// + /// Returns `nil` if `string` is empty, a root, or has more than one component + /// in it. + public init?(_ string: String) { + self.init(SystemString(string)) + } +} + +// @available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *) +extension FilePath.Root: ExpressibleByStringLiteral { + /// Create a file path root from a string literal. + /// + /// Precondition: `stringLiteral` is non-empty and is a root. + public init(stringLiteral: String) { + guard let s = FilePath.Root(stringLiteral) else { + // TODO: static assert + preconditionFailure(""" + FilePath.Root must be created from a root + """) + } + self = s + } + + /// Create a file path root from a string. + /// + /// Returns `nil` if `string` is empty or is not a root. + public init?(_ string: String) { + self.init(SystemString(string)) + } +} + // MARK: - Printing and dumping // @available(macOS 10.16, iOS 14.0, watchOS 7.0, tvOS 14.0, *) @@ -78,6 +197,40 @@ extension FilePath: CustomStringConvertible, CustomDebugStringConvertible { public var debugDescription: String { description.debugDescription } } +// @available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *) +extension FilePath.Component: CustomStringConvertible, CustomDebugStringConvertible { + + /// A textual representation of the path component. + /// + /// If the content of the path component isn't a well-formed Unicode string, + /// this replaces invalid bytes with U+FFFD. See `String.init(decoding:)`. + @inline(never) + public var description: String { String(decoding: self) } + + /// A textual representation of the path component, suitable for debugging. + /// + /// If the content of the path component isn't a well-formed Unicode string, + /// this replaces invalid bytes with U+FFFD. See `String.init(decoding:)`. + public var debugDescription: String { description.debugDescription } +} + +// @available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *) +extension FilePath.Root: CustomStringConvertible, CustomDebugStringConvertible { + + /// A textual representation of the path root. + /// + /// If the content of the path root isn't a well-formed Unicode string, + /// this replaces invalid bytes with U+FFFD. See `String.init(decoding:)`. + @inline(never) + public var description: String { String(decoding: self) } + + /// A textual representation of the path root, suitable for debugging. + /// + /// If the content of the path root isn't a well-formed Unicode string, + /// this replaces invalid bytes with U+FFFD. See `String.init(decoding:)`. + public var debugDescription: String { description.debugDescription } +} + // MARK: - Convenience helpers // Convenience helpers @@ -91,6 +244,27 @@ extension FilePath { } } +extension FilePath.Component { + /// Creates a string by interpreting the component’s content as UTF-8 on Unix + /// and UTF-16 on Windows. + /// + /// This property is equivalent to calling `String(decoding: component)`. + public var string: String { + String(decoding: self) + } +} + +extension FilePath.Root { + /// On Unix, this returns `"/"`. + /// + /// On Windows, interprets the root's content as UTF-16 on Windows. + /// + /// This property is equivalent to calling `String(decoding: root)`. + public var string: String { + String(decoding: self) + } +} + // MARK: - Decoding and validating // @available(macOS 10.16, iOS 14.0, watchOS 7.0, tvOS 14.0, *) @@ -123,6 +297,70 @@ extension String { } } +// @available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *) +extension String { + /// Creates a string by interpreting the path component's content as UTF-8 on + /// Unix and UTF-16 on Windows. + /// + /// - Parameter component: The path component to be interpreted as + /// `CInterop.PlatformUnicodeEncoding`. + /// + /// If the content of the path component isn't a well-formed Unicode string, + /// this initializer replaces invalid bytes with U+FFFD. + /// This means that, depending on the semantics of the specific file system, + /// conversion to a string and back to a path component + /// might result in a value that's different from the original path component. + public init(decoding component: FilePath.Component) { + self.init(_decoding: component) + } + + /// Creates a string from a path component, validating its contents as UTF-8 + /// on Unix and UTF-16 on Windows. + /// + /// - Parameter component: The path component to be interpreted as + /// `CInterop.PlatformUnicodeEncoding`. + /// + /// If the contents of the path component isn't a well-formed Unicode string, + /// this initializer returns `nil`. + public init?(validating component: FilePath.Component) { + self.init(_validating: component) + } +} + +// @available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *) +extension String { + /// On Unix, creates the string `"/"` + /// + /// On Windows, creates a string by interpreting the path root's content as + /// UTF-16. + /// + /// - Parameter root: The path root to be interpreted as + /// `CInterop.PlatformUnicodeEncoding`. + /// + /// If the content of the path root isn't a well-formed Unicode string, + /// this initializer replaces invalid bytes with U+FFFD. + /// This means that on Windows, + /// conversion to a string and back to a path root + /// might result in a value that's different from the original path root. + public init(decoding root: FilePath.Root) { + self.init(_decoding: root) + } + + /// On Unix, creates the string `"/"` + /// + /// On Windows, creates a string from a path root, validating its contents as + /// UTF-16 on Windows. + /// + /// - Parameter root: The path root to be interpreted as + /// `CInterop.PlatformUnicodeEncoding`. + /// + /// On Windows, if the contents of the path root isn't a well-formed Unicode + /// string, this initializer returns `nil`. + public init?(validating root: FilePath.Root) { + self.init(_validating: root) + } +} + // MARK: - Internal helpers extension String { @@ -140,17 +378,6 @@ extension String { } } -extension FilePath: _PlatformStringable { - func _withPlatformString(_ body: (UnsafePointer) throws -> Result) rethrows -> Result { - try _storage.withPlatformString(body) - } - - init(_platformString: UnsafePointer) { - self.init(SystemString(platformString: _platformString)) - } - - } - // MARK: - Deprecations extension String { diff --git a/Sources/System/FilePath/FilePathSyntax.swift b/Sources/System/FilePath/FilePathSyntax.swift new file mode 100644 index 00000000..070249f1 --- /dev/null +++ b/Sources/System/FilePath/FilePathSyntax.swift @@ -0,0 +1,616 @@ +/* + This source file is part of the Swift System open source project + + Copyright (c) 2020 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 +*/ + +// MARK: - Query API + +// @available(...) +extension FilePath { + /// Returns true if this path uniquely identifies the location of + /// a file without reference to an additional starting location. + /// + /// On Unix platforms, absolute paths begin with a `/`. `isAbsolute` is + /// equivalent to `root != nil`. + /// + /// On Windows, absolute paths are fully qualified paths. `isAbsolute` is + /// _not_ equivalent to `root != nil` for traditional DOS paths + /// (e.g. `C:foo` and `\bar` have roots but are not absolute). UNC paths + /// and device paths are always absolute. Traditional DOS paths are + /// absolute only if they begin with a volume or drive followed by + /// a `:` and a separator. + /// + /// NOTE: This does not perform shell expansion or substitute + /// environment variables; paths beginning with `~` are considered relative. + /// + /// Examples: + /// * Unix: + /// * `/usr/local/bin` + /// * `/tmp/foo.txt` + /// * `/` + /// * Windows: + /// * `C:\Users\` + /// * `\\?\UNC\server\share\bar.exe` + /// * `\\server\share\bar.exe` + public var isAbsolute: Bool { + self.root?.isAbsolute ?? false + } + + /// Returns true if this path is not absolute (see `isAbsolute`). + /// + /// Examples: + /// * Unix: + /// * `~/bar` + /// * `tmp/foo.txt` + /// * Windows: + /// * `bar\baz` + /// * `C:Users\` + /// * `\Users` + public var isRelative: Bool { !isAbsolute } + + /// Returns whether `other` is a prefix of `self`, only considering + /// whole path components. + /// + /// Example: + /// + /// let path: FilePath = "/usr/bin/ls" + /// path.starts(with: "/") // true + /// path.starts(with: "/usr/bin") // true + /// path.starts(with: "/usr/bin/ls") // true + /// path.starts(with: "/usr/bin/ls///") // true + /// path.starts(with: "/us") // false + /// + /// TODO(Windows docs): examples with roots, such as whether `\foo\bar` + /// starts with `C:\foo` + /// + public func starts(with other: FilePath) -> Bool { + guard !other.isEmpty else { return true } + return self.root == other.root && components.starts( + with: other.components) + } + + /// Returns whether `other` is a suffix of `self`, only considering + /// whole path components. + /// + /// Example: + /// + /// let path: FilePath = "/usr/bin/ls" + /// path.ends(with: "ls") // true + /// path.ends(with: "bin/ls") // true + /// path.ends(with: "usr/bin/ls") // true + /// path.ends(with: "/usr/bin/ls///") // true + /// path.ends(with: "/ls") // false + /// + /// TODO(Windows docs): examples with roots, such as whether `C:\foo\bar` + /// ends with `C:bar` + /// + public func ends(with other: FilePath) -> Bool { + if other.root != nil { + // TODO: anything tricky here for Windows? + return self == other + } + + return components.reversed().starts( + with: other.components.reversed()) + } + + /// Whether this path is empty + public var isEmpty: Bool { _storage.isEmpty } +} + +// MARK: - Decompose a path +extension FilePath { + /// Returns the root of a path if there is one, otherwise `nil`. + /// + /// On Unix, this will return the leading `/` if the path is absolute + /// and `nil` if the path is relative. + /// + /// On Windows, for traditional DOS paths, this will return + /// the path prefix up to and including a root directory or + /// a supplied drive or volume. Otherwise, if the path is relative to + /// both the current directory and current drive, returns `nil`. + /// + /// On Windows, for UNC or device paths, this will return the path prefix + /// up to and including the host and share for UNC paths or the volume for + /// device paths followed by any subsequent separator. + /// + /// Examples: + /// * Unix: + /// * `/foo/bar => /` + /// * `foo/bar => nil` + /// * Windows: + /// * `C:\foo\bar => C:\` + /// * `C:foo\bar => C:` + /// * `\foo\bar => \ ` + /// * `foo\bar => nil` + /// * `\\server\share\file => \\server\share\` + /// * `\\?\UNC\server\share\file => \\?\UNC\server\share\` + /// * `\\.\device\folder => \\.\device\` + /// + /// Setting the root to `nil` will remove the root and setting a new + /// root will replace the root. + /// + /// Example: + /// + /// var path: FilePath = "/foo/bar" + /// path.root = nil // path is "foo/bar" + /// path.root = "/" // path is "/foo/bar" + /// + /// Example (Windows): + /// + /// var path: FilePath = #"\foo\bar"# + /// path.root = nil // path is #"foo\bar"# + /// path.root = "C:" // path is #"C:foo\bar"# + /// path.root = #"C:\"# // path is #"C:\foo\bar"# + /// + public var root: FilePath.Root? { + get { + guard _hasRoot else { return nil } + return Root(self, rootEnd: _relativeStart) + } + set { + defer { _invariantCheck() } + guard let r = newValue else { + _storage.removeSubrange(..<_relativeStart) + return + } + _storage.replaceSubrange(..<_relativeStart, with: r._slice) + } + } + + /// Creates a new path containing just the components, i.e. everything + /// after `root`. + /// + /// Returns self if `root == nil`. + /// + /// Examples: + /// * Unix: + /// * `/foo/bar => foo/bar` + /// * `foo/bar => foo/bar` + /// * `/ => ""` + /// * Windows: + /// * `C:\foo\bar => foo\bar` + /// * `foo\bar => foo\bar` + /// * `\\?\UNC\server\share\file => file` + /// * `\\?\device\folder\file.exe => folder\file.exe` + /// * `\\server\share\file => file` + /// * `\ => ""` + /// + public __consuming func removingRoot() -> FilePath { + var copy = self + copy.root = nil + return copy + } +} + +extension FilePath { + /// Returns the final component of the path. + /// Returns `nil` if the path is empty or only contains a root. + /// + /// Note: Even if the final component is a special directory + /// (`.` or `..`), it will still be returned. See `lexicallyNormalize()`. + /// + /// Examples: + /// * Unix: + /// * `/usr/local/bin/ => bin` + /// * `/tmp/foo.txt => foo.txt` + /// * `/tmp/foo.txt/.. => ..` + /// * `/tmp/foo.txt/. => .` + /// * `/ => nil` + /// * Windows: + /// * `C:\Users\ => Users` + /// * `C:Users\ => Users` + /// * `C:\ => nil` + /// * `\Users\ => Users` + /// * `\\?\UNC\server\share\bar.exe => bar.exe` + /// * `\\server\share => nil` + /// * `\\?\UNC\server\share\ => nil` + /// + public var lastComponent: Component? { components.last } + + /// Creates a new path with everything up to but not including + /// `lastComponent`. + /// + /// If the path only contains a root, returns `self`. + /// If the path has no root and only includes a single component, + /// returns an empty FilePath. + /// + /// Examples: + /// * Unix: + /// * `/usr/bin/ls => /usr/bin` + /// * `/foo => /` + /// * `/ => /` + /// * `foo => ""` + /// * Windows: + /// * `C:\foo\bar.exe => C:\foo` + /// * `C:\ => C:\` + /// * `\\server\share\folder\file.txt => \\server\share\folder` + /// * `\\server\share\ => \\server\share\` + public __consuming func removingLastComponent() -> FilePath { + var copy = self + copy.removeLastComponent() + return copy + } + + /// In-place mutating variant of `removingLastComponent`. + /// + /// If `self` only contains a root, does nothing and returns `false`. + /// Otherwise removes `lastComponent` and returns `true`. + /// + /// Example: + /// + /// var path = "/usr/bin" + /// path.removeLastComponent() == true // path is "/usr" + /// path.removeLastComponent() == true // path is "/" + /// path.removeLastComponent() == false // path is "/" + /// + @discardableResult + public mutating func removeLastComponent() -> Bool { + defer { _invariantCheck() } + guard let lastRel = lastComponent else { return false } + _storage.removeSubrange(lastRel._slice.indices) + _removeTrailingSeparator() + return true + } +} + +extension FilePath.Component { + /// The extension of this file or directory component. + /// + /// If `self` does not contain a `.` anywhere, or only + /// at the start, returns `nil`. Otherwise, returns everything after the dot. + /// + /// Examples: + /// * `foo.txt => txt` + /// * `foo.tar.gz => gz` + /// * `Foo.app => app` + /// * `.hidden => nil` + /// * `.. => nil` + /// + public var `extension`: String? { + guard let range = _extensionRange() else { return nil } + return _slice[range].string + } + + /// The non-extension portion of this file or directory component. + /// + /// Examples: + /// * `foo.txt => foo` + /// * `foo.tar.gz => foo.tar` + /// * `Foo.app => Foo` + /// * `.hidden => .hidden` + /// * `.. => ..` + /// + public var stem: String { + _slice[_stemRange()].string + } +} + +extension FilePath { + + /// The extension of the file or directory last component. + /// + /// If `lastComponent` is `nil` or one of the special path components + /// `.` or `..`, `get` returns `nil` and `set` does nothing. + /// + /// If `lastComponent` does not contain a `.` anywhere, or only + /// at the start, `get` returns `nil` and `set` will append a + /// `.` and `newValue` to `lastComponent`. + /// + /// Otherwise `get` returns everything after the last `.` and `set` will + /// replace the extension. + /// + /// Examples: + /// * `/tmp/foo.txt => txt` + /// * `/Appliations/Foo.app/ => app` + /// * `/Appliations/Foo.app/bar.txt => txt` + /// * `/tmp/foo.tar.gz => gz` + /// * `/tmp/.hidden => nil` + /// * `/tmp/.hidden. => ""` + /// * `/tmp/.. => nil` + /// + /// Example: + /// + /// var path = "/tmp/file" + /// path.extension = ".txt" // path is "/tmp/file.txt" + /// path.extension = ".o" // path is "/tmp/file.o" + /// path.extension = nil // path is "/tmp/file" + /// path.extension = "" // path is "/tmp/file." + /// + public var `extension`: String? { + get { lastComponent?.extension } + set { + defer { _invariantCheck() } + guard let base = lastComponent, !base.isSpecialDirectory else { return } + + let suffix: SystemString + if let ext = newValue { + suffix = _makeExtension(ext) + } else { + suffix = SystemString() + } + + let extRange = ( + base._extensionIndex() ?? base._slice.endIndex + ) ..< base._slice.endIndex + + _storage.replaceSubrange(extRange, with: suffix) + } + } + + /// The non-extension portion of the file or directory last component. + /// + /// Returns `nil` if `lastComponent` is `nil` + /// + /// * `/tmp/foo.txt => foo` + /// * `/Appliations/Foo.app/ => Foo` + /// * `/Appliations/Foo.app/bar.txt => bar` + /// * `/tmp/.hidden => .hidden` + /// * `/tmp/.. => ..` + /// * `/ => nil` + public var stem: String? { lastComponent?.stem } + +} + +extension FilePath { + /// Whether the path is in lexical-normal form, that is `.` and `..` + /// components have been collapsed lexically (i.e. without following + /// symlinks). + /// + /// Examples: + /// * `"/usr/local/bin".isLexicallyNormal == true` + /// * `"../local/bin".isLexicallyNormal == true` + /// * `"local/bin/..".isLexicallyNormal == false` + public var isLexicallyNormal: Bool { + // `..` components are permitted at the front of a + // relative path, otherwise there should be no special directories + // + // FIXME: Windows `C:..\foo\bar` should probably be lexically normal, but + // `\..\foo\bar` should not. + components.drop( + while: { root == nil && $0.kind == .parentDirectory } + ).allSatisfy { !$0.isSpecialDirectory } + } + + /// Collapse `.` and `..` components lexically (i.e. without following + /// symlinks). + /// + /// Examples: + /// * `/usr/./local/bin/.. => /usr/local` + /// * `/../usr/local/bin => /usr/local/bin` + /// * `../usr/local/../bin => ../usr/bin` + public mutating func lexicallyNormalize() { + defer { _invariantCheck() } + _normalizeSpecialDirectories() + } + + /// Returns a copy of `self` in lexical-normal form, that is `.` and `..` + /// components have been collapsed lexically (i.e. without following + /// symlinks). See `lexicallyNormalize` + public var lexicallyNormal: FilePath { + __consuming get { + var copy = self + copy.lexicallyNormalize() + return copy + } + } + + /// Create a new `FilePath` by resolving `subpath` relative to `self`, + /// ensuring that the result is lexically contained within `self`. + /// + /// `subpath` will be lexically normalized (see `lexicallyNormalize`) as + /// part of resolution, meaning any contained `.` and `..` components will + /// be collapsed without resolving symlinks. Any root in `subpath` will be + /// ignored. + /// + /// Returns `nil` if the result would "escape" from `self` through use of + /// the special directory component `..`. + /// + /// This is useful for protecting against arbitrary path traversal from an + /// untrusted subpath: the result is guaranteed to be lexically contained + /// within `self`. Since this operation does not consult the file system to + /// resolve symlinks, any escaping symlinks nested inside of `self` can still + /// be targeted by the result. + /// + /// Example: + /// + /// let staticContent: FilePath = "/var/www/my-website/static" + /// let links: [FilePath] = + /// ["index.html", "/assets/main.css", "../../../../etc/passwd"] + /// links.map { staticContent.lexicallyResolving($0) } + /// // ["/var/www/my-website/static/index.html", + /// // "/var/www/my-website/static/assets/main.css", + /// // nil] + public __consuming func lexicallyResolving( + _ subpath: __owned FilePath + ) -> FilePath? { + let subpath = subpath.removingRoot().lexicallyNormal + guard !subpath.isEmpty else { return self } + guard subpath.components.first?.kind != .parentDirectory else { + return nil + } + return self.appending(subpath.components) + } +} + +// Modification and concatenation API +extension FilePath { + /// If `prefix` is a prefix of `self`, removes it and returns `true`. + /// Otherwise returns `false`. + /// + /// Example: + /// + /// var path: FilePath = "/usr/local/bin" + /// path.removePrefix("/usr/bin") // false + /// path.removePrefix("/us") // false + /// path.removePrefix("/usr/local") // true, path is "bin" + /// + /// TODO(Windows docs): example with roots + /// + public mutating func removePrefix(_ prefix: FilePath) -> Bool { + defer { _invariantCheck() } + // FIXME: Should Windows have more nuanced semantics? + guard root == prefix.root else { return false } + let (tail, remainder) = _dropCommonPrefix(components, prefix.components) + guard remainder.isEmpty else { return false } + self._storage.removeSubrange(..( + _ components: __owned C + ) where C.Element == FilePath.Component { + defer { _invariantCheck() } + for c in components { + _append(unchecked: c._slice) + } + } + + /// Append the contents of `other`, ignoring any spurious leading separators. + /// + /// A leading separator is spurious if `self` is non-empty. + /// + /// Example: + /// var path: FilePath = "" + /// path.append("/var/www/website") // "/var/www/website" + /// path.append("static/assets") // "/var/www/website/static/assets" + /// path.append("/main.css") // "/var/www/website/static/assets/main.css" + /// + /// TODO(Windows docs): example with roots, should we rephrase this "spurious + /// roots"? + /// + public mutating func append(_ other: __owned String) { + defer { _invariantCheck() } + guard !other.utf8.isEmpty else { return } + guard !isEmpty else { + self = FilePath(other) + return + } + let otherPath = FilePath(other) + _append(unchecked: otherPath._storage[otherPath._relativeStart...]) + } + + /// Non-mutating version of `append(_:Component)`. + /// + /// TODO(Windows docs): example with roots + /// + public __consuming func appending(_ other: __owned Component) -> FilePath { + var copy = self + copy.append(other) + return copy + } + + /// Non-mutating version of `append(_:C)`. + /// + /// TODO(Windows docs): example with roots + /// + public __consuming func appending( + _ components: __owned C + ) -> FilePath where C.Element == FilePath.Component { + var copy = self + copy.append(components) + return copy + } + + /// Non-mutating version of `append(_:String)`. + /// + /// TODO(Windows docs): example with roots + /// + public __consuming func appending(_ other: __owned String) -> FilePath { + var copy = self + copy.append(other) + return copy + } + + /// If `other` does not have a root, append each component of `other`. If + /// `other` has a root, replaces `self` with other. + /// + /// This operation mimics traversing a directory structure (similar to the + /// `cd` command), where pushing a relative path will append its components + /// and pushing an absolute path will first clear `self`'s existing + /// components. + /// + /// Example: + /// + /// var path: FilePath = "/tmp" + /// path.push("dir/file.txt") // path is "/tmp/dir/file.txt" + /// path.push("/bin") // path is "/bin" + /// + /// TODO(Windows docs): examples and docs with roots, update/generalize doc + /// comment + /// + public mutating func push(_ other: __owned FilePath) { + defer { _invariantCheck() } + guard other.root == nil else { + self = other + return + } + /// FIXME: Windows drive-relative roots, etc? + _append(unchecked: other._storage[...]) + } + + /// Non-mutating version of `push()` + /// + /// TODO(Windows docs): examples and docs with roots + /// + public __consuming func pushing(_ other: __owned FilePath) -> FilePath { + var copy = self + copy.push(other) + return copy + } + + /// Remove the contents of the path, keeping the null terminator. + public mutating func removeAll(keepingCapacity: Bool = false) { + defer { _invariantCheck() } + _storage.removeAll(keepingCapacity: keepingCapacity) + } + + /// Reserve enough storage space to store `minimumCapacity` platform + /// characters. + public mutating func reserveCapacity(_ minimumCapacity: Int) { + defer { _invariantCheck() } + self._storage.reserveCapacity(minimumCapacity) + } +} + +// MARK - Renamed +extension FilePath { + @available(*, unavailable, renamed: "removingLastComponent()") + public var dirname: FilePath { removingLastComponent() } + + @available(*, unavailable, renamed: "lastComponent") + public var basename: Component? { lastComponent } +} diff --git a/Tests/SystemTests/FilePathTests/FilePathComponentsTest.swift b/Tests/SystemTests/FilePathTests/FilePathComponentsTest.swift new file mode 100644 index 00000000..a43b1b0f --- /dev/null +++ b/Tests/SystemTests/FilePathTests/FilePathComponentsTest.swift @@ -0,0 +1,388 @@ +/* + This source file is part of the Swift System open source project + + Copyright (c) 2020 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 +*/ + +import XCTest +import SystemPackage + +@testable +import SystemPackage + + +// Helper to organize some ad-hoc testing +// +// TODO: Currently a class so we can overwrite file/line, but that can be +// re-evaluated when we have source loc stacks. +private final class AdHocComponentsTest: TestCase { + // TODO (source-loc stack): Push fil/line from init onto stack + + // Record the top-most file/line info (`expect` overwrites these values) + // + // TODO: When we have source loc stacks, push the location from the init, + // and `expect` will be push/pops + var file: StaticString + var line: UInt + + var path: FilePath + var body: (AdHocComponentsTest) -> () + + init( + _ path: FilePath, + _ file: StaticString = #file, + _ line: UInt = #line, + _ body: @escaping (AdHocComponentsTest) -> () + ) { + self.file = file + self.line = line + self.path = path + self.body = body + } + + func runAllTests() { + body(self) + } +} + +private func adhocComponentsTest( + _ path: FilePath, + _ file: StaticString = #file, + _ line: UInt = #line, + _ body: @escaping (AdHocComponentsTest) -> () +) { + let test = AdHocComponentsTest(path, file, line, body) + test.runAllTests() +} + +extension AdHocComponentsTest { + // Temporarily re-bind file/line + func withSourceLoc( + _ newFile: StaticString, + _ newLine: UInt, + _ body: () -> () + ) { + let (origFile, origLine) = (self.file, self.line) + (self.file, self.line) = (newFile, newLine) + defer { (self.file, self.line) = (origFile, origLine) } + body() + } + + // Customize error report by adding our path and components to output + func failureMessage(_ reason: String?) -> String { + """ + + Fail + path: \(path) + components: \(Array(path.components)) + \(reason ?? "") + """ + } + + func expect( + _ expected: FilePath, + file: StaticString = #file, line: UInt = #line + ) { + + withSourceLoc(file, line) { + expectEqual(expected, path, "expected: \(expected)") + } + } + + func expectRelative( + file: StaticString = #file, line: UInt = #line + ) { + withSourceLoc(file, line) { + expectTrue(path.isRelative, "expected relative") + } + } + + func expectAbsolute( + file: StaticString = #file, line: UInt = #line + ) { + withSourceLoc(file, line) { + expectTrue(path.isAbsolute, "expected absolute") + } + } + + // TODO: Do we want to overload others like expectEqual[Sequence]? +} + +// @available(9999....) +struct TestPathComponents: TestCase { + var path: FilePath + var expectedRoot: FilePath.Root? + var expectedComponents: [FilePath.Component] + + var pathComponents: Array { Array(path.components) } + + let file: StaticString + let line: UInt + + func failureMessage(_ reason: String?) -> String { + """ + + Fail \(reason ?? "") + path: \(path) + components: \(pathComponents)) + expected: \(expectedComponents) + """ + } + + init( + _ path: FilePath, + root: FilePath.Root?, + _ components: C, + file: StaticString = #file, line: UInt = #line + ) where C.Element == FilePath.Component { + self.path = path + self.expectedRoot = root + self.expectedComponents = Array(components) + self.file = file + self.line = line + } + + func testComponents() { + expectEqual(expectedRoot, path.root) + expectEqualSequence( + expectedComponents, Array(path.components), "testComponents()") + } + + func testBidi() { + + + } + + func testRRC() { + // TODO: Convert tests into mutation tests +// // What generalized tests can we do, given how initial "/" is special? +// // E.g. absolute path inserted into itself can have only one root +// +// do { +// var path = self.path +// if path.isAbsolute { +// path.components.removeFirst() +// } +// expectTrue(path.isRelative) +// +// let componentsArray = Array(path.components) +// path.components.append(contentsOf: componentsArray) +// expectEqualSequence(componentsArray + componentsArray, path.components) +// +// // TODO: Other generalized tests which work on relative paths +// } + } + + func runAllTests() { + testComponents() + testBidi() + testRRC() + } +} + +// TODO: Note that double-reversal will drop root if FilePath is constructed in between ... + +// @available(macOS 10.16, iOS 14.0, watchOS 7.0, tvOS 14.0, *) +final class FilePathComponentsTest: XCTestCase { + + let testPaths: Array = [ + TestPathComponents("", root: nil, []), + TestPathComponents("/", root: "/", []), + TestPathComponents("foo", root: nil, ["foo"]), + TestPathComponents("foo/", root: nil, ["foo"]), + TestPathComponents("/foo", root: "/", ["foo"]), + TestPathComponents("foo/bar", root: nil, ["foo", "bar"]), + TestPathComponents("foo/bar/", root: nil, ["foo", "bar"]), + TestPathComponents("/foo/bar", root: "/", ["foo", "bar"]), + TestPathComponents("///foo//", root: "/", ["foo"]), + TestPathComponents("/foo///bar", root: "/", ["foo", "bar"]), + TestPathComponents("foo/bar/", root: nil, ["foo", "bar"]), + TestPathComponents("foo///bar/baz/", root: nil, ["foo", "bar", "baz"]), + TestPathComponents("//foo///bar/baz/", root: "/", ["foo", "bar", "baz"]), + TestPathComponents("./", root: nil, ["."]), + TestPathComponents("./..", root: nil, [".", ".."]), + TestPathComponents("/./..//", root: "/", [".", ".."]), + ] + + // TODO: generalize to a driver protocol that will inherit from XCTest, expose allTestCases + // based on an associated type, and provide the testCasees func, assuming XCTest supports + // that. + func testCases() { + testPaths.forEach { $0.runAllTests() } + } + + // TODO: Convert these kinds of test cases into mutation API test cases. + func testAdHocRRC() { + var path: FilePath = "/usr/local/bin" + + func expect( + _ s: String, + _ file: StaticString = #file, + _ line: UInt = #line + ) { + if path == FilePath(s) { return } + + defer { print("expected: \(s), actual: \(path)") } + XCTAssert(false, file: file, line: line) + } + + // Run `body`, restoring `path` afterwards + func restoreAfter( + body: () -> () + ) { + let copy = path + defer { path = copy } + body() + } + + // Interior removal keeps path prefix intact, if there is one + restoreAfter { + path = "prefix//middle1/middle2////suffix" + let suffix = path.components.indices.last! + path.components.removeSubrange(.. = [ + "/a/b", + "/a/b/", + "/a//b/", + "/a/b//", + "/a/b////", + "/a////b/", + ] + #if !os(Windows) + paths.append("//a/b") + paths.append("///a/b") + paths.append("///a////b") + paths.append("///a////b///") + #endif + + for path in paths { + var path = path + path._normalizeSeparators() + XCTAssertEqual(path, "/a/b") + } + } +} + +// TODO: Test hashValue and equatable for equal components, i.e. make +// sure indices are not part of the hash. diff --git a/Tests/SystemTests/FilePathTests/FilePathExtras.swift b/Tests/SystemTests/FilePathTests/FilePathExtras.swift new file mode 100644 index 00000000..66c4c93f --- /dev/null +++ b/Tests/SystemTests/FilePathTests/FilePathExtras.swift @@ -0,0 +1,101 @@ + +@testable import SystemPackage + +// Why can't I write this extension on `FilePath.ComponentView.SubSequence`? +extension Slice where Base == FilePath.ComponentView { + internal var _storageSlice: SystemString.SubSequence { + base._path._storage[self.startIndex._storage ..< self.endIndex._storage] + } +} + + +// Proposed API that didn't make the cut, but we stil want to keep our testing for +extension FilePath { + /// Returns `self` relative to `base`. + /// This does not cosult the file system or resolve symlinks. + /// + /// Returns `nil` if `self.root != base.root`. + /// + /// On Windows, if any component of either path could be interpreted as the root of + /// a traditional DOS path (e.g. a directory named `C:`), returns `nil`. + /// + /// Example: + /// + /// let path: FilePath = "/usr/local/bin" + /// path.lexicallyRelative(toBase: "/usr/local") == "bin" + /// path.lexicallyRelative(toBase: "/usr/local/bin/ls") == ".." + /// path.lexicallyRelative(toBase: "/tmp/foo.txt") == "../../usr/local/bin" + /// path.lexicallyRelative(toBase: "local/bin") == nil + internal func lexicallyRelative(toBase base: FilePath) -> FilePath? { + guard root == base.root else { return nil } + + // FIXME: On Windows, return nil if any component looks like a root + + let (tail, baseTail) = _dropCommonPrefix(components, base.components) + + var prefix = SystemString() + for _ in 0.. Bool { + guard !other.isEmpty else { return true } + guard !isEmpty else { return false } + + let (selfLex, otherLex) = (self.lexicallyNormal, other.lexicallyNormal) + if otherLex.isAbsolute { return selfLex.starts(with: otherLex) } + + // FIXME: Windows semantics with relative roots? + + // TODO: better than this naive algorithm + var slice = selfLex.components[...] + while !slice.isEmpty { + if slice.starts(with: otherLex.components) { return true } + slice = slice.dropFirst() + } + return false + } +} + +extension Collection where Element: Equatable, SubSequence == Slice { + // Mock up RangeSet functionality until it's real + func indices(where p: (Element) throws -> Bool) rethrows -> [Range] { + var result = Array>() + guard !isEmpty else { return result } + + var i = startIndex + while i != endIndex { + let next = index(after: i) + if try p(self[i]) { + result.append(i..]) { + guard !subranges.isEmpty else { return } + + var result = Self() + var idx = startIndex + for range in subranges { + result.append(contentsOf: self[idx.. ParsingTestCase { + ParsingTestCase( + isWindows: false, + pathStr: path, normalized: normalized, + file: file, line: line) + } + + static func windows( + _ path: String, normalized: String, + file: StaticString = #file, line: UInt = #line + ) -> ParsingTestCase { + ParsingTestCase( + isWindows: true, + pathStr: path, normalized: normalized, + file: file, line: line) + } +} + +extension ParsingTestCase { + func runAllTests() { + withWindowsPaths(enabled: isWindows) { + let path = FilePath(pathStr) + expectEqual(normalized, path.description) + } + } +} + +@testable import SystemPackage + +final class FilePathParsingTest: XCTestCase { + func testNormalization() { + let unixPaths: Array = [ + .unix("/", normalized: "/"), + .unix("", normalized: ""), + .unix("//", normalized: "/"), + .unix("///", normalized: "/"), + .unix("/foo/bar/", normalized: "/foo/bar"), + .unix("foo//bar", normalized: "foo/bar"), + .unix("//foo/bar//baz/", normalized: "/foo/bar/baz"), + .unix("/foo/bar/baz//", normalized: "/foo/bar/baz"), + .unix("/foo/bar/baz///", normalized: "/foo/bar/baz"), + ] + + let windowsPaths: Array = [ + .windows(#"C:\\folder\file\"#, normalized: #"C:\folder\file"#), + .windows(#"C:folder\\\file\\\"#, normalized: #"C:folder\file"#), + .windows(#"C:/foo//bar/"#, normalized: #"C:\foo\bar"#), + + .windows(#"\\server\share\"#, normalized: #"\\server\share\"#), + .windows(#"//server/share/"#, normalized: #"\\server\share\"#), + .windows(#"\\?\UNC/server\share\"#, normalized: #"\\?\UNC\server\share\"#), + + .windows(#"\\.\C:\"#, normalized: #"\\.\C:\"#), + .windows(#"C:\"#, normalized: #"C:\"#), + .windows(#"\"#, normalized: #"\"#), + ] + + for test in unixPaths { + test.runAllTests() + } + for test in windowsPaths { + test.runAllTests() + } + } +} diff --git a/Tests/SystemTests/FilePathTests/FilePathSyntaxTest.swift b/Tests/SystemTests/FilePathTests/FilePathSyntaxTest.swift new file mode 100644 index 00000000..146b720a --- /dev/null +++ b/Tests/SystemTests/FilePathTests/FilePathSyntaxTest.swift @@ -0,0 +1,1231 @@ +/* + This source file is part of the Swift System open source project + + Copyright (c) 2020 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 +*/ + +import XCTest +import SystemPackage + +private struct SyntaxTestCase: TestCase { + // Whether we want the path to be constructed and syntactically + // manipulated as though it were a Windows path + let isWindows: Bool + + // We defer forming the path until `runAllTests()` executes, + // so that we can switch between unix and windows behavior. + let pathStr: String + + let normalized: String + + let absolute: Bool + + let root: String? + let relative: String + + let dirname: String + let basename: String? + + let stem: String? + let `extension`: String? + + let components: [String] + + let lexicallyNormalized: String + + var file: StaticString + var line: UInt +} + +extension SyntaxTestCase { + // Convenience constructor which can substitute sensible defaults + private static func testCase( + isWindows: Bool, + _ path: String, + + // Nil `normalized` means use `path` + normalized: String?, + + // Nil means use the precense of a root + absolute: Bool?, + + // Nil `root` means no root. Nil `relative` means use `normalized` + root: String?, relative: String?, + + // Nil `dirname` requires nil `basename` and means use `normalized` + dirname: String?, basename: String?, + + // `nil` stem means use `basename` + stem: String?, extension: String?, + + components: [String], + + // Nil `lexicallyNormalized` means use `normalized` + lexicallyNormalized: String?, + + file: StaticString, line: UInt + ) -> SyntaxTestCase { + if dirname == nil { + assert(basename == nil ) + } + + let normalized = normalized ?? path + let lexicallyNormalized = lexicallyNormalized ?? normalized + let absolute = absolute ?? (root != nil) + let relative = relative ?? normalized + let dirname = dirname ?? (normalized) + let stem = stem ?? basename + return SyntaxTestCase( + isWindows: isWindows, + pathStr: path, + normalized: normalized, + absolute: absolute, + root: root, relative: relative, + dirname: dirname, basename: basename, + stem: stem, extension: `extension`, + components: components, + lexicallyNormalized: lexicallyNormalized, + file: file, line: line) + } + + // Conveience constructor for unix path test cases + static func unix( + _ path: String, + normalized: String? = nil, + root: String? = nil, relative: String? = nil, + dirname: String? = nil, basename: String? = nil, + stem: String? = nil, extension: String? = nil, + components: [String], + lexicallyNormalized: String? = nil, + file: StaticString = #file, line: UInt = #line + ) -> SyntaxTestCase { + .testCase( + isWindows: false, + path, + normalized: normalized, + absolute: nil, + root: root, relative: relative, + dirname: dirname, basename: basename, + stem: stem, extension: `extension`, + components: components, + lexicallyNormalized: lexicallyNormalized, + file: file, line: line) + } + + // Conveience constructor for unix path test cases + static func windows( + _ path: String, + normalized: String? = nil, + absolute: Bool, + root: String? = nil, relative: String? = nil, + dirname: String? = nil, basename: String? = nil, + stem: String? = nil, extension: String? = nil, + components: [String], + lexicallyNormalized: String? = nil, + file: StaticString = #file, line: UInt = #line + ) -> SyntaxTestCase { + .testCase( + isWindows: true, + path, + normalized: normalized, + absolute: absolute, + root: root, relative: relative, + dirname: dirname, basename: basename, + stem: stem, extension: `extension`, + components: components, + lexicallyNormalized: lexicallyNormalized, + file: file, line: line) + } +} + +extension SyntaxTestCase { + func testComponents(_ path: FilePath, expected: [String]) { + let expectedComponents = expected.map { FilePath.Component($0)! } + + expectEqualSequence(expectedComponents, Array(path.components), + "expected components") + expectEqualSequence(expectedComponents, Array(path.removingRoot().components), + "expected components") + + expectEqualSequence(expectedComponents, path.components) + expectEqual(expectedComponents.first, path.components.first) + expectEqual(expectedComponents.last, path.components.last) + + expectEqual(path, FilePath(root: path.root, expectedComponents), "init(root:components)") + expectEqual(path, FilePath(root: path.root, path.components), "init(root:components)") + expectEqual(path, FilePath( + root: path.root, path.components[...]), + "init(_ components: Slice)") + + let reversedPathComponents = path.components.reversed() + expectEqual(expectedComponents.count, reversedPathComponents.count) + expectEqualSequence(expectedComponents.reversed(), reversedPathComponents, "reversed") + + expectEqualSequence( + expectedComponents, reversedPathComponents.reversed(), "doubly reversed") + + expectEqualSequence(path.removingRoot().components, path.components, + "relativeComponents") + + let doublyReversed = FilePath( + root: nil, path.removingRoot().components.reversed() + ).components.reversed() + expectEqualSequence(path.removingRoot().components, doublyReversed, + "relative path doubly reversed") + + expectTrue(path.starts(with: path.removingLastComponent()), + "starts(with: dirname)") + expectEqual(path.removingLastComponent(), FilePath( + root: path.root, path.components.dropLast()), + "ComponentView.dirname") + + var prefixComps = expectedComponents + var prefixBasenamePath = path + var prefixPopLastPath = path + var compView = path.components[...] + var prefixDirname = path.removingLastComponent() + while !prefixComps.isEmpty { + expectTrue(path.starts(with: FilePath(root: path.root, prefixComps)), "startswith") + expectTrue(path.starts(with: prefixBasenamePath), "startswith") + expectTrue(path.starts(with: prefixPopLastPath), "startswith") + expectEqual(prefixBasenamePath, prefixPopLastPath, "popLast/basename") + expectEqual(prefixBasenamePath, FilePath(root: path.root, compView), + "popLast/basename") + prefixComps.removeLast() + prefixBasenamePath = prefixBasenamePath.removingLastComponent() + prefixPopLastPath.removeLastComponent() + compView = compView.dropLast() + + expectEqual(prefixBasenamePath, prefixDirname, "prefix dirname") + prefixDirname = prefixDirname.removingLastComponent() + } + var suffixComps = expectedComponents + compView = path.components[...] + while !suffixComps.isEmpty { + expectTrue(path.ends(with: FilePath(root: nil, suffixComps)), "endswith") + expectEqual(FilePath(root: nil, compView), FilePath(root: nil, suffixComps)) + suffixComps.removeFirst() + compView = compView.dropFirst() + } + + // FIXME: If we add operator `/` back, uncomment this + #if false + let slashPath = _path.components.reduce("", /) + let pushPath: FilePath = _path.components.reduce( + into: "", { $0.pushLast($1) }) + + expectEqual(_path, slashPath, "`/`") + expectEqual(_path, pushPath, "pushLast") + #endif + } + + + func runAllTests() { + // Assert we were set up correctly if non-nil + func assertNonEmpty(_ c: C?) { + assert(c == nil || !c!.isEmpty) + } + assertNonEmpty(root) + assertNonEmpty(basename) + assertNonEmpty(stem) + + withWindowsPaths(enabled: isWindows) { + let path = FilePath(pathStr) + + expectTrue((path == "") == path.isEmpty, "isEmpty") + + expectEqual(normalized, path.description, "normalized") + + var copy = path + copy.lexicallyNormalize() + expectEqual(copy == path, path.isLexicallyNormal, "isLexicallyNormal") + expectEqual(lexicallyNormalized, copy.description, "lexically normalized") + expectEqual(copy, path.lexicallyNormal, "lexicallyNormal") + + expectEqual(absolute, path.isAbsolute, "absolute") + expectEqual(!absolute, path.isRelative, "!absolute") + + expectEqual(root, path.root?.description, "root") + expectEqual(relative, path.removingRoot().description, "relative") + + if path.isRelative { + if path.root == nil { + expectEqual(path, path.removingRoot(), "relative idempotent") + } else { + expectTrue(isWindows) + expectFalse(path == path.removingRoot()) + var relPathCopy = path.removingRoot() + relPathCopy.root = path.root + expectEqual(path, relPathCopy) + + // TODO: Windows root analysis tests + } + } else { + var pathCopy = path + pathCopy.root = nil + expectEqual(pathCopy, path.removingRoot(), "set nil root") + expectEqual(relative, path.removingRoot().description, "set root to nil") + pathCopy.root = path.root + expectTrue(pathCopy.isAbsolute) + expectEqual(path, pathCopy) + expectTrue(path.root != nil) + } + + if let root = path.root { + var pathCopy = path + pathCopy.components.removeAll() + expectEqual(FilePath(root: root), pathCopy, "set empty relative") + } else { + var pathCopy = path + pathCopy.components.removeAll() + expectTrue(pathCopy.isEmpty, "set empty relative") + } + + expectEqual(dirname, path.removingLastComponent().description, "dirname") + expectEqual(basename, path.lastComponent?.description, "basename") + + do { + var path = path + var pathCopy = path + while !path.removingRoot().isEmpty { + pathCopy = pathCopy.removingLastComponent() + path.removeLastComponent() + expectEqual(path, pathCopy) + } + } + + expectEqual(stem, path.stem, "stem") + expectEqual(`extension`, path.extension, "extension") + + if let base = path.lastComponent { + expectEqual(path.stem, base.stem) + expectEqual(path.extension, base.extension) + } + + var pathCopy = path + while pathCopy.extension != nil { + var name = pathCopy.lastComponent!.description + name.removeSubrange(name.lastIndex(of: ".")!...) + pathCopy.extension = nil + expectEqual(name, pathCopy.lastComponent!.description, "set nil extension (2)") + } + expectTrue(pathCopy.extension == nil, "set nil extension") + + pathCopy = path + pathCopy.removeAll(keepingCapacity: true) + expectTrue(pathCopy.isEmpty) + + testComponents(path, expected: self.components) + } + } +} + +private struct WindowsRootTestCase: TestCase { + // We defer forming the path until `runAllTests()` executes, + // so that we can switch between unix and windows behavior. + let rootStr: String + + let expected: String + + let absolute: Bool + + var file: StaticString + var line: UInt +} + +extension WindowsRootTestCase { + func runAllTests() { + withWindowsPaths(enabled: true) { + let path = FilePath(rootStr) + expectEqual(expected, path.string) + expectNotNil(path.root) + expectEqual(path, FilePath(root: path.root ?? "")) + expectTrue(path.components.isEmpty) + expectTrue(path.removingRoot().isEmpty) + expectTrue(path.isLexicallyNormal) + } + } +} + +final class FilePathSyntaxTest: XCTestCase { + func testPathSyntax() { + let unixPaths: Array = [ + .unix("", components: []), + + .unix( + "/", + root: "/", relative: "", + components: [] + ), + + .unix( + "/..", + root: "/", relative: "..", + dirname: "/", basename: "..", + components: [".."], + lexicallyNormalized: "/" + ), + + .unix( + "/.", + root: "/", relative: ".", + dirname: "/", basename: ".", + components: ["."], + lexicallyNormalized: "/" + ), + + .unix( + "/../.", + root: "/", relative: "../.", + dirname: "/..", basename: ".", + components: ["..", "."], + lexicallyNormalized: "/" + ), + + .unix( + ".", + dirname: "", basename: ".", + components: ["."], + lexicallyNormalized: "" + ), + + .unix( + "..", + dirname: "", basename: "..", + components: [".."], + lexicallyNormalized: ".." + ), + + .unix( + "./..", + dirname: ".", basename: "..", + components: [".", ".."], + lexicallyNormalized: ".." + ), + + .unix( + "../.", + dirname: "..", basename: ".", + components: ["..", "."], + lexicallyNormalized: ".." + ), + + .unix( + "../..", + dirname: "..", basename: "..", + components: ["..", ".."], + lexicallyNormalized: "../.." + ), + + .unix( + "a/../..", + dirname: "a/..", basename: "..", + components: ["a", "..", ".."], + lexicallyNormalized: ".." + ), + + .unix( + "a/.././.././../b", + dirname: "a/.././.././..", basename: "b", + components: ["a", "..", ".", "..", ".", "..", "b"], + lexicallyNormalized: "../../b" + ), + + .unix( + "/a/.././.././../b", + root: "/", relative: "a/.././.././../b", + dirname: "/a/.././.././..", basename: "b", + components: ["a", "..", ".", "..", ".", "..", "b"], + lexicallyNormalized: "/b" + ), + + .unix( + "./.", + dirname: ".", basename: ".", + components: [".", "."], + lexicallyNormalized: "" + ), + + .unix( + "foo.txt", + dirname: "", basename: "foo.txt", + stem: "foo", extension: "txt", + components: ["foo.txt"] + ), + + .unix( + "a/foo/bar/../..", + dirname: "a/foo/bar/..", basename: "..", + components: ["a", "foo", "bar", "..", ".."], + lexicallyNormalized: "a" + ), + + .unix( + "a/./foo/bar/.././../.", + dirname: "a/./foo/bar/.././..", basename: ".", + components: ["a", ".", "foo", "bar", "..", ".", "..", "."], + lexicallyNormalized: "a" + ), + + .unix( + "a/../b", + dirname: "a/..", basename: "b", + components: ["a", "..", "b"], + lexicallyNormalized: "b" + ), + + .unix( + "/a/../b/../c/../../d", + root: "/", relative: "a/../b/../c/../../d", + dirname: "/a/../b/../c/../..", basename: "d", + components: ["a", "..", "b", "..", "c", "..", "..", "d"], + lexicallyNormalized: "/d" + ), + + .unix( + "/usr/bin/ls", + root: "/", relative: "usr/bin/ls", + dirname: "/usr/bin", basename: "ls", + components: ["usr", "bin", "ls"] + ), + + .unix( + "bin/ls", + dirname: "bin", basename: "ls", + components: ["bin", "ls"] + ), + + .unix( + "~/bar.app", + dirname: "~", basename: "bar.app", + stem: "bar", extension: "app", + components: ["~", "bar.app"] + ), + + .unix( + "~/bar.app.bak/", + normalized: "~/bar.app.bak", + dirname: "~", basename: "bar.app.bak", + stem: "bar.app", extension: "bak", + components: ["~", "bar.app.bak"] + ), + + .unix( + "/tmp/.", + root: "/", relative: "tmp/.", + dirname: "/tmp", basename: ".", + components: ["tmp", "."], + lexicallyNormalized: "/tmp" + ), + + .unix( + "/tmp/..", + root: "/", relative: "tmp/..", + dirname: "/tmp", basename: "..", + components: ["tmp", ".."], + lexicallyNormalized: "/" + ), + + .unix( + "/tmp/../", + normalized: "/tmp/..", + root: "/", relative: "tmp/..", + dirname: "/tmp", basename: "..", + components: ["tmp", ".."], + lexicallyNormalized: "/" + ), + + .unix( + "/tmp/./a/../b", + root: "/", relative: "tmp/./a/../b", + dirname: "/tmp/./a/..", basename: "b", + components: ["tmp", ".", "a", "..", "b"], + lexicallyNormalized: "/tmp/b" + ), + + .unix( + "/tmp/.hidden", + root: "/", relative: "tmp/.hidden", + dirname: "/tmp", basename: ".hidden", + components: ["tmp", ".hidden"] + ), + + .unix( + "/tmp/.hidden.", + root: "/", relative: "tmp/.hidden.", + dirname: "/tmp", basename: ".hidden.", + stem: ".hidden", extension: "", + components: ["tmp", ".hidden."] + ), + + .unix( + "/tmp/.hidden.o", + root: "/", relative: "tmp/.hidden.o", + dirname: "/tmp", basename: ".hidden.o", + stem: ".hidden", extension: "o", + components: ["tmp", ".hidden.o"] + ), + + .unix( + "/tmp/.hidden.o.", + root: "/", relative: "tmp/.hidden.o.", + dirname: "/tmp", basename: ".hidden.o.", + stem: ".hidden.o", extension: "", + components: ["tmp", ".hidden.o."] + ), + + // Backslash is not a separator, nor a root + .unix( + #"\bin\.\ls"#, + dirname: "", basename: #"\bin\.\ls"#, + stem: #"\bin\"#, extension: #"\ls"#, + components: [#"\bin\.\ls"#] + ), + ] + + let windowsPaths: Array = [ + .windows(#""#, absolute: false, components: []), + + .windows( + #"C"#, + absolute: false, + dirname: "", basename: "C", + components: ["C"] + ), + + .windows( + #"C:"#, + absolute: false, + root: #"C:"#, relative: #""#, + components: [] + ), + + .windows( + #"C:\"#, + absolute: true, + root: #"C:\"#, relative: #""#, + components: [] + ), + + .windows( + #"C:\foo\bar.exe"#, + absolute: true, + root: #"C:\"#, relative: #"foo\bar.exe"#, + dirname: #"C:\foo"#, basename: "bar.exe", + stem: "bar", extension: "exe", + components: ["foo", "bar.exe"] + ), + + .windows( + #"C:foo\bar"#, + absolute: false, + root: #"C:"#, relative: #"foo\bar"#, + dirname: #"C:foo"#, basename: "bar", + components: ["foo", "bar"] + ), + + .windows( + #"C:foo\bar\..\.."#, + absolute: false, + root: #"C:"#, relative: #"foo\bar\..\.."#, + dirname: #"C:foo\bar\.."#, basename: "..", + components: ["foo", "bar", "..", ".."], + lexicallyNormalized: "C:" + ), + + .windows( + #"C:foo\bar\..\..\.."#, + absolute: false, + root: #"C:"#, relative: #"foo\bar\..\..\.."#, + dirname: #"C:foo\bar\..\.."#, basename: "..", + components: ["foo", "bar", "..", "..", ".."], + lexicallyNormalized: "C:" + ), + + .windows( + #"\foo\bar.exe"#, + absolute: false, + root: #"\"#, relative: #"foo\bar.exe"#, + dirname: #"\foo"#, basename: "bar.exe", + stem: "bar", extension: "exe", + components: ["foo", "bar.exe"] + ), + + .windows( + #"foo\bar.exe"#, + absolute: false, + dirname: #"foo"#, basename: "bar.exe", + stem: "bar", extension: "exe", + components: ["foo", "bar.exe"] + ), + + .windows( + #"\\?\device\"#, + absolute: true, + root: #"\\?\device\"#, relative: "", + components: [] + ), + + .windows( + #"\\?\device\folder\file.exe"#, + absolute: true, + root: #"\\?\device\"#, relative: #"folder\file.exe"#, + dirname: #"\\?\device\folder"#, basename: "file.exe", + stem: "file", extension: "exe", + components: ["folder", "file.exe"] + ), + + .windows( + #"\\?\UNC\server\share\"#, + absolute: true, + root: #"\\?\UNC\server\share\"#, relative: #""#, + components: [] + ), + + .windows( + #"\\?\UNC\server\share\folder\file.txt"#, + absolute: true, + root: #"\\?\UNC\server\share\"#, relative: #"folder\file.txt"#, + dirname: #"\\?\UNC\server\share\folder"#, basename: "file.txt", + stem: "file", extension: "txt", + components: ["folder", "file.txt"] + ), + + .windows( + #"\\server\share\"#, + absolute: true, + root: #"\\server\share\"#, relative: "", + components: [] + ), + + .windows( + #"\\server\share\folder\file.txt"#, + absolute: true, + root: #"\\server\share\"#, relative: #"folder\file.txt"#, + dirname: #"\\server\share\folder"#, basename: "file.txt", + stem: "file", extension: "txt", + components: ["folder", "file.txt"] + ), + + .windows( + #"\\server\share\folder\file.txt\.."#, + absolute: true, + root: #"\\server\share\"#, relative: #"folder\file.txt\.."#, + dirname: #"\\server\share\folder\file.txt"#, basename: "..", + components: ["folder", "file.txt", ".."], + lexicallyNormalized: #"\\server\share\folder"# + ), + + .windows( + #"\\server\share\folder\file.txt\..\.."#, + absolute: true, + root: #"\\server\share\"#, relative: #"folder\file.txt\..\.."#, + dirname: #"\\server\share\folder\file.txt\.."#, basename: "..", + components: ["folder", "file.txt", "..", ".."], + lexicallyNormalized: #"\\server\share\"# + ), + + .windows( + #"\\server\share\folder\file.txt\..\..\..\.."#, + absolute: true, + root: #"\\server\share\"#, relative: #"folder\file.txt\..\..\..\.."#, + dirname: #"\\server\share\folder\file.txt\..\..\.."#, basename: "..", + components: ["folder", "file.txt", "..", "..", "..", ".."], + lexicallyNormalized: #"\\server\share\"# + ), + + // Actually a rooted relative path + .windows( + #"\server\share\folder\file.txt\..\..\.."#, + absolute: false, + root: #"\"#, relative: #"server\share\folder\file.txt\..\..\.."#, + dirname: #"\server\share\folder\file.txt\..\.."#, basename: "..", + components: ["server", "share", "folder", "file.txt", "..", "..", ".."], + lexicallyNormalized: #"\server"# + ), + + .windows( + #"\\?\Volume{12345678-abcd-1111-2222-123445789abc}\folder\file"#, + absolute: true, + root: #"\\?\Volume{12345678-abcd-1111-2222-123445789abc}\"#, + relative: #"folder\file"#, + dirname: #"\\?\Volume{12345678-abcd-1111-2222-123445789abc}\folder"#, + basename: "file", + components: ["folder", "file"] + ) + + // TODO: partially-formed Windows roots, we should fully form them... + ] + + for test in unixPaths { + test.runAllTests() + } + for test in windowsPaths { + test.runAllTests() + } + } + + + func testPrefixSuffix() { + let startswith: Array<(String, String)> = [ + ("/usr/bin/ls", "/"), + ("/usr/bin/ls", "/usr"), + ("/usr/bin/ls", "/usr/bin"), + ("/usr/bin/ls", "/usr/bin/ls"), + ("/usr/bin/ls", "/usr/bin/ls//"), + ("/usr/bin/ls", ""), + ] + + let noStartswith: Array<(String, String)> = [ + ("/usr/bin/ls", "/u"), + ("/usr/bin/ls", "/us"), + ("/usr/bin/ls", "/usr/bi"), + ("/usr/bin/ls", "usr/bin/ls"), + ("/usr/bin/ls", "usr/"), + ("/usr/bin/ls", "ls"), + ] + + for (path, pre) in startswith { + XCTAssert(FilePath(path).starts(with: FilePath(pre))) + } + for (path, pre) in noStartswith { + XCTAssertFalse(FilePath(path).starts(with: FilePath(pre))) + } + + let endswith: Array<(String, String)> = [ + ("/usr/bin/ls", "ls"), + ("/usr/bin/ls", "bin/ls"), + ("/usr/bin/ls", "usr/bin/ls"), + ("/usr/bin/ls", "/usr/bin/ls"), + ("/usr/bin/ls", "/usr/bin/ls///"), + ("/usr/bin/ls", ""), + ] + + let noEndswith: Array<(String, String)> = [ + ("/usr/bin/ls", "/ls"), + ("/usr/bin/ls", "/bin/ls"), + ("/usr/bin/ls", "/usr/bin"), + ("/usr/bin/ls", "foo"), + ] + + for (path, suf) in endswith { + XCTAssert(FilePath(path).ends(with: FilePath(suf))) + } + for (path, suf) in noEndswith { + XCTAssertFalse(FilePath(path).ends(with: FilePath(suf))) + } + } + + func testLexicallyRelative() { + let path: FilePath = "/usr/local/bin" + XCTAssert(path.lexicallyRelative(toBase: "/usr/local") == "bin") + XCTAssert(path.lexicallyRelative(toBase: "/usr/local/bin/ls") == "..") + XCTAssert(path.lexicallyRelative(toBase: "/tmp/foo.txt") == "../../usr/local/bin") + XCTAssert(path.lexicallyRelative(toBase: "local/bin") == nil) + + let rel = FilePath(root: nil, path.components) + XCTAssert(rel.lexicallyRelative(toBase: "/usr/local") == nil) + XCTAssert(rel.lexicallyRelative(toBase: "usr/local") == "bin") + XCTAssert(rel.lexicallyRelative(toBase: "usr/local/bin/ls") == "..") + XCTAssert(rel.lexicallyRelative(toBase: "tmp/foo.txt") == "../../usr/local/bin") + XCTAssert(rel.lexicallyRelative(toBase: "local/bin") == "../../usr/local/bin") + + // TODO: Test Windows path with root pushed + } + + func testAdHocMutations() { + var path: FilePath = "/usr/local/bin" + + func expect( + _ s: String, + _ file: StaticString = #file, + _ line: UInt = #line + ) { + if path == FilePath(s) { return } + + defer { print("expected: \(s), actual: \(path)") } + XCTAssert(false, file: file, line: line) + } + + // Run `body`, restoring `path` afterwards + func restoreAfter( + body: () -> () + ) { + let copy = path + defer { path = copy } + body() + } + + restoreAfter { + path.root = nil + expect("usr/local/bin") + path.components = FilePath("ls").components + expect("ls") + } + + restoreAfter { + path.components = FilePath("/bin/ls").components + expect("/bin/ls") + path.components.removeAll() + expect("/") + } + + restoreAfter { + path = path.removingLastComponent().appending("lib") + expect("/usr/local/lib") + path = path.removingLastComponent() + expect("/usr/local") + path = path.removingLastComponent().appending("bin") + expect("/usr/bin") + } + + restoreAfter { + path = FilePath("~").appending(path.lastComponent!) + expect("~/bin") + path = FilePath("").appending(path.lastComponent!) + expect("bin") + path = FilePath("").appending(path.lastComponent!) + expect("bin") + path = FilePath("/usr/local").appending(path.lastComponent!) + expect("/usr/local/bin") + } + + restoreAfter { + path.removeLastComponent() + expect("/usr/local") + path.removeLastComponent() + expect("/usr") + path.removeLastComponent() + expect("/") + path.removeLastComponent() + expect("/") + + path.removeAll() + expect("") + + path.append("tmp") + expect("tmp") + path.append("cat") + expect("tmp/cat") + path.push("/") + expect("/") + + path.append(".") + expect("/.") + XCTAssert(path.components.last!.kind == .currentDirectory) + XCTAssert(path.components.last!.isSpecialDirectory) + path.lexicallyNormalize() + expect("/") + + path.append("..") + expect("/..") + XCTAssert(path.components.last!.kind == .parentDirectory) + XCTAssert(path.components.last!.isSpecialDirectory) + path.lexicallyNormalize() + expect("/") + + path.append("foo") + path.append("..") + expect("/foo/..") + path.lexicallyNormalize() + expect("/") + } + + restoreAfter { + path.append("ls") + expect("/usr/local/bin/ls") + path.extension = "exe" + expect("/usr/local/bin/ls.exe") + path.extension = "txt" + expect("/usr/local/bin/ls.txt") + + path.extension = nil + expect("/usr/local/bin/ls") + + path.extension = "" + expect("/usr/local/bin/ls.") + XCTAssert(path.extension == "") + path.extension = "txt" + expect("/usr/local/bin/ls.txt") + } + + restoreAfter { + path.append("..") + expect("/usr/local/bin/..") + XCTAssert(path.components.last!.kind == .parentDirectory) + path.extension = "txt" + expect("/usr/local/bin/..") + XCTAssert(path.components.last!.kind == .parentDirectory) + path.removeAll() + expect("") + path.extension = "txt" + expect("") + path.append("/") + expect("/") + path.extension = "txt" + expect("/") + } + + restoreAfter { + XCTAssert(!path.removePrefix("/usr/bin")) + expect("/usr/local/bin") + + XCTAssert(!path.removePrefix("/us")) + expect("/usr/local/bin") + + XCTAssert(path.removePrefix("/usr/local")) + expect("bin") + + XCTAssert(path.removePrefix("bin")) + expect("") + } + + restoreAfter { + path.append("utils/widget/") + expect("/usr/local/bin/utils/widget") + path.append("/bin///ls") + expect("/usr/local/bin/utils/widget/bin/ls") + path.push("/bin/ls") + expect("/bin/ls") + path.append("/") + expect("/bin/ls") + path.push("/") + expect("/") + + path.append("tmp") + expect("/tmp") + path.append("foo/bar") + expect("/tmp/foo/bar") + XCTAssert(!path.isEmpty) + + path.append(FilePath.Component("baz")) + expect("/tmp/foo/bar/baz") + path.append("/") + expect("/tmp/foo/bar/baz") + path.removeAll() + expect("") + XCTAssert(path.isEmpty) + path.append("") + expect("") + + path.append("/bar/baz") + expect("/bar/baz") + path.removeAll() + expect("") + path.append(FilePath.Component("usr")) + expect("usr") + path.push("/bin/ls") + expect("/bin/ls") + path.removeAll() + expect("") + + path.append("bar/baz") + expect("bar/baz") + + path.append(["a", "b", "c"]) + expect("bar/baz/a/b/c") + + path.removeAll() + expect("") + path.append(["a", "b", "c"]) + expect("a/b/c") + } + + restoreAfter { + expect("/usr/local/bin") + path.push("bar/baz") + expect("/usr/local/bin/bar/baz") + path.push("/") + expect("/") + path.push("tmp") + expect("/tmp") + path.push("/dev/null") + expect("/dev/null") + } + + restoreAfter { + let same = path.string + path.reserveCapacity(0) + expect(same) + path.reserveCapacity(1000) + expect(same) + } + + restoreAfter { + XCTAssert(path.lexicallyContains("usr")) + XCTAssert(path.lexicallyContains("/usr")) + XCTAssert(path.lexicallyContains("local/bin")) + XCTAssert(!path.lexicallyContains("/local/bin")) + path.append("..") + XCTAssert(!path.lexicallyContains("local/bin")) + XCTAssert(path.lexicallyContains("local/bin/..")) + expect("/usr/local/bin/..") + XCTAssert(path.lexicallyContains("usr/local")) + XCTAssert(path.lexicallyContains("usr/local/.")) + } + + restoreAfter { + XCTAssert(path.lexicallyResolving("ls") == "/usr/local/bin/ls") + XCTAssert(path.lexicallyResolving("/ls") == "/usr/local/bin/ls") + XCTAssert(path.lexicallyResolving("../bin/ls") == nil) + XCTAssert(path.lexicallyResolving("/../bin/ls") == nil) + + XCTAssert(path.lexicallyResolving("/../bin/../lib/target") == nil) + XCTAssert(path.lexicallyResolving("./ls/../../lib/target") == nil) + + let staticContent: FilePath = "/var/www/my-website/static" + let links: [FilePath] = + ["index.html", "/assets/main.css", "../../../../etc/passwd"] + let paths = links.map { staticContent.lexicallyResolving($0) } + XCTAssert(paths == [ + "/var/www/my-website/static/index.html", + "/var/www/my-website/static/assets/main.css", + nil]) + + } + + restoreAfter { + path = "/tmp" + let sub: FilePath = "foo/./bar/../baz/." + for comp in sub.components.filter({ $0.kind != .currentDirectory }) { + path.append(comp) + } + expect("/tmp/foo/bar/../baz") + } + + restoreAfter { + path = "/usr/bin" + let binIdx = path.components.firstIndex(of: "bin")! + path.components.insert("local", at: binIdx) + expect("/usr/local/bin") + } + + restoreAfter { + path = "/./home/./username/scripts/./tree" + let scriptIdx = path.components.lastIndex(of: "scripts")! + path.components.insert("bin", at: scriptIdx) + expect("/./home/./username/bin/scripts/./tree") + + path.components.removeAll { $0.kind == .currentDirectory } + expect("/home/username/bin/scripts/tree") + } + + restoreAfter { + path = "/usr/bin" + XCTAssert(path.removeLastComponent()) + expect("/usr") + XCTAssert(path.removeLastComponent()) + expect("/") + XCTAssertFalse(path.removeLastComponent()) + expect("/") + } + + restoreAfter { + path = "" + path.append("/var/www/website") + expect("/var/www/website") + path.append("static/assets") + expect("/var/www/website/static/assets") + path.append("/main.css") + expect("/var/www/website/static/assets/main.css") + + } + } + + func testFailableStringInitializers() { + let invalidComps: Array = [ + "", "/", "a/b", + ] + let invalidRoots: Array = [ + "", "a", "a/b", + ] + for c in invalidComps { + XCTAssertNil(FilePath.Component(c)) + } + for c in invalidRoots { + XCTAssertNil(FilePath.Root(c)) + } + + // Due to SE-0213, this is how you call he failable init explicitly, + // otherwise it will be considered a literal `as` cast. + XCTAssertNil(FilePath.Component.init("/")) + } + + func testPartialWindowsRoots() { + func partial( + _ str: String, + _ full: String, + absolute: Bool = true, + file: StaticString = #file, line: UInt = #line + ) -> WindowsRootTestCase { + WindowsRootTestCase( + rootStr: str, expected: full, absolute: absolute, + file: file, line: line) + } + func full( + _ str: String, absolute: Bool = true, + file: StaticString = #file, line: UInt = #line + ) -> WindowsRootTestCase { + partial(str, str, absolute: absolute, + file: file, line: line) + } + + // TODO: Some of these are kinda funky (like `\\` -> `\\\\`), but + // I'm not aware of a sane fixup behavior here, so we go + // with a lesser of insanes. + let partialRootTestCases: [WindowsRootTestCase] = [ + // Full roots + full(#"\"#, absolute: false), + full(#"C:"#, absolute: false), + full(#"C:\"#), + + // Full UNCs (with omitted fields) + full(#"\\server\share\"#), + full(#"\\server\\"#), + full(#"\\\share\"#), + full(#"\\\\"#), + + // Full device UNCs (with omitted fields) + full(#"\\.\UNC\server\share\"#), + full(#"\\.\UNC\server\\"#), + full(#"\\.\UNC\\share\"#), + full(#"\\.\UNC\\\"#), + + // Full device (with omitted fields) + full(#"\\.\volume\"#), + full(#"\\.\\"#), + + // Partial UNCs + partial(#"\\server\share"#, #"\\server\share\"#), + partial(#"\\server\"#, #"\\server\\"#), + partial(#"\\server"#, #"\\server\\"#), + partial(#"\\\\"#, #"\\\\"#), + partial(#"\\\"#, #"\\\\"#), + partial(#"\\"#, #"\\\\"#), + + // Partial device UNCs + partial(#"\\.\UNC\server\share"#, #"\\.\UNC\server\share\"#), + partial(#"\\.\UNC\server\"#, #"\\.\UNC\server\\"#), + partial(#"\\.\UNC\server"#, #"\\.\UNC\server\\"#), + partial(#"\\.\UNC\\\"#, #"\\.\UNC\\\"#), + partial(#"\\.\UNC\\"#, #"\\.\UNC\\\"#), + partial(#"\\.\UNC\"#, #"\\.\UNC\\\"#), + partial(#"\\.\UNC"#, #"\\.\UNC\\\"#), + + // Partial device + partial(#"\\.\volume"#, #"\\.\volume\"#), + partial(#"\\.\"#, #"\\.\\"#), + partial(#"\\."#, #"\\.\\"#), + ] + + for partialRootTest in partialRootTestCases { + partialRootTest.runAllTests() + } + + + } + +} diff --git a/Tests/SystemTests/SystemStringTests.swift b/Tests/SystemTests/SystemStringTests.swift index 96cfa6e6..1c893500 100644 --- a/Tests/SystemTests/SystemStringTests.swift +++ b/Tests/SystemTests/SystemStringTests.swift @@ -152,6 +152,33 @@ struct StringTest: TestCase { } } + let isComponent = string == fpRaw.components.first?.string + + if hasNormalSeparators && isComponent { + // Test FilePath.Component + let compStr = FilePath.Component(string)! + expectEqualSequence( + string.unicodeScalars, compStr.string.unicodeScalars, "Component from string") + expectEqual(string, String(decoding: compStr), "Component from string") + expectEqual(string, String(validating: compStr), "Component from string") + expectEqual(sysStr, compStr._slice.base, "Component from string") + + let compRaw = FilePath.Component(sysRaw)! + expectEqual(string, String(decoding: compRaw), "raw Component") + expectEqual(isValid, nil != String(validating: compRaw), "raw Component") + expectEqual(sysRaw, compRaw._slice.base, "raw Component") + expectEqual(isValid, compStr == compRaw, "raw Component") + + // TODO: Below works after we add last component optimization + // compRaw.withPlatformString { fp0 in + // compRaw.slice.base.withPlatformString { storage0 in + // expectEqual(fp0, storage0, + // "Component withPlatformString address forwarding") + // } + // } + + } + sysRaw.withPlatformString { let len = system_platform_strlen($0) expectEqual(raw.count, 1+len, "SystemString.withPlatformString") @@ -159,6 +186,10 @@ struct StringTest: TestCase { "SystemString.withPlatformString") if hasNormalSeparators { expectEqual(sysRaw, FilePath(platformString: $0)._storage) + if isComponent { + expectEqual( + sysRaw, FilePath.Component(platformString: $0)!._slice.base) + } } } diff --git a/Tests/SystemTests/XCTestManifests.swift b/Tests/SystemTests/XCTestManifests.swift index c40f26c1..abbfe070 100644 --- a/Tests/SystemTests/XCTestManifests.swift +++ b/Tests/SystemTests/XCTestManifests.swift @@ -31,6 +31,41 @@ extension FileOperationsTest { ] } +extension FilePathComponentsTest { + // DO NOT MODIFY: This is autogenerated, use: + // `swift test --generate-linuxmain` + // to regenerate. + static let __allTests__FilePathComponentsTest = [ + ("testAdHocRRC", testAdHocRRC), + ("testCases", testCases), + ("testConcatenation", testConcatenation), + ("testSeparatorNormalization", testSeparatorNormalization), + ] +} + +extension FilePathParsingTest { + // DO NOT MODIFY: This is autogenerated, use: + // `swift test --generate-linuxmain` + // to regenerate. + static let __allTests__FilePathParsingTest = [ + ("testNormalization", testNormalization), + ] +} + +extension FilePathSyntaxTest { + // DO NOT MODIFY: This is autogenerated, use: + // `swift test --generate-linuxmain` + // to regenerate. + static let __allTests__FilePathSyntaxTest = [ + ("testAdHocMutations", testAdHocMutations), + ("testFailableStringInitializers", testFailableStringInitializers), + ("testLexicallyRelative", testLexicallyRelative), + ("testPartialWindowsRoots", testPartialWindowsRoots), + ("testPathSyntax", testPathSyntax), + ("testPrefixSuffix", testPrefixSuffix), + ] +} + extension FilePathTest { // DO NOT MODIFY: This is autogenerated, use: // `swift test --generate-linuxmain` @@ -58,14 +93,28 @@ extension MockingTest { ] } +extension SystemStringTest { + // DO NOT MODIFY: This is autogenerated, use: + // `swift test --generate-linuxmain` + // to regenerate. + static let __allTests__SystemStringTest = [ + ("testAdHoc", testAdHoc), + ("testPlatformString", testPlatformString), + ] +} + public func __allTests() -> [XCTestCaseEntry] { return [ testCase(ErrnoTest.__allTests__ErrnoTest), testCase(FileDescriptorTest.__allTests__FileDescriptorTest), testCase(FileOperationsTest.__allTests__FileOperationsTest), + testCase(FilePathComponentsTest.__allTests__FilePathComponentsTest), + testCase(FilePathParsingTest.__allTests__FilePathParsingTest), + testCase(FilePathSyntaxTest.__allTests__FilePathSyntaxTest), testCase(FilePathTest.__allTests__FilePathTest), testCase(FilePermissionsTest.__allTests__FilePermissionsTest), testCase(MockingTest.__allTests__MockingTest), + testCase(SystemStringTest.__allTests__SystemStringTest), ] } #endif From 05e3d04b0cc1ff37b444fdfc223baa3cc83baefb Mon Sep 17 00:00:00 2001 From: Michael Ilseman Date: Tue, 2 Feb 2021 07:49:42 -0700 Subject: [PATCH 006/427] Drop Component.isSpecialDirectory This API is fully redundant with .kind == .regular and could be a source of confusion and namespace pollution. Drop it for now, and we can add something equivalent in the future when we have more component-analysis APIs. --- Sources/System/FilePath/FilePathComponents.swift | 5 +---- Sources/System/FilePath/FilePathSyntax.swift | 4 ++-- Tests/SystemTests/FilePathTests/FilePathSyntaxTest.swift | 2 -- 3 files changed, 3 insertions(+), 8 deletions(-) diff --git a/Sources/System/FilePath/FilePathComponents.swift b/Sources/System/FilePath/FilePathComponents.swift index 22fbe10c..c7aed661 100644 --- a/Sources/System/FilePath/FilePathComponents.swift +++ b/Sources/System/FilePath/FilePathComponents.swift @@ -94,9 +94,6 @@ extension FilePath.Component { if _path._isParentDirectory(_range) { return .parentDirectory } return .regular } - - /// Whether this component is either special directory `.` or `..`. - public var isSpecialDirectory: Bool { _path._isSpecialDirectory(_range) } } extension FilePath.Root { @@ -207,7 +204,7 @@ extension FilePath: _PlatformStringable { extension FilePath.Component { // The index of the `.` denoting an extension internal func _extensionIndex() -> SystemString.Index? { - guard !isSpecialDirectory, + guard kind == .regular, let idx = _slice.lastIndex(of: .dot), idx != _slice.startIndex else { return nil } diff --git a/Sources/System/FilePath/FilePathSyntax.swift b/Sources/System/FilePath/FilePathSyntax.swift index 070249f1..daae8c04 100644 --- a/Sources/System/FilePath/FilePathSyntax.swift +++ b/Sources/System/FilePath/FilePathSyntax.swift @@ -325,7 +325,7 @@ extension FilePath { get { lastComponent?.extension } set { defer { _invariantCheck() } - guard let base = lastComponent, !base.isSpecialDirectory else { return } + guard let base = lastComponent, base.kind == .regular else { return } let suffix: SystemString if let ext = newValue { @@ -373,7 +373,7 @@ extension FilePath { // `\..\foo\bar` should not. components.drop( while: { root == nil && $0.kind == .parentDirectory } - ).allSatisfy { !$0.isSpecialDirectory } + ).allSatisfy { $0.kind == .regular } } /// Collapse `.` and `..` components lexically (i.e. without following diff --git a/Tests/SystemTests/FilePathTests/FilePathSyntaxTest.swift b/Tests/SystemTests/FilePathTests/FilePathSyntaxTest.swift index 146b720a..4aed9002 100644 --- a/Tests/SystemTests/FilePathTests/FilePathSyntaxTest.swift +++ b/Tests/SystemTests/FilePathTests/FilePathSyntaxTest.swift @@ -916,14 +916,12 @@ final class FilePathSyntaxTest: XCTestCase { path.append(".") expect("/.") XCTAssert(path.components.last!.kind == .currentDirectory) - XCTAssert(path.components.last!.isSpecialDirectory) path.lexicallyNormalize() expect("/") path.append("..") expect("/..") XCTAssert(path.components.last!.kind == .parentDirectory) - XCTAssert(path.components.last!.isSpecialDirectory) path.lexicallyNormalize() expect("/") From d24083e1be145148258a5468331bbc0197383c50 Mon Sep 17 00:00:00 2001 From: Michael Ilseman Date: Wed, 3 Feb 2021 14:30:56 -0700 Subject: [PATCH 007/427] Integrate API review Fix bug in _modify and add better testing. Change lexicallyNormal to lexicallyNormalized() to match "ed" rule. Fix up some comments and remove some dead code Co-authored-by: Kyle Macomber --- .../FilePath/FilePathComponentView.swift | 11 +- .../System/FilePath/FilePathComponents.swift | 2 - Sources/System/FilePath/FilePathParsing.swift | 24 +- Sources/System/FilePath/FilePathSyntax.swift | 26 +-- .../FilePathComponentsTest.swift | 212 +++++------------- .../FilePathTests/FilePathExtras.swift | 3 +- .../FilePathTests/FilePathSyntaxTest.swift | 2 +- 7 files changed, 70 insertions(+), 210 deletions(-) diff --git a/Sources/System/FilePath/FilePathComponentView.swift b/Sources/System/FilePath/FilePathComponentView.swift index d281708d..a79c3abd 100644 --- a/Sources/System/FilePath/FilePathComponentView.swift +++ b/Sources/System/FilePath/FilePathComponentView.swift @@ -43,7 +43,7 @@ extension FilePath { public var components: ComponentView { __consuming get { ComponentView(self) } _modify { - // RRC's empty init means that we cann't guarantee that the yielded + // RRC's empty init means that we can't guarantee that the yielded // view will restore our root. So copy it out first. // // TODO(perf): Small-form root (especially on Unix). Have Root @@ -54,13 +54,7 @@ extension FilePath { self = FilePath() defer { self = comp._path - - if !rootStr.isEmpty { - if let r = self.root { - // Roots can be forgotten but never altered - assert(r._slice.elementsEqual(rootStr)) - } - // TODO: conditional on it having changed? + if root?._slice.elementsEqual(rootStr) != true { self.root = Root(rootStr) } } @@ -105,7 +99,6 @@ extension FilePath.ComponentView: BidirectionalCollection { extension FilePath.ComponentView: RangeReplaceableCollection { public init() { - // FIXME: but what about the root? self.init(FilePath()) } diff --git a/Sources/System/FilePath/FilePathComponents.swift b/Sources/System/FilePath/FilePathComponents.swift index c7aed661..9be9aa8a 100644 --- a/Sources/System/FilePath/FilePathComponents.swift +++ b/Sources/System/FilePath/FilePathComponents.swift @@ -199,8 +199,6 @@ extension FilePath: _PlatformStringable { } -// TODO(root): Re-think these initializers. We want them to be conv - extension FilePath.Component { // The index of the `.` denoting an extension internal func _extensionIndex() -> SystemString.Index? { diff --git a/Sources/System/FilePath/FilePathParsing.swift b/Sources/System/FilePath/FilePathParsing.swift index b4ef51fa..42905d15 100644 --- a/Sources/System/FilePath/FilePathParsing.swift +++ b/Sources/System/FilePath/FilePathParsing.swift @@ -158,8 +158,6 @@ extension FilePath { if writeIdx != relStart { let priorComponent = _parseComponent(priorTo: writeIdx) - // FIXME: it's not up until writeIdx because separator, - // parseComponent should give the component... if !_isParentDirectory(priorComponent) { writeIdx = priorComponent.lowerBound readIdx = nextStart @@ -182,8 +180,6 @@ extension FilePath { assert(readIdx == _storage.endIndex && readIdx >= writeIdx) if readIdx != writeIdx { - // TODO: Why was it everything after writeIdx? -// storage.removeSubrange(storage.index(after: writeIdx)...) _storage.removeSubrange(writeIdx...) _removeTrailingSeparator() } @@ -275,24 +271,6 @@ extension FilePath.ComponentView { internal var _relativeStart: SystemString.Index { _path._relativeStart } - - internal func parseComponentStart( - endingAt i: SystemString.Index - ) -> SystemString.Index { - if i == _relativeStart, i != _path._storage.startIndex { - return _path._storage.startIndex - } - var i = i - if i != _path._storage.endIndex { - assert(isSeparator(_path._storage[i])) - i = _path._storage.index(before: i) - } - var slice = _path._storage[..) { assert(FilePath(SystemString(content)).root == nil) if content.isEmpty { return } diff --git a/Sources/System/FilePath/FilePathSyntax.swift b/Sources/System/FilePath/FilePathSyntax.swift index daae8c04..fdd97822 100644 --- a/Sources/System/FilePath/FilePathSyntax.swift +++ b/Sources/System/FilePath/FilePathSyntax.swift @@ -305,13 +305,13 @@ extension FilePath { /// replace the extension. /// /// Examples: - /// * `/tmp/foo.txt => txt` - /// * `/Appliations/Foo.app/ => app` - /// * `/Appliations/Foo.app/bar.txt => txt` - /// * `/tmp/foo.tar.gz => gz` - /// * `/tmp/.hidden => nil` - /// * `/tmp/.hidden. => ""` - /// * `/tmp/.. => nil` + /// * `/tmp/foo.txt => txt` + /// * `/Applications/Foo.app/ => app` + /// * `/Applications/Foo.app/bar.txt => txt` + /// * `/tmp/foo.tar.gz => gz` + /// * `/tmp/.hidden => nil` + /// * `/tmp/.hidden. => ""` + /// * `/tmp/.. => nil` /// /// Example: /// @@ -391,12 +391,10 @@ extension FilePath { /// Returns a copy of `self` in lexical-normal form, that is `.` and `..` /// components have been collapsed lexically (i.e. without following /// symlinks). See `lexicallyNormalize` - public var lexicallyNormal: FilePath { - __consuming get { - var copy = self - copy.lexicallyNormalize() - return copy - } + public __consuming func lexicallyNormalized() -> FilePath { + var copy = self + copy.lexicallyNormalize() + return copy } /// Create a new `FilePath` by resolving `subpath` relative to `self`, @@ -428,7 +426,7 @@ extension FilePath { public __consuming func lexicallyResolving( _ subpath: __owned FilePath ) -> FilePath? { - let subpath = subpath.removingRoot().lexicallyNormal + let subpath = subpath.removingRoot().lexicallyNormalized() guard !subpath.isEmpty else { return self } guard subpath.components.first?.kind != .parentDirectory else { return nil diff --git a/Tests/SystemTests/FilePathTests/FilePathComponentsTest.swift b/Tests/SystemTests/FilePathTests/FilePathComponentsTest.swift index a43b1b0f..97281c83 100644 --- a/Tests/SystemTests/FilePathTests/FilePathComponentsTest.swift +++ b/Tests/SystemTests/FilePathTests/FilePathComponentsTest.swift @@ -13,104 +13,6 @@ import SystemPackage @testable import SystemPackage - -// Helper to organize some ad-hoc testing -// -// TODO: Currently a class so we can overwrite file/line, but that can be -// re-evaluated when we have source loc stacks. -private final class AdHocComponentsTest: TestCase { - // TODO (source-loc stack): Push fil/line from init onto stack - - // Record the top-most file/line info (`expect` overwrites these values) - // - // TODO: When we have source loc stacks, push the location from the init, - // and `expect` will be push/pops - var file: StaticString - var line: UInt - - var path: FilePath - var body: (AdHocComponentsTest) -> () - - init( - _ path: FilePath, - _ file: StaticString = #file, - _ line: UInt = #line, - _ body: @escaping (AdHocComponentsTest) -> () - ) { - self.file = file - self.line = line - self.path = path - self.body = body - } - - func runAllTests() { - body(self) - } -} - -private func adhocComponentsTest( - _ path: FilePath, - _ file: StaticString = #file, - _ line: UInt = #line, - _ body: @escaping (AdHocComponentsTest) -> () -) { - let test = AdHocComponentsTest(path, file, line, body) - test.runAllTests() -} - -extension AdHocComponentsTest { - // Temporarily re-bind file/line - func withSourceLoc( - _ newFile: StaticString, - _ newLine: UInt, - _ body: () -> () - ) { - let (origFile, origLine) = (self.file, self.line) - (self.file, self.line) = (newFile, newLine) - defer { (self.file, self.line) = (origFile, origLine) } - body() - } - - // Customize error report by adding our path and components to output - func failureMessage(_ reason: String?) -> String { - """ - - Fail - path: \(path) - components: \(Array(path.components)) - \(reason ?? "") - """ - } - - func expect( - _ expected: FilePath, - file: StaticString = #file, line: UInt = #line - ) { - - withSourceLoc(file, line) { - expectEqual(expected, path, "expected: \(expected)") - } - } - - func expectRelative( - file: StaticString = #file, line: UInt = #line - ) { - withSourceLoc(file, line) { - expectTrue(path.isRelative, "expected relative") - } - } - - func expectAbsolute( - file: StaticString = #file, line: UInt = #line - ) { - withSourceLoc(file, line) { - expectTrue(path.isAbsolute, "expected absolute") - } - } - - // TODO: Do we want to overload others like expectEqual[Sequence]? -} - // @available(9999....) struct TestPathComponents: TestCase { var path: FilePath @@ -152,34 +54,47 @@ struct TestPathComponents: TestCase { } func testBidi() { - - + expectEqualSequence( + expectedComponents.reversed(), path.components.reversed(), "reversed()") + expectEqualSequence( + path.components, path.components.reversed().reversed(), + "reversed().reversed()") + for i in 0 ..< path.components.count { + expectEqualSequence( + expectedComponents.dropLast(i), path.components.dropLast(i), "dropLast") + expectEqualSequence( + expectedComponents.suffix(i), path.components.suffix(i), "suffix") + } } func testRRC() { - // TODO: Convert tests into mutation tests -// // What generalized tests can we do, given how initial "/" is special? -// // E.g. absolute path inserted into itself can have only one root -// -// do { -// var path = self.path -// if path.isAbsolute { -// path.components.removeFirst() -// } -// expectTrue(path.isRelative) -// -// let componentsArray = Array(path.components) -// path.components.append(contentsOf: componentsArray) -// expectEqualSequence(componentsArray + componentsArray, path.components) -// -// // TODO: Other generalized tests which work on relative paths -// } + // TODO: programmatic tests showing parity with Array + } + + func testModify() { + if path.root == nil { + let rootedPath = FilePath(root: "/", path.components) + expectNotEqual(rootedPath, path) + var pathCopy = path + expectEqual(path, pathCopy) + pathCopy.components = rootedPath.components + expectNil(pathCopy.root, "components.set doesn't assign root") + expectEqual(path, pathCopy) + } else { + let rootlessPath = FilePath(root: nil, path.components) + var pathCopy = path + expectEqual(path, pathCopy) + pathCopy.components = rootlessPath.components + expectNotNil(pathCopy.root, "components.set preserves root") + expectEqual(path, pathCopy) + } } func runAllTests() { testComponents() testBidi() testRRC() + testModify() } } @@ -187,34 +102,6 @@ struct TestPathComponents: TestCase { // @available(macOS 10.16, iOS 14.0, watchOS 7.0, tvOS 14.0, *) final class FilePathComponentsTest: XCTestCase { - - let testPaths: Array = [ - TestPathComponents("", root: nil, []), - TestPathComponents("/", root: "/", []), - TestPathComponents("foo", root: nil, ["foo"]), - TestPathComponents("foo/", root: nil, ["foo"]), - TestPathComponents("/foo", root: "/", ["foo"]), - TestPathComponents("foo/bar", root: nil, ["foo", "bar"]), - TestPathComponents("foo/bar/", root: nil, ["foo", "bar"]), - TestPathComponents("/foo/bar", root: "/", ["foo", "bar"]), - TestPathComponents("///foo//", root: "/", ["foo"]), - TestPathComponents("/foo///bar", root: "/", ["foo", "bar"]), - TestPathComponents("foo/bar/", root: nil, ["foo", "bar"]), - TestPathComponents("foo///bar/baz/", root: nil, ["foo", "bar", "baz"]), - TestPathComponents("//foo///bar/baz/", root: "/", ["foo", "bar", "baz"]), - TestPathComponents("./", root: nil, ["."]), - TestPathComponents("./..", root: nil, [".", ".."]), - TestPathComponents("/./..//", root: "/", [".", ".."]), - ] - - // TODO: generalize to a driver protocol that will inherit from XCTest, expose allTestCases - // based on an associated type, and provide the testCasees func, assuming XCTest supports - // that. - func testCases() { - testPaths.forEach { $0.runAllTests() } - } - - // TODO: Convert these kinds of test cases into mutation API test cases. func testAdHocRRC() { var path: FilePath = "/usr/local/bin" @@ -343,21 +230,26 @@ final class FilePathComponentsTest: XCTestCase { } } - func testConcatenation() { - // TODO: convert tests into mutation tests - -// for lhsTest in testPaths { -// let lhs = lhsTest.path -// for rhsTest in testPaths { -// let rhs = rhsTest.path -// adhocComponentsTest(lhs + rhs) { concatpath in -// // (lhs + rhs).components == (lhs.components + rhs.compontents) -// concatpath.expectEqualSequence(lhs.components + rhs.components, concatpath.components) -// -// // TODO: More tests around non-normalized separators -// } -// } -// } + func testCases() { + let testPaths: Array = [ + TestPathComponents("", root: nil, []), + TestPathComponents("/", root: "/", []), + TestPathComponents("foo", root: nil, ["foo"]), + TestPathComponents("foo/", root: nil, ["foo"]), + TestPathComponents("/foo", root: "/", ["foo"]), + TestPathComponents("foo/bar", root: nil, ["foo", "bar"]), + TestPathComponents("foo/bar/", root: nil, ["foo", "bar"]), + TestPathComponents("/foo/bar", root: "/", ["foo", "bar"]), + TestPathComponents("///foo//", root: "/", ["foo"]), + TestPathComponents("/foo///bar", root: "/", ["foo", "bar"]), + TestPathComponents("foo/bar/", root: nil, ["foo", "bar"]), + TestPathComponents("foo///bar/baz/", root: nil, ["foo", "bar", "baz"]), + TestPathComponents("//foo///bar/baz/", root: "/", ["foo", "bar", "baz"]), + TestPathComponents("./", root: nil, ["."]), + TestPathComponents("./..", root: nil, [".", ".."]), + TestPathComponents("/./..//", root: "/", [".", ".."]), + ] + testPaths.forEach { $0.runAllTests() } } func testSeparatorNormalization() { diff --git a/Tests/SystemTests/FilePathTests/FilePathExtras.swift b/Tests/SystemTests/FilePathTests/FilePathExtras.swift index 66c4c93f..f5b2daeb 100644 --- a/Tests/SystemTests/FilePathTests/FilePathExtras.swift +++ b/Tests/SystemTests/FilePathTests/FilePathExtras.swift @@ -50,7 +50,8 @@ extension FilePath { guard !other.isEmpty else { return true } guard !isEmpty else { return false } - let (selfLex, otherLex) = (self.lexicallyNormal, other.lexicallyNormal) + let (selfLex, otherLex) = + (self.lexicallyNormalized(), other.lexicallyNormalized()) if otherLex.isAbsolute { return selfLex.starts(with: otherLex) } // FIXME: Windows semantics with relative roots? diff --git a/Tests/SystemTests/FilePathTests/FilePathSyntaxTest.swift b/Tests/SystemTests/FilePathTests/FilePathSyntaxTest.swift index 4aed9002..34d17d2f 100644 --- a/Tests/SystemTests/FilePathTests/FilePathSyntaxTest.swift +++ b/Tests/SystemTests/FilePathTests/FilePathSyntaxTest.swift @@ -243,7 +243,7 @@ extension SyntaxTestCase { copy.lexicallyNormalize() expectEqual(copy == path, path.isLexicallyNormal, "isLexicallyNormal") expectEqual(lexicallyNormalized, copy.description, "lexically normalized") - expectEqual(copy, path.lexicallyNormal, "lexicallyNormal") + expectEqual(copy, path.lexicallyNormalized(), "lexicallyNormal") expectEqual(absolute, path.isAbsolute, "absolute") expectEqual(!absolute, path.isRelative, "!absolute") From ac6f82162c7c3fca0e4ce87fd6f37bb262eaabb2 Mon Sep 17 00:00:00 2001 From: Prabaljit Walia Date: Fri, 5 Feb 2021 13:04:16 +0530 Subject: [PATCH 008/427] Update README.md --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index dd8c634a..5cef221b 100644 --- a/README.md +++ b/README.md @@ -57,3 +57,9 @@ let package = Package( ] ) ``` + +## Contributing +Before contributing, please read [CONTRIBUTING.md](CONTRIBUTING.md). + +## LICENSE +See [LICENSE](LICENSE.txt) for license information. From 11cc26a46bcc64a1e73e433f9571c3645dd2cf52 Mon Sep 17 00:00:00 2001 From: Prabaljit Walia Date: Fri, 5 Feb 2021 13:05:23 +0530 Subject: [PATCH 009/427] Update README.md --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 5cef221b..f67a283f 100644 --- a/README.md +++ b/README.md @@ -59,7 +59,9 @@ let package = Package( ``` ## Contributing + Before contributing, please read [CONTRIBUTING.md](CONTRIBUTING.md). ## LICENSE + See [LICENSE](LICENSE.txt) for license information. From 2904f9ead35bb4abea8175b1902caa55e3ac4832 Mon Sep 17 00:00:00 2001 From: Karoy Lorentey Date: Fri, 5 Feb 2021 19:35:18 -0800 Subject: [PATCH 010/427] Add symbolic names for standard file descriptors --- Sources/System/FileDescriptor.swift | 17 ++++++++++++++++- Tests/SystemTests/FileTypesTest.swift | 8 +++++++- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/Sources/System/FileDescriptor.swift b/Sources/System/FileDescriptor.swift index 031a6abd..8659c3f8 100644 --- a/Sources/System/FileDescriptor.swift +++ b/Sources/System/FileDescriptor.swift @@ -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 - 2021 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 @@ -25,6 +25,21 @@ public struct FileDescriptor: RawRepresentable, Hashable, Codable { public init(rawValue: CInt) { self.rawValue = rawValue } } +// Standard file descriptors +extension FileDescriptor { + /// The standard input file descriptor, with a numeric value of 0. + @_alwaysEmitIntoClient + public static var standardInput: FileDescriptor { .init(rawValue: 0) } + + /// The standard output file descriptor, with a numeric value of 1. + @_alwaysEmitIntoClient + public static var standardOutput: FileDescriptor { .init(rawValue: 1) } + + /// The standard error file descriptor, with a numeric value of 2. + @_alwaysEmitIntoClient + public static var standardError: FileDescriptor { .init(rawValue: 2) } +} + // @available(macOS 10.16, iOS 14.0, watchOS 7.0, tvOS 14.0, *) extension FileDescriptor { /// The desired read and write access for a newly opened file. diff --git a/Tests/SystemTests/FileTypesTest.swift b/Tests/SystemTests/FileTypesTest.swift index f6dc7f07..26eceb47 100644 --- a/Tests/SystemTests/FileTypesTest.swift +++ b/Tests/SystemTests/FileTypesTest.swift @@ -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 - 2021 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 @@ -12,6 +12,12 @@ import SystemPackage // @available(macOS 10.16, iOS 14.0, watchOS 7.0, tvOS 14.0, *) final class FileDescriptorTest: XCTestCase { + func testStandardDescriptors() { + XCTAssertEqual(FileDescriptor.standardInput.rawValue, 0) + XCTAssertEqual(FileDescriptor.standardOutput.rawValue, 1) + XCTAssertEqual(FileDescriptor.standardError.rawValue, 2) + } + // Test the constants match the C header values. For various reasons, func testConstants() { XCTAssertEqual(O_RDONLY, FileDescriptor.AccessMode.readOnly.rawValue) From 03c0b7ada5c736eab462d16b15ba4f6a1f28df73 Mon Sep 17 00:00:00 2001 From: Michael Ilseman Date: Mon, 8 Feb 2021 09:20:25 -0700 Subject: [PATCH 011/427] Fix release builds by making assert into fatalError --- Sources/System/FilePath/FilePathWindows.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Sources/System/FilePath/FilePathWindows.swift b/Sources/System/FilePath/FilePathWindows.swift index 0d3216f6..db0651eb 100644 --- a/Sources/System/FilePath/FilePathWindows.swift +++ b/Sources/System/FilePath/FilePathWindows.swift @@ -339,7 +339,7 @@ extension SystemString { func parseUNC(deviceSigil: SystemChar?) -> _ParsedWindowsRoot { let serverRange = lexer.eatComponent() guard lexer.eatBackslash() else { - assert(false, "expected normalized root to contain backslash") + fatalError("expected normalized root to contain backslash") } let shareRange = lexer.eatComponent() let rootEnd = lexer.current @@ -375,12 +375,12 @@ extension SystemString { _ = sigil // suppress warnings guard lexer.eatBackslash() else { - assert(false, "expected normalized root to contain backslash") + fatalError("expected normalized root to contain backslash") } if lexer.eatUNC() { guard lexer.eatBackslash() else { - assert(false, "expected normalized root to contain backslash") + fatalError("expected normalized root to contain backslash") } return parseUNC(deviceSigil: sigil) } From b5a89a46012a1c43c1456debf547a2c3e8479e4a Mon Sep 17 00:00:00 2001 From: Michael Ilseman Date: Sun, 21 Feb 2021 09:48:53 -0700 Subject: [PATCH 012/427] [test] Add a test for issue-26 --- Tests/SystemTests/FileOperationsTest.swift | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/Tests/SystemTests/FileOperationsTest.swift b/Tests/SystemTests/FileOperationsTest.swift index 57149eae..71fdcc96 100644 --- a/Tests/SystemTests/FileOperationsTest.swift +++ b/Tests/SystemTests/FileOperationsTest.swift @@ -111,5 +111,20 @@ final class FileOperationsTest: XCTestCase { fatalError("FATAL: `testAdHocOpen`") } } + + func testGithubIssues() { + // https://github.com/apple/swift-system/issues/26 + let issue26 = MockTestCase( + name: "open", "a path", O_WRONLY | O_CREAT, 0o020, interruptable: true + ) { + retryOnInterrupt in + _ = try FileDescriptor.open( + "a path", .writeOnly, options: [.create], + permissions: [.groupWrite], + retryOnInterrupt: retryOnInterrupt) + } + issue26.runAllTests() + + } } From 2d24a58a41e20dfcfc3dca6ded7f54c560b5200a Mon Sep 17 00:00:00 2001 From: Karoy Lorentey Date: Wed, 24 Feb 2021 13:30:07 -0800 Subject: [PATCH 013/427] Add support for dup/dup2 --- Sources/System/FileOperations.swift | 62 ++++++++++++++++++++++ Sources/SystemInternals/Syscalls.swift | 20 +++++-- Tests/SystemTests/FileOperationsTest.swift | 8 +++ 3 files changed, 87 insertions(+), 3 deletions(-) diff --git a/Sources/System/FileOperations.swift b/Sources/System/FileOperations.swift index 83ce9bfa..88ec3773 100644 --- a/Sources/System/FileOperations.swift +++ b/Sources/System/FileOperations.swift @@ -309,4 +309,66 @@ extension FileDescriptor { buffer, retryOnInterrupt: retryOnInterrupt) } + + /// Duplicate this file descriptor and return the newly created copy. + /// + /// - Parameters: + /// - `target`: The desired target file descriptor, or `nil`, in which case + /// the copy is assigned to the file descriptor with the lowest raw value + /// that is not currently in use by the process. + /// - retryOnInterrupt: Whether to retry the write operation + /// if it throws ``Errno/interrupted``. The default is `true`. + /// Pass `false` to try only once and throw an error upon interruption. + /// - Returns: The new file descriptor. + /// + /// If the `target` descriptor is already in use, then it is first + /// deallocated as if a close(2) call had been done first. + /// + /// File descriptors are merely references to some underlying system resource. + /// The system does not distinguish between the original and the new file + /// descriptor in any way. For example, read, write and seek operations on + /// one of them also affect the logical file position in the other, and + /// append mode, non-blocking I/O and asynchronous I/O options are shared + /// between the references. If a separate pointer into the file is desired, + /// a different object reference to the file must be obtained by issuing an + /// additional call to `open`. + /// + /// However, each file descriptor maintains its own close-on-exec flag. + /// + /// + /// The corresponding C functions are `dup` and `dup2`. + @_alwaysEmitIntoClient + // @available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *) + public func duplicate( + as target: FileDescriptor? = nil, + retryOnInterrupt: Bool = true + ) throws -> FileDescriptor { + try _duplicate(as: target, retryOnInterrupt: retryOnInterrupt).get() + } + + // @available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *) + @usableFromInline + internal func _duplicate( + as target: FileDescriptor?, + retryOnInterrupt: Bool + ) throws -> Result { + valueOrErrno(retryOnInterrupt: retryOnInterrupt) { + if let target = target { + return system_dup2(self.rawValue, target.rawValue) + } + return system_dup(self.rawValue) + }.map(FileDescriptor.init(rawValue:)) + } + + @_alwaysEmitIntoClient + @available(*, unavailable, renamed: "duplicate") + public func dup() throws -> FileDescriptor { + fatalError("Not implemented") + } + + @_alwaysEmitIntoClient + @available(*, unavailable, renamed: "duplicate") + public func dup2() throws -> FileDescriptor { + fatalError("Not implemented") + } } diff --git a/Sources/SystemInternals/Syscalls.swift b/Sources/SystemInternals/Syscalls.swift index 4c95f114..4fa556cd 100644 --- a/Sources/SystemInternals/Syscalls.swift +++ b/Sources/SystemInternals/Syscalls.swift @@ -19,9 +19,10 @@ import ucrt #if ENABLE_MOCKING // Strip the mock_system prefix and the arg list suffix -private func originalSyscallName(_ s: String) -> String { - precondition(s.starts(with: "system_")) - return String(s.dropFirst("system_".count).prefix { $0.isLetter }) +private func originalSyscallName(_ function: String) -> String { + // `function` must be of format `system_()` + precondition(function.starts(with: "system_")) + return String(function.dropFirst("system_".count).prefix { $0 != "(" }) } private func mockImpl( @@ -152,3 +153,16 @@ public func system_pwrite( return pwrite(fd, buf, nbyte, offset) } +public func system_dup(_ fd: Int32) -> Int32 { + #if ENABLE_MOCKING + if mockingEnabled { return mock(fd) } + #endif + return dup(fd) +} + +public func system_dup2(_ fd: Int32, _ fd2: Int32) -> Int32 { + #if ENABLE_MOCKING + if mockingEnabled { return mock(fd, fd2) } + #endif + return dup2(fd, fd2) +} diff --git a/Tests/SystemTests/FileOperationsTest.swift b/Tests/SystemTests/FileOperationsTest.swift index 71fdcc96..644e4eac 100644 --- a/Tests/SystemTests/FileOperationsTest.swift +++ b/Tests/SystemTests/FileOperationsTest.swift @@ -68,6 +68,14 @@ final class FileOperationsTest: XCTestCase { _ = try fd.close() }, + MockTestCase(name: "dup", rawFD, interruptable: true) { retryOnInterrupt in + _ = try fd.duplicate(retryOnInterrupt: retryOnInterrupt) + }, + + MockTestCase(name: "dup2", rawFD, 42, interruptable: true) { retryOnInterrupt in + _ = try fd.duplicate(as: FileDescriptor(rawValue: 42), + retryOnInterrupt: retryOnInterrupt) + }, ] for test in syscallTestCases { test.runAllTests() } From 4f0eafd24ec8e02f681414a7ece966891d292a7e Mon Sep 17 00:00:00 2001 From: Michael Ilseman Date: Tue, 9 Feb 2021 16:09:45 -0700 Subject: [PATCH 014/427] Ease binary distribution: testing Add a SYSTEM_PACKAGE define and conditionalize import statements in tests to ease testing of binary distributions of System. --- Package.swift | 5 ++++- Tests/SystemTests/ErrnoTest.swift | 5 +++++ Tests/SystemTests/FileOperationsTest.swift | 5 +++++ .../FilePathTests/FilePathComponentsTest.swift | 8 +++++--- Tests/SystemTests/FilePathTests/FilePathExtras.swift | 4 ++++ .../SystemTests/FilePathTests/FilePathParsingTest.swift | 9 +++++++++ Tests/SystemTests/FilePathTests/FilePathSyntaxTest.swift | 5 +++++ Tests/SystemTests/FilePathTests/FilePathTest.swift | 5 +++++ Tests/SystemTests/FileTypesTest.swift | 5 +++++ Tests/SystemTests/MockingTest.swift | 6 ++++++ Tests/SystemTests/SystemStringTests.swift | 6 +++++- Tests/SystemTests/TestingInfrastructure.swift | 5 +++++ 12 files changed, 63 insertions(+), 5 deletions(-) diff --git a/Package.swift b/Package.swift index 8e20419b..561194bc 100644 --- a/Package.swift +++ b/Package.swift @@ -29,7 +29,10 @@ let targets: [PackageDescription.Target] = [ .testTarget( name: "SystemTests", - dependencies: ["SystemPackage"]), + dependencies: ["SystemPackage"], + swiftSettings: [ + .define("SYSTEM_PACKAGE") + ]), ] let package = Package( diff --git a/Tests/SystemTests/ErrnoTest.swift b/Tests/SystemTests/ErrnoTest.swift index 5c026b3f..86a7664e 100644 --- a/Tests/SystemTests/ErrnoTest.swift +++ b/Tests/SystemTests/ErrnoTest.swift @@ -8,7 +8,12 @@ */ import XCTest + +#if SYSTEM_PACKAGE import SystemPackage +#else +import System +#endif // @available(macOS 10.16, iOS 14.0, watchOS 7.0, tvOS 14.0, *) final class ErrnoTest: XCTestCase { diff --git a/Tests/SystemTests/FileOperationsTest.swift b/Tests/SystemTests/FileOperationsTest.swift index 644e4eac..0635e880 100644 --- a/Tests/SystemTests/FileOperationsTest.swift +++ b/Tests/SystemTests/FileOperationsTest.swift @@ -8,7 +8,12 @@ */ import XCTest + +#if SYSTEM_PACKAGE import SystemPackage +#else +import System +#endif // @available(macOS 10.16, iOS 14.0, watchOS 7.0, tvOS 14.0, *) final class FileOperationsTest: XCTestCase { diff --git a/Tests/SystemTests/FilePathTests/FilePathComponentsTest.swift b/Tests/SystemTests/FilePathTests/FilePathComponentsTest.swift index 97281c83..052fcc9d 100644 --- a/Tests/SystemTests/FilePathTests/FilePathComponentsTest.swift +++ b/Tests/SystemTests/FilePathTests/FilePathComponentsTest.swift @@ -8,10 +8,12 @@ */ import XCTest -import SystemPackage -@testable -import SystemPackage +#if SYSTEM_PACKAGE +@testable import SystemPackage +#else +@testable import System +#endif // @available(9999....) struct TestPathComponents: TestCase { diff --git a/Tests/SystemTests/FilePathTests/FilePathExtras.swift b/Tests/SystemTests/FilePathTests/FilePathExtras.swift index f5b2daeb..1fe790cc 100644 --- a/Tests/SystemTests/FilePathTests/FilePathExtras.swift +++ b/Tests/SystemTests/FilePathTests/FilePathExtras.swift @@ -1,5 +1,9 @@ +#if SYSTEM_PACKAGE @testable import SystemPackage +#else +@testable import System +#endif // Why can't I write this extension on `FilePath.ComponentView.SubSequence`? extension Slice where Base == FilePath.ComponentView { diff --git a/Tests/SystemTests/FilePathTests/FilePathParsingTest.swift b/Tests/SystemTests/FilePathTests/FilePathParsingTest.swift index 420a6b4f..c268613d 100644 --- a/Tests/SystemTests/FilePathTests/FilePathParsingTest.swift +++ b/Tests/SystemTests/FilePathTests/FilePathParsingTest.swift @@ -8,7 +8,12 @@ */ import XCTest + +#if SYSTEM_PACKAGE import SystemPackage +#else +import System +#endif private struct ParsingTestCase: TestCase { // Whether we want the path to be constructed and syntactically @@ -57,7 +62,11 @@ extension ParsingTestCase { } } +#if SYSTEM_PACKAGE @testable import SystemPackage +#else +@testable import System +#endif final class FilePathParsingTest: XCTestCase { func testNormalization() { diff --git a/Tests/SystemTests/FilePathTests/FilePathSyntaxTest.swift b/Tests/SystemTests/FilePathTests/FilePathSyntaxTest.swift index 34d17d2f..403f5a06 100644 --- a/Tests/SystemTests/FilePathTests/FilePathSyntaxTest.swift +++ b/Tests/SystemTests/FilePathTests/FilePathSyntaxTest.swift @@ -8,7 +8,12 @@ */ import XCTest + +#if SYSTEM_PACKAGE import SystemPackage +#else +import System +#endif private struct SyntaxTestCase: TestCase { // Whether we want the path to be constructed and syntactically diff --git a/Tests/SystemTests/FilePathTests/FilePathTest.swift b/Tests/SystemTests/FilePathTests/FilePathTest.swift index ae4f8755..c6679323 100644 --- a/Tests/SystemTests/FilePathTests/FilePathTest.swift +++ b/Tests/SystemTests/FilePathTests/FilePathTest.swift @@ -8,7 +8,12 @@ */ import XCTest + +#if SYSTEM_PACKAGE import SystemPackage +#else +import System +#endif // @available(macOS 10.16, iOS 14.0, watchOS 7.0, tvOS 14.0, *) func filePathFromUnterminatedBytes(_ bytes: S) -> FilePath where S.Element == UInt8 { diff --git a/Tests/SystemTests/FileTypesTest.swift b/Tests/SystemTests/FileTypesTest.swift index 26eceb47..3c40a154 100644 --- a/Tests/SystemTests/FileTypesTest.swift +++ b/Tests/SystemTests/FileTypesTest.swift @@ -8,7 +8,12 @@ */ import XCTest + +#if SYSTEM_PACKAGE import SystemPackage +#else +import System +#endif // @available(macOS 10.16, iOS 14.0, watchOS 7.0, tvOS 14.0, *) final class FileDescriptorTest: XCTestCase { diff --git a/Tests/SystemTests/MockingTest.swift b/Tests/SystemTests/MockingTest.swift index 87b55c04..87bee81f 100644 --- a/Tests/SystemTests/MockingTest.swift +++ b/Tests/SystemTests/MockingTest.swift @@ -8,7 +8,13 @@ */ import XCTest + +#if SYSTEM_PACKAGE import SystemPackage +#else +import System +#endif + @testable import SystemInternals // @available... diff --git a/Tests/SystemTests/SystemStringTests.swift b/Tests/SystemTests/SystemStringTests.swift index 1c893500..2c409fff 100644 --- a/Tests/SystemTests/SystemStringTests.swift +++ b/Tests/SystemTests/SystemStringTests.swift @@ -35,9 +35,13 @@ extension UnsafeBufferPointer where Element == CChar { import XCTest -import SystemPackage +#if SYSTEM_PACKAGE @testable import SystemPackage +#else +@testable import System +#endif + import SystemInternals private func makeRaw( diff --git a/Tests/SystemTests/TestingInfrastructure.swift b/Tests/SystemTests/TestingInfrastructure.swift index 8aded96d..11a4dff6 100644 --- a/Tests/SystemTests/TestingInfrastructure.swift +++ b/Tests/SystemTests/TestingInfrastructure.swift @@ -9,7 +9,12 @@ import XCTest import SystemInternals + +#if SYSTEM_PACKAGE import SystemPackage +#else +import System +#endif // To aid debugging, force failures to fatal error internal var forceFatalFailures = false From 7b33caf850e2ac926ca94ac5a63bcf372dc3ec96 Mon Sep 17 00:00:00 2001 From: Michael Ilseman Date: Tue, 9 Feb 2021 16:31:10 -0700 Subject: [PATCH 015/427] Add commented out availability. Add it for public APIs, though we will likely have to add it for internal stuff too as a compiler workaround. --- Sources/System/FilePath/FilePathComponentView.swift | 2 ++ Sources/System/FilePath/FilePathComponents.swift | 2 +- Sources/System/FilePath/FilePathParsing.swift | 1 - Sources/System/FilePath/FilePathString.swift | 9 ++++++++- Sources/System/FilePath/FilePathSyntax.swift | 9 ++++++++- Sources/System/Platform/Platform.swift | 1 + Sources/System/Platform/PlatformString.swift | 2 ++ .../FilePathTests/FilePathComponentsTest.swift | 6 ++---- Tests/SystemTests/FilePathTests/FilePathExtras.swift | 2 ++ .../SystemTests/FilePathTests/FilePathParsingTest.swift | 1 + Tests/SystemTests/FilePathTests/FilePathSyntaxTest.swift | 3 +++ Tests/SystemTests/FilePathTests/FilePathTest.swift | 2 +- Tests/SystemTests/MockingTest.swift | 2 +- 13 files changed, 32 insertions(+), 10 deletions(-) diff --git a/Sources/System/FilePath/FilePathComponentView.swift b/Sources/System/FilePath/FilePathComponentView.swift index a79c3abd..7b5394bb 100644 --- a/Sources/System/FilePath/FilePathComponentView.swift +++ b/Sources/System/FilePath/FilePathComponentView.swift @@ -9,6 +9,7 @@ // MARK: - API +// @available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *) extension FilePath { /// A bidirectional, range replaceable collection of the non-root components /// that make up a file path. @@ -97,6 +98,7 @@ extension FilePath.ComponentView: BidirectionalCollection { } } +// @available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *) extension FilePath.ComponentView: RangeReplaceableCollection { public init() { self.init(FilePath()) diff --git a/Sources/System/FilePath/FilePathComponents.swift b/Sources/System/FilePath/FilePathComponents.swift index 9be9aa8a..7bb55d59 100644 --- a/Sources/System/FilePath/FilePathComponents.swift +++ b/Sources/System/FilePath/FilePathComponents.swift @@ -73,6 +73,7 @@ extension FilePath { } } +// @available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *) extension FilePath.Component { /// Whether a component is a regular file or directory name, or a special @@ -253,7 +254,6 @@ extension FilePath.Root { // MARK: - Invariants -// @available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *) extension FilePath.Component { // TODO: ensure this all gets easily optimized away in release... internal func _invariantCheck() { diff --git a/Sources/System/FilePath/FilePathParsing.swift b/Sources/System/FilePath/FilePathParsing.swift index 42905d15..e65833a3 100644 --- a/Sources/System/FilePath/FilePathParsing.swift +++ b/Sources/System/FilePath/FilePathParsing.swift @@ -111,7 +111,6 @@ extension SystemString { } } -// @available(macOS 10.16, iOS 14.0, watchOS 7.0, tvOS 14.0, *) extension FilePath { internal mutating func _removeTrailingSeparator() { _storage._removeTrailingSeparator() diff --git a/Sources/System/FilePath/FilePathString.swift b/Sources/System/FilePath/FilePathString.swift index eb9b27b3..842367c1 100644 --- a/Sources/System/FilePath/FilePathString.swift +++ b/Sources/System/FilePath/FilePathString.swift @@ -9,7 +9,7 @@ // MARK: - Platform string -// @available(macOS 10.16, iOS 14.0, watchOS 7.0, tvOS 14.0, *) +// @available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *) extension FilePath { /// Creates a file path by copying bytes from a null-terminated platform /// string. @@ -39,6 +39,7 @@ extension FilePath { } } +// @available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *) extension FilePath.Component { /// Creates a file path component by copying bytes from a null-terminated /// platform string. @@ -75,6 +76,7 @@ extension FilePath.Component { } } +// @available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *) extension FilePath.Root { /// Creates a file path root by copying bytes from a null-terminated platform /// string. @@ -234,6 +236,7 @@ extension FilePath.Root: CustomStringConvertible, CustomDebugStringConvertible { // MARK: - Convenience helpers // Convenience helpers +// @available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *) extension FilePath { /// Creates a string by interpreting the path’s content as UTF-8 on Unix /// and UTF-16 on Windows. @@ -244,6 +247,7 @@ extension FilePath { } } +// @available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *) extension FilePath.Component { /// Creates a string by interpreting the component’s content as UTF-8 on Unix /// and UTF-16 on Windows. @@ -254,6 +258,7 @@ extension FilePath.Component { } } +// @available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *) extension FilePath.Root { /// On Unix, this returns `"/"`. /// @@ -380,6 +385,7 @@ extension String { // MARK: - Deprecations +// @available(macOS 10.16, iOS 14.0, watchOS 7.0, tvOS 14.0, *) extension String { @available(*, deprecated, renamed: "init(decoding:)") public init(_ path: FilePath) { self.init(decoding: path) } @@ -388,6 +394,7 @@ extension String { public init?(validatingUTF8 path: FilePath) { self.init(validating: path) } } +// @available(macOS 10.16, iOS 14.0, watchOS 7.0, tvOS 14.0, *) extension FilePath { @available(*, deprecated, renamed: "init(platformString:)") public init(cString: UnsafePointer) { diff --git a/Sources/System/FilePath/FilePathSyntax.swift b/Sources/System/FilePath/FilePathSyntax.swift index fdd97822..ee02aa5c 100644 --- a/Sources/System/FilePath/FilePathSyntax.swift +++ b/Sources/System/FilePath/FilePathSyntax.swift @@ -9,7 +9,7 @@ // MARK: - Query API -// @available(...) +// @available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *) extension FilePath { /// Returns true if this path uniquely identifies the location of /// a file without reference to an additional starting location. @@ -103,6 +103,7 @@ extension FilePath { } // MARK: - Decompose a path +// @available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *) extension FilePath { /// Returns the root of a path if there is one, otherwise `nil`. /// @@ -187,6 +188,7 @@ extension FilePath { } } +// @available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *) extension FilePath { /// Returns the final component of the path. /// Returns `nil` if the path is empty or only contains a root. @@ -258,6 +260,7 @@ extension FilePath { } } +// @available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *) extension FilePath.Component { /// The extension of this file or directory component. /// @@ -290,6 +293,7 @@ extension FilePath.Component { } } +// @available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *) extension FilePath { /// The extension of the file or directory last component. @@ -356,6 +360,7 @@ extension FilePath { } +// @available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *) extension FilePath { /// Whether the path is in lexical-normal form, that is `.` and `..` /// components have been collapsed lexically (i.e. without following @@ -436,6 +441,7 @@ extension FilePath { } // Modification and concatenation API +// @available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *) extension FilePath { /// If `prefix` is a prefix of `self`, removes it and returns `true`. /// Otherwise returns `false`. @@ -605,6 +611,7 @@ extension FilePath { } // MARK - Renamed +// @available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *) extension FilePath { @available(*, unavailable, renamed: "removingLastComponent()") public var dirname: FilePath { removingLastComponent() } diff --git a/Sources/System/Platform/Platform.swift b/Sources/System/Platform/Platform.swift index 167bc61e..069f90be 100644 --- a/Sources/System/Platform/Platform.swift +++ b/Sources/System/Platform/Platform.swift @@ -17,6 +17,7 @@ public typealias CModeT = CInterop.Mode /// A namespace for C and platform types +// @available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *) public enum CInterop { #if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) /// The C `mode_t` type. diff --git a/Sources/System/Platform/PlatformString.swift b/Sources/System/Platform/PlatformString.swift index af1bee73..d68575f1 100644 --- a/Sources/System/Platform/PlatformString.swift +++ b/Sources/System/Platform/PlatformString.swift @@ -7,6 +7,7 @@ See https://swift.org/LICENSE.txt for license information */ +// @available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *) extension CInterop { #if os(Windows) /// The platform's preferred character type. On Unix, this is an 8-bit C @@ -54,6 +55,7 @@ extension CInterop.PlatformUnicodeEncoding.CodeUnit { } } +// @available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *) extension String { /// Creates a string by interpreting the null-terminated platform string as /// UTF-8 on Unix and UTF-16 on Windows. diff --git a/Tests/SystemTests/FilePathTests/FilePathComponentsTest.swift b/Tests/SystemTests/FilePathTests/FilePathComponentsTest.swift index 052fcc9d..0979f952 100644 --- a/Tests/SystemTests/FilePathTests/FilePathComponentsTest.swift +++ b/Tests/SystemTests/FilePathTests/FilePathComponentsTest.swift @@ -15,7 +15,7 @@ import XCTest @testable import System #endif -// @available(9999....) +// @available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *) struct TestPathComponents: TestCase { var path: FilePath var expectedRoot: FilePath.Root? @@ -100,9 +100,7 @@ struct TestPathComponents: TestCase { } } -// TODO: Note that double-reversal will drop root if FilePath is constructed in between ... - -// @available(macOS 10.16, iOS 14.0, watchOS 7.0, tvOS 14.0, *) +// @available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *) final class FilePathComponentsTest: XCTestCase { func testAdHocRRC() { var path: FilePath = "/usr/local/bin" diff --git a/Tests/SystemTests/FilePathTests/FilePathExtras.swift b/Tests/SystemTests/FilePathTests/FilePathExtras.swift index 1fe790cc..a9fa61ad 100644 --- a/Tests/SystemTests/FilePathTests/FilePathExtras.swift +++ b/Tests/SystemTests/FilePathTests/FilePathExtras.swift @@ -6,6 +6,7 @@ #endif // Why can't I write this extension on `FilePath.ComponentView.SubSequence`? +// @available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *) extension Slice where Base == FilePath.ComponentView { internal var _storageSlice: SystemString.SubSequence { base._path._storage[self.startIndex._storage ..< self.endIndex._storage] @@ -14,6 +15,7 @@ extension Slice where Base == FilePath.ComponentView { // Proposed API that didn't make the cut, but we stil want to keep our testing for +// @available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *) extension FilePath { /// Returns `self` relative to `base`. /// This does not cosult the file system or resolve symlinks. diff --git a/Tests/SystemTests/FilePathTests/FilePathParsingTest.swift b/Tests/SystemTests/FilePathTests/FilePathParsingTest.swift index c268613d..1636568a 100644 --- a/Tests/SystemTests/FilePathTests/FilePathParsingTest.swift +++ b/Tests/SystemTests/FilePathTests/FilePathParsingTest.swift @@ -68,6 +68,7 @@ extension ParsingTestCase { @testable import System #endif +// @available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *) final class FilePathParsingTest: XCTestCase { func testNormalization() { let unixPaths: Array = [ diff --git a/Tests/SystemTests/FilePathTests/FilePathSyntaxTest.swift b/Tests/SystemTests/FilePathTests/FilePathSyntaxTest.swift index 403f5a06..cc931d4d 100644 --- a/Tests/SystemTests/FilePathTests/FilePathSyntaxTest.swift +++ b/Tests/SystemTests/FilePathTests/FilePathSyntaxTest.swift @@ -146,6 +146,7 @@ extension SyntaxTestCase { } } +// @available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *) extension SyntaxTestCase { func testComponents(_ path: FilePath, expected: [String]) { let expectedComponents = expected.map { FilePath.Component($0)! } @@ -341,6 +342,7 @@ private struct WindowsRootTestCase: TestCase { var line: UInt } +// @available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *) extension WindowsRootTestCase { func runAllTests() { withWindowsPaths(enabled: true) { @@ -355,6 +357,7 @@ extension WindowsRootTestCase { } } +// @available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *) final class FilePathSyntaxTest: XCTestCase { func testPathSyntax() { let unixPaths: Array = [ diff --git a/Tests/SystemTests/FilePathTests/FilePathTest.swift b/Tests/SystemTests/FilePathTests/FilePathTest.swift index c6679323..93bc3504 100644 --- a/Tests/SystemTests/FilePathTests/FilePathTest.swift +++ b/Tests/SystemTests/FilePathTests/FilePathTest.swift @@ -27,7 +27,7 @@ func filePathFromUnterminatedBytes(_ bytes: S) -> FilePath where S. } let invalidBytes: [UInt8] = [0x2F, 0x61, 0x2F, 0x62, 0x2F, 0x83] -// @available(macOS 10.16, iOS 14.0, watchOS 7.0, tvOS 14.0, *) +// @available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *) final class FilePathTest: XCTestCase { struct TestPath { let filePath: FilePath diff --git a/Tests/SystemTests/MockingTest.swift b/Tests/SystemTests/MockingTest.swift index 87bee81f..b377b090 100644 --- a/Tests/SystemTests/MockingTest.swift +++ b/Tests/SystemTests/MockingTest.swift @@ -17,7 +17,7 @@ import System @testable import SystemInternals -// @available... +// @available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *) final class MockingTest: XCTestCase { func testMocking() { XCTAssertFalse(mockingEnabled) From b63554656da8e581c71409ae063d9533ee9f3f87 Mon Sep 17 00:00:00 2001 From: Michael Ilseman Date: Wed, 10 Feb 2021 18:09:39 -0700 Subject: [PATCH 016/427] [gardening] Privatization of Mocking --- Sources/SystemInternals/Exports.swift | 39 ++++ Sources/SystemInternals/Mocking.swift | 167 +++++++++--------- Sources/SystemInternals/Syscalls.swift | 72 ++------ Tests/SystemTests/TestingInfrastructure.swift | 12 +- 4 files changed, 131 insertions(+), 159 deletions(-) diff --git a/Sources/SystemInternals/Exports.swift b/Sources/SystemInternals/Exports.swift index 868fe2b4..5cd70917 100644 --- a/Sources/SystemInternals/Exports.swift +++ b/Sources/SystemInternals/Exports.swift @@ -141,6 +141,45 @@ extension String { self.init(cString: platformString) #endif } +} +// TLS +#if os(Windows) +internal typealias _PlatformTLSKey = DWORD +#else +internal typealias _PlatformTLSKey = pthread_key_t +#endif +internal func makeTLSKey() -> _PlatformTLSKey { + #if os(Windows) + let raw: DWORD = FlsAlloc(nil) + if raw == FLS_OUT_OF_INDEXES { + fatalError("Unable to create key") + } + return raw + #else + var raw = pthread_key_t() + guard 0 == pthread_key_create(&raw, nil) else { + fatalError("Unable to create key") + } + return raw + #endif +} +internal func setTLS(_ key: _PlatformTLSKey, _ p: UnsafeMutableRawPointer?) { + #if os(Windows) + guard FlsSetValue(key, p) else { + fatalError("Unable to set TLS") + } + #else + guard 0 == pthread_setspecific(key, p) else { + fatalError("Unable to set TLS") + } + #endif +} +internal func getTLS(_ key: _PlatformTLSKey) -> UnsafeMutableRawPointer? { + #if os(Windows) + FlsGetValue(key) + #else + pthread_getspecific(key) + #endif } diff --git a/Sources/SystemInternals/Mocking.swift b/Sources/SystemInternals/Mocking.swift index 118575ef..41549d4a 100644 --- a/Sources/SystemInternals/Mocking.swift +++ b/Sources/SystemInternals/Mocking.swift @@ -18,12 +18,12 @@ // #if ENABLE_MOCKING -public struct Trace { - public struct Entry: Hashable { - var name: String - var arguments: [AnyHashable] +internal struct Trace { + internal struct Entry: Hashable { + private var name: String + private var arguments: [AnyHashable] - public init(name: String, _ arguments: [AnyHashable]) { + internal init(name: String, _ arguments: [AnyHashable]) { self.name = name self.arguments = arguments } @@ -32,39 +32,20 @@ public struct Trace { private var entries: [Entry] = [] private var firstEntry: Int = 0 - public var isEmpty: Bool { firstEntry >= entries.count } + internal var isEmpty: Bool { firstEntry >= entries.count } - public mutating func dequeue() -> Entry? { + internal mutating func dequeue() -> Entry? { guard !self.isEmpty else { return nil } defer { firstEntry += 1 } return entries[firstEntry] } - internal mutating func add(_ e: Entry) { + fileprivate mutating func add(_ e: Entry) { entries.append(e) } - - public mutating func clear() { entries.removeAll() } -} - -// TODO: Track -public struct WriteBuffer { - public var enabled: Bool = false - - private var buffer: [UInt8] = [] - private var chunkSize: Int? = nil - - internal mutating func write(_ buf: UnsafeRawBufferPointer) -> Int { - guard enabled else { return 0 } - let chunk = chunkSize ?? buf.count - buffer.append(contentsOf: buf.prefix(chunk)) - return chunk - } - - public var contents: [UInt8] { buffer } } -public enum ForceErrno: Equatable { +internal enum ForceErrno: Equatable { case none case always(errno: CInt) @@ -74,70 +55,17 @@ public enum ForceErrno: Equatable { // Provide access to the driver, context, and trace stack of mocking public class MockingDriver { // Record syscalls and their arguments - public var trace = Trace() + internal var trace = Trace() // Mock errors inside syscalls - public var forceErrno = ForceErrno.none - - // A buffer to put `write` bytes into - public var writeBuffer = WriteBuffer() + internal var forceErrno = ForceErrno.none // Whether we should pretend to be Windows for syntactic operations // inside FilePath - public var forceWindowsSyntaxForPaths = false + fileprivate var forceWindowsSyntaxForPaths = false } -#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) -import Darwin -#elseif os(Linux) || os(FreeBSD) || os(Android) -import Glibc -#elseif os(Windows) -import ucrt -import WinSDK -#else -#error("Unsupported Platform") -#endif - -// TLS helper functions -#if os(Windows) -internal typealias TLSKey = DWORD -internal func makeTLSKey() -> TLSKey { - let raw: DWORD = FlsAlloc(nil) - if raw == FLS_OUT_OF_INDEXES { - fatalError("Unable to create key") - } - return raw -} -internal func setTLS(_ key: TLSKey, _ p: UnsafeMutableRawPointer?) { - guard FlsSetValue(key, p) else { - fatalError("Unable to set TLS") - } -} -internal func getTLS(_ key: TLSKey) -> UnsafeMutableRawPointer? { - FlsGetValue(key) -} - -#else - -internal typealias TLSKey = pthread_key_t -internal func makeTLSKey() -> TLSKey { - var raw = pthread_key_t() - guard 0 == pthread_key_create(&raw, nil) else { - fatalError("Unable to create key") - } - return raw -} -internal func setTLS(_ key: TLSKey, _ p: UnsafeMutableRawPointer?) { - guard 0 == pthread_setspecific(key, p) else { - fatalError("Unable to set TLS") - } -} -internal func getTLS(_ key: TLSKey) -> UnsafeMutableRawPointer? { - pthread_getspecific(key) -} -#endif - -private let driverKey: TLSKey = { makeTLSKey() }() +private let driverKey: _PlatformTLSKey = { makeTLSKey() }() internal var currentMockingDriver: MockingDriver? { #if !ENABLE_MOCKING @@ -152,7 +80,7 @@ internal var currentMockingDriver: MockingDriver? { extension MockingDriver { /// Enables mocking for the duration of `f` with a clean trace queue /// Restores prior mocking status and trace queue after execution - public static func withMockingEnabled( + internal static func withMockingEnabled( _ f: (MockingDriver) throws -> () ) rethrows { let priorMocking = currentMockingDriver @@ -179,7 +107,7 @@ private var contextualMockingEnabled: Bool { } extension MockingDriver { - public static var enabled: Bool { mockingEnabled } + internal static var enabled: Bool { mockingEnabled } public static var forceWindowsPaths: Bool { currentMockingDriver?.forceWindowsSyntaxForPaths ?? false @@ -198,7 +126,7 @@ internal var mockingEnabled: Bool { #endif } -@inlinable @inline(__always) +@inline(__always) @inlinable public var forceWindowsPaths: Bool { #if !ENABLE_MOCKING return false @@ -206,3 +134,66 @@ public var forceWindowsPaths: Bool { return MockingDriver.forceWindowsPaths #endif } + + +#if ENABLE_MOCKING +// Strip the mock_system prefix and the arg list suffix +private func originalSyscallName(_ s: String) -> String { + // `function` must be of format `system_()` + precondition(s.starts(with: "system_")) + return String(s.dropFirst("system_".count).prefix { $0.isLetter }) +} + +private func mockImpl( + name: String, + _ args: [AnyHashable] +) -> CInt { + let origName = originalSyscallName(name) + guard let driver = currentMockingDriver else { + fatalError("Mocking requested from non-mocking context") + } + driver.trace.add(Trace.Entry(name: origName, args)) + + switch driver.forceErrno { + case .none: break + case .always(let e): + system_errno = e + return -1 + case .counted(let e, let count): + assert(count >= 1) + system_errno = e + driver.forceErrno = count > 1 ? .counted(errno: e, count: count-1) : .none + return -1 + } + + return 0 +} + +internal func _mock( + name: String = #function, _ args: AnyHashable... +) -> CInt { + precondition(mockingEnabled) + return mockImpl(name: name, args) +} +internal func _mockInt( + name: String = #function, _ args: AnyHashable... +) -> Int { + Int(mockImpl(name: name, args)) +} + +internal func _mockOffT( + name: String = #function, _ args: AnyHashable... +) -> COffT { + COffT(mockImpl(name: name, args)) +} +#endif // ENABLE_MOCKING + +// Force paths to be treated as Windows syntactically if `enabled` is +// true. +internal func _withWindowsPaths(enabled: Bool, _ body: () -> ()) { + guard enabled else { return body() } + MockingDriver.withMockingEnabled { driver in + driver.forceWindowsSyntaxForPaths = true + body() + } +} diff --git a/Sources/SystemInternals/Syscalls.swift b/Sources/SystemInternals/Syscalls.swift index 4fa556cd..22940824 100644 --- a/Sources/SystemInternals/Syscalls.swift +++ b/Sources/SystemInternals/Syscalls.swift @@ -17,58 +17,6 @@ import ucrt #error("Unsupported Platform") #endif -#if ENABLE_MOCKING -// Strip the mock_system prefix and the arg list suffix -private func originalSyscallName(_ function: String) -> String { - // `function` must be of format `system_()` - precondition(function.starts(with: "system_")) - return String(function.dropFirst("system_".count).prefix { $0 != "(" }) -} - -private func mockImpl( - name: String, - _ args: [AnyHashable] -) -> CInt { - let origName = originalSyscallName(name) - guard let driver = currentMockingDriver else { - fatalError("Mocking requested from non-mocking context") - } - driver.trace.add(Trace.Entry(name: origName, args)) - - switch driver.forceErrno { - case .none: break - case .always(let e): - system_errno = e - return -1 - case .counted(let e, let count): - assert(count >= 1) - system_errno = e - driver.forceErrno = count > 1 ? .counted(errno: e, count: count-1) : .none - return -1 - } - - return 0 -} - -private func mock( - name: String = #function, _ args: AnyHashable... -) -> CInt { - precondition(mockingEnabled) - return mockImpl(name: name, args) -} -private func mockInt( - name: String = #function, _ args: AnyHashable... -) -> Int { - Int(mockImpl(name: name, args)) -} - -private func mockOffT( - name: String = #function, _ args: AnyHashable... -) -> off_t { - off_t(mockImpl(name: name, args)) -} -#endif // ENABLE_MOCKING - // Interacting with the mocking system, tracing, etc., is a potentially significant // amount of code size, so we hand outline that code for every syscall @@ -78,7 +26,7 @@ public func system_open( ) -> CInt { #if ENABLE_MOCKING if mockingEnabled { - return mock(String(_errorCorrectingPlatformString: path), oflag) + return _mock(String(_errorCorrectingPlatformString: path), oflag) } #endif return open(path, oflag) @@ -89,7 +37,7 @@ public func system_open( ) -> CInt { #if ENABLE_MOCKING if mockingEnabled { - return mock(String(_errorCorrectingPlatformString: path), oflag, mode) + return _mock(String(_errorCorrectingPlatformString: path), oflag, mode) } #endif return open(path, oflag, mode) @@ -98,7 +46,7 @@ public func system_open( // close public func system_close(_ fd: Int32) -> Int32 { #if ENABLE_MOCKING - if mockingEnabled { return mock(fd) } + if mockingEnabled { return _mock(fd) } #endif return close(fd) } @@ -108,7 +56,7 @@ public func system_read( _ fd: Int32, _ buf: UnsafeMutableRawPointer!, _ nbyte: Int ) -> Int { #if ENABLE_MOCKING - if mockingEnabled { return mockInt(fd, buf, nbyte) } + if mockingEnabled { return _mockInt(fd, buf, nbyte) } #endif return read(fd, buf, nbyte) } @@ -118,7 +66,7 @@ public func system_pread( _ fd: Int32, _ buf: UnsafeMutableRawPointer!, _ nbyte: Int, _ offset: off_t ) -> Int { #if ENABLE_MOCKING - if mockingEnabled { return mockInt(fd, buf, nbyte, offset) } + if mockingEnabled { return _mockInt(fd, buf, nbyte, offset) } #endif return pread(fd, buf, nbyte, offset) } @@ -128,7 +76,7 @@ public func system_lseek( _ fd: Int32, _ off: off_t, _ whence: Int32 ) -> off_t { #if ENABLE_MOCKING - if mockingEnabled { return mockOffT(fd, off, whence) } + if mockingEnabled { return _mockOffT(fd, off, whence) } #endif return lseek(fd, off, whence) } @@ -138,7 +86,7 @@ public func system_write( _ fd: Int32, _ buf: UnsafeRawPointer!, _ nbyte: Int ) -> Int { #if ENABLE_MOCKING - if mockingEnabled { return mockInt(fd, buf, nbyte) } + if mockingEnabled { return _mockInt(fd, buf, nbyte) } #endif return write(fd, buf, nbyte) } @@ -148,21 +96,21 @@ public func system_pwrite( _ fd: Int32, _ buf: UnsafeRawPointer!, _ nbyte: Int, _ offset: off_t ) -> Int { #if ENABLE_MOCKING - if mockingEnabled { return mockInt(fd, buf, nbyte, offset) } + if mockingEnabled { return _mockInt(fd, buf, nbyte, offset) } #endif return pwrite(fd, buf, nbyte, offset) } public func system_dup(_ fd: Int32) -> Int32 { #if ENABLE_MOCKING - if mockingEnabled { return mock(fd) } + if mockingEnabled { return _mock(fd) } #endif return dup(fd) } public func system_dup2(_ fd: Int32, _ fd2: Int32) -> Int32 { #if ENABLE_MOCKING - if mockingEnabled { return mock(fd, fd2) } + if mockingEnabled { return _mock(fd, fd2) } #endif return dup2(fd, fd2) } diff --git a/Tests/SystemTests/TestingInfrastructure.swift b/Tests/SystemTests/TestingInfrastructure.swift index 11a4dff6..fa44bb0b 100644 --- a/Tests/SystemTests/TestingInfrastructure.swift +++ b/Tests/SystemTests/TestingInfrastructure.swift @@ -8,7 +8,8 @@ */ import XCTest -import SystemInternals + +@testable import SystemInternals #if SYSTEM_PACKAGE import SystemPackage @@ -177,13 +178,6 @@ internal struct MockTestCase: TestCase { } } -// Force paths to be treated as Windows syntactically if `enabled` is -// true. internal func withWindowsPaths(enabled: Bool, _ body: () -> ()) { - guard enabled else { return body() } - MockingDriver.withMockingEnabled { driver in - driver.forceWindowsSyntaxForPaths = true - body() - } + _withWindowsPaths(enabled: enabled, body) } - From d7611ca8addf532fb03871c918604e371c32d49f Mon Sep 17 00:00:00 2001 From: Michael Ilseman Date: Thu, 11 Feb 2021 12:58:28 -0700 Subject: [PATCH 017/427] Internalize SystemInternals Scrap the separate module SystemInternals and instead have it be a subdirectory. Adjust all access control accordingly. One day, when Swift gets proper submodules or supermodules (i.e. resilience domains), we can reinstate it. For now, a separate module makes binary distribution much more complicated. --- Package.swift | 6 +- Sources/System/Errno.swift | 1 - Sources/System/FileOperations.swift | 8 +-- Sources/System/FilePath/FilePathParsing.swift | 1 - .../Internals}/Exports.swift | 48 +++++-------- .../Internals}/Mocking.swift | 12 ++-- .../Internals}/Syscalls.swift | 27 ++++---- .../Internals}/WindowsSyscallAdapters.swift | 8 +-- Sources/System/Platform/Platform.swift | 30 ++++++++- Sources/System/Platform/PlatformString.swift | 67 ++++++------------- Sources/System/SystemString.swift | 1 - Tests/SystemTests/MockingTest.swift | 6 +- Tests/SystemTests/SystemStringTests.swift | 2 - Tests/SystemTests/TestingInfrastructure.swift | 6 +- 14 files changed, 93 insertions(+), 130 deletions(-) rename Sources/{SystemInternals => System/Internals}/Exports.swift (78%) rename Sources/{SystemInternals => System/Internals}/Mocking.swift (96%) rename Sources/{SystemInternals => System/Internals}/Syscalls.swift (82%) rename Sources/{SystemInternals => System/Internals}/WindowsSyscallAdapters.swift (92%) diff --git a/Package.swift b/Package.swift index 561194bc..8ebc98ba 100644 --- a/Package.swift +++ b/Package.swift @@ -15,18 +15,14 @@ import PackageDescription let targets: [PackageDescription.Target] = [ .target( name: "SystemPackage", - dependencies: ["SystemInternals"], - path: "Sources/System"), - .target( - name: "SystemInternals", dependencies: ["CSystem"], + path: "Sources/System", swiftSettings: [ .define("ENABLE_MOCKING", .when(configuration: .debug)) ]), .target( name: "CSystem", dependencies: []), - .testTarget( name: "SystemTests", dependencies: ["SystemPackage"], diff --git a/Sources/System/Errno.swift b/Sources/System/Errno.swift index aeddbc31..d74acef8 100644 --- a/Sources/System/Errno.swift +++ b/Sources/System/Errno.swift @@ -1472,7 +1472,6 @@ extension Errno { #endif } -@_implementationOnly import SystemInternals // @available(macOS 10.16, iOS 14.0, watchOS 7.0, tvOS 14.0, *) extension Errno { // TODO: We want to provide safe access to `errno`, but we need a diff --git a/Sources/System/FileOperations.swift b/Sources/System/FileOperations.swift index 88ec3773..e51a979e 100644 --- a/Sources/System/FileOperations.swift +++ b/Sources/System/FileOperations.swift @@ -7,8 +7,6 @@ See https://swift.org/LICENSE.txt for license information */ -@_implementationOnly import SystemInternals - // @available(macOS 10.16, iOS 14.0, watchOS 7.0, tvOS 14.0, *) extension FileDescriptor { /// Opens or creates a file for reading or writing. @@ -122,7 +120,7 @@ extension FileDescriptor { internal func _seek( offset: Int64, from whence: FileDescriptor.SeekOrigin ) -> Result { - let newOffset = system_lseek(self.rawValue, COffT(offset), whence.rawValue) + let newOffset = system_lseek(self.rawValue, _COffT(offset), whence.rawValue) return valueOrErrno(Int64(newOffset)) } @@ -210,7 +208,7 @@ extension FileDescriptor { retryOnInterrupt: Bool ) -> Result { valueOrErrno(retryOnInterrupt: retryOnInterrupt) { - system_pread(self.rawValue, buffer.baseAddress, buffer.count, COffT(offset)) + system_pread(self.rawValue, buffer.baseAddress, buffer.count, _COffT(offset)) } } @@ -292,7 +290,7 @@ extension FileDescriptor { retryOnInterrupt: Bool ) -> Result { valueOrErrno(retryOnInterrupt: retryOnInterrupt) { - system_pwrite(self.rawValue, buffer.baseAddress, buffer.count, COffT(offset)) + system_pwrite(self.rawValue, buffer.baseAddress, buffer.count, _COffT(offset)) } } diff --git a/Sources/System/FilePath/FilePathParsing.swift b/Sources/System/FilePath/FilePathParsing.swift index e65833a3..26753989 100644 --- a/Sources/System/FilePath/FilePathParsing.swift +++ b/Sources/System/FilePath/FilePathParsing.swift @@ -322,7 +322,6 @@ extension FilePath { } // Whether we are providing Windows paths -@_implementationOnly import var SystemInternals.forceWindowsPaths @inline(__always) internal var _windowsPaths: Bool { #if os(Windows) diff --git a/Sources/SystemInternals/Exports.swift b/Sources/System/Internals/Exports.swift similarity index 78% rename from Sources/SystemInternals/Exports.swift rename to Sources/System/Internals/Exports.swift index 5cd70917..91c9e01a 100644 --- a/Sources/SystemInternals/Exports.swift +++ b/Sources/System/Internals/Exports.swift @@ -15,32 +15,26 @@ import CSystem // TODO: Should CSystem just include all the header files we need? #if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) -import Darwin +@_implementationOnly import Darwin #elseif os(Linux) || os(FreeBSD) || os(Android) -import Glibc +@_implementationOnly import Glibc #elseif os(Windows) -import ucrt +@_implementationOnly import ucrt #else #error("Unsupported Platform") #endif -public typealias COffT = off_t - -#if os(Windows) -public typealias CModeT = CInt -#else -public typealias CModeT = mode_t -#endif +internal typealias _COffT = off_t // MARK: syscalls and variables #if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) -public var system_errno: CInt { +internal var system_errno: CInt { get { Darwin.errno } set { Darwin.errno = newValue } } #elseif os(Windows) -public var system_errno: CInt { +internal var system_errno: CInt { get { var value: CInt = 0 // TODO(compnerd) handle the error? @@ -52,7 +46,7 @@ public var system_errno: CInt { } } #else -public var system_errno: CInt { +internal var system_errno: CInt { get { Glibc.errno } set { Glibc.errno = newValue } } @@ -62,33 +56,21 @@ public var system_errno: CInt { // Convention: `system_foo` is system's wrapper for `foo`. -public func system_strerror(_ __errnum: Int32) -> UnsafeMutablePointer! { +internal func system_strerror(_ __errnum: Int32) -> UnsafeMutablePointer! { strerror(__errnum) } -public func system_strlen(_ s: UnsafePointer) -> Int { +internal func system_strlen(_ s: UnsafePointer) -> Int { strlen(s) } -#if os(Windows) -public typealias _PlatformChar = UInt16 -#else -public typealias _PlatformChar = CChar -#endif -#if os(Windows) -public typealias _PlatformUnicodeEncoding = UTF16 -#else -public typealias _PlatformUnicodeEncoding = UTF8 -#endif - - // Convention: `system_platform_foo` is a // platform-representation-abstracted wrapper around `foo`-like functionality. // Type and layout differences such as the `char` vs `wchar` are abstracted. // // strlen for the platform string -public func system_platform_strlen(_ s: UnsafePointer<_PlatformChar>) -> Int { +internal func system_platform_strlen(_ s: UnsafePointer) -> Int { #if os(Windows) return wcslen(s) #else @@ -98,8 +80,8 @@ public func system_platform_strlen(_ s: UnsafePointer<_PlatformChar>) -> Int { // Interop between String and platfrom string extension String { - public func _withPlatformString( - _ body: (UnsafePointer<_PlatformChar>) throws -> Result + internal func _withPlatformString( + _ body: (UnsafePointer) throws -> Result ) rethrows -> Result { // Need to #if because CChar may be signed #if os(Windows) @@ -109,7 +91,7 @@ extension String { #endif } - public init?(_platformString platformString: UnsafePointer<_PlatformChar>) { + internal init?(_platformString platformString: UnsafePointer) { // Need to #if because CChar may be signed #if os(Windows) guard let strRes = String.decodeCString( @@ -126,8 +108,8 @@ extension String { #endif } - public init( - _errorCorrectingPlatformString platformString: UnsafePointer<_PlatformChar> + internal init( + _errorCorrectingPlatformString platformString: UnsafePointer ) { // Need to #if because CChar may be signed #if os(Windows) diff --git a/Sources/SystemInternals/Mocking.swift b/Sources/System/Internals/Mocking.swift similarity index 96% rename from Sources/SystemInternals/Mocking.swift rename to Sources/System/Internals/Mocking.swift index 41549d4a..3af54935 100644 --- a/Sources/SystemInternals/Mocking.swift +++ b/Sources/System/Internals/Mocking.swift @@ -53,7 +53,7 @@ internal enum ForceErrno: Equatable { } // Provide access to the driver, context, and trace stack of mocking -public class MockingDriver { +internal class MockingDriver { // Record syscalls and their arguments internal var trace = Trace() @@ -109,7 +109,7 @@ private var contextualMockingEnabled: Bool { extension MockingDriver { internal static var enabled: Bool { mockingEnabled } - public static var forceWindowsPaths: Bool { + internal static var forceWindowsPaths: Bool { currentMockingDriver?.forceWindowsSyntaxForPaths ?? false } } @@ -126,8 +126,8 @@ internal var mockingEnabled: Bool { #endif } -@inline(__always) @inlinable -public var forceWindowsPaths: Bool { +@inline(__always) +internal var forceWindowsPaths: Bool { #if !ENABLE_MOCKING return false #else @@ -183,8 +183,8 @@ internal func _mockInt( internal func _mockOffT( name: String = #function, _ args: AnyHashable... -) -> COffT { - COffT(mockImpl(name: name, args)) +) -> _COffT { + _COffT(mockImpl(name: name, args)) } #endif // ENABLE_MOCKING diff --git a/Sources/SystemInternals/Syscalls.swift b/Sources/System/Internals/Syscalls.swift similarity index 82% rename from Sources/SystemInternals/Syscalls.swift rename to Sources/System/Internals/Syscalls.swift index 22940824..e0f2cebd 100644 --- a/Sources/SystemInternals/Syscalls.swift +++ b/Sources/System/Internals/Syscalls.swift @@ -8,11 +8,11 @@ */ #if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) -import Darwin +@_implementationOnly import Darwin #elseif os(Linux) || os(FreeBSD) || os(Android) -import Glibc +@_implementationOnly import Glibc #elseif os(Windows) -import ucrt +@_implementationOnly import ucrt #else #error("Unsupported Platform") #endif @@ -21,8 +21,8 @@ import ucrt // amount of code size, so we hand outline that code for every syscall // open -public func system_open( - _ path: UnsafePointer<_PlatformChar>, _ oflag: Int32 +internal func system_open( + _ path: UnsafePointer, _ oflag: Int32 ) -> CInt { #if ENABLE_MOCKING if mockingEnabled { @@ -32,8 +32,9 @@ public func system_open( return open(path, oflag) } -public func system_open( - _ path: UnsafePointer<_PlatformChar>, _ oflag: Int32, _ mode: CModeT +internal func system_open( + _ path: UnsafePointer, + _ oflag: Int32, _ mode: CInterop.Mode ) -> CInt { #if ENABLE_MOCKING if mockingEnabled { @@ -44,7 +45,7 @@ public func system_open( } // close -public func system_close(_ fd: Int32) -> Int32 { +internal func system_close(_ fd: Int32) -> Int32 { #if ENABLE_MOCKING if mockingEnabled { return _mock(fd) } #endif @@ -52,7 +53,7 @@ public func system_close(_ fd: Int32) -> Int32 { } // read -public func system_read( +internal func system_read( _ fd: Int32, _ buf: UnsafeMutableRawPointer!, _ nbyte: Int ) -> Int { #if ENABLE_MOCKING @@ -62,7 +63,7 @@ public func system_read( } // pread -public func system_pread( +internal func system_pread( _ fd: Int32, _ buf: UnsafeMutableRawPointer!, _ nbyte: Int, _ offset: off_t ) -> Int { #if ENABLE_MOCKING @@ -72,7 +73,7 @@ public func system_pread( } // lseek -public func system_lseek( +internal func system_lseek( _ fd: Int32, _ off: off_t, _ whence: Int32 ) -> off_t { #if ENABLE_MOCKING @@ -82,7 +83,7 @@ public func system_lseek( } // write -public func system_write( +internal func system_write( _ fd: Int32, _ buf: UnsafeRawPointer!, _ nbyte: Int ) -> Int { #if ENABLE_MOCKING @@ -92,7 +93,7 @@ public func system_write( } // pwrite -public func system_pwrite( +internal func system_pwrite( _ fd: Int32, _ buf: UnsafeRawPointer!, _ nbyte: Int, _ offset: off_t ) -> Int { #if ENABLE_MOCKING diff --git a/Sources/SystemInternals/WindowsSyscallAdapters.swift b/Sources/System/Internals/WindowsSyscallAdapters.swift similarity index 92% rename from Sources/SystemInternals/WindowsSyscallAdapters.swift rename to Sources/System/Internals/WindowsSyscallAdapters.swift index f1d74f06..48c28459 100644 --- a/Sources/SystemInternals/WindowsSyscallAdapters.swift +++ b/Sources/System/Internals/WindowsSyscallAdapters.swift @@ -9,12 +9,12 @@ #if os(Windows) -import ucrt -import WinSDK +@_implementationOnly import ucrt +@_implementationOnly import WinSDK @inline(__always) internal func open( - _ path: UnsafePointer<_PlatformChar>, _ oflag: Int32 + _ path: UnsafePointer, _ oflag: Int32 ) -> CInt { var fh: CInt = -1 _ = _wsopen_s(&fh, path, oflag, _SH_DENYNO, _S_IREAD | _S_IWRITE) @@ -23,7 +23,7 @@ internal func open( @inline(__always) internal func open( - _ path: UnsafePointer<_PlatformChar>, _ oflag: Int32, _ mode: CModeT + _ path: UnsafePointer, _ oflag: Int32, _ mode: CModeT ) -> CInt { // TODO(compnerd): Apply read/write permissions var fh: CInt = -1 diff --git a/Sources/System/Platform/Platform.swift b/Sources/System/Platform/Platform.swift index 069f90be..0cad6b6c 100644 --- a/Sources/System/Platform/Platform.swift +++ b/Sources/System/Platform/Platform.swift @@ -7,9 +7,7 @@ See https://swift.org/LICENSE.txt for license information */ -@_implementationOnly import SystemInternals - -// Public typealiases that can't be reexported from SystemInternals +// MARK: - Public typealiases /// The C `mode_t` type. // @available(macOS 10.16, iOS 14.0, watchOS 7.0, tvOS 14.0, *) @@ -32,4 +30,30 @@ public enum CInterop { /// The C `char` type public typealias Char = CChar + + #if os(Windows) + /// The platform's preferred character type. On Unix, this is an 8-bit C + /// `char` (which may be signed or unsigned, depending on platform). On + /// Windows, this is `UInt16` (a "wide" character). + public typealias PlatformChar = UInt16 + #else + /// The platform's preferred character type. On Unix, this is an 8-bit C + /// `char` (which may be signed or unsigned, depending on platform). On + /// Windows, this is `UInt16` (a "wide" character). + public typealias PlatformChar = CInterop.Char + #endif + + #if os(Windows) + /// The platform's preferred Unicode encoding. On Unix this is UTF-8 and on + /// Windows it is UTF-16. Native strings may contain invalid Unicode, + /// which will be handled by either error-correction or failing, depending + /// on API. + public typealias PlatformUnicodeEncoding = UTF16 + #else + /// The platform's preferred Unicode encoding. On Unix this is UTF-8 and on + /// Windows it is UTF-16. Native strings may contain invalid Unicode, + /// which will be handled by either error-correction or failing, depending + /// on API. + public typealias PlatformUnicodeEncoding = UTF8 + #endif } diff --git a/Sources/System/Platform/PlatformString.swift b/Sources/System/Platform/PlatformString.swift index d68575f1..6464869e 100644 --- a/Sources/System/Platform/PlatformString.swift +++ b/Sources/System/Platform/PlatformString.swift @@ -7,54 +7,6 @@ See https://swift.org/LICENSE.txt for license information */ -// @available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *) -extension CInterop { - #if os(Windows) - /// The platform's preferred character type. On Unix, this is an 8-bit C - /// `char` (which may be signed or unsigned, depending on platform). On - /// Windows, this is `UInt16` (a "wide" character). - public typealias PlatformChar = UInt16 - #else - /// The platform's preferred character type. On Unix, this is an 8-bit C - /// `char` (which may be signed or unsigned, depending on platform). On - /// Windows, this is `UInt16` (a "wide" character). - public typealias PlatformChar = CInterop.Char - #endif - - #if os(Windows) - /// The platform's preferred Unicode encoding. On Unix this is UTF-8 and on - /// Windows it is UTF-16. Native strings may contain invalid Unicode, - /// which will be handled by either error-correction or failing, depending - /// on API. - public typealias PlatformUnicodeEncoding = UTF16 - #else - /// The platform's preferred Unicode encoding. On Unix this is UTF-8 and on - /// Windows it is UTF-16. Native strings may contain invalid Unicode, - /// which will be handled by either error-correction or failing, depending - /// on API. - public typealias PlatformUnicodeEncoding = UTF8 - #endif -} - -extension CInterop.PlatformChar { - internal var _platformCodeUnit: CInterop.PlatformUnicodeEncoding.CodeUnit { - #if os(Windows) - return self - #else - return CInterop.PlatformUnicodeEncoding.CodeUnit(bitPattern: self) - #endif - } -} -extension CInterop.PlatformUnicodeEncoding.CodeUnit { - internal var _platformChar: CInterop.PlatformChar { - #if os(Windows) - return self - #else - return CInterop.PlatformChar(bitPattern: self) - #endif - } -} - // @available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *) extension String { /// Creates a string by interpreting the null-terminated platform string as @@ -106,6 +58,25 @@ extension String { } +extension CInterop.PlatformChar { + internal var _platformCodeUnit: CInterop.PlatformUnicodeEncoding.CodeUnit { + #if os(Windows) + return self + #else + return CInterop.PlatformUnicodeEncoding.CodeUnit(bitPattern: self) + #endif + } +} +extension CInterop.PlatformUnicodeEncoding.CodeUnit { + internal var _platformChar: CInterop.PlatformChar { + #if os(Windows) + return self + #else + return CInterop.PlatformChar(bitPattern: self) + #endif + } +} + internal protocol _PlatformStringable { func _withPlatformString( _ body: (UnsafePointer) throws -> Result diff --git a/Sources/System/SystemString.swift b/Sources/System/SystemString.swift index 4ab93099..507c8964 100644 --- a/Sources/System/SystemString.swift +++ b/Sources/System/SystemString.swift @@ -239,7 +239,6 @@ extension SystemString: CustomStringConvertible, CustomDebugStringConvertible { internal var debugDescription: String { description.debugDescription } } -@_implementationOnly import func SystemInternals.system_platform_strlen extension SystemString { /// Creates a system string by copying bytes from a null-terminated platform string. /// diff --git a/Tests/SystemTests/MockingTest.swift b/Tests/SystemTests/MockingTest.swift index b377b090..38545988 100644 --- a/Tests/SystemTests/MockingTest.swift +++ b/Tests/SystemTests/MockingTest.swift @@ -10,13 +10,11 @@ import XCTest #if SYSTEM_PACKAGE -import SystemPackage +@testable import SystemPackage #else -import System +@testable import System #endif -@testable import SystemInternals - // @available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *) final class MockingTest: XCTestCase { func testMocking() { diff --git a/Tests/SystemTests/SystemStringTests.swift b/Tests/SystemTests/SystemStringTests.swift index 2c409fff..bcfe632c 100644 --- a/Tests/SystemTests/SystemStringTests.swift +++ b/Tests/SystemTests/SystemStringTests.swift @@ -42,8 +42,6 @@ import XCTest @testable import System #endif -import SystemInternals - private func makeRaw( _ str: String ) -> [CInterop.PlatformChar] { diff --git a/Tests/SystemTests/TestingInfrastructure.swift b/Tests/SystemTests/TestingInfrastructure.swift index fa44bb0b..53bb12f8 100644 --- a/Tests/SystemTests/TestingInfrastructure.swift +++ b/Tests/SystemTests/TestingInfrastructure.swift @@ -9,12 +9,10 @@ import XCTest -@testable import SystemInternals - #if SYSTEM_PACKAGE -import SystemPackage +@testable import SystemPackage #else -import System +@testable import System #endif // To aid debugging, force failures to fatal error From 0734450867a9e880801699e2d316737281bb6115 Mon Sep 17 00:00:00 2001 From: Michael Ilseman Date: Thu, 11 Feb 2021 15:16:45 -0700 Subject: [PATCH 018/427] Fixup compilation failures for some compilers --- Sources/System/Internals/Exports.swift | 4 ++-- Sources/System/Internals/Mocking.swift | 9 ++++++++- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/Sources/System/Internals/Exports.swift b/Sources/System/Internals/Exports.swift index 91c9e01a..107a74be 100644 --- a/Sources/System/Internals/Exports.swift +++ b/Sources/System/Internals/Exports.swift @@ -160,8 +160,8 @@ internal func setTLS(_ key: _PlatformTLSKey, _ p: UnsafeMutableRawPointer?) { } internal func getTLS(_ key: _PlatformTLSKey) -> UnsafeMutableRawPointer? { #if os(Windows) - FlsGetValue(key) + return FlsGetValue(key) #else - pthread_getspecific(key) + return pthread_getspecific(key) #endif } diff --git a/Sources/System/Internals/Mocking.swift b/Sources/System/Internals/Mocking.swift index 3af54935..4a2a2650 100644 --- a/Sources/System/Internals/Mocking.swift +++ b/Sources/System/Internals/Mocking.swift @@ -191,9 +191,16 @@ internal func _mockOffT( // Force paths to be treated as Windows syntactically if `enabled` is // true. internal func _withWindowsPaths(enabled: Bool, _ body: () -> ()) { - guard enabled else { return body() } + #if ENABLE_MOCKING + guard enabled else { + body() + return + } MockingDriver.withMockingEnabled { driver in driver.forceWindowsSyntaxForPaths = true body() } + #else + body() + #endif } From b9d290dc1ee5bf4493aef2fd58db2b8707eecc23 Mon Sep 17 00:00:00 2001 From: Karoy Lorentey Date: Thu, 18 Feb 2021 16:14:14 -0800 Subject: [PATCH 019/427] Add missing @_implementationOnly attribute --- Sources/System/Internals/Exports.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/System/Internals/Exports.swift b/Sources/System/Internals/Exports.swift index 107a74be..8ce94f39 100644 --- a/Sources/System/Internals/Exports.swift +++ b/Sources/System/Internals/Exports.swift @@ -7,7 +7,7 @@ See https://swift.org/LICENSE.txt for license information */ -import CSystem +@_implementationOnly import CSystem // Internal wrappers and typedefs which help reduce #if littering in System's // code base. From b6558a3a9078a9a41a873c00b92427525937ed8e Mon Sep 17 00:00:00 2001 From: Michael Ilseman Date: Thu, 25 Feb 2021 21:38:12 -0700 Subject: [PATCH 020/427] Normal imports System is not an overlay and will not pull C and platform modules into the user's namespace. Import constants normally rather than redefining them. --- Sources/System/Internals/Constants.swift | 471 +++++++++++++++++ Sources/System/Internals/Exports.swift | 8 +- Sources/System/Internals/Syscalls.swift | 6 +- .../Internals/WindowsSyscallAdapters.swift | 4 +- .../Platform/DarwinPlatformConstants.swift | 427 --------------- .../Platform/LinuxPlatformConstants.swift | 487 ------------------ .../Platform/WindowsPlatformConstants.swift | 328 ------------ 7 files changed, 480 insertions(+), 1251 deletions(-) create mode 100644 Sources/System/Internals/Constants.swift delete mode 100644 Sources/System/Platform/DarwinPlatformConstants.swift delete mode 100644 Sources/System/Platform/LinuxPlatformConstants.swift delete mode 100644 Sources/System/Platform/WindowsPlatformConstants.swift diff --git a/Sources/System/Internals/Constants.swift b/Sources/System/Internals/Constants.swift new file mode 100644 index 00000000..90059268 --- /dev/null +++ b/Sources/System/Internals/Constants.swift @@ -0,0 +1,471 @@ +/* + This source file is part of the Swift System open source project + + Copyright (c) 2020 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 +*/ + +// For platform constants redefined in Swift. We define them here so that +// they can be used anywhere without imports and without confusion to +// unavailable local decls. + +import CSystem +#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) +import Darwin +#elseif os(Linux) || os(FreeBSD) || os(Android) +import Glibc +#elseif os(Windows) +import ucrt +#else +#error("Unsupported Platform") +#endif + +// MARK: errno +#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) +@_alwaysEmitIntoClient +internal var _ERRNO_NOT_USED: CInt { 0 } +#endif + +@_alwaysEmitIntoClient +internal var _EPERM: CInt { EPERM } + +@_alwaysEmitIntoClient +internal var _ENOENT: CInt { ENOENT } + +@_alwaysEmitIntoClient +internal var _ESRCH: CInt { ESRCH } + +@_alwaysEmitIntoClient +internal var _EINTR: CInt { EINTR } + +@_alwaysEmitIntoClient +internal var _EIO: CInt { EIO } + +@_alwaysEmitIntoClient +internal var _ENXIO: CInt { ENXIO } + +@_alwaysEmitIntoClient +internal var _E2BIG: CInt { E2BIG } + +@_alwaysEmitIntoClient +internal var _ENOEXEC: CInt { ENOEXEC } + +@_alwaysEmitIntoClient +internal var _EBADF: CInt { EBADF } + +@_alwaysEmitIntoClient +internal var _ECHILD: CInt { ECHILD } + +@_alwaysEmitIntoClient +internal var _EDEADLK: CInt { EDEADLK } + +@_alwaysEmitIntoClient +internal var _ENOMEM: CInt { ENOMEM } + +@_alwaysEmitIntoClient +internal var _EACCES: CInt { EACCES } + +@_alwaysEmitIntoClient +internal var _EFAULT: CInt { EFAULT } + +#if !os(Windows) +@_alwaysEmitIntoClient +internal var _ENOTBLK: CInt { ENOTBLK } +#endif + +@_alwaysEmitIntoClient +internal var _EBUSY: CInt { EBUSY } + +@_alwaysEmitIntoClient +internal var _EEXIST: CInt { EEXIST } + +@_alwaysEmitIntoClient +internal var _EXDEV: CInt { EXDEV } + +@_alwaysEmitIntoClient +internal var _ENODEV: CInt { ENODEV } + +@_alwaysEmitIntoClient +internal var _ENOTDIR: CInt { ENOTDIR } + +@_alwaysEmitIntoClient +internal var _EISDIR: CInt { EISDIR } + +@_alwaysEmitIntoClient +internal var _EINVAL: CInt { EINVAL } + +@_alwaysEmitIntoClient +internal var _ENFILE: CInt { ENFILE } + +@_alwaysEmitIntoClient +internal var _EMFILE: CInt { EMFILE } + +#if !os(Windows) +@_alwaysEmitIntoClient +internal var _ENOTTY: CInt { ENOTTY } + +@_alwaysEmitIntoClient +internal var _ETXTBSY: CInt { ETXTBSY } +#endif + +@_alwaysEmitIntoClient +internal var _EFBIG: CInt { EFBIG } + +@_alwaysEmitIntoClient +internal var _ENOSPC: CInt { ENOSPC } + +@_alwaysEmitIntoClient +internal var _ESPIPE: CInt { ESPIPE } + +@_alwaysEmitIntoClient +internal var _EROFS: CInt { EROFS } + +@_alwaysEmitIntoClient +internal var _EMLINK: CInt { EMLINK } + +@_alwaysEmitIntoClient +internal var _EPIPE: CInt { EPIPE } + +@_alwaysEmitIntoClient +internal var _EDOM: CInt { EDOM } + +@_alwaysEmitIntoClient +internal var _ERANGE: CInt { ERANGE } + +@_alwaysEmitIntoClient +internal var _EAGAIN: CInt { EAGAIN } + +@_alwaysEmitIntoClient +internal var _EWOULDBLOCK: CInt { EWOULDBLOCK } + +@_alwaysEmitIntoClient +internal var _EINPROGRESS: CInt { EINPROGRESS } + +@_alwaysEmitIntoClient +internal var _EALREADY: CInt { EALREADY } + +@_alwaysEmitIntoClient +internal var _ENOTSOCK: CInt { ENOTSOCK } + +@_alwaysEmitIntoClient +internal var _EDESTADDRREQ: CInt { EDESTADDRREQ } + +@_alwaysEmitIntoClient +internal var _EMSGSIZE: CInt { EMSGSIZE } + +@_alwaysEmitIntoClient +internal var _EPROTOTYPE: CInt { EPROTOTYPE } + +@_alwaysEmitIntoClient +internal var _ENOPROTOOPT: CInt { ENOPROTOOPT } + +@_alwaysEmitIntoClient +internal var _EPROTONOSUPPORT: CInt { EPROTONOSUPPORT } + +@_alwaysEmitIntoClient +internal var _ESOCKTNOSUPPORT: CInt { ESOCKTNOSUPPORT } + +#if !os(Windows) +@_alwaysEmitIntoClient +internal var _ENOTSUP: CInt { ENOTSUP } +#endif + +@_alwaysEmitIntoClient +internal var _EPFNOSUPPORT: CInt { EPFNOSUPPORT } + +@_alwaysEmitIntoClient +internal var _EAFNOSUPPORT: CInt { EAFNOSUPPORT } + +@_alwaysEmitIntoClient +internal var _EADDRINUSE: CInt { EADDRINUSE } + +@_alwaysEmitIntoClient +internal var _EADDRNOTAVAIL: CInt { EADDRNOTAVAIL } + +@_alwaysEmitIntoClient +internal var _ENETDOWN: CInt { ENETDOWN } + +@_alwaysEmitIntoClient +internal var _ENETUNREACH: CInt { ENETUNREACH } + +@_alwaysEmitIntoClient +internal var _ENETRESET: CInt { ENETRESET } + +@_alwaysEmitIntoClient +internal var _ECONNABORTED: CInt { ECONNABORTED } + +@_alwaysEmitIntoClient +internal var _ECONNRESET: CInt { ECONNRESET } + +@_alwaysEmitIntoClient +internal var _ENOBUFS: CInt { ENOBUFS } + +@_alwaysEmitIntoClient +internal var _EISCONN: CInt { EISCONN } + +@_alwaysEmitIntoClient +internal var _ENOTCONN: CInt { ENOTCONN } + +@_alwaysEmitIntoClient +internal var _ESHUTDOWN: CInt { ESHUTDOWN } + +@_alwaysEmitIntoClient +internal var _ETOOMANYREFS: CInt { ETOOMANYREFS } + +@_alwaysEmitIntoClient +internal var _ETIMEDOUT: CInt { ETIMEDOUT } + +@_alwaysEmitIntoClient +internal var _ECONNREFUSED: CInt { ECONNREFUSED } + +@_alwaysEmitIntoClient +internal var _ELOOP: CInt { ELOOP } + +@_alwaysEmitIntoClient +internal var _ENAMETOOLONG: CInt { ENAMETOOLONG } + +@_alwaysEmitIntoClient +internal var _EHOSTDOWN: CInt { EHOSTDOWN } + +@_alwaysEmitIntoClient +internal var _EHOSTUNREACH: CInt { EHOSTUNREACH } + +@_alwaysEmitIntoClient +internal var _ENOTEMPTY: CInt { ENOTEMPTY } + +#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) +@_alwaysEmitIntoClient +internal var _EPROCLIM: CInt { EPROCLIM } +#endif + +@_alwaysEmitIntoClient +internal var _EUSERS: CInt { EUSERS } + +@_alwaysEmitIntoClient +internal var _EDQUOT: CInt { EDQUOT } + +@_alwaysEmitIntoClient +internal var _ESTALE: CInt { ESTALE } + +@_alwaysEmitIntoClient +internal var _EREMOTE: CInt { EREMOTE } + +#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) +@_alwaysEmitIntoClient +internal var _EBADRPC: CInt { EBADRPC } + +@_alwaysEmitIntoClient +internal var _ERPCMISMATCH: CInt { ERPCMISMATCH } + +@_alwaysEmitIntoClient +internal var _EPROGUNAVAIL: CInt { EPROGUNAVAIL } + +@_alwaysEmitIntoClient +internal var _EPROGMISMATCH: CInt { EPROGMISMATCH } + +@_alwaysEmitIntoClient +internal var _EPROCUNAVAIL: CInt { EPROCUNAVAIL } +#endif + +@_alwaysEmitIntoClient +internal var _ENOLCK: CInt { ENOLCK } + +@_alwaysEmitIntoClient +internal var _ENOSYS: CInt { ENOSYS } + +#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) +@_alwaysEmitIntoClient +internal var _EFTYPE: CInt { EFTYPE } + +@_alwaysEmitIntoClient +internal var _EAUTH: CInt { EAUTH } + +@_alwaysEmitIntoClient +internal var _ENEEDAUTH: CInt { ENEEDAUTH } + +@_alwaysEmitIntoClient +internal var _EPWROFF: CInt { EPWROFF } + +@_alwaysEmitIntoClient +internal var _EDEVERR: CInt { EDEVERR } +#endif + +#if !os(Windows) +@_alwaysEmitIntoClient +internal var _EOVERFLOW: CInt { EOVERFLOW } +#endif + +#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) +@_alwaysEmitIntoClient +internal var _EBADEXEC: CInt { EBADEXEC } + +@_alwaysEmitIntoClient +internal var _EBADARCH: CInt { EBADARCH } + +@_alwaysEmitIntoClient +internal var _ESHLIBVERS: CInt { ESHLIBVERS } + +@_alwaysEmitIntoClient +internal var _EBADMACHO: CInt { EBADMACHO } +#endif + +@_alwaysEmitIntoClient +internal var _ECANCELED: CInt { ECANCELED } + +#if !os(Windows) +@_alwaysEmitIntoClient +internal var _EIDRM: CInt { EIDRM } + +@_alwaysEmitIntoClient +internal var _ENOMSG: CInt { ENOMSG } +#endif + +@_alwaysEmitIntoClient +internal var _EILSEQ: CInt { EILSEQ } + +#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) +@_alwaysEmitIntoClient +internal var _ENOATTR: CInt { ENOATTR } +#endif + +#if !os(Windows) +@_alwaysEmitIntoClient +internal var _EBADMSG: CInt { EBADMSG } + +@_alwaysEmitIntoClient +internal var _EMULTIHOP: CInt { EMULTIHOP } + +@_alwaysEmitIntoClient +internal var _ENODATA: CInt { ENODATA } + +@_alwaysEmitIntoClient +internal var _ENOLINK: CInt { ENOLINK } + +@_alwaysEmitIntoClient +internal var _ENOSR: CInt { ENOSR } + +@_alwaysEmitIntoClient +internal var _ENOSTR: CInt { ENOSTR } + +@_alwaysEmitIntoClient +internal var _EPROTO: CInt { EPROTO } + +@_alwaysEmitIntoClient +internal var _ETIME: CInt { ETIME } +#endif + +@_alwaysEmitIntoClient +internal var _EOPNOTSUPP: CInt { EOPNOTSUPP } + +#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) +@_alwaysEmitIntoClient +internal var _ENOPOLICY: CInt { ENOPOLICY } +#endif + +#if !os(Windows) +@_alwaysEmitIntoClient +internal var _ENOTRECOVERABLE: CInt { ENOTRECOVERABLE } + +@_alwaysEmitIntoClient +internal var _EOWNERDEAD: CInt { EOWNERDEAD } +#endif + +#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) +@_alwaysEmitIntoClient +internal var _EQFULL: CInt { EQFULL } + +@_alwaysEmitIntoClient +internal var _ELAST: CInt { ELAST } +#endif + +// MARK: File Operations + +@_alwaysEmitIntoClient +internal var _O_RDONLY: CInt { O_RDONLY } + +@_alwaysEmitIntoClient +internal var _O_WRONLY: CInt { O_WRONLY } + +@_alwaysEmitIntoClient +internal var _O_RDWR: CInt { O_RDWR } + +// TODO: API? +@_alwaysEmitIntoClient +internal var _O_ACCMODE: CInt { O_ACCMODE } + +#if !os(Windows) +@_alwaysEmitIntoClient +internal var _O_NONBLOCK: CInt { O_NONBLOCK } +#endif + +@_alwaysEmitIntoClient +internal var _O_APPEND: CInt { O_APPEND } + +#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) +@_alwaysEmitIntoClient +internal var _O_SHLOCK: CInt { O_SHLOCK } + +@_alwaysEmitIntoClient +internal var _O_EXLOCK: CInt { O_EXLOCK } +#endif + +// TODO: API? +@_alwaysEmitIntoClient +internal var _O_ASYNC: CInt { O_ASYNC } + +#if !os(Windows) +@_alwaysEmitIntoClient +internal var _O_NOFOLLOW: CInt { O_NOFOLLOW } +#endif + +@_alwaysEmitIntoClient +internal var _O_CREAT: CInt { O_CREAT } + +@_alwaysEmitIntoClient +internal var _O_TRUNC: CInt { O_TRUNC } + +@_alwaysEmitIntoClient +internal var _O_EXCL: CInt { O_EXCL } + +#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) +@_alwaysEmitIntoClient +internal var _O_EVTONLY: CInt { O_EVTONLY } +#endif + +// TODO: API? +@_alwaysEmitIntoClient +internal var _O_NOCTTY: CInt { O_NOCTTY } + +// TODO: API? +@_alwaysEmitIntoClient +internal var _O_DIRECTORY: CInt { O_DIRECTORY } + +#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) +@_alwaysEmitIntoClient +internal var _O_SYMLINK: CInt { O_SYMLINK } +#endif + +#if !os(Windows) +@_alwaysEmitIntoClient +internal var _O_CLOEXEC: CInt { O_CLOEXEC } +#endif + +@_alwaysEmitIntoClient +internal var _SEEK_SET: CInt { SEEK_SET } + +@_alwaysEmitIntoClient +internal var _SEEK_CUR: CInt { SEEK_CUR } + +@_alwaysEmitIntoClient +internal var _SEEK_END: CInt { SEEK_END } + +#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) +@_alwaysEmitIntoClient +internal var _SEEK_HOLE: CInt { SEEK_HOLE } + +@_alwaysEmitIntoClient +internal var _SEEK_DATA: CInt { SEEK_DATA } +#endif + diff --git a/Sources/System/Internals/Exports.swift b/Sources/System/Internals/Exports.swift index 8ce94f39..6d35e405 100644 --- a/Sources/System/Internals/Exports.swift +++ b/Sources/System/Internals/Exports.swift @@ -7,7 +7,7 @@ See https://swift.org/LICENSE.txt for license information */ -@_implementationOnly import CSystem +import CSystem // Internal wrappers and typedefs which help reduce #if littering in System's // code base. @@ -15,11 +15,11 @@ // TODO: Should CSystem just include all the header files we need? #if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) -@_implementationOnly import Darwin +import Darwin #elseif os(Linux) || os(FreeBSD) || os(Android) -@_implementationOnly import Glibc +import Glibc #elseif os(Windows) -@_implementationOnly import ucrt +import ucrt #else #error("Unsupported Platform") #endif diff --git a/Sources/System/Internals/Syscalls.swift b/Sources/System/Internals/Syscalls.swift index e0f2cebd..e4897cc1 100644 --- a/Sources/System/Internals/Syscalls.swift +++ b/Sources/System/Internals/Syscalls.swift @@ -8,11 +8,11 @@ */ #if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) -@_implementationOnly import Darwin +import Darwin #elseif os(Linux) || os(FreeBSD) || os(Android) -@_implementationOnly import Glibc +import Glibc #elseif os(Windows) -@_implementationOnly import ucrt +import ucrt #else #error("Unsupported Platform") #endif diff --git a/Sources/System/Internals/WindowsSyscallAdapters.swift b/Sources/System/Internals/WindowsSyscallAdapters.swift index 48c28459..5aa075be 100644 --- a/Sources/System/Internals/WindowsSyscallAdapters.swift +++ b/Sources/System/Internals/WindowsSyscallAdapters.swift @@ -9,8 +9,8 @@ #if os(Windows) -@_implementationOnly import ucrt -@_implementationOnly import WinSDK +import ucrt +import WinSDK @inline(__always) internal func open( diff --git a/Sources/System/Platform/DarwinPlatformConstants.swift b/Sources/System/Platform/DarwinPlatformConstants.swift deleted file mode 100644 index 305fd356..00000000 --- a/Sources/System/Platform/DarwinPlatformConstants.swift +++ /dev/null @@ -1,427 +0,0 @@ -/* - This source file is part of the Swift System open source project - - Copyright (c) 2020 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 -*/ - -#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) - -// For platform constants redefined in Swift. We redefine them here so that they -// can be @_alwaysEmitIntoClient without depending on Darwin or re-including a -// header (and applying attributes). - -// MARK: errno -@_alwaysEmitIntoClient -internal var _ERRNO_NOT_USED: CInt { 0 } - -@_alwaysEmitIntoClient -internal var _EPERM: CInt { 1 } - -@_alwaysEmitIntoClient -internal var _ENOENT: CInt { 2 } - -@_alwaysEmitIntoClient -internal var _ESRCH: CInt { 3 } - -@_alwaysEmitIntoClient -internal var _EINTR: CInt { 4 } - -@_alwaysEmitIntoClient -internal var _EIO: CInt { 5 } - -@_alwaysEmitIntoClient -internal var _ENXIO: CInt { 6 } - -@_alwaysEmitIntoClient -internal var _E2BIG: CInt { 7 } - -@_alwaysEmitIntoClient -internal var _ENOEXEC: CInt { 8 } - -@_alwaysEmitIntoClient -internal var _EBADF: CInt { 9 } - -@_alwaysEmitIntoClient -internal var _ECHILD: CInt { 10 } - -@_alwaysEmitIntoClient -internal var _EDEADLK: CInt { 11 } - -@_alwaysEmitIntoClient -internal var _ENOMEM: CInt { 12 } - -@_alwaysEmitIntoClient -internal var _EACCES: CInt { 13 } - -@_alwaysEmitIntoClient -internal var _EFAULT: CInt { 14 } - -@_alwaysEmitIntoClient -internal var _ENOTBLK: CInt { 15 } - -@_alwaysEmitIntoClient -internal var _EBUSY: CInt { 16 } - -@_alwaysEmitIntoClient -internal var _EEXIST: CInt { 17 } - -@_alwaysEmitIntoClient -internal var _EXDEV: CInt { 18 } - -@_alwaysEmitIntoClient -internal var _ENODEV: CInt { 19 } - -@_alwaysEmitIntoClient -internal var _ENOTDIR: CInt { 20 } - -@_alwaysEmitIntoClient -internal var _EISDIR: CInt { 21 } - -@_alwaysEmitIntoClient -internal var _EINVAL: CInt { 22 } - -@_alwaysEmitIntoClient -internal var _ENFILE: CInt { 23 } - -@_alwaysEmitIntoClient -internal var _EMFILE: CInt { 24 } - -@_alwaysEmitIntoClient -internal var _ENOTTY: CInt { 25 } - -@_alwaysEmitIntoClient -internal var _ETXTBSY: CInt { 26 } - -@_alwaysEmitIntoClient -internal var _EFBIG: CInt { 27 } - -@_alwaysEmitIntoClient -internal var _ENOSPC: CInt { 28 } - -@_alwaysEmitIntoClient -internal var _ESPIPE: CInt { 29 } - -@_alwaysEmitIntoClient -internal var _EROFS: CInt { 30 } - -@_alwaysEmitIntoClient -internal var _EMLINK: CInt { 31 } - -@_alwaysEmitIntoClient -internal var _EPIPE: CInt { 32 } - -@_alwaysEmitIntoClient -internal var _EDOM: CInt { 33 } - -@_alwaysEmitIntoClient -internal var _ERANGE: CInt { 34 } - -@_alwaysEmitIntoClient -internal var _EAGAIN: CInt { 35 } - -@_alwaysEmitIntoClient -internal var _EWOULDBLOCK: CInt { _EAGAIN } - -@_alwaysEmitIntoClient -internal var _EINPROGRESS: CInt { 36 } - -@_alwaysEmitIntoClient -internal var _EALREADY: CInt { 37 } - -@_alwaysEmitIntoClient -internal var _ENOTSOCK: CInt { 38 } - -@_alwaysEmitIntoClient -internal var _EDESTADDRREQ: CInt { 39 } - -@_alwaysEmitIntoClient -internal var _EMSGSIZE: CInt { 40 } - -@_alwaysEmitIntoClient -internal var _EPROTOTYPE: CInt { 41 } - -@_alwaysEmitIntoClient -internal var _ENOPROTOOPT: CInt { 42 } - -@_alwaysEmitIntoClient -internal var _EPROTONOSUPPORT: CInt { 43 } - -@_alwaysEmitIntoClient -internal var _ESOCKTNOSUPPORT: CInt { 44 } - -@_alwaysEmitIntoClient -internal var _ENOTSUP: CInt { 45 } - -@_alwaysEmitIntoClient -internal var _EPFNOSUPPORT: CInt { 46 } - -@_alwaysEmitIntoClient -internal var _EAFNOSUPPORT: CInt { 47 } - -@_alwaysEmitIntoClient -internal var _EADDRINUSE: CInt { 48 } - -@_alwaysEmitIntoClient -internal var _EADDRNOTAVAIL: CInt { 49 } - -@_alwaysEmitIntoClient -internal var _ENETDOWN: CInt { 50 } - -@_alwaysEmitIntoClient -internal var _ENETUNREACH: CInt { 51 } - -@_alwaysEmitIntoClient -internal var _ENETRESET: CInt { 52 } - -@_alwaysEmitIntoClient -internal var _ECONNABORTED: CInt { 53 } - -@_alwaysEmitIntoClient -internal var _ECONNRESET: CInt { 54 } - -@_alwaysEmitIntoClient -internal var _ENOBUFS: CInt { 55 } - -@_alwaysEmitIntoClient -internal var _EISCONN: CInt { 56 } - -@_alwaysEmitIntoClient -internal var _ENOTCONN: CInt { 57 } - -@_alwaysEmitIntoClient -internal var _ESHUTDOWN: CInt { 58 } - -@_alwaysEmitIntoClient -internal var _ETOOMANYREFS: CInt { 59 } - -@_alwaysEmitIntoClient -internal var _ETIMEDOUT: CInt { 60 } - -@_alwaysEmitIntoClient -internal var _ECONNREFUSED: CInt { 61 } - -@_alwaysEmitIntoClient -internal var _ELOOP: CInt { 62 } - -@_alwaysEmitIntoClient -internal var _ENAMETOOLONG: CInt { 63 } - -@_alwaysEmitIntoClient -internal var _EHOSTDOWN: CInt { 64 } - -@_alwaysEmitIntoClient -internal var _EHOSTUNREACH: CInt { 65 } - -@_alwaysEmitIntoClient -internal var _ENOTEMPTY: CInt { 66 } - -@_alwaysEmitIntoClient -internal var _EPROCLIM: CInt { 67 } - -@_alwaysEmitIntoClient -internal var _EUSERS: CInt { 68 } - -@_alwaysEmitIntoClient -internal var _EDQUOT: CInt { 69 } - -@_alwaysEmitIntoClient -internal var _ESTALE: CInt { 70 } - -@_alwaysEmitIntoClient -internal var _EREMOTE: CInt { 71 } - -@_alwaysEmitIntoClient -internal var _EBADRPC: CInt { 72 } - -@_alwaysEmitIntoClient -internal var _ERPCMISMATCH: CInt { 73 } - -@_alwaysEmitIntoClient -internal var _EPROGUNAVAIL: CInt { 74 } - -@_alwaysEmitIntoClient -internal var _EPROGMISMATCH: CInt { 75 } - -@_alwaysEmitIntoClient -internal var _EPROCUNAVAIL: CInt { 76 } - -@_alwaysEmitIntoClient -internal var _ENOLCK: CInt { 77 } - -@_alwaysEmitIntoClient -internal var _ENOSYS: CInt { 78 } - -@_alwaysEmitIntoClient -internal var _EFTYPE: CInt { 79 } - -@_alwaysEmitIntoClient -internal var _EAUTH: CInt { 80 } - -@_alwaysEmitIntoClient -internal var _ENEEDAUTH: CInt { 81 } - -@_alwaysEmitIntoClient -internal var _EPWROFF: CInt { 82 } - -@_alwaysEmitIntoClient -internal var _EDEVERR: CInt { 83 } - -@_alwaysEmitIntoClient -internal var _EOVERFLOW: CInt { 84 } - -@_alwaysEmitIntoClient -internal var _EBADEXEC: CInt { 85 } - -@_alwaysEmitIntoClient -internal var _EBADARCH: CInt { 86 } - -@_alwaysEmitIntoClient -internal var _ESHLIBVERS: CInt { 87 } - -@_alwaysEmitIntoClient -internal var _EBADMACHO: CInt { 88 } - -@_alwaysEmitIntoClient -internal var _ECANCELED: CInt { 89 } - -@_alwaysEmitIntoClient -internal var _EIDRM: CInt { 90 } - -@_alwaysEmitIntoClient -internal var _ENOMSG: CInt { 91 } - -@_alwaysEmitIntoClient -internal var _EILSEQ: CInt { 92 } - -@_alwaysEmitIntoClient -internal var _ENOATTR: CInt { 93 } - -@_alwaysEmitIntoClient -internal var _EBADMSG: CInt { 94 } - -@_alwaysEmitIntoClient -internal var _EMULTIHOP: CInt { 95 } - -@_alwaysEmitIntoClient -internal var _ENODATA: CInt { 96 } - -@_alwaysEmitIntoClient -internal var _ENOLINK: CInt { 97 } - -@_alwaysEmitIntoClient -internal var _ENOSR: CInt { 98 } - -@_alwaysEmitIntoClient -internal var _ENOSTR: CInt { 99 } - -@_alwaysEmitIntoClient -internal var _EPROTO: CInt { 100 } - -@_alwaysEmitIntoClient -internal var _ETIME: CInt { 101 } - -@_alwaysEmitIntoClient -internal var _EOPNOTSUPP: CInt { 102 } - -@_alwaysEmitIntoClient -internal var _ENOPOLICY: CInt { 103 } - -@_alwaysEmitIntoClient -internal var _ENOTRECOVERABLE: CInt { 104 } - -@_alwaysEmitIntoClient -internal var _EOWNERDEAD: CInt { 105 } - -@_alwaysEmitIntoClient -internal var _EQFULL: CInt { 106 } - -@_alwaysEmitIntoClient -internal var _ELAST: CInt { 106 } - -// MARK: File Operations - -@_alwaysEmitIntoClient -internal var _O_RDONLY: CInt { 0x0000 } - -@_alwaysEmitIntoClient -internal var _O_WRONLY: CInt { 0x0001 } - -@_alwaysEmitIntoClient -internal var _O_RDWR: CInt { 0x0002 } - -// TODO: API? -@_alwaysEmitIntoClient -internal var _O_ACCMODE: CInt { 0x0003 } - -@_alwaysEmitIntoClient -internal var _O_NONBLOCK: CInt { 0x0004 } - -@_alwaysEmitIntoClient -internal var _O_APPEND: CInt { 0x0008 } - -@_alwaysEmitIntoClient -internal var _O_SHLOCK: CInt { 0x0010 } - -@_alwaysEmitIntoClient -internal var _O_EXLOCK: CInt { 0x0020 } - -// TODO: API? -@_alwaysEmitIntoClient -internal var _O_ASYNC: CInt { 0x0040 } - -@_alwaysEmitIntoClient -internal var _O_NOFOLLOW: CInt { 0x0100 } - -@_alwaysEmitIntoClient -internal var _O_CREAT: CInt { 0x0200 } - -@_alwaysEmitIntoClient -internal var _O_TRUNC: CInt { 0x0400 } - -@_alwaysEmitIntoClient -internal var _O_EXCL: CInt { 0x0800 } - -@_alwaysEmitIntoClient -internal var _O_EVTONLY: CInt { 0x8000 } - -// TODO: API? -@_alwaysEmitIntoClient -internal var _O_NOCTTY: CInt { 0x20000 } - -// TODO: API? -@_alwaysEmitIntoClient -internal var _O_DIRECTORY: CInt { 0x100000 } - -@_alwaysEmitIntoClient -internal var _O_SYMLINK: CInt { 0x200000 } - -@_alwaysEmitIntoClient -internal var _O_CLOEXEC: CInt { 0x1000000 } - -// TODO: API? -@_alwaysEmitIntoClient -internal var _O_DP_GETRAWENCRYPTED: CInt { 0x0001 } - -// TODO: API? -@_alwaysEmitIntoClient -internal var _O_DP_GETRAWUNENCRYPTED: CInt { 0x0002 } - -@_alwaysEmitIntoClient -internal var _SEEK_SET: CInt { 0 } - -@_alwaysEmitIntoClient -internal var _SEEK_CUR: CInt { 1 } - -@_alwaysEmitIntoClient -internal var _SEEK_END: CInt { 2 } - -@_alwaysEmitIntoClient -internal var _SEEK_HOLE: CInt { 3 } - -@_alwaysEmitIntoClient -internal var _SEEK_DATA: CInt { 4 } - -#endif diff --git a/Sources/System/Platform/LinuxPlatformConstants.swift b/Sources/System/Platform/LinuxPlatformConstants.swift deleted file mode 100644 index 3a197b94..00000000 --- a/Sources/System/Platform/LinuxPlatformConstants.swift +++ /dev/null @@ -1,487 +0,0 @@ -/* - This source file is part of the Swift System open source project - - Copyright (c) 2020 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 -*/ - -#if os(Linux) - -// Ugh, this is really bad. For Darwin, we can at least rely on -// these values not changing much, but in theory they could change -// per Linux flavor or version (if no ABI) - -// MARK: errno - -@_alwaysEmitIntoClient -internal var _EPERM: CInt { 1 } - -@_alwaysEmitIntoClient -internal var _ENOENT: CInt { 2 } - -@_alwaysEmitIntoClient -internal var _ESRCH: CInt { 3 } - -@_alwaysEmitIntoClient -internal var _EINTR: CInt { 4 } - -@_alwaysEmitIntoClient -internal var _EIO: CInt { 5 } - -@_alwaysEmitIntoClient -internal var _ENXIO: CInt { 6 } - -@_alwaysEmitIntoClient -internal var _E2BIG: CInt { 7 } - -@_alwaysEmitIntoClient -internal var _ENOEXEC: CInt { 8 } - -@_alwaysEmitIntoClient -internal var _EBADF: CInt { 9 } - -@_alwaysEmitIntoClient -internal var _ECHILD: CInt { 10 } - -@_alwaysEmitIntoClient -internal var _EAGAIN: CInt { 11 } - -@_alwaysEmitIntoClient -internal var _ENOMEM: CInt { 12 } - -@_alwaysEmitIntoClient -internal var _EACCES: CInt { 13 } - -@_alwaysEmitIntoClient -internal var _EFAULT: CInt { 14 } - -@_alwaysEmitIntoClient -internal var _ENOTBLK: CInt { 15 } - -@_alwaysEmitIntoClient -internal var _EBUSY: CInt { 16 } - -@_alwaysEmitIntoClient -internal var _EEXIST: CInt { 17 } - -@_alwaysEmitIntoClient -internal var _EXDEV: CInt { 18 } - -@_alwaysEmitIntoClient -internal var _ENODEV: CInt { 19 } - -@_alwaysEmitIntoClient -internal var _ENOTDIR: CInt { 20 } - -@_alwaysEmitIntoClient -internal var _EISDIR: CInt { 21 } - -@_alwaysEmitIntoClient -internal var _EINVAL: CInt { 22 } - -@_alwaysEmitIntoClient -internal var _ENFILE: CInt { 23 } - -@_alwaysEmitIntoClient -internal var _EMFILE: CInt { 24 } - -@_alwaysEmitIntoClient -internal var _ENOTTY: CInt { 25 } - -@_alwaysEmitIntoClient -internal var _ETXTBSY: CInt { 26 } - -@_alwaysEmitIntoClient -internal var _EFBIG: CInt { 27 } - -@_alwaysEmitIntoClient -internal var _ENOSPC: CInt { 28 } - -@_alwaysEmitIntoClient -internal var _ESPIPE: CInt { 29 } - -@_alwaysEmitIntoClient -internal var _EROFS: CInt { 30 } - -@_alwaysEmitIntoClient -internal var _EMLINK: CInt { 31 } - -@_alwaysEmitIntoClient -internal var _EPIPE: CInt { 32 } - -@_alwaysEmitIntoClient -internal var _EDOM: CInt { 33 } - -@_alwaysEmitIntoClient -internal var _ERANGE: CInt { 34 } - -@_alwaysEmitIntoClient -internal var _EDEADLK: CInt { 35 } - -@_alwaysEmitIntoClient -internal var _ENAMETOOLONG: CInt { 36 } - -@_alwaysEmitIntoClient -internal var _ENOLCK: CInt { 37 } - -@_alwaysEmitIntoClient -internal var _ENOSYS: CInt { 38 } - -@_alwaysEmitIntoClient -internal var _ENOTEMPTY: CInt { 39 } - -@_alwaysEmitIntoClient -internal var _ELOOP: CInt { 40 } - -@_alwaysEmitIntoClient -internal var _EWOULDBLOCK: CInt { _EAGAIN } - -@_alwaysEmitIntoClient -internal var _ENOMSG: CInt { 42 } - -@_alwaysEmitIntoClient -internal var _EIDRM: CInt { 43 } - -@_alwaysEmitIntoClient -internal var _ECHRNG: CInt { 44 } - -@_alwaysEmitIntoClient -internal var _EL2NSYNC: CInt { 45 } - -@_alwaysEmitIntoClient -internal var _EL3HLT: CInt { 46 } - -@_alwaysEmitIntoClient -internal var _EL3RST: CInt { 47 } - -@_alwaysEmitIntoClient -internal var _ELNRNG: CInt { 48 } - -@_alwaysEmitIntoClient -internal var _EUNATCH: CInt { 49 } - -@_alwaysEmitIntoClient -internal var _ENOCSI: CInt { 50 } - -@_alwaysEmitIntoClient -internal var _EL2HLT: CInt { 51 } - -@_alwaysEmitIntoClient -internal var _EBADE: CInt { 52 } - -@_alwaysEmitIntoClient -internal var _EBADR: CInt { 53 } - -@_alwaysEmitIntoClient -internal var _EXFULL: CInt { 54 } - -@_alwaysEmitIntoClient -internal var _ENOANO: CInt { 55 } - -@_alwaysEmitIntoClient -internal var _EBADRQC: CInt { 56 } - -@_alwaysEmitIntoClient -internal var _EBADSLT: CInt { 57 } - -@_alwaysEmitIntoClient -internal var _EDEADLOCK: CInt { _EDEADLK } - -@_alwaysEmitIntoClient -internal var _EBFONT: CInt { 59 } - -@_alwaysEmitIntoClient -internal var _ENOSTR: CInt { 60 } - -@_alwaysEmitIntoClient -internal var _ENODATA: CInt { 61 } - -@_alwaysEmitIntoClient -internal var _ETIME: CInt { 62 } - -@_alwaysEmitIntoClient -internal var _ENOSR: CInt { 63 } - -@_alwaysEmitIntoClient -internal var _ENONET: CInt { 64 } - -@_alwaysEmitIntoClient -internal var _ENOPKG: CInt { 65 } - -@_alwaysEmitIntoClient -internal var _EREMOTE: CInt { 66 } - -@_alwaysEmitIntoClient -internal var _ENOLINK: CInt { 67 } - -@_alwaysEmitIntoClient -internal var _EADV: CInt { 68 } - -@_alwaysEmitIntoClient -internal var _ESRMNT: CInt { 69 } - -@_alwaysEmitIntoClient -internal var _ECOMM: CInt { 70 } - -@_alwaysEmitIntoClient -internal var _EPROTO: CInt { 71 } - -@_alwaysEmitIntoClient -internal var _EMULTIHOP: CInt { 72 } - -@_alwaysEmitIntoClient -internal var _EDOTDOT: CInt { 73 } - -@_alwaysEmitIntoClient -internal var _EBADMSG: CInt { 74 } - -@_alwaysEmitIntoClient -internal var _EOVERFLOW: CInt { 75 } - -@_alwaysEmitIntoClient -internal var _ENOTUNIQ: CInt { 76 } - -@_alwaysEmitIntoClient -internal var _EBADFD: CInt { 77 } - -@_alwaysEmitIntoClient -internal var _EREMCHG: CInt { 78 } - -@_alwaysEmitIntoClient -internal var _ELIBACC: CInt { 79 } - -@_alwaysEmitIntoClient -internal var _ELIBBAD: CInt { 80 } - -@_alwaysEmitIntoClient -internal var _ELIBSCN: CInt { 81 } - -@_alwaysEmitIntoClient -internal var _ELIBMAX: CInt { 82 } - -@_alwaysEmitIntoClient -internal var _ELIBEXEC: CInt { 83 } - -@_alwaysEmitIntoClient -internal var _EILSEQ: CInt { 84 } - -@_alwaysEmitIntoClient -internal var _ERESTART: CInt { 85 } - -@_alwaysEmitIntoClient -internal var _ESTRPIPE: CInt { 86 } - -@_alwaysEmitIntoClient -internal var _EUSERS: CInt { 87 } - -@_alwaysEmitIntoClient -internal var _ENOTSOCK: CInt { 88 } - -@_alwaysEmitIntoClient -internal var _EDESTADDRREQ: CInt { 89 } - -@_alwaysEmitIntoClient -internal var _EMSGSIZE: CInt { 90 } - -@_alwaysEmitIntoClient -internal var _EPROTOTYPE: CInt { 91 } - -@_alwaysEmitIntoClient -internal var _ENOPROTOOPT: CInt { 92 } - -@_alwaysEmitIntoClient -internal var _EPROTONOSUPPORT: CInt { 93 } - -@_alwaysEmitIntoClient -internal var _ESOCKTNOSUPPORT: CInt { 94 } - -@_alwaysEmitIntoClient -internal var _EOPNOTSUPP: CInt { 95 } - -@_alwaysEmitIntoClient -internal var _ENOTSUP: CInt { _EOPNOTSUPP } - -@_alwaysEmitIntoClient -internal var _EPFNOSUPPORT: CInt { 96 } - -@_alwaysEmitIntoClient -internal var _EAFNOSUPPORT: CInt { 97 } - -@_alwaysEmitIntoClient -internal var _EADDRINUSE: CInt { 98 } - -@_alwaysEmitIntoClient -internal var _EADDRNOTAVAIL: CInt { 99 } - -@_alwaysEmitIntoClient -internal var _ENETDOWN: CInt { 100 } - -@_alwaysEmitIntoClient -internal var _ENETUNREACH: CInt { 101 } - -@_alwaysEmitIntoClient -internal var _ENETRESET: CInt { 102 } - -@_alwaysEmitIntoClient -internal var _ECONNABORTED: CInt { 103 } - -@_alwaysEmitIntoClient -internal var _ECONNRESET: CInt { 104 } - -@_alwaysEmitIntoClient -internal var _ENOBUFS: CInt { 105 } - -@_alwaysEmitIntoClient -internal var _EISCONN: CInt { 106 } - -@_alwaysEmitIntoClient -internal var _ENOTCONN: CInt { 107 } - -@_alwaysEmitIntoClient -internal var _ESHUTDOWN: CInt { 108 } - -@_alwaysEmitIntoClient -internal var _ETOOMANYREFS: CInt { 109 } - -@_alwaysEmitIntoClient -internal var _ETIMEDOUT: CInt { 110 } - -@_alwaysEmitIntoClient -internal var _ECONNREFUSED: CInt { 111 } - -@_alwaysEmitIntoClient -internal var _EHOSTDOWN: CInt { 112 } - -@_alwaysEmitIntoClient -internal var _EHOSTUNREACH: CInt { 113 } - -@_alwaysEmitIntoClient -internal var _EALREADY: CInt { 114 } - -@_alwaysEmitIntoClient -internal var _EINPROGRESS: CInt { 115 } - -@_alwaysEmitIntoClient -internal var _ESTALE: CInt { 116 } - -@_alwaysEmitIntoClient -internal var _EUCLEAN: CInt { 117 } - -@_alwaysEmitIntoClient -internal var _ENOTNAM: CInt { 118 } - -@_alwaysEmitIntoClient -internal var _ENAVAIL: CInt { 119 } - -@_alwaysEmitIntoClient -internal var _EISNAM: CInt { 120 } - -@_alwaysEmitIntoClient -internal var _EREMOTEIO: CInt { 121 } - -@_alwaysEmitIntoClient -internal var _EDQUOT: CInt { 122 } - -@_alwaysEmitIntoClient -internal var _ENOMEDIUM: CInt { 123 } - -@_alwaysEmitIntoClient -internal var _EMEDIUMTYPE: CInt { 124 } - -@_alwaysEmitIntoClient -internal var _ECANCELED: CInt { 125 } - -@_alwaysEmitIntoClient -internal var _ENOKEY: CInt { 126 } - -@_alwaysEmitIntoClient -internal var _EKEYEXPIRED: CInt { 127 } - -@_alwaysEmitIntoClient -internal var _EKEYREVOKED: CInt { 128 } - -@_alwaysEmitIntoClient -internal var _EKEYREJECTED: CInt { 129 } - -@_alwaysEmitIntoClient -internal var _EOWNERDEAD: CInt { 130 } - -@_alwaysEmitIntoClient -internal var _ENOTRECOVERABLE: CInt { 131 } - -@_alwaysEmitIntoClient -internal var _ERFKILL: CInt { 132 } - -@_alwaysEmitIntoClient -internal var _EHWPOISON: CInt { 133 } - - -// MARK: File Operations - -@_alwaysEmitIntoClient -internal var _O_ACCMODE: CInt { 0o00000003 } - -@_alwaysEmitIntoClient -internal var _O_RDONLY: CInt { 0o00000000 } - -@_alwaysEmitIntoClient -internal var _O_WRONLY: CInt { 0o00000001 } - -@_alwaysEmitIntoClient -internal var _O_RDWR: CInt { 0o00000002 } - -@_alwaysEmitIntoClient -internal var _O_CREAT: CInt { 0o00000100 } - -@_alwaysEmitIntoClient -internal var _O_EXCL: CInt { 0o00000200 } - -@_alwaysEmitIntoClient -internal var _O_NOCTTY: CInt { 0o00000400 } - -@_alwaysEmitIntoClient -internal var _O_TRUNC: CInt { 0o00001000 } - -@_alwaysEmitIntoClient -internal var _O_APPEND: CInt { 0o00002000 } - -@_alwaysEmitIntoClient -internal var _O_NONBLOCK: CInt { 0o00004000 } - -@_alwaysEmitIntoClient -internal var _O_DSYNC: CInt { 0o00010000 } - -@_alwaysEmitIntoClient -internal var _FASYNC: CInt { 0o00020000 } - -@_alwaysEmitIntoClient -internal var _O_DIRECT: CInt { 0o00040000 } - -@_alwaysEmitIntoClient -internal var _O_LARGEFILE: CInt { 0o00100000 } - -@_alwaysEmitIntoClient -internal var _O_DIRECTORY: CInt { 0o00200000 } - -@_alwaysEmitIntoClient -internal var _O_NOFOLLOW: CInt { 0o00400000 } - -@_alwaysEmitIntoClient -internal var _O_NOATIME: CInt { 0o01000000 } - -@_alwaysEmitIntoClient -internal var _O_CLOEXEC: CInt { 0o02000000 } - - -@_alwaysEmitIntoClient -internal var _SEEK_SET: CInt { 0 } - -@_alwaysEmitIntoClient -internal var _SEEK_CUR: CInt { 1 } - -@_alwaysEmitIntoClient -internal var _SEEK_END: CInt { 2 } - -#endif diff --git a/Sources/System/Platform/WindowsPlatformConstants.swift b/Sources/System/Platform/WindowsPlatformConstants.swift deleted file mode 100644 index 7b26eda2..00000000 --- a/Sources/System/Platform/WindowsPlatformConstants.swift +++ /dev/null @@ -1,328 +0,0 @@ -/* - This source file is part of the Swift System open source project - - Copyright (c) 2020 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 -*/ - -#if os(Windows) - -@_alwaysEmitIntoClient -internal var _EPERM: CInt { 1 } - -@_alwaysEmitIntoClient -internal var _ENOENT: CInt { 2 } - -@_alwaysEmitIntoClient -internal var _ESRCH: CInt { 3 } - -@_alwaysEmitIntoClient -internal var _EINTR: CInt { 4 } - -@_alwaysEmitIntoClient -internal var _EIO: CInt { 5 } - -@_alwaysEmitIntoClient -internal var _ENXIO: CInt { 6 } - -@_alwaysEmitIntoClient -internal var _E2BIG: CInt { 7 } - -@_alwaysEmitIntoClient -internal var _ENOEXEC: CInt { 8 } - -@_alwaysEmitIntoClient -internal var _EBADF: CInt { 9 } - -@_alwaysEmitIntoClient -internal var _ECHILD: CInt { 10 } - -@_alwaysEmitIntoClient -internal var _EAGAIN: CInt { 11 } - -@_alwaysEmitIntoClient -internal var _ENOMEM: CInt { 12 } - -@_alwaysEmitIntoClient -internal var _EACCES: CInt { 13 } - -@_alwaysEmitIntoClient -internal var _EFAULT: CInt { 14 } - -@_alwaysEmitIntoClient -internal var _EBUSY: CInt { 16 } - -@_alwaysEmitIntoClient -internal var _EEXIST: CInt { 17 } - -@_alwaysEmitIntoClient -internal var _EXDEV: CInt { 18 } - -@_alwaysEmitIntoClient -internal var _ENODEV: CInt { 19 } - -@_alwaysEmitIntoClient -internal var _ENOTDIR: CInt { 20 } - -@_alwaysEmitIntoClient -internal var _EISDIR: CInt { 21 } - -@_alwaysEmitIntoClient -internal var _EINVAL: CInt { 22 } - -@_alwaysEmitIntoClient -internal var _ENFILE: CInt { 23 } - -@_alwaysEmitIntoClient -internal var _EMFILE: CInt { 24 } - -@_alwaysEmitIntoClient -internal var _ENOTIFY: CInt { 25 } - -@_alwaysEmitIntoClient -internal var _EFBIG: CInt { 27 } - -@_alwaysEmitIntoClient -internal var _ENOSPC: CInt { 28 } - -@_alwaysEmitIntoClient -internal var _ESPIPE: CInt { 29 } - -@_alwaysEmitIntoClient -internal var _EROFS: CInt { 30 } - -@_alwaysEmitIntoClient -internal var _EMLINK: CInt { 31 } - -@_alwaysEmitIntoClient -internal var _EPIPE: CInt { 32 } - -@_alwaysEmitIntoClient -internal var _EDOM: CInt { 33 } - -@_alwaysEmitIntoClient -internal var _ERANGE: CInt { 34 } - -@_alwaysEmitIntoClient -internal var _EDEADLK: CInt { 36 } - -@_alwaysEmitIntoClient -internal var _EDEADLOCK: CInt { 36 } - -@_alwaysEmitIntoClient -internal var _ENAMETOOLONG: CInt { 38 } - -@_alwaysEmitIntoClient -internal var _ENOLCK: CInt { 39 } - -@_alwaysEmitIntoClient -internal var _ENOSYS: CInt { 40 } - -@_alwaysEmitIntoClient -internal var _ENOTEMPTY: CInt { 41 } - -@_alwaysEmitIntoClient -internal var _EILSEQ: CInt { 42 } - -@_alwaysEmitIntoClient -internal var _STRUNCATE: CInt { 80 } - - -@_alwaysEmitIntoClient -internal var _O_RDONLY: CInt { 0x0000 } - -@_alwaysEmitIntoClient -internal var _O_WRONLY: CInt { 0x0001 } - -@_alwaysEmitIntoClient -internal var _O_RDWR: CInt { 0x0002 } - -@_alwaysEmitIntoClient -internal var _O_APPEND: CInt { 0x0008 } - -@_alwaysEmitIntoClient -internal var _O_CREAT: CInt { 0x0100 } - -@_alwaysEmitIntoClient -internal var _O_TRUNC: CInt { 0x0200 } - -@_alwaysEmitIntoClient -internal var _O_EXCL: CInt { 0x0400 } - - -@_alwaysEmitIntoClient -internal var _SEEK_SET: CInt { 0 } - -@_alwaysEmitIntoClient -internal var _SEEK_CUR: CInt { 1 } - -@_alwaysEmitIntoClient -internal var _SEEK_END: CInt { 2 } - - -// WinSock2 - -@_alwaysEmitIntoClient -internal var __EINTR: CInt { 10004 } - -@_alwaysEmitIntoClient -internal var __EBADF: CInt { 10009 } - -@_alwaysEmitIntoClient -internal var __EACCES: CInt { 10013 } - -@_alwaysEmitIntoClient -internal var __EFAULT: CInt { 10014 } - -@_alwaysEmitIntoClient -internal var __EINVAL: CInt { 10022 } - -@_alwaysEmitIntoClient -internal var __EMFILE: CInt { 10024 } - - -@_alwaysEmitIntoClient -internal var _EWOULDBLOCK: CInt { 10035 } - -@_alwaysEmitIntoClient -internal var _EINPROGRESS: CInt { 10036 } - -@_alwaysEmitIntoClient -internal var _EALREADY: CInt { 10037 } - -@_alwaysEmitIntoClient -internal var _ENOTSOCK: CInt { 10038 } - -@_alwaysEmitIntoClient -internal var _EDESTADDRREQ: CInt { 10039 } - -@_alwaysEmitIntoClient -internal var _EMSGSIZE: CInt { 10040 } - -@_alwaysEmitIntoClient -internal var _EPROTOTYPE: CInt { 10041 } - -@_alwaysEmitIntoClient -internal var _ENOPROTOOPT: CInt { 10042 } - -@_alwaysEmitIntoClient -internal var _EPROTONOSUPPORT: CInt { 10043 } - -@_alwaysEmitIntoClient -internal var _ESOCKTNOSUPPORT: CInt { 10044 } - -@_alwaysEmitIntoClient -internal var _EOPNOTSUPP: CInt { 10045 } - -@_alwaysEmitIntoClient -internal var _EPFNOSUPPORT: CInt { 10046 } - -@_alwaysEmitIntoClient -internal var _EAFNOSUPPORT: CInt { 10047 } - -@_alwaysEmitIntoClient -internal var _EADDRINUSE: CInt { 10048 } - -@_alwaysEmitIntoClient -internal var _EADDRNOTAVAIL: CInt { 10049 } - -@_alwaysEmitIntoClient -internal var _ENETDOWN: CInt { 10050 } - -@_alwaysEmitIntoClient -internal var _ENETUNREACH: CInt { 10051 } - -@_alwaysEmitIntoClient -internal var _ENETRESET: CInt { 10052 } - -@_alwaysEmitIntoClient -internal var _ECONNABORTED: CInt { 10053 } - -@_alwaysEmitIntoClient -internal var _ECONNRESET: CInt { 10054 } - -@_alwaysEmitIntoClient -internal var _ENOBUFS: CInt { 10055 } - -@_alwaysEmitIntoClient -internal var _EISCONN: CInt { 10056 } - -@_alwaysEmitIntoClient -internal var _ENOTCONN: CInt { 10057 } - -@_alwaysEmitIntoClient -internal var _ESHUTDOWN: CInt { 10058 } - -@_alwaysEmitIntoClient -internal var _ETOOMANYREFS: CInt { 10059 } - -@_alwaysEmitIntoClient -internal var _ETIMEDOUT: CInt { 10060 } - -@_alwaysEmitIntoClient -internal var _ECONNREFUSED: CInt { 10061 } - -@_alwaysEmitIntoClient -internal var _ELOOP: CInt { 10062 } - -@_alwaysEmitIntoClient -internal var __ENAMETOOLONG: CInt { 10063 } - -@_alwaysEmitIntoClient -internal var _EHOSTDOWN: CInt { 10064 } - -@_alwaysEmitIntoClient -internal var _EHOSTUNREACH: CInt { 10065 } - -@_alwaysEmitIntoClient -internal var __ENOTEMPTY: CInt { 10066 } - -@_alwaysEmitIntoClient -internal var _EPROCLIM: CInt { 10067 } - -@_alwaysEmitIntoClient -internal var _EUSERS: CInt { 10068 } - -@_alwaysEmitIntoClient -internal var _EDQUOT: CInt { 10069 } - -@_alwaysEmitIntoClient -internal var _ESTALE: CInt { 10070 } - -@_alwaysEmitIntoClient -internal var _EREMOTE: CInt { 10071 } - -// WSASYSNOTREADY = 10091 -// WSAVERNOTSUPPORTED = 10092 -// WSANOTINITIALIZED = 10093 - -@_alwaysEmitIntoClient -internal var _EDISCON: CInt { 10101 } - -@_alwaysEmitIntoClient -internal var _ENOMORE: CInt { 10102 } - -@_alwaysEmitIntoClient -internal var _ECANCELED: CInt { 10103 } - -@_alwaysEmitIntoClient -internal var _EINVALIDPROCTABLE: CInt { 10104 } - -@_alwaysEmitIntoClient -internal var _EINVALIDPROVIDER: CInt { 10105 } - -@_alwaysEmitIntoClient -internal var _EPROVIDERFAILEDINIT: CInt { 10106 } - -// WSASYSCALLFAILURE = 10107 -// WSASERVICE_NOT_FOUND = 10108 -// WSATYPE_NOT_FOUND = 10109 -// WSA_E_NO_MORE = 10110 -// WSA_E_CANCELLED = 10111 - -@_alwaysEmitIntoClient -internal var _EREFUSED: CInt { 10112 } - -#endif From bae68a565100b46007d45a05443a9fa1421441ab Mon Sep 17 00:00:00 2001 From: Michael Ilseman Date: Thu, 25 Feb 2021 21:49:27 -0700 Subject: [PATCH 021/427] Use C type in typealias --- Sources/System/Platform/Platform.swift | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/Sources/System/Platform/Platform.swift b/Sources/System/Platform/Platform.swift index 0cad6b6c..06ec50be 100644 --- a/Sources/System/Platform/Platform.swift +++ b/Sources/System/Platform/Platform.swift @@ -14,19 +14,21 @@ @available(*, deprecated, renamed: "CInterop.Mode") public typealias CModeT = CInterop.Mode +import CSystem +#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) +import Darwin +#elseif os(Linux) || os(FreeBSD) || os(Android) +import Glibc +#elseif os(Windows) +import ucrt +#else +#error("Unsupported Platform") +#endif + /// A namespace for C and platform types // @available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *) public enum CInterop { - #if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) - /// The C `mode_t` type. - public typealias Mode = UInt16 - #elseif os(Windows) - /// The C `mode_t` type. - public typealias Mode = Int32 - #else - /// The C `mode_t` type. - public typealias Mode = UInt32 - #endif + public typealias Mode = mode_t /// The C `char` type public typealias Char = CChar From b28263a9104913ae3f3370a862de3e773bd41faa Mon Sep 17 00:00:00 2001 From: Michael Ilseman Date: Fri, 26 Feb 2021 12:59:21 -0700 Subject: [PATCH 022/427] Fixup some merge issues --- Sources/System/Internals/Mocking.swift | 6 +++--- Sources/System/Internals/Syscalls.swift | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Sources/System/Internals/Mocking.swift b/Sources/System/Internals/Mocking.swift index 4a2a2650..b1ca4856 100644 --- a/Sources/System/Internals/Mocking.swift +++ b/Sources/System/Internals/Mocking.swift @@ -138,10 +138,10 @@ internal var forceWindowsPaths: Bool { #if ENABLE_MOCKING // Strip the mock_system prefix and the arg list suffix -private func originalSyscallName(_ s: String) -> String { +private func originalSyscallName(_ function: String) -> String { // `function` must be of format `system_()` - precondition(s.starts(with: "system_")) - return String(s.dropFirst("system_".count).prefix { $0.isLetter }) + precondition(function.starts(with: "system_")) + return String(function.dropFirst("system_".count).prefix { $0 != "(" }) } private func mockImpl( diff --git a/Sources/System/Internals/Syscalls.swift b/Sources/System/Internals/Syscalls.swift index e4897cc1..a755928e 100644 --- a/Sources/System/Internals/Syscalls.swift +++ b/Sources/System/Internals/Syscalls.swift @@ -102,14 +102,14 @@ internal func system_pwrite( return pwrite(fd, buf, nbyte, offset) } -public func system_dup(_ fd: Int32) -> Int32 { +internal func system_dup(_ fd: Int32) -> Int32 { #if ENABLE_MOCKING if mockingEnabled { return _mock(fd) } #endif return dup(fd) } -public func system_dup2(_ fd: Int32, _ fd2: Int32) -> Int32 { +internal func system_dup2(_ fd: Int32, _ fd2: Int32) -> Int32 { #if ENABLE_MOCKING if mockingEnabled { return _mock(fd, fd2) } #endif From 8b989f724f3bbe1d4517ea4a79cf70da79a60217 Mon Sep 17 00:00:00 2001 From: Michael Ilseman Date: Sat, 27 Feb 2021 10:59:37 -0700 Subject: [PATCH 023/427] Do not import CSystem on Darwin --- .../{Platform/Platform.swift => Internals/CInterop.swift} | 3 ++- Sources/System/Internals/Constants.swift | 3 ++- Sources/System/Internals/Exports.swift | 4 ++-- Sources/System/{Platform => }/PlatformString.swift | 0 4 files changed, 6 insertions(+), 4 deletions(-) rename Sources/System/{Platform/Platform.swift => Internals/CInterop.swift} (99%) rename Sources/System/{Platform => }/PlatformString.swift (100%) diff --git a/Sources/System/Platform/Platform.swift b/Sources/System/Internals/CInterop.swift similarity index 99% rename from Sources/System/Platform/Platform.swift rename to Sources/System/Internals/CInterop.swift index 06ec50be..892a2d9a 100644 --- a/Sources/System/Platform/Platform.swift +++ b/Sources/System/Internals/CInterop.swift @@ -14,12 +14,13 @@ @available(*, deprecated, renamed: "CInterop.Mode") public typealias CModeT = CInterop.Mode -import CSystem #if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) import Darwin #elseif os(Linux) || os(FreeBSD) || os(Android) +import CSystem import Glibc #elseif os(Windows) +import CSystem import ucrt #else #error("Unsupported Platform") diff --git a/Sources/System/Internals/Constants.swift b/Sources/System/Internals/Constants.swift index 90059268..22bdf488 100644 --- a/Sources/System/Internals/Constants.swift +++ b/Sources/System/Internals/Constants.swift @@ -11,12 +11,13 @@ // they can be used anywhere without imports and without confusion to // unavailable local decls. -import CSystem #if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) import Darwin #elseif os(Linux) || os(FreeBSD) || os(Android) +import CSystem import Glibc #elseif os(Windows) +import CSystem import ucrt #else #error("Unsupported Platform") diff --git a/Sources/System/Internals/Exports.swift b/Sources/System/Internals/Exports.swift index 6d35e405..5ed7d4e3 100644 --- a/Sources/System/Internals/Exports.swift +++ b/Sources/System/Internals/Exports.swift @@ -7,8 +7,6 @@ See https://swift.org/LICENSE.txt for license information */ -import CSystem - // Internal wrappers and typedefs which help reduce #if littering in System's // code base. @@ -17,8 +15,10 @@ import CSystem #if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) import Darwin #elseif os(Linux) || os(FreeBSD) || os(Android) +import CSystem import Glibc #elseif os(Windows) +import CSystem import ucrt #else #error("Unsupported Platform") diff --git a/Sources/System/Platform/PlatformString.swift b/Sources/System/PlatformString.swift similarity index 100% rename from Sources/System/Platform/PlatformString.swift rename to Sources/System/PlatformString.swift From 8031239cef9dd239b7f2d7aa1db88f5481a46c22 Mon Sep 17 00:00:00 2001 From: Michael Ilseman Date: Sun, 14 Mar 2021 17:48:45 -0600 Subject: [PATCH 024/427] Emit withPlatformString as an availability workaround for open --- Sources/System/FilePath/FilePathString.swift | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/Sources/System/FilePath/FilePathString.swift b/Sources/System/FilePath/FilePathString.swift index 842367c1..9f43a9c0 100644 --- a/Sources/System/FilePath/FilePathString.swift +++ b/Sources/System/FilePath/FilePathString.swift @@ -32,10 +32,15 @@ extension FilePath { /// The pointer passed as an argument to `body` is valid /// only during the execution of this method. /// Don't try to store the pointer for later use. + @_alwaysEmitIntoClient public func withPlatformString( _ body: (UnsafePointer) throws -> Result ) rethrows -> Result { - try _withPlatformString(body) + #if !os(Windows) + return try withCString(body) + #else + return try _withPlatformString(body) + #endif } } @@ -412,7 +417,7 @@ extension FilePath { #if os(Windows) fatalError("FilePath.withCString() unsupported on Windows ") #else - return try withPlatformString(body) + return try _withPlatformString(body) #endif } } From a611ed59c41b97a72a82dd79507c2a6038cbffe9 Mon Sep 17 00:00:00 2001 From: Michael Ilseman Date: Mon, 15 Mar 2021 13:35:36 -0600 Subject: [PATCH 025/427] Update linux test main --- Tests/SystemTests/XCTestManifests.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Tests/SystemTests/XCTestManifests.swift b/Tests/SystemTests/XCTestManifests.swift index abbfe070..68eb9e82 100644 --- a/Tests/SystemTests/XCTestManifests.swift +++ b/Tests/SystemTests/XCTestManifests.swift @@ -17,6 +17,7 @@ extension FileDescriptorTest { // to regenerate. static let __allTests__FileDescriptorTest = [ ("testConstants", testConstants), + ("testStandardDescriptors", testStandardDescriptors), ] } @@ -26,6 +27,7 @@ extension FileOperationsTest { // to regenerate. static let __allTests__FileOperationsTest = [ ("testAdHocOpen", testAdHocOpen), + ("testGithubIssues", testGithubIssues), ("testHelpers", testHelpers), ("testSyscalls", testSyscalls), ] @@ -38,7 +40,6 @@ extension FilePathComponentsTest { static let __allTests__FilePathComponentsTest = [ ("testAdHocRRC", testAdHocRRC), ("testCases", testCases), - ("testConcatenation", testConcatenation), ("testSeparatorNormalization", testSeparatorNormalization), ] } From cd4b42e03d88c007700b8a41481b26c220458974 Mon Sep 17 00:00:00 2001 From: Michael Ilseman Date: Mon, 15 Mar 2021 13:34:02 -0600 Subject: [PATCH 026/427] Mocking helper for path strings --- Sources/System/Internals/Mocking.swift | 22 ++++++++++++++-------- Sources/System/Internals/Syscalls.swift | 4 ++-- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/Sources/System/Internals/Mocking.swift b/Sources/System/Internals/Mocking.swift index b1ca4856..4c74b6a4 100644 --- a/Sources/System/Internals/Mocking.swift +++ b/Sources/System/Internals/Mocking.swift @@ -146,13 +146,20 @@ private func originalSyscallName(_ function: String) -> String { private func mockImpl( name: String, + path: UnsafePointer?, _ args: [AnyHashable] ) -> CInt { + precondition(mockingEnabled) let origName = originalSyscallName(name) guard let driver = currentMockingDriver else { fatalError("Mocking requested from non-mocking context") } - driver.trace.add(Trace.Entry(name: origName, args)) + var mockArgs: Array = [] + if let p = path { + mockArgs.append(String(_errorCorrectingPlatformString: p)) + } + mockArgs.append(contentsOf: args) + driver.trace.add(Trace.Entry(name: origName, mockArgs)) switch driver.forceErrno { case .none: break @@ -170,21 +177,20 @@ private func mockImpl( } internal func _mock( - name: String = #function, _ args: AnyHashable... + name: String = #function, path: UnsafePointer? = nil, _ args: AnyHashable... ) -> CInt { - precondition(mockingEnabled) - return mockImpl(name: name, args) + return mockImpl(name: name, path: path, args) } internal func _mockInt( - name: String = #function, _ args: AnyHashable... + name: String = #function, path: UnsafePointer? = nil, _ args: AnyHashable... ) -> Int { - Int(mockImpl(name: name, args)) + Int(mockImpl(name: name, path: path, args)) } internal func _mockOffT( - name: String = #function, _ args: AnyHashable... + name: String = #function, path: UnsafePointer? = nil, _ args: AnyHashable... ) -> _COffT { - _COffT(mockImpl(name: name, args)) + _COffT(mockImpl(name: name, path: path, args)) } #endif // ENABLE_MOCKING diff --git a/Sources/System/Internals/Syscalls.swift b/Sources/System/Internals/Syscalls.swift index a755928e..ecfdc843 100644 --- a/Sources/System/Internals/Syscalls.swift +++ b/Sources/System/Internals/Syscalls.swift @@ -26,7 +26,7 @@ internal func system_open( ) -> CInt { #if ENABLE_MOCKING if mockingEnabled { - return _mock(String(_errorCorrectingPlatformString: path), oflag) + return _mock(path: path, oflag) } #endif return open(path, oflag) @@ -38,7 +38,7 @@ internal func system_open( ) -> CInt { #if ENABLE_MOCKING if mockingEnabled { - return _mock(String(_errorCorrectingPlatformString: path), oflag, mode) + return _mock(path: path, oflag, mode) } #endif return open(path, oflag, mode) From 58e9e1c0c97b47d0e343ca8c1a2ae470c7f6a007 Mon Sep 17 00:00:00 2001 From: Michael Ilseman Date: Mon, 15 Mar 2021 17:43:14 -0600 Subject: [PATCH 027/427] Ugly, ugly workaround for Swift 5.3.3 bug --- Package.swift | 1 + .../FilePath/FilePathComponentView.swift | 25 +++++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/Package.swift b/Package.swift index 8ebc98ba..4c6e4ab2 100644 --- a/Package.swift +++ b/Package.swift @@ -18,6 +18,7 @@ let targets: [PackageDescription.Target] = [ dependencies: ["CSystem"], path: "Sources/System", swiftSettings: [ + .define("SYSTEM_PACKAGE"), .define("ENABLE_MOCKING", .when(configuration: .debug)) ]), .target( diff --git a/Sources/System/FilePath/FilePathComponentView.swift b/Sources/System/FilePath/FilePathComponentView.swift index 7b5394bb..5a4a8dc6 100644 --- a/Sources/System/FilePath/FilePathComponentView.swift +++ b/Sources/System/FilePath/FilePathComponentView.swift @@ -40,6 +40,30 @@ extension FilePath { } } + #if SYSTEM_PACKAGE + /// View the non-root components that make up this path. + public var components: ComponentView { + get { ComponentView(self) } + _modify { + // RRC's empty init means that we can't guarantee that the yielded + // view will restore our root. So copy it out first. + // + // TODO(perf): Small-form root (especially on Unix). Have Root + // always copy out (not worth ref counting). Make sure that we're + // not needlessly sliding values around or triggering a COW + let rootStr = self.root?._systemString ?? SystemString() + var comp = ComponentView(self) + self = FilePath() + defer { + self = comp._path + if root?._slice.elementsEqual(rootStr) != true { + self.root = Root(rootStr) + } + } + yield &comp + } + } + #else /// View the non-root components that make up this path. public var components: ComponentView { __consuming get { ComponentView(self) } @@ -62,6 +86,7 @@ extension FilePath { yield &comp } } + #endif } // @available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *) From eb215cf414b6307b3559bdf15bcbad443a174aca Mon Sep 17 00:00:00 2001 From: Saleem Abdulrasool Date: Mon, 15 Mar 2021 18:23:48 -0700 Subject: [PATCH 028/427] git: ignore vim swap files --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 95c43209..36bbb6fb 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ /Packages /*.xcodeproj xcuserdata/ +.*.sw? From 83875613ec4e4f0a0dc3fed4e932ff2eddea9cb1 Mon Sep 17 00:00:00 2001 From: Saleem Abdulrasool Date: Tue, 16 Mar 2021 18:24:10 -0700 Subject: [PATCH 029/427] Internals: use CInterop.PlatformUnicodeString on Windows (#34) The typealias was renamed but the Windows side of the build was not updated. This prevents building System on Windows. --- Sources/System/Internals/Exports.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Sources/System/Internals/Exports.swift b/Sources/System/Internals/Exports.swift index 5ed7d4e3..2b4ae3be 100644 --- a/Sources/System/Internals/Exports.swift +++ b/Sources/System/Internals/Exports.swift @@ -85,7 +85,7 @@ extension String { ) rethrows -> Result { // Need to #if because CChar may be signed #if os(Windows) - return try withCString(encodedAs: _PlatformUnicodeEncoding.self, body) + return try withCString(encodedAs: CInterop.PlatformUnicodeEncoding.self, body) #else return try withCString(body) #endif @@ -96,7 +96,7 @@ extension String { #if os(Windows) guard let strRes = String.decodeCString( platformString, - as: _PlatformUnicodeEncoding.self, + as: CInterop.PlatformUnicodeEncoding.self, repairingInvalidCodeUnits: false ) else { return nil } assert(strRes.repairsMade == false) @@ -115,7 +115,7 @@ extension String { #if os(Windows) let strRes = String.decodeCString( platformString, - as: _PlatformUnicodeEncoding.self, + as: CInterop.PlatformUnicodeEncoding.self, repairingInvalidCodeUnits: true) self = strRes!.result return From bdfeb29148b1c9aa63e4bed602737a88ef84e356 Mon Sep 17 00:00:00 2001 From: Saleem Abdulrasool Date: Tue, 16 Mar 2021 18:24:59 -0700 Subject: [PATCH 030/427] Internals: adjust the definition of `CInterop.Mode` for Windows (#36) Windows does not have a `mode_t` type, instead it uses `int` as a stand in. Adjust the type alias to reflect this. --- Sources/System/Internals/CInterop.swift | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Sources/System/Internals/CInterop.swift b/Sources/System/Internals/CInterop.swift index 892a2d9a..7f3b96d7 100644 --- a/Sources/System/Internals/CInterop.swift +++ b/Sources/System/Internals/CInterop.swift @@ -29,7 +29,11 @@ import ucrt /// A namespace for C and platform types // @available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *) public enum CInterop { +#if os(Windows) + public typealias Mode = CInt +#else public typealias Mode = mode_t +#endif /// The C `char` type public typealias Char = CChar From dd1da2991ef7ba5e9d302e087dcf95422aa2e200 Mon Sep 17 00:00:00 2001 From: Saleem Abdulrasool Date: Tue, 16 Mar 2021 18:25:12 -0700 Subject: [PATCH 031/427] Internals: add Windows error code equivalences (#37) Windows does not have the same set of error codes as a POSIX system. However, the WinSock subsystem provides a set of error codes which are meant to provide a means for handling similar error scenarios with sockets. This additionally adds an additional error code which allows us to add back a previously ignored error code. --- Sources/System/Errno.swift | 2 - Sources/System/Internals/Constants.swift | 86 ++++++++++++++++++++---- 2 files changed, 73 insertions(+), 15 deletions(-) diff --git a/Sources/System/Errno.swift b/Sources/System/Errno.swift index d74acef8..03c3ff9e 100644 --- a/Sources/System/Errno.swift +++ b/Sources/System/Errno.swift @@ -634,7 +634,6 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { @available(*, unavailable, renamed: "socketTypeNotSupported") public static var ESOCKTNOSUPPORT: Errno { socketTypeNotSupported } -#if !os(Windows) /// Not supported. /// /// The attempted operation isn't supported @@ -647,7 +646,6 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { @_alwaysEmitIntoClient @available(*, unavailable, renamed: "notSupported") public static var ENOTSUP: Errno { notSupported } -#endif /// Protocol family not supported. /// diff --git a/Sources/System/Internals/Constants.swift b/Sources/System/Internals/Constants.swift index 22bdf488..f5a64b0b 100644 --- a/Sources/System/Internals/Constants.swift +++ b/Sources/System/Internals/Constants.swift @@ -166,15 +166,31 @@ internal var _ENOPROTOOPT: CInt { ENOPROTOOPT } internal var _EPROTONOSUPPORT: CInt { EPROTONOSUPPORT } @_alwaysEmitIntoClient -internal var _ESOCKTNOSUPPORT: CInt { ESOCKTNOSUPPORT } +internal var _ESOCKTNOSUPPORT: CInt { +#if os(Windows) + return WSAESOCKTNOSUPPORT +#else + return ESOCKTNOSUPPORT +#endif +} -#if !os(Windows) @_alwaysEmitIntoClient -internal var _ENOTSUP: CInt { ENOTSUP } +internal var _ENOTSUP: CInt { +#if os(Windows) + return WSAEOPNOTSUPP +#else + return ENOTSUP #endif +} @_alwaysEmitIntoClient -internal var _EPFNOSUPPORT: CInt { EPFNOSUPPORT } +internal var _EPFNOSUPPORT: CInt { +#if os(Windows) + return WSAEPFNOSUPPORT +#else + return EPFNOSUPPORT +#endif +} @_alwaysEmitIntoClient internal var _EAFNOSUPPORT: CInt { EAFNOSUPPORT } @@ -210,10 +226,22 @@ internal var _EISCONN: CInt { EISCONN } internal var _ENOTCONN: CInt { ENOTCONN } @_alwaysEmitIntoClient -internal var _ESHUTDOWN: CInt { ESHUTDOWN } +internal var _ESHUTDOWN: CInt { +#if os(Windows) + return WSAESHUTDOWN +#else + return ESHUTDOWN +#endif +} @_alwaysEmitIntoClient -internal var _ETOOMANYREFS: CInt { ETOOMANYREFS } +internal var _ETOOMANYREFS: CInt { +#if os(Windows) + return WSAETOOMANYREFS +#else + return ETOOMANYREFS +#endif +} @_alwaysEmitIntoClient internal var _ETIMEDOUT: CInt { ETIMEDOUT } @@ -228,7 +256,13 @@ internal var _ELOOP: CInt { ELOOP } internal var _ENAMETOOLONG: CInt { ENAMETOOLONG } @_alwaysEmitIntoClient -internal var _EHOSTDOWN: CInt { EHOSTDOWN } +internal var _EHOSTDOWN: CInt { +#if os(Windows) + return WSAEHOSTDOWN +#else + return EHOSTDOWN +#endif +} @_alwaysEmitIntoClient internal var _EHOSTUNREACH: CInt { EHOSTUNREACH } @@ -242,16 +276,40 @@ internal var _EPROCLIM: CInt { EPROCLIM } #endif @_alwaysEmitIntoClient -internal var _EUSERS: CInt { EUSERS } +internal var _EUSERS: CInt { +#if os(Windows) + return WSAEUSERS +#else + return EUSERS +#endif +} @_alwaysEmitIntoClient -internal var _EDQUOT: CInt { EDQUOT } +internal var _EDQUOT: CInt { +#if os(Windows) + return WSAEDQUOT +#else + return EDQUOT +#endif +} @_alwaysEmitIntoClient -internal var _ESTALE: CInt { ESTALE } +internal var _ESTALE: CInt { +#if os(Windows) + return WSAESTALE +#else + return ESTALE +#endif +} @_alwaysEmitIntoClient -internal var _EREMOTE: CInt { EREMOTE } +internal var _EREMOTE: CInt { +#if os(Windows) + return WSAEREMOTE +#else + return EREMOTE +#endif +} #if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) @_alwaysEmitIntoClient @@ -392,11 +450,11 @@ internal var _O_WRONLY: CInt { O_WRONLY } @_alwaysEmitIntoClient internal var _O_RDWR: CInt { O_RDWR } +#if !os(Windows) // TODO: API? @_alwaysEmitIntoClient internal var _O_ACCMODE: CInt { O_ACCMODE } -#if !os(Windows) @_alwaysEmitIntoClient internal var _O_NONBLOCK: CInt { O_NONBLOCK } #endif @@ -412,11 +470,11 @@ internal var _O_SHLOCK: CInt { O_SHLOCK } internal var _O_EXLOCK: CInt { O_EXLOCK } #endif +#if !os(Windows) // TODO: API? @_alwaysEmitIntoClient internal var _O_ASYNC: CInt { O_ASYNC } -#if !os(Windows) @_alwaysEmitIntoClient internal var _O_NOFOLLOW: CInt { O_NOFOLLOW } #endif @@ -435,6 +493,7 @@ internal var _O_EXCL: CInt { O_EXCL } internal var _O_EVTONLY: CInt { O_EVTONLY } #endif +#if !os(Windows) // TODO: API? @_alwaysEmitIntoClient internal var _O_NOCTTY: CInt { O_NOCTTY } @@ -442,6 +501,7 @@ internal var _O_NOCTTY: CInt { O_NOCTTY } // TODO: API? @_alwaysEmitIntoClient internal var _O_DIRECTORY: CInt { O_DIRECTORY } +#endif #if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) @_alwaysEmitIntoClient From b614185c9030a802331790c476228dc698f04f68 Mon Sep 17 00:00:00 2001 From: Saleem Abdulrasool Date: Thu, 18 Mar 2021 15:18:24 -0700 Subject: [PATCH 032/427] build: add a CMake based build system (#38) This will allow using Swift System in the toolchain stack, allowing us to bootstrap a toolchain. --- CMakeLists.txt | 29 ++++++++ Sources/CMakeLists.txt | 11 +++ Sources/CSystem/CMakeLists.txt | 15 ++++ Sources/CSystem/include/module.modulemap | 5 ++ Sources/System/CMakeLists.txt | 40 ++++++++++ cmake/modules/SwiftSupport.cmake | 94 ++++++++++++++++++++++++ 6 files changed, 194 insertions(+) create mode 100644 CMakeLists.txt create mode 100644 Sources/CMakeLists.txt create mode 100644 Sources/CSystem/CMakeLists.txt create mode 100644 Sources/CSystem/include/module.modulemap create mode 100644 Sources/System/CMakeLists.txt create mode 100644 cmake/modules/SwiftSupport.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 00000000..aff3fe16 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,29 @@ +#[[ +This source file is part of the Swift System open source project + +Copyright (c) 2020 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 +#]] + +cmake_minimum_required(VERSION 3.16.0) +project(swift-system + LANGUAGES C Swift) + +list(APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake/modules) + +set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) +set(CMAKE_Swift_MODULE_DIRECTORY ${CMAKE_BINARY_DIR}/swift) + +include(SwiftSupport) + +add_subdirectory(Sources) + +get_property(SWIFT_SYSTEM_EXPORTS GLOBAL PROPERTY SWIFT_SYSTEM_EXPORTS) +export(TARGETS ${SWIFT_SYSTEM_EXPORTS} + NAMESPACE SwiftSystem:: + FILE swift-system-config.cmake + EXPORT_LINK_INTERFACE_LIBRARIES) diff --git a/Sources/CMakeLists.txt b/Sources/CMakeLists.txt new file mode 100644 index 00000000..d788b8c3 --- /dev/null +++ b/Sources/CMakeLists.txt @@ -0,0 +1,11 @@ +#[[ +This source file is part of the Swift System open source project + +Copyright (c) 2020 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 +#]] + +add_subdirectory(CSystem) +add_subdirectory(System) diff --git a/Sources/CSystem/CMakeLists.txt b/Sources/CSystem/CMakeLists.txt new file mode 100644 index 00000000..6dba7ff2 --- /dev/null +++ b/Sources/CSystem/CMakeLists.txt @@ -0,0 +1,15 @@ +#[[ +This source file is part of the Swift System open source project + +Copyright (c) 2020 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 +#]] + +add_library(CSystem INTERFACE) +target_include_directories(CSystem INTERFACE + include) + + +set_property(GLOBAL APPEND PROPERTY SWIFT_SYSTEM_EXPORTS CSystem) diff --git a/Sources/CSystem/include/module.modulemap b/Sources/CSystem/include/module.modulemap new file mode 100644 index 00000000..776f7766 --- /dev/null +++ b/Sources/CSystem/include/module.modulemap @@ -0,0 +1,5 @@ +module CSystem { + header "CSystemLinux.h" + header "CSystemWindows.h" + export * +} diff --git a/Sources/System/CMakeLists.txt b/Sources/System/CMakeLists.txt new file mode 100644 index 00000000..5ab79b32 --- /dev/null +++ b/Sources/System/CMakeLists.txt @@ -0,0 +1,40 @@ +#[[ +This source file is part of the Swift System open source project + +Copyright (c) 2020 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 +#]] + +add_library(System + Errno.swift + FileDescriptor.swift + FileHelpers.swift + FileOperations.swift + FilePermissions.swift + PlatformString.swift + SystemString.swift + Util.swift + UtilConsumers.swift) +target_sources(System PRIVATE + FilePath/FilePath.swift + FilePath/FilePathComponents.swift + FilePath/FilePathComponentView.swift + FilePath/FilePathParsing.swift + FilePath/FilePathString.swift + FilePath/FilePathSyntax.swift + FilePath/FilePathWindows.swift) +target_sources(System PRIVATE + Internals/CInterop.swift + Internals/Constants.swift + Internals/Exports.swift + Internals/Mocking.swift + Internals/Syscalls.swift + Internals/WindowsSyscallAdapters.swift) +target_link_libraries(System PRIVATE + CSystem) + + +_install_target(System) +set_property(GLOBAL APPEND PROPERTY SWIFT_SYSTEM_EXPORTS System) diff --git a/cmake/modules/SwiftSupport.cmake b/cmake/modules/SwiftSupport.cmake new file mode 100644 index 00000000..915993cb --- /dev/null +++ b/cmake/modules/SwiftSupport.cmake @@ -0,0 +1,94 @@ +#[[ +This source file is part of the Swift System open source project + +Copyright (c) 2020 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 +#]] + +# Returns the architecture name in a variable +# +# Usage: +# get_swift_host_arch(result_var_name) +# +# Sets ${result_var_name} with the converted architecture name derived from +# CMAKE_SYSTEM_PROCESSOR. +function(get_swift_host_arch result_var_name) + if("${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "x86_64") + set("${result_var_name}" "x86_64" PARENT_SCOPE) + elseif("${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "aarch64") + set("${result_var_name}" "aarch64" PARENT_SCOPE) + elseif("${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "ppc64") + set("${result_var_name}" "powerpc64" PARENT_SCOPE) + elseif("${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "ppc64le") + set("${result_var_name}" "powerpc64le" PARENT_SCOPE) + elseif("${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "s390x") + set("${result_var_name}" "s390x" PARENT_SCOPE) + elseif("${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "armv6l") + set("${result_var_name}" "armv6" PARENT_SCOPE) + elseif("${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "armv7l") + set("${result_var_name}" "armv7" PARENT_SCOPE) + elseif("${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "armv7-a") + set("${result_var_name}" "armv7" PARENT_SCOPE) + elseif("${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "AMD64") + set("${result_var_name}" "x86_64" PARENT_SCOPE) + elseif("${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "IA64") + set("${result_var_name}" "itanium" PARENT_SCOPE) + elseif("${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "x86") + set("${result_var_name}" "i686" PARENT_SCOPE) + elseif("${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "i686") + set("${result_var_name}" "i686" PARENT_SCOPE) + else() + message(FATAL_ERROR "Unrecognized architecture on host system: ${CMAKE_SYSTEM_PROCESSOR}") + endif() +endfunction() + +# Returns the os name in a variable +# +# Usage: +# get_swift_host_os(result_var_name) +# +# +# Sets ${result_var_name} with the converted OS name derived from +# CMAKE_SYSTEM_NAME. +function(get_swift_host_os result_var_name) + if(CMAKE_SYSTEM_NAME STREQUAL Darwin) + set(${result_var_name} macosx PARENT_SCOPE) + else() + string(TOLOWER ${CMAKE_SYSTEM_NAME} cmake_system_name_lc) + set(${result_var_name} ${cmake_system_name_lc} PARENT_SCOPE) + endif() +endfunction() + +function(_install_target module) + get_swift_host_os(swift_os) + get_target_property(type ${module} TYPE) + + if(type STREQUAL STATIC_LIBRARY) + set(swift swift_static) + else() + set(swift swift) + endif() + + install(TARGETS ${module} + ARCHIVE DESTINATION lib/${swift}/${swift_os} + LIBRARY DESTINATION lib/${swift}/${swift_os} + RUNTIME DESTINATION bin) + if(type STREQUAL EXECUTABLE) + return() + endif() + + get_swift_host_arch(swift_arch) + get_target_property(module_name ${module} Swift_MODULE_NAME) + if(NOT module_name) + set(module_name ${module}) + endif() + + install(FILES $/${module_name}.swiftdoc + DESTINATION lib/${swift}/${swift_os}/${module_name}.swiftmodule + RENAME ${swift_arch}.swiftdoc) + install(FILES $/${module_name}.swiftmodule + DESTINATION lib/${swift}/${swift_os}/${module_name}.swiftmodule + RENAME ${swift_arch}.swiftmodule) +endfunction() From a973b9714701a76f7415dc202531ee5e65573f43 Mon Sep 17 00:00:00 2001 From: Michael Ilseman Date: Thu, 18 Mar 2021 16:18:39 -0600 Subject: [PATCH 033/427] [docs] Fixup code example (#39) --- Sources/System/FilePath/FilePathSyntax.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/System/FilePath/FilePathSyntax.swift b/Sources/System/FilePath/FilePathSyntax.swift index ee02aa5c..442cda80 100644 --- a/Sources/System/FilePath/FilePathSyntax.swift +++ b/Sources/System/FilePath/FilePathSyntax.swift @@ -320,8 +320,8 @@ extension FilePath { /// Example: /// /// var path = "/tmp/file" - /// path.extension = ".txt" // path is "/tmp/file.txt" - /// path.extension = ".o" // path is "/tmp/file.o" + /// path.extension = "txt" // path is "/tmp/file.txt" + /// path.extension = "o" // path is "/tmp/file.o" /// path.extension = nil // path is "/tmp/file" /// path.extension = "" // path is "/tmp/file." /// From 65a906baf18fc035e6ddeb869c65b4ff5e209972 Mon Sep 17 00:00:00 2001 From: Saleem Abdulrasool Date: Sat, 20 Mar 2021 11:32:13 -0700 Subject: [PATCH 034/427] Internals: replace `CModeT` with `CInterop.Mode` (#40) The typealias was renamed but the reference to it was not fully removed. This updates the instance enabling building System on Windows. --- Sources/System/Internals/WindowsSyscallAdapters.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Sources/System/Internals/WindowsSyscallAdapters.swift b/Sources/System/Internals/WindowsSyscallAdapters.swift index 5aa075be..36acaceb 100644 --- a/Sources/System/Internals/WindowsSyscallAdapters.swift +++ b/Sources/System/Internals/WindowsSyscallAdapters.swift @@ -23,7 +23,8 @@ internal func open( @inline(__always) internal func open( - _ path: UnsafePointer, _ oflag: Int32, _ mode: CModeT + _ path: UnsafePointer, _ oflag: Int32, + _ mode: CInterop.Mode ) -> CInt { // TODO(compnerd): Apply read/write permissions var fh: CInt = -1 From 603718e247a928ee2bd6cd68e95ddcb3953ece10 Mon Sep 17 00:00:00 2001 From: Saleem Abdulrasool Date: Sat, 20 Mar 2021 16:29:54 -0700 Subject: [PATCH 035/427] FilePath: enable building on Windows (#41) When building on Windows, we rely on the `withPlatformString` to convert the strings to the native platform encoding (UTF-16). The public `FilePath.withPlatformString` is marked as `@_alwaysEmitIntoClient` and references the `_withPlatformString` which is marked as `internal`. Annotate the function as `@usableFromInline` to enable the inlined method to reference this function. --- Sources/System/FilePath/FilePathComponents.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Sources/System/FilePath/FilePathComponents.swift b/Sources/System/FilePath/FilePathComponents.swift index 7bb55d59..d7ca9d47 100644 --- a/Sources/System/FilePath/FilePathComponents.swift +++ b/Sources/System/FilePath/FilePathComponents.swift @@ -190,6 +190,7 @@ extension FilePath.Root: _PathSlice { } } extension FilePath: _PlatformStringable { + @usableFromInline func _withPlatformString(_ body: (UnsafePointer) throws -> Result) rethrows -> Result { try _storage.withPlatformString(body) } From 090d109c0f790a717efb722242508df6491cfae6 Mon Sep 17 00:00:00 2001 From: Michael Ilseman Date: Mon, 22 Mar 2021 10:01:41 -0600 Subject: [PATCH 036/427] Make retryOnInterrupt always explicit internally --- Sources/System/FileOperations.swift | 11 ++++++----- Sources/System/Util.swift | 11 +++++++++-- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/Sources/System/FileOperations.swift b/Sources/System/FileOperations.swift index e51a979e..e2332eee 100644 --- a/Sources/System/FileOperations.swift +++ b/Sources/System/FileOperations.swift @@ -70,7 +70,7 @@ extension FileDescriptor { _ mode: FileDescriptor.AccessMode, options: FileDescriptor.OpenOptions, permissions: FilePermissions?, - retryOnInterrupt: Bool = true + retryOnInterrupt: Bool ) -> Result { let oFlag = mode.rawValue | options.rawValue let descOrError: Result = valueOrErrno(retryOnInterrupt: retryOnInterrupt) { @@ -96,7 +96,7 @@ extension FileDescriptor { @usableFromInline internal func _close() -> Result<(), Errno> { - nothingOrErrno(system_close(self.rawValue)) + nothingOrErrno(retryOnInterrupt: false) { system_close(self.rawValue) } } /// Reposition the offset for the given file descriptor. @@ -120,8 +120,9 @@ extension FileDescriptor { internal func _seek( offset: Int64, from whence: FileDescriptor.SeekOrigin ) -> Result { - let newOffset = system_lseek(self.rawValue, _COffT(offset), whence.rawValue) - return valueOrErrno(Int64(newOffset)) + valueOrErrno(retryOnInterrupt: false) { + Int64(system_lseek(self.rawValue, _COffT(offset), whence.rawValue)) + } } @@ -163,7 +164,7 @@ extension FileDescriptor { @usableFromInline internal func _read( into buffer: UnsafeMutableRawBufferPointer, - retryOnInterrupt: Bool = true + retryOnInterrupt: Bool ) throws -> Result { valueOrErrno(retryOnInterrupt: retryOnInterrupt) { system_read(self.rawValue, buffer.baseAddress, buffer.count) diff --git a/Sources/System/Util.swift b/Sources/System/Util.swift index d70c2892..c038d461 100644 --- a/Sources/System/Util.swift +++ b/Sources/System/Util.swift @@ -9,14 +9,14 @@ // Results in errno if i == -1 // @available(macOS 10.16, iOS 14.0, watchOS 7.0, tvOS 14.0, *) -internal func valueOrErrno( +private func valueOrErrno( _ i: I ) -> Result { i == -1 ? .failure(Errno.current) : .success(i) } // @available(macOS 10.16, iOS 14.0, watchOS 7.0, tvOS 14.0, *) -internal func nothingOrErrno( +private func nothingOrErrno( _ i: I ) -> Result<(), Errno> { valueOrErrno(i).map { _ in () } @@ -36,6 +36,13 @@ internal func valueOrErrno( } while true } +// @available(macOS 10.16, iOS 14.0, watchOS 7.0, tvOS 14.0, *) +internal func nothingOrErrno( + retryOnInterrupt: Bool, _ f: () -> I +) -> Result<(), Errno> { + valueOrErrno(retryOnInterrupt: retryOnInterrupt, f).map { _ in () } +} + // Run a precondition for debug client builds internal func _debugPrecondition( _ condition: @autoclosure () -> Bool, From 8e3c23987f32d4f449c68ff4b702121025980a7c Mon Sep 17 00:00:00 2001 From: Michael Ilseman Date: Mon, 22 Mar 2021 10:57:00 -0600 Subject: [PATCH 037/427] [testing] Generalize interruptable behavior to support non-erroring syscalls --- Tests/SystemTests/FileOperationsTest.swift | 22 ++++++------ Tests/SystemTests/TestingInfrastructure.swift | 34 ++++++++++++++++--- 2 files changed, 41 insertions(+), 15 deletions(-) diff --git a/Tests/SystemTests/FileOperationsTest.swift b/Tests/SystemTests/FileOperationsTest.swift index 0635e880..f39a052a 100644 --- a/Tests/SystemTests/FileOperationsTest.swift +++ b/Tests/SystemTests/FileOperationsTest.swift @@ -29,13 +29,13 @@ final class FileOperationsTest: XCTestCase { let writeBufAddr = writeBuf.baseAddress let syscallTestCases: Array = [ - MockTestCase(name: "open", "a path", O_RDWR | O_APPEND, interruptable: true) { + MockTestCase(name: "open", .interruptable, "a path", O_RDWR | O_APPEND) { retryOnInterrupt in _ = try FileDescriptor.open( "a path", .readWrite, options: [.append], retryOnInterrupt: retryOnInterrupt) }, - MockTestCase(name: "open", "a path", O_WRONLY | O_CREAT | O_APPEND, 0o777, interruptable: true) { + MockTestCase(name: "open", .interruptable, "a path", O_WRONLY | O_CREAT | O_APPEND, 0o777) { retryOnInterrupt in _ = try FileDescriptor.open( "a path", .writeOnly, options: [.create, .append], @@ -43,41 +43,41 @@ final class FileOperationsTest: XCTestCase { retryOnInterrupt: retryOnInterrupt) }, - MockTestCase(name: "read", rawFD, bufAddr, bufCount, interruptable: true) { + MockTestCase(name: "read", .interruptable, rawFD, bufAddr, bufCount) { retryOnInterrupt in _ = try fd.read(into: rawBuf, retryOnInterrupt: retryOnInterrupt) }, - MockTestCase(name: "pread", rawFD, bufAddr, bufCount, 5, interruptable: true) { + MockTestCase(name: "pread", .interruptable, rawFD, bufAddr, bufCount, 5) { retryOnInterrupt in _ = try fd.read(fromAbsoluteOffset: 5, into: rawBuf, retryOnInterrupt: retryOnInterrupt) }, - MockTestCase(name: "lseek", rawFD, -2, SEEK_END, interruptable: false) { + MockTestCase(name: "lseek", .noInterrupt, rawFD, -2, SEEK_END) { _ in _ = try fd.seek(offset: -2, from: .end) }, - MockTestCase(name: "write", rawFD, writeBufAddr, bufCount, interruptable: true) { + MockTestCase(name: "write", .interruptable, rawFD, writeBufAddr, bufCount) { retryOnInterrupt in _ = try fd.write(writeBuf, retryOnInterrupt: retryOnInterrupt) }, - MockTestCase(name: "pwrite", rawFD, writeBufAddr, bufCount, 7, interruptable: true) { + MockTestCase(name: "pwrite", .interruptable, rawFD, writeBufAddr, bufCount, 7) { retryOnInterrupt in _ = try fd.write(toAbsoluteOffset: 7, writeBuf, retryOnInterrupt: retryOnInterrupt) }, - MockTestCase(name: "close", rawFD, interruptable: false) { + MockTestCase(name: "close", .noInterrupt, rawFD) { _ in _ = try fd.close() }, - MockTestCase(name: "dup", rawFD, interruptable: true) { retryOnInterrupt in + MockTestCase(name: "dup", .interruptable, rawFD) { retryOnInterrupt in _ = try fd.duplicate(retryOnInterrupt: retryOnInterrupt) }, - MockTestCase(name: "dup2", rawFD, 42, interruptable: true) { retryOnInterrupt in + MockTestCase(name: "dup2", .interruptable, rawFD, 42) { retryOnInterrupt in _ = try fd.duplicate(as: FileDescriptor(rawValue: 42), retryOnInterrupt: retryOnInterrupt) }, @@ -128,7 +128,7 @@ final class FileOperationsTest: XCTestCase { func testGithubIssues() { // https://github.com/apple/swift-system/issues/26 let issue26 = MockTestCase( - name: "open", "a path", O_WRONLY | O_CREAT, 0o020, interruptable: true + name: "open", .interruptable, "a path", O_WRONLY | O_CREAT, 0o020 ) { retryOnInterrupt in _ = try FileDescriptor.open( diff --git a/Tests/SystemTests/TestingInfrastructure.swift b/Tests/SystemTests/TestingInfrastructure.swift index 53bb12f8..36e90be0 100644 --- a/Tests/SystemTests/TestingInfrastructure.swift +++ b/Tests/SystemTests/TestingInfrastructure.swift @@ -107,7 +107,20 @@ internal struct MockTestCase: TestCase { var line: UInt var expected: Trace.Entry - var interruptable: Bool + var interruptBehavior: InterruptBehavior + + var interruptable: Bool { return interruptBehavior == .interruptable } + + internal enum InterruptBehavior { + // Retry the syscall on EINTR + case interruptable + + // Cannot return EINTR + case noInterrupt + + // Cannot error at all + case noError + } var body: (_ retryOnInterrupt: Bool) throws -> () @@ -115,14 +128,14 @@ internal struct MockTestCase: TestCase { _ file: StaticString = #file, _ line: UInt = #line, name: String, + _ interruptable: InterruptBehavior, _ args: AnyHashable..., - interruptable: Bool, - _ body: @escaping (_ retryOnInterrupt: Bool) throws -> () + body: @escaping (_ retryOnInterrupt: Bool) throws -> () ) { self.file = file self.line = line self.expected = Trace.Entry(name: name, args) - self.interruptable = interruptable + self.interruptBehavior = interruptable self.body = body } @@ -141,6 +154,19 @@ internal struct MockTestCase: TestCase { self.fail() } + // Non-error-ing syscalls shouldn't ever throw + guard interruptBehavior != .noError else { + do { + try body(interruptable) + self.expectEqual(self.expected, mocking.trace.dequeue()) + try body(!interruptable) + self.expectEqual(self.expected, mocking.trace.dequeue()) + } catch { + self.fail() + } + return + } + // Test interupt behavior. Interruptable calls will be told not to // retry to catch the EINTR. Non-interruptable calls will be told to // retry, to make sure they don't spin (e.g. if API changes to include From 578586ab80466d8298d9e886c18f12b22dc4d9c1 Mon Sep 17 00:00:00 2001 From: Michael Ilseman Date: Tue, 20 Apr 2021 09:59:37 -0600 Subject: [PATCH 038/427] Un-deprecate withCString Un-deprecate withCString, but make sure it's not available on Windows (because the types will not match). withPlatformString is still the preferred expression of this and favors more portable code. --- Sources/System/FilePath/FilePathString.swift | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/Sources/System/FilePath/FilePathString.swift b/Sources/System/FilePath/FilePathString.swift index 9f43a9c0..6116cb6e 100644 --- a/Sources/System/FilePath/FilePathString.swift +++ b/Sources/System/FilePath/FilePathString.swift @@ -36,6 +36,7 @@ extension FilePath { public func withPlatformString( _ body: (UnsafePointer) throws -> Result ) rethrows -> Result { + // For backwards deployment, call withCString if available. #if !os(Windows) return try withCString(body) #else @@ -399,25 +400,21 @@ extension String { public init?(validatingUTF8 path: FilePath) { self.init(validating: path) } } +#if !os(Windows) // @available(macOS 10.16, iOS 14.0, watchOS 7.0, tvOS 14.0, *) extension FilePath { - @available(*, deprecated, renamed: "init(platformString:)") + /// For backwards compatibility only. This initializer is equivalent to + /// the preferred `FilePath(platformString:)`. public init(cString: UnsafePointer) { - #if os(Windows) - fatalError("FilePath.init(cString:) unsupported on Windows ") - #else self.init(platformString: cString) - #endif } - @available(*, deprecated, renamed: "withPlatformString(_:)") + /// For backwards compatibility only. This function is equivalent to + /// the preferred `withPlatformString`. public func withCString( _ body: (UnsafePointer) throws -> Result ) rethrows -> Result { - #if os(Windows) - fatalError("FilePath.withCString() unsupported on Windows ") - #else return try _withPlatformString(body) - #endif } } +#endif From 2bc160bfe34d843ae5ff47168080add24dfd7eac Mon Sep 17 00:00:00 2001 From: Michael Ilseman Date: Tue, 11 May 2021 10:03:09 -0600 Subject: [PATCH 039/427] FilePath.Component.Kind is meant to be frozen --- Sources/System/FilePath/FilePathComponents.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Sources/System/FilePath/FilePathComponents.swift b/Sources/System/FilePath/FilePathComponents.swift index d7ca9d47..a47a05f3 100644 --- a/Sources/System/FilePath/FilePathComponents.swift +++ b/Sources/System/FilePath/FilePathComponents.swift @@ -78,6 +78,7 @@ extension FilePath.Component { /// Whether a component is a regular file or directory name, or a special /// directory `.` or `..` + @frozen public enum Kind { /// The special directory `.`, representing the current directory. case currentDirectory From 591ff41944c74223a9ebf775094dc5f5fceebfbd Mon Sep 17 00:00:00 2001 From: Alex Martini Date: Wed, 9 Jun 2021 09:49:49 -0700 Subject: [PATCH 040/427] Move TODO and FIXME markers to normal comments. When they appear in triple-slash comments, that makes them part of the actual documentation that gets published. Fixes . --- Sources/System/FilePath/FilePath.swift | 6 +-- Sources/System/FilePath/FilePathSyntax.swift | 43 +++++++------------ Sources/System/FilePath/FilePathWindows.swift | 4 +- 3 files changed, 21 insertions(+), 32 deletions(-) diff --git a/Sources/System/FilePath/FilePath.swift b/Sources/System/FilePath/FilePath.swift index a678ff59..1b57a434 100644 --- a/Sources/System/FilePath/FilePath.swift +++ b/Sources/System/FilePath/FilePath.swift @@ -28,9 +28,6 @@ /// let fd = try FileDescriptor.open(path, .writeOnly, options: .append) /// try fd.closeAfter { try fd.writeAll(message.utf8) } /// -/// TODO(docs): Section on all the new syntactic operations, lexical normalization, decomposition, -/// components, etc. -/// /// File paths conform to the /// and /// and protocols @@ -40,6 +37,9 @@ /// However, the rules for path equivalence /// are file-system–specific and have additional considerations /// like case insensitivity, Unicode normalization, and symbolic links. +/// +// TODO(docs): Section on all the new syntactic operations, lexical normalization, decomposition, +// components, etc. // @available(macOS 10.16, iOS 14.0, watchOS 7.0, tvOS 14.0, *) public struct FilePath { internal var _storage: SystemString diff --git a/Sources/System/FilePath/FilePathSyntax.swift b/Sources/System/FilePath/FilePathSyntax.swift index 442cda80..693b326e 100644 --- a/Sources/System/FilePath/FilePathSyntax.swift +++ b/Sources/System/FilePath/FilePathSyntax.swift @@ -64,9 +64,8 @@ extension FilePath { /// path.starts(with: "/usr/bin/ls///") // true /// path.starts(with: "/us") // false /// - /// TODO(Windows docs): examples with roots, such as whether `\foo\bar` - /// starts with `C:\foo` - /// + // TODO(Windows docs): examples with roots, such as whether `\foo\bar` + // starts with `C:\foo` public func starts(with other: FilePath) -> Bool { guard !other.isEmpty else { return true } return self.root == other.root && components.starts( @@ -85,9 +84,8 @@ extension FilePath { /// path.ends(with: "/usr/bin/ls///") // true /// path.ends(with: "/ls") // false /// - /// TODO(Windows docs): examples with roots, such as whether `C:\foo\bar` - /// ends with `C:bar` - /// + // TODO(Windows docs): examples with roots, such as whether `C:\foo\bar` + // ends with `C:bar` public func ends(with other: FilePath) -> Bool { if other.root != nil { // TODO: anything tricky here for Windows? @@ -453,8 +451,7 @@ extension FilePath { /// path.removePrefix("/us") // false /// path.removePrefix("/usr/local") // true, path is "bin" /// - /// TODO(Windows docs): example with roots - /// + // TODO(Windows docs): example with roots public mutating func removePrefix(_ prefix: FilePath) -> Bool { defer { _invariantCheck() } // FIXME: Should Windows have more nuanced semantics? @@ -476,8 +473,7 @@ extension FilePath { /// } /// // path is "/tmp/foo/bar/../baz" /// - /// TODO(Windows docs): example with roots - /// + // TODO(Windows docs): example with roots public mutating func append(_ component: __owned FilePath.Component) { defer { _invariantCheck() } _append(unchecked: component._slice) @@ -492,8 +488,7 @@ extension FilePath { /// let otherPath: FilePath = "/bin/ls" /// path.append(otherPath.components) // path is "/usr/local/bin/ls" /// - /// TODO(Windows docs): example with roots - /// + // TODO(Windows docs): example with roots public mutating func append( _ components: __owned C ) where C.Element == FilePath.Component { @@ -513,9 +508,8 @@ extension FilePath { /// path.append("static/assets") // "/var/www/website/static/assets" /// path.append("/main.css") // "/var/www/website/static/assets/main.css" /// - /// TODO(Windows docs): example with roots, should we rephrase this "spurious - /// roots"? - /// + // TODO(Windows docs): example with roots, should we rephrase this "spurious + // roots"? public mutating func append(_ other: __owned String) { defer { _invariantCheck() } guard !other.utf8.isEmpty else { return } @@ -529,8 +523,7 @@ extension FilePath { /// Non-mutating version of `append(_:Component)`. /// - /// TODO(Windows docs): example with roots - /// + // TODO(Windows docs): example with roots public __consuming func appending(_ other: __owned Component) -> FilePath { var copy = self copy.append(other) @@ -539,8 +532,7 @@ extension FilePath { /// Non-mutating version of `append(_:C)`. /// - /// TODO(Windows docs): example with roots - /// + // TODO(Windows docs): example with roots public __consuming func appending( _ components: __owned C ) -> FilePath where C.Element == FilePath.Component { @@ -551,8 +543,7 @@ extension FilePath { /// Non-mutating version of `append(_:String)`. /// - /// TODO(Windows docs): example with roots - /// + // TODO(Windows docs): example with roots public __consuming func appending(_ other: __owned String) -> FilePath { var copy = self copy.append(other) @@ -573,23 +564,21 @@ extension FilePath { /// path.push("dir/file.txt") // path is "/tmp/dir/file.txt" /// path.push("/bin") // path is "/bin" /// - /// TODO(Windows docs): examples and docs with roots, update/generalize doc - /// comment - /// + // TODO(Windows docs): examples and docs with roots, update/generalize doc + // comment public mutating func push(_ other: __owned FilePath) { defer { _invariantCheck() } guard other.root == nil else { self = other return } - /// FIXME: Windows drive-relative roots, etc? + // FIXME: Windows drive-relative roots, etc? _append(unchecked: other._storage[...]) } /// Non-mutating version of `push()` /// - /// TODO(Windows docs): examples and docs with roots - /// + // TODO(Windows docs): examples and docs with roots public __consuming func pushing(_ other: __owned FilePath) -> FilePath { var copy = self copy.push(other) diff --git a/Sources/System/FilePath/FilePathWindows.swift b/Sources/System/FilePath/FilePathWindows.swift index db0651eb..a86651d1 100644 --- a/Sources/System/FilePath/FilePathWindows.swift +++ b/Sources/System/FilePath/FilePathWindows.swift @@ -156,7 +156,7 @@ internal struct WindowsRootInfo { /// * Device disk: `\\.\C:\`, `\\?\C:\` /// * UNC: `\\server\e:\`, `\\?\UNC\server\e:\` /// - /// TODO: NT paths? Admin paths using `$`? + // TODO: NT paths? Admin paths using `$`? case drive(Character) /// A volume with a GUID in a non-traditional path @@ -164,7 +164,7 @@ internal struct WindowsRootInfo { /// * UNC: `\\host\Volume{0000-...}\`, `\\.\UNC\host\Volume{0000-...}\` /// * Device roots: `\\.\Volume{0000-...}\`, `\\?\Volume{000-...}\` /// - /// TODO: GUID type? + // TODO: GUID type? case guid(String) // TODO: Legacy DOS devices, such as COM1? From 4f36710d0b5def6dca8638a6dcd37f14ba058d9a Mon Sep 17 00:00:00 2001 From: Rauhul Varma Date: Thu, 8 Jul 2021 17:42:58 -0700 Subject: [PATCH 041/427] Expose O_DIRECTORY as api - Adds a `directory` OpenOptions as a type-safe version of O_DIRECTORY. Allows clients to only open a FileDescriptor for FilePaths that refer to directories. --- Sources/System/FileDescriptor.swift | 14 ++++++++++++++ Sources/System/Internals/Constants.swift | 1 - 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/Sources/System/FileDescriptor.swift b/Sources/System/FileDescriptor.swift index 8659c3f8..43ef3b1e 100644 --- a/Sources/System/FileDescriptor.swift +++ b/Sources/System/FileDescriptor.swift @@ -234,6 +234,20 @@ extension FileDescriptor { @_alwaysEmitIntoClient @available(*, unavailable, renamed: "noFollow") public static var O_NOFOLLOW: OpenOptions { noFollow } + + /// Indicates that opening the file only succeeds if the file is a directory. + /// + /// If you specify this option and the file path you pass to + /// + /// is a not a directory, then that open operation fails. + /// + /// The corresponding C constant is `O_DIRECTORY`. + @_alwaysEmitIntoClient + public static var directory: OpenOptions { OpenOptions(_O_DIRECTORY) } + + @_alwaysEmitIntoClient + @available(*, unavailable, renamed: "directory") + public static var O_DIRECTORY: OpenOptions { directory } #endif #if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) diff --git a/Sources/System/Internals/Constants.swift b/Sources/System/Internals/Constants.swift index f5a64b0b..a4dbb89c 100644 --- a/Sources/System/Internals/Constants.swift +++ b/Sources/System/Internals/Constants.swift @@ -498,7 +498,6 @@ internal var _O_EVTONLY: CInt { O_EVTONLY } @_alwaysEmitIntoClient internal var _O_NOCTTY: CInt { O_NOCTTY } -// TODO: API? @_alwaysEmitIntoClient internal var _O_DIRECTORY: CInt { O_DIRECTORY } #endif From e520e616161f2bcc949309d1b318c54132b4d1c7 Mon Sep 17 00:00:00 2001 From: Rauhul Varma Date: Wed, 4 Aug 2021 14:05:04 -0700 Subject: [PATCH 042/427] Fix SystemChar isLetter logic - Fixes range expression in isLetter logic and adds test coverage. --- Sources/System/SystemString.swift | 4 +-- Tests/SystemTests/SystemCharTest.swift | 39 +++++++++++++++++++++++++ Tests/SystemTests/XCTestManifests.swift | 10 +++++++ 3 files changed, 51 insertions(+), 2 deletions(-) create mode 100644 Tests/SystemTests/SystemCharTest.swift diff --git a/Sources/System/SystemString.swift b/Sources/System/SystemString.swift index 507c8964..be5b6a8c 100644 --- a/Sources/System/SystemString.swift +++ b/Sources/System/SystemString.swift @@ -53,8 +53,8 @@ extension SystemChar { internal var isLetter: Bool { guard isASCII else { return false } let asciiRaw: UInt8 = numericCast(rawValue) - return (UInt8(ascii: "a") ..< UInt8(ascii: "z")).contains(asciiRaw) || - (UInt8(ascii: "A") ..< UInt8(ascii: "Z")).contains(asciiRaw) + return (UInt8(ascii: "a") ... UInt8(ascii: "z")).contains(asciiRaw) || + (UInt8(ascii: "A") ... UInt8(ascii: "Z")).contains(asciiRaw) } } diff --git a/Tests/SystemTests/SystemCharTest.swift b/Tests/SystemTests/SystemCharTest.swift new file mode 100644 index 00000000..368f21ae --- /dev/null +++ b/Tests/SystemTests/SystemCharTest.swift @@ -0,0 +1,39 @@ +/* + This source file is part of the Swift System open source project + + Copyright (c) 2020 - 2021 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 +*/ + +import XCTest + +#if SYSTEM_PACKAGE +@testable import SystemPackage +#else +@testable import System +#endif + +final class SystemCharTest: XCTestCase { + func testIsLetter() { + let valid = SystemString( + "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") + for char in valid { + XCTAssertTrue(char.isLetter) + } + + // non printable + for value in 0..<(UInt8(ascii: " ")) { + XCTAssertFalse(SystemChar(codeUnit: value).isLetter) + } + XCTAssertFalse(SystemChar(codeUnit: 0x7F).isLetter) // DEL + + // misc other + let invalid = SystemString( + ##" !"#$%&'()*+,-./0123456789:;<=>?@[\]^_`{|}~"##) + for char in invalid { + XCTAssertFalse(char.isLetter) + } + } +} diff --git a/Tests/SystemTests/XCTestManifests.swift b/Tests/SystemTests/XCTestManifests.swift index 68eb9e82..c5f13f7c 100644 --- a/Tests/SystemTests/XCTestManifests.swift +++ b/Tests/SystemTests/XCTestManifests.swift @@ -94,6 +94,15 @@ extension MockingTest { ] } +extension SystemCharTest { + // DO NOT MODIFY: This is autogenerated, use: + // `swift test --generate-linuxmain` + // to regenerate. + static let __allTests__SystemCharTest = [ + ("testIsLetter", testIsLetter), + ] +} + extension SystemStringTest { // DO NOT MODIFY: This is autogenerated, use: // `swift test --generate-linuxmain` @@ -115,6 +124,7 @@ public func __allTests() -> [XCTestCaseEntry] { testCase(FilePathTest.__allTests__FilePathTest), testCase(FilePermissionsTest.__allTests__FilePermissionsTest), testCase(MockingTest.__allTests__MockingTest), + testCase(SystemCharTest.__allTests__SystemCharTest), testCase(SystemStringTest.__allTests__SystemStringTest), ] } From 58a27250130e9a46cff8a1bfb4687d77e0023fc5 Mon Sep 17 00:00:00 2001 From: YR Chen Date: Tue, 8 Jun 2021 16:13:05 +0800 Subject: [PATCH 043/427] Fix building on Windows --- Package.swift | 10 +++++++++- Sources/System/Internals/Mocking.swift | 8 ++++---- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/Package.swift b/Package.swift index 4c6e4ab2..12755d94 100644 --- a/Package.swift +++ b/Package.swift @@ -12,11 +12,19 @@ import PackageDescription -let targets: [PackageDescription.Target] = [ +var windowsPlatform: [Platform] = [] +#if os(Windows) +windowsPlatform.append(.windows) +#endif + +let targets: [Target] = [ .target( name: "SystemPackage", dependencies: ["CSystem"], path: "Sources/System", + cSettings: [ + .define("_CRT_SECURE_NO_WARNINGS", .when(platforms: windowsPlatform)), + ], swiftSettings: [ .define("SYSTEM_PACKAGE"), .define("ENABLE_MOCKING", .when(configuration: .debug)) diff --git a/Sources/System/Internals/Mocking.swift b/Sources/System/Internals/Mocking.swift index 4c74b6a4..eb1fe834 100644 --- a/Sources/System/Internals/Mocking.swift +++ b/Sources/System/Internals/Mocking.swift @@ -146,7 +146,7 @@ private func originalSyscallName(_ function: String) -> String { private func mockImpl( name: String, - path: UnsafePointer?, + path: UnsafePointer?, _ args: [AnyHashable] ) -> CInt { precondition(mockingEnabled) @@ -177,18 +177,18 @@ private func mockImpl( } internal func _mock( - name: String = #function, path: UnsafePointer? = nil, _ args: AnyHashable... + name: String = #function, path: UnsafePointer? = nil, _ args: AnyHashable... ) -> CInt { return mockImpl(name: name, path: path, args) } internal func _mockInt( - name: String = #function, path: UnsafePointer? = nil, _ args: AnyHashable... + name: String = #function, path: UnsafePointer? = nil, _ args: AnyHashable... ) -> Int { Int(mockImpl(name: name, path: path, args)) } internal func _mockOffT( - name: String = #function, path: UnsafePointer? = nil, _ args: AnyHashable... + name: String = #function, path: UnsafePointer? = nil, _ args: AnyHashable... ) -> _COffT { _COffT(mockImpl(name: name, path: path, args)) } From 39774ef16a6d91dee6f666b940e00ea202710cf7 Mon Sep 17 00:00:00 2001 From: YR Chen Date: Fri, 18 Jun 2021 10:16:55 +0800 Subject: [PATCH 044/427] Simplify Package.swift --- Package.swift | 51 ++++++++++++++++++++++----------------------------- 1 file changed, 22 insertions(+), 29 deletions(-) diff --git a/Package.swift b/Package.swift index 12755d94..b081a872 100644 --- a/Package.swift +++ b/Package.swift @@ -12,39 +12,32 @@ import PackageDescription -var windowsPlatform: [Platform] = [] -#if os(Windows) -windowsPlatform.append(.windows) -#endif - -let targets: [Target] = [ - .target( - name: "SystemPackage", - dependencies: ["CSystem"], - path: "Sources/System", - cSettings: [ - .define("_CRT_SECURE_NO_WARNINGS", .when(platforms: windowsPlatform)), - ], - swiftSettings: [ - .define("SYSTEM_PACKAGE"), - .define("ENABLE_MOCKING", .when(configuration: .debug)) - ]), - .target( - name: "CSystem", - dependencies: []), - .testTarget( - name: "SystemTests", - dependencies: ["SystemPackage"], - swiftSettings: [ - .define("SYSTEM_PACKAGE") - ]), -] - let package = Package( name: "swift-system", products: [ .library(name: "SystemPackage", targets: ["SystemPackage"]), ], dependencies: [], - targets: targets + targets: [ + .target( + name: "CSystem", + dependencies: []), + .target( + name: "SystemPackage", + dependencies: ["CSystem"], + path: "Sources/System", + cSettings: [ + .define("_CRT_SECURE_NO_WARNINGS") + ], + swiftSettings: [ + .define("SYSTEM_PACKAGE"), + .define("ENABLE_MOCKING", .when(configuration: .debug)) + ]), + .testTarget( + name: "SystemTests", + dependencies: ["SystemPackage"], + swiftSettings: [ + .define("SYSTEM_PACKAGE") + ]), + ] ) From ce8b4910730114fa1eb46967a031a04f7594f42d Mon Sep 17 00:00:00 2001 From: Michael Ilseman Date: Thu, 26 Aug 2021 20:53:12 -0600 Subject: [PATCH 045/427] Update README for 1.0 --- README.md | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index f67a283f..11e57eb2 100644 --- a/README.md +++ b/README.md @@ -28,16 +28,7 @@ To use the `SystemPackage` library in a SwiftPM project, add the following line to the dependencies in your `Package.swift` file: ```swift -.package(url: "https://github.com/apple/swift-system", from: "0.0.1"), -``` - -Because `SystemPackage` is under active development, -source-stability is only guaranteed within minor versions (e.g. between `0.0.3` and `0.0.4`). -If you don't want potentially source-breaking package updates, -use this dependency specification instead: - -```swift -.package(url: "https://github.com/apple/swift-system", .upToNextMinor(from: "0.0.1")), +.package(url: "https://github.com/apple/swift-system", from: "1.0.0"), ``` Finally, include `"SystemPackage"` as a dependency for your executable target: @@ -46,7 +37,7 @@ Finally, include `"SystemPackage"` as a dependency for your executable target: let package = Package( // name, platforms, products, etc. dependencies: [ - .package(url: "https://github.com/apple/swift-system", from: "0.0.1"), + .package(url: "https://github.com/apple/swift-system", from: "1.0.0"), // other dependencies ], targets: [ @@ -58,6 +49,12 @@ let package = Package( ) ``` +## Source Stability + +The Swift System package is source stable. The version numbers follow [Semantic Versioning][semver] -- source breaking changes to public API can only land in a new major version. + +[semver]: https://semver.org + ## Contributing Before contributing, please read [CONTRIBUTING.md](CONTRIBUTING.md). From 689390f50fbe05990e7bb9d93b8ea92a8ecaf92d Mon Sep 17 00:00:00 2001 From: George Lyon Date: Mon, 27 Sep 2021 15:02:52 -0700 Subject: [PATCH 046/427] Implement `FileDescriptor.Pipe()` (#58) Adds support for pipe(2) --- Sources/System/FileOperations.swift | 23 ++++++++++++++++++++++ Sources/System/Internals/Syscalls.swift | 8 ++++++++ Tests/SystemTests/FileOperationsTest.swift | 21 ++++++++++++++++++++ 3 files changed, 52 insertions(+) diff --git a/Sources/System/FileOperations.swift b/Sources/System/FileOperations.swift index e2332eee..1193a04a 100644 --- a/Sources/System/FileOperations.swift +++ b/Sources/System/FileOperations.swift @@ -370,4 +370,27 @@ extension FileDescriptor { public func dup2() throws -> FileDescriptor { fatalError("Not implemented") } + + #if !os(Windows) + /// Create a pipe, a unidirectional data channel which can be used for interprocess communication. + /// + /// - Returns: The pair of file descriptors. + /// + /// The corresponding C function is `pipe`. + @_alwaysEmitIntoClient + // @available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *) + public static func pipe() throws -> (readEnd: FileDescriptor, writeEnd: FileDescriptor) { + try _pipe().get() + } + + @usableFromInline + internal static func _pipe() -> Result<(readEnd: FileDescriptor, writeEnd: FileDescriptor), Errno> { + var fds: (Int32, Int32) = (-1, -1) + return withUnsafeMutablePointer(to: &fds) { pointer in + valueOrErrno(retryOnInterrupt: false) { + system_pipe(UnsafeMutableRawPointer(pointer).assumingMemoryBound(to: Int32.self)) + } + }.map { _ in (.init(rawValue: fds.0), .init(rawValue: fds.1)) } + } + #endif } diff --git a/Sources/System/Internals/Syscalls.swift b/Sources/System/Internals/Syscalls.swift index ecfdc843..453c02fc 100644 --- a/Sources/System/Internals/Syscalls.swift +++ b/Sources/System/Internals/Syscalls.swift @@ -115,3 +115,11 @@ internal func system_dup2(_ fd: Int32, _ fd2: Int32) -> Int32 { #endif return dup2(fd, fd2) } +#if !os(Windows) +internal func system_pipe(_ fds: UnsafeMutablePointer) -> CInt { +#if ENABLE_MOCKING + if mockingEnabled { return _mock(fds) } +#endif + return pipe(fds) +} +#endif diff --git a/Tests/SystemTests/FileOperationsTest.swift b/Tests/SystemTests/FileOperationsTest.swift index f39a052a..a65301df 100644 --- a/Tests/SystemTests/FileOperationsTest.swift +++ b/Tests/SystemTests/FileOperationsTest.swift @@ -89,6 +89,27 @@ final class FileOperationsTest: XCTestCase { func testHelpers() { // TODO: Test writeAll, writeAll(toAbsoluteOffset), closeAfter } + +#if !os(Windows) + 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`. + let pipe = try FileDescriptor.pipe() + try pipe.readEnd.closeAfter { + try pipe.writeEnd.closeAfter { + var abc = "abc" + try abc.withUTF8 { + _ = try pipe.writeEnd.write(UnsafeRawBufferPointer($0)) + } + let readLen = 3 + let readBytes = try Array(unsafeUninitializedCapacity: readLen) { buf, count in + count = try pipe.readEnd.read(into: UnsafeMutableRawBufferPointer(buf)) + } + XCTAssertEqual(readBytes, Array(abc.utf8)) + } + } + } +#endif func testAdHocOpen() { // Ad-hoc test touching a file system. From 78847afcd4f3361d780b1e19062c8bfa9140c2a7 Mon Sep 17 00:00:00 2001 From: Alex Martini Date: Wed, 9 Jun 2021 13:43:34 -0700 Subject: [PATCH 047/427] Remove stray word. --- Sources/System/FilePath/FilePath.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/System/FilePath/FilePath.swift b/Sources/System/FilePath/FilePath.swift index 1b57a434..b7177400 100644 --- a/Sources/System/FilePath/FilePath.swift +++ b/Sources/System/FilePath/FilePath.swift @@ -29,7 +29,7 @@ /// try fd.closeAfter { try fd.writeAll(message.utf8) } /// /// File paths conform to the -/// and +/// /// and protocols /// by performing the protocols' operations on their raw byte contents. /// This conformance allows file paths to be used, From d2c4e4507aa34e6a28a0fd557b398e960c976bff Mon Sep 17 00:00:00 2001 From: Alex Martini Date: Tue, 22 Jun 2021 20:24:14 -0700 Subject: [PATCH 048/427] Fix spacing and indentation. --- Sources/System/FilePath/FilePathSyntax.swift | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/Sources/System/FilePath/FilePathSyntax.swift b/Sources/System/FilePath/FilePathSyntax.swift index 693b326e..b3e8eca0 100644 --- a/Sources/System/FilePath/FilePathSyntax.swift +++ b/Sources/System/FilePath/FilePathSyntax.swift @@ -442,7 +442,7 @@ extension FilePath { // @available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *) extension FilePath { /// If `prefix` is a prefix of `self`, removes it and returns `true`. - /// Otherwise returns `false`. + /// Otherwise returns `false`. /// /// Example: /// @@ -503,10 +503,11 @@ extension FilePath { /// A leading separator is spurious if `self` is non-empty. /// /// Example: - /// var path: FilePath = "" - /// path.append("/var/www/website") // "/var/www/website" - /// path.append("static/assets") // "/var/www/website/static/assets" - /// path.append("/main.css") // "/var/www/website/static/assets/main.css" + /// + /// var path: FilePath = "" + /// path.append("/var/www/website") // "/var/www/website" + /// path.append("static/assets") // "/var/www/website/static/assets" + /// path.append("/main.css") // "/var/www/website/static/assets/main.css" /// // TODO(Windows docs): example with roots, should we rephrase this "spurious // roots"? From 0ac080ea562c6b5e9afde0f169754763b9e8694a Mon Sep 17 00:00:00 2001 From: Alex Martini Date: Wed, 23 Jun 2021 16:05:55 -0700 Subject: [PATCH 049/427] Add missing period at end of abstract. --- Sources/System/FilePath/FilePathSyntax.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/System/FilePath/FilePathSyntax.swift b/Sources/System/FilePath/FilePathSyntax.swift index b3e8eca0..b9f20fef 100644 --- a/Sources/System/FilePath/FilePathSyntax.swift +++ b/Sources/System/FilePath/FilePathSyntax.swift @@ -577,7 +577,7 @@ extension FilePath { _append(unchecked: other._storage[...]) } - /// Non-mutating version of `push()` + /// Non-mutating version of `push()`. /// // TODO(Windows docs): examples and docs with roots public __consuming func pushing(_ other: __owned FilePath) -> FilePath { From 0c291a65c2588125b224656cd4c572ad903482eb Mon Sep 17 00:00:00 2001 From: Alex Martini Date: Mon, 8 Nov 2021 19:43:49 -0800 Subject: [PATCH 050/427] Fix typo. Fixes . --- Sources/System/FilePath/FilePath.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/System/FilePath/FilePath.swift b/Sources/System/FilePath/FilePath.swift index b7177400..c2797f5e 100644 --- a/Sources/System/FilePath/FilePath.swift +++ b/Sources/System/FilePath/FilePath.swift @@ -16,7 +16,7 @@ /// encoding. /// /// On construction, `FilePath` will normalize separators by removing -/// reduncant intermediary separators and stripping any trailing separators. +/// redundant intermediary separators and stripping any trailing separators. /// On Windows, `FilePath` will also normalize forward slashes `/` into /// backslashes `\`, as preferred by the platform. /// From 5703dc2b4da22c716e7f42a790de92c5a8b4d203 Mon Sep 17 00:00:00 2001 From: tomer doron Date: Tue, 30 Nov 2021 09:50:50 -0800 Subject: [PATCH 051/427] update cmake setup so that the library can be used by SwiftPM cmake build (#73) Update cmake setup so that the library can be used by SwiftPM cmake build Motivation: integrate with SwiftPM changes: * rename the top level project to SwiftSystem instead of swift-system to align with other projects * rename System to SystemPackage to align with SwifPM setup * update architectures to include arm64 * Update cmake/modules/SwiftSystemConfig.cmake.in * Update cmake/modules/SwiftSupport.cmake --- CMakeLists.txt | 9 ++------- Sources/System/CMakeLists.txt | 14 ++++++++------ cmake/modules/CMakeLists.txt | 19 +++++++++++++++++++ cmake/modules/SwiftSupport.cmake | 10 ++++++++-- cmake/modules/SwiftSystemConfig.cmake.in | 12 ++++++++++++ 5 files changed, 49 insertions(+), 15 deletions(-) create mode 100644 cmake/modules/CMakeLists.txt create mode 100644 cmake/modules/SwiftSystemConfig.cmake.in diff --git a/CMakeLists.txt b/CMakeLists.txt index aff3fe16..2ed4f5d1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -8,7 +8,7 @@ See https://swift.org/LICENSE.txt for license information #]] cmake_minimum_required(VERSION 3.16.0) -project(swift-system +project(SwiftSystem LANGUAGES C Swift) list(APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake/modules) @@ -21,9 +21,4 @@ set(CMAKE_Swift_MODULE_DIRECTORY ${CMAKE_BINARY_DIR}/swift) include(SwiftSupport) add_subdirectory(Sources) - -get_property(SWIFT_SYSTEM_EXPORTS GLOBAL PROPERTY SWIFT_SYSTEM_EXPORTS) -export(TARGETS ${SWIFT_SYSTEM_EXPORTS} - NAMESPACE SwiftSystem:: - FILE swift-system-config.cmake - EXPORT_LINK_INTERFACE_LIBRARIES) +add_subdirectory(cmake/modules) diff --git a/Sources/System/CMakeLists.txt b/Sources/System/CMakeLists.txt index 5ab79b32..e77fa5d9 100644 --- a/Sources/System/CMakeLists.txt +++ b/Sources/System/CMakeLists.txt @@ -7,7 +7,7 @@ Licensed under Apache License v2.0 with Runtime Library Exception See https://swift.org/LICENSE.txt for license information #]] -add_library(System +add_library(SystemPackage Errno.swift FileDescriptor.swift FileHelpers.swift @@ -17,7 +17,9 @@ add_library(System SystemString.swift Util.swift UtilConsumers.swift) -target_sources(System PRIVATE +set_target_properties(SystemPackage PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES ${CMAKE_Swift_MODULE_DIRECTORY}) +target_sources(SystemPackage PRIVATE FilePath/FilePath.swift FilePath/FilePathComponents.swift FilePath/FilePathComponentView.swift @@ -25,16 +27,16 @@ target_sources(System PRIVATE FilePath/FilePathString.swift FilePath/FilePathSyntax.swift FilePath/FilePathWindows.swift) -target_sources(System PRIVATE +target_sources(SystemPackage PRIVATE Internals/CInterop.swift Internals/Constants.swift Internals/Exports.swift Internals/Mocking.swift Internals/Syscalls.swift Internals/WindowsSyscallAdapters.swift) -target_link_libraries(System PRIVATE +target_link_libraries(SystemPackage PRIVATE CSystem) -_install_target(System) -set_property(GLOBAL APPEND PROPERTY SWIFT_SYSTEM_EXPORTS System) +_install_target(SystemPackage) +set_property(GLOBAL APPEND PROPERTY SWIFT_SYSTEM_EXPORTS SystemPackage) diff --git a/cmake/modules/CMakeLists.txt b/cmake/modules/CMakeLists.txt new file mode 100644 index 00000000..17f3f0b8 --- /dev/null +++ b/cmake/modules/CMakeLists.txt @@ -0,0 +1,19 @@ +#[[ +This source file is part of the Swift System open source Project + +Copyright (c) 2021 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 +#]] + +set(SWIFT_SYSTEM_EXPORTS_FILE ${CMAKE_CURRENT_BINARY_DIR}/SwiftSystemExports.cmake) + +configure_file(SwiftSystemConfig.cmake.in + ${CMAKE_CURRENT_BINARY_DIR}/SwiftSystemConfig.cmake) + +get_property(SWIFT_SYSTEM_EXPORTS GLOBAL PROPERTY SWIFT_SYSTEM_EXPORTS) +export(TARGETS ${SWIFT_SYSTEM_EXPORTS} + NAMESPACE SwiftSystem:: + FILE ${SWIFT_SYSTEM_EXPORTS_FILE} + EXPORT_LINK_INTERFACE_LIBRARIES) diff --git a/cmake/modules/SwiftSupport.cmake b/cmake/modules/SwiftSupport.cmake index 915993cb..82961db3 100644 --- a/cmake/modules/SwiftSupport.cmake +++ b/cmake/modules/SwiftSupport.cmake @@ -17,8 +17,12 @@ See https://swift.org/LICENSE.txt for license information function(get_swift_host_arch result_var_name) if("${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "x86_64") set("${result_var_name}" "x86_64" PARENT_SCOPE) - elseif("${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "aarch64") - set("${result_var_name}" "aarch64" PARENT_SCOPE) + elseif ("${CMAKE_SYSTEM_PROCESSOR}" MATCHES "AArch64|aarch64|arm64") + if(CMAKE_SYSTEM_NAME MATCHES Darwin) + set("${result_var_name}" "arm64" PARENT_SCOPE) + else() + set("${result_var_name}" "aarch64" PARENT_SCOPE) + endif() elseif("${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "ppc64") set("${result_var_name}" "powerpc64" PARENT_SCOPE) elseif("${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "ppc64le") @@ -31,6 +35,8 @@ function(get_swift_host_arch result_var_name) set("${result_var_name}" "armv7" PARENT_SCOPE) elseif("${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "armv7-a") set("${result_var_name}" "armv7" PARENT_SCOPE) + elseif("${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "amd64") + set("${result_var_name}" "amd64" PARENT_SCOPE) elseif("${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "AMD64") set("${result_var_name}" "x86_64" PARENT_SCOPE) elseif("${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "IA64") diff --git a/cmake/modules/SwiftSystemConfig.cmake.in b/cmake/modules/SwiftSystemConfig.cmake.in new file mode 100644 index 00000000..12a95c32 --- /dev/null +++ b/cmake/modules/SwiftSystemConfig.cmake.in @@ -0,0 +1,12 @@ +#[[ +This source file is part of the Swift System open source Project + +Copyright (c) 2021 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 +#]] + +if(NOT TARGET SystemPackage) + include(@SWIFT_SYSTEM_EXPORTS_FILE@) +endif() From 5d68cf0eca9dab6068d04aeefad0333c5d8a9989 Mon Sep 17 00:00:00 2001 From: Saleem Abdulrasool Date: Thu, 2 Dec 2021 09:19:43 -0800 Subject: [PATCH 052/427] build: install CSystem into the system CSystem is a leaky library dependency from System and must be distributed. Install the files and make the dependency public so that it is emitted into the clients. --- Sources/CSystem/CMakeLists.txt | 5 +++++ Sources/System/CMakeLists.txt | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/Sources/CSystem/CMakeLists.txt b/Sources/CSystem/CMakeLists.txt index 6dba7ff2..0f5d8e8a 100644 --- a/Sources/CSystem/CMakeLists.txt +++ b/Sources/CSystem/CMakeLists.txt @@ -12,4 +12,9 @@ target_include_directories(CSystem INTERFACE include) +install(FILES + include/CSystemLinux.h + include/CSystemWindows.h + include/module.modulemap + DESTINATION include/CSystem) set_property(GLOBAL APPEND PROPERTY SWIFT_SYSTEM_EXPORTS CSystem) diff --git a/Sources/System/CMakeLists.txt b/Sources/System/CMakeLists.txt index e77fa5d9..43958d31 100644 --- a/Sources/System/CMakeLists.txt +++ b/Sources/System/CMakeLists.txt @@ -34,7 +34,7 @@ target_sources(SystemPackage PRIVATE Internals/Mocking.swift Internals/Syscalls.swift Internals/WindowsSyscallAdapters.swift) -target_link_libraries(SystemPackage PRIVATE +target_link_libraries(SystemPackage PUBLIC CSystem) From 1daa0c96773558cc0a0c7f82c6f3b09a3367c2f3 Mon Sep 17 00:00:00 2001 From: Karoy Lorentey Date: Mon, 20 Dec 2021 17:10:59 -0800 Subject: [PATCH 053/427] Unify availability annotations and synchronize them with macOS 12 / iOS 15 Availability annotations now follow a rigid syntax that mentions the swift-system version that introduced the entry point: ``` /*System 0.0.2, @available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *)*/ public func greeting() -> String { "Hello" } ``` This is designed to be processed by automated source generation tools. --- Sources/System/Errno.swift | 12 +++---- Sources/System/FileDescriptor.swift | 10 +++--- Sources/System/FileHelpers.swift | 2 +- Sources/System/FileOperations.swift | 14 +++++--- Sources/System/FilePath/FilePath.swift | 6 ++-- .../FilePath/FilePathComponentView.swift | 8 ++--- .../System/FilePath/FilePathComponents.swift | 4 +-- Sources/System/FilePath/FilePathString.swift | 34 +++++++++---------- Sources/System/FilePath/FilePathSyntax.swift | 16 ++++----- Sources/System/FilePermissions.swift | 4 +-- Sources/System/Internals/CInterop.swift | 4 +-- Sources/System/PlatformString.swift | 2 +- Sources/System/Util.swift | 8 ++--- Tests/SystemTests/ErrnoTest.swift | 2 +- Tests/SystemTests/FileOperationsTest.swift | 2 +- .../FilePathComponentsTest.swift | 4 +-- .../FilePathTests/FilePathExtras.swift | 4 +-- .../FilePathTests/FilePathParsingTest.swift | 2 +- .../FilePathTests/FilePathSyntaxTest.swift | 6 ++-- .../FilePathTests/FilePathTest.swift | 4 +-- Tests/SystemTests/FileTypesTest.swift | 4 +-- Tests/SystemTests/MockingTest.swift | 2 +- 22 files changed, 79 insertions(+), 75 deletions(-) diff --git a/Sources/System/Errno.swift b/Sources/System/Errno.swift index 03c3ff9e..e2ebadb3 100644 --- a/Sources/System/Errno.swift +++ b/Sources/System/Errno.swift @@ -10,7 +10,7 @@ /// An error number used by system calls to communicate what kind of error /// occurred. @frozen -// @available(macOS 10.16, iOS 14.0, watchOS 7.0, tvOS 14.0, *) +/*System 0.0.1, @available(macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0, *)*/ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// The raw C error number. @_alwaysEmitIntoClient @@ -1376,7 +1376,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { } // Constants defined in header but not man page -// @available(macOS 10.16, iOS 14.0, watchOS 7.0, tvOS 14.0, *) +/*System 0.0.1, @available(macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0, *)*/ extension Errno { /// Operation would block. @@ -1470,7 +1470,7 @@ extension Errno { #endif } -// @available(macOS 10.16, iOS 14.0, watchOS 7.0, tvOS 14.0, *) +/*System 0.0.1, @available(macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0, *)*/ extension Errno { // TODO: We want to provide safe access to `errno`, but we need a // release-barrier to do so. @@ -1485,14 +1485,14 @@ extension Errno { } // Use "hidden" entry points for `NSError` bridging -// @available(macOS 10.16, iOS 14.0, watchOS 7.0, tvOS 14.0, *) +/*System 0.0.1, @available(macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0, *)*/ extension Errno { public var _code: Int { Int(rawValue) } public var _domain: String { "NSPOSIXErrorDomain" } } -// @available(macOS 10.16, iOS 14.0, watchOS 7.0, tvOS 14.0, *) +/*System 0.0.1, @available(macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0, *)*/ extension Errno: CustomStringConvertible, CustomDebugStringConvertible { /// A textual representation of the most recent error /// returned by a system call. @@ -1512,7 +1512,7 @@ extension Errno: CustomStringConvertible, CustomDebugStringConvertible { public var debugDescription: String { self.description } } -// @available(macOS 10.16, iOS 14.0, watchOS 7.0, tvOS 14.0, *) +/*System 0.0.1, @available(macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0, *)*/ extension Errno { @_alwaysEmitIntoClient public static func ~=(_ lhs: Errno, _ rhs: Error) -> Bool { diff --git a/Sources/System/FileDescriptor.swift b/Sources/System/FileDescriptor.swift index 43ef3b1e..9a7d319c 100644 --- a/Sources/System/FileDescriptor.swift +++ b/Sources/System/FileDescriptor.swift @@ -14,7 +14,7 @@ /// of `FileDescriptor` values, /// in the same way as you manage a raw C file handle. @frozen -// @available(macOS 10.16, iOS 14.0, watchOS 7.0, tvOS 14.0, *) +/*System 0.0.1, @available(macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0, *)*/ public struct FileDescriptor: RawRepresentable, Hashable, Codable { /// The raw C file handle. @_alwaysEmitIntoClient @@ -40,7 +40,7 @@ extension FileDescriptor { public static var standardError: FileDescriptor { .init(rawValue: 2) } } -// @available(macOS 10.16, iOS 14.0, watchOS 7.0, tvOS 14.0, *) +/*System 0.0.1, @available(macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0, *)*/ extension FileDescriptor { /// The desired read and write access for a newly opened file. @frozen @@ -385,7 +385,7 @@ extension FileDescriptor { } } -// @available(macOS 10.16, iOS 14.0, watchOS 7.0, tvOS 14.0, *) +/*System 0.0.1, @available(macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0, *)*/ extension FileDescriptor.AccessMode : CustomStringConvertible, CustomDebugStringConvertible { @@ -404,7 +404,7 @@ extension FileDescriptor.AccessMode public var debugDescription: String { self.description } } -// @available(macOS 10.16, iOS 14.0, watchOS 7.0, tvOS 14.0, *) +/*System 0.0.1, @available(macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0, *)*/ extension FileDescriptor.SeekOrigin : CustomStringConvertible, CustomDebugStringConvertible { @@ -427,7 +427,7 @@ extension FileDescriptor.SeekOrigin public var debugDescription: String { self.description } } -// @available(macOS 10.16, iOS 14.0, watchOS 7.0, tvOS 14.0, *) +/*System 0.0.1, @available(macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0, *)*/ extension FileDescriptor.OpenOptions : CustomStringConvertible, CustomDebugStringConvertible { diff --git a/Sources/System/FileHelpers.swift b/Sources/System/FileHelpers.swift index d083c101..0825f83c 100644 --- a/Sources/System/FileHelpers.swift +++ b/Sources/System/FileHelpers.swift @@ -7,7 +7,7 @@ See https://swift.org/LICENSE.txt for license information */ -// @available(macOS 10.16, iOS 14.0, watchOS 7.0, tvOS 14.0, *) +/*System 0.0.1, @available(macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0, *)*/ extension FileDescriptor { /// Runs a closure and then closes the file descriptor, even if an error occurs. /// diff --git a/Sources/System/FileOperations.swift b/Sources/System/FileOperations.swift index 1193a04a..01025632 100644 --- a/Sources/System/FileOperations.swift +++ b/Sources/System/FileOperations.swift @@ -7,7 +7,7 @@ See https://swift.org/LICENSE.txt for license information */ -// @available(macOS 10.16, iOS 14.0, watchOS 7.0, tvOS 14.0, *) +/*System 0.0.1, @available(macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0, *)*/ extension FileDescriptor { /// Opens or creates a file for reading or writing. /// @@ -337,7 +337,7 @@ extension FileDescriptor { /// /// The corresponding C functions are `dup` and `dup2`. @_alwaysEmitIntoClient - // @available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *) + /*System 0.0.2, @available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *)*/ public func duplicate( as target: FileDescriptor? = nil, retryOnInterrupt: Bool = true @@ -345,7 +345,7 @@ extension FileDescriptor { try _duplicate(as: target, retryOnInterrupt: retryOnInterrupt).get() } - // @available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *) + /*System 0.0.2, @available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *)*/ @usableFromInline internal func _duplicate( as target: FileDescriptor?, @@ -370,7 +370,10 @@ extension FileDescriptor { public func dup2() throws -> FileDescriptor { fatalError("Not implemented") } - +} + +/*System 1.1.0, @available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *)*/ +extension FileDescriptor { #if !os(Windows) /// Create a pipe, a unidirectional data channel which can be used for interprocess communication. /// @@ -378,11 +381,12 @@ extension FileDescriptor { /// /// The corresponding C function is `pipe`. @_alwaysEmitIntoClient - // @available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *) + /*System 1.1.0, @available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *)*/ public static func pipe() throws -> (readEnd: FileDescriptor, writeEnd: FileDescriptor) { try _pipe().get() } + /*System 1.1.0, @available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *)*/ @usableFromInline internal static func _pipe() -> Result<(readEnd: FileDescriptor, writeEnd: FileDescriptor), Errno> { var fds: (Int32, Int32) = (-1, -1) diff --git a/Sources/System/FilePath/FilePath.swift b/Sources/System/FilePath/FilePath.swift index c2797f5e..1aaa27ec 100644 --- a/Sources/System/FilePath/FilePath.swift +++ b/Sources/System/FilePath/FilePath.swift @@ -40,7 +40,7 @@ /// // TODO(docs): Section on all the new syntactic operations, lexical normalization, decomposition, // components, etc. -// @available(macOS 10.16, iOS 14.0, watchOS 7.0, tvOS 14.0, *) +/*System 0.0.1, @available(macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0, *)*/ public struct FilePath { internal var _storage: SystemString @@ -60,12 +60,12 @@ public struct FilePath { } } -// @available(macOS 10.16, iOS 14.0, watchOS 7.0, tvOS 14.0, *) +/*System 0.0.1, @available(macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0, *)*/ extension FilePath { /// The length of the file path, excluding the null terminator. public var length: Int { _storage.length } } -// @available(macOS 10.16, iOS 14.0, watchOS 7.0, tvOS 14.0, *) +/*System 0.0.1, @available(macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0, *)*/ extension FilePath: Hashable, Codable {} diff --git a/Sources/System/FilePath/FilePathComponentView.swift b/Sources/System/FilePath/FilePathComponentView.swift index 5a4a8dc6..959ff64c 100644 --- a/Sources/System/FilePath/FilePathComponentView.swift +++ b/Sources/System/FilePath/FilePathComponentView.swift @@ -9,7 +9,7 @@ // MARK: - API -// @available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *) +/*System 0.0.2, @available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *)*/ extension FilePath { /// A bidirectional, range replaceable collection of the non-root components /// that make up a file path. @@ -89,7 +89,7 @@ extension FilePath { #endif } -// @available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *) +/*System 0.0.2, @available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *)*/ extension FilePath.ComponentView: BidirectionalCollection { public typealias Element = FilePath.Component public struct Index: Comparable, Hashable { @@ -123,7 +123,7 @@ extension FilePath.ComponentView: BidirectionalCollection { } } -// @available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *) +/*System 0.0.2, @available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *)*/ extension FilePath.ComponentView: RangeReplaceableCollection { public init() { self.init(FilePath()) @@ -173,7 +173,7 @@ extension FilePath.ComponentView: RangeReplaceableCollection { } } -// @available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *) +/*System 0.0.2, @available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *)*/ extension FilePath { /// Create a file path from a root and a collection of components. public init( diff --git a/Sources/System/FilePath/FilePathComponents.swift b/Sources/System/FilePath/FilePathComponents.swift index a47a05f3..d6b043f3 100644 --- a/Sources/System/FilePath/FilePathComponents.swift +++ b/Sources/System/FilePath/FilePathComponents.swift @@ -9,7 +9,7 @@ // MARK: - API -// @available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *) +/*System 0.0.2, @available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *)*/ extension FilePath { /// Represents a root of a file path. /// @@ -73,7 +73,7 @@ extension FilePath { } } -// @available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *) +/*System 0.0.2, @available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *)*/ extension FilePath.Component { /// Whether a component is a regular file or directory name, or a special diff --git a/Sources/System/FilePath/FilePathString.swift b/Sources/System/FilePath/FilePathString.swift index 6116cb6e..2c245e9b 100644 --- a/Sources/System/FilePath/FilePathString.swift +++ b/Sources/System/FilePath/FilePathString.swift @@ -9,7 +9,7 @@ // MARK: - Platform string -// @available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *) +/*System 0.0.2, @available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *)*/ extension FilePath { /// Creates a file path by copying bytes from a null-terminated platform /// string. @@ -45,7 +45,7 @@ extension FilePath { } } -// @available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *) +/*System 0.0.2, @available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *)*/ extension FilePath.Component { /// Creates a file path component by copying bytes from a null-terminated /// platform string. @@ -82,7 +82,7 @@ extension FilePath.Component { } } -// @available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *) +/*System 0.0.2, @available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *)*/ extension FilePath.Root { /// Creates a file path root by copying bytes from a null-terminated platform /// string. @@ -120,7 +120,7 @@ extension FilePath.Root { // MARK: - String literals -// @available(macOS 10.16, iOS 14.0, watchOS 7.0, tvOS 14.0, *) +/*System 0.0.1, @available(macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0, *)*/ extension FilePath: ExpressibleByStringLiteral { /// Creates a file path from a string literal. /// @@ -139,7 +139,7 @@ extension FilePath: ExpressibleByStringLiteral { } } -// @available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *) +/*System 0.0.2, @available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *)*/ extension FilePath.Component: ExpressibleByStringLiteral { /// Create a file path component from a string literal. /// @@ -164,7 +164,7 @@ extension FilePath.Component: ExpressibleByStringLiteral { } } -// @available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *) +/*System 0.0.2, @available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *)*/ extension FilePath.Root: ExpressibleByStringLiteral { /// Create a file path root from a string literal. /// @@ -189,7 +189,7 @@ extension FilePath.Root: ExpressibleByStringLiteral { // MARK: - Printing and dumping -// @available(macOS 10.16, iOS 14.0, watchOS 7.0, tvOS 14.0, *) +/*System 0.0.1, @available(macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0, *)*/ extension FilePath: CustomStringConvertible, CustomDebugStringConvertible { /// A textual representation of the file path. /// @@ -205,7 +205,7 @@ extension FilePath: CustomStringConvertible, CustomDebugStringConvertible { public var debugDescription: String { description.debugDescription } } -// @available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *) +/*System 0.0.2, @available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *)*/ extension FilePath.Component: CustomStringConvertible, CustomDebugStringConvertible { /// A textual representation of the path component. @@ -222,7 +222,7 @@ extension FilePath.Component: CustomStringConvertible, CustomDebugStringConverti public var debugDescription: String { description.debugDescription } } -// @available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *) +/*System 0.0.2, @available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *)*/ extension FilePath.Root: CustomStringConvertible, CustomDebugStringConvertible { /// A textual representation of the path root. @@ -242,7 +242,7 @@ extension FilePath.Root: CustomStringConvertible, CustomDebugStringConvertible { // MARK: - Convenience helpers // Convenience helpers -// @available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *) +/*System 0.0.2, @available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *)*/ extension FilePath { /// Creates a string by interpreting the path’s content as UTF-8 on Unix /// and UTF-16 on Windows. @@ -253,7 +253,7 @@ extension FilePath { } } -// @available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *) +/*System 0.0.2, @available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *)*/ extension FilePath.Component { /// Creates a string by interpreting the component’s content as UTF-8 on Unix /// and UTF-16 on Windows. @@ -264,7 +264,7 @@ extension FilePath.Component { } } -// @available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *) +/*System 0.0.2, @available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *)*/ extension FilePath.Root { /// On Unix, this returns `"/"`. /// @@ -278,7 +278,7 @@ extension FilePath.Root { // MARK: - Decoding and validating -// @available(macOS 10.16, iOS 14.0, watchOS 7.0, tvOS 14.0, *) +/*System 0.0.1, @available(macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0, *)*/ extension String { /// Creates a string by interpreting the file path's content as UTF-8 on Unix /// and UTF-16 on Windows. @@ -308,7 +308,7 @@ extension String { } } -// @available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *) +/*System 0.0.2, @available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *)*/ extension String { /// Creates a string by interpreting the path component's content as UTF-8 on /// Unix and UTF-16 on Windows. @@ -338,7 +338,7 @@ extension String { } } -// @available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *) +/*System 0.0.2, @available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *)*/ extension String { /// On Unix, creates the string `"/"` /// @@ -391,7 +391,7 @@ extension String { // MARK: - Deprecations -// @available(macOS 10.16, iOS 14.0, watchOS 7.0, tvOS 14.0, *) +/*System 0.0.1, @available(macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0, *)*/ extension String { @available(*, deprecated, renamed: "init(decoding:)") public init(_ path: FilePath) { self.init(decoding: path) } @@ -401,7 +401,7 @@ extension String { } #if !os(Windows) -// @available(macOS 10.16, iOS 14.0, watchOS 7.0, tvOS 14.0, *) +/*System 0.0.1, @available(macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0, *)*/ extension FilePath { /// For backwards compatibility only. This initializer is equivalent to /// the preferred `FilePath(platformString:)`. diff --git a/Sources/System/FilePath/FilePathSyntax.swift b/Sources/System/FilePath/FilePathSyntax.swift index b9f20fef..b02de33e 100644 --- a/Sources/System/FilePath/FilePathSyntax.swift +++ b/Sources/System/FilePath/FilePathSyntax.swift @@ -9,7 +9,7 @@ // MARK: - Query API -// @available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *) +/*System 0.0.2, @available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *)*/ extension FilePath { /// Returns true if this path uniquely identifies the location of /// a file without reference to an additional starting location. @@ -101,7 +101,7 @@ extension FilePath { } // MARK: - Decompose a path -// @available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *) +/*System 0.0.2, @available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *)*/ extension FilePath { /// Returns the root of a path if there is one, otherwise `nil`. /// @@ -186,7 +186,7 @@ extension FilePath { } } -// @available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *) +/*System 0.0.2, @available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *)*/ extension FilePath { /// Returns the final component of the path. /// Returns `nil` if the path is empty or only contains a root. @@ -258,7 +258,7 @@ extension FilePath { } } -// @available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *) +/*System 0.0.2, @available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *)*/ extension FilePath.Component { /// The extension of this file or directory component. /// @@ -291,7 +291,7 @@ extension FilePath.Component { } } -// @available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *) +/*System 0.0.2, @available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *)*/ extension FilePath { /// The extension of the file or directory last component. @@ -358,7 +358,7 @@ extension FilePath { } -// @available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *) +/*System 0.0.2, @available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *)*/ extension FilePath { /// Whether the path is in lexical-normal form, that is `.` and `..` /// components have been collapsed lexically (i.e. without following @@ -439,7 +439,7 @@ extension FilePath { } // Modification and concatenation API -// @available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *) +/*System 0.0.2, @available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *)*/ extension FilePath { /// If `prefix` is a prefix of `self`, removes it and returns `true`. /// Otherwise returns `false`. @@ -601,7 +601,7 @@ extension FilePath { } // MARK - Renamed -// @available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *) +/*System 0.0.2, @available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *)*/ extension FilePath { @available(*, unavailable, renamed: "removingLastComponent()") public var dirname: FilePath { removingLastComponent() } diff --git a/Sources/System/FilePermissions.swift b/Sources/System/FilePermissions.swift index 1ad9434a..0780ee07 100644 --- a/Sources/System/FilePermissions.swift +++ b/Sources/System/FilePermissions.swift @@ -17,7 +17,7 @@ /// let perms = FilePermissions(rawValue: 0o644) /// perms == [.ownerReadWrite, .groupRead, .otherRead] // true @frozen -// @available(macOS 10.16, iOS 14.0, watchOS 7.0, tvOS 14.0, *) +/*System 0.0.1, @available(macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0, *)*/ public struct FilePermissions: OptionSet, Hashable, Codable { /// The raw C file permissions. @_alwaysEmitIntoClient @@ -135,7 +135,7 @@ public struct FilePermissions: OptionSet, Hashable, Codable { public static var saveText: FilePermissions { FilePermissions(0o1000) } } -// @available(macOS 10.16, iOS 14.0, watchOS 7.0, tvOS 14.0, *) +/*System 0.0.1, @available(macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0, *)*/ extension FilePermissions : CustomStringConvertible, CustomDebugStringConvertible { diff --git a/Sources/System/Internals/CInterop.swift b/Sources/System/Internals/CInterop.swift index 7f3b96d7..2a9832ec 100644 --- a/Sources/System/Internals/CInterop.swift +++ b/Sources/System/Internals/CInterop.swift @@ -10,7 +10,7 @@ // MARK: - Public typealiases /// The C `mode_t` type. -// @available(macOS 10.16, iOS 14.0, watchOS 7.0, tvOS 14.0, *) +/*System 0.0.1, @available(macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0, *)*/ @available(*, deprecated, renamed: "CInterop.Mode") public typealias CModeT = CInterop.Mode @@ -27,7 +27,7 @@ import ucrt #endif /// A namespace for C and platform types -// @available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *) +/*System 0.0.2, @available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *)*/ public enum CInterop { #if os(Windows) public typealias Mode = CInt diff --git a/Sources/System/PlatformString.swift b/Sources/System/PlatformString.swift index 6464869e..358f3494 100644 --- a/Sources/System/PlatformString.swift +++ b/Sources/System/PlatformString.swift @@ -7,7 +7,7 @@ See https://swift.org/LICENSE.txt for license information */ -// @available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *) +/*System 0.0.2, @available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *)*/ extension String { /// Creates a string by interpreting the null-terminated platform string as /// UTF-8 on Unix and UTF-16 on Windows. diff --git a/Sources/System/Util.swift b/Sources/System/Util.swift index c038d461..ac25830f 100644 --- a/Sources/System/Util.swift +++ b/Sources/System/Util.swift @@ -8,21 +8,21 @@ */ // Results in errno if i == -1 -// @available(macOS 10.16, iOS 14.0, watchOS 7.0, tvOS 14.0, *) +/*System 0.0.1, @available(macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0, *)*/ private func valueOrErrno( _ i: I ) -> Result { i == -1 ? .failure(Errno.current) : .success(i) } -// @available(macOS 10.16, iOS 14.0, watchOS 7.0, tvOS 14.0, *) +/*System 0.0.1, @available(macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0, *)*/ private func nothingOrErrno( _ i: I ) -> Result<(), Errno> { valueOrErrno(i).map { _ in () } } -// @available(macOS 10.16, iOS 14.0, watchOS 7.0, tvOS 14.0, *) +/*System 0.0.1, @available(macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0, *)*/ internal func valueOrErrno( retryOnInterrupt: Bool, _ f: () -> I ) -> Result { @@ -36,7 +36,7 @@ internal func valueOrErrno( } while true } -// @available(macOS 10.16, iOS 14.0, watchOS 7.0, tvOS 14.0, *) +/*System 0.0.1, @available(macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0, *)*/ internal func nothingOrErrno( retryOnInterrupt: Bool, _ f: () -> I ) -> Result<(), Errno> { diff --git a/Tests/SystemTests/ErrnoTest.swift b/Tests/SystemTests/ErrnoTest.swift index 86a7664e..4bbe88f6 100644 --- a/Tests/SystemTests/ErrnoTest.swift +++ b/Tests/SystemTests/ErrnoTest.swift @@ -15,7 +15,7 @@ import SystemPackage import System #endif -// @available(macOS 10.16, iOS 14.0, watchOS 7.0, tvOS 14.0, *) +/*System 0.0.1, @available(macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0, *)*/ final class ErrnoTest: XCTestCase { func testConstants() { XCTAssert(EPERM == Errno.notPermitted.rawValue) diff --git a/Tests/SystemTests/FileOperationsTest.swift b/Tests/SystemTests/FileOperationsTest.swift index a65301df..e28efd5d 100644 --- a/Tests/SystemTests/FileOperationsTest.swift +++ b/Tests/SystemTests/FileOperationsTest.swift @@ -15,7 +15,7 @@ import SystemPackage import System #endif -// @available(macOS 10.16, iOS 14.0, watchOS 7.0, tvOS 14.0, *) +/*System 0.0.1, @available(macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0, *)*/ final class FileOperationsTest: XCTestCase { func testSyscalls() { let fd = FileDescriptor(rawValue: 1) diff --git a/Tests/SystemTests/FilePathTests/FilePathComponentsTest.swift b/Tests/SystemTests/FilePathTests/FilePathComponentsTest.swift index 0979f952..693fc11f 100644 --- a/Tests/SystemTests/FilePathTests/FilePathComponentsTest.swift +++ b/Tests/SystemTests/FilePathTests/FilePathComponentsTest.swift @@ -15,7 +15,7 @@ import XCTest @testable import System #endif -// @available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *) +/*System 0.0.2, @available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *)*/ struct TestPathComponents: TestCase { var path: FilePath var expectedRoot: FilePath.Root? @@ -100,7 +100,7 @@ struct TestPathComponents: TestCase { } } -// @available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *) +/*System 0.0.2, @available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *)*/ final class FilePathComponentsTest: XCTestCase { func testAdHocRRC() { var path: FilePath = "/usr/local/bin" diff --git a/Tests/SystemTests/FilePathTests/FilePathExtras.swift b/Tests/SystemTests/FilePathTests/FilePathExtras.swift index a9fa61ad..1f27cd84 100644 --- a/Tests/SystemTests/FilePathTests/FilePathExtras.swift +++ b/Tests/SystemTests/FilePathTests/FilePathExtras.swift @@ -6,7 +6,7 @@ #endif // Why can't I write this extension on `FilePath.ComponentView.SubSequence`? -// @available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *) +/*System 0.0.2, @available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *)*/ extension Slice where Base == FilePath.ComponentView { internal var _storageSlice: SystemString.SubSequence { base._path._storage[self.startIndex._storage ..< self.endIndex._storage] @@ -15,7 +15,7 @@ extension Slice where Base == FilePath.ComponentView { // Proposed API that didn't make the cut, but we stil want to keep our testing for -// @available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *) +/*System 0.0.2, @available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *)*/ extension FilePath { /// Returns `self` relative to `base`. /// This does not cosult the file system or resolve symlinks. diff --git a/Tests/SystemTests/FilePathTests/FilePathParsingTest.swift b/Tests/SystemTests/FilePathTests/FilePathParsingTest.swift index 1636568a..1c249e03 100644 --- a/Tests/SystemTests/FilePathTests/FilePathParsingTest.swift +++ b/Tests/SystemTests/FilePathTests/FilePathParsingTest.swift @@ -68,7 +68,7 @@ extension ParsingTestCase { @testable import System #endif -// @available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *) +/*System 0.0.2, @available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *)*/ final class FilePathParsingTest: XCTestCase { func testNormalization() { let unixPaths: Array = [ diff --git a/Tests/SystemTests/FilePathTests/FilePathSyntaxTest.swift b/Tests/SystemTests/FilePathTests/FilePathSyntaxTest.swift index cc931d4d..fca6b86a 100644 --- a/Tests/SystemTests/FilePathTests/FilePathSyntaxTest.swift +++ b/Tests/SystemTests/FilePathTests/FilePathSyntaxTest.swift @@ -146,7 +146,7 @@ extension SyntaxTestCase { } } -// @available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *) +/*System 0.0.2, @available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *)*/ extension SyntaxTestCase { func testComponents(_ path: FilePath, expected: [String]) { let expectedComponents = expected.map { FilePath.Component($0)! } @@ -342,7 +342,7 @@ private struct WindowsRootTestCase: TestCase { var line: UInt } -// @available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *) +/*System 0.0.2, @available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *)*/ extension WindowsRootTestCase { func runAllTests() { withWindowsPaths(enabled: true) { @@ -357,7 +357,7 @@ extension WindowsRootTestCase { } } -// @available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *) +/*System 0.0.2, @available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *)*/ final class FilePathSyntaxTest: XCTestCase { func testPathSyntax() { let unixPaths: Array = [ diff --git a/Tests/SystemTests/FilePathTests/FilePathTest.swift b/Tests/SystemTests/FilePathTests/FilePathTest.swift index 93bc3504..168eab5a 100644 --- a/Tests/SystemTests/FilePathTests/FilePathTest.swift +++ b/Tests/SystemTests/FilePathTests/FilePathTest.swift @@ -15,7 +15,7 @@ import SystemPackage import System #endif -// @available(macOS 10.16, iOS 14.0, watchOS 7.0, tvOS 14.0, *) +/*System 0.0.1, @available(macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0, *)*/ func filePathFromUnterminatedBytes(_ bytes: S) -> FilePath where S.Element == UInt8 { var array = Array(bytes) assert(array.last != 0, "already null terminated") @@ -27,7 +27,7 @@ func filePathFromUnterminatedBytes(_ bytes: S) -> FilePath where S. } let invalidBytes: [UInt8] = [0x2F, 0x61, 0x2F, 0x62, 0x2F, 0x83] -// @available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *) +/*System 0.0.2, @available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *)*/ final class FilePathTest: XCTestCase { struct TestPath { let filePath: FilePath diff --git a/Tests/SystemTests/FileTypesTest.swift b/Tests/SystemTests/FileTypesTest.swift index 3c40a154..b8cf4969 100644 --- a/Tests/SystemTests/FileTypesTest.swift +++ b/Tests/SystemTests/FileTypesTest.swift @@ -15,7 +15,7 @@ import SystemPackage import System #endif -// @available(macOS 10.16, iOS 14.0, watchOS 7.0, tvOS 14.0, *) +/*System 0.0.1, @available(macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0, *)*/ final class FileDescriptorTest: XCTestCase { func testStandardDescriptors() { XCTAssertEqual(FileDescriptor.standardInput.rawValue, 0) @@ -60,7 +60,7 @@ final class FileDescriptorTest: XCTestCase { } -// @available(macOS 10.16, iOS 14.0, watchOS 7.0, tvOS 14.0, *) +/*System 0.0.1, @available(macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0, *)*/ final class FilePermissionsTest: XCTestCase { func testPermissions() { diff --git a/Tests/SystemTests/MockingTest.swift b/Tests/SystemTests/MockingTest.swift index 38545988..8d701e22 100644 --- a/Tests/SystemTests/MockingTest.swift +++ b/Tests/SystemTests/MockingTest.swift @@ -15,7 +15,7 @@ import XCTest @testable import System #endif -// @available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *) +/*System 0.0.2, @available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *)*/ final class MockingTest: XCTestCase { func testMocking() { XCTAssertFalse(mockingEnabled) From 0c24d31e08fe7e72745b3273eec0b5bfe7e9a07a Mon Sep 17 00:00:00 2001 From: Karoy Lorentey Date: Mon, 20 Dec 2021 17:14:42 -0800 Subject: [PATCH 054/427] Add script to manage availability annotations By running this script, we can expand comments of the form ``` /*System 0.0.1*/ ``` Into either a longer comment that includes the associated OS versions numbers, ``` /*System 0.0.1, @available(macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0, *)*/ ``` or, when the `--attribute` option is used, into an actual `@available` attribute: ``` /*System 0.0.1*/@available(macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0, *) ``` The script can be run repeatedly to switch between these two forms, as needed. --- Utilities/expand-availability.py | 86 ++++++++++++++++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100755 Utilities/expand-availability.py diff --git a/Utilities/expand-availability.py b/Utilities/expand-availability.py new file mode 100755 index 00000000..60d31c19 --- /dev/null +++ b/Utilities/expand-availability.py @@ -0,0 +1,86 @@ +#!/usr/bin/env python3 + +# This script uses the file `availability-macros.def` to automatically +# add/remove `@available` attributes to declarations in Swift sources +# in this package. +# +# In order for this to work, ABI-impacting declarations need to be annotated +# with special comments in the following format: +# +# /*System 0.0.2*/ +# public func greeting() -> String { +# "Hello" +# } +# +# The script adds full availability incantations to these comments. It can run +# in one of two modes: +# +# By default, `expand-availability.py` expands availability macros within the +# comments. This is useful during package development to cross-reference +# availability across `SystemPackage` and the ABI-stable `System` module that +# ships in Apple's OS releases: +# +# /*System 0.0.2, @available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *)*/ +# public func greeting() -> String { +# "Hello" +# } +# +# `expand-availability.py --attributes` adds actual availability attributes. +# This is used by maintainers to build ABI stable releases of System on Apple's +# platforms: +# +# /*System 0.0.2*/@available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *) +# public func greeting() -> String { +# "Hello" +# } +# +# The script recognizes these attributes and updates/removes them on each run, +# so you can run the script to enable/disable attributes without worrying about +# duplicate attributes. + +import os +import os.path +import fileinput +import re +import sys +import argparse + +versions = { + "System 0.0.1": "macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0", + "System 0.0.2": "macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0", + "System 1.1.0": "macOS 9999, iOS 9999, watchOS 9999, tvOS 9999", +} + +parser = argparse.ArgumentParser(description="Expand availability macros.") +parser.add_argument("--attributes", help="Add @available attributes", + action="store_true") +args = parser.parse_args() + +def swift_sources_in(path): + result = [] + for (dir, _, files) in os.walk(path): + for file in files: + extension = os.path.splitext(file)[1] + if extension == ".swift": + result.append(os.path.join(dir, file)) + return result + +macro_pattern = re.compile( + r"/\*(System [^ *]+)(, @available\([^)]*\))?\*/(@available\([^)]*\))?") + +sources = swift_sources_in("Sources") + swift_sources_in("Tests") +for line in fileinput.input(files=sources, inplace=True): + match = re.search(macro_pattern, line) + if match: + system_version = match.group(1) + expansion = versions[system_version] + if expansion is None: + raise ValueError("{0}:{1}: error: Unknown System version '{0}'" + .format(fileinput.filename(), fileinput.lineno(), + system_version)) + if args.attributes: + replacement = "/*{0}*/@available({1}, *)".format(system_version, expansion) + else: + replacement = "/*{0}, @available({1}, *)*/".format(system_version, expansion) + line = line[:match.start()] + replacement + line[match.end():] + print(line, end="") From fd82b5a0ceb48c66ff946650710d3c0fa32f366d Mon Sep 17 00:00:00 2001 From: Karoy Lorentey Date: Mon, 20 Dec 2021 17:52:48 -0800 Subject: [PATCH 055/427] Fix docs --- Utilities/expand-availability.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/Utilities/expand-availability.py b/Utilities/expand-availability.py index 60d31c19..94b9aed7 100755 --- a/Utilities/expand-availability.py +++ b/Utilities/expand-availability.py @@ -1,8 +1,7 @@ #!/usr/bin/env python3 -# This script uses the file `availability-macros.def` to automatically -# add/remove `@available` attributes to declarations in Swift sources -# in this package. +# This script can be used to automatically add/remove `@available` attributes to +# declarations in Swift sources in this package. # # In order for this to work, ABI-impacting declarations need to be annotated # with special comments in the following format: @@ -34,9 +33,8 @@ # "Hello" # } # -# The script recognizes these attributes and updates/removes them on each run, -# so you can run the script to enable/disable attributes without worrying about -# duplicate attributes. +# The script recognizes all three forms of these annotations and updates them on +# every run, so we can run the script to enable/disable attributes as needed. import os import os.path From c0e8ef346b5cfc350dbc27aa7d74c9bcc6afea26 Mon Sep 17 00:00:00 2001 From: Karoy Lorentey Date: Tue, 21 Dec 2021 17:20:15 -0800 Subject: [PATCH 056/427] Update package with changes from recent ABI stable releases This brings the contents of this package up to date with versions of the ABI stable `System` module that shipped in recent iOS/macOS releases. Most changes only touch internal implementation details, but some ABI-related fixes also affect the public API. There are no source-incompatible changes. - ABI stability fixes (un-deprecate CModeT, don't use PlatformChar in inlinable contexts that need to deploy to iOS 14) - New utilities: `_RawBuffer`, `Array._withCStringArray`, `_withOptionalUnsafePointerOrNull`, `_withStackBuffer`, `system_memset` - Prefer using explicit optionals to implicitly-unwrapped ones - Add support for wildcard matching in mocking tests - A bunch of new availability annotations. (I expect there will be more of these coming in future PRs.) --- .gitignore | 2 +- Sources/System/CMakeLists.txt | 2 + Sources/System/FileDescriptor.swift | 3 +- Sources/System/FileOperations.swift | 11 +- .../FilePath/FilePathComponentView.swift | 4 +- .../System/FilePath/FilePathComponents.swift | 12 +- Sources/System/FilePath/FilePathParsing.swift | 8 ++ Sources/System/FilePath/FilePathString.swift | 23 +++- Sources/System/FilePermissions.swift | 6 +- Sources/System/Internals/Backcompat.swift | 29 +++++ Sources/System/Internals/CInterop.swift | 23 ++-- Sources/System/Internals/Constants.swift | 3 +- Sources/System/Internals/Exports.swift | 16 ++- Sources/System/Internals/Mocking.swift | 7 +- Sources/System/Internals/RawBuffer.swift | 102 +++++++++++++++ Sources/System/Internals/Syscalls.swift | 10 +- Sources/System/PlatformString.swift | 3 + Sources/System/Util+StringArray.swift | 117 ++++++++++++++++++ Sources/System/Util.swift | 37 +++++- Tests/SystemTests/TestingInfrastructure.swift | 62 ++++++++-- Tests/SystemTests/UtilTests.swift | 89 +++++++++++++ 21 files changed, 523 insertions(+), 46 deletions(-) create mode 100644 Sources/System/Internals/Backcompat.swift create mode 100644 Sources/System/Internals/RawBuffer.swift create mode 100644 Sources/System/Util+StringArray.swift create mode 100644 Tests/SystemTests/UtilTests.swift diff --git a/.gitignore b/.gitignore index 36bbb6fb..2196fff3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,6 @@ .DS_Store /.build /Packages -/*.xcodeproj +swift-system.xcodeproj xcuserdata/ .*.sw? diff --git a/Sources/System/CMakeLists.txt b/Sources/System/CMakeLists.txt index 43958d31..990f1c6a 100644 --- a/Sources/System/CMakeLists.txt +++ b/Sources/System/CMakeLists.txt @@ -16,6 +16,7 @@ add_library(SystemPackage PlatformString.swift SystemString.swift Util.swift + Util+StringArray.swift UtilConsumers.swift) set_target_properties(SystemPackage PROPERTIES INTERFACE_INCLUDE_DIRECTORIES ${CMAKE_Swift_MODULE_DIRECTORY}) @@ -32,6 +33,7 @@ target_sources(SystemPackage PRIVATE Internals/Constants.swift Internals/Exports.swift Internals/Mocking.swift + Internals/RawBuffer.swift Internals/Syscalls.swift Internals/WindowsSyscallAdapters.swift) target_link_libraries(SystemPackage PUBLIC diff --git a/Sources/System/FileDescriptor.swift b/Sources/System/FileDescriptor.swift index 9a7d319c..ba606962 100644 --- a/Sources/System/FileDescriptor.swift +++ b/Sources/System/FileDescriptor.swift @@ -25,7 +25,8 @@ public struct FileDescriptor: RawRepresentable, Hashable, Codable { public init(rawValue: CInt) { self.rawValue = rawValue } } -// Standard file descriptors +// Standard file descriptors. +/*System 0.0.1, @available(macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0, *)*/ extension FileDescriptor { /// The standard input file descriptor, with a numeric value of 0. @_alwaysEmitIntoClient diff --git a/Sources/System/FileOperations.swift b/Sources/System/FileOperations.swift index 01025632..e90a4946 100644 --- a/Sources/System/FileOperations.swift +++ b/Sources/System/FileOperations.swift @@ -31,7 +31,7 @@ extension FileDescriptor { permissions: FilePermissions? = nil, retryOnInterrupt: Bool = true ) throws -> FileDescriptor { - try path.withPlatformString { + try path.withCString { try FileDescriptor.open( $0, mode, options: options, permissions: permissions, retryOnInterrupt: retryOnInterrupt) } @@ -53,7 +53,7 @@ extension FileDescriptor { /// The corresponding C function is `open`. @_alwaysEmitIntoClient public static func open( - _ path: UnsafePointer, + _ path: UnsafePointer, _ mode: FileDescriptor.AccessMode, options: FileDescriptor.OpenOptions = FileDescriptor.OpenOptions(), permissions: FilePermissions? = nil, @@ -66,7 +66,7 @@ extension FileDescriptor { @usableFromInline internal static func _open( - _ path: UnsafePointer, + _ path: UnsafePointer, _ mode: FileDescriptor.AccessMode, options: FileDescriptor.OpenOptions, permissions: FilePermissions?, @@ -308,7 +308,10 @@ extension FileDescriptor { buffer, retryOnInterrupt: retryOnInterrupt) } +} +/*System 0.0.2, @available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *)*/ +extension FileDescriptor { /// Duplicate this file descriptor and return the newly created copy. /// /// - Parameters: @@ -385,7 +388,7 @@ extension FileDescriptor { public static func pipe() throws -> (readEnd: FileDescriptor, writeEnd: FileDescriptor) { try _pipe().get() } - + /*System 1.1.0, @available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *)*/ @usableFromInline internal static func _pipe() -> Result<(readEnd: FileDescriptor, writeEnd: FileDescriptor), Errno> { diff --git a/Sources/System/FilePath/FilePathComponentView.swift b/Sources/System/FilePath/FilePathComponentView.swift index 959ff64c..acbc8f36 100644 --- a/Sources/System/FilePath/FilePathComponentView.swift +++ b/Sources/System/FilePath/FilePathComponentView.swift @@ -40,7 +40,7 @@ extension FilePath { } } - #if SYSTEM_PACKAGE + #if SYSTEM_PACKAGE // Workaround for a __consuming getter bug in Swift 5.3.3 /// View the non-root components that make up this path. public var components: ComponentView { get { ComponentView(self) } @@ -202,6 +202,7 @@ extension FilePath { // MARK: - Internals +/*System 0.0.2, @available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *)*/ extension FilePath.ComponentView: _PathSlice { internal var _range: Range { _start ..< _path._storage.endIndex @@ -214,6 +215,7 @@ extension FilePath.ComponentView: _PathSlice { // MARK: - Invariants +/*System 0.0.2, @available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *)*/ extension FilePath.ComponentView { internal func _invariantCheck() { #if DEBUG diff --git a/Sources/System/FilePath/FilePathComponents.swift b/Sources/System/FilePath/FilePathComponents.swift index d6b043f3..9d57a0bb 100644 --- a/Sources/System/FilePath/FilePathComponents.swift +++ b/Sources/System/FilePath/FilePathComponents.swift @@ -98,6 +98,7 @@ extension FilePath.Component { } } +/*System 0.0.2, @available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *)*/ extension FilePath.Root { // TODO: Windows analysis APIs } @@ -183,15 +184,18 @@ extension _PathSlice { internal var _storage: SystemString { _path._storage } } +/*System 0.0.2, @available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *)*/ extension FilePath.Component: _PathSlice { } +/*System 0.0.2, @available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *)*/ extension FilePath.Root: _PathSlice { internal var _range: Range { (..<_rootEnd).relative(to: _path._storage) } } + +/*System 0.0.1, @available(macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0, *)*/ extension FilePath: _PlatformStringable { - @usableFromInline func _withPlatformString(_ body: (UnsafePointer) throws -> Result) rethrows -> Result { try _storage.withPlatformString(body) } @@ -202,6 +206,7 @@ extension FilePath: _PlatformStringable { } +/*System 0.0.2, @available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *)*/ extension FilePath.Component { // The index of the `.` denoting an extension internal func _extensionIndex() -> SystemString.Index? { @@ -230,6 +235,7 @@ internal func _makeExtension(_ ext: String) -> SystemString { return result } +/*System 0.0.2, @available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *)*/ extension FilePath.Component { internal init?(_ str: SystemString) { // FIXME: explicit null root? Or something else? @@ -242,6 +248,7 @@ extension FilePath.Component { } } +/*System 0.0.2, @available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *)*/ extension FilePath.Root { internal init?(_ str: SystemString) { // FIXME: explicit null root? Or something else? @@ -256,6 +263,7 @@ extension FilePath.Root { // MARK: - Invariants +/*System 0.0.2, @available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *)*/ extension FilePath.Component { // TODO: ensure this all gets easily optimized away in release... internal func _invariantCheck() { @@ -267,6 +275,8 @@ extension FilePath.Component { #endif // DEBUG } } + +/*System 0.0.2, @available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *)*/ extension FilePath.Root { internal func _invariantCheck() { #if DEBUG diff --git a/Sources/System/FilePath/FilePathParsing.swift b/Sources/System/FilePath/FilePathParsing.swift index 26753989..bbfd066f 100644 --- a/Sources/System/FilePath/FilePathParsing.swift +++ b/Sources/System/FilePath/FilePathParsing.swift @@ -111,6 +111,7 @@ extension SystemString { } } +/*System 0.0.1, @available(macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0, *)*/ extension FilePath { internal mutating func _removeTrailingSeparator() { _storage._removeTrailingSeparator() @@ -191,6 +192,7 @@ extension SystemString { } } +/*System 0.0.1, @available(macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0, *)*/ extension FilePath { internal var _relativeStart: SystemString.Index { _storage._relativePathStart @@ -202,6 +204,7 @@ extension FilePath { // Parse separators +/*System 0.0.1, @available(macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0, *)*/ extension FilePath { internal typealias _Index = SystemString.Index @@ -265,6 +268,7 @@ extension FilePath { } } +/*System 0.0.2, @available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *)*/ extension FilePath.ComponentView { // TODO: Store this... internal var _relativeStart: SystemString.Index { @@ -289,6 +293,7 @@ extension SystemString { } } +/*System 0.0.2, @available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *)*/ extension FilePath.Root { // Asserting self is a root, returns whether this is an // absolute root. @@ -313,6 +318,7 @@ extension FilePath.Root { } } +/*System 0.0.1, @available(macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0, *)*/ extension FilePath { internal var _portableDescription: String { guard _windowsPaths else { return description } @@ -331,6 +337,7 @@ internal var _windowsPaths: Bool { #endif } +/*System 0.0.1, @available(macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0, *)*/ extension FilePath { // Whether we should add a separator when doing an append internal var _needsSeparatorForAppend: Bool { @@ -358,6 +365,7 @@ extension FilePath { } // MARK: - Invariants +/*System 0.0.1, @available(macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0, *)*/ extension FilePath { internal func _invariantCheck() { #if DEBUG diff --git a/Sources/System/FilePath/FilePathString.swift b/Sources/System/FilePath/FilePathString.swift index 2c245e9b..2dd2ccee 100644 --- a/Sources/System/FilePath/FilePathString.swift +++ b/Sources/System/FilePath/FilePathString.swift @@ -20,6 +20,7 @@ extension FilePath { self.init(_platformString: platformString) } +#if !os(Windows) // Availability workaround /// Calls the given closure with a pointer to the contents of the file path, /// represented as a null-terminated platform string. /// @@ -37,12 +38,28 @@ extension FilePath { _ body: (UnsafePointer) throws -> Result ) rethrows -> Result { // For backwards deployment, call withCString if available. - #if !os(Windows) return try withCString(body) - #else + } +#else + /// Calls the given closure with a pointer to the contents of the file path, + /// represented as a null-terminated platform string. + /// + /// - Parameter body: A closure with a pointer parameter + /// that points to a null-terminated platform string. + /// If `body` has a return value, + /// that value is also used as the return value for this method. + /// - Returns: The return value, if any, of the `body` closure parameter. + /// + /// The pointer passed as an argument to `body` is valid + /// only during the execution of this method. + /// Don't try to store the pointer for later use. + public func withPlatformString( + _ body: (UnsafePointer) throws -> Result + ) rethrows -> Result { + // For backwards deployment, call withCString if available. return try _withPlatformString(body) - #endif } +#endif } /*System 0.0.2, @available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *)*/ diff --git a/Sources/System/FilePermissions.swift b/Sources/System/FilePermissions.swift index 0780ee07..238ba151 100644 --- a/Sources/System/FilePermissions.swift +++ b/Sources/System/FilePermissions.swift @@ -21,14 +21,14 @@ public struct FilePermissions: OptionSet, Hashable, Codable { /// The raw C file permissions. @_alwaysEmitIntoClient - public let rawValue: CInterop.Mode + public let rawValue: CModeT /// Create a strongly-typed file permission from a raw C value. @_alwaysEmitIntoClient - public init(rawValue: CInterop.Mode) { self.rawValue = rawValue } + public init(rawValue: CModeT) { self.rawValue = rawValue } @_alwaysEmitIntoClient - private init(_ raw: CInterop.Mode) { self.init(rawValue: raw) } + private init(_ raw: CModeT) { self.init(rawValue: raw) } /// Indicates that other users have read-only permission. @_alwaysEmitIntoClient diff --git a/Sources/System/Internals/Backcompat.swift b/Sources/System/Internals/Backcompat.swift new file mode 100644 index 00000000..a376dd2e --- /dev/null +++ b/Sources/System/Internals/Backcompat.swift @@ -0,0 +1,29 @@ +/* + This source file is part of the Swift System open source project + + Copyright (c) 2021 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 +*/ + +extension String { + internal init( + _unsafeUninitializedCapacity capacity: Int, + initializingUTF8With body: (UnsafeMutableBufferPointer) throws -> Int + ) rethrows { + if #available(macOS 11, iOS 14.0, watchOS 7.0, tvOS 14.0, *) { + self = try String( + unsafeUninitializedCapacity: capacity, + initializingUTF8With: body) + return + } + + let array = try Array( + unsafeUninitializedCapacity: capacity + ) { buffer, count in + count = try body(buffer) + } + self = String(decoding: array, as: UTF8.self) + } +} diff --git a/Sources/System/Internals/CInterop.swift b/Sources/System/Internals/CInterop.swift index 2a9832ec..58988133 100644 --- a/Sources/System/Internals/CInterop.swift +++ b/Sources/System/Internals/CInterop.swift @@ -1,19 +1,12 @@ /* 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 - 2021 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 */ -// MARK: - Public typealiases - -/// The C `mode_t` type. -/*System 0.0.1, @available(macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0, *)*/ -@available(*, deprecated, renamed: "CInterop.Mode") -public typealias CModeT = CInterop.Mode - #if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) import Darwin #elseif os(Linux) || os(FreeBSD) || os(Android) @@ -26,6 +19,20 @@ import ucrt #error("Unsupported Platform") #endif +// MARK: - Public typealiases + +// FIXME: `CModeT` ought to be deprecated and replaced with `CInterop.Mode` +// if/when the compiler becomes less strict about availability checking +// of "namespaced" typealiases. (rdar://81722893) +#if os(Windows) +/// The C `mode_t` type. +public typealias CModeT = CInt +#else +/// The C `mode_t` type. +/*System 0.0.1, @available(macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0, *)*/ +public typealias CModeT = mode_t +#endif + /// A namespace for C and platform types /*System 0.0.2, @available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *)*/ public enum CInterop { diff --git a/Sources/System/Internals/Constants.swift b/Sources/System/Internals/Constants.swift index a4dbb89c..e6916761 100644 --- a/Sources/System/Internals/Constants.swift +++ b/Sources/System/Internals/Constants.swift @@ -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 - 2021 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 @@ -528,4 +528,3 @@ internal var _SEEK_HOLE: CInt { SEEK_HOLE } @_alwaysEmitIntoClient internal var _SEEK_DATA: CInt { SEEK_DATA } #endif - diff --git a/Sources/System/Internals/Exports.swift b/Sources/System/Internals/Exports.swift index 2b4ae3be..0a5a26eb 100644 --- a/Sources/System/Internals/Exports.swift +++ b/Sources/System/Internals/Exports.swift @@ -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 - 2021 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 @@ -60,7 +60,10 @@ internal func system_strerror(_ __errnum: Int32) -> UnsafeMutablePointer! strerror(__errnum) } -internal func system_strlen(_ s: UnsafePointer) -> Int { +internal func system_strlen(_ s: UnsafePointer) -> Int { + strlen(s) +} +internal func system_strlen(_ s: UnsafeMutablePointer) -> Int { strlen(s) } @@ -78,6 +81,15 @@ internal func system_platform_strlen(_ s: UnsafePointer) #endif } +// memset for raw buffers +// FIXME: Do we really not have something like this in the stdlib already? +internal func system_memset( + _ buffer: UnsafeMutableRawBufferPointer, + to byte: UInt8 +) { + memset(buffer.baseAddress, CInt(byte), buffer.count) +} + // Interop between String and platfrom string extension String { internal func _withPlatformString( diff --git a/Sources/System/Internals/Mocking.swift b/Sources/System/Internals/Mocking.swift index eb1fe834..405dc342 100644 --- a/Sources/System/Internals/Mocking.swift +++ b/Sources/System/Internals/Mocking.swift @@ -19,9 +19,10 @@ #if ENABLE_MOCKING internal struct Trace { - internal struct Entry: Hashable { - private var name: String - private var arguments: [AnyHashable] + internal struct Entry { + + internal var name: String + internal var arguments: [AnyHashable] internal init(name: String, _ arguments: [AnyHashable]) { self.name = name diff --git a/Sources/System/Internals/RawBuffer.swift b/Sources/System/Internals/RawBuffer.swift new file mode 100644 index 00000000..7f9461c3 --- /dev/null +++ b/Sources/System/Internals/RawBuffer.swift @@ -0,0 +1,102 @@ +/* + This source file is part of the Swift System open source project + + Copyright (c) 2021 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 +*/ + +// A copy-on-write fixed-size buffer of raw memory. +internal struct _RawBuffer { + internal var _storage: Storage? + + internal init() { + self._storage = nil + } + + internal init(minimumCapacity: Int) { + if minimumCapacity > 0 { + self._storage = Storage.create(minimumCapacity: minimumCapacity) + } else { + self._storage = nil + } + } +} + +extension _RawBuffer { + internal var capacity: Int { + _storage?.header ?? 0 // Note: not capacity! + } + + internal mutating func ensureUnique() { + guard _storage != nil else { return } + let unique = isKnownUniquelyReferenced(&_storage) + if !unique { + _storage = _copy(capacity: capacity) + } + } + + internal func _grow(desired: Int) -> Int { + let next = Int(1.75 * Double(self.capacity)) + return Swift.max(next, desired) + } + + internal mutating func ensureUnique(capacity: Int) { + let unique = isKnownUniquelyReferenced(&_storage) + if !unique || self.capacity < capacity { + _storage = _copy(capacity: _grow(desired: capacity)) + } + } + + internal func withUnsafeBytes( + _ body: (UnsafeRawBufferPointer) throws -> R + ) rethrows -> R { + guard let storage = _storage else { + return try body(UnsafeRawBufferPointer(start: nil, count: 0)) + } + return try storage.withUnsafeMutablePointers { count, bytes in + let buffer = UnsafeRawBufferPointer(start: bytes, count: count.pointee) + return try body(buffer) + } + } + + internal mutating func withUnsafeMutableBytes( + _ body: (UnsafeMutableRawBufferPointer) throws -> R + ) rethrows -> R { + guard _storage != nil else { + return try body(UnsafeMutableRawBufferPointer(start: nil, count: 0)) + } + ensureUnique() + return try _storage!.withUnsafeMutablePointers { count, bytes in + let buffer = UnsafeMutableRawBufferPointer(start: bytes, count: count.pointee) + return try body(buffer) + } + } +} + +extension _RawBuffer { + internal class Storage: ManagedBuffer { + internal static func create(minimumCapacity: Int) -> Storage { + Storage.create( + minimumCapacity: minimumCapacity, + makingHeaderWith: { $0.capacity } + ) as! Storage + } + } + + internal func _copy(capacity: Int) -> Storage { + let copy = Storage.create(minimumCapacity: capacity) + copy.withUnsafeMutablePointers { dstlen, dst in + self.withUnsafeBytes { src in + guard src.count > 0 else { return } + assert(src.count <= dstlen.pointee) + UnsafeMutableRawPointer(dst) + .copyMemory( + from: src.baseAddress!, + byteCount: Swift.min(src.count, dstlen.pointee)) + } + } + return copy + } +} diff --git a/Sources/System/Internals/Syscalls.swift b/Sources/System/Internals/Syscalls.swift index 453c02fc..6db3ad42 100644 --- a/Sources/System/Internals/Syscalls.swift +++ b/Sources/System/Internals/Syscalls.swift @@ -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 - 2021 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 @@ -54,7 +54,7 @@ internal func system_close(_ fd: Int32) -> Int32 { // read internal func system_read( - _ fd: Int32, _ buf: UnsafeMutableRawPointer!, _ nbyte: Int + _ fd: Int32, _ buf: UnsafeMutableRawPointer?, _ nbyte: Int ) -> Int { #if ENABLE_MOCKING if mockingEnabled { return _mockInt(fd, buf, nbyte) } @@ -64,7 +64,7 @@ internal func system_read( // pread internal func system_pread( - _ fd: Int32, _ buf: UnsafeMutableRawPointer!, _ nbyte: Int, _ offset: off_t + _ fd: Int32, _ buf: UnsafeMutableRawPointer?, _ nbyte: Int, _ offset: off_t ) -> Int { #if ENABLE_MOCKING if mockingEnabled { return _mockInt(fd, buf, nbyte, offset) } @@ -84,7 +84,7 @@ internal func system_lseek( // write internal func system_write( - _ fd: Int32, _ buf: UnsafeRawPointer!, _ nbyte: Int + _ fd: Int32, _ buf: UnsafeRawPointer?, _ nbyte: Int ) -> Int { #if ENABLE_MOCKING if mockingEnabled { return _mockInt(fd, buf, nbyte) } @@ -94,7 +94,7 @@ internal func system_write( // pwrite internal func system_pwrite( - _ fd: Int32, _ buf: UnsafeRawPointer!, _ nbyte: Int, _ offset: off_t + _ fd: Int32, _ buf: UnsafeRawPointer?, _ nbyte: Int, _ offset: off_t ) -> Int { #if ENABLE_MOCKING if mockingEnabled { return _mockInt(fd, buf, nbyte, offset) } diff --git a/Sources/System/PlatformString.swift b/Sources/System/PlatformString.swift index 358f3494..9fe398a2 100644 --- a/Sources/System/PlatformString.swift +++ b/Sources/System/PlatformString.swift @@ -58,6 +58,7 @@ extension String { } +/*System 0.0.2, @available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *)*/ extension CInterop.PlatformChar { internal var _platformCodeUnit: CInterop.PlatformUnicodeEncoding.CodeUnit { #if os(Windows) @@ -67,6 +68,8 @@ extension CInterop.PlatformChar { #endif } } + +/*System 0.0.2, @available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *)*/ extension CInterop.PlatformUnicodeEncoding.CodeUnit { internal var _platformChar: CInterop.PlatformChar { #if os(Windows) diff --git a/Sources/System/Util+StringArray.swift b/Sources/System/Util+StringArray.swift new file mode 100644 index 00000000..2a8ba286 --- /dev/null +++ b/Sources/System/Util+StringArray.swift @@ -0,0 +1,117 @@ +/* + This source file is part of the Swift System open source project + + Copyright (c) 2021 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 +*/ + +extension Array where Element == String { + internal typealias CStr = UnsafePointer? + + /// Call `body` with a buffer of `UnsafePointer?` values, + /// suitable for passing to a C function that expects a C string array. + /// The buffer is guaranteed to be followed by an extra storage slot + /// containing a null pointer. (For C functions that expect an array + /// terminated with a null pointer.) + /// + /// This function is careful not to heap allocate memory unless there are + /// too many strings, or if it needs to copy too much character data. + internal func _withCStringArray( + _ body: (UnsafeBufferPointer?>) throws -> R + ) rethrows -> R { + if self.count == 0 { + // Fast path: empty array. + let p: CStr = nil + return try Swift.withUnsafePointer(to: p) { array in + try body(UnsafeBufferPointer(start: array, count: 0)) + } + } + #if SYSTEM_OS_BUILD // String._guts isn't accessible from SwiftPM or CMake + if self.count == 1, self[0]._guts._isLargeZeroTerminatedContiguousUTF8 { + // Fast path: Single fast string. + let start = self[0]._guts._largeContiguousUTF8CodeUnits.baseAddress! + var p: (CStr, CStr) = ( + UnsafeRawPointer(start).assumingMemoryBound(to: CChar.self), + nil + ) + return try Swift.withUnsafeBytes(of: &p) { buffer in + let start = buffer.baseAddress!.assumingMemoryBound(to: CStr.self) + return try body(UnsafeBufferPointer(start: start, count: 1)) + } + } + #endif + // We need to create a buffer for the C array. + return try _withStackBuffer( + capacity: (self.count + 1) * MemoryLayout.stride + ) { array in + let array = array.bindMemory(to: CStr.self) + // Calculate number of bytes we need for character storage + let bytes = self.reduce(into: 0) { count, string in + #if SYSTEM_OS_BUILD + if string._guts._isLargeZeroTerminatedContiguousUTF8 { return } + #endif + count += string.utf8.count + 1 // Plus one for terminating NUL + } + #if SYSTEM_OS_BUILD + if bytes == 0 { + // Fast path: we only contain strings with stable null-terminated storage + for i in self.indices { + let string = self[i] + precondition(string._guts._isLargeZeroTerminatedContiguousUTF8) + let address = string._guts._largeContiguousUTF8CodeUnits.baseAddress! + array[i] = UnsafeRawPointer(address).assumingMemoryBound(to: CChar.self) + } + array[self.count] = nil + return try body(UnsafeBufferPointer(rebasing: array.dropLast())) + } + #endif + return try _withStackBuffer(capacity: bytes) { chars in + var chars = chars + for i in self.indices { + let (cstr, scratchUsed) = self[i]._getCStr(with: chars) + array[i] = cstr.assumingMemoryBound(to: CChar.self) + chars = .init(rebasing: chars[scratchUsed...]) + } + array[self.count] = nil + return try body(UnsafeBufferPointer(rebasing: array.dropLast())) + } + } + } +} + +extension String { + fileprivate func _getCStr( + with scratch: UnsafeMutableRawBufferPointer + ) -> (cstr: UnsafeRawPointer, scratchUsed: Int) { + #if SYSTEM_OS_BUILD + if _guts._isLargeZeroTerminatedContiguousUTF8 { + // This is a wonderful string, we can just use its storage address. + let address = _guts._largeContiguousUTF8CodeUnits.baseAddress! + return (UnsafeRawPointer(address), 0) + } + #endif + let r: (UnsafeRawPointer, Int)? = self.utf8.withContiguousStorageIfAvailable { source in + // This is a somewhat okay string -- we need to use memcpy. + precondition(source.count <= scratch.count) + let start = scratch.baseAddress! + start.copyMemory(from: source.baseAddress!, byteCount: source.count) + start.storeBytes(of: 0, toByteOffset: source.count, as: UInt8.self) + return (UnsafeRawPointer(start), source.count + 1) + } + if let r = r { return r } + + // What a horrible string; we need to copy individual bytes. + precondition(self.utf8.count <= scratch.count) + var c = 0 + for byte in self.utf8 { + scratch[c] = byte + c += 1 + } + scratch[c] = 0 + c += 1 + return (UnsafeRawPointer(scratch.baseAddress!), c) + } +} + diff --git a/Sources/System/Util.swift b/Sources/System/Util.swift index ac25830f..a119576d 100644 --- a/Sources/System/Util.swift +++ b/Sources/System/Util.swift @@ -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 - 2021 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 @@ -127,3 +127,38 @@ extension MutableCollection where Element: Equatable { } } } + +internal func _withOptionalUnsafePointerOrNull( + to value: T?, + _ body: (UnsafePointer?) throws -> R +) rethrows -> R { + guard let value = value else { + return try body(nil) + } + return try withUnsafePointer(to: value, body) +} + +/// Calls `body` with a temporary buffer of the indicated size, +/// possibly stack-allocated. +internal func _withStackBuffer( + capacity: Int, + _ body: (UnsafeMutableRawBufferPointer) throws -> R +) rethrows -> R { + typealias StackStorage = ( + UInt64, UInt64, UInt64, UInt64, + UInt64, UInt64, UInt64, UInt64, + UInt64, UInt64, UInt64, UInt64, + UInt64, UInt64, UInt64, UInt64 + ) + if capacity > MemoryLayout.size { + var buffer = _RawBuffer(minimumCapacity: capacity) + return try buffer.withUnsafeMutableBytes { buffer in + try body(.init(rebasing: buffer[.. Bool { + guard self.name == other.name else { return false } + guard self.arguments.count == other.arguments.count else { return false } + for i in self.arguments.indices { + if self.arguments[i] is Wildcard || other.arguments[i] is Wildcard { + continue + } + guard self.arguments[i] == other.arguments[i] else { return false } + } + return true + } +} + // To aid debugging, force failures to fatal error internal var forceFatalFailures = false @@ -40,7 +59,7 @@ extension TestCase { _ message: String? = nil ) where S1.Element: Equatable, S1.Element == S2.Element { if !expected.elementsEqual(actual) { - defer { print("expected: \(expected), actual: \(actual)") } + defer { print("expected: \(expected)\n actual: \(actual)") } fail(message) } } @@ -49,7 +68,7 @@ extension TestCase { _ message: String? = nil ) { if actual != expected { - defer { print("expected: \(expected), actual: \(actual)") } + defer { print("expected: \(expected)\n actual: \(actual)") } fail(message) } } @@ -62,6 +81,27 @@ extension TestCase { fail(message) } } + func expectMatch( + _ expected: Trace.Entry?, _ actual: Trace.Entry?, + _ message: String? = nil + ) { + func check() -> Bool { + switch (expected, actual) { + case let (expected?, actual?): + return expected.matches(actual) + case (nil, nil): + return true + default: + return false + } + } + if !check() { + let e = expected.map { "\($0)" } ?? "nil" + let a = actual.map { "\($0)" } ?? "nil" + defer { print("expected: \(e)\n actual: \(a)") } + fail(message) + } + } func expectNil( _ actual: T?, _ message: String? = nil @@ -149,7 +189,7 @@ internal struct MockTestCase: TestCase { // Test our API mappings to the lower-level syscall invocation do { try body(true) - self.expectEqual(self.expected, mocking.trace.dequeue()) + self.expectMatch(self.expected, mocking.trace.dequeue()) } catch { self.fail() } @@ -158,9 +198,9 @@ internal struct MockTestCase: TestCase { guard interruptBehavior != .noError else { do { try body(interruptable) - self.expectEqual(self.expected, mocking.trace.dequeue()) + self.expectMatch(self.expected, mocking.trace.dequeue()) try body(!interruptable) - self.expectEqual(self.expected, mocking.trace.dequeue()) + self.expectMatch(self.expected, mocking.trace.dequeue()) } catch { self.fail() } @@ -177,7 +217,7 @@ internal struct MockTestCase: TestCase { self.fail() } catch Errno.interrupted { // Success! - self.expectEqual(self.expected, mocking.trace.dequeue()) + self.expectMatch(self.expected, mocking.trace.dequeue()) } catch { self.fail() } @@ -188,13 +228,13 @@ internal struct MockTestCase: TestCase { mocking.forceErrno = .counted(errno: EINTR, count: 3) try body(interruptable) - self.expectEqual(self.expected, mocking.trace.dequeue()) // EINTR - self.expectEqual(self.expected, mocking.trace.dequeue()) // EINTR - self.expectEqual(self.expected, mocking.trace.dequeue()) // EINTR - self.expectEqual(self.expected, mocking.trace.dequeue()) // Success + self.expectMatch(self.expected, mocking.trace.dequeue()) // EINTR + self.expectMatch(self.expected, mocking.trace.dequeue()) // EINTR + self.expectMatch(self.expected, mocking.trace.dequeue()) // EINTR + self.expectMatch(self.expected, mocking.trace.dequeue()) // Success } catch Errno.interrupted { self.expectFalse(interruptable) - self.expectEqual(self.expected, mocking.trace.dequeue()) // EINTR + self.expectMatch(self.expected, mocking.trace.dequeue()) // EINTR } catch { self.fail() } diff --git a/Tests/SystemTests/UtilTests.swift b/Tests/SystemTests/UtilTests.swift new file mode 100644 index 00000000..bfd21341 --- /dev/null +++ b/Tests/SystemTests/UtilTests.swift @@ -0,0 +1,89 @@ +/* + This source file is part of the Swift System open source project + + Copyright (c) 2021 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 +*/ + +import XCTest + +#if SYSTEM_PACKAGE +@testable import SystemPackage +#else +@testable import System +#endif + +class UtilTests: XCTestCase { + func testStackBuffer() { + // Exercises _withStackBuffer a bit, in hopes any bugs will + // show up as ASan failures. + for size in stride(from: 0, to: 1000, by: 5) { + var called = false + _withStackBuffer(capacity: size) { buffer in + XCTAssertFalse(called) + called = true + + buffer.initializeMemory(as: UInt8.self, repeating: 42) + } + XCTAssertTrue(called) + } + } + + func testCStringArray() { + func check( + _ array: [String], + file: StaticString = #file, + line: UInt = #line + ) { + array._withCStringArray { carray in + let actual = carray.map { $0.map { String(cString: $0) } ?? "" } + XCTAssertEqual(actual, array, file: file, line: line) + // Verify that there is a null pointer following the last item in + // carray. (Note: this is intentionally addressing beyond the + // end of the buffer, as the function promises that is going to be okay.) + XCTAssertNil((carray.baseAddress! + carray.count).pointee) + } + } + + check([]) + check([""]) + check(["", ""]) + check(["", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", ""]) + // String literals of various sizes and counts + check(["hello"]) + check(["hello", "world"]) + check(["This is a rather large string literal"]) + check([ + "This is a rather large string literal", + "This is small", + "This one is not that small -- it's even longer than the first", + ]) + check([ + "This is a rather large string literal", + "This one is not that small -- it's even longer than the first", + "And this is the largest of them all. I wonder if it even fits on a line" + ]) + check(Array(repeating: "", count: 100)) + check(Array(repeating: "Hiii", count: 100)) + check(["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m"]) + check(["", "b", "", "d", "", "f", "", "h", "", "j", "", "👨‍👨‍👧‍👦👩‍❤️‍💋‍👨", "m"]) + + var girls = ["Dörothy", "Róse", "Blánche", "Sőphia"] + check(girls) // Small strings + for i in girls.indices { + // Convert to native + girls[i] = "\(girls[i]) \(girls[i]) \(girls[i])" + } + check(girls) // Native large strings + for i in girls.indices { + let data = girls[i].data(using: .utf16)! + girls[i] = NSString( + data: data, + encoding: String.Encoding.utf16.rawValue + )! as String + } + check(girls) // UTF-16 Cocoa strings + } +} From 6469433909b273fe7d24bc268f7184ba9a7be60d Mon Sep 17 00:00:00 2001 From: Karoy Lorentey Date: Tue, 21 Dec 2021 17:39:44 -0800 Subject: [PATCH 057/427] Work around `memset` taking a non-optional pointer on Linux --- Sources/System/Internals/Exports.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Sources/System/Internals/Exports.swift b/Sources/System/Internals/Exports.swift index 0a5a26eb..01650a77 100644 --- a/Sources/System/Internals/Exports.swift +++ b/Sources/System/Internals/Exports.swift @@ -87,7 +87,8 @@ internal func system_memset( _ buffer: UnsafeMutableRawBufferPointer, to byte: UInt8 ) { - memset(buffer.baseAddress, CInt(byte), buffer.count) + guard buffer.count > 0 else { return } + memset(buffer.baseAddress!, CInt(byte), buffer.count) } // Interop between String and platfrom string From 915070e00671074fd1ac8f07042719151643b378 Mon Sep 17 00:00:00 2001 From: Karoy Lorentey Date: Tue, 21 Dec 2021 18:40:27 -0800 Subject: [PATCH 058/427] Add Internals/Backcompat.swift to CMake config --- Sources/System/CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/Sources/System/CMakeLists.txt b/Sources/System/CMakeLists.txt index 990f1c6a..33fb46b3 100644 --- a/Sources/System/CMakeLists.txt +++ b/Sources/System/CMakeLists.txt @@ -29,6 +29,7 @@ target_sources(SystemPackage PRIVATE FilePath/FilePathSyntax.swift FilePath/FilePathWindows.swift) target_sources(SystemPackage PRIVATE + Internals/Backcompat.swift Internals/CInterop.swift Internals/Constants.swift Internals/Exports.swift From 81d78be49904c51d55c737c04a24604b015ae262 Mon Sep 17 00:00:00 2001 From: Karoy Lorentey Date: Sun, 26 Dec 2021 21:53:28 -0800 Subject: [PATCH 059/427] Fix Windows build --- Sources/System/FileOperations.swift | 59 ++++++++++++++++++++ Sources/System/FilePath/FilePathString.swift | 10 +++- 2 files changed, 66 insertions(+), 3 deletions(-) diff --git a/Sources/System/FileOperations.swift b/Sources/System/FileOperations.swift index e90a4946..368d4d18 100644 --- a/Sources/System/FileOperations.swift +++ b/Sources/System/FileOperations.swift @@ -31,12 +31,23 @@ extension FileDescriptor { permissions: FilePermissions? = nil, retryOnInterrupt: Bool = true ) throws -> FileDescriptor { + #if !os(Windows) try path.withCString { try FileDescriptor.open( $0, mode, options: options, permissions: permissions, retryOnInterrupt: retryOnInterrupt) } + #else + try path.withPlatformString { + try FileDescriptor.open( + $0, mode, options: options, permissions: permissions, retryOnInterrupt: retryOnInterrupt) + } + #endif } + #if !os(Windows) + // On Darwin, `CInterop.PlatformChar` is less available than + // `FileDescriptor.open`, so we need to use `CChar` instead. + /// Opens or creates a file for reading or writing. /// /// - Parameters: @@ -83,6 +94,54 @@ extension FileDescriptor { } return descOrError.map { FileDescriptor(rawValue: $0) } } + #else + /// Opens or creates a file for reading or writing. + /// + /// - Parameters: + /// - path: The location of the file to open. + /// - mode: The read and write access to use. + /// - options: The behavior for opening the file. + /// - permissions: The file permissions to use for created files. + /// - retryOnInterrupt: Whether to retry the open operation + /// if it throws ``Errno/interrupted``. + /// The default is `true`. + /// Pass `false` to try only once and throw an error upon interruption. + /// - Returns: A file descriptor for the open file + /// + /// The corresponding C function is `open`. + @_alwaysEmitIntoClient + public static func open( + _ path: UnsafePointer, + _ mode: FileDescriptor.AccessMode, + options: FileDescriptor.OpenOptions = FileDescriptor.OpenOptions(), + permissions: FilePermissions? = nil, + retryOnInterrupt: Bool = true + ) throws -> FileDescriptor { + try FileDescriptor._open( + path, mode, options: options, permissions: permissions, retryOnInterrupt: retryOnInterrupt + ).get() + } + + @usableFromInline + internal static func _open( + _ path: UnsafePointer, + _ mode: FileDescriptor.AccessMode, + options: FileDescriptor.OpenOptions, + permissions: FilePermissions?, + retryOnInterrupt: Bool + ) -> Result { + let oFlag = mode.rawValue | options.rawValue + let descOrError: Result = valueOrErrno(retryOnInterrupt: retryOnInterrupt) { + if let permissions = permissions { + return system_open(path, oFlag, permissions.rawValue) + } + precondition(!options.contains(.create), + "Create must be given permissions") + return system_open(path, oFlag) + } + return descOrError.map { FileDescriptor(rawValue: $0) } + } + #endif /// Deletes a file descriptor. /// diff --git a/Sources/System/FilePath/FilePathString.swift b/Sources/System/FilePath/FilePathString.swift index 2dd2ccee..21f3548d 100644 --- a/Sources/System/FilePath/FilePathString.swift +++ b/Sources/System/FilePath/FilePathString.swift @@ -20,7 +20,13 @@ extension FilePath { self.init(_platformString: platformString) } -#if !os(Windows) // Availability workaround + #if !os(Windows) + // Note: This function should have been opaque, but it shipped as + // `@_alwaysEmitIntoClient` in macOS 12/iOS 15, and now it is stuck + // this way forever. (Or until the language provides a way for us + // to declare separate availability for a function's exported symbol + // and its inlinable body.) + /// Calls the given closure with a pointer to the contents of the file path, /// represented as a null-terminated platform string. /// @@ -37,7 +43,6 @@ extension FilePath { public func withPlatformString( _ body: (UnsafePointer) throws -> Result ) rethrows -> Result { - // For backwards deployment, call withCString if available. return try withCString(body) } #else @@ -56,7 +61,6 @@ extension FilePath { public func withPlatformString( _ body: (UnsafePointer) throws -> Result ) rethrows -> Result { - // For backwards deployment, call withCString if available. return try _withPlatformString(body) } #endif From ca700460b1bc23e6e46c2021b7d80f1442b4fd3a Mon Sep 17 00:00:00 2001 From: Karoy Lorentey Date: Sun, 26 Dec 2021 22:00:07 -0800 Subject: [PATCH 060/427] Fix compatibility with Swift 5.3.3 --- Sources/System/FileOperations.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/System/FileOperations.swift b/Sources/System/FileOperations.swift index 368d4d18..e081278a 100644 --- a/Sources/System/FileOperations.swift +++ b/Sources/System/FileOperations.swift @@ -32,12 +32,12 @@ extension FileDescriptor { retryOnInterrupt: Bool = true ) throws -> FileDescriptor { #if !os(Windows) - try path.withCString { + return try path.withCString { try FileDescriptor.open( $0, mode, options: options, permissions: permissions, retryOnInterrupt: retryOnInterrupt) } #else - try path.withPlatformString { + return try path.withPlatformString { try FileDescriptor.open( $0, mode, options: options, permissions: permissions, retryOnInterrupt: retryOnInterrupt) } From a77a331dc7aa3043dae5002932a61e738a688ecd Mon Sep 17 00:00:00 2001 From: Daniel Duan Date: Wed, 20 Apr 2022 13:22:03 -0700 Subject: [PATCH 061/427] Update Linux test manifests (#81) Run `swift package --generate-linuxmain` results in this patch. --- Tests/SystemTests/XCTestManifests.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Tests/SystemTests/XCTestManifests.swift b/Tests/SystemTests/XCTestManifests.swift index c5f13f7c..0480347d 100644 --- a/Tests/SystemTests/XCTestManifests.swift +++ b/Tests/SystemTests/XCTestManifests.swift @@ -27,6 +27,7 @@ extension FileOperationsTest { // to regenerate. static let __allTests__FileOperationsTest = [ ("testAdHocOpen", testAdHocOpen), + ("testAdHocPipe", testAdHocPipe), ("testGithubIssues", testGithubIssues), ("testHelpers", testHelpers), ("testSyscalls", testSyscalls), From 23ac1906318799fd925f5efe321fb0d8f34b87fa Mon Sep 17 00:00:00 2001 From: Si Beaumont Date: Wed, 4 May 2022 12:33:03 +0100 Subject: [PATCH 062/427] FileHelpers: Add FileDescriptor.fileSize Signed-off-by: Si Beaumont --- Sources/System/FileHelpers.swift | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/Sources/System/FileHelpers.swift b/Sources/System/FileHelpers.swift index 0825f83c..6f7885f5 100644 --- a/Sources/System/FileHelpers.swift +++ b/Sources/System/FileHelpers.swift @@ -122,4 +122,18 @@ extension FileDescriptor { return .success(buffer.count) } } + + /// Return the current size of the file. + /// + /// - Returns: The current size of the file, in bytes. + @_alwaysEmitIntoClient + @discardableResult + public func fileSize( + retryOnInterrupt: Bool = true + ) throws -> Int64 { + let current = try seek(offset: 0, from: .current) + let size = try seek(offset: 0, from: .end) + try seek(offset: current, from: .start) + return size + } } From 5d3519dcef5c4a650c34b7a9bec70942d7975d22 Mon Sep 17 00:00:00 2001 From: Si Beaumont Date: Wed, 4 May 2022 12:34:11 +0100 Subject: [PATCH 063/427] FileOperations: Add FileDescriptor.resize, using ftruncate(2) Signed-off-by: Si Beaumont --- Sources/System/FileOperations.swift | 41 +++++++++++++++++ Sources/System/Internals/Syscalls.swift | 8 ++++ .../Internals/WindowsSyscallAdapters.swift | 7 +++ Tests/SystemTests/FileOperationsTest.swift | 46 +++++++++++++++++++ 4 files changed, 102 insertions(+) diff --git a/Sources/System/FileOperations.swift b/Sources/System/FileOperations.swift index 01025632..b9609a6c 100644 --- a/Sources/System/FileOperations.swift +++ b/Sources/System/FileOperations.swift @@ -370,6 +370,47 @@ extension FileDescriptor { public func dup2() throws -> FileDescriptor { fatalError("Not implemented") } + + /// Truncate or extend the file referenced by this file descriptor. + /// + /// - Parameters: + /// - newSize: The length in bytes to resize the file to. + /// - retryOnInterrupt: Whether to retry the write operation + /// if it throws ``Errno/interrupted``. The default is `true`. + /// Pass `false` to try only once and throw an error upon interruption. + /// + /// The file referenced by this file descriptor will by truncated (or extended) to `newSize`. + /// + /// If the current size of the file exceeds `newSize`, any extra data is discarded. If the current + /// size of the file is smaller than `newSize`, the file is extended and filled with zeros to the + /// provided size. + /// + /// This function requires that the file has been opened for writing. + /// + /// - Note: This function does not modify the current offset for any open file descriptors + /// associated with the file. + /// + /// The corresponding C function is `ftruncate`. + @_alwaysEmitIntoClient + public func resize( + to newSize: Int64, + retryOnInterrupt: Bool = true + ) throws { + try _resize( + to: newSize, + retryOnInterrupt: retryOnInterrupt + ).get() + } + + @usableFromInline + internal func _resize( + to newSize: Int64, + retryOnInterrupt: Bool + ) -> Result<(), Errno> { + nothingOrErrno(retryOnInterrupt: retryOnInterrupt) { + system_ftruncate(self.rawValue, _COffT(newSize)) + } + } } /*System 1.1.0, @available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *)*/ diff --git a/Sources/System/Internals/Syscalls.swift b/Sources/System/Internals/Syscalls.swift index 453c02fc..493155b7 100644 --- a/Sources/System/Internals/Syscalls.swift +++ b/Sources/System/Internals/Syscalls.swift @@ -52,6 +52,14 @@ internal func system_close(_ fd: Int32) -> Int32 { return close(fd) } +// truncate +internal func system_ftruncate(_ fd: Int32, _ length: off_t) -> Int32 { +#if ENABLE_MOCKING + if mockingEnabled { return _mock(fd, length) } +#endif + return ftruncate(fd, length) +} + // read internal func system_read( _ fd: Int32, _ buf: UnsafeMutableRawPointer!, _ nbyte: Int diff --git a/Sources/System/Internals/WindowsSyscallAdapters.swift b/Sources/System/Internals/WindowsSyscallAdapters.swift index 36acaceb..d2c42fbe 100644 --- a/Sources/System/Internals/WindowsSyscallAdapters.swift +++ b/Sources/System/Internals/WindowsSyscallAdapters.swift @@ -105,4 +105,11 @@ internal func pwrite( return Int(nNumberOfBytesWritten) } +@inline(__always) +internal func ftruncate( + _ fd: Int32, _ length: off_t +) -> Int32 { + _chsize(fd, numericCast(length)) +} + #endif diff --git a/Tests/SystemTests/FileOperationsTest.swift b/Tests/SystemTests/FileOperationsTest.swift index e28efd5d..c12cc847 100644 --- a/Tests/SystemTests/FileOperationsTest.swift +++ b/Tests/SystemTests/FileOperationsTest.swift @@ -81,6 +81,10 @@ final class FileOperationsTest: XCTestCase { _ = try fd.duplicate(as: FileDescriptor(rawValue: 42), retryOnInterrupt: retryOnInterrupt) }, + + MockTestCase(name: "ftruncate", .interruptable, rawFD, 42) { retryOnInterrupt in + _ = try fd.resize(to: 42, retryOnInterrupt: retryOnInterrupt) + }, ] for test in syscallTestCases { test.runAllTests() } @@ -160,5 +164,47 @@ final class FileOperationsTest: XCTestCase { issue26.runAllTests() } + + func testResizeFile() throws { + let fd = try FileDescriptor.open("/tmp/b.txt", .readWrite, options: [.create, .truncate], permissions: .ownerReadWrite) + try fd.closeAfter { + // File should be empty initially. + XCTAssertEqual(try fd.fileSize(), 0) + // Write 3 bytes. + try fd.writeAll("abc".utf8) + // File should now be 3 bytes. + XCTAssertEqual(try fd.fileSize(), 3) + // Resize to 6 bytes. + try fd.resize(to: 6) + // File should now be 6 bytes. + XCTAssertEqual(try fd.fileSize(), 6) + // Read in the 6 bytes. + let readBytes = try Array(unsafeUninitializedCapacity: 6) { (buf, count) in + try fd.seek(offset: 0, from: .start) + // Should have read all 6 bytes. + count = try fd.read(into: UnsafeMutableRawBufferPointer(buf)) + XCTAssertEqual(count, 6) + } + // First 3 bytes should be unaffected by resize. + XCTAssertEqual(Array(readBytes[..<3]), Array("abc".utf8)) + // Extension should be padded with zeros. + XCTAssertEqual(Array(readBytes[3...]), Array(repeating: 0, count: 3)) + // File should still be 6 bytes. + XCTAssertEqual(try fd.fileSize(), 6) + // Resize to 2 bytes. + try fd.resize(to: 2) + // File should now be 2 bytes. + XCTAssertEqual(try fd.fileSize(), 2) + // Read in file with a buffer big enough for 6 bytes. + let readBytesAfterTruncation = try Array(unsafeUninitializedCapacity: 6) { (buf, count) in + try fd.seek(offset: 0, from: .start) + count = try fd.read(into: UnsafeMutableRawBufferPointer(buf)) + // Should only have read 2 bytes. + XCTAssertEqual(count, 2) + } + // Written content was trunctated. + XCTAssertEqual(readBytesAfterTruncation, Array("ab".utf8)) + } + } } From 7fab65f340e976cd229fb8bccf86b8d5d6cd4295 Mon Sep 17 00:00:00 2001 From: Si Beaumont Date: Fri, 6 May 2022 11:09:20 +0100 Subject: [PATCH 064/427] fixup: Remove Windows syscall adaptor for ftruncate --- Sources/System/Internals/WindowsSyscallAdapters.swift | 7 ------- 1 file changed, 7 deletions(-) diff --git a/Sources/System/Internals/WindowsSyscallAdapters.swift b/Sources/System/Internals/WindowsSyscallAdapters.swift index d2c42fbe..36acaceb 100644 --- a/Sources/System/Internals/WindowsSyscallAdapters.swift +++ b/Sources/System/Internals/WindowsSyscallAdapters.swift @@ -105,11 +105,4 @@ internal func pwrite( return Int(nNumberOfBytesWritten) } -@inline(__always) -internal func ftruncate( - _ fd: Int32, _ length: off_t -) -> Int32 { - _chsize(fd, numericCast(length)) -} - #endif From 02daf4b6a77f6e88a244d97b4d6e12a49fb7cec9 Mon Sep 17 00:00:00 2001 From: Si Beaumont Date: Fri, 6 May 2022 11:23:45 +0100 Subject: [PATCH 065/427] fixup: Move FileDescriptor.resize to extension with #if !os(Windows) --- Sources/System/FileOperations.swift | 59 ++++++++++++---------- Tests/SystemTests/FileOperationsTest.swift | 2 + 2 files changed, 35 insertions(+), 26 deletions(-) diff --git a/Sources/System/FileOperations.swift b/Sources/System/FileOperations.swift index b9609a6c..d55b2e86 100644 --- a/Sources/System/FileOperations.swift +++ b/Sources/System/FileOperations.swift @@ -370,7 +370,38 @@ extension FileDescriptor { public func dup2() throws -> FileDescriptor { fatalError("Not implemented") } +} + +/*System 1.1.0, @available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *)*/ +extension FileDescriptor { + #if !os(Windows) + /// Create a pipe, a unidirectional data channel which can be used for interprocess communication. + /// + /// - Returns: The pair of file descriptors. + /// + /// The corresponding C function is `pipe`. + @_alwaysEmitIntoClient + /*System 1.1.0, @available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *)*/ + public static func pipe() throws -> (readEnd: FileDescriptor, writeEnd: FileDescriptor) { + try _pipe().get() + } + + /*System 1.1.0, @available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *)*/ + @usableFromInline + internal static func _pipe() -> Result<(readEnd: FileDescriptor, writeEnd: FileDescriptor), Errno> { + var fds: (Int32, Int32) = (-1, -1) + return withUnsafeMutablePointer(to: &fds) { pointer in + valueOrErrno(retryOnInterrupt: false) { + system_pipe(UnsafeMutableRawPointer(pointer).assumingMemoryBound(to: Int32.self)) + } + }.map { _ in (.init(rawValue: fds.0), .init(rawValue: fds.1)) } + } + #endif +} +/*System 1.2.0, @available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *)*/ +extension FileDescriptor { + #if !os(Windows) /// Truncate or extend the file referenced by this file descriptor. /// /// - Parameters: @@ -391,6 +422,7 @@ extension FileDescriptor { /// associated with the file. /// /// The corresponding C function is `ftruncate`. + /*System 1.2.0, @available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *)*/ @_alwaysEmitIntoClient public func resize( to newSize: Int64, @@ -402,6 +434,7 @@ extension FileDescriptor { ).get() } + /*System 1.2.0, @available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *)*/ @usableFromInline internal func _resize( to newSize: Int64, @@ -411,31 +444,5 @@ extension FileDescriptor { system_ftruncate(self.rawValue, _COffT(newSize)) } } -} - -/*System 1.1.0, @available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *)*/ -extension FileDescriptor { - #if !os(Windows) - /// Create a pipe, a unidirectional data channel which can be used for interprocess communication. - /// - /// - Returns: The pair of file descriptors. - /// - /// The corresponding C function is `pipe`. - @_alwaysEmitIntoClient - /*System 1.1.0, @available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *)*/ - public static func pipe() throws -> (readEnd: FileDescriptor, writeEnd: FileDescriptor) { - try _pipe().get() - } - - /*System 1.1.0, @available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *)*/ - @usableFromInline - internal static func _pipe() -> Result<(readEnd: FileDescriptor, writeEnd: FileDescriptor), Errno> { - var fds: (Int32, Int32) = (-1, -1) - return withUnsafeMutablePointer(to: &fds) { pointer in - valueOrErrno(retryOnInterrupt: false) { - system_pipe(UnsafeMutableRawPointer(pointer).assumingMemoryBound(to: Int32.self)) - } - }.map { _ in (.init(rawValue: fds.0), .init(rawValue: fds.1)) } - } #endif } diff --git a/Tests/SystemTests/FileOperationsTest.swift b/Tests/SystemTests/FileOperationsTest.swift index c12cc847..4f11fc14 100644 --- a/Tests/SystemTests/FileOperationsTest.swift +++ b/Tests/SystemTests/FileOperationsTest.swift @@ -165,6 +165,7 @@ final class FileOperationsTest: XCTestCase { } +#if !os(Windows) func testResizeFile() throws { let fd = try FileDescriptor.open("/tmp/b.txt", .readWrite, options: [.create, .truncate], permissions: .ownerReadWrite) try fd.closeAfter { @@ -206,5 +207,6 @@ final class FileOperationsTest: XCTestCase { XCTAssertEqual(readBytesAfterTruncation, Array("ab".utf8)) } } +#endif } From d90bf8529a6df69ad59d93b929d2b0d254c4c847 Mon Sep 17 00:00:00 2001 From: Si Beaumont Date: Fri, 6 May 2022 11:39:58 +0100 Subject: [PATCH 066/427] fixup: Move FileDescriptor.fileSize() to an internal extension in SystemTests --- Sources/System/FileHelpers.swift | 14 ----------- Tests/SystemTests/FileDescriptorExtras.swift | 25 ++++++++++++++++++++ 2 files changed, 25 insertions(+), 14 deletions(-) create mode 100644 Tests/SystemTests/FileDescriptorExtras.swift diff --git a/Sources/System/FileHelpers.swift b/Sources/System/FileHelpers.swift index 6f7885f5..0825f83c 100644 --- a/Sources/System/FileHelpers.swift +++ b/Sources/System/FileHelpers.swift @@ -122,18 +122,4 @@ extension FileDescriptor { return .success(buffer.count) } } - - /// Return the current size of the file. - /// - /// - Returns: The current size of the file, in bytes. - @_alwaysEmitIntoClient - @discardableResult - public func fileSize( - retryOnInterrupt: Bool = true - ) throws -> Int64 { - let current = try seek(offset: 0, from: .current) - let size = try seek(offset: 0, from: .end) - try seek(offset: current, from: .start) - return size - } } diff --git a/Tests/SystemTests/FileDescriptorExtras.swift b/Tests/SystemTests/FileDescriptorExtras.swift new file mode 100644 index 00000000..b72816fa --- /dev/null +++ b/Tests/SystemTests/FileDescriptorExtras.swift @@ -0,0 +1,25 @@ +/* + This source file is part of the Swift System open source project + + Copyright (c) 2020 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 +*/ + +#if SYSTEM_PACKAGE +@testable import SystemPackage +#else +@testable import System +#endif + +extension FileDescriptor { + internal func fileSize( + retryOnInterrupt: Bool = true + ) throws -> Int64 { + let current = try seek(offset: 0, from: .current) + let size = try seek(offset: 0, from: .end) + try seek(offset: current, from: .start) + return size + } +} From fea7d5030dc082cfd24866cee42d5b9dd5a19862 Mon Sep 17 00:00:00 2001 From: Si Beaumont Date: Mon, 16 May 2022 15:28:47 +0100 Subject: [PATCH 067/427] docs: Removed duplicate paragraph from FileDescriptor.writeAll(_:) docs (#85) --- Sources/System/FileHelpers.swift | 3 --- 1 file changed, 3 deletions(-) diff --git a/Sources/System/FileHelpers.swift b/Sources/System/FileHelpers.swift index 0825f83c..af7cedfe 100644 --- a/Sources/System/FileHelpers.swift +++ b/Sources/System/FileHelpers.swift @@ -46,9 +46,6 @@ extension FileDescriptor { /// increments that position by the number of bytes written. /// See also ``seek(offset:from:)``. /// - /// This method either writes the entire contents of `sequence`, - /// or throws an error if only part of the content was written. - /// /// If `sequence` doesn't implement /// the method, /// temporary space will be allocated as needed. From ca6e88845586a3d449596d9be735add2c38d391c Mon Sep 17 00:00:00 2001 From: Si Beaumont Date: Fri, 20 May 2022 18:24:28 +0100 Subject: [PATCH 068/427] Utilities: Add version 1.2.0 to expand-availability.py tool Signed-off-by: Si Beaumont --- Utilities/expand-availability.py | 1 + 1 file changed, 1 insertion(+) diff --git a/Utilities/expand-availability.py b/Utilities/expand-availability.py index 94b9aed7..63190802 100755 --- a/Utilities/expand-availability.py +++ b/Utilities/expand-availability.py @@ -47,6 +47,7 @@ "System 0.0.1": "macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0", "System 0.0.2": "macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0", "System 1.1.0": "macOS 9999, iOS 9999, watchOS 9999, tvOS 9999", + "System 1.2.0": "macOS 9999, iOS 9999, watchOS 9999, tvOS 9999", } parser = argparse.ArgumentParser(description="Expand availability macros.") From 7bb5279b57465d9cb62928355142754f4556247b Mon Sep 17 00:00:00 2001 From: Si Beaumont Date: Fri, 20 May 2022 19:25:03 +0100 Subject: [PATCH 069/427] fixup: Tests run in parallel; use unique temp file name Signed-off-by: Si Beaumont --- Tests/SystemTests/FileOperationsTest.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/SystemTests/FileOperationsTest.swift b/Tests/SystemTests/FileOperationsTest.swift index 4f11fc14..2b0a910b 100644 --- a/Tests/SystemTests/FileOperationsTest.swift +++ b/Tests/SystemTests/FileOperationsTest.swift @@ -167,7 +167,7 @@ final class FileOperationsTest: XCTestCase { #if !os(Windows) func testResizeFile() throws { - let fd = try FileDescriptor.open("/tmp/b.txt", .readWrite, options: [.create, .truncate], permissions: .ownerReadWrite) + let fd = try FileDescriptor.open("/tmp/\(UUID().uuidString).txt", .readWrite, options: [.create, .truncate], permissions: .ownerReadWrite) try fd.closeAfter { // File should be empty initially. XCTAssertEqual(try fd.fileSize(), 0) From 147ebc053a4026a096e6d74af7b97f52157e4fcc Mon Sep 17 00:00:00 2001 From: Karoy Lorentey Date: Sat, 21 May 2022 11:57:00 -0700 Subject: [PATCH 070/427] Unbreak the Windows port --- Sources/System/FileOperations.swift | 8 ++++---- Sources/System/Internals/Syscalls.swift | 18 ++++++++++-------- Tests/SystemTests/FileOperationsTest.swift | 2 ++ 3 files changed, 16 insertions(+), 12 deletions(-) diff --git a/Sources/System/FileOperations.swift b/Sources/System/FileOperations.swift index d55b2e86..1193ec94 100644 --- a/Sources/System/FileOperations.swift +++ b/Sources/System/FileOperations.swift @@ -372,9 +372,9 @@ extension FileDescriptor { } } +#if !os(Windows) /*System 1.1.0, @available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *)*/ extension FileDescriptor { - #if !os(Windows) /// Create a pipe, a unidirectional data channel which can be used for interprocess communication. /// /// - Returns: The pair of file descriptors. @@ -396,12 +396,12 @@ extension FileDescriptor { } }.map { _ in (.init(rawValue: fds.0), .init(rawValue: fds.1)) } } - #endif } +#endif +#if !os(Windows) /*System 1.2.0, @available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *)*/ extension FileDescriptor { - #if !os(Windows) /// Truncate or extend the file referenced by this file descriptor. /// /// - Parameters: @@ -444,5 +444,5 @@ extension FileDescriptor { system_ftruncate(self.rawValue, _COffT(newSize)) } } - #endif } +#endif diff --git a/Sources/System/Internals/Syscalls.swift b/Sources/System/Internals/Syscalls.swift index 493155b7..b22d6a3f 100644 --- a/Sources/System/Internals/Syscalls.swift +++ b/Sources/System/Internals/Syscalls.swift @@ -52,14 +52,6 @@ internal func system_close(_ fd: Int32) -> Int32 { return close(fd) } -// truncate -internal func system_ftruncate(_ fd: Int32, _ length: off_t) -> Int32 { -#if ENABLE_MOCKING - if mockingEnabled { return _mock(fd, length) } -#endif - return ftruncate(fd, length) -} - // read internal func system_read( _ fd: Int32, _ buf: UnsafeMutableRawPointer!, _ nbyte: Int @@ -123,6 +115,7 @@ internal func system_dup2(_ fd: Int32, _ fd2: Int32) -> Int32 { #endif return dup2(fd, fd2) } + #if !os(Windows) internal func system_pipe(_ fds: UnsafeMutablePointer) -> CInt { #if ENABLE_MOCKING @@ -131,3 +124,12 @@ internal func system_pipe(_ fds: UnsafeMutablePointer) -> CInt { return pipe(fds) } #endif + +#if !os(Windows) +internal func system_ftruncate(_ fd: Int32, _ length: off_t) -> Int32 { +#if ENABLE_MOCKING + if mockingEnabled { return _mock(fd, length) } +#endif + return ftruncate(fd, length) +} +#endif diff --git a/Tests/SystemTests/FileOperationsTest.swift b/Tests/SystemTests/FileOperationsTest.swift index 2b0a910b..a48e87e9 100644 --- a/Tests/SystemTests/FileOperationsTest.swift +++ b/Tests/SystemTests/FileOperationsTest.swift @@ -82,9 +82,11 @@ final class FileOperationsTest: XCTestCase { retryOnInterrupt: retryOnInterrupt) }, +#if !os(Windows) MockTestCase(name: "ftruncate", .interruptable, rawFD, 42) { retryOnInterrupt in _ = try fd.resize(to: 42, retryOnInterrupt: retryOnInterrupt) }, +#endif ] for test in syscallTestCases { test.runAllTests() } From bd5a7bb703d9d74114193556c7cae3bd7f7e246c Mon Sep 17 00:00:00 2001 From: Karoy Lorentey Date: Sat, 21 May 2022 12:43:06 -0700 Subject: [PATCH 071/427] Remove test of minimal value --- Tests/SystemTests/FileOperationsTest.swift | 6 ------ 1 file changed, 6 deletions(-) diff --git a/Tests/SystemTests/FileOperationsTest.swift b/Tests/SystemTests/FileOperationsTest.swift index a48e87e9..419e1c97 100644 --- a/Tests/SystemTests/FileOperationsTest.swift +++ b/Tests/SystemTests/FileOperationsTest.swift @@ -81,12 +81,6 @@ final class FileOperationsTest: XCTestCase { _ = try fd.duplicate(as: FileDescriptor(rawValue: 42), retryOnInterrupt: retryOnInterrupt) }, - -#if !os(Windows) - MockTestCase(name: "ftruncate", .interruptable, rawFD, 42) { retryOnInterrupt in - _ = try fd.resize(to: 42, retryOnInterrupt: retryOnInterrupt) - }, -#endif ] for test in syscallTestCases { test.runAllTests() } From 914c782b96ec0fcdb91e3aae66568e38ef70670a Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Wed, 11 May 2022 10:05:29 -0600 Subject: [PATCH 072/427] Replace `assumingMemoryBound` with temporary rebinding MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - this doesn’t use the new memory binding API. --- Sources/System/FileOperations.swift | 8 +++-- Sources/System/SystemString.swift | 31 +++++++++++-------- .../FilePathTests/FilePathTest.swift | 4 ++- Tests/SystemTests/SystemStringTests.swift | 25 +-------------- 4 files changed, 27 insertions(+), 41 deletions(-) diff --git a/Sources/System/FileOperations.swift b/Sources/System/FileOperations.swift index 1193ec94..28226e45 100644 --- a/Sources/System/FileOperations.swift +++ b/Sources/System/FileOperations.swift @@ -391,10 +391,12 @@ extension FileDescriptor { internal static func _pipe() -> Result<(readEnd: FileDescriptor, writeEnd: FileDescriptor), Errno> { var fds: (Int32, Int32) = (-1, -1) return withUnsafeMutablePointer(to: &fds) { pointer in - valueOrErrno(retryOnInterrupt: false) { - system_pipe(UnsafeMutableRawPointer(pointer).assumingMemoryBound(to: Int32.self)) + pointer.withMemoryRebound(to: Int32.self, capacity: 2) { fds in + valueOrErrno(retryOnInterrupt: false) { + system_pipe(fds) + }.map { _ in (.init(rawValue: fds[0]), .init(rawValue: fds[1])) } } - }.map { _ in (.init(rawValue: fds.0), .init(rawValue: fds.1)) } + } } } #endif diff --git a/Sources/System/SystemString.swift b/Sources/System/SystemString.swift index be5b6a8c..a918a929 100644 --- a/Sources/System/SystemString.swift +++ b/Sources/System/SystemString.swift @@ -171,12 +171,15 @@ extension SystemString { internal func withCodeUnits( _ f: (UnsafeBufferPointer) throws -> T ) rethrows -> T { - try withSystemChars { - // TODO: Is this the right way? - let base = UnsafeRawPointer( - $0.baseAddress - )!.assumingMemoryBound(to: CInterop.PlatformUnicodeEncoding.CodeUnit.self) - return try f(UnsafeBufferPointer(start: base, count: $0.count)) + try withSystemChars { chars in + let length = chars.count * MemoryLayout.stride + let count = length / MemoryLayout.stride + return try chars.baseAddress!.withMemoryRebound( + to: CInterop.PlatformUnicodeEncoding.CodeUnit.self, + capacity: count + ) { pointer in + try f(UnsafeBufferPointer(start: pointer, count: count)) + } } } } @@ -272,13 +275,15 @@ extension SystemString { internal func withPlatformString( _ f: (UnsafePointer) throws -> T ) rethrows -> T { - try withSystemChars { - // TODO: Is this the right way? - let base = UnsafeRawPointer( - $0.baseAddress - )!.assumingMemoryBound(to: CInterop.PlatformChar.self) - assert(base[self.count] == 0) - return try f(base) + try withSystemChars { chars in + let length = chars.count * MemoryLayout.stride + return try chars.baseAddress!.withMemoryRebound( + to: CInterop.PlatformChar.self, + capacity: length / MemoryLayout.stride + ) { pointer in + assert(pointer[self.count] == 0) + return try f(pointer) + } } } } diff --git a/Tests/SystemTests/FilePathTests/FilePathTest.swift b/Tests/SystemTests/FilePathTests/FilePathTest.swift index 168eab5a..4634129a 100644 --- a/Tests/SystemTests/FilePathTests/FilePathTest.swift +++ b/Tests/SystemTests/FilePathTests/FilePathTest.swift @@ -22,7 +22,9 @@ func filePathFromUnterminatedBytes(_ bytes: S) -> FilePath where S. array += [0] return array.withUnsafeBufferPointer { - FilePath(platformString: $0.baseAddress!._asCChar) + $0.withMemoryRebound(to: CChar.self) { + FilePath(platformString: $0.baseAddress!) + } } } let invalidBytes: [UInt8] = [0x2F, 0x61, 0x2F, 0x62, 0x2F, 0x83] diff --git a/Tests/SystemTests/SystemStringTests.swift b/Tests/SystemTests/SystemStringTests.swift index bcfe632c..bb13aa2c 100644 --- a/Tests/SystemTests/SystemStringTests.swift +++ b/Tests/SystemTests/SystemStringTests.swift @@ -10,29 +10,6 @@ // Tests for PlatformString, SystemString, and FilePath's forwarding APIs // TODO: Adapt test to Windows -extension UnsafePointer where Pointee == UInt8 { - internal var _asCChar: UnsafePointer { - UnsafeRawPointer(self).assumingMemoryBound(to: CChar.self) - } -} -extension UnsafePointer where Pointee == CChar { - internal var _asUInt8: UnsafePointer { - UnsafeRawPointer(self).assumingMemoryBound(to: UInt8.self) - } -} -extension UnsafeBufferPointer where Element == UInt8 { - internal var _asCChar: UnsafeBufferPointer { - let base = baseAddress?._asCChar - return UnsafeBufferPointer(start: base, count: self.count) - } -} -extension UnsafeBufferPointer where Element == CChar { - internal var _asUInt8: UnsafeBufferPointer { - let base = baseAddress?._asUInt8 - return UnsafeBufferPointer(start: base, count: self.count) - } -} - import XCTest @@ -46,7 +23,7 @@ private func makeRaw( _ str: String ) -> [CInterop.PlatformChar] { var str = str - var array = str.withUTF8 { Array($0._asCChar) } + var array = str.withUTF8 { $0.withMemoryRebound(to: CChar.self, Array.init) } array.append(0) return array } From 6db5a4a43bc3770145489a4517fa567d89be0041 Mon Sep 17 00:00:00 2001 From: Alex Martini Date: Mon, 10 Jan 2022 16:40:45 -0800 Subject: [PATCH 073/427] Fix broken links. Fixes rdar://79096457 --- Sources/System/FileDescriptor.swift | 12 ++++++------ Sources/System/FileOperations.swift | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Sources/System/FileDescriptor.swift b/Sources/System/FileDescriptor.swift index 9a7d319c..fbb7df30 100644 --- a/Sources/System/FileDescriptor.swift +++ b/Sources/System/FileDescriptor.swift @@ -106,7 +106,7 @@ extension FileDescriptor { /// the system doesn't wait for the device or file /// to be ready or available. /// If the - /// + /// /// call would result in the process being blocked for some reason, /// that method returns immediately. /// This flag also has the effect of making all @@ -164,14 +164,14 @@ extension FileDescriptor { /// expecting that it doesn't exist. /// /// If this option and ``create`` are both specified and the file exists, - /// + /// /// returns an error instead of creating the file. /// You can use this, for example, /// to implement a simple exclusive-access locking mechanism. /// /// If this option and ``create`` are both specified /// and the last component of the file's path is a symbolic link, - /// + /// /// fails even if the symbolic link points to a nonexistent name. /// /// The corresponding C constant is `O_EXCL`. @@ -223,7 +223,7 @@ extension FileDescriptor { /// /// If you specify this option /// and the file path you pass to - /// + /// /// is a symbolic link, /// then that open operation fails. /// @@ -238,7 +238,7 @@ extension FileDescriptor { /// Indicates that opening the file only succeeds if the file is a directory. /// /// If you specify this option and the file path you pass to - /// + /// /// is a not a directory, then that open operation fails. /// /// The corresponding C constant is `O_DIRECTORY`. @@ -256,7 +256,7 @@ extension FileDescriptor { /// /// If you specify this option /// and the file path you pass to - /// + /// /// is a symbolic link, /// then the link itself is opened instead of what it links to. /// diff --git a/Sources/System/FileOperations.swift b/Sources/System/FileOperations.swift index 28226e45..a8b107cd 100644 --- a/Sources/System/FileOperations.swift +++ b/Sources/System/FileOperations.swift @@ -185,7 +185,7 @@ extension FileDescriptor { /// The property of `buffer` /// determines the maximum number of bytes that are read into that buffer. /// - /// Unlike , + /// Unlike , /// this method leaves the file's existing offset unchanged. /// /// The corresponding C function is `pread`. From 25380a1bea51ffade7553d5c624e3cb5359d758e Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Fri, 8 Apr 2022 06:20:24 -0600 Subject: [PATCH 074/427] Mitigate misuse of pointer conversion in String inits --- Sources/System/PlatformString.swift | 106 ++++++++++++++++++++++++++++ 1 file changed, 106 insertions(+) diff --git a/Sources/System/PlatformString.swift b/Sources/System/PlatformString.swift index 358f3494..b75a2c89 100644 --- a/Sources/System/PlatformString.swift +++ b/Sources/System/PlatformString.swift @@ -24,6 +24,56 @@ extension String { self.init(_errorCorrectingPlatformString: platformString) } + /// Creates a string by interpreting the null-terminated platform string as + /// UTF-8 on Unix and UTF-16 on Windows. + /// + /// - Parameter platformString: The null-terminated platform string to be + /// interpreted as `CInterop.PlatformUnicodeEncoding`. + /// + /// - Note It is a precondition that `platformString` must be null-terminated. + /// The absence of a null byte will trigger a runtime error. + /// + /// If the content of the platform string isn't well-formed Unicode, + /// this initializer replaces invalid bytes with U+FFFD. + /// This means that, depending on the semantics of the specific platform, + /// conversion to a string and back might result in a value that's different + /// from the original platform string. + @inlinable + @_alwaysEmitIntoClient + public init(platformString: [CInterop.PlatformChar]) { + guard let _ = platformString.firstIndex(of: 0) else { + fatalError( + "input of String.init(platformString:) must be null-terminated" + ) + } + self = platformString.withUnsafeBufferPointer { + String(platformString: $0.baseAddress!) + } + } + + @available(*, deprecated, message: "Use String(_ scalar: Unicode.Scalar)") + @inlinable + @_alwaysEmitIntoClient + public init(platformString: inout CInterop.PlatformChar) { + guard platformString == 0 else { + fatalError( + "input of String.init(platformString:) must be null-terminated" + ) + } + self = "" + } + + @available(*, deprecated, message: "Use a copy of the String argument") + @inlinable + @_alwaysEmitIntoClient + public init(platformString: String) { + if let nullLoc = platformString.firstIndex(of: "\0") { + self = String(platformString[.. Date: Fri, 8 Apr 2022 06:20:54 -0600 Subject: [PATCH 075/427] Mitigate misuse of pointer conversion in FilePath inits --- Sources/System/FilePath/FilePathString.swift | 168 ++++++++++++++++++- 1 file changed, 166 insertions(+), 2 deletions(-) diff --git a/Sources/System/FilePath/FilePathString.swift b/Sources/System/FilePath/FilePathString.swift index 2c245e9b..b690979c 100644 --- a/Sources/System/FilePath/FilePathString.swift +++ b/Sources/System/FilePath/FilePathString.swift @@ -20,6 +20,49 @@ extension FilePath { self.init(_platformString: platformString) } + /// Creates a file path by copying bytes from a null-terminated platform + /// string. + /// + /// - Note It is a precondition that `platformString` must be null-terminated. + /// The absence of a null byte will trigger a runtime error. + /// + /// - Parameter platformString: A null-terminated platform string. + @inlinable + @_alwaysEmitIntoClient + public init(platformString: [CInterop.PlatformChar]) { + guard let _ = platformString.firstIndex(of: 0) else { + fatalError( + "input of FilePath.init(platformString:) must be null-terminated" + ) + } + self = platformString.withUnsafeBufferPointer { + FilePath(platformString: $0.baseAddress!) + } + } + + @inlinable + @_alwaysEmitIntoClient + @available(*, deprecated, message: "Use FilePath(_: String) to create a path from a String.") + public init(platformString: inout CInterop.PlatformChar) { + guard platformString == 0 else { + fatalError( + "input of FilePath.init(platformString:) must be null-terminated" + ) + } + self = FilePath() + } + + @inlinable + @_alwaysEmitIntoClient + @available(*, deprecated, message: "Use FilePath(_: String) to create a path from a String.") + public init(platformString: String) { + if let nullLoc = platformString.firstIndex(of: "\0") { + self = FilePath(String(platformString[..) { self.init(platformString: cString) } + @available(*, deprecated, renamed: "init(platformString:)") + public init(cString: [CChar]) { + self.init(platformString: cString) + } + + @available(*, deprecated, renamed: "init(platformString:)") + public init(cString: inout CChar) { + self.init(platformString: &cString) + } + + @available(*, deprecated, renamed: "init(platformString:)") + public init(cString: String) { + self.init(platformString: cString) + } + /// For backwards compatibility only. This function is equivalent to /// the preferred `withPlatformString`. public func withCString( From 469ede0fa2d99c0246f28a68b1fe6f59ff67eda9 Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Wed, 11 May 2022 02:53:29 -0600 Subject: [PATCH 076/427] Add tests for every new overload added --- Tests/SystemTests/SystemStringTests.swift | 162 ++++++++++++++++++++++ 1 file changed, 162 insertions(+) diff --git a/Tests/SystemTests/SystemStringTests.swift b/Tests/SystemTests/SystemStringTests.swift index bb13aa2c..a64e8f50 100644 --- a/Tests/SystemTests/SystemStringTests.swift +++ b/Tests/SystemTests/SystemStringTests.swift @@ -252,3 +252,165 @@ final class SystemStringTest: XCTestCase { } +extension SystemStringTest { + func test_String_initWithArrayConversion() { + let source: [CInterop.PlatformChar] = [0x61, 0x62, 0, 0x63] + let str = String(platformString: source) + source.withUnsafeBufferPointer { + XCTAssertEqual(str, String(platformString: $0.baseAddress!)) + } + } + + @available(*, deprecated) // silence the warning for using a deprecated api + func test_String_initWithStringConversion() { + let source = "ab\0c" + var str: String + str = String(platformString: source) + source.withPlatformString { + XCTAssertEqual(str, String(platformString: $0)) + } + str = String(platformString: "") + XCTAssertEqual(str.isEmpty, true) + } + + @available(*, deprecated) // silence the warning for using a deprecated api + func test_String_initWithInoutConversion() { + var c: CInterop.PlatformChar = 0 + let str = String(platformString: &c) + // Any other value of `c` would violate the null-terminated precondition + XCTAssertEqual(str.isEmpty, true) + } + + func test_String_validatingPlatformStringWithArrayConversion() { + var source: [CInterop.PlatformChar] = [0x61, 0x62, 0, 0x63] + var str: String? + str = String(validatingPlatformString: source) + source.withUnsafeBufferPointer { + XCTAssertEqual(str, String(validatingPlatformString: $0.baseAddress!)) + } + source[1] = CInterop.PlatformChar(truncatingIfNeeded: 0xffff) + str = String(validatingPlatformString: source) + XCTAssertNil(str) + } + + @available(*, deprecated) // silence the warning for using a deprecated api + func test_String_validatingPlatformStringWithStringConversion() { + let source = "ab\0c" + var str: String? + str = String(validatingPlatformString: source) + XCTAssertNotNil(str) + source.withPlatformString { + XCTAssertEqual(str, String.init(validatingPlatformString: $0)) + } + str = String(validatingPlatformString: "") + XCTAssertNotNil(str) + XCTAssertEqual(str?.isEmpty, true) + } + + @available(*, deprecated) // silence the warning for using a deprecated api + func test_String_validatingPlatformStringWithInoutConversion() { + var c: CInterop.PlatformChar = 0 + let str = String(validatingPlatformString: &c) + // Any other value of `c` would violate the null-terminated precondition + XCTAssertNotNil(str) + XCTAssertEqual(str?.isEmpty, true) + } + + func test_FilePath_initWithArrayConversion() { + let source: [CInterop.PlatformChar] = [0x61, 0x62, 0, 0x63] + let path = FilePath(platformString: source) + source.withUnsafeBufferPointer { + XCTAssertEqual(path, FilePath(platformString: $0.baseAddress!)) + } + } + + @available(*, deprecated) // silence the warning for using a deprecated api + func test_FilePath_initWithStringConversion() { + let source = "ab\0c" + var path: FilePath + path = FilePath(platformString: source) + source.withPlatformString { + XCTAssertEqual(path, FilePath(platformString: $0)) + } + path = FilePath(platformString: "") + XCTAssertEqual(path.string.isEmpty, true) + } + + @available(*, deprecated) // silence the warning for using a deprecated api + func test_FilePath_initWithInoutConversion() { + var c: CInterop.PlatformChar = 0 + let path = FilePath(platformString: &c) + // Any other value of `c` would violate the null-terminated precondition + XCTAssertEqual(path.string.isEmpty, true) + } + + func test_FilePathComponent_initWithArrayConversion() { + var source: [CInterop.PlatformChar] = [0x61, 0x62, 0, 0x63] + var component: FilePath.Component? + component = FilePath.Component(platformString: source) + source.withUnsafeBufferPointer { + XCTAssertEqual(component, .init(platformString: $0.baseAddress!)) + } + source[1] = CInterop.PlatformChar(truncatingIfNeeded: 0xffff) + component = FilePath.Component(platformString: source) + source.withUnsafeBufferPointer { + XCTAssertEqual(component, .init(platformString: $0.baseAddress!)) + } + } + + @available(*, deprecated) // silence the warning for using a deprecated api + func test_FilePathComponent_initWithStringConversion() { + let source = "ab\0c" + var component: FilePath.Component? + component = FilePath.Component(platformString: source) + source.withPlatformString { + XCTAssertEqual(component, FilePath.Component(platformString: $0)) + } + component = FilePath.Component(platformString: "") + XCTAssertNil(component) + } + + @available(*, deprecated) // silence the warning for using a deprecated api + func test_FilePathComponent_initWithInoutConversion() { + var c: CInterop.PlatformChar = 0 + let component = FilePath.Component(platformString: &c) + XCTAssertNil(component) + } + + func test_FilePathRoot_initWithArrayConversion() { + let source: [CInterop.PlatformChar] + #if os(Windows) + source = [0x41, 0x3a, 0x5c, 0, 0x7f] + #else // unix + source = [0x2f, 0, 0x7f] + #endif + var root: FilePath.Root? + root = FilePath.Root(platformString: source) + source.withUnsafeBufferPointer { + XCTAssertEqual(root, FilePath.Root(platformString: $0.baseAddress!)) + } + } + + @available(*, deprecated) // silence the warning for using a deprecated api + func test_FilePathRoot_initWithStringConversion() { + #if os(Windows) + let source = "C:\\\0 and the rest" + #else // unix + let source = "/\0 and the rest" + #endif + var root: FilePath.Root? + root = FilePath.Root(platformString: source) + source.withPlatformString { + XCTAssertEqual(root, FilePath.Root(platformString: $0)) + } + root = FilePath.Root(platformString: "") + XCTAssertNil(root) + } + + @available(*, deprecated) // silence the warning for using a deprecated api + func test_FilePathRoot_initWithInoutConversion() { + var c: CInterop.PlatformChar = 0 + let root = FilePath.Root(platformString: &c) + XCTAssertNil(root) + } +} From 3920df78d9e76d76bf30f39803aee0134e89962d Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Fri, 3 Jun 2022 16:35:00 -0600 Subject: [PATCH 077/427] Correct deprecation messages --- Sources/System/FilePath/FilePathString.swift | 12 ++++++------ Sources/System/PlatformString.swift | 8 ++++---- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/Sources/System/FilePath/FilePathString.swift b/Sources/System/FilePath/FilePathString.swift index b690979c..5b71f30a 100644 --- a/Sources/System/FilePath/FilePathString.swift +++ b/Sources/System/FilePath/FilePathString.swift @@ -42,7 +42,7 @@ extension FilePath { @inlinable @_alwaysEmitIntoClient - @available(*, deprecated, message: "Use FilePath(_: String) to create a path from a String.") + @available(*, deprecated, message: "Use FilePath.init(_ scalar: Unicode.Scalar)") public init(platformString: inout CInterop.PlatformChar) { guard platformString == 0 else { fatalError( @@ -54,7 +54,7 @@ extension FilePath { @inlinable @_alwaysEmitIntoClient - @available(*, deprecated, message: "Use FilePath(_: String) to create a path from a String.") + @available(*, deprecated, message: "Use FilePath(_: String) to create a path from a String") public init(platformString: String) { if let nullLoc = platformString.firstIndex(of: "\0") { self = FilePath(String(platformString[.. Date: Wed, 8 Jun 2022 18:54:55 -0700 Subject: [PATCH 078/427] Import C module as `@_implementationOnly` (#83) --- Sources/System/Internals/CInterop.swift | 2 +- Sources/System/Internals/Constants.swift | 1 - Sources/System/Internals/Exports.swift | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/Sources/System/Internals/CInterop.swift b/Sources/System/Internals/CInterop.swift index 2a9832ec..cb84a590 100644 --- a/Sources/System/Internals/CInterop.swift +++ b/Sources/System/Internals/CInterop.swift @@ -17,7 +17,7 @@ public typealias CModeT = CInterop.Mode #if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) import Darwin #elseif os(Linux) || os(FreeBSD) || os(Android) -import CSystem +@_implementationOnly import CSystem import Glibc #elseif os(Windows) import CSystem diff --git a/Sources/System/Internals/Constants.swift b/Sources/System/Internals/Constants.swift index a4dbb89c..5489a550 100644 --- a/Sources/System/Internals/Constants.swift +++ b/Sources/System/Internals/Constants.swift @@ -14,7 +14,6 @@ #if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) import Darwin #elseif os(Linux) || os(FreeBSD) || os(Android) -import CSystem import Glibc #elseif os(Windows) import CSystem diff --git a/Sources/System/Internals/Exports.swift b/Sources/System/Internals/Exports.swift index 2b4ae3be..5c0a58a6 100644 --- a/Sources/System/Internals/Exports.swift +++ b/Sources/System/Internals/Exports.swift @@ -15,7 +15,7 @@ #if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) import Darwin #elseif os(Linux) || os(FreeBSD) || os(Android) -import CSystem +@_implementationOnly import CSystem import Glibc #elseif os(Windows) import CSystem From 94fa6bd2ff28d04b01ae4147c26a111c2a8ba25f Mon Sep 17 00:00:00 2001 From: Si Beaumont Date: Thu, 9 Jun 2022 16:51:28 +0100 Subject: [PATCH 079/427] FileOperations: Add Windows implementation for FileDescriptor.resize(to:) (#89) * FileOperations: Add Windows implementation for FileDescriptor.resize(to:) Replace _chsize with _chsize_s on Windows --- Sources/System/FileOperations.swift | 2 -- Sources/System/Internals/WindowsSyscallAdapters.swift | 7 +++++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/Sources/System/FileOperations.swift b/Sources/System/FileOperations.swift index a8b107cd..055daeea 100644 --- a/Sources/System/FileOperations.swift +++ b/Sources/System/FileOperations.swift @@ -401,7 +401,6 @@ extension FileDescriptor { } #endif -#if !os(Windows) /*System 1.2.0, @available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *)*/ extension FileDescriptor { /// Truncate or extend the file referenced by this file descriptor. @@ -447,4 +446,3 @@ extension FileDescriptor { } } } -#endif diff --git a/Sources/System/Internals/WindowsSyscallAdapters.swift b/Sources/System/Internals/WindowsSyscallAdapters.swift index 36acaceb..13f42c96 100644 --- a/Sources/System/Internals/WindowsSyscallAdapters.swift +++ b/Sources/System/Internals/WindowsSyscallAdapters.swift @@ -105,4 +105,11 @@ internal func pwrite( return Int(nNumberOfBytesWritten) } +@inline(__always) +internal func ftruncate( + _ fd: Int32, + _ length: off_t +) -> Int32 { + _chsize_s(fd, numericCast(length)) +} #endif From f5e4aac51b30df607f4c5fbd1886fabacc424b41 Mon Sep 17 00:00:00 2001 From: Karoy Lorentey Date: Sat, 11 Jun 2022 12:54:19 -0700 Subject: [PATCH 080/427] Revert "FileOperations: Add Windows implementation for FileDescriptor.resize(to:) (#89)" This reverts commit 94fa6bd2ff28d04b01ae4147c26a111c2a8ba25f. --- Sources/System/FileOperations.swift | 2 ++ Sources/System/Internals/WindowsSyscallAdapters.swift | 7 ------- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/Sources/System/FileOperations.swift b/Sources/System/FileOperations.swift index 055daeea..a8b107cd 100644 --- a/Sources/System/FileOperations.swift +++ b/Sources/System/FileOperations.swift @@ -401,6 +401,7 @@ extension FileDescriptor { } #endif +#if !os(Windows) /*System 1.2.0, @available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *)*/ extension FileDescriptor { /// Truncate or extend the file referenced by this file descriptor. @@ -446,3 +447,4 @@ extension FileDescriptor { } } } +#endif diff --git a/Sources/System/Internals/WindowsSyscallAdapters.swift b/Sources/System/Internals/WindowsSyscallAdapters.swift index 13f42c96..36acaceb 100644 --- a/Sources/System/Internals/WindowsSyscallAdapters.swift +++ b/Sources/System/Internals/WindowsSyscallAdapters.swift @@ -105,11 +105,4 @@ internal func pwrite( return Int(nNumberOfBytesWritten) } -@inline(__always) -internal func ftruncate( - _ fd: Int32, - _ length: off_t -) -> Int32 { - _chsize_s(fd, numericCast(length)) -} #endif From 6d8abddbc851df54d40af0c0872752d1a251b843 Mon Sep 17 00:00:00 2001 From: Saleem Abdulrasool Date: Thu, 9 Jun 2022 17:26:49 -0700 Subject: [PATCH 081/427] Tests: explicitly convert the character type The platform character type is different across Unix and Windows. Explicitly convert the value to the expected type. --- Tests/SystemTests/SystemCharTest.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/SystemTests/SystemCharTest.swift b/Tests/SystemTests/SystemCharTest.swift index 368f21ae..0ec5411e 100644 --- a/Tests/SystemTests/SystemCharTest.swift +++ b/Tests/SystemTests/SystemCharTest.swift @@ -25,7 +25,7 @@ final class SystemCharTest: XCTestCase { // non printable for value in 0..<(UInt8(ascii: " ")) { - XCTAssertFalse(SystemChar(codeUnit: value).isLetter) + XCTAssertFalse(SystemChar(codeUnit: CInterop.PlatformUnicodeEncoding.CodeUnit(value)).isLetter) } XCTAssertFalse(SystemChar(codeUnit: 0x7F).isLetter) // DEL From 59ddf00c8e7c8b7bc97aceb99223bee6153f7041 Mon Sep 17 00:00:00 2001 From: Saleem Abdulrasool Date: Sat, 11 Jun 2022 14:54:35 -0700 Subject: [PATCH 082/427] Tests: remove error codes that are unavailable on windows Not all the error codes are available on Windows, this matches SystemErrno cases to remove the cases that have no definition on Windows. --- Tests/SystemTests/ErrnoTest.swift | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/Tests/SystemTests/ErrnoTest.swift b/Tests/SystemTests/ErrnoTest.swift index 4bbe88f6..02d738e7 100644 --- a/Tests/SystemTests/ErrnoTest.swift +++ b/Tests/SystemTests/ErrnoTest.swift @@ -32,7 +32,9 @@ final class ErrnoTest: XCTestCase { XCTAssert(ENOMEM == Errno.noMemory.rawValue) XCTAssert(EACCES == Errno.permissionDenied.rawValue) XCTAssert(EFAULT == Errno.badAddress.rawValue) +#if !os(Windows) XCTAssert(ENOTBLK == Errno.notBlockDevice.rawValue) +#endif XCTAssert(EBUSY == Errno.resourceBusy.rawValue) XCTAssert(EEXIST == Errno.fileExists.rawValue) XCTAssert(EXDEV == Errno.improperLink.rawValue) @@ -42,8 +44,10 @@ final class ErrnoTest: XCTestCase { XCTAssert(EINVAL == Errno.invalidArgument.rawValue) XCTAssert(ENFILE == Errno.tooManyOpenFilesInSystem.rawValue) XCTAssert(EMFILE == Errno.tooManyOpenFiles.rawValue) +#if !os(Windows) XCTAssert(ENOTTY == Errno.inappropriateIOCTLForDevice.rawValue) XCTAssert(ETXTBSY == Errno.textFileBusy.rawValue) +#endif XCTAssert(EFBIG == Errno.fileTooLarge.rawValue) XCTAssert(ENOSPC == Errno.noSpace.rawValue) XCTAssert(ESPIPE == Errno.illegalSeek.rawValue) @@ -111,7 +115,9 @@ final class ErrnoTest: XCTestCase { XCTAssert(EDEVERR == Errno.deviceError.rawValue) #endif +#if !os(Windows) XCTAssert(EOVERFLOW == Errno.overflow.rawValue) +#endif #if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) XCTAssert(EBADEXEC == Errno.badExecutable.rawValue) @@ -121,14 +127,17 @@ final class ErrnoTest: XCTestCase { #endif XCTAssert(ECANCELED == Errno.canceled.rawValue) +#if !os(Windows) XCTAssert(EIDRM == Errno.identifierRemoved.rawValue) XCTAssert(ENOMSG == Errno.noMessage.rawValue) +#endif XCTAssert(EILSEQ == Errno.illegalByteSequence.rawValue) #if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) XCTAssert(ENOATTR == Errno.attributeNotFound.rawValue) #endif +#if !os(Windows) XCTAssert(EBADMSG == Errno.badMessage.rawValue) XCTAssert(EMULTIHOP == Errno.multiHop.rawValue) XCTAssert(ENODATA == Errno.noData.rawValue) @@ -137,6 +146,7 @@ final class ErrnoTest: XCTestCase { XCTAssert(ENOSTR == Errno.notStream.rawValue) XCTAssert(EPROTO == Errno.protocolError.rawValue) XCTAssert(ETIME == Errno.timeout.rawValue) +#endif XCTAssert(EOPNOTSUPP == Errno.notSupportedOnSocket.rawValue) // From headers but not man page @@ -148,8 +158,10 @@ final class ErrnoTest: XCTestCase { XCTAssert(ENOPOLICY == Errno.noSuchPolicy.rawValue) #endif +#if !os(Windows) XCTAssert(ENOTRECOVERABLE == Errno.notRecoverable.rawValue) XCTAssert(EOWNERDEAD == Errno.previousOwnerDied.rawValue) +#endif #if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) XCTAssert(EQFULL == Errno.outputQueueFull.rawValue) From 8e247d927e1e5edfc3962c4db1b61c3d7e99849f Mon Sep 17 00:00:00 2001 From: Saleem Abdulrasool Date: Sun, 12 Jun 2022 11:30:50 -0700 Subject: [PATCH 083/427] Tests: remove some flags from file tests Windows does not support some flags (e.g. `O_CLOEXEC`). This removes the unsupported options on Windows. --- Tests/SystemTests/FileTypesTest.swift | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Tests/SystemTests/FileTypesTest.swift b/Tests/SystemTests/FileTypesTest.swift index b8cf4969..81b21684 100644 --- a/Tests/SystemTests/FileTypesTest.swift +++ b/Tests/SystemTests/FileTypesTest.swift @@ -29,13 +29,17 @@ final class FileDescriptorTest: XCTestCase { XCTAssertEqual(O_WRONLY, FileDescriptor.AccessMode.writeOnly.rawValue) XCTAssertEqual(O_RDWR, FileDescriptor.AccessMode.readWrite.rawValue) +#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) +#if !os(Windows) XCTAssertEqual(O_NOFOLLOW, FileDescriptor.OpenOptions.noFollow.rawValue) XCTAssertEqual(O_CLOEXEC, FileDescriptor.OpenOptions.closeOnExec.rawValue) +#endif // BSD only #if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) From f9a514897ef42460a1699c9e35bdc82ed9f21a87 Mon Sep 17 00:00:00 2001 From: Saleem Abdulrasool Date: Sun, 12 Jun 2022 11:34:51 -0700 Subject: [PATCH 084/427] Tests: explicitly convert to UTF16 on Windows File paths on Windows are represented in UTF16, while Swift strings are UTF8. Explicitly convert from UTF16 on Windows. --- Tests/SystemTests/FilePathTests/FilePathTest.swift | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Tests/SystemTests/FilePathTests/FilePathTest.swift b/Tests/SystemTests/FilePathTests/FilePathTest.swift index 4634129a..9dc36f9e 100644 --- a/Tests/SystemTests/FilePathTests/FilePathTest.swift +++ b/Tests/SystemTests/FilePathTests/FilePathTest.swift @@ -69,7 +69,11 @@ final class FilePathTest: XCTestCase { } testPath.filePath.withPlatformString { +#if os(Windows) + XCTAssertEqual(testPath.string, String(decodingCString: $0, as: UTF16.self)) +#else XCTAssertEqual(testPath.string, String(cString: $0)) +#endif XCTAssertEqual(testPath.filePath, FilePath(platformString: $0)) } } From 18c5f7a54b5fd24106d82444d965527aa6169142 Mon Sep 17 00:00:00 2001 From: Saleem Abdulrasool Date: Sun, 12 Jun 2022 11:54:15 -0700 Subject: [PATCH 085/427] Tests: change the conversion for string to array Windows uses UTF16 for the platform character, not UTF8, and does not map to CChar. Add a Windows path for the conversion. --- Tests/SystemTests/SystemStringTests.swift | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Tests/SystemTests/SystemStringTests.swift b/Tests/SystemTests/SystemStringTests.swift index a64e8f50..7ac0d340 100644 --- a/Tests/SystemTests/SystemStringTests.swift +++ b/Tests/SystemTests/SystemStringTests.swift @@ -23,7 +23,11 @@ private func makeRaw( _ str: String ) -> [CInterop.PlatformChar] { var str = str +#if os(Windows) + var array = Array(str.utf16) +#else var array = str.withUTF8 { $0.withMemoryRebound(to: CChar.self, Array.init) } +#endif array.append(0) return array } From 429f64259fbad1dff4ca7989dfafbc5fa1cd16bc Mon Sep 17 00:00:00 2001 From: Saleem Abdulrasool Date: Sun, 12 Jun 2022 11:44:21 -0700 Subject: [PATCH 086/427] Tests: adjust the Windows error codes The Windows error codes are not re-exported, explicitly import the WinSDK module to gain access to the error codes. Add Windows specific checks to ensure that the named forms match the underlying implementation. --- Tests/SystemTests/ErrnoTest.swift | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/Tests/SystemTests/ErrnoTest.swift b/Tests/SystemTests/ErrnoTest.swift index 4bbe88f6..62df0b21 100644 --- a/Tests/SystemTests/ErrnoTest.swift +++ b/Tests/SystemTests/ErrnoTest.swift @@ -15,6 +15,10 @@ import SystemPackage import System #endif +#if os(Windows) +import WinSDK +#endif + /*System 0.0.1, @available(macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0, *)*/ final class ErrnoTest: XCTestCase { func testConstants() { @@ -61,9 +65,15 @@ final class ErrnoTest: XCTestCase { XCTAssert(EPROTOTYPE == Errno.protocolWrongTypeForSocket.rawValue) XCTAssert(ENOPROTOOPT == Errno.protocolNotAvailable.rawValue) XCTAssert(EPROTONOSUPPORT == Errno.protocolNotSupported.rawValue) +#if os(Windows) + XCTAssert(WSAESOCKTNOSUPPORT == Errno.socketTypeNotSupported.rawValue) + XCTAssert(WSAEOPNOTSUPP == Errno.notSupported.rawValue) + XCTAssert(WSAEPFNOSUPPORT == Errno.protocolFamilyNotSupported.rawValue) +#else XCTAssert(ESOCKTNOSUPPORT == Errno.socketTypeNotSupported.rawValue) XCTAssert(ENOTSUP == Errno.notSupported.rawValue) XCTAssert(EPFNOSUPPORT == Errno.protocolFamilyNotSupported.rawValue) +#endif XCTAssert(EAFNOSUPPORT == Errno.addressFamilyNotSupported.rawValue) XCTAssert(EADDRINUSE == Errno.addressInUse.rawValue) XCTAssert(EADDRNOTAVAIL == Errno.addressNotAvailable.rawValue) @@ -75,12 +85,20 @@ final class ErrnoTest: XCTestCase { XCTAssert(ENOBUFS == Errno.noBufferSpace.rawValue) XCTAssert(EISCONN == Errno.socketIsConnected.rawValue) XCTAssert(ENOTCONN == Errno.socketNotConnected.rawValue) +#if os(Windows) + XCTAssert(WSAESHUTDOWN == Errno.socketShutdown.rawValue) +#else XCTAssert(ESHUTDOWN == Errno.socketShutdown.rawValue) +#endif XCTAssert(ETIMEDOUT == Errno.timedOut.rawValue) XCTAssert(ECONNREFUSED == Errno.connectionRefused.rawValue) XCTAssert(ELOOP == Errno.tooManySymbolicLinkLevels.rawValue) XCTAssert(ENAMETOOLONG == Errno.fileNameTooLong.rawValue) +#if os(Windows) + XCTAssert(WSAEHOSTDOWN == Errno.hostIsDown.rawValue) +#else XCTAssert(EHOSTDOWN == Errno.hostIsDown.rawValue) +#endif XCTAssert(EHOSTUNREACH == Errno.noRouteToHost.rawValue) XCTAssert(ENOTEMPTY == Errno.directoryNotEmpty.rawValue) @@ -88,9 +106,15 @@ final class ErrnoTest: XCTestCase { XCTAssert(EPROCLIM == Errno.tooManyProcesses.rawValue) #endif +#if os(Windows) + XCTAssert(WSAEUSERS == Errno.tooManyUsers.rawValue) + XCTAssert(WSAEDQUOT == Errno.diskQuotaExceeded.rawValue) + XCTAssert(WSAESTALE == Errno.staleNFSFileHandle.rawValue) +#else XCTAssert(EUSERS == Errno.tooManyUsers.rawValue) XCTAssert(EDQUOT == Errno.diskQuotaExceeded.rawValue) XCTAssert(ESTALE == Errno.staleNFSFileHandle.rawValue) +#endif #if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) XCTAssert(EBADRPC == Errno.rpcUnsuccessful.rawValue) @@ -141,8 +165,13 @@ final class ErrnoTest: XCTestCase { // From headers but not man page XCTAssert(EWOULDBLOCK == Errno.wouldBlock.rawValue) +#if os(Windows) + XCTAssert(WSAETOOMANYREFS == Errno.tooManyReferences.rawValue) + XCTAssert(WSAEREMOTE == Errno.tooManyRemoteLevels.rawValue) +#else XCTAssert(ETOOMANYREFS == Errno.tooManyReferences.rawValue) XCTAssert(EREMOTE == Errno.tooManyRemoteLevels.rawValue) +#endif #if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) XCTAssert(ENOPOLICY == Errno.noSuchPolicy.rawValue) From a5c43b17a95119f439540e2a8bc6d460dd288205 Mon Sep 17 00:00:00 2001 From: Saleem Abdulrasool Date: Sun, 12 Jun 2022 14:30:04 -0700 Subject: [PATCH 087/427] Tests: adjust invalid character Adjust the injected character to be an invalid UTF-16 and UTF-8 character. This is required to make the tests pass on Windows. --- Tests/SystemTests/SystemStringTests.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/SystemTests/SystemStringTests.swift b/Tests/SystemTests/SystemStringTests.swift index a64e8f50..2d445936 100644 --- a/Tests/SystemTests/SystemStringTests.swift +++ b/Tests/SystemTests/SystemStringTests.swift @@ -288,7 +288,7 @@ extension SystemStringTest { source.withUnsafeBufferPointer { XCTAssertEqual(str, String(validatingPlatformString: $0.baseAddress!)) } - source[1] = CInterop.PlatformChar(truncatingIfNeeded: 0xffff) + source[1] = CInterop.PlatformChar(truncatingIfNeeded: 0xdfff) str = String(validatingPlatformString: source) XCTAssertNil(str) } From e378fb2f0d7a1fdf37576a4bf6c32450a3805b4b Mon Sep 17 00:00:00 2001 From: Saleem Abdulrasool Date: Wed, 15 Jun 2022 09:00:23 -0700 Subject: [PATCH 088/427] Tests: ignore XCTest manifest on newer Swift (#91) This is motivated by the desire to build the test suite on Windows. Newer versions of SPM have test discovery which obviates the test manifest, which incidentally repairs the build on Windows while adopting newer functionality and reducing management complexity. --- Tests/SystemTests/XCTestManifests.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/SystemTests/XCTestManifests.swift b/Tests/SystemTests/XCTestManifests.swift index 0480347d..de99bd81 100644 --- a/Tests/SystemTests/XCTestManifests.swift +++ b/Tests/SystemTests/XCTestManifests.swift @@ -1,4 +1,4 @@ -#if !canImport(ObjectiveC) +#if !canImport(ObjectiveC) && swift(<5.5) import XCTest extension ErrnoTest { From 036f567f234a79312e1c7fcdcdbe3313d29366c6 Mon Sep 17 00:00:00 2001 From: Daniel Duan Date: Tue, 21 Jun 2022 08:50:24 -0700 Subject: [PATCH 089/427] Remove Windows warnings (#105) These calls are missing their Windows counter parts, resulting warnings stating `X is deprecated: The POSIX name for this item is deprecated. Instead, use the ISO C and C++ conformant name: Y. See online help for details`. Add them like the rest. --- .../Internals/WindowsSyscallAdapters.swift | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/Sources/System/Internals/WindowsSyscallAdapters.swift b/Sources/System/Internals/WindowsSyscallAdapters.swift index 36acaceb..2f3851b6 100644 --- a/Sources/System/Internals/WindowsSyscallAdapters.swift +++ b/Sources/System/Internals/WindowsSyscallAdapters.swift @@ -58,6 +58,23 @@ internal func write( Int(_write(fd, buf, numericCast(nbyte))) } +@inline(__always) +internal func lseek( + _ fd: Int32, _ off: off_t, _ whence: Int32 +) -> off_t { + _lseek(fd, off, whence) +} + +@inline(__always) +internal func dup(_ fd: Int32) -> Int32 { + _dup(fd) +} + +@inline(__always) +internal func dup2(_ fd: Int32, _ fd2: Int32) -> Int32 { + _dup2(fd, fd2) +} + @inline(__always) internal func pread( _ fd: Int32, _ buf: UnsafeMutableRawPointer!, _ nbyte: Int, _ offset: off_t From 015e9baf40046a90964bbaa39f908cfd018e01aa Mon Sep 17 00:00:00 2001 From: Alex Martini Date: Tue, 23 Aug 2022 06:21:09 -0700 Subject: [PATCH 090/427] Fix a broken link. (#107) --- Sources/System/FileOperations.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/System/FileOperations.swift b/Sources/System/FileOperations.swift index a8b107cd..81681608 100644 --- a/Sources/System/FileOperations.swift +++ b/Sources/System/FileOperations.swift @@ -144,7 +144,7 @@ extension FileDescriptor { /// Pass `false` to try only once and throw an error upon interruption. /// - Returns: The number of bytes that were read. /// - /// The property of `buffer` + /// The property of `buffer` /// determines the maximum number of bytes that are read into that buffer. /// /// After reading, @@ -182,7 +182,7 @@ extension FileDescriptor { /// Pass `false` to try only once and throw an error upon interruption. /// - Returns: The number of bytes that were read. /// - /// The property of `buffer` + /// The property of `buffer` /// determines the maximum number of bytes that are read into that buffer. /// /// Unlike , From e1f6974ba695f3af07d267a8b45f51d3227a5a16 Mon Sep 17 00:00:00 2001 From: Marcin Krzyzanowski Date: Sat, 10 Sep 2022 02:21:53 +0200 Subject: [PATCH 091/427] Quote include path --- cmake/modules/SwiftSystemConfig.cmake.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/modules/SwiftSystemConfig.cmake.in b/cmake/modules/SwiftSystemConfig.cmake.in index 12a95c32..321d9389 100644 --- a/cmake/modules/SwiftSystemConfig.cmake.in +++ b/cmake/modules/SwiftSystemConfig.cmake.in @@ -8,5 +8,5 @@ See https://swift.org/LICENSE.txt for license information #]] if(NOT TARGET SystemPackage) - include(@SWIFT_SYSTEM_EXPORTS_FILE@) + include("@SWIFT_SYSTEM_EXPORTS_FILE@") endif() From 18cceda484a71b408b17a89bdab2ab8124782a2b Mon Sep 17 00:00:00 2001 From: 3405691582 Date: Mon, 31 Oct 2022 11:22:05 -0400 Subject: [PATCH 092/427] Support OpenBSD. (#113) Add the usual `os` conditionals and exclude errno constants that aren't present on this platform. Maybe availability annotations are a better way to deal with this, but that is likely something that requires more careful consideration. --- Sources/System/Errno.swift | 4 ++++ Sources/System/Internals/CInterop.swift | 2 +- Sources/System/Internals/Constants.swift | 6 +++++- Sources/System/Internals/Exports.swift | 2 +- Sources/System/Internals/Syscalls.swift | 2 +- 5 files changed, 12 insertions(+), 4 deletions(-) diff --git a/Sources/System/Errno.swift b/Sources/System/Errno.swift index e2ebadb3..9b0cf886 100644 --- a/Sources/System/Errno.swift +++ b/Sources/System/Errno.swift @@ -1274,6 +1274,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { @available(*, unavailable, renamed: "badMessage") public static var EBADMSG: Errno { badMessage } +#if !os(OpenBSD) /// Reserved. /// /// This error is reserved for future use. @@ -1333,6 +1334,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { @_alwaysEmitIntoClient @available(*, unavailable, renamed: "notStream") public static var ENOSTR: Errno { notStream } +#endif /// Protocol error. /// @@ -1348,6 +1350,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { @available(*, unavailable, renamed: "protocolError") public static var EPROTO: Errno { protocolError } +#if !os(OpenBSD) /// Reserved. /// /// This error is reserved for future use. @@ -1359,6 +1362,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { @_alwaysEmitIntoClient @available(*, unavailable, renamed: "timeout") public static var ETIME: Errno { timeout } +#endif #endif /// Operation not supported on socket. diff --git a/Sources/System/Internals/CInterop.swift b/Sources/System/Internals/CInterop.swift index cb84a590..5a08e1e8 100644 --- a/Sources/System/Internals/CInterop.swift +++ b/Sources/System/Internals/CInterop.swift @@ -16,7 +16,7 @@ public typealias CModeT = CInterop.Mode #if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) import Darwin -#elseif os(Linux) || os(FreeBSD) || os(Android) +#elseif os(Linux) || os(FreeBSD) || os(OpenBSD) || os(Android) @_implementationOnly import CSystem import Glibc #elseif os(Windows) diff --git a/Sources/System/Internals/Constants.swift b/Sources/System/Internals/Constants.swift index 5489a550..dcdeb926 100644 --- a/Sources/System/Internals/Constants.swift +++ b/Sources/System/Internals/Constants.swift @@ -13,7 +13,7 @@ #if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) import Darwin -#elseif os(Linux) || os(FreeBSD) || os(Android) +#elseif os(Linux) || os(FreeBSD) || os(OpenBSD) || os(Android) import Glibc #elseif os(Windows) import CSystem @@ -392,6 +392,7 @@ internal var _ENOATTR: CInt { ENOATTR } @_alwaysEmitIntoClient internal var _EBADMSG: CInt { EBADMSG } +#if !os(OpenBSD) @_alwaysEmitIntoClient internal var _EMULTIHOP: CInt { EMULTIHOP } @@ -406,13 +407,16 @@ internal var _ENOSR: CInt { ENOSR } @_alwaysEmitIntoClient internal var _ENOSTR: CInt { ENOSTR } +#endif @_alwaysEmitIntoClient internal var _EPROTO: CInt { EPROTO } +#if !os(OpenBSD) @_alwaysEmitIntoClient internal var _ETIME: CInt { ETIME } #endif +#endif @_alwaysEmitIntoClient internal var _EOPNOTSUPP: CInt { EOPNOTSUPP } diff --git a/Sources/System/Internals/Exports.swift b/Sources/System/Internals/Exports.swift index 5c0a58a6..44a4f90c 100644 --- a/Sources/System/Internals/Exports.swift +++ b/Sources/System/Internals/Exports.swift @@ -14,7 +14,7 @@ #if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) import Darwin -#elseif os(Linux) || os(FreeBSD) || os(Android) +#elseif os(Linux) || os(FreeBSD) || os(OpenBSD) || os(Android) @_implementationOnly import CSystem import Glibc #elseif os(Windows) diff --git a/Sources/System/Internals/Syscalls.swift b/Sources/System/Internals/Syscalls.swift index b22d6a3f..1cc27c7f 100644 --- a/Sources/System/Internals/Syscalls.swift +++ b/Sources/System/Internals/Syscalls.swift @@ -9,7 +9,7 @@ #if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) import Darwin -#elseif os(Linux) || os(FreeBSD) || os(Android) +#elseif os(Linux) || os(FreeBSD) || os(OpenBSD) || os(Android) import Glibc #elseif os(Windows) import ucrt From 77d9ca18c1953cd406c12934b68a2d735bd5d5b5 Mon Sep 17 00:00:00 2001 From: Florian Friedrich Date: Wed, 30 Nov 2022 22:04:49 +0100 Subject: [PATCH 093/427] Add Sendable conformances (#115) --- Sources/System/FileDescriptor.swift | 11 +++++++++++ Sources/System/FilePath/FilePath.swift | 3 +++ Sources/System/FilePath/FilePathComponentView.swift | 5 +++++ Sources/System/FilePath/FilePathComponents.swift | 6 ++++++ Sources/System/FilePermissions.swift | 4 ++++ Sources/System/SystemString.swift | 5 +++++ 6 files changed, 34 insertions(+) diff --git a/Sources/System/FileDescriptor.swift b/Sources/System/FileDescriptor.swift index fbb7df30..5cf0e07f 100644 --- a/Sources/System/FileDescriptor.swift +++ b/Sources/System/FileDescriptor.swift @@ -473,3 +473,14 @@ extension FileDescriptor.OpenOptions /// A textual representation of the open options, suitable for debugging. public var debugDescription: String { self.description } } + +#if compiler(>=5.5) && canImport(_Concurrency) +// The decision on whether to make FileDescriptor Sendable or not +// is currently being discussed in https://github.com/apple/swift-system/pull/112 +//@available(*, unavailable, message: "File descriptors are not completely thread-safe.") +//extension FileDescriptor: Sendable {} + +extension FileDescriptor.AccessMode: Sendable {} +extension FileDescriptor.OpenOptions: Sendable {} +extension FileDescriptor.SeekOrigin: Sendable {} +#endif diff --git a/Sources/System/FilePath/FilePath.swift b/Sources/System/FilePath/FilePath.swift index 1aaa27ec..206b2bb2 100644 --- a/Sources/System/FilePath/FilePath.swift +++ b/Sources/System/FilePath/FilePath.swift @@ -69,3 +69,6 @@ extension FilePath { /*System 0.0.1, @available(macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0, *)*/ extension FilePath: Hashable, Codable {} +#if compiler(>=5.5) && canImport(_Concurrency) +extension FilePath: Sendable {} +#endif diff --git a/Sources/System/FilePath/FilePathComponentView.swift b/Sources/System/FilePath/FilePathComponentView.swift index 959ff64c..b8324fd6 100644 --- a/Sources/System/FilePath/FilePathComponentView.swift +++ b/Sources/System/FilePath/FilePathComponentView.swift @@ -238,3 +238,8 @@ extension FilePath.ComponentView { #endif // DEBUG } } + +#if compiler(>=5.5) && canImport(_Concurrency) +extension FilePath.ComponentView: Sendable {} +extension FilePath.ComponentView.Index: Sendable {} +#endif diff --git a/Sources/System/FilePath/FilePathComponents.swift b/Sources/System/FilePath/FilePathComponents.swift index d6b043f3..19eca940 100644 --- a/Sources/System/FilePath/FilePathComponents.swift +++ b/Sources/System/FilePath/FilePathComponents.swift @@ -276,3 +276,9 @@ extension FilePath.Root { #endif } } + +#if compiler(>=5.5) && canImport(_Concurrency) +extension FilePath.Root: Sendable {} +extension FilePath.Component: Sendable {} +extension FilePath.Component.Kind: Sendable {} +#endif diff --git a/Sources/System/FilePermissions.swift b/Sources/System/FilePermissions.swift index 0780ee07..1465e3f7 100644 --- a/Sources/System/FilePermissions.swift +++ b/Sources/System/FilePermissions.swift @@ -175,3 +175,7 @@ extension FilePermissions /// A textual representation of the file permissions, suitable for debugging. public var debugDescription: String { self.description } } + +#if compiler(>=5.5) && canImport(_Concurrency) +extension FilePermissions: Sendable {} +#endif diff --git a/Sources/System/SystemString.swift b/Sources/System/SystemString.swift index a918a929..14651d52 100644 --- a/Sources/System/SystemString.swift +++ b/Sources/System/SystemString.swift @@ -288,6 +288,11 @@ extension SystemString { } } +#if compiler(>=5.5) && canImport(_Concurrency) +extension SystemChar: Sendable {} +extension SystemString: Sendable {} +#endif + // TODO: SystemString should use a COW-interchangable storage form rather // than array, so you could "borrow" the storage from a non-bridged String // or Data or whatever From 304ccc3abbd4bdfcd42b84cf4aace5e1a1dbf51d Mon Sep 17 00:00:00 2001 From: Daniel Loffgren Date: Wed, 7 Dec 2022 17:19:36 -0800 Subject: [PATCH 094/427] Add Mach.Port --- Sources/System/CMakeLists.txt | 1 + Sources/System/MachPort.swift | 318 ++++++++++++++++++++++++++ Tests/SystemTests/MachPortTests.swift | 140 ++++++++++++ 3 files changed, 459 insertions(+) create mode 100644 Sources/System/MachPort.swift create mode 100644 Tests/SystemTests/MachPortTests.swift diff --git a/Sources/System/CMakeLists.txt b/Sources/System/CMakeLists.txt index 43958d31..4d46dc48 100644 --- a/Sources/System/CMakeLists.txt +++ b/Sources/System/CMakeLists.txt @@ -13,6 +13,7 @@ add_library(SystemPackage FileHelpers.swift FileOperations.swift FilePermissions.swift + MachPort.swift PlatformString.swift SystemString.swift Util.swift diff --git a/Sources/System/MachPort.swift b/Sources/System/MachPort.swift new file mode 100644 index 00000000..dcd9831f --- /dev/null +++ b/Sources/System/MachPort.swift @@ -0,0 +1,318 @@ +/* + This source file is part of the Swift System open source project + + Copyright (c) 2022 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 +*/ + +#if $MoveOnly && (os(macOS) || os(iOS) || os(watchOS) || os(tvOS)) + +import Darwin.Mach + +protocol MachPortRight {} + +enum Mach { + @_moveOnly + struct Port { + internal var name:mach_port_name_t + internal var context:mach_port_context_t + + /// Transfer ownership of an existing unmanaged Mach port right into a + /// Mach.Port by name. + /// + /// This initializer aborts if name is MACH_PORT_NULL. + /// + /// If the type of the right does not match the type T of Mach.Port + /// being constructed, behavior is undefined. + /// + /// The underlying port right will be automatically deallocated at the + /// end of the Mach.Port instance's lifetime. + /// + /// This initializer makes a syscall to guard the right. + init(name:mach_port_name_t) { + assert(name != mach_port_name_t(MACH_PORT_NULL)) + self.name = name + + if (RightType.self == ReceiveRight.self) { + let secret = mach_port_context_t(arc4random()) + let kr = mach_port_guard(mach_task_self_, name, secret, 0) + assert(kr == KERN_SUCCESS) + self.context = secret + } + else { + self.context = 0 + } + } + + /// Borrow access to the port name in a block that can perform + /// non-consuming operations. + /// + /// Take care when using this function; many operations consume rights, + /// and send-once rights are easily consumed. + /// + /// If the right is consumed, behavior is undefined. + /// + /// The body block may optionally return something, which will then be + /// returned to the caller of withBorrowedName. + func withBorrowedName(body:(mach_port_name_t) -> ReturnType) -> ReturnType { + return body(name) + } + + deinit { + if name != 0xFFFFFFFF /* MACH_PORT_DEAD */ { + if RightType.self == ReceiveRight.self { + // recv rights must be mod ref'ed instead of deallocated + let kr = mach_port_unguard(mach_task_self_, name, context) + assert(kr == KERN_SUCCESS) + + let kr2 = mach_port_mod_refs(mach_task_self_, name, MACH_PORT_RIGHT_RECEIVE, -1) + assert(kr2 == KERN_SUCCESS) + } else { + mach_port_deallocate(mach_task_self_, name) + } + } + } + } + + /// Possible errors that can be thrown by Mach.Port operations. + enum PortRightError : Error { + /// Returned when an operation cannot be completed, because the Mach + /// port right has become a dead name. This is caused by deallocation of the + /// receive right on the other end. + case deadName + } + + /// The MachPortRight type used to manage a receive right. + struct ReceiveRight : MachPortRight {} + + /// The MachPortRight type used to manage a send right. + struct SendRight : MachPortRight {} + + /// The MachPortRight type used to manage a send-once right. + /// + /// Send-once rights are the most restrictive type of Mach port rights. + /// They cannot create other rights, and are consumed upon use. + /// + /// Upon destruction a send-once notification will be sent to the + /// receiving end. + struct SendOnceRight : MachPortRight {} + + /// Create a connected pair of rights, one receive, and one send. + /// + /// This function will abort if the rights could not be created. + /// Callers may assert that valid rights are always returned. + static func allocatePortRightPair() -> (Mach.Port, Mach.Port) { + var name = mach_port_name_t(MACH_PORT_NULL) + let secret = mach_port_context_t(arc4random()) + withUnsafeMutablePointer(to: &name) { name in + var options = mach_port_options_t() + options.flags = UInt32(MPO_INSERT_SEND_RIGHT); + withUnsafeMutablePointer(to: &options) { options in + let kr = mach_port_construct(mach_task_self_, options, secret, name) + assert(kr == KERN_SUCCESS) + } + } + return (Mach.Port(name: name, context: secret), Mach.Port(name: name)) + } +} + +extension Mach.Port where RightType == Mach.ReceiveRight { + /// Transfer ownership of an existing, unmanaged, but already guarded, + /// Mach port right into a Mach.Port by name. + /// + /// This initializer aborts if name is MACH_PORT_NULL. + /// + /// If the type of the right does not match the type T of Mach.Port + /// being constructed, the behavior is undefined. + /// + /// The underlying port right will be automatically deallocated when + /// the Mach.Port object is destroyed. + init(name:mach_port_name_t, context:mach_port_context_t) { + self.name = name + self.context = context + } + + /// Allocate a new Mach port with a receive right, creating a + /// Mach.Port to manage it. + /// + /// This initializer will abort if the right could not be created. + /// Callers may assert that a valid right is always returned. + init() { + var storage:mach_port_name_t = 0 + withUnsafeMutablePointer(to:&storage) { storage in + let kr = mach_port_allocate(mach_task_self_, MACH_PORT_RIGHT_RECEIVE, storage) + assert(kr == KERN_SUCCESS) + } + + // name-only init will guard ReceiveRights + self.init(name:storage) + } + + + /// Transfer ownership of the underlying port right to the caller. + /// + /// Returns a tuple containing the Mach port name representing the right, + /// and the context value used to guard the right. + /// + /// This operation liberates the right from management by the Mach.Port, + /// and the underlying right will no longer be automatically deallocated. + /// + /// After this function completes, the Mach.Port is destroyed and no longer + /// usable. + __consuming func relinquish() -> (mach_port_name_t, mach_port_context_t) { + return (name, context) + } + + /// Remove guard and transfer ownership of the underlying port right to + /// the caller. + /// + /// Returns the Mach port name representing the right. + /// + /// This operation liberates the right from management by the Mach.Port, + /// and the underlying right will no longer be automatically deallocated. + /// + /// After this function completes, the Mach.Port is destroyed and no longer + /// usable. + /// + /// This function makes a syscall to remove the guard from + /// Mach.ReceiveRights. Use relinquish() to avoid the syscall and extract + /// the context value along with the port name. + __consuming func unguardAndRelinquish() -> mach_port_name_t { + let kr = mach_port_unguard(mach_task_self_, name, context); + assert(kr == KERN_SUCCESS) + return name + } + + /// Borrow access to the port name in a block that can perform + /// non-consuming operations. + /// + /// Take care when using this function; many operations consume rights. + /// + /// If the right is consumed, behavior is undefined. + /// + /// The body block may optionally return something, which will then be + /// returned to the caller of withBorrowedName. + func withBorrowedName(body:(mach_port_name_t, mach_port_context_t) -> ReturnType) -> ReturnType { + body(name, context) + } + + /// Create a send-once right for a given receive right. + /// + /// This does not affect the makeSendCount of the receive right. + /// + /// This function will abort if the right could not be created. + /// Callers may assert that a valid right is always returned. + func makeSendOnceRight() -> Mach.Port { + // send once rights do not coalesce + var kr:kern_return_t = KERN_FAILURE + var newRight:mach_port_name_t = mach_port_name_t(MACH_PORT_NULL) + var newRightType:mach_port_type_t = MACH_PORT_TYPE_NONE + + withUnsafeMutablePointer(to: &newRight) { newRight in + withUnsafeMutablePointer(to: &newRightType) { newRightType in + kr = mach_port_extract_right(mach_task_self_, name, mach_msg_type_name_t(MACH_MSG_TYPE_MAKE_SEND_ONCE), newRight, newRightType) + + } + } + + // The value of newRight is validated by the Mach.Port initializer + assert(kr == KERN_SUCCESS) + assert(newRightType == MACH_MSG_TYPE_MOVE_SEND_ONCE) + + return Mach.Port(name:newRight) + } + + /// Create a send right for a given receive right. + /// + /// This increments the makeSendCount of the receive right. + /// + /// This function will abort if the right could not be created. + /// Callers may assert that a valid right is always returned. + func makeSendRight() -> Mach.Port { + let how = MACH_MSG_TYPE_MAKE_SEND + + // send and recv rights are coalesced + let kr = mach_port_insert_right(mach_task_self_, name, name, mach_msg_type_name_t(how)) + assert(kr == KERN_SUCCESS) + + return Mach.Port(name:name) + } + + /// Access the make-send count. + /// + /// Each get/set of this property makes a syscall. + var makeSendCount : mach_port_mscount_t { + get { + var status:mach_port_status = mach_port_status() + var size:mach_msg_type_number_t = mach_msg_type_number_t(MemoryLayout.size) + withUnsafeMutablePointer(to: &size) { size in + withUnsafeMutablePointer(to: &status) { status in + let info = UnsafeMutableRawPointer(status).bindMemory(to: integer_t.self, capacity: 1) + let kr = mach_port_get_attributes(mach_task_self_, name, MACH_PORT_RECEIVE_STATUS, info, size) + assert(kr == KERN_SUCCESS); + } + } + return status.mps_mscount + } + + set { + let kr = mach_port_set_mscount(mach_task_self_, name, newValue) + assert(kr == KERN_SUCCESS) + } + } +} + +extension Mach.Port where RightType == Mach.SendRight { + /// Transfer ownership of the underlying port right to the caller. + /// + /// Returns the Mach port name representing the right. + /// + /// This operation liberates the right from management by the Mach.Port, + /// and the underlying right will no longer be automatically deallocated. + /// + /// After this function completes, the Mach.Port is destroyed and no longer + /// usable. + __consuming func relinquish() -> mach_port_name_t { + return name + } + + /// Create another send right from a given send right. + /// + /// This does not affect the makeSendCount of the receive right. + /// + /// If the send right being copied has become a dead name, meaning the + /// receiving side has been deallocated, then copySendRight() will throw + /// a Mach.PortRightError.deadName error. + func copySendRight() throws -> Mach.Port { + let how = MACH_MSG_TYPE_COPY_SEND + + // send rights are coalesced + let kr = mach_port_insert_right(mach_task_self_, name, name, mach_msg_type_name_t(how)) + if kr == KERN_INVALID_CAPABILITY { + throw Mach.PortRightError.deadName + } + assert(kr == KERN_SUCCESS) + + return Mach.Port(name:name) + } +} + + +extension Mach.Port where RightType == Mach.SendOnceRight { + /// Transfer ownership of the underlying port right to the caller. + /// + /// Returns the Mach port name representing the right. + /// + /// This operation liberates the right from management by the Mach.Port, + /// and the underlying right will no longer be automatically deallocated. + /// + /// After this function completes, the Mach.Port is destroyed and no longer + /// usable. + __consuming func relinquish() -> mach_port_name_t { + return name + } +} + +#endif diff --git a/Tests/SystemTests/MachPortTests.swift b/Tests/SystemTests/MachPortTests.swift new file mode 100644 index 00000000..0def85ac --- /dev/null +++ b/Tests/SystemTests/MachPortTests.swift @@ -0,0 +1,140 @@ +/* + This source file is part of the Swift System open source project + + Copyright (c) 2022 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 +*/ + +#if $MoveOnly && (os(macOS) || os(iOS) || os(watchOS) || os(tvOS)) + +import XCTest +import Darwin.Mach + +final class MachPortTests: XCTestCase { + func refCountForMachPortName(name:mach_port_name_t, kind:mach_port_right_t) -> mach_port_urefs_t { + var refCount:mach_port_urefs_t = 0 + withUnsafeMutablePointer(to: &refCount) { refCount in + let kr = mach_port_get_refs(mach_task_self_, name, kind, refCount) + assert(kr == KERN_SUCCESS) + } + return refCount + } + + func scopedReceiveRight(name:mach_port_name_t) -> mach_port_urefs_t { + _ = Mach.Port(name:name) // this should automatically deallocate when going out of scope + return refCountForMachPortName(name:name, kind:MACH_PORT_RIGHT_RECEIVE) + } + + func testRecieveRightDeallocation() throws { + var name:mach_port_name_t = 0 // Never read + withUnsafeMutablePointer(to:&name) { name in + let kr = mach_port_allocate(mach_task_self_, MACH_PORT_RIGHT_RECEIVE, name) + assert(kr == KERN_SUCCESS) + } + + XCTAssert(name != 0xFFFFFFFF) + + let one = scopedReceiveRight(name:name) + let zero = refCountForMachPortName(name:name, kind: MACH_PORT_RIGHT_RECEIVE) + + XCTAssert(one == 1); + XCTAssert(zero == 0); + } + + func consumeSendRightAutomatically(name:mach_port_name_t) -> mach_port_urefs_t { + let send = Mach.Port(name:name) // this should automatically deallocate when going out of scope + return send.withBorrowedName { name in + // Get the ref count before automatic deallocation happens + return refCountForMachPortName(name:name, kind:MACH_PORT_RIGHT_SEND) + } + } + + func testSendRightDeallocation() throws { + let recv = Mach.Port() + recv.withBorrowedName { name in + let kr = mach_port_insert_right(mach_task_self_, name, name, mach_msg_type_name_t(MACH_MSG_TYPE_MAKE_SEND)) + XCTAssert(kr == KERN_SUCCESS) + let one = consumeSendRightAutomatically(name:name) + XCTAssert(one == 1); + let zero = refCountForMachPortName(name:name, kind:MACH_PORT_RIGHT_SEND) + XCTAssert(zero == 0); + } + } + + func testSendRightRelinquishment() throws { + let recv = Mach.Port() + + let name = ({ + let send = recv.makeSendRight() + let one = send.withBorrowedName { name in + return self.refCountForMachPortName(name:name, kind:MACH_PORT_RIGHT_SEND) + } + XCTAssert(one == 1) + + return send.relinquish() + })() + + let stillOne = refCountForMachPortName(name:name, kind:MACH_PORT_RIGHT_SEND) + XCTAssert(stillOne == 1) + } + + func testMakeSendCountSettable() throws { + var recv = Mach.Port() + XCTAssert(recv.makeSendCount == 0) + recv.makeSendCount = 7 + XCTAssert(recv.makeSendCount == 7) + } + + func makeSendRight() throws -> Mach.Port { + let recv = Mach.Port() + let zero = recv.makeSendCount + XCTAssert(zero == 0) + let send = recv.makeSendRight() + let one = recv.makeSendCount + XCTAssert(one == 1) + return send + } + + func testMakeSendCountIncrement() throws { + _ = try makeSendRight() + } + + func testMakeSendOnceDoesntIncrementMakeSendCount() throws { + let recv = Mach.Port() + let zero = recv.makeSendCount + XCTAssert(zero == 0) + _ = recv.makeSendOnceRight() + let same = recv.makeSendCount + XCTAssert(same == zero) + } + + func testMakePair() throws { + let (recv, send) = Mach.allocatePortRightPair() + XCTAssert(recv.makeSendCount == 1) + recv.withBorrowedName { rName in + send.withBorrowedName { sName in + XCTAssert(rName != 0xFFFFFFFF) + XCTAssert(rName != MACH_PORT_NULL) + // send and recv port names coalesce + XCTAssert(rName == sName) + } + } + } + + func testCopySend() throws { + let recv = Mach.Port() + let zero = recv.makeSendCount + XCTAssert(zero == 0) + let send = recv.makeSendRight() + let one = recv.makeSendCount + XCTAssert(one == 1) + _ = try send.copySendRight() + let same = recv.makeSendCount + XCTAssert(same == one) + + } +} + +#endif From c2d940ecf39e011784ec462ab39755701e3b3449 Mon Sep 17 00:00:00 2001 From: loffgren <117697138+loffgren@users.noreply.github.com> Date: Mon, 19 Dec 2022 16:41:33 -0800 Subject: [PATCH 095/427] Remove parens around RightType condition in initializer Co-authored-by: Matt Wright --- Sources/System/MachPort.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/System/MachPort.swift b/Sources/System/MachPort.swift index dcd9831f..13013035 100644 --- a/Sources/System/MachPort.swift +++ b/Sources/System/MachPort.swift @@ -35,7 +35,7 @@ enum Mach { assert(name != mach_port_name_t(MACH_PORT_NULL)) self.name = name - if (RightType.self == ReceiveRight.self) { + if RightType.self == ReceiveRight.self { let secret = mach_port_context_t(arc4random()) let kr = mach_port_guard(mach_task_self_, name, secret, 0) assert(kr == KERN_SUCCESS) From bdc776bd3e1752358876d70b739a6c2791773ca2 Mon Sep 17 00:00:00 2001 From: Daniel Loffgren Date: Mon, 19 Dec 2022 17:14:53 -0800 Subject: [PATCH 096/427] Correct parameter whitespace --- Sources/System/MachPort.swift | 36 +++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/Sources/System/MachPort.swift b/Sources/System/MachPort.swift index 13013035..2fe21683 100644 --- a/Sources/System/MachPort.swift +++ b/Sources/System/MachPort.swift @@ -15,9 +15,9 @@ protocol MachPortRight {} enum Mach { @_moveOnly - struct Port { - internal var name:mach_port_name_t - internal var context:mach_port_context_t + struct Port { + internal var name: mach_port_name_t + internal var context: mach_port_context_t /// Transfer ownership of an existing unmanaged Mach port right into a /// Mach.Port by name. @@ -31,7 +31,7 @@ enum Mach { /// end of the Mach.Port instance's lifetime. /// /// This initializer makes a syscall to guard the right. - init(name:mach_port_name_t) { + init(name: mach_port_name_t) { assert(name != mach_port_name_t(MACH_PORT_NULL)) self.name = name @@ -56,7 +56,7 @@ enum Mach { /// /// The body block may optionally return something, which will then be /// returned to the caller of withBorrowedName. - func withBorrowedName(body:(mach_port_name_t) -> ReturnType) -> ReturnType { + func withBorrowedName(body: (mach_port_name_t) -> ReturnType) -> ReturnType { return body(name) } @@ -129,7 +129,7 @@ extension Mach.Port where RightType == Mach.ReceiveRight { /// /// The underlying port right will be automatically deallocated when /// the Mach.Port object is destroyed. - init(name:mach_port_name_t, context:mach_port_context_t) { + init(name: mach_port_name_t, context: mach_port_context_t) { self.name = name self.context = context } @@ -140,14 +140,14 @@ extension Mach.Port where RightType == Mach.ReceiveRight { /// This initializer will abort if the right could not be created. /// Callers may assert that a valid right is always returned. init() { - var storage:mach_port_name_t = 0 - withUnsafeMutablePointer(to:&storage) { storage in + var storage: mach_port_name_t = 0 + withUnsafeMutablePointer(to: &storage) { storage in let kr = mach_port_allocate(mach_task_self_, MACH_PORT_RIGHT_RECEIVE, storage) assert(kr == KERN_SUCCESS) } // name-only init will guard ReceiveRights - self.init(name:storage) + self.init(name: storage) } @@ -194,7 +194,7 @@ extension Mach.Port where RightType == Mach.ReceiveRight { /// /// The body block may optionally return something, which will then be /// returned to the caller of withBorrowedName. - func withBorrowedName(body:(mach_port_name_t, mach_port_context_t) -> ReturnType) -> ReturnType { + func withBorrowedName(body: (mach_port_name_t, mach_port_context_t) -> ReturnType) -> ReturnType { body(name, context) } @@ -206,9 +206,9 @@ extension Mach.Port where RightType == Mach.ReceiveRight { /// Callers may assert that a valid right is always returned. func makeSendOnceRight() -> Mach.Port { // send once rights do not coalesce - var kr:kern_return_t = KERN_FAILURE - var newRight:mach_port_name_t = mach_port_name_t(MACH_PORT_NULL) - var newRightType:mach_port_type_t = MACH_PORT_TYPE_NONE + var kr: kern_return_t = KERN_FAILURE + var newRight: mach_port_name_t = mach_port_name_t(MACH_PORT_NULL) + var newRightType: mach_port_type_t = MACH_PORT_TYPE_NONE withUnsafeMutablePointer(to: &newRight) { newRight in withUnsafeMutablePointer(to: &newRightType) { newRightType in @@ -221,7 +221,7 @@ extension Mach.Port where RightType == Mach.ReceiveRight { assert(kr == KERN_SUCCESS) assert(newRightType == MACH_MSG_TYPE_MOVE_SEND_ONCE) - return Mach.Port(name:newRight) + return Mach.Port(name: newRight) } /// Create a send right for a given receive right. @@ -237,7 +237,7 @@ extension Mach.Port where RightType == Mach.ReceiveRight { let kr = mach_port_insert_right(mach_task_self_, name, name, mach_msg_type_name_t(how)) assert(kr == KERN_SUCCESS) - return Mach.Port(name:name) + return Mach.Port(name: name) } /// Access the make-send count. @@ -245,8 +245,8 @@ extension Mach.Port where RightType == Mach.ReceiveRight { /// Each get/set of this property makes a syscall. var makeSendCount : mach_port_mscount_t { get { - var status:mach_port_status = mach_port_status() - var size:mach_msg_type_number_t = mach_msg_type_number_t(MemoryLayout.size) + var status: mach_port_status = mach_port_status() + var size: mach_msg_type_number_t = mach_msg_type_number_t(MemoryLayout.size) withUnsafeMutablePointer(to: &size) { size in withUnsafeMutablePointer(to: &status) { status in let info = UnsafeMutableRawPointer(status).bindMemory(to: integer_t.self, capacity: 1) @@ -295,7 +295,7 @@ extension Mach.Port where RightType == Mach.SendRight { } assert(kr == KERN_SUCCESS) - return Mach.Port(name:name) + return Mach.Port(name: name) } } From 4536c4776325a4f41aa2a8fa3724cccffd432a77 Mon Sep 17 00:00:00 2001 From: Daniel Loffgren Date: Tue, 20 Dec 2022 12:51:07 -0800 Subject: [PATCH 097/427] Use precondition instead of assert, adding a machPrecondition for the common KERN_SUCCESS check --- Sources/System/MachPort.swift | 61 ++++++++++++++++++----------------- 1 file changed, 32 insertions(+), 29 deletions(-) diff --git a/Sources/System/MachPort.swift b/Sources/System/MachPort.swift index 2fe21683..bc896635 100644 --- a/Sources/System/MachPort.swift +++ b/Sources/System/MachPort.swift @@ -13,6 +13,16 @@ import Darwin.Mach protocol MachPortRight {} +private func machPrecondition( + file: StaticString = #file, + line: UInt = #line, + _ body: @autoclosure () -> kern_return_t +) { + let kr = body() + let expected = KERN_SUCCESS + precondition(kr == expected, file: file, line: line) +} + enum Mach { @_moveOnly struct Port { @@ -32,13 +42,12 @@ enum Mach { /// /// This initializer makes a syscall to guard the right. init(name: mach_port_name_t) { - assert(name != mach_port_name_t(MACH_PORT_NULL)) + precondition(name != mach_port_name_t(MACH_PORT_NULL)) self.name = name if RightType.self == ReceiveRight.self { let secret = mach_port_context_t(arc4random()) - let kr = mach_port_guard(mach_task_self_, name, secret, 0) - assert(kr == KERN_SUCCESS) + machPrecondition(mach_port_guard(mach_task_self_, name, secret, 0)) self.context = secret } else { @@ -64,13 +73,10 @@ enum Mach { if name != 0xFFFFFFFF /* MACH_PORT_DEAD */ { if RightType.self == ReceiveRight.self { // recv rights must be mod ref'ed instead of deallocated - let kr = mach_port_unguard(mach_task_self_, name, context) - assert(kr == KERN_SUCCESS) - - let kr2 = mach_port_mod_refs(mach_task_self_, name, MACH_PORT_RIGHT_RECEIVE, -1) - assert(kr2 == KERN_SUCCESS) + machPrecondition(mach_port_unguard(mach_task_self_, name, context)) + machPrecondition(mach_port_mod_refs(mach_task_self_, name, MACH_PORT_RIGHT_RECEIVE, -1)) } else { - mach_port_deallocate(mach_task_self_, name) + machPrecondition(mach_port_deallocate(mach_task_self_, name)) } } } @@ -110,8 +116,7 @@ enum Mach { var options = mach_port_options_t() options.flags = UInt32(MPO_INSERT_SEND_RIGHT); withUnsafeMutablePointer(to: &options) { options in - let kr = mach_port_construct(mach_task_self_, options, secret, name) - assert(kr == KERN_SUCCESS) + machPrecondition(mach_port_construct(mach_task_self_, options, secret, name)) } } return (Mach.Port(name: name, context: secret), Mach.Port(name: name)) @@ -142,8 +147,7 @@ extension Mach.Port where RightType == Mach.ReceiveRight { init() { var storage: mach_port_name_t = 0 withUnsafeMutablePointer(to: &storage) { storage in - let kr = mach_port_allocate(mach_task_self_, MACH_PORT_RIGHT_RECEIVE, storage) - assert(kr == KERN_SUCCESS) + machPrecondition(mach_port_allocate(mach_task_self_, MACH_PORT_RIGHT_RECEIVE, storage)) } // name-only init will guard ReceiveRights @@ -180,8 +184,7 @@ extension Mach.Port where RightType == Mach.ReceiveRight { /// Mach.ReceiveRights. Use relinquish() to avoid the syscall and extract /// the context value along with the port name. __consuming func unguardAndRelinquish() -> mach_port_name_t { - let kr = mach_port_unguard(mach_task_self_, name, context); - assert(kr == KERN_SUCCESS) + machPrecondition(mach_port_unguard(mach_task_self_, name, context)) return name } @@ -206,20 +209,23 @@ extension Mach.Port where RightType == Mach.ReceiveRight { /// Callers may assert that a valid right is always returned. func makeSendOnceRight() -> Mach.Port { // send once rights do not coalesce - var kr: kern_return_t = KERN_FAILURE var newRight: mach_port_name_t = mach_port_name_t(MACH_PORT_NULL) var newRightType: mach_port_type_t = MACH_PORT_TYPE_NONE withUnsafeMutablePointer(to: &newRight) { newRight in withUnsafeMutablePointer(to: &newRightType) { newRightType in - kr = mach_port_extract_right(mach_task_self_, name, mach_msg_type_name_t(MACH_MSG_TYPE_MAKE_SEND_ONCE), newRight, newRightType) - + machPrecondition( + mach_port_extract_right(mach_task_self_, + name, + mach_msg_type_name_t(MACH_MSG_TYPE_MAKE_SEND_ONCE), + newRight, + newRightType) + ) } } // The value of newRight is validated by the Mach.Port initializer - assert(kr == KERN_SUCCESS) - assert(newRightType == MACH_MSG_TYPE_MOVE_SEND_ONCE) + precondition(newRightType == MACH_MSG_TYPE_MOVE_SEND_ONCE) return Mach.Port(name: newRight) } @@ -233,9 +239,8 @@ extension Mach.Port where RightType == Mach.ReceiveRight { func makeSendRight() -> Mach.Port { let how = MACH_MSG_TYPE_MAKE_SEND - // send and recv rights are coalesced - let kr = mach_port_insert_right(mach_task_self_, name, name, mach_msg_type_name_t(how)) - assert(kr == KERN_SUCCESS) + // name is the same because send and recv rights are coalesced + machPrecondition(mach_port_insert_right(mach_task_self_, name, name, mach_msg_type_name_t(how))) return Mach.Port(name: name) } @@ -250,16 +255,14 @@ extension Mach.Port where RightType == Mach.ReceiveRight { withUnsafeMutablePointer(to: &size) { size in withUnsafeMutablePointer(to: &status) { status in let info = UnsafeMutableRawPointer(status).bindMemory(to: integer_t.self, capacity: 1) - let kr = mach_port_get_attributes(mach_task_self_, name, MACH_PORT_RECEIVE_STATUS, info, size) - assert(kr == KERN_SUCCESS); + machPrecondition(mach_port_get_attributes(mach_task_self_, name, MACH_PORT_RECEIVE_STATUS, info, size)) } } return status.mps_mscount } set { - let kr = mach_port_set_mscount(mach_task_self_, name, newValue) - assert(kr == KERN_SUCCESS) + machPrecondition(mach_port_set_mscount(mach_task_self_, name, newValue)) } } } @@ -288,12 +291,12 @@ extension Mach.Port where RightType == Mach.SendRight { func copySendRight() throws -> Mach.Port { let how = MACH_MSG_TYPE_COPY_SEND - // send rights are coalesced + // name is the same because send rights are coalesced let kr = mach_port_insert_right(mach_task_self_, name, name, mach_msg_type_name_t(how)) if kr == KERN_INVALID_CAPABILITY { throw Mach.PortRightError.deadName } - assert(kr == KERN_SUCCESS) + machPrecondition(kr) return Mach.Port(name: name) } From bd036acda642174bc322eee9471d5f7a4d27ece7 Mon Sep 17 00:00:00 2001 From: Daniel Loffgren Date: Tue, 20 Dec 2022 12:56:45 -0800 Subject: [PATCH 098/427] Storage should be initialized to MACH_PORT_NULL, rather than 0 --- Sources/System/MachPort.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/System/MachPort.swift b/Sources/System/MachPort.swift index bc896635..be02c987 100644 --- a/Sources/System/MachPort.swift +++ b/Sources/System/MachPort.swift @@ -145,7 +145,7 @@ extension Mach.Port where RightType == Mach.ReceiveRight { /// This initializer will abort if the right could not be created. /// Callers may assert that a valid right is always returned. init() { - var storage: mach_port_name_t = 0 + var storage: mach_port_name_t = mach_port_name_t(MACH_PORT_NULL) withUnsafeMutablePointer(to: &storage) { storage in machPrecondition(mach_port_allocate(mach_task_self_, MACH_PORT_RIGHT_RECEIVE, storage)) } From 213165ab72af039d38e3654432a864e456e54e8f Mon Sep 17 00:00:00 2001 From: Daniel Loffgren Date: Tue, 20 Dec 2022 13:02:18 -0800 Subject: [PATCH 099/427] size should be the number of natural_t's --- Sources/System/MachPort.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/System/MachPort.swift b/Sources/System/MachPort.swift index be02c987..cf5ca1b9 100644 --- a/Sources/System/MachPort.swift +++ b/Sources/System/MachPort.swift @@ -251,7 +251,7 @@ extension Mach.Port where RightType == Mach.ReceiveRight { var makeSendCount : mach_port_mscount_t { get { var status: mach_port_status = mach_port_status() - var size: mach_msg_type_number_t = mach_msg_type_number_t(MemoryLayout.size) + var size: mach_msg_type_number_t = mach_msg_type_number_t(MemoryLayout.size / MemoryLayout.size) withUnsafeMutablePointer(to: &size) { size in withUnsafeMutablePointer(to: &status) { status in let info = UnsafeMutableRawPointer(status).bindMemory(to: integer_t.self, capacity: 1) From 296374ecef32aa2fd27cd6fd57a4c62aaf733d07 Mon Sep 17 00:00:00 2001 From: Daniel Loffgren Date: Wed, 21 Dec 2022 11:46:06 -0800 Subject: [PATCH 100/427] Label tuple contents for allocatePortPair() and relinquish() --- Sources/System/MachPort.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Sources/System/MachPort.swift b/Sources/System/MachPort.swift index cf5ca1b9..a2cbe501 100644 --- a/Sources/System/MachPort.swift +++ b/Sources/System/MachPort.swift @@ -109,7 +109,7 @@ enum Mach { /// /// This function will abort if the rights could not be created. /// Callers may assert that valid rights are always returned. - static func allocatePortRightPair() -> (Mach.Port, Mach.Port) { + static func allocatePortRightPair() -> (receive: Mach.Port, send: Mach.Port) { var name = mach_port_name_t(MACH_PORT_NULL) let secret = mach_port_context_t(arc4random()) withUnsafeMutablePointer(to: &name) { name in @@ -165,8 +165,8 @@ extension Mach.Port where RightType == Mach.ReceiveRight { /// /// After this function completes, the Mach.Port is destroyed and no longer /// usable. - __consuming func relinquish() -> (mach_port_name_t, mach_port_context_t) { - return (name, context) + __consuming func relinquish() -> (name: mach_port_name_t, context: mach_port_context_t) { + return (name: name, context: context) } /// Remove guard and transfer ownership of the underlying port right to From 551d7b897f7dd2d76429ee8aad4a006ecd1e069d Mon Sep 17 00:00:00 2001 From: Daniel Loffgren Date: Wed, 21 Dec 2022 12:49:30 -0800 Subject: [PATCH 101/427] Use mach_port_destruct() to deinit receive rights eliminating the second syscall, and use mach_port_mod_refs() for all other right types to get a free type check at deinit time mach_port_mod_refs() requires the type of right being modified. So, MACH_PORT_DEAD must be identified as a separate type. As this is something that shouldn't be possible for receive rights, preconditions have also been added to protect against this particular error. --- Sources/System/MachPort.swift | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/Sources/System/MachPort.swift b/Sources/System/MachPort.swift index a2cbe501..da7122c3 100644 --- a/Sources/System/MachPort.swift +++ b/Sources/System/MachPort.swift @@ -32,7 +32,8 @@ enum Mach { /// Transfer ownership of an existing unmanaged Mach port right into a /// Mach.Port by name. /// - /// This initializer aborts if name is MACH_PORT_NULL. + /// This initializer aborts if name is MACH_PORT_NULL, or if name is + /// MACH_PORT_DEAD and the type T of Mach.Port is Mach.ReceiveRight. /// /// If the type of the right does not match the type T of Mach.Port /// being constructed, behavior is undefined. @@ -46,6 +47,8 @@ enum Mach { self.name = name if RightType.self == ReceiveRight.self { + precondition(name != 0xFFFFFFFF /* MACH_PORT_DEAD */, "Receive rights cannot be dead names") + let secret = mach_port_context_t(arc4random()) machPrecondition(mach_port_guard(mach_task_self_, name, secret, 0)) self.context = secret @@ -70,13 +73,16 @@ enum Mach { } deinit { - if name != 0xFFFFFFFF /* MACH_PORT_DEAD */ { + if name == 0xFFFFFFFF /* MACH_PORT_DEAD */ { + precondition(RightType.self != ReceiveRight.self, "Receive rights cannot be dead names") + machPrecondition(mach_port_mod_refs(mach_task_self_, name, MACH_PORT_RIGHT_DEAD_NAME, -1)) + } else { if RightType.self == ReceiveRight.self { - // recv rights must be mod ref'ed instead of deallocated - machPrecondition(mach_port_unguard(mach_task_self_, name, context)) - machPrecondition(mach_port_mod_refs(mach_task_self_, name, MACH_PORT_RIGHT_RECEIVE, -1)) - } else { - machPrecondition(mach_port_deallocate(mach_task_self_, name)) + machPrecondition(mach_port_destruct(mach_task_self_, name, -1, context)) + } else if RightType.self == SendRight.self { + machPrecondition(mach_port_mod_refs(mach_task_self_, name, MACH_PORT_RIGHT_SEND, -1)) + } else if RightType.self == SendOnceRight.self { + machPrecondition(mach_port_mod_refs(mach_task_self_, name, MACH_PORT_RIGHT_SEND_ONCE, -1)) } } } From 6dd57fe3a9f9238394683b5e25cb7632d645bf1c Mon Sep 17 00:00:00 2001 From: Daniel Loffgren Date: Wed, 21 Dec 2022 12:57:25 -0800 Subject: [PATCH 102/427] Add a friendly precondition message for the MACH_PORT_NULL check --- Sources/System/MachPort.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/System/MachPort.swift b/Sources/System/MachPort.swift index da7122c3..4ba16e96 100644 --- a/Sources/System/MachPort.swift +++ b/Sources/System/MachPort.swift @@ -43,7 +43,7 @@ enum Mach { /// /// This initializer makes a syscall to guard the right. init(name: mach_port_name_t) { - precondition(name != mach_port_name_t(MACH_PORT_NULL)) + precondition(name != mach_port_name_t(MACH_PORT_NULL), "Mach.Port cannot be initialized with MACH_PORT_NULL") self.name = name if RightType.self == ReceiveRight.self { From 7f4560f02245027ec729fbabd1d7033baad206a8 Mon Sep 17 00:00:00 2001 From: Daniel Loffgren Date: Wed, 21 Dec 2022 13:12:13 -0800 Subject: [PATCH 103/427] Remove unnecessary RightTypes for Mach.Ports being constructed inline with a return --- Sources/System/MachPort.swift | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Sources/System/MachPort.swift b/Sources/System/MachPort.swift index 4ba16e96..090067bd 100644 --- a/Sources/System/MachPort.swift +++ b/Sources/System/MachPort.swift @@ -125,7 +125,7 @@ enum Mach { machPrecondition(mach_port_construct(mach_task_self_, options, secret, name)) } } - return (Mach.Port(name: name, context: secret), Mach.Port(name: name)) + return (Mach.Port(name: name, context: secret), Mach.Port(name: name)) } } @@ -233,7 +233,7 @@ extension Mach.Port where RightType == Mach.ReceiveRight { // The value of newRight is validated by the Mach.Port initializer precondition(newRightType == MACH_MSG_TYPE_MOVE_SEND_ONCE) - return Mach.Port(name: newRight) + return Mach.Port(name: newRight) } /// Create a send right for a given receive right. @@ -248,7 +248,7 @@ extension Mach.Port where RightType == Mach.ReceiveRight { // name is the same because send and recv rights are coalesced machPrecondition(mach_port_insert_right(mach_task_self_, name, name, mach_msg_type_name_t(how))) - return Mach.Port(name: name) + return Mach.Port(name: name) } /// Access the make-send count. @@ -304,7 +304,7 @@ extension Mach.Port where RightType == Mach.SendRight { } machPrecondition(kr) - return Mach.Port(name: name) + return Mach.Port(name: name) } } From 6a9ebce25967fd856847af8f0c3c5a81034b1563 Mon Sep 17 00:00:00 2001 From: Daniel Loffgren Date: Wed, 21 Dec 2022 13:20:38 -0800 Subject: [PATCH 104/427] Add public annotations where applicable --- Sources/System/MachPort.swift | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/Sources/System/MachPort.swift b/Sources/System/MachPort.swift index 090067bd..bb32e148 100644 --- a/Sources/System/MachPort.swift +++ b/Sources/System/MachPort.swift @@ -11,7 +11,7 @@ import Darwin.Mach -protocol MachPortRight {} +public protocol MachPortRight {} private func machPrecondition( file: StaticString = #file, @@ -23,9 +23,9 @@ private func machPrecondition( precondition(kr == expected, file: file, line: line) } -enum Mach { +public enum Mach { @_moveOnly - struct Port { + public struct Port { internal var name: mach_port_name_t internal var context: mach_port_context_t @@ -42,7 +42,7 @@ enum Mach { /// end of the Mach.Port instance's lifetime. /// /// This initializer makes a syscall to guard the right. - init(name: mach_port_name_t) { + public init(name: mach_port_name_t) { precondition(name != mach_port_name_t(MACH_PORT_NULL), "Mach.Port cannot be initialized with MACH_PORT_NULL") self.name = name @@ -68,7 +68,7 @@ enum Mach { /// /// The body block may optionally return something, which will then be /// returned to the caller of withBorrowedName. - func withBorrowedName(body: (mach_port_name_t) -> ReturnType) -> ReturnType { + public func withBorrowedName(body: (mach_port_name_t) -> ReturnType) -> ReturnType { return body(name) } @@ -89,7 +89,7 @@ enum Mach { } /// Possible errors that can be thrown by Mach.Port operations. - enum PortRightError : Error { + public enum PortRightError : Error { /// Returned when an operation cannot be completed, because the Mach /// port right has become a dead name. This is caused by deallocation of the /// receive right on the other end. @@ -97,10 +97,10 @@ enum Mach { } /// The MachPortRight type used to manage a receive right. - struct ReceiveRight : MachPortRight {} + public struct ReceiveRight : MachPortRight {} /// The MachPortRight type used to manage a send right. - struct SendRight : MachPortRight {} + public struct SendRight : MachPortRight {} /// The MachPortRight type used to manage a send-once right. /// @@ -109,13 +109,13 @@ enum Mach { /// /// Upon destruction a send-once notification will be sent to the /// receiving end. - struct SendOnceRight : MachPortRight {} + public struct SendOnceRight : MachPortRight {} /// Create a connected pair of rights, one receive, and one send. /// /// This function will abort if the rights could not be created. /// Callers may assert that valid rights are always returned. - static func allocatePortRightPair() -> (receive: Mach.Port, send: Mach.Port) { + public static func allocatePortRightPair() -> (receive: Mach.Port, send: Mach.Port) { var name = mach_port_name_t(MACH_PORT_NULL) let secret = mach_port_context_t(arc4random()) withUnsafeMutablePointer(to: &name) { name in @@ -129,7 +129,7 @@ enum Mach { } } -extension Mach.Port where RightType == Mach.ReceiveRight { +public extension Mach.Port where RightType == Mach.ReceiveRight { /// Transfer ownership of an existing, unmanaged, but already guarded, /// Mach port right into a Mach.Port by name. /// @@ -273,7 +273,7 @@ extension Mach.Port where RightType == Mach.ReceiveRight { } } -extension Mach.Port where RightType == Mach.SendRight { +public extension Mach.Port where RightType == Mach.SendRight { /// Transfer ownership of the underlying port right to the caller. /// /// Returns the Mach port name representing the right. @@ -309,7 +309,7 @@ extension Mach.Port where RightType == Mach.SendRight { } -extension Mach.Port where RightType == Mach.SendOnceRight { +public extension Mach.Port where RightType == Mach.SendOnceRight { /// Transfer ownership of the underlying port right to the caller. /// /// Returns the Mach port name representing the right. From 47feb93a38cc69af60411392977ac30e53fe7cf7 Mon Sep 17 00:00:00 2001 From: Daniel Loffgren Date: Wed, 21 Dec 2022 13:43:26 -0800 Subject: [PATCH 105/427] Add a test that ensures makeSendOnce is creating unique send-once rights --- Tests/SystemTests/MachPortTests.swift | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/Tests/SystemTests/MachPortTests.swift b/Tests/SystemTests/MachPortTests.swift index 0def85ac..7faa9760 100644 --- a/Tests/SystemTests/MachPortTests.swift +++ b/Tests/SystemTests/MachPortTests.swift @@ -110,6 +110,17 @@ final class MachPortTests: XCTestCase { XCTAssert(same == zero) } + func testMakeSendOnceIsUnique() throws { + let recv = Mach.Port() + let once = recv.makeSendOnceRight() + recv.withBorrowedName { rname in + once.withBorrowedName { oname in + print(oname, rname) + XCTAssert(oname != rname) + } + } + } + func testMakePair() throws { let (recv, send) = Mach.allocatePortRightPair() XCTAssert(recv.makeSendCount == 1) From ae6b1f24a99bfe07bc06c7c50a3aeadc3766be51 Mon Sep 17 00:00:00 2001 From: loffgren <117697138+loffgren@users.noreply.github.com> Date: Thu, 22 Dec 2022 14:52:36 -0800 Subject: [PATCH 106/427] Remove space before inheritance colon Co-authored-by: Michael Ilseman --- Sources/System/MachPort.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Sources/System/MachPort.swift b/Sources/System/MachPort.swift index bb32e148..0d8794d7 100644 --- a/Sources/System/MachPort.swift +++ b/Sources/System/MachPort.swift @@ -97,10 +97,10 @@ public enum Mach { } /// The MachPortRight type used to manage a receive right. - public struct ReceiveRight : MachPortRight {} + public struct ReceiveRight: MachPortRight {} /// The MachPortRight type used to manage a send right. - public struct SendRight : MachPortRight {} + public struct SendRight: MachPortRight {} /// The MachPortRight type used to manage a send-once right. /// @@ -109,7 +109,7 @@ public enum Mach { /// /// Upon destruction a send-once notification will be sent to the /// receiving end. - public struct SendOnceRight : MachPortRight {} + public struct SendOnceRight: MachPortRight {} /// Create a connected pair of rights, one receive, and one send. /// From d129f62ce91766c5b1fe50ba391a27c5deb9a401 Mon Sep 17 00:00:00 2001 From: loffgren <117697138+loffgren@users.noreply.github.com> Date: Thu, 22 Dec 2022 17:12:26 -0800 Subject: [PATCH 107/427] Use Markdown code voice for initializer comment Co-authored-by: Karoy Lorentey --- Sources/System/MachPort.swift | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Sources/System/MachPort.swift b/Sources/System/MachPort.swift index 0d8794d7..4d225a1d 100644 --- a/Sources/System/MachPort.swift +++ b/Sources/System/MachPort.swift @@ -30,16 +30,16 @@ public enum Mach { internal var context: mach_port_context_t /// Transfer ownership of an existing unmanaged Mach port right into a - /// Mach.Port by name. + /// `Mach.Port` by name. /// - /// This initializer aborts if name is MACH_PORT_NULL, or if name is - /// MACH_PORT_DEAD and the type T of Mach.Port is Mach.ReceiveRight. + /// This initializer traps if `name` is `MACH_PORT_NULL`, or if `name` is + /// `MACH_PORT_DEAD` and the `RightType` is `Mach.ReceiveRight`. /// - /// If the type of the right does not match the type T of Mach.Port - /// being constructed, behavior is undefined. + /// If the type of the right does not match the `RightType` of the + /// `Mach.Port` being constructed, behavior is undefined. /// /// The underlying port right will be automatically deallocated at the - /// end of the Mach.Port instance's lifetime. + /// end of the `Mach.Port` instance's lifetime. /// /// This initializer makes a syscall to guard the right. public init(name: mach_port_name_t) { From 700b8f867e9fd1c4ffe07e318fd56ccf20560f21 Mon Sep 17 00:00:00 2001 From: Daniel Loffgren Date: Thu, 22 Dec 2022 18:01:40 -0800 Subject: [PATCH 108/427] Use two space indentation --- Sources/System/MachPort.swift | 534 +++++++++++++++++----------------- 1 file changed, 267 insertions(+), 267 deletions(-) diff --git a/Sources/System/MachPort.swift b/Sources/System/MachPort.swift index 4d225a1d..8e7f8cfa 100644 --- a/Sources/System/MachPort.swift +++ b/Sources/System/MachPort.swift @@ -14,314 +14,314 @@ import Darwin.Mach public protocol MachPortRight {} private func machPrecondition( - file: StaticString = #file, - line: UInt = #line, - _ body: @autoclosure () -> kern_return_t + file: StaticString = #file, + line: UInt = #line, + _ body: @autoclosure () -> kern_return_t ) { - let kr = body() - let expected = KERN_SUCCESS - precondition(kr == expected, file: file, line: line) + let kr = body() + let expected = KERN_SUCCESS + precondition(kr == expected, file: file, line: line) } public enum Mach { - @_moveOnly - public struct Port { - internal var name: mach_port_name_t - internal var context: mach_port_context_t - - /// Transfer ownership of an existing unmanaged Mach port right into a - /// `Mach.Port` by name. - /// - /// This initializer traps if `name` is `MACH_PORT_NULL`, or if `name` is - /// `MACH_PORT_DEAD` and the `RightType` is `Mach.ReceiveRight`. - /// - /// If the type of the right does not match the `RightType` of the - /// `Mach.Port` being constructed, behavior is undefined. - /// - /// The underlying port right will be automatically deallocated at the - /// end of the `Mach.Port` instance's lifetime. - /// - /// This initializer makes a syscall to guard the right. - public init(name: mach_port_name_t) { - precondition(name != mach_port_name_t(MACH_PORT_NULL), "Mach.Port cannot be initialized with MACH_PORT_NULL") - self.name = name - - if RightType.self == ReceiveRight.self { - precondition(name != 0xFFFFFFFF /* MACH_PORT_DEAD */, "Receive rights cannot be dead names") - - let secret = mach_port_context_t(arc4random()) - machPrecondition(mach_port_guard(mach_task_self_, name, secret, 0)) - self.context = secret - } - else { - self.context = 0 - } - } - - /// Borrow access to the port name in a block that can perform - /// non-consuming operations. - /// - /// Take care when using this function; many operations consume rights, - /// and send-once rights are easily consumed. - /// - /// If the right is consumed, behavior is undefined. - /// - /// The body block may optionally return something, which will then be - /// returned to the caller of withBorrowedName. - public func withBorrowedName(body: (mach_port_name_t) -> ReturnType) -> ReturnType { - return body(name) - } - - deinit { - if name == 0xFFFFFFFF /* MACH_PORT_DEAD */ { - precondition(RightType.self != ReceiveRight.self, "Receive rights cannot be dead names") - machPrecondition(mach_port_mod_refs(mach_task_self_, name, MACH_PORT_RIGHT_DEAD_NAME, -1)) - } else { - if RightType.self == ReceiveRight.self { - machPrecondition(mach_port_destruct(mach_task_self_, name, -1, context)) - } else if RightType.self == SendRight.self { - machPrecondition(mach_port_mod_refs(mach_task_self_, name, MACH_PORT_RIGHT_SEND, -1)) - } else if RightType.self == SendOnceRight.self { - machPrecondition(mach_port_mod_refs(mach_task_self_, name, MACH_PORT_RIGHT_SEND_ONCE, -1)) - } - } - } - } - - /// Possible errors that can be thrown by Mach.Port operations. - public enum PortRightError : Error { - /// Returned when an operation cannot be completed, because the Mach - /// port right has become a dead name. This is caused by deallocation of the - /// receive right on the other end. - case deadName - } + @_moveOnly + public struct Port { + internal var name: mach_port_name_t + internal var context: mach_port_context_t - /// The MachPortRight type used to manage a receive right. - public struct ReceiveRight: MachPortRight {} - - /// The MachPortRight type used to manage a send right. - public struct SendRight: MachPortRight {} - - /// The MachPortRight type used to manage a send-once right. + /// Transfer ownership of an existing unmanaged Mach port right into a + /// `Mach.Port` by name. /// - /// Send-once rights are the most restrictive type of Mach port rights. - /// They cannot create other rights, and are consumed upon use. + /// This initializer traps if `name` is `MACH_PORT_NULL`, or if `name` is + /// `MACH_PORT_DEAD` and the `RightType` is `Mach.ReceiveRight`. /// - /// Upon destruction a send-once notification will be sent to the - /// receiving end. - public struct SendOnceRight: MachPortRight {} - - /// Create a connected pair of rights, one receive, and one send. + /// If the type of the right does not match the `RightType` of the + /// `Mach.Port` being constructed, behavior is undefined. /// - /// This function will abort if the rights could not be created. - /// Callers may assert that valid rights are always returned. - public static func allocatePortRightPair() -> (receive: Mach.Port, send: Mach.Port) { - var name = mach_port_name_t(MACH_PORT_NULL) - let secret = mach_port_context_t(arc4random()) - withUnsafeMutablePointer(to: &name) { name in - var options = mach_port_options_t() - options.flags = UInt32(MPO_INSERT_SEND_RIGHT); - withUnsafeMutablePointer(to: &options) { options in - machPrecondition(mach_port_construct(mach_task_self_, options, secret, name)) - } - } - return (Mach.Port(name: name, context: secret), Mach.Port(name: name)) - } -} - -public extension Mach.Port where RightType == Mach.ReceiveRight { - /// Transfer ownership of an existing, unmanaged, but already guarded, - /// Mach port right into a Mach.Port by name. - /// - /// This initializer aborts if name is MACH_PORT_NULL. - /// - /// If the type of the right does not match the type T of Mach.Port - /// being constructed, the behavior is undefined. - /// - /// The underlying port right will be automatically deallocated when - /// the Mach.Port object is destroyed. - init(name: mach_port_name_t, context: mach_port_context_t) { - self.name = name - self.context = context - } - - /// Allocate a new Mach port with a receive right, creating a - /// Mach.Port to manage it. + /// The underlying port right will be automatically deallocated at the + /// end of the `Mach.Port` instance's lifetime. /// - /// This initializer will abort if the right could not be created. - /// Callers may assert that a valid right is always returned. - init() { - var storage: mach_port_name_t = mach_port_name_t(MACH_PORT_NULL) - withUnsafeMutablePointer(to: &storage) { storage in - machPrecondition(mach_port_allocate(mach_task_self_, MACH_PORT_RIGHT_RECEIVE, storage)) - } + /// This initializer makes a syscall to guard the right. + public init(name: mach_port_name_t) { + precondition(name != mach_port_name_t(MACH_PORT_NULL), "Mach.Port cannot be initialized with MACH_PORT_NULL") + self.name = name - // name-only init will guard ReceiveRights - self.init(name: storage) - } + if RightType.self == ReceiveRight.self { + precondition(name != 0xFFFFFFFF /* MACH_PORT_DEAD */, "Receive rights cannot be dead names") - - /// Transfer ownership of the underlying port right to the caller. - /// - /// Returns a tuple containing the Mach port name representing the right, - /// and the context value used to guard the right. - /// - /// This operation liberates the right from management by the Mach.Port, - /// and the underlying right will no longer be automatically deallocated. - /// - /// After this function completes, the Mach.Port is destroyed and no longer - /// usable. - __consuming func relinquish() -> (name: mach_port_name_t, context: mach_port_context_t) { - return (name: name, context: context) - } - - /// Remove guard and transfer ownership of the underlying port right to - /// the caller. - /// - /// Returns the Mach port name representing the right. - /// - /// This operation liberates the right from management by the Mach.Port, - /// and the underlying right will no longer be automatically deallocated. - /// - /// After this function completes, the Mach.Port is destroyed and no longer - /// usable. - /// - /// This function makes a syscall to remove the guard from - /// Mach.ReceiveRights. Use relinquish() to avoid the syscall and extract - /// the context value along with the port name. - __consuming func unguardAndRelinquish() -> mach_port_name_t { - machPrecondition(mach_port_unguard(mach_task_self_, name, context)) - return name + let secret = mach_port_context_t(arc4random()) + machPrecondition(mach_port_guard(mach_task_self_, name, secret, 0)) + self.context = secret + } + else { + self.context = 0 + } } /// Borrow access to the port name in a block that can perform /// non-consuming operations. /// - /// Take care when using this function; many operations consume rights. + /// Take care when using this function; many operations consume rights, + /// and send-once rights are easily consumed. /// /// If the right is consumed, behavior is undefined. /// /// The body block may optionally return something, which will then be /// returned to the caller of withBorrowedName. - func withBorrowedName(body: (mach_port_name_t, mach_port_context_t) -> ReturnType) -> ReturnType { - body(name, context) + public func withBorrowedName(body: (mach_port_name_t) -> ReturnType) -> ReturnType { + return body(name) } - /// Create a send-once right for a given receive right. - /// - /// This does not affect the makeSendCount of the receive right. - /// - /// This function will abort if the right could not be created. - /// Callers may assert that a valid right is always returned. - func makeSendOnceRight() -> Mach.Port { - // send once rights do not coalesce - var newRight: mach_port_name_t = mach_port_name_t(MACH_PORT_NULL) - var newRightType: mach_port_type_t = MACH_PORT_TYPE_NONE - - withUnsafeMutablePointer(to: &newRight) { newRight in - withUnsafeMutablePointer(to: &newRightType) { newRightType in - machPrecondition( - mach_port_extract_right(mach_task_self_, - name, - mach_msg_type_name_t(MACH_MSG_TYPE_MAKE_SEND_ONCE), - newRight, - newRightType) - ) - } + deinit { + if name == 0xFFFFFFFF /* MACH_PORT_DEAD */ { + precondition(RightType.self != ReceiveRight.self, "Receive rights cannot be dead names") + machPrecondition(mach_port_mod_refs(mach_task_self_, name, MACH_PORT_RIGHT_DEAD_NAME, -1)) + } else { + if RightType.self == ReceiveRight.self { + machPrecondition(mach_port_destruct(mach_task_self_, name, -1, context)) + } else if RightType.self == SendRight.self { + machPrecondition(mach_port_mod_refs(mach_task_self_, name, MACH_PORT_RIGHT_SEND, -1)) + } else if RightType.self == SendOnceRight.self { + machPrecondition(mach_port_mod_refs(mach_task_self_, name, MACH_PORT_RIGHT_SEND_ONCE, -1)) } - - // The value of newRight is validated by the Mach.Port initializer - precondition(newRightType == MACH_MSG_TYPE_MOVE_SEND_ONCE) - - return Mach.Port(name: newRight) + } } + } + + /// Possible errors that can be thrown by Mach.Port operations. + public enum PortRightError : Error { + /// Returned when an operation cannot be completed, because the Mach + /// port right has become a dead name. This is caused by deallocation of the + /// receive right on the other end. + case deadName + } + + /// The MachPortRight type used to manage a receive right. + public struct ReceiveRight: MachPortRight {} + + /// The MachPortRight type used to manage a send right. + public struct SendRight: MachPortRight {} + + /// The MachPortRight type used to manage a send-once right. + /// + /// Send-once rights are the most restrictive type of Mach port rights. + /// They cannot create other rights, and are consumed upon use. + /// + /// Upon destruction a send-once notification will be sent to the + /// receiving end. + public struct SendOnceRight: MachPortRight {} + + /// Create a connected pair of rights, one receive, and one send. + /// + /// This function will abort if the rights could not be created. + /// Callers may assert that valid rights are always returned. + public static func allocatePortRightPair() -> (receive: Mach.Port, send: Mach.Port) { + var name = mach_port_name_t(MACH_PORT_NULL) + let secret = mach_port_context_t(arc4random()) + withUnsafeMutablePointer(to: &name) { name in + var options = mach_port_options_t() + options.flags = UInt32(MPO_INSERT_SEND_RIGHT); + withUnsafeMutablePointer(to: &options) { options in + machPrecondition(mach_port_construct(mach_task_self_, options, secret, name)) + } + } + return (Mach.Port(name: name, context: secret), Mach.Port(name: name)) + } +} - /// Create a send right for a given receive right. - /// - /// This increments the makeSendCount of the receive right. - /// - /// This function will abort if the right could not be created. - /// Callers may assert that a valid right is always returned. - func makeSendRight() -> Mach.Port { - let how = MACH_MSG_TYPE_MAKE_SEND - - // name is the same because send and recv rights are coalesced - machPrecondition(mach_port_insert_right(mach_task_self_, name, name, mach_msg_type_name_t(how))) +public extension Mach.Port where RightType == Mach.ReceiveRight { + /// Transfer ownership of an existing, unmanaged, but already guarded, + /// Mach port right into a Mach.Port by name. + /// + /// This initializer aborts if name is MACH_PORT_NULL. + /// + /// If the type of the right does not match the type T of Mach.Port + /// being constructed, the behavior is undefined. + /// + /// The underlying port right will be automatically deallocated when + /// the Mach.Port object is destroyed. + init(name: mach_port_name_t, context: mach_port_context_t) { + self.name = name + self.context = context + } + + /// Allocate a new Mach port with a receive right, creating a + /// Mach.Port to manage it. + /// + /// This initializer will abort if the right could not be created. + /// Callers may assert that a valid right is always returned. + init() { + var storage: mach_port_name_t = mach_port_name_t(MACH_PORT_NULL) + withUnsafeMutablePointer(to: &storage) { storage in + machPrecondition(mach_port_allocate(mach_task_self_, MACH_PORT_RIGHT_RECEIVE, storage)) + } - return Mach.Port(name: name) + // name-only init will guard ReceiveRights + self.init(name: storage) + } + + + /// Transfer ownership of the underlying port right to the caller. + /// + /// Returns a tuple containing the Mach port name representing the right, + /// and the context value used to guard the right. + /// + /// This operation liberates the right from management by the Mach.Port, + /// and the underlying right will no longer be automatically deallocated. + /// + /// After this function completes, the Mach.Port is destroyed and no longer + /// usable. + __consuming func relinquish() -> (name: mach_port_name_t, context: mach_port_context_t) { + return (name: name, context: context) + } + + /// Remove guard and transfer ownership of the underlying port right to + /// the caller. + /// + /// Returns the Mach port name representing the right. + /// + /// This operation liberates the right from management by the Mach.Port, + /// and the underlying right will no longer be automatically deallocated. + /// + /// After this function completes, the Mach.Port is destroyed and no longer + /// usable. + /// + /// This function makes a syscall to remove the guard from + /// Mach.ReceiveRights. Use relinquish() to avoid the syscall and extract + /// the context value along with the port name. + __consuming func unguardAndRelinquish() -> mach_port_name_t { + machPrecondition(mach_port_unguard(mach_task_self_, name, context)) + return name + } + + /// Borrow access to the port name in a block that can perform + /// non-consuming operations. + /// + /// Take care when using this function; many operations consume rights. + /// + /// If the right is consumed, behavior is undefined. + /// + /// The body block may optionally return something, which will then be + /// returned to the caller of withBorrowedName. + func withBorrowedName(body: (mach_port_name_t, mach_port_context_t) -> ReturnType) -> ReturnType { + body(name, context) + } + + /// Create a send-once right for a given receive right. + /// + /// This does not affect the makeSendCount of the receive right. + /// + /// This function will abort if the right could not be created. + /// Callers may assert that a valid right is always returned. + func makeSendOnceRight() -> Mach.Port { + // send once rights do not coalesce + var newRight: mach_port_name_t = mach_port_name_t(MACH_PORT_NULL) + var newRightType: mach_port_type_t = MACH_PORT_TYPE_NONE + + withUnsafeMutablePointer(to: &newRight) { newRight in + withUnsafeMutablePointer(to: &newRightType) { newRightType in + machPrecondition( + mach_port_extract_right(mach_task_self_, + name, + mach_msg_type_name_t(MACH_MSG_TYPE_MAKE_SEND_ONCE), + newRight, + newRightType) + ) + } } - /// Access the make-send count. - /// - /// Each get/set of this property makes a syscall. - var makeSendCount : mach_port_mscount_t { - get { - var status: mach_port_status = mach_port_status() - var size: mach_msg_type_number_t = mach_msg_type_number_t(MemoryLayout.size / MemoryLayout.size) - withUnsafeMutablePointer(to: &size) { size in - withUnsafeMutablePointer(to: &status) { status in - let info = UnsafeMutableRawPointer(status).bindMemory(to: integer_t.self, capacity: 1) - machPrecondition(mach_port_get_attributes(mach_task_self_, name, MACH_PORT_RECEIVE_STATUS, info, size)) - } - } - return status.mps_mscount + // The value of newRight is validated by the Mach.Port initializer + precondition(newRightType == MACH_MSG_TYPE_MOVE_SEND_ONCE) + + return Mach.Port(name: newRight) + } + + /// Create a send right for a given receive right. + /// + /// This increments the makeSendCount of the receive right. + /// + /// This function will abort if the right could not be created. + /// Callers may assert that a valid right is always returned. + func makeSendRight() -> Mach.Port { + let how = MACH_MSG_TYPE_MAKE_SEND + + // name is the same because send and recv rights are coalesced + machPrecondition(mach_port_insert_right(mach_task_self_, name, name, mach_msg_type_name_t(how))) + + return Mach.Port(name: name) + } + + /// Access the make-send count. + /// + /// Each get/set of this property makes a syscall. + var makeSendCount : mach_port_mscount_t { + get { + var status: mach_port_status = mach_port_status() + var size: mach_msg_type_number_t = mach_msg_type_number_t(MemoryLayout.size / MemoryLayout.size) + withUnsafeMutablePointer(to: &size) { size in + withUnsafeMutablePointer(to: &status) { status in + let info = UnsafeMutableRawPointer(status).bindMemory(to: integer_t.self, capacity: 1) + machPrecondition(mach_port_get_attributes(mach_task_self_, name, MACH_PORT_RECEIVE_STATUS, info, size)) } + } + return status.mps_mscount + } - set { - machPrecondition(mach_port_set_mscount(mach_task_self_, name, newValue)) - } + set { + machPrecondition(mach_port_set_mscount(mach_task_self_, name, newValue)) } + } } public extension Mach.Port where RightType == Mach.SendRight { - /// Transfer ownership of the underlying port right to the caller. - /// - /// Returns the Mach port name representing the right. - /// - /// This operation liberates the right from management by the Mach.Port, - /// and the underlying right will no longer be automatically deallocated. - /// - /// After this function completes, the Mach.Port is destroyed and no longer - /// usable. - __consuming func relinquish() -> mach_port_name_t { - return name + /// Transfer ownership of the underlying port right to the caller. + /// + /// Returns the Mach port name representing the right. + /// + /// This operation liberates the right from management by the Mach.Port, + /// and the underlying right will no longer be automatically deallocated. + /// + /// After this function completes, the Mach.Port is destroyed and no longer + /// usable. + __consuming func relinquish() -> mach_port_name_t { + return name + } + + /// Create another send right from a given send right. + /// + /// This does not affect the makeSendCount of the receive right. + /// + /// If the send right being copied has become a dead name, meaning the + /// receiving side has been deallocated, then copySendRight() will throw + /// a Mach.PortRightError.deadName error. + func copySendRight() throws -> Mach.Port { + let how = MACH_MSG_TYPE_COPY_SEND + + // name is the same because send rights are coalesced + let kr = mach_port_insert_right(mach_task_self_, name, name, mach_msg_type_name_t(how)) + if kr == KERN_INVALID_CAPABILITY { + throw Mach.PortRightError.deadName } + machPrecondition(kr) - /// Create another send right from a given send right. - /// - /// This does not affect the makeSendCount of the receive right. - /// - /// If the send right being copied has become a dead name, meaning the - /// receiving side has been deallocated, then copySendRight() will throw - /// a Mach.PortRightError.deadName error. - func copySendRight() throws -> Mach.Port { - let how = MACH_MSG_TYPE_COPY_SEND - - // name is the same because send rights are coalesced - let kr = mach_port_insert_right(mach_task_self_, name, name, mach_msg_type_name_t(how)) - if kr == KERN_INVALID_CAPABILITY { - throw Mach.PortRightError.deadName - } - machPrecondition(kr) - - return Mach.Port(name: name) - } + return Mach.Port(name: name) + } } public extension Mach.Port where RightType == Mach.SendOnceRight { - /// Transfer ownership of the underlying port right to the caller. - /// - /// Returns the Mach port name representing the right. - /// - /// This operation liberates the right from management by the Mach.Port, - /// and the underlying right will no longer be automatically deallocated. - /// - /// After this function completes, the Mach.Port is destroyed and no longer - /// usable. - __consuming func relinquish() -> mach_port_name_t { - return name - } + /// Transfer ownership of the underlying port right to the caller. + /// + /// Returns the Mach port name representing the right. + /// + /// This operation liberates the right from management by the Mach.Port, + /// and the underlying right will no longer be automatically deallocated. + /// + /// After this function completes, the Mach.Port is destroyed and no longer + /// usable. + __consuming func relinquish() -> mach_port_name_t { + return name + } } #endif From 3f66dfbaf8601ac05d7ca5c8cfe7781c157e56b7 Mon Sep 17 00:00:00 2001 From: Max Desiatov Date: Thu, 5 Jan 2023 10:58:04 +0000 Subject: [PATCH 109/427] MachPort: fix build issues with Swift 5.7 It seems that `#if $MoveOnly` check has no effect on Swift 5.7, so we should check whether Swift 5.8 or later are available first. Resolves #117. --- Sources/System/MachPort.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/System/MachPort.swift b/Sources/System/MachPort.swift index 8e7f8cfa..39fdd664 100644 --- a/Sources/System/MachPort.swift +++ b/Sources/System/MachPort.swift @@ -7,7 +7,7 @@ See https://swift.org/LICENSE.txt for license information */ -#if $MoveOnly && (os(macOS) || os(iOS) || os(watchOS) || os(tvOS)) +#if swift(>=5.8) && $MoveOnly && (os(macOS) || os(iOS) || os(watchOS) || os(tvOS)) import Darwin.Mach From b5e330163a05601c6fc98b966b6cb282e46e8c26 Mon Sep 17 00:00:00 2001 From: Max Desiatov Date: Thu, 5 Jan 2023 14:35:22 +0000 Subject: [PATCH 110/427] MachPortTests: fix build issues with Swift 5.7 --- Tests/SystemTests/MachPortTests.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/SystemTests/MachPortTests.swift b/Tests/SystemTests/MachPortTests.swift index 7faa9760..5c119d17 100644 --- a/Tests/SystemTests/MachPortTests.swift +++ b/Tests/SystemTests/MachPortTests.swift @@ -7,7 +7,7 @@ See https://swift.org/LICENSE.txt for license information */ -#if $MoveOnly && (os(macOS) || os(iOS) || os(watchOS) || os(tvOS)) +#if swift(>=5.8) && $MoveOnly && (os(macOS) || os(iOS) || os(watchOS) || os(tvOS)) import XCTest import Darwin.Mach From deb56e1ad2eddd94c607fa57993eb28656abebf6 Mon Sep 17 00:00:00 2001 From: Alex Martini Date: Tue, 31 Jan 2023 12:07:17 -0800 Subject: [PATCH 111/427] Keep doc comments immediately before declarations. If there's anything between them, DocC doesn't recognize that the doc comment belongs to the declaration that comes after the interruption, resulting in that declaration being undocumented. --- Sources/System/FilePath/FilePath.swift | 6 ++-- Sources/System/FilePath/FilePathSyntax.swift | 30 +++++++++---------- Sources/System/FilePath/FilePathWindows.swift | 4 +-- 3 files changed, 20 insertions(+), 20 deletions(-) diff --git a/Sources/System/FilePath/FilePath.swift b/Sources/System/FilePath/FilePath.swift index 206b2bb2..bb296289 100644 --- a/Sources/System/FilePath/FilePath.swift +++ b/Sources/System/FilePath/FilePath.swift @@ -7,6 +7,9 @@ See https://swift.org/LICENSE.txt for license information */ +// TODO(docs): Section on all the new syntactic operations, lexical normalization, decomposition, +// components, etc. +/*System 0.0.1, @available(macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0, *)*/ /// Represents a location in the file system. /// /// This structure recognizes directory separators (e.g. `/`), roots, and @@ -38,9 +41,6 @@ /// are file-system–specific and have additional considerations /// like case insensitivity, Unicode normalization, and symbolic links. /// -// TODO(docs): Section on all the new syntactic operations, lexical normalization, decomposition, -// components, etc. -/*System 0.0.1, @available(macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0, *)*/ public struct FilePath { internal var _storage: SystemString diff --git a/Sources/System/FilePath/FilePathSyntax.swift b/Sources/System/FilePath/FilePathSyntax.swift index b02de33e..9462d599 100644 --- a/Sources/System/FilePath/FilePathSyntax.swift +++ b/Sources/System/FilePath/FilePathSyntax.swift @@ -52,6 +52,8 @@ extension FilePath { /// * `\Users` public var isRelative: Bool { !isAbsolute } + // TODO(Windows docs): examples with roots, such as whether `\foo\bar` + // starts with `C:\foo` /// Returns whether `other` is a prefix of `self`, only considering /// whole path components. /// @@ -64,14 +66,14 @@ extension FilePath { /// path.starts(with: "/usr/bin/ls///") // true /// path.starts(with: "/us") // false /// - // TODO(Windows docs): examples with roots, such as whether `\foo\bar` - // starts with `C:\foo` public func starts(with other: FilePath) -> Bool { guard !other.isEmpty else { return true } return self.root == other.root && components.starts( with: other.components) } + // TODO(Windows docs): examples with roots, such as whether `C:\foo\bar` + // ends with `C:bar` /// Returns whether `other` is a suffix of `self`, only considering /// whole path components. /// @@ -84,8 +86,6 @@ extension FilePath { /// path.ends(with: "/usr/bin/ls///") // true /// path.ends(with: "/ls") // false /// - // TODO(Windows docs): examples with roots, such as whether `C:\foo\bar` - // ends with `C:bar` public func ends(with other: FilePath) -> Bool { if other.root != nil { // TODO: anything tricky here for Windows? @@ -441,6 +441,7 @@ extension FilePath { // Modification and concatenation API /*System 0.0.2, @available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *)*/ extension FilePath { + // TODO(Windows docs): example with roots /// If `prefix` is a prefix of `self`, removes it and returns `true`. /// Otherwise returns `false`. /// @@ -451,7 +452,6 @@ extension FilePath { /// path.removePrefix("/us") // false /// path.removePrefix("/usr/local") // true, path is "bin" /// - // TODO(Windows docs): example with roots public mutating func removePrefix(_ prefix: FilePath) -> Bool { defer { _invariantCheck() } // FIXME: Should Windows have more nuanced semantics? @@ -462,6 +462,7 @@ extension FilePath { return true } + // TODO(Windows docs): example with roots /// Append a `component` on to the end of this path. /// /// Example: @@ -473,12 +474,12 @@ extension FilePath { /// } /// // path is "/tmp/foo/bar/../baz" /// - // TODO(Windows docs): example with roots public mutating func append(_ component: __owned FilePath.Component) { defer { _invariantCheck() } _append(unchecked: component._slice) } + // TODO(Windows docs): example with roots /// Append `components` on to the end of this path. /// /// Example: @@ -488,7 +489,6 @@ extension FilePath { /// let otherPath: FilePath = "/bin/ls" /// path.append(otherPath.components) // path is "/usr/local/bin/ls" /// - // TODO(Windows docs): example with roots public mutating func append( _ components: __owned C ) where C.Element == FilePath.Component { @@ -498,6 +498,8 @@ extension FilePath { } } + // TODO(Windows docs): example with roots, should we rephrase this "spurious + // roots"? /// Append the contents of `other`, ignoring any spurious leading separators. /// /// A leading separator is spurious if `self` is non-empty. @@ -509,8 +511,6 @@ extension FilePath { /// path.append("static/assets") // "/var/www/website/static/assets" /// path.append("/main.css") // "/var/www/website/static/assets/main.css" /// - // TODO(Windows docs): example with roots, should we rephrase this "spurious - // roots"? public mutating func append(_ other: __owned String) { defer { _invariantCheck() } guard !other.utf8.isEmpty else { return } @@ -522,18 +522,18 @@ extension FilePath { _append(unchecked: otherPath._storage[otherPath._relativeStart...]) } + // TODO(Windows docs): example with roots /// Non-mutating version of `append(_:Component)`. /// - // TODO(Windows docs): example with roots public __consuming func appending(_ other: __owned Component) -> FilePath { var copy = self copy.append(other) return copy } + // TODO(Windows docs): example with roots /// Non-mutating version of `append(_:C)`. /// - // TODO(Windows docs): example with roots public __consuming func appending( _ components: __owned C ) -> FilePath where C.Element == FilePath.Component { @@ -542,15 +542,17 @@ extension FilePath { return copy } + // TODO(Windows docs): example with roots /// Non-mutating version of `append(_:String)`. /// - // TODO(Windows docs): example with roots public __consuming func appending(_ other: __owned String) -> FilePath { var copy = self copy.append(other) return copy } + // TODO(Windows docs): examples and docs with roots, update/generalize doc + // comment /// If `other` does not have a root, append each component of `other`. If /// `other` has a root, replaces `self` with other. /// @@ -565,8 +567,6 @@ extension FilePath { /// path.push("dir/file.txt") // path is "/tmp/dir/file.txt" /// path.push("/bin") // path is "/bin" /// - // TODO(Windows docs): examples and docs with roots, update/generalize doc - // comment public mutating func push(_ other: __owned FilePath) { defer { _invariantCheck() } guard other.root == nil else { @@ -577,9 +577,9 @@ extension FilePath { _append(unchecked: other._storage[...]) } + // TODO(Windows docs): examples and docs with roots /// Non-mutating version of `push()`. /// - // TODO(Windows docs): examples and docs with roots public __consuming func pushing(_ other: __owned FilePath) -> FilePath { var copy = self copy.push(other) diff --git a/Sources/System/FilePath/FilePathWindows.swift b/Sources/System/FilePath/FilePathWindows.swift index a86651d1..d0656c35 100644 --- a/Sources/System/FilePath/FilePathWindows.swift +++ b/Sources/System/FilePath/FilePathWindows.swift @@ -150,21 +150,21 @@ internal struct WindowsRootInfo { /// * Omitted volume from other forms: `\\.\`, `\\.\UNC\server\\`, `\\server\\` case empty + // TODO: NT paths? Admin paths using `$`? /// A specified drive. /// /// * Traditional disk: `C:\`, `C:` /// * Device disk: `\\.\C:\`, `\\?\C:\` /// * UNC: `\\server\e:\`, `\\?\UNC\server\e:\` /// - // TODO: NT paths? Admin paths using `$`? case drive(Character) + // TODO: GUID type? /// A volume with a GUID in a non-traditional path /// /// * UNC: `\\host\Volume{0000-...}\`, `\\.\UNC\host\Volume{0000-...}\` /// * Device roots: `\\.\Volume{0000-...}\`, `\\?\Volume{000-...}\` /// - // TODO: GUID type? case guid(String) // TODO: Legacy DOS devices, such as COM1? From 2c2a831f66b4a1d139f3b34b1671d65ca30a6c2c Mon Sep 17 00:00:00 2001 From: Alex Martini Date: Tue, 31 Jan 2023 12:35:33 -0800 Subject: [PATCH 112/427] Remove blank lines at the end of doc comments. --- Sources/System/FilePath/FilePath.swift | 1 - .../FilePath/FilePathComponentView.swift | 1 - .../System/FilePath/FilePathComponents.swift | 1 - Sources/System/FilePath/FilePathString.swift | 2 -- Sources/System/FilePath/FilePathSyntax.swift | 18 ------------------ Sources/System/FilePath/FilePathWindows.swift | 2 -- .../FilePathTests/FilePathExtras.swift | 1 - 7 files changed, 26 deletions(-) diff --git a/Sources/System/FilePath/FilePath.swift b/Sources/System/FilePath/FilePath.swift index bb296289..20bb9af3 100644 --- a/Sources/System/FilePath/FilePath.swift +++ b/Sources/System/FilePath/FilePath.swift @@ -40,7 +40,6 @@ /// However, the rules for path equivalence /// are file-system–specific and have additional considerations /// like case insensitivity, Unicode normalization, and symbolic links. -/// public struct FilePath { internal var _storage: SystemString diff --git a/Sources/System/FilePath/FilePathComponentView.swift b/Sources/System/FilePath/FilePathComponentView.swift index b8324fd6..2fdd0e48 100644 --- a/Sources/System/FilePath/FilePathComponentView.swift +++ b/Sources/System/FilePath/FilePathComponentView.swift @@ -28,7 +28,6 @@ extension FilePath { /// /// path.components.removeAll { $0.kind == .currentDirectory } /// // path is "/home/username/bin/scripts/tree" - /// public struct ComponentView { internal var _path: FilePath internal var _start: SystemString.Index diff --git a/Sources/System/FilePath/FilePathComponents.swift b/Sources/System/FilePath/FilePathComponents.swift index 19eca940..80d4be91 100644 --- a/Sources/System/FilePath/FilePathComponents.swift +++ b/Sources/System/FilePath/FilePathComponents.swift @@ -54,7 +54,6 @@ extension FilePath { /// file.kind == .regular // true /// file.extension // "txt" /// path.append(file) // path is "/tmp/foo.txt" - /// public struct Component { internal var _path: FilePath internal var _range: Range diff --git a/Sources/System/FilePath/FilePathString.swift b/Sources/System/FilePath/FilePathString.swift index 5b71f30a..2fd42225 100644 --- a/Sources/System/FilePath/FilePathString.swift +++ b/Sources/System/FilePath/FilePathString.swift @@ -98,7 +98,6 @@ extension FilePath.Component { /// /// - Parameter platformString: A pointer to a null-terminated platform /// string. - /// public init?(platformString: UnsafePointer) { self.init(_platformString: platformString) } @@ -187,7 +186,6 @@ extension FilePath.Root { /// /// - Parameter platformString: A pointer to a null-terminated platform /// string. - /// public init?(platformString: UnsafePointer) { self.init(_platformString: platformString) } diff --git a/Sources/System/FilePath/FilePathSyntax.swift b/Sources/System/FilePath/FilePathSyntax.swift index 9462d599..81bf195f 100644 --- a/Sources/System/FilePath/FilePathSyntax.swift +++ b/Sources/System/FilePath/FilePathSyntax.swift @@ -65,7 +65,6 @@ extension FilePath { /// path.starts(with: "/usr/bin/ls") // true /// path.starts(with: "/usr/bin/ls///") // true /// path.starts(with: "/us") // false - /// public func starts(with other: FilePath) -> Bool { guard !other.isEmpty else { return true } return self.root == other.root && components.starts( @@ -85,7 +84,6 @@ extension FilePath { /// path.ends(with: "usr/bin/ls") // true /// path.ends(with: "/usr/bin/ls///") // true /// path.ends(with: "/ls") // false - /// public func ends(with other: FilePath) -> Bool { if other.root != nil { // TODO: anything tricky here for Windows? @@ -145,7 +143,6 @@ extension FilePath { /// path.root = nil // path is #"foo\bar"# /// path.root = "C:" // path is #"C:foo\bar"# /// path.root = #"C:\"# // path is #"C:\foo\bar"# - /// public var root: FilePath.Root? { get { guard _hasRoot else { return nil } @@ -178,7 +175,6 @@ extension FilePath { /// * `\\?\device\folder\file.exe => folder\file.exe` /// * `\\server\share\file => file` /// * `\ => ""` - /// public __consuming func removingRoot() -> FilePath { var copy = self copy.root = nil @@ -209,7 +205,6 @@ extension FilePath { /// * `\\?\UNC\server\share\bar.exe => bar.exe` /// * `\\server\share => nil` /// * `\\?\UNC\server\share\ => nil` - /// public var lastComponent: Component? { components.last } /// Creates a new path with everything up to but not including @@ -247,7 +242,6 @@ extension FilePath { /// path.removeLastComponent() == true // path is "/usr" /// path.removeLastComponent() == true // path is "/" /// path.removeLastComponent() == false // path is "/" - /// @discardableResult public mutating func removeLastComponent() -> Bool { defer { _invariantCheck() } @@ -271,7 +265,6 @@ extension FilePath.Component { /// * `Foo.app => app` /// * `.hidden => nil` /// * `.. => nil` - /// public var `extension`: String? { guard let range = _extensionRange() else { return nil } return _slice[range].string @@ -285,7 +278,6 @@ extension FilePath.Component { /// * `Foo.app => Foo` /// * `.hidden => .hidden` /// * `.. => ..` - /// public var stem: String { _slice[_stemRange()].string } @@ -322,7 +314,6 @@ extension FilePath { /// path.extension = "o" // path is "/tmp/file.o" /// path.extension = nil // path is "/tmp/file" /// path.extension = "" // path is "/tmp/file." - /// public var `extension`: String? { get { lastComponent?.extension } set { @@ -451,7 +442,6 @@ extension FilePath { /// path.removePrefix("/usr/bin") // false /// path.removePrefix("/us") // false /// path.removePrefix("/usr/local") // true, path is "bin" - /// public mutating func removePrefix(_ prefix: FilePath) -> Bool { defer { _invariantCheck() } // FIXME: Should Windows have more nuanced semantics? @@ -473,7 +463,6 @@ extension FilePath { /// path.append(comp) /// } /// // path is "/tmp/foo/bar/../baz" - /// public mutating func append(_ component: __owned FilePath.Component) { defer { _invariantCheck() } _append(unchecked: component._slice) @@ -488,7 +477,6 @@ extension FilePath { /// path.append(["usr", "local"]) // path is "/usr/local" /// let otherPath: FilePath = "/bin/ls" /// path.append(otherPath.components) // path is "/usr/local/bin/ls" - /// public mutating func append( _ components: __owned C ) where C.Element == FilePath.Component { @@ -510,7 +498,6 @@ extension FilePath { /// path.append("/var/www/website") // "/var/www/website" /// path.append("static/assets") // "/var/www/website/static/assets" /// path.append("/main.css") // "/var/www/website/static/assets/main.css" - /// public mutating func append(_ other: __owned String) { defer { _invariantCheck() } guard !other.utf8.isEmpty else { return } @@ -524,7 +511,6 @@ extension FilePath { // TODO(Windows docs): example with roots /// Non-mutating version of `append(_:Component)`. - /// public __consuming func appending(_ other: __owned Component) -> FilePath { var copy = self copy.append(other) @@ -533,7 +519,6 @@ extension FilePath { // TODO(Windows docs): example with roots /// Non-mutating version of `append(_:C)`. - /// public __consuming func appending( _ components: __owned C ) -> FilePath where C.Element == FilePath.Component { @@ -544,7 +529,6 @@ extension FilePath { // TODO(Windows docs): example with roots /// Non-mutating version of `append(_:String)`. - /// public __consuming func appending(_ other: __owned String) -> FilePath { var copy = self copy.append(other) @@ -566,7 +550,6 @@ extension FilePath { /// var path: FilePath = "/tmp" /// path.push("dir/file.txt") // path is "/tmp/dir/file.txt" /// path.push("/bin") // path is "/bin" - /// public mutating func push(_ other: __owned FilePath) { defer { _invariantCheck() } guard other.root == nil else { @@ -579,7 +562,6 @@ extension FilePath { // TODO(Windows docs): examples and docs with roots /// Non-mutating version of `push()`. - /// public __consuming func pushing(_ other: __owned FilePath) -> FilePath { var copy = self copy.push(other) diff --git a/Sources/System/FilePath/FilePathWindows.swift b/Sources/System/FilePath/FilePathWindows.swift index d0656c35..b725dd17 100644 --- a/Sources/System/FilePath/FilePathWindows.swift +++ b/Sources/System/FilePath/FilePathWindows.swift @@ -156,7 +156,6 @@ internal struct WindowsRootInfo { /// * Traditional disk: `C:\`, `C:` /// * Device disk: `\\.\C:\`, `\\?\C:\` /// * UNC: `\\server\e:\`, `\\?\UNC\server\e:\` - /// case drive(Character) // TODO: GUID type? @@ -164,7 +163,6 @@ internal struct WindowsRootInfo { /// /// * UNC: `\\host\Volume{0000-...}\`, `\\.\UNC\host\Volume{0000-...}\` /// * Device roots: `\\.\Volume{0000-...}\`, `\\?\Volume{000-...}\` - /// case guid(String) // TODO: Legacy DOS devices, such as COM1? diff --git a/Tests/SystemTests/FilePathTests/FilePathExtras.swift b/Tests/SystemTests/FilePathTests/FilePathExtras.swift index 1f27cd84..efb31f3e 100644 --- a/Tests/SystemTests/FilePathTests/FilePathExtras.swift +++ b/Tests/SystemTests/FilePathTests/FilePathExtras.swift @@ -51,7 +51,6 @@ extension FilePath { /// Whether a lexically-normalized `self` contains a lexically-normalized /// `other`. - /// public func lexicallyContains(_ other: FilePath) -> Bool { guard !other.isEmpty else { return true } guard !isEmpty else { return false } From 12baba970667a991f2f563a7d38c742a28361b87 Mon Sep 17 00:00:00 2001 From: Daniel Loffgren Date: Fri, 10 Feb 2023 17:59:16 -0800 Subject: [PATCH 113/427] machPrecondition should have a leading underscore --- Sources/System/MachPort.swift | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/Sources/System/MachPort.swift b/Sources/System/MachPort.swift index 39fdd664..f6c10aac 100644 --- a/Sources/System/MachPort.swift +++ b/Sources/System/MachPort.swift @@ -13,7 +13,7 @@ import Darwin.Mach public protocol MachPortRight {} -private func machPrecondition( +private func _machPrecondition( file: StaticString = #file, line: UInt = #line, _ body: @autoclosure () -> kern_return_t @@ -50,7 +50,7 @@ public enum Mach { precondition(name != 0xFFFFFFFF /* MACH_PORT_DEAD */, "Receive rights cannot be dead names") let secret = mach_port_context_t(arc4random()) - machPrecondition(mach_port_guard(mach_task_self_, name, secret, 0)) + _machPrecondition(mach_port_guard(mach_task_self_, name, secret, 0)) self.context = secret } else { @@ -75,14 +75,14 @@ public enum Mach { deinit { if name == 0xFFFFFFFF /* MACH_PORT_DEAD */ { precondition(RightType.self != ReceiveRight.self, "Receive rights cannot be dead names") - machPrecondition(mach_port_mod_refs(mach_task_self_, name, MACH_PORT_RIGHT_DEAD_NAME, -1)) + _machPrecondition(mach_port_mod_refs(mach_task_self_, name, MACH_PORT_RIGHT_DEAD_NAME, -1)) } else { if RightType.self == ReceiveRight.self { - machPrecondition(mach_port_destruct(mach_task_self_, name, -1, context)) + _machPrecondition(mach_port_destruct(mach_task_self_, name, -1, context)) } else if RightType.self == SendRight.self { - machPrecondition(mach_port_mod_refs(mach_task_self_, name, MACH_PORT_RIGHT_SEND, -1)) + _machPrecondition(mach_port_mod_refs(mach_task_self_, name, MACH_PORT_RIGHT_SEND, -1)) } else if RightType.self == SendOnceRight.self { - machPrecondition(mach_port_mod_refs(mach_task_self_, name, MACH_PORT_RIGHT_SEND_ONCE, -1)) + _machPrecondition(mach_port_mod_refs(mach_task_self_, name, MACH_PORT_RIGHT_SEND_ONCE, -1)) } } } @@ -122,7 +122,7 @@ public enum Mach { var options = mach_port_options_t() options.flags = UInt32(MPO_INSERT_SEND_RIGHT); withUnsafeMutablePointer(to: &options) { options in - machPrecondition(mach_port_construct(mach_task_self_, options, secret, name)) + _machPrecondition(mach_port_construct(mach_task_self_, options, secret, name)) } } return (Mach.Port(name: name, context: secret), Mach.Port(name: name)) @@ -153,7 +153,7 @@ public extension Mach.Port where RightType == Mach.ReceiveRight { init() { var storage: mach_port_name_t = mach_port_name_t(MACH_PORT_NULL) withUnsafeMutablePointer(to: &storage) { storage in - machPrecondition(mach_port_allocate(mach_task_self_, MACH_PORT_RIGHT_RECEIVE, storage)) + _machPrecondition(mach_port_allocate(mach_task_self_, MACH_PORT_RIGHT_RECEIVE, storage)) } // name-only init will guard ReceiveRights @@ -190,7 +190,7 @@ public extension Mach.Port where RightType == Mach.ReceiveRight { /// Mach.ReceiveRights. Use relinquish() to avoid the syscall and extract /// the context value along with the port name. __consuming func unguardAndRelinquish() -> mach_port_name_t { - machPrecondition(mach_port_unguard(mach_task_self_, name, context)) + _machPrecondition(mach_port_unguard(mach_task_self_, name, context)) return name } @@ -220,7 +220,7 @@ public extension Mach.Port where RightType == Mach.ReceiveRight { withUnsafeMutablePointer(to: &newRight) { newRight in withUnsafeMutablePointer(to: &newRightType) { newRightType in - machPrecondition( + _machPrecondition( mach_port_extract_right(mach_task_self_, name, mach_msg_type_name_t(MACH_MSG_TYPE_MAKE_SEND_ONCE), @@ -246,7 +246,7 @@ public extension Mach.Port where RightType == Mach.ReceiveRight { let how = MACH_MSG_TYPE_MAKE_SEND // name is the same because send and recv rights are coalesced - machPrecondition(mach_port_insert_right(mach_task_self_, name, name, mach_msg_type_name_t(how))) + _machPrecondition(mach_port_insert_right(mach_task_self_, name, name, mach_msg_type_name_t(how))) return Mach.Port(name: name) } @@ -261,14 +261,14 @@ public extension Mach.Port where RightType == Mach.ReceiveRight { withUnsafeMutablePointer(to: &size) { size in withUnsafeMutablePointer(to: &status) { status in let info = UnsafeMutableRawPointer(status).bindMemory(to: integer_t.self, capacity: 1) - machPrecondition(mach_port_get_attributes(mach_task_self_, name, MACH_PORT_RECEIVE_STATUS, info, size)) + _machPrecondition(mach_port_get_attributes(mach_task_self_, name, MACH_PORT_RECEIVE_STATUS, info, size)) } } return status.mps_mscount } set { - machPrecondition(mach_port_set_mscount(mach_task_self_, name, newValue)) + _machPrecondition(mach_port_set_mscount(mach_task_self_, name, newValue)) } } } @@ -302,7 +302,7 @@ public extension Mach.Port where RightType == Mach.SendRight { if kr == KERN_INVALID_CAPABILITY { throw Mach.PortRightError.deadName } - machPrecondition(kr) + _machPrecondition(kr) return Mach.Port(name: name) } From 3750b96b2ec8bec4dc030ec4f8dcb9ea172ca3ab Mon Sep 17 00:00:00 2001 From: Daniel Loffgren Date: Fri, 10 Feb 2023 18:03:31 -0800 Subject: [PATCH 114/427] name and context should have leading underscores --- Sources/System/MachPort.swift | 52 +++++++++++++++++------------------ 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/Sources/System/MachPort.swift b/Sources/System/MachPort.swift index f6c10aac..ad555170 100644 --- a/Sources/System/MachPort.swift +++ b/Sources/System/MachPort.swift @@ -26,8 +26,8 @@ private func _machPrecondition( public enum Mach { @_moveOnly public struct Port { - internal var name: mach_port_name_t - internal var context: mach_port_context_t + internal var _name: mach_port_name_t + internal var _context: mach_port_context_t /// Transfer ownership of an existing unmanaged Mach port right into a /// `Mach.Port` by name. @@ -44,17 +44,17 @@ public enum Mach { /// This initializer makes a syscall to guard the right. public init(name: mach_port_name_t) { precondition(name != mach_port_name_t(MACH_PORT_NULL), "Mach.Port cannot be initialized with MACH_PORT_NULL") - self.name = name + self._name = name if RightType.self == ReceiveRight.self { precondition(name != 0xFFFFFFFF /* MACH_PORT_DEAD */, "Receive rights cannot be dead names") let secret = mach_port_context_t(arc4random()) _machPrecondition(mach_port_guard(mach_task_self_, name, secret, 0)) - self.context = secret + self._context = secret } else { - self.context = 0 + self._context = 0 } } @@ -69,20 +69,20 @@ public enum Mach { /// The body block may optionally return something, which will then be /// returned to the caller of withBorrowedName. public func withBorrowedName(body: (mach_port_name_t) -> ReturnType) -> ReturnType { - return body(name) + return body(_name) } deinit { - if name == 0xFFFFFFFF /* MACH_PORT_DEAD */ { + if _name == 0xFFFFFFFF /* MACH_PORT_DEAD */ { precondition(RightType.self != ReceiveRight.self, "Receive rights cannot be dead names") - _machPrecondition(mach_port_mod_refs(mach_task_self_, name, MACH_PORT_RIGHT_DEAD_NAME, -1)) + _machPrecondition(mach_port_mod_refs(mach_task_self_, _name, MACH_PORT_RIGHT_DEAD_NAME, -1)) } else { if RightType.self == ReceiveRight.self { - _machPrecondition(mach_port_destruct(mach_task_self_, name, -1, context)) + _machPrecondition(mach_port_destruct(mach_task_self_, _name, -1, _context)) } else if RightType.self == SendRight.self { - _machPrecondition(mach_port_mod_refs(mach_task_self_, name, MACH_PORT_RIGHT_SEND, -1)) + _machPrecondition(mach_port_mod_refs(mach_task_self_, _name, MACH_PORT_RIGHT_SEND, -1)) } else if RightType.self == SendOnceRight.self { - _machPrecondition(mach_port_mod_refs(mach_task_self_, name, MACH_PORT_RIGHT_SEND_ONCE, -1)) + _machPrecondition(mach_port_mod_refs(mach_task_self_, _name, MACH_PORT_RIGHT_SEND_ONCE, -1)) } } } @@ -141,8 +141,8 @@ public extension Mach.Port where RightType == Mach.ReceiveRight { /// The underlying port right will be automatically deallocated when /// the Mach.Port object is destroyed. init(name: mach_port_name_t, context: mach_port_context_t) { - self.name = name - self.context = context + self._name = name + self._context = context } /// Allocate a new Mach port with a receive right, creating a @@ -172,7 +172,7 @@ public extension Mach.Port where RightType == Mach.ReceiveRight { /// After this function completes, the Mach.Port is destroyed and no longer /// usable. __consuming func relinquish() -> (name: mach_port_name_t, context: mach_port_context_t) { - return (name: name, context: context) + return (name: _name, context: _context) } /// Remove guard and transfer ownership of the underlying port right to @@ -190,8 +190,8 @@ public extension Mach.Port where RightType == Mach.ReceiveRight { /// Mach.ReceiveRights. Use relinquish() to avoid the syscall and extract /// the context value along with the port name. __consuming func unguardAndRelinquish() -> mach_port_name_t { - _machPrecondition(mach_port_unguard(mach_task_self_, name, context)) - return name + _machPrecondition(mach_port_unguard(mach_task_self_, _name, _context)) + return _name } /// Borrow access to the port name in a block that can perform @@ -204,7 +204,7 @@ public extension Mach.Port where RightType == Mach.ReceiveRight { /// The body block may optionally return something, which will then be /// returned to the caller of withBorrowedName. func withBorrowedName(body: (mach_port_name_t, mach_port_context_t) -> ReturnType) -> ReturnType { - body(name, context) + body(_name, _context) } /// Create a send-once right for a given receive right. @@ -222,7 +222,7 @@ public extension Mach.Port where RightType == Mach.ReceiveRight { withUnsafeMutablePointer(to: &newRightType) { newRightType in _machPrecondition( mach_port_extract_right(mach_task_self_, - name, + _name, mach_msg_type_name_t(MACH_MSG_TYPE_MAKE_SEND_ONCE), newRight, newRightType) @@ -246,9 +246,9 @@ public extension Mach.Port where RightType == Mach.ReceiveRight { let how = MACH_MSG_TYPE_MAKE_SEND // name is the same because send and recv rights are coalesced - _machPrecondition(mach_port_insert_right(mach_task_self_, name, name, mach_msg_type_name_t(how))) + _machPrecondition(mach_port_insert_right(mach_task_self_, _name, _name, mach_msg_type_name_t(how))) - return Mach.Port(name: name) + return Mach.Port(name: _name) } /// Access the make-send count. @@ -261,14 +261,14 @@ public extension Mach.Port where RightType == Mach.ReceiveRight { withUnsafeMutablePointer(to: &size) { size in withUnsafeMutablePointer(to: &status) { status in let info = UnsafeMutableRawPointer(status).bindMemory(to: integer_t.self, capacity: 1) - _machPrecondition(mach_port_get_attributes(mach_task_self_, name, MACH_PORT_RECEIVE_STATUS, info, size)) + _machPrecondition(mach_port_get_attributes(mach_task_self_, _name, MACH_PORT_RECEIVE_STATUS, info, size)) } } return status.mps_mscount } set { - _machPrecondition(mach_port_set_mscount(mach_task_self_, name, newValue)) + _machPrecondition(mach_port_set_mscount(mach_task_self_, _name, newValue)) } } } @@ -284,7 +284,7 @@ public extension Mach.Port where RightType == Mach.SendRight { /// After this function completes, the Mach.Port is destroyed and no longer /// usable. __consuming func relinquish() -> mach_port_name_t { - return name + return _name } /// Create another send right from a given send right. @@ -298,13 +298,13 @@ public extension Mach.Port where RightType == Mach.SendRight { let how = MACH_MSG_TYPE_COPY_SEND // name is the same because send rights are coalesced - let kr = mach_port_insert_right(mach_task_self_, name, name, mach_msg_type_name_t(how)) + let kr = mach_port_insert_right(mach_task_self_, _name, _name, mach_msg_type_name_t(how)) if kr == KERN_INVALID_CAPABILITY { throw Mach.PortRightError.deadName } _machPrecondition(kr) - return Mach.Port(name: name) + return Mach.Port(name: _name) } } @@ -320,7 +320,7 @@ public extension Mach.Port where RightType == Mach.SendOnceRight { /// After this function completes, the Mach.Port is destroyed and no longer /// usable. __consuming func relinquish() -> mach_port_name_t { - return name + return _name } } From 13bf719c29fabaaf4dfe18a5c4dadad9806dd1f2 Mon Sep 17 00:00:00 2001 From: Daniel Loffgren Date: Fri, 10 Feb 2023 18:04:55 -0800 Subject: [PATCH 115/427] Freeze the Mach namespace, as it should only ever be used as a namespace --- Sources/System/MachPort.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Sources/System/MachPort.swift b/Sources/System/MachPort.swift index ad555170..cbd65a61 100644 --- a/Sources/System/MachPort.swift +++ b/Sources/System/MachPort.swift @@ -23,6 +23,7 @@ private func _machPrecondition( precondition(kr == expected, file: file, line: line) } +@frozen public enum Mach { @_moveOnly public struct Port { From 7c11f19259d1e56bd9cd9fb7b0b13082fac59214 Mon Sep 17 00:00:00 2001 From: Daniel Loffgren Date: Mon, 13 Feb 2023 17:53:03 -0800 Subject: [PATCH 116/427] Add System 1.3.0 to expand-availability.py --- Utilities/expand-availability.py | 1 + 1 file changed, 1 insertion(+) diff --git a/Utilities/expand-availability.py b/Utilities/expand-availability.py index 63190802..efb13b34 100755 --- a/Utilities/expand-availability.py +++ b/Utilities/expand-availability.py @@ -48,6 +48,7 @@ "System 0.0.2": "macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0", "System 1.1.0": "macOS 9999, iOS 9999, watchOS 9999, tvOS 9999", "System 1.2.0": "macOS 9999, iOS 9999, watchOS 9999, tvOS 9999", + "System 1.3.0": "macOS 9999, iOS 9999, watchOS 9999, tvOS 9999", } parser = argparse.ArgumentParser(description="Expand availability macros.") From 16a277564503ba390ae57ceb8728cf96c114fbfb Mon Sep 17 00:00:00 2001 From: Daniel Loffgren Date: Mon, 13 Feb 2023 17:53:42 -0800 Subject: [PATCH 117/427] Annotate entire Mach namespace as being available in System 1.3.0 --- Sources/System/MachPort.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Sources/System/MachPort.swift b/Sources/System/MachPort.swift index cbd65a61..6dd55fe0 100644 --- a/Sources/System/MachPort.swift +++ b/Sources/System/MachPort.swift @@ -23,6 +23,7 @@ private func _machPrecondition( precondition(kr == expected, file: file, line: line) } +/*System 1.3.0, @available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *)*/ @frozen public enum Mach { @_moveOnly From 0fb71dd6adcee7e956c50df83a193de42704e788 Mon Sep 17 00:00:00 2001 From: Daniel Loffgren Date: Mon, 13 Feb 2023 23:53:06 -0800 Subject: [PATCH 118/427] Fit all function signatures within 80 cols --- Sources/System/MachPort.swift | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/Sources/System/MachPort.swift b/Sources/System/MachPort.swift index 6dd55fe0..07fdd7a6 100644 --- a/Sources/System/MachPort.swift +++ b/Sources/System/MachPort.swift @@ -70,7 +70,9 @@ public enum Mach { /// /// The body block may optionally return something, which will then be /// returned to the caller of withBorrowedName. - public func withBorrowedName(body: (mach_port_name_t) -> ReturnType) -> ReturnType { + public func withBorrowedName( + body: (mach_port_name_t) -> ReturnType + ) -> ReturnType { return body(_name) } @@ -117,7 +119,8 @@ public enum Mach { /// /// This function will abort if the rights could not be created. /// Callers may assert that valid rights are always returned. - public static func allocatePortRightPair() -> (receive: Mach.Port, send: Mach.Port) { + public static func allocatePortRightPair() -> + (receive: Mach.Port, send: Mach.Port) { var name = mach_port_name_t(MACH_PORT_NULL) let secret = mach_port_context_t(arc4random()) withUnsafeMutablePointer(to: &name) { name in @@ -173,7 +176,8 @@ public extension Mach.Port where RightType == Mach.ReceiveRight { /// /// After this function completes, the Mach.Port is destroyed and no longer /// usable. - __consuming func relinquish() -> (name: mach_port_name_t, context: mach_port_context_t) { + __consuming func relinquish() -> + (name: mach_port_name_t, context: mach_port_context_t) { return (name: _name, context: _context) } @@ -205,7 +209,9 @@ public extension Mach.Port where RightType == Mach.ReceiveRight { /// /// The body block may optionally return something, which will then be /// returned to the caller of withBorrowedName. - func withBorrowedName(body: (mach_port_name_t, mach_port_context_t) -> ReturnType) -> ReturnType { + func withBorrowedName( + body: (mach_port_name_t, mach_port_context_t) -> ReturnType + ) -> ReturnType { body(_name, _context) } From 16c7885387866ef14124549ef1b989e0b80b106d Mon Sep 17 00:00:00 2001 From: Daniel Loffgren Date: Mon, 20 Feb 2023 14:45:17 -0800 Subject: [PATCH 119/427] Move public annotations from extensions down to each function explicitly --- Sources/System/MachPort.swift | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/Sources/System/MachPort.swift b/Sources/System/MachPort.swift index 07fdd7a6..13af7216 100644 --- a/Sources/System/MachPort.swift +++ b/Sources/System/MachPort.swift @@ -134,7 +134,7 @@ public enum Mach { } } -public extension Mach.Port where RightType == Mach.ReceiveRight { +extension Mach.Port where RightType == Mach.ReceiveRight { /// Transfer ownership of an existing, unmanaged, but already guarded, /// Mach port right into a Mach.Port by name. /// @@ -145,7 +145,7 @@ public extension Mach.Port where RightType == Mach.ReceiveRight { /// /// The underlying port right will be automatically deallocated when /// the Mach.Port object is destroyed. - init(name: mach_port_name_t, context: mach_port_context_t) { + public init(name: mach_port_name_t, context: mach_port_context_t) { self._name = name self._context = context } @@ -155,7 +155,7 @@ public extension Mach.Port where RightType == Mach.ReceiveRight { /// /// This initializer will abort if the right could not be created. /// Callers may assert that a valid right is always returned. - init() { + public init() { var storage: mach_port_name_t = mach_port_name_t(MACH_PORT_NULL) withUnsafeMutablePointer(to: &storage) { storage in _machPrecondition(mach_port_allocate(mach_task_self_, MACH_PORT_RIGHT_RECEIVE, storage)) @@ -176,7 +176,7 @@ public extension Mach.Port where RightType == Mach.ReceiveRight { /// /// After this function completes, the Mach.Port is destroyed and no longer /// usable. - __consuming func relinquish() -> + public __consuming func relinquish() -> (name: mach_port_name_t, context: mach_port_context_t) { return (name: _name, context: _context) } @@ -195,7 +195,7 @@ public extension Mach.Port where RightType == Mach.ReceiveRight { /// This function makes a syscall to remove the guard from /// Mach.ReceiveRights. Use relinquish() to avoid the syscall and extract /// the context value along with the port name. - __consuming func unguardAndRelinquish() -> mach_port_name_t { + public __consuming func unguardAndRelinquish() -> mach_port_name_t { _machPrecondition(mach_port_unguard(mach_task_self_, _name, _context)) return _name } @@ -209,7 +209,7 @@ public extension Mach.Port where RightType == Mach.ReceiveRight { /// /// The body block may optionally return something, which will then be /// returned to the caller of withBorrowedName. - func withBorrowedName( + public func withBorrowedName( body: (mach_port_name_t, mach_port_context_t) -> ReturnType ) -> ReturnType { body(_name, _context) @@ -221,7 +221,7 @@ public extension Mach.Port where RightType == Mach.ReceiveRight { /// /// This function will abort if the right could not be created. /// Callers may assert that a valid right is always returned. - func makeSendOnceRight() -> Mach.Port { + public func makeSendOnceRight() -> Mach.Port { // send once rights do not coalesce var newRight: mach_port_name_t = mach_port_name_t(MACH_PORT_NULL) var newRightType: mach_port_type_t = MACH_PORT_TYPE_NONE @@ -250,7 +250,7 @@ public extension Mach.Port where RightType == Mach.ReceiveRight { /// /// This function will abort if the right could not be created. /// Callers may assert that a valid right is always returned. - func makeSendRight() -> Mach.Port { + public func makeSendRight() -> Mach.Port { let how = MACH_MSG_TYPE_MAKE_SEND // name is the same because send and recv rights are coalesced @@ -262,7 +262,7 @@ public extension Mach.Port where RightType == Mach.ReceiveRight { /// Access the make-send count. /// /// Each get/set of this property makes a syscall. - var makeSendCount : mach_port_mscount_t { + public var makeSendCount : mach_port_mscount_t { get { var status: mach_port_status = mach_port_status() var size: mach_msg_type_number_t = mach_msg_type_number_t(MemoryLayout.size / MemoryLayout.size) @@ -281,7 +281,7 @@ public extension Mach.Port where RightType == Mach.ReceiveRight { } } -public extension Mach.Port where RightType == Mach.SendRight { +extension Mach.Port where RightType == Mach.SendRight { /// Transfer ownership of the underlying port right to the caller. /// /// Returns the Mach port name representing the right. @@ -291,7 +291,7 @@ public extension Mach.Port where RightType == Mach.SendRight { /// /// After this function completes, the Mach.Port is destroyed and no longer /// usable. - __consuming func relinquish() -> mach_port_name_t { + public __consuming func relinquish() -> mach_port_name_t { return _name } @@ -302,7 +302,7 @@ public extension Mach.Port where RightType == Mach.SendRight { /// If the send right being copied has become a dead name, meaning the /// receiving side has been deallocated, then copySendRight() will throw /// a Mach.PortRightError.deadName error. - func copySendRight() throws -> Mach.Port { + public func copySendRight() throws -> Mach.Port { let how = MACH_MSG_TYPE_COPY_SEND // name is the same because send rights are coalesced @@ -317,7 +317,7 @@ public extension Mach.Port where RightType == Mach.SendRight { } -public extension Mach.Port where RightType == Mach.SendOnceRight { +extension Mach.Port where RightType == Mach.SendOnceRight { /// Transfer ownership of the underlying port right to the caller. /// /// Returns the Mach port name representing the right. @@ -327,7 +327,7 @@ public extension Mach.Port where RightType == Mach.SendOnceRight { /// /// After this function completes, the Mach.Port is destroyed and no longer /// usable. - __consuming func relinquish() -> mach_port_name_t { + public __consuming func relinquish() -> mach_port_name_t { return _name } } From e708e4addafcb09f587df8f7fef253668d223341 Mon Sep 17 00:00:00 2001 From: Karoy Lorentey Date: Tue, 28 Feb 2023 16:44:43 -0800 Subject: [PATCH 120/427] Mach: Avoid non-inlinable generics where possible MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Leave Ports as non-frozen for now, but expose its two stored properties. - Make most members inlinable. The core initializers and the deinitializer must remain opaque for now. - Mark the empty `MachPortRight` types as `@frozen` — these are markers, not carrying any data. rdar://106057781 --- Sources/System/MachPort.swift | 28 +++++++++++++++++++++++----- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/Sources/System/MachPort.swift b/Sources/System/MachPort.swift index 13af7216..b366dcfc 100644 --- a/Sources/System/MachPort.swift +++ b/Sources/System/MachPort.swift @@ -13,7 +13,8 @@ import Darwin.Mach public protocol MachPortRight {} -private func _machPrecondition( +@inlinable +internal func _machPrecondition( file: StaticString = #file, line: UInt = #line, _ body: @autoclosure () -> kern_return_t @@ -28,7 +29,10 @@ private func _machPrecondition( public enum Mach { @_moveOnly public struct Port { + @usableFromInline internal var _name: mach_port_name_t + + @usableFromInline internal var _context: mach_port_context_t /// Transfer ownership of an existing unmanaged Mach port right into a @@ -70,6 +74,7 @@ public enum Mach { /// /// The body block may optionally return something, which will then be /// returned to the caller of withBorrowedName. + @inlinable public func withBorrowedName( body: (mach_port_name_t) -> ReturnType ) -> ReturnType { @@ -101,9 +106,11 @@ public enum Mach { } /// The MachPortRight type used to manage a receive right. + @frozen public struct ReceiveRight: MachPortRight {} /// The MachPortRight type used to manage a send right. + @frozen public struct SendRight: MachPortRight {} /// The MachPortRight type used to manage a send-once right. @@ -113,6 +120,7 @@ public enum Mach { /// /// Upon destruction a send-once notification will be sent to the /// receiving end. + @frozen public struct SendOnceRight: MachPortRight {} /// Create a connected pair of rights, one receive, and one send. @@ -155,6 +163,7 @@ extension Mach.Port where RightType == Mach.ReceiveRight { /// /// This initializer will abort if the right could not be created. /// Callers may assert that a valid right is always returned. + @inlinable public init() { var storage: mach_port_name_t = mach_port_name_t(MACH_PORT_NULL) withUnsafeMutablePointer(to: &storage) { storage in @@ -176,8 +185,9 @@ extension Mach.Port where RightType == Mach.ReceiveRight { /// /// After this function completes, the Mach.Port is destroyed and no longer /// usable. - public __consuming func relinquish() -> - (name: mach_port_name_t, context: mach_port_context_t) { + @inlinable + public __consuming func relinquish( + ) -> (name: mach_port_name_t, context: mach_port_context_t) { return (name: _name, context: _context) } @@ -195,6 +205,7 @@ extension Mach.Port where RightType == Mach.ReceiveRight { /// This function makes a syscall to remove the guard from /// Mach.ReceiveRights. Use relinquish() to avoid the syscall and extract /// the context value along with the port name. + @inlinable public __consuming func unguardAndRelinquish() -> mach_port_name_t { _machPrecondition(mach_port_unguard(mach_task_self_, _name, _context)) return _name @@ -209,10 +220,11 @@ extension Mach.Port where RightType == Mach.ReceiveRight { /// /// The body block may optionally return something, which will then be /// returned to the caller of withBorrowedName. + @inlinable public func withBorrowedName( body: (mach_port_name_t, mach_port_context_t) -> ReturnType ) -> ReturnType { - body(_name, _context) + return body(_name, _context) } /// Create a send-once right for a given receive right. @@ -221,6 +233,7 @@ extension Mach.Port where RightType == Mach.ReceiveRight { /// /// This function will abort if the right could not be created. /// Callers may assert that a valid right is always returned. + @inlinable public func makeSendOnceRight() -> Mach.Port { // send once rights do not coalesce var newRight: mach_port_name_t = mach_port_name_t(MACH_PORT_NULL) @@ -250,6 +263,7 @@ extension Mach.Port where RightType == Mach.ReceiveRight { /// /// This function will abort if the right could not be created. /// Callers may assert that a valid right is always returned. + @inlinable public func makeSendRight() -> Mach.Port { let how = MACH_MSG_TYPE_MAKE_SEND @@ -262,7 +276,8 @@ extension Mach.Port where RightType == Mach.ReceiveRight { /// Access the make-send count. /// /// Each get/set of this property makes a syscall. - public var makeSendCount : mach_port_mscount_t { + @inlinable + public var makeSendCount: mach_port_mscount_t { get { var status: mach_port_status = mach_port_status() var size: mach_msg_type_number_t = mach_msg_type_number_t(MemoryLayout.size / MemoryLayout.size) @@ -291,6 +306,7 @@ extension Mach.Port where RightType == Mach.SendRight { /// /// After this function completes, the Mach.Port is destroyed and no longer /// usable. + @inlinable public __consuming func relinquish() -> mach_port_name_t { return _name } @@ -302,6 +318,7 @@ extension Mach.Port where RightType == Mach.SendRight { /// If the send right being copied has become a dead name, meaning the /// receiving side has been deallocated, then copySendRight() will throw /// a Mach.PortRightError.deadName error. + @inlinable public func copySendRight() throws -> Mach.Port { let how = MACH_MSG_TYPE_COPY_SEND @@ -327,6 +344,7 @@ extension Mach.Port where RightType == Mach.SendOnceRight { /// /// After this function completes, the Mach.Port is destroyed and no longer /// usable. + @inlinable public __consuming func relinquish() -> mach_port_name_t { return _name } From cee9335aebf9ef43bd822c80658843e9f55a5b5c Mon Sep 17 00:00:00 2001 From: Karoy Lorentey Date: Wed, 1 Mar 2023 16:29:39 -0800 Subject: [PATCH 121/427] Update gitignore --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 2196fff3..2c0658b9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,8 @@ .DS_Store +/.swiftpm /.build /Packages swift-system.xcodeproj xcuserdata/ .*.sw? +.docc-build From e86702b34a4c86198a608543d60f2b5a77bce422 Mon Sep 17 00:00:00 2001 From: Karoy Lorentey Date: Wed, 1 Mar 2023 16:26:59 -0800 Subject: [PATCH 122/427] Fix expand-availability syntax not to interfere with doc comment parsing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This was our previous convention: /// Fiddle with the thing. /*System 0.0.2*/ public func foo() /// Fiddle with the thing. /*System 0.0.2, @available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *)*/ public func foo() /// Fiddle with the thing. /*System 0.0.2*/@available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *) public func foo() Unfortunately, regular comments in between a declaration and its preceding doc comments interfere with documentation tools. Change things to the style below instead: /// Fiddle with the thing. @available(/*System 0.0.2*/macOS 10, *) public func foo() /// Fiddle with the thing. @available(/*SwiftSystem 0.0.2: macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0*/macOS 10, *) public func foo() /// Fiddle with the thing. @available(/*SwiftSystem 0.0.2*/macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *) public func foo() Unfortunately, `@available(*)` isn’t valid syntax, so we have to add a dummy “macOS 10” version spec. It should have no actual effect. --- Utilities/expand-availability.py | 59 ++++++++++++++++++++++++-------- 1 file changed, 44 insertions(+), 15 deletions(-) diff --git a/Utilities/expand-availability.py b/Utilities/expand-availability.py index efb13b34..5bb1878c 100755 --- a/Utilities/expand-availability.py +++ b/Utilities/expand-availability.py @@ -6,11 +6,18 @@ # In order for this to work, ABI-impacting declarations need to be annotated # with special comments in the following format: # -# /*System 0.0.2*/ +# @available(/*System 0.0.2*/iOS 8, *) # public func greeting() -> String { # "Hello" # } # +# (The iOS 8 availability is a dummy no-op declaration -- it only has to be there because +# `@available(*)` isn't valid syntax, and commenting out the entire `@available` attribute +# would interfere with parser tools for doc comments. `iOS 8` is the shortest version string that +# matches the minimum possible deployment target for Swift code, so we use that as our dummy +# availability version. `@available(iOS 8, *)` is functionally equivalent to not having an +# `@available` attribute at all.) +# # The script adds full availability incantations to these comments. It can run # in one of two modes: # @@ -19,16 +26,16 @@ # availability across `SystemPackage` and the ABI-stable `System` module that # ships in Apple's OS releases: # -# /*System 0.0.2, @available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *)*/ +# @available(/*System 0.0.2: macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0*/iOS 8, *) # public func greeting() -> String { # "Hello" # } # -# `expand-availability.py --attributes` adds actual availability attributes. +# `expand-availability.py --attributes` adds actual availability declarations. # This is used by maintainers to build ABI stable releases of System on Apple's # platforms: # -# /*System 0.0.2*/@available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *) +# @available(/*System 0.0.2: */macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *) # public func greeting() -> String { # "Hello" # } @@ -65,22 +72,44 @@ def swift_sources_in(path): result.append(os.path.join(dir, file)) return result -macro_pattern = re.compile( +# Old-style syntax: +# /*System 0.0.2*/ +# /*System 0.0.2, @available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *)*/ +# /*System 0.0.2*/@available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *) +old_macro_pattern = re.compile( r"/\*(System [^ *]+)(, @available\([^)]*\))?\*/(@available\([^)]*\))?") +# New-style comments: +# @available(/*SwiftSystem 0.0.2*/macOS 10, *) +# @available(/*SwiftSystem 0.0.2: macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0*/iOS 8, *) +# @available(/*SwiftSystem 0.0.2*/macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *) +# +# These do not interfere with our tools' ability to find doc comments. +macro_pattern = re.compile( + r"@available\(/\*(System [^ *:]+)[^*/)]*\*/([^)]*)\*\)") + +def available_attribute(filename, lineno, symbolic_version): + expansion = versions[symbolic_version] + if expansion is None: + raise ValueError("{0}:{1}: error: Unknown System version '{0}'" + .format(fileinput.filename(), fileinput.lineno(), symbolic_version)) + if args.attributes: + attribute = "@available(/*{0}*/{1}, *)".format(symbolic_version, expansion) + else: + # Sadly `@available(*)` is not valid syntax, so we have to mention at least one actual + # platform here. + attribute = "@available(/*{0}: {1}*/iOS 8, *)".format(symbolic_version, expansion) + return attribute + + sources = swift_sources_in("Sources") + swift_sources_in("Tests") for line in fileinput.input(files=sources, inplace=True): match = re.search(macro_pattern, line) + if match is None: + match = re.search(old_macro_pattern, line) if match: - system_version = match.group(1) - expansion = versions[system_version] - if expansion is None: - raise ValueError("{0}:{1}: error: Unknown System version '{0}'" - .format(fileinput.filename(), fileinput.lineno(), - system_version)) - if args.attributes: - replacement = "/*{0}*/@available({1}, *)".format(system_version, expansion) - else: - replacement = "/*{0}, @available({1}, *)*/".format(system_version, expansion) + symbolic_version = match.group(1) + replacement = available_attribute( + fileinput.filename(), fileinput.lineno(), symbolic_version) line = line[:match.start()] + replacement + line[match.end():] print(line, end="") From bb6ab43e4421b01b6052a9183d797890c22c4f6e Mon Sep 17 00:00:00 2001 From: Karoy Lorentey Date: Wed, 1 Mar 2023 16:28:35 -0800 Subject: [PATCH 123/427] Update availability declarations --- Sources/System/Errno.swift | 12 +++---- Sources/System/FileDescriptor.swift | 12 +++---- Sources/System/FileHelpers.swift | 2 +- Sources/System/FileOperations.swift | 20 +++++------ Sources/System/FilePath/FilePath.swift | 10 +++--- .../FilePath/FilePathComponentView.swift | 12 +++---- .../System/FilePath/FilePathComponents.swift | 22 ++++++------ Sources/System/FilePath/FilePathParsing.swift | 16 ++++----- Sources/System/FilePath/FilePathString.swift | 34 +++++++++---------- Sources/System/FilePath/FilePathSyntax.swift | 16 ++++----- Sources/System/FilePermissions.swift | 4 +-- Sources/System/Internals/CInterop.swift | 4 +-- Sources/System/MachPort.swift | 2 +- Sources/System/PlatformString.swift | 6 ++-- Sources/System/Util.swift | 8 ++--- Tests/SystemTests/ErrnoTest.swift | 2 +- Tests/SystemTests/FileOperationsTest.swift | 2 +- .../FilePathComponentsTest.swift | 4 +-- .../FilePathTests/FilePathExtras.swift | 4 +-- .../FilePathTests/FilePathParsingTest.swift | 2 +- .../FilePathTests/FilePathSyntaxTest.swift | 6 ++-- .../FilePathTests/FilePathTest.swift | 4 +-- Tests/SystemTests/FileTypesTest.swift | 4 +-- Tests/SystemTests/MockingTest.swift | 2 +- Utilities/expand-availability.py | 17 +++++----- 25 files changed, 114 insertions(+), 113 deletions(-) diff --git a/Sources/System/Errno.swift b/Sources/System/Errno.swift index 9b0cf886..d55e52bf 100644 --- a/Sources/System/Errno.swift +++ b/Sources/System/Errno.swift @@ -10,7 +10,7 @@ /// An error number used by system calls to communicate what kind of error /// occurred. @frozen -/*System 0.0.1, @available(macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0, *)*/ +@available(/*System 0.0.1: macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0*/iOS 8, *) public struct Errno: RawRepresentable, Error, Hashable, Codable { /// The raw C error number. @_alwaysEmitIntoClient @@ -1380,7 +1380,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { } // Constants defined in header but not man page -/*System 0.0.1, @available(macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0, *)*/ +@available(/*System 0.0.1: macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0*/iOS 8, *) extension Errno { /// Operation would block. @@ -1474,7 +1474,7 @@ extension Errno { #endif } -/*System 0.0.1, @available(macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0, *)*/ +@available(/*System 0.0.1: macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0*/iOS 8, *) extension Errno { // TODO: We want to provide safe access to `errno`, but we need a // release-barrier to do so. @@ -1489,14 +1489,14 @@ extension Errno { } // Use "hidden" entry points for `NSError` bridging -/*System 0.0.1, @available(macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0, *)*/ +@available(/*System 0.0.1: macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0*/iOS 8, *) extension Errno { public var _code: Int { Int(rawValue) } public var _domain: String { "NSPOSIXErrorDomain" } } -/*System 0.0.1, @available(macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0, *)*/ +@available(/*System 0.0.1: macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0*/iOS 8, *) extension Errno: CustomStringConvertible, CustomDebugStringConvertible { /// A textual representation of the most recent error /// returned by a system call. @@ -1516,7 +1516,7 @@ extension Errno: CustomStringConvertible, CustomDebugStringConvertible { public var debugDescription: String { self.description } } -/*System 0.0.1, @available(macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0, *)*/ +@available(/*System 0.0.1: macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0*/iOS 8, *) extension Errno { @_alwaysEmitIntoClient public static func ~=(_ lhs: Errno, _ rhs: Error) -> Bool { diff --git a/Sources/System/FileDescriptor.swift b/Sources/System/FileDescriptor.swift index c870b65f..292c2a51 100644 --- a/Sources/System/FileDescriptor.swift +++ b/Sources/System/FileDescriptor.swift @@ -14,7 +14,7 @@ /// of `FileDescriptor` values, /// in the same way as you manage a raw C file handle. @frozen -/*System 0.0.1, @available(macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0, *)*/ +@available(/*System 0.0.1: macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0*/iOS 8, *) public struct FileDescriptor: RawRepresentable, Hashable, Codable { /// The raw C file handle. @_alwaysEmitIntoClient @@ -26,7 +26,7 @@ public struct FileDescriptor: RawRepresentable, Hashable, Codable { } // Standard file descriptors. -/*System 0.0.1, @available(macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0, *)*/ +@available(/*System 0.0.1: macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0*/iOS 8, *) extension FileDescriptor { /// The standard input file descriptor, with a numeric value of 0. @_alwaysEmitIntoClient @@ -41,7 +41,7 @@ extension FileDescriptor { public static var standardError: FileDescriptor { .init(rawValue: 2) } } -/*System 0.0.1, @available(macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0, *)*/ +@available(/*System 0.0.1: macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0*/iOS 8, *) extension FileDescriptor { /// The desired read and write access for a newly opened file. @frozen @@ -386,7 +386,7 @@ extension FileDescriptor { } } -/*System 0.0.1, @available(macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0, *)*/ +@available(/*System 0.0.1: macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0*/iOS 8, *) extension FileDescriptor.AccessMode : CustomStringConvertible, CustomDebugStringConvertible { @@ -405,7 +405,7 @@ extension FileDescriptor.AccessMode public var debugDescription: String { self.description } } -/*System 0.0.1, @available(macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0, *)*/ +@available(/*System 0.0.1: macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0*/iOS 8, *) extension FileDescriptor.SeekOrigin : CustomStringConvertible, CustomDebugStringConvertible { @@ -428,7 +428,7 @@ extension FileDescriptor.SeekOrigin public var debugDescription: String { self.description } } -/*System 0.0.1, @available(macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0, *)*/ +@available(/*System 0.0.1: macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0*/iOS 8, *) extension FileDescriptor.OpenOptions : CustomStringConvertible, CustomDebugStringConvertible { diff --git a/Sources/System/FileHelpers.swift b/Sources/System/FileHelpers.swift index af7cedfe..2ddb0729 100644 --- a/Sources/System/FileHelpers.swift +++ b/Sources/System/FileHelpers.swift @@ -7,7 +7,7 @@ See https://swift.org/LICENSE.txt for license information */ -/*System 0.0.1, @available(macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0, *)*/ +@available(/*System 0.0.1: macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0*/iOS 8, *) extension FileDescriptor { /// Runs a closure and then closes the file descriptor, even if an error occurs. /// diff --git a/Sources/System/FileOperations.swift b/Sources/System/FileOperations.swift index 23eb9306..97a0c8e7 100644 --- a/Sources/System/FileOperations.swift +++ b/Sources/System/FileOperations.swift @@ -7,7 +7,7 @@ See https://swift.org/LICENSE.txt for license information */ -/*System 0.0.1, @available(macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0, *)*/ +@available(/*System 0.0.1: macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0*/iOS 8, *) extension FileDescriptor { /// Opens or creates a file for reading or writing. /// @@ -369,7 +369,7 @@ extension FileDescriptor { } } -/*System 0.0.2, @available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *)*/ +@available(/*System 0.0.2: macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0*/iOS 8, *) extension FileDescriptor { /// Duplicate this file descriptor and return the newly created copy. /// @@ -399,7 +399,7 @@ extension FileDescriptor { /// /// The corresponding C functions are `dup` and `dup2`. @_alwaysEmitIntoClient - /*System 0.0.2, @available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *)*/ + @available(/*System 0.0.2: macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0*/iOS 8, *) public func duplicate( as target: FileDescriptor? = nil, retryOnInterrupt: Bool = true @@ -407,7 +407,7 @@ extension FileDescriptor { try _duplicate(as: target, retryOnInterrupt: retryOnInterrupt).get() } - /*System 0.0.2, @available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *)*/ + @available(/*System 0.0.2: macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0*/iOS 8, *) @usableFromInline internal func _duplicate( as target: FileDescriptor?, @@ -435,7 +435,7 @@ extension FileDescriptor { } #if !os(Windows) -/*System 1.1.0, @available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *)*/ +@available(/*System 1.1.0: macOS 9999, iOS 9999, watchOS 9999, tvOS 9999*/iOS 8, *) extension FileDescriptor { /// Create a pipe, a unidirectional data channel which can be used for interprocess communication. /// @@ -443,12 +443,12 @@ extension FileDescriptor { /// /// The corresponding C function is `pipe`. @_alwaysEmitIntoClient - /*System 1.1.0, @available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *)*/ + @available(/*System 1.1.0: macOS 9999, iOS 9999, watchOS 9999, tvOS 9999*/iOS 8, *) public static func pipe() throws -> (readEnd: FileDescriptor, writeEnd: FileDescriptor) { try _pipe().get() } - /*System 1.1.0, @available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *)*/ + @available(/*System 1.1.0: macOS 9999, iOS 9999, watchOS 9999, tvOS 9999*/iOS 8, *) @usableFromInline internal static func _pipe() -> Result<(readEnd: FileDescriptor, writeEnd: FileDescriptor), Errno> { var fds: (Int32, Int32) = (-1, -1) @@ -464,7 +464,7 @@ extension FileDescriptor { #endif #if !os(Windows) -/*System 1.2.0, @available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *)*/ +@available(/*System 1.2.0: macOS 9999, iOS 9999, watchOS 9999, tvOS 9999*/iOS 8, *) extension FileDescriptor { /// Truncate or extend the file referenced by this file descriptor. /// @@ -486,7 +486,7 @@ extension FileDescriptor { /// associated with the file. /// /// The corresponding C function is `ftruncate`. - /*System 1.2.0, @available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *)*/ + @available(/*System 1.2.0: macOS 9999, iOS 9999, watchOS 9999, tvOS 9999*/iOS 8, *) @_alwaysEmitIntoClient public func resize( to newSize: Int64, @@ -498,7 +498,7 @@ extension FileDescriptor { ).get() } - /*System 1.2.0, @available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *)*/ + @available(/*System 1.2.0: macOS 9999, iOS 9999, watchOS 9999, tvOS 9999*/iOS 8, *) @usableFromInline internal func _resize( to newSize: Int64, diff --git a/Sources/System/FilePath/FilePath.swift b/Sources/System/FilePath/FilePath.swift index 20bb9af3..09b8142a 100644 --- a/Sources/System/FilePath/FilePath.swift +++ b/Sources/System/FilePath/FilePath.swift @@ -7,9 +7,6 @@ See https://swift.org/LICENSE.txt for license information */ -// TODO(docs): Section on all the new syntactic operations, lexical normalization, decomposition, -// components, etc. -/*System 0.0.1, @available(macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0, *)*/ /// Represents a location in the file system. /// /// This structure recognizes directory separators (e.g. `/`), roots, and @@ -40,7 +37,10 @@ /// However, the rules for path equivalence /// are file-system–specific and have additional considerations /// like case insensitivity, Unicode normalization, and symbolic links. +@available(/*System 0.0.1: macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0*/iOS 8, *) public struct FilePath { + // TODO(docs): Section on all the new syntactic operations, lexical normalization, decomposition, + // components, etc. internal var _storage: SystemString /// Creates an empty, null-terminated path. @@ -59,13 +59,13 @@ public struct FilePath { } } -/*System 0.0.1, @available(macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0, *)*/ +@available(/*System 0.0.1: macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0*/iOS 8, *) extension FilePath { /// The length of the file path, excluding the null terminator. public var length: Int { _storage.length } } -/*System 0.0.1, @available(macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0, *)*/ +@available(/*System 0.0.1: macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0*/iOS 8, *) extension FilePath: Hashable, Codable {} #if compiler(>=5.5) && canImport(_Concurrency) diff --git a/Sources/System/FilePath/FilePathComponentView.swift b/Sources/System/FilePath/FilePathComponentView.swift index dc64374f..157b2393 100644 --- a/Sources/System/FilePath/FilePathComponentView.swift +++ b/Sources/System/FilePath/FilePathComponentView.swift @@ -9,7 +9,7 @@ // MARK: - API -/*System 0.0.2, @available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *)*/ +@available(/*System 0.0.2: macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0*/iOS 8, *) extension FilePath { /// A bidirectional, range replaceable collection of the non-root components /// that make up a file path. @@ -88,7 +88,7 @@ extension FilePath { #endif } -/*System 0.0.2, @available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *)*/ +@available(/*System 0.0.2: macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0*/iOS 8, *) extension FilePath.ComponentView: BidirectionalCollection { public typealias Element = FilePath.Component public struct Index: Comparable, Hashable { @@ -122,7 +122,7 @@ extension FilePath.ComponentView: BidirectionalCollection { } } -/*System 0.0.2, @available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *)*/ +@available(/*System 0.0.2: macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0*/iOS 8, *) extension FilePath.ComponentView: RangeReplaceableCollection { public init() { self.init(FilePath()) @@ -172,7 +172,7 @@ extension FilePath.ComponentView: RangeReplaceableCollection { } } -/*System 0.0.2, @available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *)*/ +@available(/*System 0.0.2: macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0*/iOS 8, *) extension FilePath { /// Create a file path from a root and a collection of components. public init( @@ -201,7 +201,7 @@ extension FilePath { // MARK: - Internals -/*System 0.0.2, @available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *)*/ +@available(/*System 0.0.2: macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0*/iOS 8, *) extension FilePath.ComponentView: _PathSlice { internal var _range: Range { _start ..< _path._storage.endIndex @@ -214,7 +214,7 @@ extension FilePath.ComponentView: _PathSlice { // MARK: - Invariants -/*System 0.0.2, @available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *)*/ +@available(/*System 0.0.2: macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0*/iOS 8, *) extension FilePath.ComponentView { internal func _invariantCheck() { #if DEBUG diff --git a/Sources/System/FilePath/FilePathComponents.swift b/Sources/System/FilePath/FilePathComponents.swift index 5fc02679..782561c6 100644 --- a/Sources/System/FilePath/FilePathComponents.swift +++ b/Sources/System/FilePath/FilePathComponents.swift @@ -9,7 +9,7 @@ // MARK: - API -/*System 0.0.2, @available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *)*/ +@available(/*System 0.0.2: macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0*/iOS 8, *) extension FilePath { /// Represents a root of a file path. /// @@ -72,7 +72,7 @@ extension FilePath { } } -/*System 0.0.2, @available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *)*/ +@available(/*System 0.0.2: macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0*/iOS 8, *) extension FilePath.Component { /// Whether a component is a regular file or directory name, or a special @@ -97,7 +97,7 @@ extension FilePath.Component { } } -/*System 0.0.2, @available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *)*/ +@available(/*System 0.0.2: macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0*/iOS 8, *) extension FilePath.Root { // TODO: Windows analysis APIs } @@ -183,17 +183,17 @@ extension _PathSlice { internal var _storage: SystemString { _path._storage } } -/*System 0.0.2, @available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *)*/ +@available(/*System 0.0.2: macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0*/iOS 8, *) extension FilePath.Component: _PathSlice { } -/*System 0.0.2, @available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *)*/ +@available(/*System 0.0.2: macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0*/iOS 8, *) extension FilePath.Root: _PathSlice { internal var _range: Range { (..<_rootEnd).relative(to: _path._storage) } } -/*System 0.0.1, @available(macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0, *)*/ +@available(/*System 0.0.1: macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0*/iOS 8, *) extension FilePath: _PlatformStringable { func _withPlatformString(_ body: (UnsafePointer) throws -> Result) rethrows -> Result { try _storage.withPlatformString(body) @@ -205,7 +205,7 @@ extension FilePath: _PlatformStringable { } -/*System 0.0.2, @available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *)*/ +@available(/*System 0.0.2: macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0*/iOS 8, *) extension FilePath.Component { // The index of the `.` denoting an extension internal func _extensionIndex() -> SystemString.Index? { @@ -234,7 +234,7 @@ internal func _makeExtension(_ ext: String) -> SystemString { return result } -/*System 0.0.2, @available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *)*/ +@available(/*System 0.0.2: macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0*/iOS 8, *) extension FilePath.Component { internal init?(_ str: SystemString) { // FIXME: explicit null root? Or something else? @@ -247,7 +247,7 @@ extension FilePath.Component { } } -/*System 0.0.2, @available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *)*/ +@available(/*System 0.0.2: macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0*/iOS 8, *) extension FilePath.Root { internal init?(_ str: SystemString) { // FIXME: explicit null root? Or something else? @@ -262,7 +262,7 @@ extension FilePath.Root { // MARK: - Invariants -/*System 0.0.2, @available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *)*/ +@available(/*System 0.0.2: macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0*/iOS 8, *) extension FilePath.Component { // TODO: ensure this all gets easily optimized away in release... internal func _invariantCheck() { @@ -275,7 +275,7 @@ extension FilePath.Component { } } -/*System 0.0.2, @available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *)*/ +@available(/*System 0.0.2: macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0*/iOS 8, *) extension FilePath.Root { internal func _invariantCheck() { #if DEBUG diff --git a/Sources/System/FilePath/FilePathParsing.swift b/Sources/System/FilePath/FilePathParsing.swift index bbfd066f..6d014774 100644 --- a/Sources/System/FilePath/FilePathParsing.swift +++ b/Sources/System/FilePath/FilePathParsing.swift @@ -111,7 +111,7 @@ extension SystemString { } } -/*System 0.0.1, @available(macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0, *)*/ +@available(/*System 0.0.1: macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0*/iOS 8, *) extension FilePath { internal mutating func _removeTrailingSeparator() { _storage._removeTrailingSeparator() @@ -192,7 +192,7 @@ extension SystemString { } } -/*System 0.0.1, @available(macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0, *)*/ +@available(/*System 0.0.1: macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0*/iOS 8, *) extension FilePath { internal var _relativeStart: SystemString.Index { _storage._relativePathStart @@ -204,7 +204,7 @@ extension FilePath { // Parse separators -/*System 0.0.1, @available(macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0, *)*/ +@available(/*System 0.0.1: macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0*/iOS 8, *) extension FilePath { internal typealias _Index = SystemString.Index @@ -268,7 +268,7 @@ extension FilePath { } } -/*System 0.0.2, @available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *)*/ +@available(/*System 0.0.2: macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0*/iOS 8, *) extension FilePath.ComponentView { // TODO: Store this... internal var _relativeStart: SystemString.Index { @@ -293,7 +293,7 @@ extension SystemString { } } -/*System 0.0.2, @available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *)*/ +@available(/*System 0.0.2: macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0*/iOS 8, *) extension FilePath.Root { // Asserting self is a root, returns whether this is an // absolute root. @@ -318,7 +318,7 @@ extension FilePath.Root { } } -/*System 0.0.1, @available(macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0, *)*/ +@available(/*System 0.0.1: macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0*/iOS 8, *) extension FilePath { internal var _portableDescription: String { guard _windowsPaths else { return description } @@ -337,7 +337,7 @@ internal var _windowsPaths: Bool { #endif } -/*System 0.0.1, @available(macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0, *)*/ +@available(/*System 0.0.1: macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0*/iOS 8, *) extension FilePath { // Whether we should add a separator when doing an append internal var _needsSeparatorForAppend: Bool { @@ -365,7 +365,7 @@ extension FilePath { } // MARK: - Invariants -/*System 0.0.1, @available(macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0, *)*/ +@available(/*System 0.0.1: macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0*/iOS 8, *) extension FilePath { internal func _invariantCheck() { #if DEBUG diff --git a/Sources/System/FilePath/FilePathString.swift b/Sources/System/FilePath/FilePathString.swift index 8626b600..4fe9c1fd 100644 --- a/Sources/System/FilePath/FilePathString.swift +++ b/Sources/System/FilePath/FilePathString.swift @@ -9,7 +9,7 @@ // MARK: - Platform string -/*System 0.0.2, @available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *)*/ +@available(/*System 0.0.2: macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0*/iOS 8, *) extension FilePath { /// Creates a file path by copying bytes from a null-terminated platform /// string. @@ -109,7 +109,7 @@ extension FilePath { #endif } -/*System 0.0.2, @available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *)*/ +@available(/*System 0.0.2: macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0*/iOS 8, *) extension FilePath.Component { /// Creates a file path component by copying bytes from a null-terminated /// platform string. @@ -198,7 +198,7 @@ extension FilePath.Component { } } -/*System 0.0.2, @available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *)*/ +@available(/*System 0.0.2: macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0*/iOS 8, *) extension FilePath.Root { /// Creates a file path root by copying bytes from a null-terminated platform /// string. @@ -287,7 +287,7 @@ extension FilePath.Root { // MARK: - String literals -/*System 0.0.1, @available(macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0, *)*/ +@available(/*System 0.0.1: macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0*/iOS 8, *) extension FilePath: ExpressibleByStringLiteral { /// Creates a file path from a string literal. /// @@ -306,7 +306,7 @@ extension FilePath: ExpressibleByStringLiteral { } } -/*System 0.0.2, @available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *)*/ +@available(/*System 0.0.2: macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0*/iOS 8, *) extension FilePath.Component: ExpressibleByStringLiteral { /// Create a file path component from a string literal. /// @@ -331,7 +331,7 @@ extension FilePath.Component: ExpressibleByStringLiteral { } } -/*System 0.0.2, @available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *)*/ +@available(/*System 0.0.2: macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0*/iOS 8, *) extension FilePath.Root: ExpressibleByStringLiteral { /// Create a file path root from a string literal. /// @@ -356,7 +356,7 @@ extension FilePath.Root: ExpressibleByStringLiteral { // MARK: - Printing and dumping -/*System 0.0.1, @available(macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0, *)*/ +@available(/*System 0.0.1: macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0*/iOS 8, *) extension FilePath: CustomStringConvertible, CustomDebugStringConvertible { /// A textual representation of the file path. /// @@ -372,7 +372,7 @@ extension FilePath: CustomStringConvertible, CustomDebugStringConvertible { public var debugDescription: String { description.debugDescription } } -/*System 0.0.2, @available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *)*/ +@available(/*System 0.0.2: macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0*/iOS 8, *) extension FilePath.Component: CustomStringConvertible, CustomDebugStringConvertible { /// A textual representation of the path component. @@ -389,7 +389,7 @@ extension FilePath.Component: CustomStringConvertible, CustomDebugStringConverti public var debugDescription: String { description.debugDescription } } -/*System 0.0.2, @available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *)*/ +@available(/*System 0.0.2: macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0*/iOS 8, *) extension FilePath.Root: CustomStringConvertible, CustomDebugStringConvertible { /// A textual representation of the path root. @@ -409,7 +409,7 @@ extension FilePath.Root: CustomStringConvertible, CustomDebugStringConvertible { // MARK: - Convenience helpers // Convenience helpers -/*System 0.0.2, @available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *)*/ +@available(/*System 0.0.2: macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0*/iOS 8, *) extension FilePath { /// Creates a string by interpreting the path’s content as UTF-8 on Unix /// and UTF-16 on Windows. @@ -420,7 +420,7 @@ extension FilePath { } } -/*System 0.0.2, @available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *)*/ +@available(/*System 0.0.2: macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0*/iOS 8, *) extension FilePath.Component { /// Creates a string by interpreting the component’s content as UTF-8 on Unix /// and UTF-16 on Windows. @@ -431,7 +431,7 @@ extension FilePath.Component { } } -/*System 0.0.2, @available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *)*/ +@available(/*System 0.0.2: macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0*/iOS 8, *) extension FilePath.Root { /// On Unix, this returns `"/"`. /// @@ -445,7 +445,7 @@ extension FilePath.Root { // MARK: - Decoding and validating -/*System 0.0.1, @available(macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0, *)*/ +@available(/*System 0.0.1: macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0*/iOS 8, *) extension String { /// Creates a string by interpreting the file path's content as UTF-8 on Unix /// and UTF-16 on Windows. @@ -475,7 +475,7 @@ extension String { } } -/*System 0.0.2, @available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *)*/ +@available(/*System 0.0.2: macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0*/iOS 8, *) extension String { /// Creates a string by interpreting the path component's content as UTF-8 on /// Unix and UTF-16 on Windows. @@ -505,7 +505,7 @@ extension String { } } -/*System 0.0.2, @available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *)*/ +@available(/*System 0.0.2: macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0*/iOS 8, *) extension String { /// On Unix, creates the string `"/"` /// @@ -558,7 +558,7 @@ extension String { // MARK: - Deprecations -/*System 0.0.1, @available(macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0, *)*/ +@available(/*System 0.0.1: macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0*/iOS 8, *) extension String { @available(*, deprecated, renamed: "init(decoding:)") public init(_ path: FilePath) { self.init(decoding: path) } @@ -568,7 +568,7 @@ extension String { } #if !os(Windows) -/*System 0.0.1, @available(macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0, *)*/ +@available(/*System 0.0.1: macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0*/iOS 8, *) extension FilePath { /// For backwards compatibility only. This initializer is equivalent to /// the preferred `FilePath(platformString:)`. diff --git a/Sources/System/FilePath/FilePathSyntax.swift b/Sources/System/FilePath/FilePathSyntax.swift index 81bf195f..a72fe4ea 100644 --- a/Sources/System/FilePath/FilePathSyntax.swift +++ b/Sources/System/FilePath/FilePathSyntax.swift @@ -9,7 +9,7 @@ // MARK: - Query API -/*System 0.0.2, @available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *)*/ +@available(/*System 0.0.2: macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0*/iOS 8, *) extension FilePath { /// Returns true if this path uniquely identifies the location of /// a file without reference to an additional starting location. @@ -99,7 +99,7 @@ extension FilePath { } // MARK: - Decompose a path -/*System 0.0.2, @available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *)*/ +@available(/*System 0.0.2: macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0*/iOS 8, *) extension FilePath { /// Returns the root of a path if there is one, otherwise `nil`. /// @@ -182,7 +182,7 @@ extension FilePath { } } -/*System 0.0.2, @available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *)*/ +@available(/*System 0.0.2: macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0*/iOS 8, *) extension FilePath { /// Returns the final component of the path. /// Returns `nil` if the path is empty or only contains a root. @@ -252,7 +252,7 @@ extension FilePath { } } -/*System 0.0.2, @available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *)*/ +@available(/*System 0.0.2: macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0*/iOS 8, *) extension FilePath.Component { /// The extension of this file or directory component. /// @@ -283,7 +283,7 @@ extension FilePath.Component { } } -/*System 0.0.2, @available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *)*/ +@available(/*System 0.0.2: macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0*/iOS 8, *) extension FilePath { /// The extension of the file or directory last component. @@ -349,7 +349,7 @@ extension FilePath { } -/*System 0.0.2, @available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *)*/ +@available(/*System 0.0.2: macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0*/iOS 8, *) extension FilePath { /// Whether the path is in lexical-normal form, that is `.` and `..` /// components have been collapsed lexically (i.e. without following @@ -430,7 +430,7 @@ extension FilePath { } // Modification and concatenation API -/*System 0.0.2, @available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *)*/ +@available(/*System 0.0.2: macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0*/iOS 8, *) extension FilePath { // TODO(Windows docs): example with roots /// If `prefix` is a prefix of `self`, removes it and returns `true`. @@ -583,7 +583,7 @@ extension FilePath { } // MARK - Renamed -/*System 0.0.2, @available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *)*/ +@available(/*System 0.0.2: macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0*/iOS 8, *) extension FilePath { @available(*, unavailable, renamed: "removingLastComponent()") public var dirname: FilePath { removingLastComponent() } diff --git a/Sources/System/FilePermissions.swift b/Sources/System/FilePermissions.swift index 37423165..bd899ce2 100644 --- a/Sources/System/FilePermissions.swift +++ b/Sources/System/FilePermissions.swift @@ -17,7 +17,7 @@ /// let perms = FilePermissions(rawValue: 0o644) /// perms == [.ownerReadWrite, .groupRead, .otherRead] // true @frozen -/*System 0.0.1, @available(macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0, *)*/ +@available(/*System 0.0.1: macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0*/iOS 8, *) public struct FilePermissions: OptionSet, Hashable, Codable { /// The raw C file permissions. @_alwaysEmitIntoClient @@ -135,7 +135,7 @@ public struct FilePermissions: OptionSet, Hashable, Codable { public static var saveText: FilePermissions { FilePermissions(0o1000) } } -/*System 0.0.1, @available(macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0, *)*/ +@available(/*System 0.0.1: macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0*/iOS 8, *) extension FilePermissions : CustomStringConvertible, CustomDebugStringConvertible { diff --git a/Sources/System/Internals/CInterop.swift b/Sources/System/Internals/CInterop.swift index aef1cce2..5e46bafe 100644 --- a/Sources/System/Internals/CInterop.swift +++ b/Sources/System/Internals/CInterop.swift @@ -29,12 +29,12 @@ import ucrt public typealias CModeT = CInt #else /// The C `mode_t` type. -/*System 0.0.1, @available(macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0, *)*/ +@available(/*System 0.0.1: macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0*/iOS 8, *) public typealias CModeT = mode_t #endif /// A namespace for C and platform types -/*System 0.0.2, @available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *)*/ +@available(/*System 0.0.2: macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0*/iOS 8, *) public enum CInterop { #if os(Windows) public typealias Mode = CInt diff --git a/Sources/System/MachPort.swift b/Sources/System/MachPort.swift index b366dcfc..5615b314 100644 --- a/Sources/System/MachPort.swift +++ b/Sources/System/MachPort.swift @@ -24,7 +24,7 @@ internal func _machPrecondition( precondition(kr == expected, file: file, line: line) } -/*System 1.3.0, @available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *)*/ +@available(/*System 1.3.0: macOS 9999, iOS 9999, watchOS 9999, tvOS 9999*/iOS 8, *) @frozen public enum Mach { @_moveOnly diff --git a/Sources/System/PlatformString.swift b/Sources/System/PlatformString.swift index ba05553e..4e2e7ddf 100644 --- a/Sources/System/PlatformString.swift +++ b/Sources/System/PlatformString.swift @@ -7,7 +7,7 @@ See https://swift.org/LICENSE.txt for license information */ -/*System 0.0.2, @available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *)*/ +@available(/*System 0.0.2: macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0*/iOS 8, *) extension String { /// Creates a string by interpreting the null-terminated platform string as /// UTF-8 on Unix and UTF-16 on Windows. @@ -164,7 +164,7 @@ extension String { } -/*System 0.0.2, @available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *)*/ +@available(/*System 0.0.2: macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0*/iOS 8, *) extension CInterop.PlatformChar { internal var _platformCodeUnit: CInterop.PlatformUnicodeEncoding.CodeUnit { #if os(Windows) @@ -175,7 +175,7 @@ extension CInterop.PlatformChar { } } -/*System 0.0.2, @available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *)*/ +@available(/*System 0.0.2: macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0*/iOS 8, *) extension CInterop.PlatformUnicodeEncoding.CodeUnit { internal var _platformChar: CInterop.PlatformChar { #if os(Windows) diff --git a/Sources/System/Util.swift b/Sources/System/Util.swift index a119576d..3a8df9ac 100644 --- a/Sources/System/Util.swift +++ b/Sources/System/Util.swift @@ -8,21 +8,21 @@ */ // Results in errno if i == -1 -/*System 0.0.1, @available(macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0, *)*/ +@available(/*System 0.0.1: macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0*/iOS 8, *) private func valueOrErrno( _ i: I ) -> Result { i == -1 ? .failure(Errno.current) : .success(i) } -/*System 0.0.1, @available(macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0, *)*/ +@available(/*System 0.0.1: macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0*/iOS 8, *) private func nothingOrErrno( _ i: I ) -> Result<(), Errno> { valueOrErrno(i).map { _ in () } } -/*System 0.0.1, @available(macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0, *)*/ +@available(/*System 0.0.1: macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0*/iOS 8, *) internal func valueOrErrno( retryOnInterrupt: Bool, _ f: () -> I ) -> Result { @@ -36,7 +36,7 @@ internal func valueOrErrno( } while true } -/*System 0.0.1, @available(macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0, *)*/ +@available(/*System 0.0.1: macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0*/iOS 8, *) internal func nothingOrErrno( retryOnInterrupt: Bool, _ f: () -> I ) -> Result<(), Errno> { diff --git a/Tests/SystemTests/ErrnoTest.swift b/Tests/SystemTests/ErrnoTest.swift index 8b2da658..05cd5185 100644 --- a/Tests/SystemTests/ErrnoTest.swift +++ b/Tests/SystemTests/ErrnoTest.swift @@ -19,7 +19,7 @@ import System import WinSDK #endif -/*System 0.0.1, @available(macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0, *)*/ +@available(/*System 0.0.1: macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0*/iOS 8, *) final class ErrnoTest: XCTestCase { func testConstants() { XCTAssert(EPERM == Errno.notPermitted.rawValue) diff --git a/Tests/SystemTests/FileOperationsTest.swift b/Tests/SystemTests/FileOperationsTest.swift index 419e1c97..8062aedc 100644 --- a/Tests/SystemTests/FileOperationsTest.swift +++ b/Tests/SystemTests/FileOperationsTest.swift @@ -15,7 +15,7 @@ import SystemPackage import System #endif -/*System 0.0.1, @available(macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0, *)*/ +@available(/*System 0.0.1: macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0*/iOS 8, *) final class FileOperationsTest: XCTestCase { func testSyscalls() { let fd = FileDescriptor(rawValue: 1) diff --git a/Tests/SystemTests/FilePathTests/FilePathComponentsTest.swift b/Tests/SystemTests/FilePathTests/FilePathComponentsTest.swift index 693fc11f..ad69cb4c 100644 --- a/Tests/SystemTests/FilePathTests/FilePathComponentsTest.swift +++ b/Tests/SystemTests/FilePathTests/FilePathComponentsTest.swift @@ -15,7 +15,7 @@ import XCTest @testable import System #endif -/*System 0.0.2, @available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *)*/ +@available(/*System 0.0.2: macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0*/iOS 8, *) struct TestPathComponents: TestCase { var path: FilePath var expectedRoot: FilePath.Root? @@ -100,7 +100,7 @@ struct TestPathComponents: TestCase { } } -/*System 0.0.2, @available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *)*/ +@available(/*System 0.0.2: macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0*/iOS 8, *) final class FilePathComponentsTest: XCTestCase { func testAdHocRRC() { var path: FilePath = "/usr/local/bin" diff --git a/Tests/SystemTests/FilePathTests/FilePathExtras.swift b/Tests/SystemTests/FilePathTests/FilePathExtras.swift index efb31f3e..82f11373 100644 --- a/Tests/SystemTests/FilePathTests/FilePathExtras.swift +++ b/Tests/SystemTests/FilePathTests/FilePathExtras.swift @@ -6,7 +6,7 @@ #endif // Why can't I write this extension on `FilePath.ComponentView.SubSequence`? -/*System 0.0.2, @available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *)*/ +@available(/*System 0.0.2: macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0*/iOS 8, *) extension Slice where Base == FilePath.ComponentView { internal var _storageSlice: SystemString.SubSequence { base._path._storage[self.startIndex._storage ..< self.endIndex._storage] @@ -15,7 +15,7 @@ extension Slice where Base == FilePath.ComponentView { // Proposed API that didn't make the cut, but we stil want to keep our testing for -/*System 0.0.2, @available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *)*/ +@available(/*System 0.0.2: macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0*/iOS 8, *) extension FilePath { /// Returns `self` relative to `base`. /// This does not cosult the file system or resolve symlinks. diff --git a/Tests/SystemTests/FilePathTests/FilePathParsingTest.swift b/Tests/SystemTests/FilePathTests/FilePathParsingTest.swift index 1c249e03..766bdf8d 100644 --- a/Tests/SystemTests/FilePathTests/FilePathParsingTest.swift +++ b/Tests/SystemTests/FilePathTests/FilePathParsingTest.swift @@ -68,7 +68,7 @@ extension ParsingTestCase { @testable import System #endif -/*System 0.0.2, @available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *)*/ +@available(/*System 0.0.2: macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0*/iOS 8, *) final class FilePathParsingTest: XCTestCase { func testNormalization() { let unixPaths: Array = [ diff --git a/Tests/SystemTests/FilePathTests/FilePathSyntaxTest.swift b/Tests/SystemTests/FilePathTests/FilePathSyntaxTest.swift index fca6b86a..ccaa7827 100644 --- a/Tests/SystemTests/FilePathTests/FilePathSyntaxTest.swift +++ b/Tests/SystemTests/FilePathTests/FilePathSyntaxTest.swift @@ -146,7 +146,7 @@ extension SyntaxTestCase { } } -/*System 0.0.2, @available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *)*/ +@available(/*System 0.0.2: macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0*/iOS 8, *) extension SyntaxTestCase { func testComponents(_ path: FilePath, expected: [String]) { let expectedComponents = expected.map { FilePath.Component($0)! } @@ -342,7 +342,7 @@ private struct WindowsRootTestCase: TestCase { var line: UInt } -/*System 0.0.2, @available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *)*/ +@available(/*System 0.0.2: macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0*/iOS 8, *) extension WindowsRootTestCase { func runAllTests() { withWindowsPaths(enabled: true) { @@ -357,7 +357,7 @@ extension WindowsRootTestCase { } } -/*System 0.0.2, @available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *)*/ +@available(/*System 0.0.2: macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0*/iOS 8, *) final class FilePathSyntaxTest: XCTestCase { func testPathSyntax() { let unixPaths: Array = [ diff --git a/Tests/SystemTests/FilePathTests/FilePathTest.swift b/Tests/SystemTests/FilePathTests/FilePathTest.swift index 9dc36f9e..a56a00e9 100644 --- a/Tests/SystemTests/FilePathTests/FilePathTest.swift +++ b/Tests/SystemTests/FilePathTests/FilePathTest.swift @@ -15,7 +15,7 @@ import SystemPackage import System #endif -/*System 0.0.1, @available(macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0, *)*/ +@available(/*System 0.0.1: macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0*/iOS 8, *) func filePathFromUnterminatedBytes(_ bytes: S) -> FilePath where S.Element == UInt8 { var array = Array(bytes) assert(array.last != 0, "already null terminated") @@ -29,7 +29,7 @@ func filePathFromUnterminatedBytes(_ bytes: S) -> FilePath where S. } let invalidBytes: [UInt8] = [0x2F, 0x61, 0x2F, 0x62, 0x2F, 0x83] -/*System 0.0.2, @available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *)*/ +@available(/*System 0.0.2: macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0*/iOS 8, *) final class FilePathTest: XCTestCase { struct TestPath { let filePath: FilePath diff --git a/Tests/SystemTests/FileTypesTest.swift b/Tests/SystemTests/FileTypesTest.swift index 81b21684..4bddf410 100644 --- a/Tests/SystemTests/FileTypesTest.swift +++ b/Tests/SystemTests/FileTypesTest.swift @@ -15,7 +15,7 @@ import SystemPackage import System #endif -/*System 0.0.1, @available(macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0, *)*/ +@available(/*System 0.0.1: macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0*/iOS 8, *) final class FileDescriptorTest: XCTestCase { func testStandardDescriptors() { XCTAssertEqual(FileDescriptor.standardInput.rawValue, 0) @@ -64,7 +64,7 @@ final class FileDescriptorTest: XCTestCase { } -/*System 0.0.1, @available(macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0, *)*/ +@available(/*System 0.0.1: macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0*/iOS 8, *) final class FilePermissionsTest: XCTestCase { func testPermissions() { diff --git a/Tests/SystemTests/MockingTest.swift b/Tests/SystemTests/MockingTest.swift index 8d701e22..1f2c96da 100644 --- a/Tests/SystemTests/MockingTest.swift +++ b/Tests/SystemTests/MockingTest.swift @@ -15,7 +15,7 @@ import XCTest @testable import System #endif -/*System 0.0.2, @available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *)*/ +@available(/*System 0.0.2: macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0*/iOS 8, *) final class MockingTest: XCTestCase { func testMocking() { XCTAssertFalse(mockingEnabled) diff --git a/Utilities/expand-availability.py b/Utilities/expand-availability.py index 5bb1878c..81dd97d9 100755 --- a/Utilities/expand-availability.py +++ b/Utilities/expand-availability.py @@ -11,12 +11,13 @@ # "Hello" # } # -# (The iOS 8 availability is a dummy no-op declaration -- it only has to be there because -# `@available(*)` isn't valid syntax, and commenting out the entire `@available` attribute -# would interfere with parser tools for doc comments. `iOS 8` is the shortest version string that -# matches the minimum possible deployment target for Swift code, so we use that as our dummy -# availability version. `@available(iOS 8, *)` is functionally equivalent to not having an -# `@available` attribute at all.) +# (The iOS 8 availability is a dummy no-op declaration -- it only has to be +# there because `@available(*)` isn't valid syntax, and commenting out the +# entire `@available` attribute would interfere with parser tools for doc +# comments. `iOS 8` is the shortest version string that matches the minimum +# possible deployment target for Swift code, so we use that as our dummy +# availability version. `@available(iOS 8, *)` is functionally equivalent to not +# having an `@available` attribute at all.) # # The script adds full availability incantations to these comments. It can run # in one of two modes: @@ -96,8 +97,8 @@ def available_attribute(filename, lineno, symbolic_version): if args.attributes: attribute = "@available(/*{0}*/{1}, *)".format(symbolic_version, expansion) else: - # Sadly `@available(*)` is not valid syntax, so we have to mention at least one actual - # platform here. + # Sadly `@available(*)` is not valid syntax, so we have to mention at + # least one actual platform here. attribute = "@available(/*{0}: {1}*/iOS 8, *)".format(symbolic_version, expansion) return attribute From 9674a9fee890c14cc303659c1d349b2b064dd3f8 Mon Sep 17 00:00:00 2001 From: Saleem Abdulrasool Date: Thu, 2 Mar 2023 10:56:49 -0800 Subject: [PATCH 124/427] build: add ARM64 for mapping on Windows Windows uses `ARM64` for `CMAKE_SYSTEM_PROCESSOR` which doesn't match the current spellings. --- cmake/modules/SwiftSupport.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/modules/SwiftSupport.cmake b/cmake/modules/SwiftSupport.cmake index 82961db3..a73a4e57 100644 --- a/cmake/modules/SwiftSupport.cmake +++ b/cmake/modules/SwiftSupport.cmake @@ -17,7 +17,7 @@ See https://swift.org/LICENSE.txt for license information function(get_swift_host_arch result_var_name) if("${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "x86_64") set("${result_var_name}" "x86_64" PARENT_SCOPE) - elseif ("${CMAKE_SYSTEM_PROCESSOR}" MATCHES "AArch64|aarch64|arm64") + elseif ("${CMAKE_SYSTEM_PROCESSOR}" MATCHES "AArch64|aarch64|arm64|ARM64") if(CMAKE_SYSTEM_NAME MATCHES Darwin) set("${result_var_name}" "arm64" PARENT_SCOPE) else() From 7c6609eaca0825cd0b2ab1c86c9791e77502cfc9 Mon Sep 17 00:00:00 2001 From: Max Desiatov Date: Thu, 1 Jun 2023 17:01:30 +0100 Subject: [PATCH 125/427] FilePathSyntax.swift: fix doc comment typo `Appliations` -> `Applications` --- Sources/System/FilePath/FilePathSyntax.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/System/FilePath/FilePathSyntax.swift b/Sources/System/FilePath/FilePathSyntax.swift index a72fe4ea..1c3fc097 100644 --- a/Sources/System/FilePath/FilePathSyntax.swift +++ b/Sources/System/FilePath/FilePathSyntax.swift @@ -340,8 +340,8 @@ extension FilePath { /// Returns `nil` if `lastComponent` is `nil` /// /// * `/tmp/foo.txt => foo` - /// * `/Appliations/Foo.app/ => Foo` - /// * `/Appliations/Foo.app/bar.txt => bar` + /// * `/Applications/Foo.app/ => Foo` + /// * `/Applications/Foo.app/bar.txt => bar` /// * `/tmp/.hidden => .hidden` /// * `/tmp/.. => ..` /// * `/ => nil` From 5b1b4cab5698822182010cdf125c4d09e5247eba Mon Sep 17 00:00:00 2001 From: Daniel Loffgren Date: Wed, 10 May 2023 13:52:33 -0700 Subject: [PATCH 126/427] Remove allocatePortRightPair(), as move only types are not permitted in tuples --- Sources/System/MachPort.swift | 18 ------------------ Tests/SystemTests/MachPortTests.swift | 13 ------------- 2 files changed, 31 deletions(-) diff --git a/Sources/System/MachPort.swift b/Sources/System/MachPort.swift index 5615b314..55e3514b 100644 --- a/Sources/System/MachPort.swift +++ b/Sources/System/MachPort.swift @@ -122,24 +122,6 @@ public enum Mach { /// receiving end. @frozen public struct SendOnceRight: MachPortRight {} - - /// Create a connected pair of rights, one receive, and one send. - /// - /// This function will abort if the rights could not be created. - /// Callers may assert that valid rights are always returned. - public static func allocatePortRightPair() -> - (receive: Mach.Port, send: Mach.Port) { - var name = mach_port_name_t(MACH_PORT_NULL) - let secret = mach_port_context_t(arc4random()) - withUnsafeMutablePointer(to: &name) { name in - var options = mach_port_options_t() - options.flags = UInt32(MPO_INSERT_SEND_RIGHT); - withUnsafeMutablePointer(to: &options) { options in - _machPrecondition(mach_port_construct(mach_task_self_, options, secret, name)) - } - } - return (Mach.Port(name: name, context: secret), Mach.Port(name: name)) - } } extension Mach.Port where RightType == Mach.ReceiveRight { diff --git a/Tests/SystemTests/MachPortTests.swift b/Tests/SystemTests/MachPortTests.swift index 5c119d17..6c22a041 100644 --- a/Tests/SystemTests/MachPortTests.swift +++ b/Tests/SystemTests/MachPortTests.swift @@ -121,19 +121,6 @@ final class MachPortTests: XCTestCase { } } - func testMakePair() throws { - let (recv, send) = Mach.allocatePortRightPair() - XCTAssert(recv.makeSendCount == 1) - recv.withBorrowedName { rName in - send.withBorrowedName { sName in - XCTAssert(rName != 0xFFFFFFFF) - XCTAssert(rName != MACH_PORT_NULL) - // send and recv port names coalesce - XCTAssert(rName == sName) - } - } - } - func testCopySend() throws { let recv = Mach.Port() let zero = recv.makeSendCount From 2d6c6a82b3e5158b5c8c320c123c38a97f2e0c98 Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Thu, 8 Jun 2023 13:51:05 -0700 Subject: [PATCH 127/427] import module correctly in test file --- Tests/SystemTests/MachPortTests.swift | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Tests/SystemTests/MachPortTests.swift b/Tests/SystemTests/MachPortTests.swift index 6c22a041..49c324e9 100644 --- a/Tests/SystemTests/MachPortTests.swift +++ b/Tests/SystemTests/MachPortTests.swift @@ -12,6 +12,12 @@ import XCTest import Darwin.Mach +#if SYSTEM_PACKAGE +import SystemPackage +#else +import System +#endif + final class MachPortTests: XCTestCase { func refCountForMachPortName(name:mach_port_name_t, kind:mach_port_right_t) -> mach_port_urefs_t { var refCount:mach_port_urefs_t = 0 From 6bbed6b1991e7f16f6506eeed5e6aa51929048b3 Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Thu, 8 Jun 2023 14:18:54 -0700 Subject: [PATCH 128/427] update non-copyable type declaration syntax --- Sources/System/MachPort.swift | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Sources/System/MachPort.swift b/Sources/System/MachPort.swift index 55e3514b..64aee276 100644 --- a/Sources/System/MachPort.swift +++ b/Sources/System/MachPort.swift @@ -27,8 +27,7 @@ internal func _machPrecondition( @available(/*System 1.3.0: macOS 9999, iOS 9999, watchOS 9999, tvOS 9999*/iOS 8, *) @frozen public enum Mach { - @_moveOnly - public struct Port { + public struct Port: ~Copyable { @usableFromInline internal var _name: mach_port_name_t From 7bcc01f0bfff8f33d75bc11fa099e95d3f3204e6 Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Fri, 9 Jun 2023 16:46:48 -0700 Subject: [PATCH 129/427] deactivate Mach.Port Will reactivate when the following issue is fixed: https://github.com/apple/swift/issues/66299 (rdar://110496872) --- Sources/System/MachPort.swift | 2 +- Tests/SystemTests/MachPortTests.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/System/MachPort.swift b/Sources/System/MachPort.swift index 64aee276..04d2cc5b 100644 --- a/Sources/System/MachPort.swift +++ b/Sources/System/MachPort.swift @@ -7,7 +7,7 @@ See https://swift.org/LICENSE.txt for license information */ -#if swift(>=5.8) && $MoveOnly && (os(macOS) || os(iOS) || os(watchOS) || os(tvOS)) +#if false && swift(>=5.8) && $MoveOnly && (os(macOS) || os(iOS) || os(watchOS) || os(tvOS)) import Darwin.Mach diff --git a/Tests/SystemTests/MachPortTests.swift b/Tests/SystemTests/MachPortTests.swift index 49c324e9..31a654cb 100644 --- a/Tests/SystemTests/MachPortTests.swift +++ b/Tests/SystemTests/MachPortTests.swift @@ -7,7 +7,7 @@ See https://swift.org/LICENSE.txt for license information */ -#if swift(>=5.8) && $MoveOnly && (os(macOS) || os(iOS) || os(watchOS) || os(tvOS)) +#if false && swift(>=5.8) && $MoveOnly && (os(macOS) || os(iOS) || os(watchOS) || os(tvOS)) import XCTest import Darwin.Mach From 1d1bef36c98bb430906d413279c8a18a6180f291 Mon Sep 17 00:00:00 2001 From: Alex Martini Date: Wed, 21 Jun 2023 10:26:04 -0700 Subject: [PATCH 130/427] Fix abstracts to follow reference style. - Use a present tense verb phrase - Avoid repeating the symbol's name --- Sources/System/FileOperations.swift | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Sources/System/FileOperations.swift b/Sources/System/FileOperations.swift index 97a0c8e7..2297aeba 100644 --- a/Sources/System/FileOperations.swift +++ b/Sources/System/FileOperations.swift @@ -158,7 +158,7 @@ extension FileDescriptor { nothingOrErrno(retryOnInterrupt: false) { system_close(self.rawValue) } } - /// Reposition the offset for the given file descriptor. + /// Repositions the offset for the given file descriptor. /// /// - Parameters: /// - offset: The new offset for the file descriptor. @@ -371,7 +371,7 @@ extension FileDescriptor { @available(/*System 0.0.2: macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0*/iOS 8, *) extension FileDescriptor { - /// Duplicate this file descriptor and return the newly created copy. + /// Duplicates this file descriptor and return the newly created copy. /// /// - Parameters: /// - `target`: The desired target file descriptor, or `nil`, in which case @@ -437,7 +437,7 @@ extension FileDescriptor { #if !os(Windows) @available(/*System 1.1.0: macOS 9999, iOS 9999, watchOS 9999, tvOS 9999*/iOS 8, *) extension FileDescriptor { - /// Create a pipe, a unidirectional data channel which can be used for interprocess communication. + /// Creates a unidirectional data channel, which can be used for interprocess communication. /// /// - Returns: The pair of file descriptors. /// @@ -466,7 +466,7 @@ extension FileDescriptor { #if !os(Windows) @available(/*System 1.2.0: macOS 9999, iOS 9999, watchOS 9999, tvOS 9999*/iOS 8, *) extension FileDescriptor { - /// Truncate or extend the file referenced by this file descriptor. + /// Truncates or extends the file referenced by this file descriptor. /// /// - Parameters: /// - newSize: The length in bytes to resize the file to. From adbf8d285669fc5d97fe0fb9c68d61ac69c632ea Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Wed, 21 Jun 2023 14:50:47 -0700 Subject: [PATCH 131/427] Add availability attributes to `Sendable` conformances --- Sources/System/FileDescriptor.swift | 5 +++++ Sources/System/FilePath/FilePath.swift | 1 + Sources/System/FilePath/FilePathComponentView.swift | 3 +++ Sources/System/FilePath/FilePathComponents.swift | 5 +++++ Sources/System/FilePermissions.swift | 1 + 5 files changed, 15 insertions(+) diff --git a/Sources/System/FileDescriptor.swift b/Sources/System/FileDescriptor.swift index 292c2a51..556e63ed 100644 --- a/Sources/System/FileDescriptor.swift +++ b/Sources/System/FileDescriptor.swift @@ -481,7 +481,12 @@ extension FileDescriptor.OpenOptions //@available(*, unavailable, message: "File descriptors are not completely thread-safe.") //extension FileDescriptor: Sendable {} +@available(/*System 0.0.1: macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0*/iOS 8, *) extension FileDescriptor.AccessMode: Sendable {} + +@available(/*System 0.0.1: macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0*/iOS 8, *) extension FileDescriptor.OpenOptions: Sendable {} + +@available(/*System 0.0.1: macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0*/iOS 8, *) extension FileDescriptor.SeekOrigin: Sendable {} #endif diff --git a/Sources/System/FilePath/FilePath.swift b/Sources/System/FilePath/FilePath.swift index 09b8142a..c1dfd7f3 100644 --- a/Sources/System/FilePath/FilePath.swift +++ b/Sources/System/FilePath/FilePath.swift @@ -69,5 +69,6 @@ extension FilePath { extension FilePath: Hashable, Codable {} #if compiler(>=5.5) && canImport(_Concurrency) +@available(/*System 0.0.1: macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0*/iOS 8, *) extension FilePath: Sendable {} #endif diff --git a/Sources/System/FilePath/FilePathComponentView.swift b/Sources/System/FilePath/FilePathComponentView.swift index 157b2393..efdd084a 100644 --- a/Sources/System/FilePath/FilePathComponentView.swift +++ b/Sources/System/FilePath/FilePathComponentView.swift @@ -241,6 +241,9 @@ extension FilePath.ComponentView { } #if compiler(>=5.5) && canImport(_Concurrency) +@available(/*System 0.0.2: macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0*/iOS 8, *) extension FilePath.ComponentView: Sendable {} + +@available(/*System 0.0.2: macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0*/iOS 8, *) extension FilePath.ComponentView.Index: Sendable {} #endif diff --git a/Sources/System/FilePath/FilePathComponents.swift b/Sources/System/FilePath/FilePathComponents.swift index 782561c6..ecd713ea 100644 --- a/Sources/System/FilePath/FilePathComponents.swift +++ b/Sources/System/FilePath/FilePathComponents.swift @@ -287,7 +287,12 @@ extension FilePath.Root { } #if compiler(>=5.5) && canImport(_Concurrency) +@available(/*System 0.0.2: macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0*/iOS 8, *) extension FilePath.Root: Sendable {} + +@available(/*System 0.0.2: macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0*/iOS 8, *) extension FilePath.Component: Sendable {} + +@available(/*System 0.0.2: macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0*/iOS 8, *) extension FilePath.Component.Kind: Sendable {} #endif diff --git a/Sources/System/FilePermissions.swift b/Sources/System/FilePermissions.swift index bd899ce2..9bd6c5fb 100644 --- a/Sources/System/FilePermissions.swift +++ b/Sources/System/FilePermissions.swift @@ -177,5 +177,6 @@ extension FilePermissions } #if compiler(>=5.5) && canImport(_Concurrency) +@available(/*System 0.0.1: macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0*/iOS 8, *) extension FilePermissions: Sendable {} #endif From 0062c82c90ca7a1a7f0450ee28b5c54e334a23d3 Mon Sep 17 00:00:00 2001 From: Max Desiatov Date: Tue, 4 Jul 2023 16:50:13 +0100 Subject: [PATCH 132/427] Add support for Musl libc (#133) Since Musl is sufficiently different from Glibc (see https://wiki.musl-libc.org/functional-differences-from-glibc.html), it requires a different import, which now should be applied to files that have `import Glibc` in them. --- Sources/System/Internals/CInterop.swift | 9 ++++++--- Sources/System/Internals/Constants.swift | 11 +++++++++-- Sources/System/Internals/Exports.swift | 16 ++++++++++++---- Sources/System/Internals/Syscalls.swift | 4 +++- 4 files changed, 30 insertions(+), 10 deletions(-) diff --git a/Sources/System/Internals/CInterop.swift b/Sources/System/Internals/CInterop.swift index 5e46bafe..a71ef127 100644 --- a/Sources/System/Internals/CInterop.swift +++ b/Sources/System/Internals/CInterop.swift @@ -9,12 +9,15 @@ #if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) import Darwin -#elseif os(Linux) || os(FreeBSD) || os(OpenBSD) || os(Android) -@_implementationOnly import CSystem -import Glibc #elseif os(Windows) import CSystem import ucrt +#elseif canImport(Glibc) +@_implementationOnly import CSystem +import Glibc +#elseif canImport(Musl) +@_implementationOnly import CSystem +import Musl #else #error("Unsupported Platform") #endif diff --git a/Sources/System/Internals/Constants.swift b/Sources/System/Internals/Constants.swift index 85f9f3de..592e38c5 100644 --- a/Sources/System/Internals/Constants.swift +++ b/Sources/System/Internals/Constants.swift @@ -13,11 +13,14 @@ #if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) import Darwin -#elseif os(Linux) || os(FreeBSD) || os(OpenBSD) || os(Android) -import Glibc #elseif os(Windows) import CSystem import ucrt +#elseif canImport(Glibc) +import Glibc +#elseif canImport(Musl) +import CSystem +import Musl #else #error("Unsupported Platform") #endif @@ -454,9 +457,13 @@ internal var _O_WRONLY: CInt { O_WRONLY } internal var _O_RDWR: CInt { O_RDWR } #if !os(Windows) +#if canImport(Musl) +internal var _O_ACCMODE: CInt { 0x03|O_SEARCH } +#else // TODO: API? @_alwaysEmitIntoClient internal var _O_ACCMODE: CInt { O_ACCMODE } +#endif @_alwaysEmitIntoClient internal var _O_NONBLOCK: CInt { O_NONBLOCK } diff --git a/Sources/System/Internals/Exports.swift b/Sources/System/Internals/Exports.swift index 78bf2074..57abc6fc 100644 --- a/Sources/System/Internals/Exports.swift +++ b/Sources/System/Internals/Exports.swift @@ -14,12 +14,15 @@ #if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) import Darwin -#elseif os(Linux) || os(FreeBSD) || os(OpenBSD) || os(Android) -@_implementationOnly import CSystem -import Glibc #elseif os(Windows) import CSystem import ucrt +#elseif canImport(Glibc) +@_implementationOnly import CSystem +import Glibc +#elseif canImport(Musl) +@_implementationOnly import CSystem +import Musl #else #error("Unsupported Platform") #endif @@ -45,11 +48,16 @@ internal var system_errno: CInt { _ = ucrt._set_errno(newValue) } } -#else +#elseif canImport(Glibc) internal var system_errno: CInt { get { Glibc.errno } set { Glibc.errno = newValue } } +#elseif canImport(Musl) +internal var system_errno: CInt { + get { Musl.errno } + set { Musl.errno = newValue } +} #endif // MARK: C stdlib decls diff --git a/Sources/System/Internals/Syscalls.swift b/Sources/System/Internals/Syscalls.swift index c5c376c3..9fcb2f90 100644 --- a/Sources/System/Internals/Syscalls.swift +++ b/Sources/System/Internals/Syscalls.swift @@ -9,8 +9,10 @@ #if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) import Darwin -#elseif os(Linux) || os(FreeBSD) || os(OpenBSD) || os(Android) +#elseif canImport(Glibc) import Glibc +#elseif canImport(Musl) +import Musl #elseif os(Windows) import ucrt #else From 32026456baa704061925ed9589bd88349c98fb73 Mon Sep 17 00:00:00 2001 From: Saleem Abdulrasool Date: Thu, 13 Jul 2023 08:05:27 -0700 Subject: [PATCH 133/427] Tests: adjust the `FilePathTest.testFilePath` to build on Windows UTF-8 is not the universal encoding for file systems. Use a platform specific invalid string encoding for testing the sequence. The internal representation of a `FilePath` is dependent on the system, and Windows uses UTF-16 as the base encoding for C interoperability. Adjust the test to use an invalid sequence in the proper encoding. This allows the tests to now build on Windows once again. --- .../FilePathTests/FilePathTest.swift | 50 ++++++++++++++----- 1 file changed, 38 insertions(+), 12 deletions(-) diff --git a/Tests/SystemTests/FilePathTests/FilePathTest.swift b/Tests/SystemTests/FilePathTests/FilePathTest.swift index a56a00e9..b008b15b 100644 --- a/Tests/SystemTests/FilePathTests/FilePathTest.swift +++ b/Tests/SystemTests/FilePathTests/FilePathTest.swift @@ -16,39 +16,65 @@ import System #endif @available(/*System 0.0.1: macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0*/iOS 8, *) -func filePathFromUnterminatedBytes(_ bytes: S) -> FilePath where S.Element == UInt8 { +func filePathFromInvalidCodePointSequence(_ bytes: S) -> FilePath where S.Element == UTF16.CodeUnit { var array = Array(bytes) assert(array.last != 0, "already null terminated") array += [0] return array.withUnsafeBufferPointer { - $0.withMemoryRebound(to: CChar.self) { + $0.withMemoryRebound(to: CInterop.PlatformChar.self) { + FilePath(platformString: $0.baseAddress!) + } + } +} + +@available(/*System 0.0.1: macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0*/iOS 8, *) +func filePathFromInvalidCodePointSequence(_ bytes: S) -> FilePath where S.Element == UTF8.CodeUnit { + var array = Array(bytes) + assert(array.last != 0, "already null terminated") + array += [0] + + return array.withUnsafeBufferPointer { + $0.withMemoryRebound(to: CInterop.PlatformChar.self) { FilePath(platformString: $0.baseAddress!) } } } -let invalidBytes: [UInt8] = [0x2F, 0x61, 0x2F, 0x62, 0x2F, 0x83] @available(/*System 0.0.2: macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0*/iOS 8, *) final class FilePathTest: XCTestCase { struct TestPath { let filePath: FilePath let string: String - let validUTF8: Bool + let validString: Bool } - let testPaths: [TestPath] = [ +#if os(Windows) + static let invalidSequence: [UTF16.CodeUnit] = [0xd800, 0x0020] + static let invalidSequenceTest = + TestPath(filePath: filePathFromInvalidCodePointSequence(invalidSequence), + string: String(decoding: invalidSequence, as: UTF16.self), + validString: false) +#else + static let invalidSequence: [UTF8.CodeUnit] = [0x2F, 0x61, 0x2F, 0x62, 0x2F, 0x83] + static let invalidSequenceTest = + TestPath(filePath: filePathFromInvalidCodePointSequence(invalidSequence), + string: String(decoding: invalidSequence, as: UTF8.self), + validString: false) +#endif + + var testPaths: [TestPath] = [ // empty - TestPath(filePath: FilePath(), string: String(), validUTF8: true), + TestPath(filePath: FilePath(), string: String(), validString: true), // valid ascii - TestPath(filePath: "/a/b/c", string: "/a/b/c", validUTF8: true), + TestPath(filePath: "/a/b/c", string: "/a/b/c", validString: true), // valid utf8 - TestPath(filePath: "/あ/🧟‍♀️", string: "/あ/🧟‍♀️", validUTF8: true), + TestPath(filePath: "/あ/🧟‍♀️", string: "/あ/🧟‍♀️", validString: true), - // invalid utf8 - TestPath(filePath: filePathFromUnterminatedBytes(invalidBytes), string: String(decoding: invalidBytes, as: UTF8.self), validUTF8: false), + // invalid sequence + invalidSequenceTest, ] func testFilePath() { @@ -59,8 +85,8 @@ final class FilePathTest: XCTestCase { XCTAssertEqual(testPath.string, String(decoding: testPath.filePath)) - // TODO: test component UTF8 validation - if testPath.validUTF8 { + // TODO: test component CodeUnit representation validation + if testPath.validString { XCTAssertEqual(testPath.filePath, FilePath(testPath.string)) XCTAssertEqual(testPath.string, String(validating: testPath.filePath)) } else { From e1ab86805936103d3820f1f44097655dbf048b70 Mon Sep 17 00:00:00 2001 From: Saleem Abdulrasool Date: Thu, 13 Jul 2023 10:00:45 -0700 Subject: [PATCH 134/427] Tests: avoid unnecessary duplication (NFCI) Rather than specialising the function with the same implementation, prefer to use the `CInterop.PlatformUnicodeEncoding` to determine the correct code point encoding. This allows us to share the implementation across the platforms. --- .../SystemTests/FilePathTests/FilePathTest.swift | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/Tests/SystemTests/FilePathTests/FilePathTest.swift b/Tests/SystemTests/FilePathTests/FilePathTest.swift index b008b15b..1686faa1 100644 --- a/Tests/SystemTests/FilePathTests/FilePathTest.swift +++ b/Tests/SystemTests/FilePathTests/FilePathTest.swift @@ -16,20 +16,7 @@ import System #endif @available(/*System 0.0.1: macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0*/iOS 8, *) -func filePathFromInvalidCodePointSequence(_ bytes: S) -> FilePath where S.Element == UTF16.CodeUnit { - var array = Array(bytes) - assert(array.last != 0, "already null terminated") - array += [0] - - return array.withUnsafeBufferPointer { - $0.withMemoryRebound(to: CInterop.PlatformChar.self) { - FilePath(platformString: $0.baseAddress!) - } - } -} - -@available(/*System 0.0.1: macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0*/iOS 8, *) -func filePathFromInvalidCodePointSequence(_ bytes: S) -> FilePath where S.Element == UTF8.CodeUnit { +func filePathFromInvalidCodePointSequence(_ bytes: S) -> FilePath where S.Element == CInterop.PlatformUnicodeEncoding.CodeUnit { var array = Array(bytes) assert(array.last != 0, "already null terminated") array += [0] From 15794f5cad319a58a8ed768af3947e2228ced0ce Mon Sep 17 00:00:00 2001 From: Lucy Satheesan Date: Tue, 18 Jul 2023 21:04:39 -0700 Subject: [PATCH 135/427] ioring pitch: first steps --- Package.resolved | 16 ++ Package.swift | 9 +- Sources/CSystem/include/CSystemLinux.h | 1 + Sources/CSystem/include/io_uring.h | 50 ++++ Sources/System/IOCompletion.swift | 50 ++++ Sources/System/IORequest.swift | 140 ++++++++++ Sources/System/IORing.swift | 350 +++++++++++++++++++++++++ Sources/System/IORingBuffer.swift | 0 Sources/System/IORingFileSlot.swift | 8 + Sources/System/Lock.swift | 37 +++ Sources/System/ManagedIORing.swift | 33 +++ 11 files changed, 692 insertions(+), 2 deletions(-) create mode 100644 Package.resolved create mode 100644 Sources/CSystem/include/io_uring.h create mode 100644 Sources/System/IOCompletion.swift create mode 100644 Sources/System/IORequest.swift create mode 100644 Sources/System/IORing.swift create mode 100644 Sources/System/IORingBuffer.swift create mode 100644 Sources/System/IORingFileSlot.swift create mode 100644 Sources/System/Lock.swift create mode 100644 Sources/System/ManagedIORing.swift diff --git a/Package.resolved b/Package.resolved new file mode 100644 index 00000000..b10a9832 --- /dev/null +++ b/Package.resolved @@ -0,0 +1,16 @@ +{ + "object": { + "pins": [ + { + "package": "swift-atomics", + "repositoryURL": "https://github.com/apple/swift-atomics", + "state": { + "branch": null, + "revision": "6c89474e62719ddcc1e9614989fff2f68208fe10", + "version": "1.1.0" + } + } + ] + }, + "version": 1 +} diff --git a/Package.swift b/Package.swift index b081a872..60f1a562 100644 --- a/Package.swift +++ b/Package.swift @@ -17,14 +17,19 @@ let package = Package( products: [ .library(name: "SystemPackage", targets: ["SystemPackage"]), ], - dependencies: [], + dependencies: [ + .package(url: "https://github.com/apple/swift-atomics", from: "1.1.0") + ], targets: [ .target( name: "CSystem", dependencies: []), .target( name: "SystemPackage", - dependencies: ["CSystem"], + dependencies: [ + "CSystem", + .product(name: "Atomics", package: "swift-atomics") + ], path: "Sources/System", cSettings: [ .define("_CRT_SECURE_NO_WARNINGS") diff --git a/Sources/CSystem/include/CSystemLinux.h b/Sources/CSystem/include/CSystemLinux.h index b172d658..6489c4f3 100644 --- a/Sources/CSystem/include/CSystemLinux.h +++ b/Sources/CSystem/include/CSystemLinux.h @@ -21,5 +21,6 @@ #include #include #include +#include "io_uring.h" #endif diff --git a/Sources/CSystem/include/io_uring.h b/Sources/CSystem/include/io_uring.h new file mode 100644 index 00000000..9e3d9fb3 --- /dev/null +++ b/Sources/CSystem/include/io_uring.h @@ -0,0 +1,50 @@ +#include +#include +#include + +#include +#include + +#ifdef __alpha__ +/* + * alpha is the only exception, all other architectures + * have common numbers for new system calls. + */ +# ifndef __NR_io_uring_setup +# define __NR_io_uring_setup 535 +# endif +# ifndef __NR_io_uring_enter +# define __NR_io_uring_enter 536 +# endif +# ifndef __NR_io_uring_register +# define __NR_io_uring_register 537 +# endif +#else /* !__alpha__ */ +# ifndef __NR_io_uring_setup +# define __NR_io_uring_setup 425 +# endif +# ifndef __NR_io_uring_enter +# define __NR_io_uring_enter 426 +# endif +# ifndef __NR_io_uring_register +# define __NR_io_uring_register 427 +# endif +#endif + +int io_uring_register(int fd, unsigned int opcode, void *arg, + unsigned int nr_args) +{ + return syscall(__NR_io_uring_register, fd, opcode, arg, nr_args); +} + +int io_uring_setup(unsigned int entries, struct io_uring_params *p) +{ + return syscall(__NR_io_uring_setup, entries, p); +} + +int io_uring_enter(int fd, unsigned int to_submit, unsigned int min_complete, + unsigned int flags, sigset_t *sig) +{ + return syscall(__NR_io_uring_enter, fd, to_submit, min_complete, + flags, sig, _NSIG / 8); +} diff --git a/Sources/System/IOCompletion.swift b/Sources/System/IOCompletion.swift new file mode 100644 index 00000000..5e226322 --- /dev/null +++ b/Sources/System/IOCompletion.swift @@ -0,0 +1,50 @@ +@_implementationOnly import CSystem + +public struct IOCompletion { + let rawValue: io_uring_cqe +} + +extension IOCompletion { + public struct Flags: OptionSet, Hashable, Codable { + public let rawValue: UInt32 + + public init(rawValue: UInt32) { + self.rawValue = rawValue + } + + public static let allocatedBuffer = Flags(rawValue: 1 << 0) + public static let moreCompletions = Flags(rawValue: 1 << 1) + public static let socketNotEmpty = Flags(rawValue: 1 << 2) + public static let isNotificationEvent = Flags(rawValue: 1 << 3) + } +} + +extension IOCompletion { + public var userData: UInt64 { + get { + return rawValue.user_data + } + } + + public var result: Int32 { + get { + return rawValue.res + } + } + + public var flags: IOCompletion.Flags { + get { + return Flags(rawValue: rawValue.flags & 0x0000FFFF) + } + } + + public var bufferIndex: UInt16? { + get { + if self.flags.contains(.allocatedBuffer) { + return UInt16(rawValue.flags >> 16) + } else { + return nil + } + } + } +} diff --git a/Sources/System/IORequest.swift b/Sources/System/IORequest.swift new file mode 100644 index 00000000..efa03f3b --- /dev/null +++ b/Sources/System/IORequest.swift @@ -0,0 +1,140 @@ +@_implementationOnly import CSystem + +public struct IORequest { + internal var rawValue: io_uring_sqe + + public init() { + self.rawValue = io_uring_sqe() + } +} + +extension IORequest { + public enum Operation: UInt8 { + case nop = 0 + case readv = 1 + case writev = 2 + case fsync = 3 + case readFixed = 4 + case writeFixed = 5 + case pollAdd = 6 + case pollRemove = 7 + case syncFileRange = 8 + case sendMessage = 9 + case receiveMessage = 10 + // ... + case openAt = 18 + case read = 22 + case write = 23 + case openAt2 = 28 + + } + + public struct Flags: OptionSet, Hashable, Codable { + public let rawValue: UInt8 + + public init(rawValue: UInt8) { + self.rawValue = rawValue + } + + public static let fixedFile = Flags(rawValue: 1 << 0) + public static let drainQueue = Flags(rawValue: 1 << 1) + public static let linkRequest = Flags(rawValue: 1 << 2) + public static let hardlinkRequest = Flags(rawValue: 1 << 3) + public static let asynchronous = Flags(rawValue: 1 << 4) + public static let selectBuffer = Flags(rawValue: 1 << 5) + public static let skipSuccess = Flags(rawValue: 1 << 6) + } + + public var operation: Operation { + get { Operation(rawValue: rawValue.opcode)! } + set { rawValue.opcode = newValue.rawValue } + } + + public var flags: Flags { + get { Flags(rawValue: rawValue.flags) } + set { rawValue.flags = newValue.rawValue } + } + + public var fileDescriptor: FileDescriptor { + get { FileDescriptor(rawValue: rawValue.fd) } + set { rawValue.fd = newValue.rawValue } + } + + public var offset: UInt64? { + get { + if (rawValue.off == UInt64.max) { + return nil + } else { + return rawValue.off + } + } + set { + if let val = newValue { + rawValue.off = val + } else { + rawValue.off = UInt64.max + } + } + } + + public var buffer: UnsafeMutableRawBufferPointer { + get { + let ptr = UnsafeMutableRawPointer(bitPattern: UInt(exactly: rawValue.addr)!) + return UnsafeMutableRawBufferPointer(start: ptr, count: Int(rawValue.len)) + } + + set { + // TODO: cleanup? + rawValue.addr = UInt64(Int(bitPattern: newValue.baseAddress!)) + rawValue.len = UInt32(exactly: newValue.count)! + } + } +} + +extension IORequest { + static func nop() -> IORequest { + var req = IORequest() + req.operation = .nop + return req + } + + static func read( + from fileDescriptor: FileDescriptor, + into buffer: UnsafeMutableRawBufferPointer, + at offset: UInt64? = nil + ) -> IORequest { + var req = IORequest.readWrite( + op: Operation.read, + fd: fileDescriptor, + buffer: buffer, + offset: offset + ) + fatalError() + } + + static func read( + fixedFile: Int // TODO: AsyncFileDescriptor + ) -> IORequest { + fatalError() + } + + static func write( + + ) -> IORequest { + fatalError() + } + + internal static func readWrite( + op: Operation, + fd: FileDescriptor, + buffer: UnsafeMutableRawBufferPointer, + offset: UInt64? = nil + ) -> IORequest { + var req = IORequest() + req.operation = op + req.fileDescriptor = fd + req.offset = offset + req.buffer = buffer + return req + } +} \ No newline at end of file diff --git a/Sources/System/IORing.swift b/Sources/System/IORing.swift new file mode 100644 index 00000000..6945d8c0 --- /dev/null +++ b/Sources/System/IORing.swift @@ -0,0 +1,350 @@ +@_implementationOnly import CSystem +import Glibc +import Atomics + +// XXX: this *really* shouldn't be here. oh well. +extension UnsafeMutableRawPointer { + func advanced(by offset: UInt32) -> UnsafeMutableRawPointer { + return advanced(by: Int(offset)) + } +} + +// all pointers in this struct reference kernel-visible memory +struct SQRing { + let kernelHead: UnsafeAtomic + let kernelTail: UnsafeAtomic + var userTail: UInt32 + + // from liburing: the kernel should never change these + // might change in the future with resizable rings? + let ringMask: UInt32 + // let ringEntries: UInt32 - absorbed into array.count + + // ring flags bitfield + // currently used by the kernel only in SQPOLL mode to indicate + // when the polling thread needs to be woken up + let flags: UnsafeAtomic + + // ring array + // maps indexes between the actual ring and the submissionQueueEntries list, + // allowing the latter to be used as a kind of freelist with enough work? + // currently, just 1:1 mapping (0.. +} + +struct CQRing { + let kernelHead: UnsafeAtomic + let kernelTail: UnsafeAtomic + + // TODO: determine if this is actually used + var userHead: UInt32 + + let ringMask: UInt32 + + let cqes: UnsafeBufferPointer +} + +// XXX: This should be a non-copyable type (?) +// demo only runs on Swift 5.8.1 +public final class IORing: Sendable { + let ringFlags: UInt32 + let ringDescriptor: Int32 + + var submissionRing: SQRing + var submissionMutex: Mutex + // FEAT: set this eventually + let submissionPolling: Bool = false + + var completionRing: CQRing + var completionMutex: Mutex + + let submissionQueueEntries: UnsafeMutableBufferPointer + + var registeredFiles: UnsafeMutableBufferPointer? + + // kept around for unmap / cleanup + let ringSize: Int + let ringPtr: UnsafeMutableRawPointer + + public init(queueDepth: UInt32) throws { + var params = io_uring_params() + + ringDescriptor = withUnsafeMutablePointer(to: ¶ms) { + return io_uring_setup(queueDepth, $0); + } + + if (params.features & IORING_FEAT_SINGLE_MMAP == 0 + || params.features & IORING_FEAT_NODROP == 0) { + close(ringDescriptor) + // TODO: error handling + fatalError("kernel not new enough") + } + + if (ringDescriptor < 0) { + // TODO: error handling + } + + let submitRingSize = params.sq_off.array + + params.sq_entries * UInt32(MemoryLayout.size) + + let completionRingSize = params.cq_off.cqes + + params.cq_entries * UInt32(MemoryLayout.size) + + ringSize = Int(max(submitRingSize, completionRingSize)) + + ringPtr = mmap( + /* addr: */ nil, + /* len: */ ringSize, + /* prot: */ PROT_READ | PROT_WRITE, + /* flags: */ MAP_SHARED | MAP_POPULATE, + /* fd: */ ringDescriptor, + /* offset: */ __off_t(IORING_OFF_SQ_RING) + ); + + if (ringPtr == MAP_FAILED) { + perror("mmap"); + // TODO: error handling + fatalError() + } + + let kernelHead = UnsafeAtomic(at: + ringPtr.advanced(by: params.sq_off.head) + .assumingMemoryBound(to: UInt32.AtomicRepresentation.self) + ) + + submissionRing = SQRing( + kernelHead: UnsafeAtomic( + at: ringPtr.advanced(by: params.sq_off.head) + .assumingMemoryBound(to: UInt32.AtomicRepresentation.self) + ), + kernelTail: UnsafeAtomic( + at: ringPtr.advanced(by: params.sq_off.tail) + .assumingMemoryBound(to: UInt32.AtomicRepresentation.self) + ), + userTail: 0, // no requests yet + ringMask: ringPtr.advanced(by: params.sq_off.ring_mask) + .assumingMemoryBound(to: UInt32.self).pointee, + flags: UnsafeAtomic( + at: ringPtr.advanced(by: params.sq_off.flags) + .assumingMemoryBound(to: UInt32.AtomicRepresentation.self) + ), + array: UnsafeMutableBufferPointer( + start: ringPtr.advanced(by: params.sq_off.array) + .assumingMemoryBound(to: UInt32.self), + count: Int(ringPtr.advanced(by: params.sq_off.ring_entries) + .assumingMemoryBound(to: UInt32.self).pointee) + ) + ) + + // fill submission ring array with 1:1 map to underlying SQEs + for i in 0...size, + /* prot: */ PROT_READ | PROT_WRITE, + /* flags: */ MAP_SHARED | MAP_POPULATE, + /* fd: */ ringDescriptor, + /* offset: */ __off_t(IORING_OFF_SQES) + ); + + if (sqes == MAP_FAILED) { + perror("mmap"); + // TODO: error handling + fatalError() + } + + submissionQueueEntries = UnsafeMutableBufferPointer( + start: sqes!.assumingMemoryBound(to: io_uring_sqe.self), + count: Int(params.sq_entries) + ) + + completionRing = CQRing( + kernelHead: UnsafeAtomic( + at: ringPtr.advanced(by: params.cq_off.head) + .assumingMemoryBound(to: UInt32.AtomicRepresentation.self) + ), + kernelTail: UnsafeAtomic( + at: ringPtr.advanced(by: params.cq_off.tail) + .assumingMemoryBound(to: UInt32.AtomicRepresentation.self) + ), + userHead: 0, // no completions yet + ringMask: ringPtr.advanced(by: params.cq_off.ring_mask) + .assumingMemoryBound(to: UInt32.self).pointee, + cqes: UnsafeBufferPointer( + start: ringPtr.advanced(by: params.cq_off.cqes) + .assumingMemoryBound(to: io_uring_cqe.self), + count: Int(ringPtr.advanced(by: params.cq_off.ring_entries) + .assumingMemoryBound(to: UInt32.self).pointee) + ) + ) + + self.submissionMutex = Mutex() + self.completionMutex = Mutex() + + self.ringFlags = params.flags + } + + func blockingConsumeCompletion() -> IOCompletion { + self.completionMutex.lock() + defer { self.completionMutex.unlock() } + + if let completion = _tryConsumeCompletion() { + return completion + } else { + _waitForCompletion() + return _tryConsumeCompletion().unsafelyUnwrapped + } + } + + func _waitForCompletion() { + // TODO: error handling + io_uring_enter(ringDescriptor, 0, 1, IORING_ENTER_GETEVENTS, nil) + } + + func tryConsumeCompletion() -> IOCompletion? { + self.completionMutex.lock() + defer { self.completionMutex.unlock() } + return _tryConsumeCompletion() + } + + func _tryConsumeCompletion() -> IOCompletion? { + let tail = completionRing.kernelTail.load(ordering: .acquiring) + var head = completionRing.kernelHead.load(ordering: .relaxed) + + if tail != head { + // 32 byte copy - oh well + let res = completionRing.cqes[Int(head & completionRing.ringMask)] + completionRing.kernelHead.store(head + 1, ordering: .relaxed) + return IOCompletion(rawValue: res) + } + + return nil + } + + + func registerFiles(count: UInt32) { + // TODO: implement + guard self.registeredFiles == nil else { fatalError() } + let fileBuf = UnsafeMutableBufferPointer.allocate(capacity: Int(count)) + fileBuf.initialize(repeating: UInt32.max) + io_uring_register( + self.ringDescriptor, + IORING_REGISTER_FILES, + fileBuf.baseAddress!, + count + ) + // TODO: error handling + self.registeredFiles = fileBuf + } + + func unregisterFiles() { + if self.registeredFiles != nil { + io_uring_register( + self.ringDescriptor, + IORING_UNREGISTER_FILES, + self.registeredFiles!.baseAddress!, + UInt32(self.registeredFiles!.count) + ) + // TODO: error handling + self.registeredFiles!.deallocate() + self.registeredFiles = nil + } + } + + // register a group of buffers + func registerBuffers(bufSize: UInt32, count: UInt32) { + // + + } + + func getBuffer() -> (index: Int, buf: UnsafeRawBufferPointer) { + fatalError() + } + + // TODO: types + func submitRequests() { + self.submissionMutex.lock() + defer { self.submissionMutex.unlock() } + self._submitRequests() + } + + func _submitRequests() { + let flushedEvents = _flushQueue() + + // Ring always needs enter right now; + // TODO: support SQPOLL here + + let ret = io_uring_enter(ringDescriptor, flushedEvents, 0, 0, nil) + // TODO: handle errors + } + + internal func _flushQueue() -> UInt32 { + self.submissionRing.kernelTail.store( + self.submissionRing.userTail, ordering: .relaxed + ) + return self.submissionRing.userTail - + self.submissionRing.kernelHead.load(ordering: .relaxed) + } + + + func writeRequest(_ request: __owned IORequest) -> Bool { + self.submissionMutex.lock() + defer { self.submissionMutex.unlock() } + return _writeRequest(request) + } + + internal func _writeRequest(_ request: __owned IORequest) -> Bool { + if let entry = _getSubmissionEntry() { + entry.pointee = request.rawValue + return true + } + return false + } + + internal func _blockingGetSubmissionEntry() -> UnsafeMutablePointer { + while true { + if let entry = _getSubmissionEntry() { + return entry + } + // TODO: actually block here instead of spinning + } + + } + + internal func _getSubmissionEntry() -> UnsafeMutablePointer? { + let next = self.submissionRing.userTail + 1 + + // FEAT: smp load when SQPOLL in use (not in MVP) + let kernelHead = self.submissionRing.kernelHead.load(ordering: .relaxed) + + // FEAT: 128-bit event support (not in MVP) + if (next - kernelHead <= self.submissionRing.array.count) { + // let sqe = &sq->sqes[(sq->sqe_tail & sq->ring_mask) << shift]; + let sqeIndex = Int( + self.submissionRing.userTail & self.submissionRing.ringMask + ) + + let sqe = self.submissionQueueEntries + .baseAddress.unsafelyUnwrapped + .advanced(by: sqeIndex) + + self.submissionRing.userTail = next; + return sqe + } + return nil + } + + deinit { + munmap(ringPtr, ringSize); + munmap( + UnsafeMutableRawPointer(submissionQueueEntries.baseAddress!), + submissionQueueEntries.count * MemoryLayout.size + ) + close(ringDescriptor) + } +}; + diff --git a/Sources/System/IORingBuffer.swift b/Sources/System/IORingBuffer.swift new file mode 100644 index 00000000..e69de29b diff --git a/Sources/System/IORingFileSlot.swift b/Sources/System/IORingFileSlot.swift new file mode 100644 index 00000000..d0a7c666 --- /dev/null +++ b/Sources/System/IORingFileSlot.swift @@ -0,0 +1,8 @@ +class IORingFileSlot { + + + deinit { + // return file slot + + } +} \ No newline at end of file diff --git a/Sources/System/Lock.swift b/Sources/System/Lock.swift new file mode 100644 index 00000000..c5204ba0 --- /dev/null +++ b/Sources/System/Lock.swift @@ -0,0 +1,37 @@ +// TODO: write against kernel APIs directly? +import Glibc + +public final class Mutex { + @usableFromInline let mutex: UnsafeMutablePointer + + @inlinable init() { + self.mutex = UnsafeMutablePointer.allocate(capacity: 1) + self.mutex.initialize(to: pthread_mutex_t()) + pthread_mutex_init(self.mutex, nil) + } + + @inlinable deinit { + defer { mutex.deallocate() } + guard pthread_mutex_destroy(mutex) == 0 else { + preconditionFailure("unable to destroy mutex") + } + } + + // XXX: this is because we need to lock the mutex in the context of a submit() function + // and unlock *before* the UnsafeContinuation returns. + // Code looks like: { + // // prepare request + // io_uring_get_sqe() + // io_uring_prep_foo(...) + // return await withUnsafeContinuation { + // sqe->user_data = ...; io_uring_submit(); unlock(); + // } + // } + @inlinable @inline(__always) public func lock() { + pthread_mutex_lock(mutex) + } + + @inlinable @inline(__always) public func unlock() { + pthread_mutex_unlock(mutex) + } +} diff --git a/Sources/System/ManagedIORing.swift b/Sources/System/ManagedIORing.swift new file mode 100644 index 00000000..32c84faf --- /dev/null +++ b/Sources/System/ManagedIORing.swift @@ -0,0 +1,33 @@ +final public class ManagedIORing: @unchecked Sendable { + var internalRing: IORing + + init(queueDepth: UInt32) throws { + self.internalRing = try IORing(queueDepth: queueDepth) + self.startWaiter() + } + + private func startWaiter() { + Task.detached { + while (!Task.isCancelled) { + let cqe = self.internalRing.blockingConsumeCompletion() + + let cont = unsafeBitCast(cqe.userData, to: UnsafeContinuation.self) + cont.resume(returning: cqe) + } + } + } + + @_unsafeInheritExecutor + public func submitAndWait(_ request: __owned IORequest) async -> IOCompletion { + self.internalRing.submissionMutex.lock() + return await withUnsafeContinuation { cont in + let entry = internalRing._blockingGetSubmissionEntry() + entry.pointee = request.rawValue + entry.pointee.user_data = unsafeBitCast(cont, to: UInt64.self) + self.internalRing._submitRequests() + self.internalRing.submissionMutex.unlock() + } + } + + +} \ No newline at end of file From 6338029b74b84b79795a0cc24bcc189883ec083d Mon Sep 17 00:00:00 2001 From: Lucy Satheesan Date: Mon, 31 Jul 2023 22:28:43 -0700 Subject: [PATCH 136/427] stage 2: IORequest enum --- Sources/System/IORequest.swift | 206 ++++++++++---------------- Sources/System/IORing.swift | 218 +++++++++++++++++++++------- Sources/System/IORingBuffer.swift | 0 Sources/System/IORingError.swift | 3 + Sources/System/IORingFileSlot.swift | 8 - Sources/System/Lock.swift | 2 +- Sources/System/ManagedIORing.swift | 11 +- Sources/System/RawIORequest.swift | 139 ++++++++++++++++++ 8 files changed, 396 insertions(+), 191 deletions(-) delete mode 100644 Sources/System/IORingBuffer.swift create mode 100644 Sources/System/IORingError.swift delete mode 100644 Sources/System/IORingFileSlot.swift create mode 100644 Sources/System/RawIORequest.swift diff --git a/Sources/System/IORequest.swift b/Sources/System/IORequest.swift index efa03f3b..6b26a4d1 100644 --- a/Sources/System/IORequest.swift +++ b/Sources/System/IORequest.swift @@ -1,140 +1,82 @@ -@_implementationOnly import CSystem - -public struct IORequest { - internal var rawValue: io_uring_sqe +import struct CSystem.io_uring_sqe + +public enum IORequest { + case nop // nothing here + case openat( + atDirectory: FileDescriptor, + path: UnsafePointer, + FileDescriptor.AccessMode, + options: FileDescriptor.OpenOptions = FileDescriptor.OpenOptions(), + permissions: FilePermissions? = nil, + intoSlot: IORingFileSlot? = nil + ) + case read( + file: File, + buffer: Buffer, + offset: UInt64 = 0 + ) + case write( + file: File, + buffer: Buffer, + offset: UInt64 = 0 + ) + + public enum Buffer { + case registered(IORingBuffer) + case unregistered(UnsafeMutableRawBufferPointer) + } - public init() { - self.rawValue = io_uring_sqe() + public enum File { + case registered(IORingFileSlot) + case unregistered(FileDescriptor) } } extension IORequest { - public enum Operation: UInt8 { - case nop = 0 - case readv = 1 - case writev = 2 - case fsync = 3 - case readFixed = 4 - case writeFixed = 5 - case pollAdd = 6 - case pollRemove = 7 - case syncFileRange = 8 - case sendMessage = 9 - case receiveMessage = 10 - // ... - case openAt = 18 - case read = 22 - case write = 23 - case openAt2 = 28 - - } - - public struct Flags: OptionSet, Hashable, Codable { - public let rawValue: UInt8 - - public init(rawValue: UInt8) { - self.rawValue = rawValue - } - - public static let fixedFile = Flags(rawValue: 1 << 0) - public static let drainQueue = Flags(rawValue: 1 << 1) - public static let linkRequest = Flags(rawValue: 1 << 2) - public static let hardlinkRequest = Flags(rawValue: 1 << 3) - public static let asynchronous = Flags(rawValue: 1 << 4) - public static let selectBuffer = Flags(rawValue: 1 << 5) - public static let skipSuccess = Flags(rawValue: 1 << 6) - } - - public var operation: Operation { - get { Operation(rawValue: rawValue.opcode)! } - set { rawValue.opcode = newValue.rawValue } - } - - public var flags: Flags { - get { Flags(rawValue: rawValue.flags) } - set { rawValue.flags = newValue.rawValue } - } - - public var fileDescriptor: FileDescriptor { - get { FileDescriptor(rawValue: rawValue.fd) } - set { rawValue.fd = newValue.rawValue } - } - - public var offset: UInt64? { - get { - if (rawValue.off == UInt64.max) { - return nil - } else { - return rawValue.off - } - } - set { - if let val = newValue { - rawValue.off = val - } else { - rawValue.off = UInt64.max - } - } - } - - public var buffer: UnsafeMutableRawBufferPointer { - get { - let ptr = UnsafeMutableRawPointer(bitPattern: UInt(exactly: rawValue.addr)!) - return UnsafeMutableRawBufferPointer(start: ptr, count: Int(rawValue.len)) - } - - set { - // TODO: cleanup? - rawValue.addr = UInt64(Int(bitPattern: newValue.baseAddress!)) - rawValue.len = UInt32(exactly: newValue.count)! + @inlinable @inline(__always) + public func makeRawRequest() -> RawIORequest { + var request = RawIORequest() + switch self { + case .nop: + request.operation = .nop + case .openat(let atDirectory, let path, let mode, let options, let permissions, let slot): + // TODO: use rawValue less + request.operation = .openAt + request.fileDescriptor = atDirectory + request.rawValue.addr = unsafeBitCast(path, to: UInt64.self) + request.rawValue.open_flags = UInt32(bitPattern: options.rawValue | mode.rawValue) + request.rawValue.len = permissions?.rawValue ?? 0 + request.rawValue.file_index = UInt32(slot?.index ?? 0) + case .read(let file, let buffer, let offset), .write(let file, let buffer, let offset): + if case .read = self { + if case .registered = buffer { + request.operation = .readFixed + } else { + request.operation = .read + } + } else { + if case .registered = buffer { + request.operation = .writeFixed + } else { + request.operation = .write + } + } + switch file { + case .registered(let regFile): + request.rawValue.fd = Int32(exactly: regFile.index)! + request.flags = .fixedFile + case .unregistered(let fd): + request.fileDescriptor = fd + } + switch buffer { + case .registered(let regBuf): + request.buffer = regBuf.unsafeBuffer + request.rawValue.buf_index = UInt16(exactly: regBuf.index)! + case .unregistered(let buf): + request.buffer = buf + } + request.offset = offset } + return request } } - -extension IORequest { - static func nop() -> IORequest { - var req = IORequest() - req.operation = .nop - return req - } - - static func read( - from fileDescriptor: FileDescriptor, - into buffer: UnsafeMutableRawBufferPointer, - at offset: UInt64? = nil - ) -> IORequest { - var req = IORequest.readWrite( - op: Operation.read, - fd: fileDescriptor, - buffer: buffer, - offset: offset - ) - fatalError() - } - - static func read( - fixedFile: Int // TODO: AsyncFileDescriptor - ) -> IORequest { - fatalError() - } - - static func write( - - ) -> IORequest { - fatalError() - } - - internal static func readWrite( - op: Operation, - fd: FileDescriptor, - buffer: UnsafeMutableRawBufferPointer, - offset: UInt64? = nil - ) -> IORequest { - var req = IORequest() - req.operation = op - req.fileDescriptor = fd - req.offset = offset - req.buffer = buffer - return req - } -} \ No newline at end of file diff --git a/Sources/System/IORing.swift b/Sources/System/IORing.swift index 6945d8c0..0c2f1384 100644 --- a/Sources/System/IORing.swift +++ b/Sources/System/IORing.swift @@ -1,6 +1,8 @@ @_implementationOnly import CSystem -import Glibc -import Atomics +import struct CSystem.io_uring_sqe + +@_implementationOnly import Atomics +import Glibc // needed for mmap // XXX: this *really* shouldn't be here. oh well. extension UnsafeMutableRawPointer { @@ -10,7 +12,7 @@ extension UnsafeMutableRawPointer { } // all pointers in this struct reference kernel-visible memory -struct SQRing { +@usableFromInline struct SQRing { let kernelHead: UnsafeAtomic let kernelTail: UnsafeAtomic var userTail: UInt32 @@ -44,14 +46,90 @@ struct CQRing { let cqes: UnsafeBufferPointer } +internal class ResourceManager: @unchecked Sendable { + typealias Resource = T + let resourceList: UnsafeMutableBufferPointer + var freeList: [Int] + let mutex: Mutex + + init(_ res: UnsafeMutableBufferPointer) { + self.resourceList = res + self.freeList = [Int](resourceList.indices) + self.mutex = Mutex() + } + + func getResource() -> IOResource? { + self.mutex.lock() + defer { self.mutex.unlock() } + if let index = freeList.popLast() { + return IOResource( + rescource: resourceList[index], + index: index, + manager: self + ) + } else { + return nil + } + } + + func releaseResource(index: Int) { + self.mutex.lock() + defer { self.mutex.unlock() } + self.freeList.append(index) + } +} + +public class IOResource { + typealias Resource = T + @usableFromInline let resource: T + @usableFromInline let index: Int + let manager: ResourceManager + + internal init( + rescource: T, + index: Int, + manager: ResourceManager + ) { + self.resource = rescource + self.index = index + self.manager = manager + } + + func withResource() { + + } + + deinit { + self.manager.releaseResource(index: self.index) + } +} + +public typealias IORingFileSlot = IOResource +public typealias IORingBuffer = IOResource + +extension IORingFileSlot { + public var unsafeFileSlot: Int { + return index + } +} +extension IORingBuffer { + public var unsafeBuffer: UnsafeMutableRawBufferPointer { + get { + return .init(start: resource.iov_base, count: resource.iov_len) + } + } +} + + + // XXX: This should be a non-copyable type (?) // demo only runs on Swift 5.8.1 -public final class IORing: Sendable { +public final class IORing: @unchecked Sendable { let ringFlags: UInt32 let ringDescriptor: Int32 - var submissionRing: SQRing - var submissionMutex: Mutex + @usableFromInline var submissionRing: SQRing + @usableFromInline var submissionMutex: Mutex // FEAT: set this eventually let submissionPolling: Bool = false @@ -59,13 +137,14 @@ public final class IORing: Sendable { var completionMutex: Mutex let submissionQueueEntries: UnsafeMutableBufferPointer - - var registeredFiles: UnsafeMutableBufferPointer? // kept around for unmap / cleanup let ringSize: Int let ringPtr: UnsafeMutableRawPointer + var registeredFiles: ResourceManager? + var registeredBuffers: ResourceManager? + public init(queueDepth: UInt32) throws { var params = io_uring_params() @@ -77,7 +156,7 @@ public final class IORing: Sendable { || params.features & IORING_FEAT_NODROP == 0) { close(ringDescriptor) // TODO: error handling - fatalError("kernel not new enough") + throw IORingError.missingRequiredFeatures } if (ringDescriptor < 0) { @@ -104,14 +183,9 @@ public final class IORing: Sendable { if (ringPtr == MAP_FAILED) { perror("mmap"); // TODO: error handling - fatalError() + fatalError("mmap failed in ring setup") } - let kernelHead = UnsafeAtomic(at: - ringPtr.advanced(by: params.sq_off.head) - .assumingMemoryBound(to: UInt32.AtomicRepresentation.self) - ) - submissionRing = SQRing( kernelHead: UnsafeAtomic( at: ringPtr.advanced(by: params.sq_off.head) @@ -154,7 +228,7 @@ public final class IORing: Sendable { if (sqes == MAP_FAILED) { perror("mmap"); // TODO: error handling - fatalError() + fatalError("sqe mmap failed in ring setup") } submissionQueueEntries = UnsafeMutableBufferPointer( @@ -195,16 +269,29 @@ public final class IORing: Sendable { if let completion = _tryConsumeCompletion() { return completion } else { - _waitForCompletion() + while true { + let res = io_uring_enter(ringDescriptor, 0, 1, IORING_ENTER_GETEVENTS, nil) + // error handling: + // EAGAIN / EINTR (try again), + // EBADF / EBADFD / EOPNOTSUPP / ENXIO + // (failure in ring lifetime management, fatal), + // EINVAL (bad constant flag?, fatal), + // EFAULT (bad address for argument from library, fatal) + // EBUSY (not enough space for events; implies events filled + // by kernel between kernelTail load and now) + if res >= 0 || res == -EBUSY { + break + } else if res == -EAGAIN || res == -EINTR { + continue + } + fatalError("fatal error in receiving requests: " + + Errno(rawValue: -res).debugDescription + ) + } return _tryConsumeCompletion().unsafelyUnwrapped } } - func _waitForCompletion() { - // TODO: error handling - io_uring_enter(ringDescriptor, 0, 1, IORING_ENTER_GETEVENTS, nil) - } - func tryConsumeCompletion() -> IOCompletion? { self.completionMutex.lock() defer { self.completionMutex.unlock() } @@ -213,7 +300,7 @@ public final class IORing: Sendable { func _tryConsumeCompletion() -> IOCompletion? { let tail = completionRing.kernelTail.load(ordering: .acquiring) - var head = completionRing.kernelHead.load(ordering: .relaxed) + let head = completionRing.kernelHead.load(ordering: .relaxed) if tail != head { // 32 byte copy - oh well @@ -227,7 +314,6 @@ public final class IORing: Sendable { func registerFiles(count: UInt32) { - // TODO: implement guard self.registeredFiles == nil else { fatalError() } let fileBuf = UnsafeMutableBufferPointer.allocate(capacity: Int(count)) fileBuf.initialize(repeating: UInt32.max) @@ -238,31 +324,43 @@ public final class IORing: Sendable { count ) // TODO: error handling - self.registeredFiles = fileBuf + self.registeredFiles = ResourceManager(fileBuf) } func unregisterFiles() { - if self.registeredFiles != nil { - io_uring_register( - self.ringDescriptor, - IORING_UNREGISTER_FILES, - self.registeredFiles!.baseAddress!, - UInt32(self.registeredFiles!.count) - ) - // TODO: error handling - self.registeredFiles!.deallocate() - self.registeredFiles = nil - } + fatalError("failed to unregister files") + } + + func getFile() -> IORingFileSlot? { + return self.registeredFiles?.getResource() } // register a group of buffers func registerBuffers(bufSize: UInt32, count: UInt32) { - // + let iovecs = UnsafeMutableBufferPointer.allocate(capacity: Int(count)) + let intBufSize = Int(bufSize) + for i in 0.. IORingBuffer? { + return self.registeredBuffers?.getResource() } - func getBuffer() -> (index: Int, buf: UnsafeRawBufferPointer) { - fatalError() + func unregisterBuffers() { + fatalError("failed to unregister buffers: TODO") } // TODO: types @@ -277,9 +375,22 @@ public final class IORing: Sendable { // Ring always needs enter right now; // TODO: support SQPOLL here - - let ret = io_uring_enter(ringDescriptor, flushedEvents, 0, 0, nil) - // TODO: handle errors + while true { + let ret = io_uring_enter(ringDescriptor, flushedEvents, 0, 0, nil) + // error handling: + // EAGAIN / EINTR (try again), + // EBADF / EBADFD / EOPNOTSUPP / ENXIO + // (failure in ring lifetime management, fatal), + // EINVAL (bad constant flag?, fatal), + // EFAULT (bad address for argument from library, fatal) + if ret == -EAGAIN || ret == -EINTR { + continue + } else if ret < 0 { + fatalError("fatal error in submitting requests: " + + Errno(rawValue: -ret).debugDescription + ) + } + } } internal func _flushQueue() -> UInt32 { @@ -291,20 +402,28 @@ public final class IORing: Sendable { } + @inlinable @inline(__always) func writeRequest(_ request: __owned IORequest) -> Bool { self.submissionMutex.lock() defer { self.submissionMutex.unlock() } - return _writeRequest(request) + return _writeRequest(request.makeRawRequest()) } - internal func _writeRequest(_ request: __owned IORequest) -> Bool { - if let entry = _getSubmissionEntry() { - entry.pointee = request.rawValue - return true - } - return false + @inlinable @inline(__always) + func writeAndSubmit(_ request: __owned IORequest) -> Bool { + self.submissionMutex.lock() + defer { self.submissionMutex.unlock() } + return _writeRequest(request.makeRawRequest()) + } + + @inlinable @inline(__always) + internal func _writeRequest(_ request: __owned RawIORequest) -> Bool { + let entry = _blockingGetSubmissionEntry() + entry.pointee = request.rawValue + return true } + @inlinable @inline(__always) internal func _blockingGetSubmissionEntry() -> UnsafeMutablePointer { while true { if let entry = _getSubmissionEntry() { @@ -315,6 +434,7 @@ public final class IORing: Sendable { } + @usableFromInline @inline(__always) internal func _getSubmissionEntry() -> UnsafeMutablePointer? { let next = self.submissionRing.userTail + 1 diff --git a/Sources/System/IORingBuffer.swift b/Sources/System/IORingBuffer.swift deleted file mode 100644 index e69de29b..00000000 diff --git a/Sources/System/IORingError.swift b/Sources/System/IORingError.swift new file mode 100644 index 00000000..d87b2938 --- /dev/null +++ b/Sources/System/IORingError.swift @@ -0,0 +1,3 @@ +enum IORingError: Error { + case missingRequiredFeatures +} diff --git a/Sources/System/IORingFileSlot.swift b/Sources/System/IORingFileSlot.swift deleted file mode 100644 index d0a7c666..00000000 --- a/Sources/System/IORingFileSlot.swift +++ /dev/null @@ -1,8 +0,0 @@ -class IORingFileSlot { - - - deinit { - // return file slot - - } -} \ No newline at end of file diff --git a/Sources/System/Lock.swift b/Sources/System/Lock.swift index c5204ba0..fd20c641 100644 --- a/Sources/System/Lock.swift +++ b/Sources/System/Lock.swift @@ -1,7 +1,7 @@ // TODO: write against kernel APIs directly? import Glibc -public final class Mutex { +@usableFromInline final class Mutex { @usableFromInline let mutex: UnsafeMutablePointer @inlinable init() { diff --git a/Sources/System/ManagedIORing.swift b/Sources/System/ManagedIORing.swift index 32c84faf..580eacc1 100644 --- a/Sources/System/ManagedIORing.swift +++ b/Sources/System/ManagedIORing.swift @@ -3,6 +3,8 @@ final public class ManagedIORing: @unchecked Sendable { init(queueDepth: UInt32) throws { self.internalRing = try IORing(queueDepth: queueDepth) + self.internalRing.registerBuffers(bufSize: 655336, count: 4) + self.internalRing.registerFiles(count: 32) self.startWaiter() } @@ -22,12 +24,19 @@ final public class ManagedIORing: @unchecked Sendable { self.internalRing.submissionMutex.lock() return await withUnsafeContinuation { cont in let entry = internalRing._blockingGetSubmissionEntry() - entry.pointee = request.rawValue + entry.pointee = request.makeRawRequest().rawValue entry.pointee.user_data = unsafeBitCast(cont, to: UInt64.self) self.internalRing._submitRequests() self.internalRing.submissionMutex.unlock() } } + internal func getFileSlot() -> IORingFileSlot? { + self.internalRing.getFile() + } + + internal func getBuffer() -> IORingBuffer? { + self.internalRing.getBuffer() + } } \ No newline at end of file diff --git a/Sources/System/RawIORequest.swift b/Sources/System/RawIORequest.swift new file mode 100644 index 00000000..fa50b439 --- /dev/null +++ b/Sources/System/RawIORequest.swift @@ -0,0 +1,139 @@ +// TODO: investigate @usableFromInline / @_implementationOnly dichotomy +@_implementationOnly import CSystem +import struct CSystem.io_uring_sqe + +public struct RawIORequest { + @usableFromInline var rawValue: io_uring_sqe + + public init() { + self.rawValue = io_uring_sqe() + } +} + +extension RawIORequest { + public enum Operation: UInt8 { + case nop = 0 + case readv = 1 + case writev = 2 + case fsync = 3 + case readFixed = 4 + case writeFixed = 5 + case pollAdd = 6 + case pollRemove = 7 + case syncFileRange = 8 + case sendMessage = 9 + case receiveMessage = 10 + // ... + case openAt = 18 + case read = 22 + case write = 23 + case openAt2 = 28 + + } + + public struct Flags: OptionSet, Hashable, Codable { + public let rawValue: UInt8 + + public init(rawValue: UInt8) { + self.rawValue = rawValue + } + + public static let fixedFile = Flags(rawValue: 1 << 0) + public static let drainQueue = Flags(rawValue: 1 << 1) + public static let linkRequest = Flags(rawValue: 1 << 2) + public static let hardlinkRequest = Flags(rawValue: 1 << 3) + public static let asynchronous = Flags(rawValue: 1 << 4) + public static let selectBuffer = Flags(rawValue: 1 << 5) + public static let skipSuccess = Flags(rawValue: 1 << 6) + } + + public var operation: Operation { + get { Operation(rawValue: rawValue.opcode)! } + set { rawValue.opcode = newValue.rawValue } + } + + public var flags: Flags { + get { Flags(rawValue: rawValue.flags) } + set { rawValue.flags = newValue.rawValue } + } + + public var fileDescriptor: FileDescriptor { + get { FileDescriptor(rawValue: rawValue.fd) } + set { rawValue.fd = newValue.rawValue } + } + + public var offset: UInt64? { + get { + if (rawValue.off == UInt64.max) { + return nil + } else { + return rawValue.off + } + } + set { + if let val = newValue { + rawValue.off = val + } else { + rawValue.off = UInt64.max + } + } + } + + public var buffer: UnsafeMutableRawBufferPointer { + get { + let ptr = UnsafeMutableRawPointer(bitPattern: UInt(exactly: rawValue.addr)!) + return UnsafeMutableRawBufferPointer(start: ptr, count: Int(rawValue.len)) + } + + set { + // TODO: cleanup? + rawValue.addr = UInt64(Int(bitPattern: newValue.baseAddress!)) + rawValue.len = UInt32(exactly: newValue.count)! + } + } + + public enum RequestFlags { + case readWriteFlags(ReadWriteFlags) + // case fsyncFlags(FsyncFlags?) + // poll_events + // poll32_events + // sync_range_flags + // msg_flags + // timeout_flags + // accept_flags + // cancel_flags + case openFlags(FileDescriptor.OpenOptions) + // statx_flags + // fadvise_advice + // splice_flags + } + + public struct ReadWriteFlags: OptionSet, Hashable, Codable { + public var rawValue: UInt32 + public init(rawValue: UInt32) { + self.rawValue = rawValue + } + + public static let highPriority = ReadWriteFlags(rawValue: 1 << 0) + + // sync with only data integrity + public static let dataSync = ReadWriteFlags(rawValue: 1 << 1) + + // sync with full data + file integrity + public static let fileSync = ReadWriteFlags(rawValue: 1 << 2) + + // return -EAGAIN if operation blocks + public static let noWait = ReadWriteFlags(rawValue: 1 << 3) + + // append to end of the file + public static let append = ReadWriteFlags(rawValue: 1 << 4) + } +} + +extension RawIORequest { + static func nop() -> RawIORequest { + var req = RawIORequest() + req.operation = .nop + return req + } +} \ No newline at end of file From 996e940942f976f7a2de8751375c21413812ac41 Mon Sep 17 00:00:00 2001 From: Lucy Satheesan Date: Mon, 31 Jul 2023 22:28:54 -0700 Subject: [PATCH 137/427] initial AsyncFileDescriptor work --- Sources/System/AsyncFileDescriptor.swift | 71 ++++++++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 Sources/System/AsyncFileDescriptor.swift diff --git a/Sources/System/AsyncFileDescriptor.swift b/Sources/System/AsyncFileDescriptor.swift new file mode 100644 index 00000000..97427812 --- /dev/null +++ b/Sources/System/AsyncFileDescriptor.swift @@ -0,0 +1,71 @@ +@_implementationOnly import CSystem + +public class AsyncFileDescriptor { + var open: Bool = true + @usableFromInline let fileSlot: IORingFileSlot + @usableFromInline let ring: ManagedIORing + + static func openat( + atDirectory: FileDescriptor = FileDescriptor(rawValue: AT_FDCWD), + path: FilePath, + _ mode: FileDescriptor.AccessMode, + options: FileDescriptor.OpenOptions = FileDescriptor.OpenOptions(), + permissions: FilePermissions? = nil, + onRing ring: ManagedIORing + ) async throws -> AsyncFileDescriptor { + // todo; real error type + guard let fileSlot = ring.getFileSlot() else { + throw IORingError.missingRequiredFeatures + } + let cstr = path.withCString { + return $0 // bad + } + let res = await ring.submitAndWait(.openat( + atDirectory: atDirectory, + path: cstr, + mode, + options: options, + permissions: permissions, intoSlot: fileSlot + )) + if res.result < 0 { + throw Errno(rawValue: -res.result) + } + + return AsyncFileDescriptor( + fileSlot, ring: ring + ) + } + + internal init(_ fileSlot: IORingFileSlot, ring: ManagedIORing) { + self.fileSlot = fileSlot + self.ring = ring + } + + func close() async throws { + self.open = false + fatalError() + } + + @inlinable @inline(__always) @_unsafeInheritExecutor + func read( + into buffer: IORequest.Buffer, + atAbsoluteOffset offset: UInt64 = UInt64.max + ) async throws -> UInt32 { + let res = await ring.submitAndWait(.read( + file: .registered(self.fileSlot), + buffer: buffer, + offset: offset + )) + if res.result < 0 { + throw Errno(rawValue: -res.result) + } else { + return UInt32(bitPattern: res.result) + } + } + + deinit { + if (self.open) { + // TODO: close + } + } +} From 1f821a8539af62aab858f356ab9aa21592742ccb Mon Sep 17 00:00:00 2001 From: Lucy Satheesan Date: Tue, 8 Aug 2023 09:44:32 -0700 Subject: [PATCH 138/427] AsyncSequence draft implementation --- Sources/System/AsyncFileDescriptor.swift | 52 ++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/Sources/System/AsyncFileDescriptor.swift b/Sources/System/AsyncFileDescriptor.swift index 97427812..6e39e055 100644 --- a/Sources/System/AsyncFileDescriptor.swift +++ b/Sources/System/AsyncFileDescriptor.swift @@ -69,3 +69,55 @@ public class AsyncFileDescriptor { } } } + +extension AsyncFileDescriptor: AsyncSequence { + public func makeAsyncIterator() -> FileIterator { + return .init(self) + } + + public typealias AsyncIterator = FileIterator + public typealias Element = UInt8 +} + +public struct FileIterator: AsyncIteratorProtocol { + @usableFromInline let file: AsyncFileDescriptor + @usableFromInline var buffer: IORingBuffer + @usableFromInline var done: Bool + + @usableFromInline internal var currentByte: UnsafeRawPointer? + @usableFromInline internal var lastByte: UnsafeRawPointer? + + init(_ file: AsyncFileDescriptor) { + self.file = file + self.buffer = file.ring.getBuffer()! + self.done = false + } + + @inlinable @inline(__always) + public mutating func nextBuffer() async throws { + let buffer = self.buffer + + let bytesRead = try await file.read(into: .registered(buffer)) + if _fastPath(bytesRead != 0) { + let bufPointer = buffer.unsafeBuffer.baseAddress.unsafelyUnwrapped + self.currentByte = UnsafeRawPointer(bufPointer) + self.lastByte = UnsafeRawPointer(bufPointer.advanced(by: Int(bytesRead))) + } else { + self.done = true + } + } + + @inlinable @inline(__always) @_unsafeInheritExecutor + public mutating func next() async throws -> UInt8? { + if _fastPath(currentByte != lastByte) { + // SAFETY: both pointers should be non-nil if they're not equal + let byte = currentByte.unsafelyUnwrapped.load(as: UInt8.self) + currentByte = currentByte.unsafelyUnwrapped + 1 + return byte + } else if done { + return nil + } + try await nextBuffer() + return try await next() + } +} From af4544c86920634166223e4e3f6fe99db9e2dcaf Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Fri, 9 Jun 2023 17:56:36 -0700 Subject: [PATCH 139/427] Re-enable Mach.Port --- Sources/System/MachPort.swift | 2 +- Tests/SystemTests/MachPortTests.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/System/MachPort.swift b/Sources/System/MachPort.swift index 04d2cc5b..f1d29b7e 100644 --- a/Sources/System/MachPort.swift +++ b/Sources/System/MachPort.swift @@ -7,7 +7,7 @@ See https://swift.org/LICENSE.txt for license information */ -#if false && swift(>=5.8) && $MoveOnly && (os(macOS) || os(iOS) || os(watchOS) || os(tvOS)) +#if swift(>=5.9) && (os(macOS) || os(iOS) || os(watchOS) || os(tvOS)) import Darwin.Mach diff --git a/Tests/SystemTests/MachPortTests.swift b/Tests/SystemTests/MachPortTests.swift index 31a654cb..01855e34 100644 --- a/Tests/SystemTests/MachPortTests.swift +++ b/Tests/SystemTests/MachPortTests.swift @@ -7,7 +7,7 @@ See https://swift.org/LICENSE.txt for license information */ -#if false && swift(>=5.8) && $MoveOnly && (os(macOS) || os(iOS) || os(watchOS) || os(tvOS)) +#if swift(>=5.9) && (os(macOS) || os(iOS) || os(watchOS) || os(tvOS)) import XCTest import Darwin.Mach From 2df5b0b0315456a816bff29570fb160b6500b083 Mon Sep 17 00:00:00 2001 From: Daniel Loffgren Date: Tue, 8 Aug 2023 17:36:20 -0700 Subject: [PATCH 140/427] srdelta argument to mach_port_destruct should be zero This should never have been -1, as there is no send right for it to balance when `RightType.self == ReceiveRight.self`. --- Sources/System/MachPort.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/System/MachPort.swift b/Sources/System/MachPort.swift index f1d29b7e..b1b2f1c1 100644 --- a/Sources/System/MachPort.swift +++ b/Sources/System/MachPort.swift @@ -86,7 +86,7 @@ public enum Mach { _machPrecondition(mach_port_mod_refs(mach_task_self_, _name, MACH_PORT_RIGHT_DEAD_NAME, -1)) } else { if RightType.self == ReceiveRight.self { - _machPrecondition(mach_port_destruct(mach_task_self_, _name, -1, _context)) + _machPrecondition(mach_port_destruct(mach_task_self_, _name, 0, _context)) } else if RightType.self == SendRight.self { _machPrecondition(mach_port_mod_refs(mach_task_self_, _name, MACH_PORT_RIGHT_SEND, -1)) } else if RightType.self == SendOnceRight.self { From bf16d3da30634886b7730b90d495b8f2c1fb022f Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Wed, 9 Aug 2023 13:16:40 -0700 Subject: [PATCH 141/427] [test] use `XCTAssertEqual` or `XCTAssertNotEqual` --- Tests/SystemTests/MachPortTests.swift | 43 +++++++++++++-------------- 1 file changed, 21 insertions(+), 22 deletions(-) diff --git a/Tests/SystemTests/MachPortTests.swift b/Tests/SystemTests/MachPortTests.swift index 01855e34..cf68cd6d 100644 --- a/Tests/SystemTests/MachPortTests.swift +++ b/Tests/SystemTests/MachPortTests.swift @@ -23,7 +23,7 @@ final class MachPortTests: XCTestCase { var refCount:mach_port_urefs_t = 0 withUnsafeMutablePointer(to: &refCount) { refCount in let kr = mach_port_get_refs(mach_task_self_, name, kind, refCount) - assert(kr == KERN_SUCCESS) + XCTAssertEqual(kr, KERN_SUCCESS) } return refCount } @@ -33,20 +33,20 @@ final class MachPortTests: XCTestCase { return refCountForMachPortName(name:name, kind:MACH_PORT_RIGHT_RECEIVE) } - func testRecieveRightDeallocation() throws { + func testReceiveRightDeallocation() throws { var name:mach_port_name_t = 0 // Never read withUnsafeMutablePointer(to:&name) { name in let kr = mach_port_allocate(mach_task_self_, MACH_PORT_RIGHT_RECEIVE, name) - assert(kr == KERN_SUCCESS) + XCTAssertEqual(kr, KERN_SUCCESS) } - XCTAssert(name != 0xFFFFFFFF) + XCTAssertNotEqual(name, 0xFFFFFFFF) let one = scopedReceiveRight(name:name) let zero = refCountForMachPortName(name:name, kind: MACH_PORT_RIGHT_RECEIVE) - XCTAssert(one == 1); - XCTAssert(zero == 0); + XCTAssertEqual(one, 1); + XCTAssertEqual(zero, 0); } func consumeSendRightAutomatically(name:mach_port_name_t) -> mach_port_urefs_t { @@ -61,11 +61,11 @@ final class MachPortTests: XCTestCase { let recv = Mach.Port() recv.withBorrowedName { name in let kr = mach_port_insert_right(mach_task_self_, name, name, mach_msg_type_name_t(MACH_MSG_TYPE_MAKE_SEND)) - XCTAssert(kr == KERN_SUCCESS) + XCTAssertEqual(kr, KERN_SUCCESS) let one = consumeSendRightAutomatically(name:name) - XCTAssert(one == 1); + XCTAssertEqual(one, 1); let zero = refCountForMachPortName(name:name, kind:MACH_PORT_RIGHT_SEND) - XCTAssert(zero == 0); + XCTAssertEqual(zero, 0); } } @@ -77,29 +77,29 @@ final class MachPortTests: XCTestCase { let one = send.withBorrowedName { name in return self.refCountForMachPortName(name:name, kind:MACH_PORT_RIGHT_SEND) } - XCTAssert(one == 1) + XCTAssertEqual(one, 1) return send.relinquish() })() let stillOne = refCountForMachPortName(name:name, kind:MACH_PORT_RIGHT_SEND) - XCTAssert(stillOne == 1) + XCTAssertEqual(stillOne, 1) } func testMakeSendCountSettable() throws { var recv = Mach.Port() - XCTAssert(recv.makeSendCount == 0) + XCTAssertEqual(recv.makeSendCount, 0) recv.makeSendCount = 7 - XCTAssert(recv.makeSendCount == 7) + XCTAssertEqual(recv.makeSendCount, 7) } func makeSendRight() throws -> Mach.Port { let recv = Mach.Port() let zero = recv.makeSendCount - XCTAssert(zero == 0) + XCTAssertEqual(zero, 0) let send = recv.makeSendRight() let one = recv.makeSendCount - XCTAssert(one == 1) + XCTAssertEqual(one, 1) return send } @@ -110,10 +110,10 @@ final class MachPortTests: XCTestCase { func testMakeSendOnceDoesntIncrementMakeSendCount() throws { let recv = Mach.Port() let zero = recv.makeSendCount - XCTAssert(zero == 0) + XCTAssertEqual(zero, 0) _ = recv.makeSendOnceRight() let same = recv.makeSendCount - XCTAssert(same == zero) + XCTAssertEqual(same, zero) } func testMakeSendOnceIsUnique() throws { @@ -121,8 +121,7 @@ final class MachPortTests: XCTestCase { let once = recv.makeSendOnceRight() recv.withBorrowedName { rname in once.withBorrowedName { oname in - print(oname, rname) - XCTAssert(oname != rname) + XCTAssertNotEqual(oname, rname) } } } @@ -130,13 +129,13 @@ final class MachPortTests: XCTestCase { func testCopySend() throws { let recv = Mach.Port() let zero = recv.makeSendCount - XCTAssert(zero == 0) + XCTAssertEqual(zero, 0) let send = recv.makeSendRight() let one = recv.makeSendCount - XCTAssert(one == 1) + XCTAssertEqual(one, 1) _ = try send.copySendRight() let same = recv.makeSendCount - XCTAssert(same == one) + XCTAssertEqual(same, one) } } From 9123120c08ec18e525d488080478c5c62b40099c Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Wed, 9 Aug 2023 13:17:37 -0700 Subject: [PATCH 142/427] destroy Send rights with `mach_port_deallocate` --- Sources/System/MachPort.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/System/MachPort.swift b/Sources/System/MachPort.swift index b1b2f1c1..96dd9929 100644 --- a/Sources/System/MachPort.swift +++ b/Sources/System/MachPort.swift @@ -88,7 +88,7 @@ public enum Mach { if RightType.self == ReceiveRight.self { _machPrecondition(mach_port_destruct(mach_task_self_, _name, 0, _context)) } else if RightType.self == SendRight.self { - _machPrecondition(mach_port_mod_refs(mach_task_self_, _name, MACH_PORT_RIGHT_SEND, -1)) + _machPrecondition(mach_port_deallocate(mach_task_self_, _name)) } else if RightType.self == SendOnceRight.self { _machPrecondition(mach_port_mod_refs(mach_task_self_, _name, MACH_PORT_RIGHT_SEND_ONCE, -1)) } From 669dcf2c25f7f1c63612e55dc478f8837a50409a Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Wed, 9 Aug 2023 13:48:55 -0700 Subject: [PATCH 143/427] ensure Mach.Port lives until the function returns --- Tests/SystemTests/MachPortTests.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Tests/SystemTests/MachPortTests.swift b/Tests/SystemTests/MachPortTests.swift index cf68cd6d..299809cd 100644 --- a/Tests/SystemTests/MachPortTests.swift +++ b/Tests/SystemTests/MachPortTests.swift @@ -29,7 +29,8 @@ final class MachPortTests: XCTestCase { } func scopedReceiveRight(name:mach_port_name_t) -> mach_port_urefs_t { - _ = Mach.Port(name:name) // this should automatically deallocate when going out of scope + let right = Mach.Port(name:name) // this should automatically deallocate when going out of scope + defer { _ = right } return refCountForMachPortName(name:name, kind:MACH_PORT_RIGHT_RECEIVE) } From 0539daa364e2c006e1349369c2c4f7a164126933 Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Wed, 9 Aug 2023 14:17:43 -0700 Subject: [PATCH 144/427] fix test of receive-right deallocation --- Tests/SystemTests/MachPortTests.swift | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/Tests/SystemTests/MachPortTests.swift b/Tests/SystemTests/MachPortTests.swift index 299809cd..41e28f60 100644 --- a/Tests/SystemTests/MachPortTests.swift +++ b/Tests/SystemTests/MachPortTests.swift @@ -23,7 +23,11 @@ final class MachPortTests: XCTestCase { var refCount:mach_port_urefs_t = 0 withUnsafeMutablePointer(to: &refCount) { refCount in let kr = mach_port_get_refs(mach_task_self_, name, kind, refCount) - XCTAssertEqual(kr, KERN_SUCCESS) + if kr == KERN_INVALID_NAME { + refCount.pointee = 0 + } else { + XCTAssertEqual(kr, KERN_SUCCESS) + } } return refCount } @@ -43,11 +47,14 @@ final class MachPortTests: XCTestCase { XCTAssertNotEqual(name, 0xFFFFFFFF) - let one = scopedReceiveRight(name:name) - let zero = refCountForMachPortName(name:name, kind: MACH_PORT_RIGHT_RECEIVE) + let originalCount = refCountForMachPortName(name: name, kind: MACH_PORT_RIGHT_RECEIVE) + XCTAssertEqual(originalCount, 1) + + let incrementedCount = scopedReceiveRight(name:name) + XCTAssertEqual(incrementedCount, 1); - XCTAssertEqual(one, 1); - XCTAssertEqual(zero, 0); + let deallocated = refCountForMachPortName(name:name, kind: MACH_PORT_RIGHT_RECEIVE) + XCTAssertEqual(deallocated, 0); } func consumeSendRightAutomatically(name:mach_port_name_t) -> mach_port_urefs_t { From 6ee96ffdb5a1645cb6755367f86bb87c2e9bf996 Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Wed, 9 Aug 2023 14:52:35 -0700 Subject: [PATCH 145/427] modify consuming methods to call `discard self` --- Sources/System/MachPort.swift | 26 +++++++++++++++++--------- Tests/SystemTests/MachPortTests.swift | 5 +++++ 2 files changed, 22 insertions(+), 9 deletions(-) diff --git a/Sources/System/MachPort.swift b/Sources/System/MachPort.swift index 96dd9929..1ae99bf8 100644 --- a/Sources/System/MachPort.swift +++ b/Sources/System/MachPort.swift @@ -167,9 +167,11 @@ extension Mach.Port where RightType == Mach.ReceiveRight { /// After this function completes, the Mach.Port is destroyed and no longer /// usable. @inlinable - public __consuming func relinquish( + public consuming func relinquish( ) -> (name: mach_port_name_t, context: mach_port_context_t) { - return (name: _name, context: _context) + let destructured = (name: _name, context: _context) + discard self + return destructured } /// Remove guard and transfer ownership of the underlying port right to @@ -187,9 +189,11 @@ extension Mach.Port where RightType == Mach.ReceiveRight { /// Mach.ReceiveRights. Use relinquish() to avoid the syscall and extract /// the context value along with the port name. @inlinable - public __consuming func unguardAndRelinquish() -> mach_port_name_t { - _machPrecondition(mach_port_unguard(mach_task_self_, _name, _context)) - return _name + public consuming func unguardAndRelinquish() -> mach_port_name_t { + let (name, context) = (_name, _context) + discard self + _machPrecondition(mach_port_unguard(mach_task_self_, name, context)) + return name } /// Borrow access to the port name in a block that can perform @@ -288,8 +292,10 @@ extension Mach.Port where RightType == Mach.SendRight { /// After this function completes, the Mach.Port is destroyed and no longer /// usable. @inlinable - public __consuming func relinquish() -> mach_port_name_t { - return _name + public consuming func relinquish() -> mach_port_name_t { + let name = _name + discard self + return name } /// Create another send right from a given send right. @@ -326,8 +332,10 @@ extension Mach.Port where RightType == Mach.SendOnceRight { /// After this function completes, the Mach.Port is destroyed and no longer /// usable. @inlinable - public __consuming func relinquish() -> mach_port_name_t { - return _name + public consuming func relinquish() -> mach_port_name_t { + let name = _name + discard self + return name } } diff --git a/Tests/SystemTests/MachPortTests.swift b/Tests/SystemTests/MachPortTests.swift index 41e28f60..76037615 100644 --- a/Tests/SystemTests/MachPortTests.swift +++ b/Tests/SystemTests/MachPortTests.swift @@ -92,6 +92,11 @@ final class MachPortTests: XCTestCase { let stillOne = refCountForMachPortName(name:name, kind:MACH_PORT_RIGHT_SEND) XCTAssertEqual(stillOne, 1) + + recv.withBorrowedName { + let alsoOne = refCountForMachPortName(name: $0, kind: MACH_PORT_RIGHT_RECEIVE) + XCTAssertEqual(alsoOne, 1) + } } func testMakeSendCountSettable() throws { From e8bb779909f33f0df50ae8c08700409883c9bc44 Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Wed, 9 Aug 2023 15:24:03 -0700 Subject: [PATCH 146/427] use inout conversion - this is the situation where it is appropriate --- Sources/System/MachPort.swift | 26 ++++++++++++-------------- Tests/SystemTests/MachPortTests.swift | 22 +++++++++------------- 2 files changed, 21 insertions(+), 27 deletions(-) diff --git a/Sources/System/MachPort.swift b/Sources/System/MachPort.swift index 1ae99bf8..0f8b8a44 100644 --- a/Sources/System/MachPort.swift +++ b/Sources/System/MachPort.swift @@ -147,9 +147,9 @@ extension Mach.Port where RightType == Mach.ReceiveRight { @inlinable public init() { var storage: mach_port_name_t = mach_port_name_t(MACH_PORT_NULL) - withUnsafeMutablePointer(to: &storage) { storage in - _machPrecondition(mach_port_allocate(mach_task_self_, MACH_PORT_RIGHT_RECEIVE, storage)) - } + _machPrecondition( + mach_port_allocate(mach_task_self_, MACH_PORT_RIGHT_RECEIVE, &storage) + ) // name-only init will guard ReceiveRights self.init(name: storage) @@ -224,17 +224,15 @@ extension Mach.Port where RightType == Mach.ReceiveRight { var newRight: mach_port_name_t = mach_port_name_t(MACH_PORT_NULL) var newRightType: mach_port_type_t = MACH_PORT_TYPE_NONE - withUnsafeMutablePointer(to: &newRight) { newRight in - withUnsafeMutablePointer(to: &newRightType) { newRightType in - _machPrecondition( - mach_port_extract_right(mach_task_self_, - _name, - mach_msg_type_name_t(MACH_MSG_TYPE_MAKE_SEND_ONCE), - newRight, - newRightType) - ) - } - } + _machPrecondition( + mach_port_extract_right( + mach_task_self_, + _name, + mach_msg_type_name_t(MACH_MSG_TYPE_MAKE_SEND_ONCE), + &newRight, + &newRightType + ) + ) // The value of newRight is validated by the Mach.Port initializer precondition(newRightType == MACH_MSG_TYPE_MOVE_SEND_ONCE) diff --git a/Tests/SystemTests/MachPortTests.swift b/Tests/SystemTests/MachPortTests.swift index 76037615..74ea55a0 100644 --- a/Tests/SystemTests/MachPortTests.swift +++ b/Tests/SystemTests/MachPortTests.swift @@ -20,14 +20,12 @@ import System final class MachPortTests: XCTestCase { func refCountForMachPortName(name:mach_port_name_t, kind:mach_port_right_t) -> mach_port_urefs_t { - var refCount:mach_port_urefs_t = 0 - withUnsafeMutablePointer(to: &refCount) { refCount in - let kr = mach_port_get_refs(mach_task_self_, name, kind, refCount) - if kr == KERN_INVALID_NAME { - refCount.pointee = 0 - } else { - XCTAssertEqual(kr, KERN_SUCCESS) - } + var refCount:mach_port_urefs_t = .max + let kr = mach_port_get_refs(mach_task_self_, name, kind, &refCount) + if kr == KERN_INVALID_NAME { + refCount = 0 + } else { + XCTAssertEqual(kr, KERN_SUCCESS) } return refCount } @@ -39,11 +37,9 @@ final class MachPortTests: XCTestCase { } func testReceiveRightDeallocation() throws { - var name:mach_port_name_t = 0 // Never read - withUnsafeMutablePointer(to:&name) { name in - let kr = mach_port_allocate(mach_task_self_, MACH_PORT_RIGHT_RECEIVE, name) - XCTAssertEqual(kr, KERN_SUCCESS) - } + var name: mach_port_name_t = 0xFFFFFFFF + let kr = mach_port_allocate(mach_task_self_, MACH_PORT_RIGHT_RECEIVE, &name) + XCTAssertEqual(kr, KERN_SUCCESS) XCTAssertNotEqual(name, 0xFFFFFFFF) From 40b5b3068b57e211a6245bcf599152d80ddfb4ca Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Wed, 9 Aug 2023 16:18:06 -0700 Subject: [PATCH 147/427] test all the `relinquish()` methods --- Sources/System/MachPort.swift | 3 +-- Tests/SystemTests/MachPortTests.swift | 36 +++++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/Sources/System/MachPort.swift b/Sources/System/MachPort.swift index 0f8b8a44..7fd98bf9 100644 --- a/Sources/System/MachPort.swift +++ b/Sources/System/MachPort.swift @@ -190,8 +190,7 @@ extension Mach.Port where RightType == Mach.ReceiveRight { /// the context value along with the port name. @inlinable public consuming func unguardAndRelinquish() -> mach_port_name_t { - let (name, context) = (_name, _context) - discard self + let (name, context) = self.relinquish() _machPrecondition(mach_port_unguard(mach_task_self_, name, context)) return name } diff --git a/Tests/SystemTests/MachPortTests.swift b/Tests/SystemTests/MachPortTests.swift index 74ea55a0..7c80f0de 100644 --- a/Tests/SystemTests/MachPortTests.swift +++ b/Tests/SystemTests/MachPortTests.swift @@ -95,6 +95,42 @@ final class MachPortTests: XCTestCase { } } + func testSendOnceRightRelinquishment() throws { + let recv = Mach.Port() + + let name = ({ + let send = recv.makeSendOnceRight() + let one = send.withBorrowedName { name in + return self.refCountForMachPortName(name: name, kind: MACH_PORT_RIGHT_SEND_ONCE) + } + XCTAssertEqual(one, 1) + + return send.relinquish() + })() + + let stillOne = refCountForMachPortName(name: name, kind: MACH_PORT_RIGHT_SEND_ONCE) + XCTAssertEqual(stillOne, 1) + + recv.withBorrowedName { + let alsoOne = refCountForMachPortName(name: $0, kind: MACH_PORT_RIGHT_RECEIVE) + XCTAssertEqual(alsoOne, 1) + } + } + + func testReceiveRightRelinquishment() throws { + let recv = Mach.Port() + + let one = recv.withBorrowedName { + self.refCountForMachPortName(name: $0, kind: MACH_PORT_RIGHT_RECEIVE) + } + XCTAssertEqual(one, 1) + + let name = recv.unguardAndRelinquish() + + let stillOne = refCountForMachPortName(name: name, kind: MACH_PORT_RIGHT_RECEIVE) + XCTAssertEqual(stillOne, 1) + } + func testMakeSendCountSettable() throws { var recv = Mach.Port() XCTAssertEqual(recv.makeSendCount, 0) From 5e03246e4eddb9836de18d114405b70cef44f584 Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Wed, 9 Aug 2023 17:24:49 -0700 Subject: [PATCH 148/427] test copies of dead mach port names --- Sources/System/MachPort.swift | 2 +- Tests/SystemTests/MachPortTests.swift | 11 +++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/Sources/System/MachPort.swift b/Sources/System/MachPort.swift index 7fd98bf9..9867ed89 100644 --- a/Sources/System/MachPort.swift +++ b/Sources/System/MachPort.swift @@ -308,7 +308,7 @@ extension Mach.Port where RightType == Mach.SendRight { // name is the same because send rights are coalesced let kr = mach_port_insert_right(mach_task_self_, _name, _name, mach_msg_type_name_t(how)) - if kr == KERN_INVALID_CAPABILITY { + if kr == KERN_INVALID_NAME { throw Mach.PortRightError.deadName } _machPrecondition(kr) diff --git a/Tests/SystemTests/MachPortTests.swift b/Tests/SystemTests/MachPortTests.swift index 7c80f0de..95d2f638 100644 --- a/Tests/SystemTests/MachPortTests.swift +++ b/Tests/SystemTests/MachPortTests.swift @@ -183,6 +183,17 @@ final class MachPortTests: XCTestCase { XCTAssertEqual(same, one) } + + func testCopyDeadName() throws { + let send = Mach.Port(name: 0xffffffff) + do { + let copy = try send.copySendRight() + _ = copy + } + catch Mach.PortRightError.deadName { + _ = send.relinquish() + } + } } #endif From d259ef476e1fffa2913c979eef98320eefd5eb56 Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Wed, 9 Aug 2023 17:51:54 -0700 Subject: [PATCH 149/427] test two-parameter `withBorrowedName` --- Sources/System/MachPort.swift | 1 + Tests/SystemTests/MachPortTests.swift | 16 ++++++++++++++++ 2 files changed, 17 insertions(+) diff --git a/Sources/System/MachPort.swift b/Sources/System/MachPort.swift index 9867ed89..a67a01a3 100644 --- a/Sources/System/MachPort.swift +++ b/Sources/System/MachPort.swift @@ -135,6 +135,7 @@ extension Mach.Port where RightType == Mach.ReceiveRight { /// The underlying port right will be automatically deallocated when /// the Mach.Port object is destroyed. public init(name: mach_port_name_t, context: mach_port_context_t) { + precondition(name != mach_port_name_t(MACH_PORT_NULL), "Mach.Port cannot be initialized with MACH_PORT_NULL") self._name = name self._context = context } diff --git a/Tests/SystemTests/MachPortTests.swift b/Tests/SystemTests/MachPortTests.swift index 95d2f638..bf1e118c 100644 --- a/Tests/SystemTests/MachPortTests.swift +++ b/Tests/SystemTests/MachPortTests.swift @@ -194,6 +194,22 @@ final class MachPortTests: XCTestCase { _ = send.relinquish() } } + + func testMakeReceivedRightFromExistingName() throws { + var name = mach_port_name_t(MACH_PORT_NULL) + var kr = mach_port_allocate(mach_task_self_, MACH_PORT_RIGHT_RECEIVE, &name) + XCTAssertEqual(kr, KERN_SUCCESS) + XCTAssertNotEqual(name, mach_port_name_t(MACH_PORT_NULL)) + let context = mach_port_context_t(arc4random()) + kr = mach_port_guard(mach_task_self_, name, context, 0) + XCTAssertEqual(kr, KERN_SUCCESS) + + let right = Mach.Port(name: name, context: context) + right.withBorrowedName { + XCTAssertEqual(name, $0) + XCTAssertEqual(context, $1) + } + } } #endif From e4d06ae3ea97ecc35311699e1895f61932e35499 Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Wed, 9 Aug 2023 17:52:50 -0700 Subject: [PATCH 150/427] simplify deinit --- Sources/System/MachPort.swift | 20 ++++++++++---------- Tests/SystemTests/MachPortTests.swift | 14 ++++++++++++++ 2 files changed, 24 insertions(+), 10 deletions(-) diff --git a/Sources/System/MachPort.swift b/Sources/System/MachPort.swift index a67a01a3..4ca3572e 100644 --- a/Sources/System/MachPort.swift +++ b/Sources/System/MachPort.swift @@ -81,17 +81,17 @@ public enum Mach { } deinit { - if _name == 0xFFFFFFFF /* MACH_PORT_DEAD */ { - precondition(RightType.self != ReceiveRight.self, "Receive rights cannot be dead names") - _machPrecondition(mach_port_mod_refs(mach_task_self_, _name, MACH_PORT_RIGHT_DEAD_NAME, -1)) + if RightType.self == ReceiveRight.self { + precondition(_name != 0xffffffff, "Receive rights cannot be dead names") + _machPrecondition( + mach_port_destruct(mach_task_self_, _name, 0, _context) + ) } else { - if RightType.self == ReceiveRight.self { - _machPrecondition(mach_port_destruct(mach_task_self_, _name, 0, _context)) - } else if RightType.self == SendRight.self { - _machPrecondition(mach_port_deallocate(mach_task_self_, _name)) - } else if RightType.self == SendOnceRight.self { - _machPrecondition(mach_port_mod_refs(mach_task_self_, _name, MACH_PORT_RIGHT_SEND_ONCE, -1)) - } + assert( + RightType.self == SendRight.self || + RightType.self == SendOnceRight.self + ) + _machPrecondition(mach_port_deallocate(mach_task_self_, _name)) } } } diff --git a/Tests/SystemTests/MachPortTests.swift b/Tests/SystemTests/MachPortTests.swift index bf1e118c..3c64ce88 100644 --- a/Tests/SystemTests/MachPortTests.swift +++ b/Tests/SystemTests/MachPortTests.swift @@ -210,6 +210,20 @@ final class MachPortTests: XCTestCase { XCTAssertEqual(context, $1) } } + + func testDeinitDeadSendRight() throws { + let send = Mach.Port(name: 0xffffffff) + send.withBorrowedName { + XCTAssertEqual($0, .max) + } + _ = consume send + + let send1 = Mach.Port(name: 0xffffffff) + send1.withBorrowedName { + XCTAssertEqual($0, .max) + } + _ = consume send1 + } } #endif From 0762f57d806df3f5ad9e268d18eddbe4f3cb2594 Mon Sep 17 00:00:00 2001 From: Lucy Satheesan Date: Thu, 10 Aug 2023 16:03:21 -0700 Subject: [PATCH 151/427] migrate CSystem to systemLibrary for some reason, the linker on my linux machine fails to link the tests otherwise. investigate / fix before merging. --- Package.swift | 5 ++--- Sources/CSystem/{include => }/CSystemLinux.h | 0 Sources/CSystem/{include => }/CSystemWindows.h | 0 Sources/CSystem/{include => }/io_uring.h | 5 +++++ Sources/CSystem/{include => }/module.modulemap | 0 Sources/CSystem/shims.c | 18 ------------------ 6 files changed, 7 insertions(+), 21 deletions(-) rename Sources/CSystem/{include => }/CSystemLinux.h (100%) rename Sources/CSystem/{include => }/CSystemWindows.h (100%) rename Sources/CSystem/{include => }/io_uring.h (94%) rename Sources/CSystem/{include => }/module.modulemap (100%) delete mode 100644 Sources/CSystem/shims.c diff --git a/Package.swift b/Package.swift index 60f1a562..1c058acf 100644 --- a/Package.swift +++ b/Package.swift @@ -21,9 +21,8 @@ let package = Package( .package(url: "https://github.com/apple/swift-atomics", from: "1.1.0") ], targets: [ - .target( - name: "CSystem", - dependencies: []), + .systemLibrary( + name: "CSystem"), .target( name: "SystemPackage", dependencies: [ diff --git a/Sources/CSystem/include/CSystemLinux.h b/Sources/CSystem/CSystemLinux.h similarity index 100% rename from Sources/CSystem/include/CSystemLinux.h rename to Sources/CSystem/CSystemLinux.h diff --git a/Sources/CSystem/include/CSystemWindows.h b/Sources/CSystem/CSystemWindows.h similarity index 100% rename from Sources/CSystem/include/CSystemWindows.h rename to Sources/CSystem/CSystemWindows.h diff --git a/Sources/CSystem/include/io_uring.h b/Sources/CSystem/io_uring.h similarity index 94% rename from Sources/CSystem/include/io_uring.h rename to Sources/CSystem/io_uring.h index 9e3d9fb3..5c05ed8b 100644 --- a/Sources/CSystem/include/io_uring.h +++ b/Sources/CSystem/io_uring.h @@ -5,6 +5,9 @@ #include #include +#ifndef SWIFT_IORING_C_WRAPPER +#define SWIFT_IORING_C_WRAPPER + #ifdef __alpha__ /* * alpha is the only exception, all other architectures @@ -48,3 +51,5 @@ int io_uring_enter(int fd, unsigned int to_submit, unsigned int min_complete, return syscall(__NR_io_uring_enter, fd, to_submit, min_complete, flags, sig, _NSIG / 8); } + +#endif diff --git a/Sources/CSystem/include/module.modulemap b/Sources/CSystem/module.modulemap similarity index 100% rename from Sources/CSystem/include/module.modulemap rename to Sources/CSystem/module.modulemap diff --git a/Sources/CSystem/shims.c b/Sources/CSystem/shims.c deleted file mode 100644 index f492a2ae..00000000 --- a/Sources/CSystem/shims.c +++ /dev/null @@ -1,18 +0,0 @@ -/* - This source file is part of the Swift System open source project - - Copyright (c) 2020 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 -*/ - -#ifdef __linux__ - -#include - -#endif - -#if defined(_WIN32) -#include -#endif From d099546787499e97973db394abb3de388e7e5305 Mon Sep 17 00:00:00 2001 From: Lucy Satheesan Date: Fri, 11 Aug 2023 09:06:17 -0700 Subject: [PATCH 152/427] fix access control --- Sources/System/AsyncFileDescriptor.swift | 14 +++++----- Sources/System/IORing.swift | 34 +++++++++--------------- Sources/System/ManagedIORing.swift | 2 +- 3 files changed, 22 insertions(+), 28 deletions(-) diff --git a/Sources/System/AsyncFileDescriptor.swift b/Sources/System/AsyncFileDescriptor.swift index 6e39e055..2143847f 100644 --- a/Sources/System/AsyncFileDescriptor.swift +++ b/Sources/System/AsyncFileDescriptor.swift @@ -1,12 +1,13 @@ @_implementationOnly import CSystem -public class AsyncFileDescriptor { - var open: Bool = true + +public final class AsyncFileDescriptor { + @usableFromInline var open: Bool = true @usableFromInline let fileSlot: IORingFileSlot @usableFromInline let ring: ManagedIORing - static func openat( - atDirectory: FileDescriptor = FileDescriptor(rawValue: AT_FDCWD), + public static func openat( + atDirectory: FileDescriptor = FileDescriptor(rawValue: -100), path: FilePath, _ mode: FileDescriptor.AccessMode, options: FileDescriptor.OpenOptions = FileDescriptor.OpenOptions(), @@ -25,7 +26,8 @@ public class AsyncFileDescriptor { path: cstr, mode, options: options, - permissions: permissions, intoSlot: fileSlot + permissions: permissions, + intoSlot: fileSlot )) if res.result < 0 { throw Errno(rawValue: -res.result) @@ -47,7 +49,7 @@ public class AsyncFileDescriptor { } @inlinable @inline(__always) @_unsafeInheritExecutor - func read( + public func read( into buffer: IORequest.Buffer, atAbsoluteOffset offset: UInt64 = UInt64.max ) async throws -> UInt32 { diff --git a/Sources/System/IORing.swift b/Sources/System/IORing.swift index 0c2f1384..f91dfc04 100644 --- a/Sources/System/IORing.swift +++ b/Sources/System/IORing.swift @@ -262,7 +262,7 @@ public final class IORing: @unchecked Sendable { self.ringFlags = params.flags } - func blockingConsumeCompletion() -> IOCompletion { + public func blockingConsumeCompletion() -> IOCompletion { self.completionMutex.lock() defer { self.completionMutex.unlock() } @@ -292,7 +292,7 @@ public final class IORing: @unchecked Sendable { } } - func tryConsumeCompletion() -> IOCompletion? { + public func tryConsumeCompletion() -> IOCompletion? { self.completionMutex.lock() defer { self.completionMutex.unlock() } return _tryConsumeCompletion() @@ -312,8 +312,7 @@ public final class IORing: @unchecked Sendable { return nil } - - func registerFiles(count: UInt32) { + public func registerFiles(count: UInt32) { guard self.registeredFiles == nil else { fatalError() } let fileBuf = UnsafeMutableBufferPointer.allocate(capacity: Int(count)) fileBuf.initialize(repeating: UInt32.max) @@ -327,16 +326,15 @@ public final class IORing: @unchecked Sendable { self.registeredFiles = ResourceManager(fileBuf) } - func unregisterFiles() { + public func unregisterFiles() { fatalError("failed to unregister files") } - func getFile() -> IORingFileSlot? { + public func getFile() -> IORingFileSlot? { return self.registeredFiles?.getResource() } - // register a group of buffers - func registerBuffers(bufSize: UInt32, count: UInt32) { + public func registerBuffers(bufSize: UInt32, count: UInt32) { let iovecs = UnsafeMutableBufferPointer.allocate(capacity: Int(count)) let intBufSize = Int(bufSize) for i in 0.. IORingBuffer? { + public func getBuffer() -> IORingBuffer? { return self.registeredBuffers?.getResource() } - func unregisterBuffers() { + public func unregisterBuffers() { fatalError("failed to unregister buffers: TODO") } - // TODO: types - func submitRequests() { + public func submitRequests() { self.submissionMutex.lock() defer { self.submissionMutex.unlock() } self._submitRequests() } - func _submitRequests() { + internal func _submitRequests() { let flushedEvents = _flushQueue() // Ring always needs enter right now; @@ -389,6 +386,8 @@ public final class IORing: @unchecked Sendable { fatalError("fatal error in submitting requests: " + Errno(rawValue: -ret).debugDescription ) + } else { + break } } } @@ -403,14 +402,7 @@ public final class IORing: @unchecked Sendable { @inlinable @inline(__always) - func writeRequest(_ request: __owned IORequest) -> Bool { - self.submissionMutex.lock() - defer { self.submissionMutex.unlock() } - return _writeRequest(request.makeRawRequest()) - } - - @inlinable @inline(__always) - func writeAndSubmit(_ request: __owned IORequest) -> Bool { + public func writeRequest(_ request: __owned IORequest) -> Bool { self.submissionMutex.lock() defer { self.submissionMutex.unlock() } return _writeRequest(request.makeRawRequest()) diff --git a/Sources/System/ManagedIORing.swift b/Sources/System/ManagedIORing.swift index 580eacc1..7f5bec22 100644 --- a/Sources/System/ManagedIORing.swift +++ b/Sources/System/ManagedIORing.swift @@ -1,7 +1,7 @@ final public class ManagedIORing: @unchecked Sendable { var internalRing: IORing - init(queueDepth: UInt32) throws { + public init(queueDepth: UInt32) throws { self.internalRing = try IORing(queueDepth: queueDepth) self.internalRing.registerBuffers(bufSize: 655336, count: 4) self.internalRing.registerFiles(count: 32) From b58450413e04141a7fa4e32b7cb68b3fa90809e1 Mon Sep 17 00:00:00 2001 From: Lucy Satheesan Date: Fri, 11 Aug 2023 09:07:08 -0700 Subject: [PATCH 153/427] fix off-by-one in IORequest.openat --- Sources/System/IORequest.swift | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Sources/System/IORequest.swift b/Sources/System/IORequest.swift index 6b26a4d1..39d7cdb7 100644 --- a/Sources/System/IORequest.swift +++ b/Sources/System/IORequest.swift @@ -46,7 +46,9 @@ extension IORequest { request.rawValue.addr = unsafeBitCast(path, to: UInt64.self) request.rawValue.open_flags = UInt32(bitPattern: options.rawValue | mode.rawValue) request.rawValue.len = permissions?.rawValue ?? 0 - request.rawValue.file_index = UInt32(slot?.index ?? 0) + if let fileSlot = slot { + request.rawValue.file_index = UInt32(fileSlot.index + 1) + } case .read(let file, let buffer, let offset), .write(let file, let buffer, let offset): if case .read = self { if case .registered = buffer { From b596783bd9a5e9d34f99e06b3624c0bf9e243c7d Mon Sep 17 00:00:00 2001 From: Lucy Satheesan Date: Fri, 11 Aug 2023 09:07:47 -0700 Subject: [PATCH 154/427] implement closing --- Sources/System/AsyncFileDescriptor.swift | 12 +++++++++--- Sources/System/IORequest.swift | 9 +++++++++ Sources/System/RawIORequest.swift | 6 +++++- 3 files changed, 23 insertions(+), 4 deletions(-) diff --git a/Sources/System/AsyncFileDescriptor.swift b/Sources/System/AsyncFileDescriptor.swift index 2143847f..504aec9f 100644 --- a/Sources/System/AsyncFileDescriptor.swift +++ b/Sources/System/AsyncFileDescriptor.swift @@ -43,9 +43,15 @@ public final class AsyncFileDescriptor { self.ring = ring } - func close() async throws { + @inlinable @inline(__always) @_unsafeInheritExecutor + public func close() async throws { + let res = await ring.submitAndWait(.close( + .registered(self.fileSlot) + )) + if res.result < 0 { + throw Errno(rawValue: -res.result) + } self.open = false - fatalError() } @inlinable @inline(__always) @_unsafeInheritExecutor @@ -67,7 +73,7 @@ public final class AsyncFileDescriptor { deinit { if (self.open) { - // TODO: close + // TODO: close or error? TBD } } } diff --git a/Sources/System/IORequest.swift b/Sources/System/IORequest.swift index 39d7cdb7..9548b3fb 100644 --- a/Sources/System/IORequest.swift +++ b/Sources/System/IORequest.swift @@ -20,6 +20,7 @@ public enum IORequest { buffer: Buffer, offset: UInt64 = 0 ) + case close(File) public enum Buffer { case registered(IORingBuffer) @@ -78,6 +79,14 @@ extension IORequest { request.buffer = buf } request.offset = offset + case .close(let file): + request.operation = .close + switch file { + case .registered(let regFile): + request.rawValue.file_index = UInt32(regFile.index + 1) + case .unregistered(let normalFile): + request.fileDescriptor = normalFile + } } return request } diff --git a/Sources/System/RawIORequest.swift b/Sources/System/RawIORequest.swift index fa50b439..520cb85c 100644 --- a/Sources/System/RawIORequest.swift +++ b/Sources/System/RawIORequest.swift @@ -25,10 +25,14 @@ extension RawIORequest { case receiveMessage = 10 // ... case openAt = 18 + case close = 19 + case filesUpdate = 20 + case statx = 21 case read = 22 case write = 23 + // ... case openAt2 = 28 - + // ... } public struct Flags: OptionSet, Hashable, Codable { From 6b4084c69f8a691976d7fe4deeaa80e099649356 Mon Sep 17 00:00:00 2001 From: Lucy Satheesan Date: Fri, 11 Aug 2023 09:08:43 -0700 Subject: [PATCH 155/427] introduce IORing unit tests --- Tests/LinuxMain.swift | 8 -- .../AsyncFileDescriptorTests.swift | 40 ++++++ Tests/SystemTests/IORequestTests.swift | 68 +++++++++ Tests/SystemTests/IORingTests.swift | 21 +++ Tests/SystemTests/ManagedIORingTests.swift | 19 +++ Tests/SystemTests/XCTestManifests.swift | 132 ------------------ 6 files changed, 148 insertions(+), 140 deletions(-) delete mode 100644 Tests/LinuxMain.swift create mode 100644 Tests/SystemTests/AsyncFileDescriptorTests.swift create mode 100644 Tests/SystemTests/IORequestTests.swift create mode 100644 Tests/SystemTests/IORingTests.swift create mode 100644 Tests/SystemTests/ManagedIORingTests.swift delete mode 100644 Tests/SystemTests/XCTestManifests.swift diff --git a/Tests/LinuxMain.swift b/Tests/LinuxMain.swift deleted file mode 100644 index 695f4e5b..00000000 --- a/Tests/LinuxMain.swift +++ /dev/null @@ -1,8 +0,0 @@ -import XCTest - -import SystemTests - -var tests = [XCTestCaseEntry]() -tests += SystemTests.__allTests() - -XCTMain(tests) diff --git a/Tests/SystemTests/AsyncFileDescriptorTests.swift b/Tests/SystemTests/AsyncFileDescriptorTests.swift new file mode 100644 index 00000000..0f1c103c --- /dev/null +++ b/Tests/SystemTests/AsyncFileDescriptorTests.swift @@ -0,0 +1,40 @@ +import XCTest + +#if SYSTEM_PACKAGE +import SystemPackage +#else +import System +#endif + +final class AsyncFileDescriptorTests: XCTestCase { + func testOpen() async throws { + let ring = try ManagedIORing(queueDepth: 32) + let file = try await AsyncFileDescriptor.openat( + path: "/dev/zero", + .readOnly, + onRing: ring + ) + } + + func testOpenClose() async throws { + let ring = try ManagedIORing(queueDepth: 32) + let file = try await AsyncFileDescriptor.openat( + path: "/dev/zero", + .readOnly, + onRing: ring + ) + await try file.close() + } + + func testDevNullEmpty() async throws { + let ring = try ManagedIORing(queueDepth: 32) + let file = try await AsyncFileDescriptor.openat( + path: "/dev/null", + .readOnly, + onRing: ring + ) + for try await _ in file { + XCTFail("/dev/null should be empty") + } + } +} diff --git a/Tests/SystemTests/IORequestTests.swift b/Tests/SystemTests/IORequestTests.swift new file mode 100644 index 00000000..a44a607e --- /dev/null +++ b/Tests/SystemTests/IORequestTests.swift @@ -0,0 +1,68 @@ +import XCTest + +#if SYSTEM_PACKAGE +@testable import SystemPackage +#else +import System +#endif + +func requestBytes(_ request: RawIORequest) -> [UInt8] { + return withUnsafePointer(to: request) { + let requestBuf = UnsafeBufferPointer(start: $0, count: 1) + let rawBytes = UnsafeRawBufferPointer(requestBuf) + return .init(rawBytes) + } +} + +// This test suite compares various IORequests bit-for-bit to IORequests +// that were generated with liburing or manually written out, +// which are known to work correctly. +final class IORequestTests: XCTestCase { + func testNop() { + let req = IORequest.nop.makeRawRequest() + let sourceBytes = requestBytes(req) + // convenient property of nop: it's all zeros! + // for some unknown reason, liburing sets the fd field to -1. + // we're not trying to be bug-compatible with it, so 0 *should* work. + XCTAssertEqual(sourceBytes, .init(repeating: 0, count: 64)) + } + + func testOpenatFixedFile() throws { + // TODO: come up with a better way of getting a FileSlot. + let buf = UnsafeMutableBufferPointer.allocate(capacity: 2) + let resmgr = ResourceManager.init(buf) + + let pathPtr = UnsafePointer(bitPattern: 0x414141410badf00d)! + let fileSlot = resmgr.getResource()! + let req = IORequest.openat( + atDirectory: FileDescriptor(rawValue: -100), + path: pathPtr, + .readOnly, + options: [], + permissions: nil, + intoSlot: fileSlot + ) + + let expectedRequest: [UInt8] = { + var bin = [UInt8].init(repeating: 0, count: 64) + bin[0] = 0x12 // opcode for the request + // bin[1] = 0 - no request flags + // bin[2...3] = 0 - padding + bin[4...7] = [0x9c, 0xff, 0xff, 0xff] // -100 in UInt32 - dirfd + // bin[8...15] = 0 - zeroes + withUnsafeBytes(of: pathPtr) { + // path pointer + bin[16...23] = ArraySlice($0) + } + // bin[24...43] = 0 - zeroes + withUnsafeBytes(of: UInt32(fileSlot.index + 1)) { + // file index + 1 - yes, unfortunately + bin[44...47] = ArraySlice($0) + } + return bin + }() + + let actualRequest = requestBytes(req.makeRawRequest()) + XCTAssertEqual(expectedRequest, actualRequest) + } +} diff --git a/Tests/SystemTests/IORingTests.swift b/Tests/SystemTests/IORingTests.swift new file mode 100644 index 00000000..78baa984 --- /dev/null +++ b/Tests/SystemTests/IORingTests.swift @@ -0,0 +1,21 @@ +import XCTest + +#if SYSTEM_PACKAGE +import SystemPackage +#else +import System +#endif + +final class IORingTests: XCTestCase { + func testInit() throws { + let ring = try IORing(queueDepth: 32) + } + + func testNop() throws { + let ring = try IORing(queueDepth: 32) + ring.writeRequest(.nop) + ring.submitRequests() + let completion = ring.blockingConsumeCompletion() + XCTAssertEqual(completion.result, 0) + } +} diff --git a/Tests/SystemTests/ManagedIORingTests.swift b/Tests/SystemTests/ManagedIORingTests.swift new file mode 100644 index 00000000..e7ad3f59 --- /dev/null +++ b/Tests/SystemTests/ManagedIORingTests.swift @@ -0,0 +1,19 @@ +import XCTest + +#if SYSTEM_PACKAGE +import SystemPackage +#else +import System +#endif + +final class ManagedIORingTests: XCTestCase { + func testInit() throws { + let ring = try ManagedIORing(queueDepth: 32) + } + + func testNop() async throws { + let ring = try ManagedIORing(queueDepth: 32) + let completion = await ring.submitAndWait(.nop) + XCTAssertEqual(completion.result, 0) + } +} diff --git a/Tests/SystemTests/XCTestManifests.swift b/Tests/SystemTests/XCTestManifests.swift deleted file mode 100644 index de99bd81..00000000 --- a/Tests/SystemTests/XCTestManifests.swift +++ /dev/null @@ -1,132 +0,0 @@ -#if !canImport(ObjectiveC) && swift(<5.5) -import XCTest - -extension ErrnoTest { - // DO NOT MODIFY: This is autogenerated, use: - // `swift test --generate-linuxmain` - // to regenerate. - static let __allTests__ErrnoTest = [ - ("testConstants", testConstants), - ("testPatternMatching", testPatternMatching), - ] -} - -extension FileDescriptorTest { - // DO NOT MODIFY: This is autogenerated, use: - // `swift test --generate-linuxmain` - // to regenerate. - static let __allTests__FileDescriptorTest = [ - ("testConstants", testConstants), - ("testStandardDescriptors", testStandardDescriptors), - ] -} - -extension FileOperationsTest { - // DO NOT MODIFY: This is autogenerated, use: - // `swift test --generate-linuxmain` - // to regenerate. - static let __allTests__FileOperationsTest = [ - ("testAdHocOpen", testAdHocOpen), - ("testAdHocPipe", testAdHocPipe), - ("testGithubIssues", testGithubIssues), - ("testHelpers", testHelpers), - ("testSyscalls", testSyscalls), - ] -} - -extension FilePathComponentsTest { - // DO NOT MODIFY: This is autogenerated, use: - // `swift test --generate-linuxmain` - // to regenerate. - static let __allTests__FilePathComponentsTest = [ - ("testAdHocRRC", testAdHocRRC), - ("testCases", testCases), - ("testSeparatorNormalization", testSeparatorNormalization), - ] -} - -extension FilePathParsingTest { - // DO NOT MODIFY: This is autogenerated, use: - // `swift test --generate-linuxmain` - // to regenerate. - static let __allTests__FilePathParsingTest = [ - ("testNormalization", testNormalization), - ] -} - -extension FilePathSyntaxTest { - // DO NOT MODIFY: This is autogenerated, use: - // `swift test --generate-linuxmain` - // to regenerate. - static let __allTests__FilePathSyntaxTest = [ - ("testAdHocMutations", testAdHocMutations), - ("testFailableStringInitializers", testFailableStringInitializers), - ("testLexicallyRelative", testLexicallyRelative), - ("testPartialWindowsRoots", testPartialWindowsRoots), - ("testPathSyntax", testPathSyntax), - ("testPrefixSuffix", testPrefixSuffix), - ] -} - -extension FilePathTest { - // DO NOT MODIFY: This is autogenerated, use: - // `swift test --generate-linuxmain` - // to regenerate. - static let __allTests__FilePathTest = [ - ("testFilePath", testFilePath), - ] -} - -extension FilePermissionsTest { - // DO NOT MODIFY: This is autogenerated, use: - // `swift test --generate-linuxmain` - // to regenerate. - static let __allTests__FilePermissionsTest = [ - ("testPermissions", testPermissions), - ] -} - -extension MockingTest { - // DO NOT MODIFY: This is autogenerated, use: - // `swift test --generate-linuxmain` - // to regenerate. - static let __allTests__MockingTest = [ - ("testMocking", testMocking), - ] -} - -extension SystemCharTest { - // DO NOT MODIFY: This is autogenerated, use: - // `swift test --generate-linuxmain` - // to regenerate. - static let __allTests__SystemCharTest = [ - ("testIsLetter", testIsLetter), - ] -} - -extension SystemStringTest { - // DO NOT MODIFY: This is autogenerated, use: - // `swift test --generate-linuxmain` - // to regenerate. - static let __allTests__SystemStringTest = [ - ("testAdHoc", testAdHoc), - ("testPlatformString", testPlatformString), - ] -} - -public func __allTests() -> [XCTestCaseEntry] { - return [ - testCase(ErrnoTest.__allTests__ErrnoTest), - testCase(FileDescriptorTest.__allTests__FileDescriptorTest), - testCase(FileOperationsTest.__allTests__FileOperationsTest), - testCase(FilePathComponentsTest.__allTests__FilePathComponentsTest), - testCase(FilePathParsingTest.__allTests__FilePathParsingTest), - testCase(FilePathSyntaxTest.__allTests__FilePathSyntaxTest), - testCase(FilePathTest.__allTests__FilePathTest), - testCase(FilePermissionsTest.__allTests__FilePermissionsTest), - testCase(MockingTest.__allTests__MockingTest), - testCase(SystemCharTest.__allTests__SystemCharTest), - testCase(SystemStringTest.__allTests__SystemStringTest), - ] -} -#endif From 6189c1f553ea30bc64a59f27cc851d7daae03269 Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Fri, 11 Aug 2023 14:09:44 -0700 Subject: [PATCH 156/427] fix `copySendRight()` for send rights that become dead names --- Sources/System/MachPort.swift | 2 +- Tests/SystemTests/MachPortTests.swift | 13 ++++++++++++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/Sources/System/MachPort.swift b/Sources/System/MachPort.swift index 4ca3572e..0f58243a 100644 --- a/Sources/System/MachPort.swift +++ b/Sources/System/MachPort.swift @@ -309,7 +309,7 @@ extension Mach.Port where RightType == Mach.SendRight { // name is the same because send rights are coalesced let kr = mach_port_insert_right(mach_task_self_, _name, _name, mach_msg_type_name_t(how)) - if kr == KERN_INVALID_NAME { + if kr == KERN_INVALID_NAME || kr == KERN_INVALID_CAPABILITY { throw Mach.PortRightError.deadName } _machPrecondition(kr) diff --git a/Tests/SystemTests/MachPortTests.swift b/Tests/SystemTests/MachPortTests.swift index 3c64ce88..082653e5 100644 --- a/Tests/SystemTests/MachPortTests.swift +++ b/Tests/SystemTests/MachPortTests.swift @@ -185,13 +185,24 @@ final class MachPortTests: XCTestCase { } func testCopyDeadName() throws { + let recv = Mach.Port() + let send = recv.makeSendRight() + _ = consume recv // and turn `send` into a dead name + do { + let copy = try send.copySendRight() + _ = copy + } + catch Mach.PortRightError.deadName { + } + } + + func testCopyDeadName2() throws { let send = Mach.Port(name: 0xffffffff) do { let copy = try send.copySendRight() _ = copy } catch Mach.PortRightError.deadName { - _ = send.relinquish() } } From f5edd720bd7418cda0f4ee29e779eac74705e025 Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Fri, 11 Aug 2023 18:03:24 -0700 Subject: [PATCH 157/427] clarify some tests --- Tests/SystemTests/MachPortTests.swift | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/Tests/SystemTests/MachPortTests.swift b/Tests/SystemTests/MachPortTests.swift index 082653e5..07da8bb2 100644 --- a/Tests/SystemTests/MachPortTests.swift +++ b/Tests/SystemTests/MachPortTests.swift @@ -193,6 +193,7 @@ final class MachPortTests: XCTestCase { _ = copy } catch Mach.PortRightError.deadName { + // success } } @@ -203,10 +204,11 @@ final class MachPortTests: XCTestCase { _ = copy } catch Mach.PortRightError.deadName { + // success } } - func testMakeReceivedRightFromExistingName() throws { + func testMakeReceiveRightFromExistingName() throws { var name = mach_port_name_t(MACH_PORT_NULL) var kr = mach_port_allocate(mach_task_self_, MACH_PORT_RIGHT_RECEIVE, &name) XCTAssertEqual(kr, KERN_SUCCESS) @@ -222,17 +224,14 @@ final class MachPortTests: XCTestCase { } } - func testDeinitDeadSendRight() throws { - let send = Mach.Port(name: 0xffffffff) - send.withBorrowedName { - XCTAssertEqual($0, .max) - } - _ = consume send + func testDeinitDeadSendRights() throws { + let recv = Mach.Port() + let send = recv.makeSendRight() + let send1 = recv.makeSendOnceRight() - let send1 = Mach.Port(name: 0xffffffff) - send1.withBorrowedName { - XCTAssertEqual($0, .max) - } + _ = consume recv + // `send` and `send1` have become dead names + _ = consume send _ = consume send1 } } From 65693b8b808d013cab03055963989cbe97a145ba Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Mon, 14 Aug 2023 18:00:14 -0700 Subject: [PATCH 158/427] improve memory rebinding for `mach_port_get_attributes` call --- Sources/System/MachPort.swift | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/Sources/System/MachPort.swift b/Sources/System/MachPort.swift index 0f58243a..58521469 100644 --- a/Sources/System/MachPort.swift +++ b/Sources/System/MachPort.swift @@ -263,11 +263,19 @@ extension Mach.Port where RightType == Mach.ReceiveRight { public var makeSendCount: mach_port_mscount_t { get { var status: mach_port_status = mach_port_status() - var size: mach_msg_type_number_t = mach_msg_type_number_t(MemoryLayout.size / MemoryLayout.size) - withUnsafeMutablePointer(to: &size) { size in - withUnsafeMutablePointer(to: &status) { status in - let info = UnsafeMutableRawPointer(status).bindMemory(to: integer_t.self, capacity: 1) - _machPrecondition(mach_port_get_attributes(mach_task_self_, _name, MACH_PORT_RECEIVE_STATUS, info, size)) + var size = mach_msg_type_number_t( + MemoryLayout.size / MemoryLayout.size + ) + + withUnsafeMutablePointer(to: &status) { + let status = UnsafeMutableBufferPointer(start: $0, count: 1) + status.withMemoryRebound(to: integer_t.self) { + let info = $0.baseAddress + _machPrecondition( + mach_port_get_attributes( + mach_task_self_, _name, MACH_PORT_RECEIVE_STATUS, info, &size + ) + ) } } return status.mps_mscount From 8730f2cf256694ae5cee761e2f687df064f0a480 Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Tue, 15 Aug 2023 09:14:21 -0700 Subject: [PATCH 159/427] [gardening] 80-column enforcement --- Sources/System/MachPort.swift | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/Sources/System/MachPort.swift b/Sources/System/MachPort.swift index 58521469..223757b8 100644 --- a/Sources/System/MachPort.swift +++ b/Sources/System/MachPort.swift @@ -48,11 +48,13 @@ public enum Mach { /// /// This initializer makes a syscall to guard the right. public init(name: mach_port_name_t) { - precondition(name != mach_port_name_t(MACH_PORT_NULL), "Mach.Port cannot be initialized with MACH_PORT_NULL") + precondition(name != mach_port_name_t(MACH_PORT_NULL), + "Mach.Port cannot be initialized with MACH_PORT_NULL") self._name = name if RightType.self == ReceiveRight.self { - precondition(name != 0xFFFFFFFF /* MACH_PORT_DEAD */, "Receive rights cannot be dead names") + precondition(name != 0xFFFFFFFF /* MACH_PORT_DEAD */, + "Receive rights cannot be dead names") let secret = mach_port_context_t(arc4random()) _machPrecondition(mach_port_guard(mach_task_self_, name, secret, 0)) @@ -82,7 +84,8 @@ public enum Mach { deinit { if RightType.self == ReceiveRight.self { - precondition(_name != 0xffffffff, "Receive rights cannot be dead names") + precondition(_name != 0xFFFFFFFF /* MACH_PORT_DEAD */, + "Receive rights cannot be dead names") _machPrecondition( mach_port_destruct(mach_task_self_, _name, 0, _context) ) @@ -135,7 +138,8 @@ extension Mach.Port where RightType == Mach.ReceiveRight { /// The underlying port right will be automatically deallocated when /// the Mach.Port object is destroyed. public init(name: mach_port_name_t, context: mach_port_context_t) { - precondition(name != mach_port_name_t(MACH_PORT_NULL), "Mach.Port cannot be initialized with MACH_PORT_NULL") + precondition(name != mach_port_name_t(MACH_PORT_NULL), + "Mach.Port cannot be initialized with MACH_PORT_NULL") self._name = name self._context = context } @@ -251,7 +255,11 @@ extension Mach.Port where RightType == Mach.ReceiveRight { let how = MACH_MSG_TYPE_MAKE_SEND // name is the same because send and recv rights are coalesced - _machPrecondition(mach_port_insert_right(mach_task_self_, _name, _name, mach_msg_type_name_t(how))) + _machPrecondition( + mach_port_insert_right( + mach_task_self_, _name, _name, mach_msg_type_name_t(how) + ) + ) return Mach.Port(name: _name) } @@ -316,7 +324,9 @@ extension Mach.Port where RightType == Mach.SendRight { let how = MACH_MSG_TYPE_COPY_SEND // name is the same because send rights are coalesced - let kr = mach_port_insert_right(mach_task_self_, _name, _name, mach_msg_type_name_t(how)) + let kr = mach_port_insert_right( + mach_task_self_, _name, _name, mach_msg_type_name_t(how) + ) if kr == KERN_INVALID_NAME || kr == KERN_INVALID_CAPABILITY { throw Mach.PortRightError.deadName } From a3aff2f913c26cabbc3bbe40190fb27f9f738cd7 Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Wed, 16 Aug 2023 11:01:36 -0700 Subject: [PATCH 160/427] require Swift 5.6 --- Package.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Package.swift b/Package.swift index b081a872..5f8efb05 100644 --- a/Package.swift +++ b/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version:5.2 +// swift-tools-version:5.6 // The swift-tools-version declares the minimum version of Swift required to build this package. /* From f32faad415ee97130b9aa7e6b3a79f5018514931 Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Wed, 16 Aug 2023 11:02:51 -0700 Subject: [PATCH 161/427] remove code depending on Swift older than 5.6 --- .../FilePath/FilePathComponentView.swift | 25 ---- Tests/SystemTests/XCTestManifests.swift | 132 ------------------ 2 files changed, 157 deletions(-) delete mode 100644 Tests/SystemTests/XCTestManifests.swift diff --git a/Sources/System/FilePath/FilePathComponentView.swift b/Sources/System/FilePath/FilePathComponentView.swift index efdd084a..1b9fd21f 100644 --- a/Sources/System/FilePath/FilePathComponentView.swift +++ b/Sources/System/FilePath/FilePathComponentView.swift @@ -39,30 +39,6 @@ extension FilePath { } } - #if SYSTEM_PACKAGE // Workaround for a __consuming getter bug in Swift 5.3.3 - /// View the non-root components that make up this path. - public var components: ComponentView { - get { ComponentView(self) } - _modify { - // RRC's empty init means that we can't guarantee that the yielded - // view will restore our root. So copy it out first. - // - // TODO(perf): Small-form root (especially on Unix). Have Root - // always copy out (not worth ref counting). Make sure that we're - // not needlessly sliding values around or triggering a COW - let rootStr = self.root?._systemString ?? SystemString() - var comp = ComponentView(self) - self = FilePath() - defer { - self = comp._path - if root?._slice.elementsEqual(rootStr) != true { - self.root = Root(rootStr) - } - } - yield &comp - } - } - #else /// View the non-root components that make up this path. public var components: ComponentView { __consuming get { ComponentView(self) } @@ -85,7 +61,6 @@ extension FilePath { yield &comp } } - #endif } @available(/*System 0.0.2: macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0*/iOS 8, *) diff --git a/Tests/SystemTests/XCTestManifests.swift b/Tests/SystemTests/XCTestManifests.swift deleted file mode 100644 index de99bd81..00000000 --- a/Tests/SystemTests/XCTestManifests.swift +++ /dev/null @@ -1,132 +0,0 @@ -#if !canImport(ObjectiveC) && swift(<5.5) -import XCTest - -extension ErrnoTest { - // DO NOT MODIFY: This is autogenerated, use: - // `swift test --generate-linuxmain` - // to regenerate. - static let __allTests__ErrnoTest = [ - ("testConstants", testConstants), - ("testPatternMatching", testPatternMatching), - ] -} - -extension FileDescriptorTest { - // DO NOT MODIFY: This is autogenerated, use: - // `swift test --generate-linuxmain` - // to regenerate. - static let __allTests__FileDescriptorTest = [ - ("testConstants", testConstants), - ("testStandardDescriptors", testStandardDescriptors), - ] -} - -extension FileOperationsTest { - // DO NOT MODIFY: This is autogenerated, use: - // `swift test --generate-linuxmain` - // to regenerate. - static let __allTests__FileOperationsTest = [ - ("testAdHocOpen", testAdHocOpen), - ("testAdHocPipe", testAdHocPipe), - ("testGithubIssues", testGithubIssues), - ("testHelpers", testHelpers), - ("testSyscalls", testSyscalls), - ] -} - -extension FilePathComponentsTest { - // DO NOT MODIFY: This is autogenerated, use: - // `swift test --generate-linuxmain` - // to regenerate. - static let __allTests__FilePathComponentsTest = [ - ("testAdHocRRC", testAdHocRRC), - ("testCases", testCases), - ("testSeparatorNormalization", testSeparatorNormalization), - ] -} - -extension FilePathParsingTest { - // DO NOT MODIFY: This is autogenerated, use: - // `swift test --generate-linuxmain` - // to regenerate. - static let __allTests__FilePathParsingTest = [ - ("testNormalization", testNormalization), - ] -} - -extension FilePathSyntaxTest { - // DO NOT MODIFY: This is autogenerated, use: - // `swift test --generate-linuxmain` - // to regenerate. - static let __allTests__FilePathSyntaxTest = [ - ("testAdHocMutations", testAdHocMutations), - ("testFailableStringInitializers", testFailableStringInitializers), - ("testLexicallyRelative", testLexicallyRelative), - ("testPartialWindowsRoots", testPartialWindowsRoots), - ("testPathSyntax", testPathSyntax), - ("testPrefixSuffix", testPrefixSuffix), - ] -} - -extension FilePathTest { - // DO NOT MODIFY: This is autogenerated, use: - // `swift test --generate-linuxmain` - // to regenerate. - static let __allTests__FilePathTest = [ - ("testFilePath", testFilePath), - ] -} - -extension FilePermissionsTest { - // DO NOT MODIFY: This is autogenerated, use: - // `swift test --generate-linuxmain` - // to regenerate. - static let __allTests__FilePermissionsTest = [ - ("testPermissions", testPermissions), - ] -} - -extension MockingTest { - // DO NOT MODIFY: This is autogenerated, use: - // `swift test --generate-linuxmain` - // to regenerate. - static let __allTests__MockingTest = [ - ("testMocking", testMocking), - ] -} - -extension SystemCharTest { - // DO NOT MODIFY: This is autogenerated, use: - // `swift test --generate-linuxmain` - // to regenerate. - static let __allTests__SystemCharTest = [ - ("testIsLetter", testIsLetter), - ] -} - -extension SystemStringTest { - // DO NOT MODIFY: This is autogenerated, use: - // `swift test --generate-linuxmain` - // to regenerate. - static let __allTests__SystemStringTest = [ - ("testAdHoc", testAdHoc), - ("testPlatformString", testPlatformString), - ] -} - -public func __allTests() -> [XCTestCaseEntry] { - return [ - testCase(ErrnoTest.__allTests__ErrnoTest), - testCase(FileDescriptorTest.__allTests__FileDescriptorTest), - testCase(FileOperationsTest.__allTests__FileOperationsTest), - testCase(FilePathComponentsTest.__allTests__FilePathComponentsTest), - testCase(FilePathParsingTest.__allTests__FilePathParsingTest), - testCase(FilePathSyntaxTest.__allTests__FilePathSyntaxTest), - testCase(FilePathTest.__allTests__FilePathTest), - testCase(FilePermissionsTest.__allTests__FilePermissionsTest), - testCase(MockingTest.__allTests__MockingTest), - testCase(SystemCharTest.__allTests__SystemCharTest), - testCase(SystemStringTest.__allTests__SystemStringTest), - ] -} -#endif From 87e68fca36465a17de7176c9e0267a4f5fd91591 Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Wed, 16 Aug 2023 11:19:06 -0700 Subject: [PATCH 162/427] declare `Sendable` conformances along with the types - `Sendable` is fundamental, it is preferably not declared as an extension. --- Sources/System/FileDescriptor.swift | 20 ++++++------------- Sources/System/FilePath/FilePath.swift | 7 +------ .../FilePath/FilePathComponentView.swift | 15 +++++--------- .../System/FilePath/FilePathComponents.swift | 20 ++++++------------- Sources/System/FilePermissions.swift | 7 +------ Sources/System/SystemString.swift | 10 +++------- 6 files changed, 22 insertions(+), 57 deletions(-) diff --git a/Sources/System/FileDescriptor.swift b/Sources/System/FileDescriptor.swift index 556e63ed..eda4f118 100644 --- a/Sources/System/FileDescriptor.swift +++ b/Sources/System/FileDescriptor.swift @@ -45,7 +45,8 @@ extension FileDescriptor { extension FileDescriptor { /// The desired read and write access for a newly opened file. @frozen - public struct AccessMode: RawRepresentable, Hashable, Codable { + @available(/*System 0.0.1: macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0*/iOS 8, *) + public struct AccessMode: RawRepresentable, Sendable, Hashable, Codable { /// The raw C access mode. @_alwaysEmitIntoClient public var rawValue: CInt @@ -87,7 +88,8 @@ extension FileDescriptor { /// Options that specify behavior for a newly-opened file. @frozen - public struct OpenOptions: OptionSet, Hashable, Codable { + @available(/*System 0.0.1: macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0*/iOS 8, *) + public struct OpenOptions: OptionSet, Sendable, Hashable, Codable { /// The raw C options. @_alwaysEmitIntoClient public var rawValue: CInt @@ -311,7 +313,8 @@ extension FileDescriptor { /// Options for specifying what a file descriptor's offset is relative to. @frozen - public struct SeekOrigin: RawRepresentable, Hashable, Codable { + @available(/*System 0.0.1: macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0*/iOS 8, *) + public struct SeekOrigin: RawRepresentable, Sendable, Hashable, Codable { /// The raw C value. @_alwaysEmitIntoClient public var rawValue: CInt @@ -475,18 +478,7 @@ extension FileDescriptor.OpenOptions public var debugDescription: String { self.description } } -#if compiler(>=5.5) && canImport(_Concurrency) // The decision on whether to make FileDescriptor Sendable or not // is currently being discussed in https://github.com/apple/swift-system/pull/112 //@available(*, unavailable, message: "File descriptors are not completely thread-safe.") //extension FileDescriptor: Sendable {} - -@available(/*System 0.0.1: macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0*/iOS 8, *) -extension FileDescriptor.AccessMode: Sendable {} - -@available(/*System 0.0.1: macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0*/iOS 8, *) -extension FileDescriptor.OpenOptions: Sendable {} - -@available(/*System 0.0.1: macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0*/iOS 8, *) -extension FileDescriptor.SeekOrigin: Sendable {} -#endif diff --git a/Sources/System/FilePath/FilePath.swift b/Sources/System/FilePath/FilePath.swift index c1dfd7f3..13064b5a 100644 --- a/Sources/System/FilePath/FilePath.swift +++ b/Sources/System/FilePath/FilePath.swift @@ -38,7 +38,7 @@ /// are file-system–specific and have additional considerations /// like case insensitivity, Unicode normalization, and symbolic links. @available(/*System 0.0.1: macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0*/iOS 8, *) -public struct FilePath { +public struct FilePath: Sendable { // TODO(docs): Section on all the new syntactic operations, lexical normalization, decomposition, // components, etc. internal var _storage: SystemString @@ -67,8 +67,3 @@ extension FilePath { @available(/*System 0.0.1: macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0*/iOS 8, *) extension FilePath: Hashable, Codable {} - -#if compiler(>=5.5) && canImport(_Concurrency) -@available(/*System 0.0.1: macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0*/iOS 8, *) -extension FilePath: Sendable {} -#endif diff --git a/Sources/System/FilePath/FilePathComponentView.swift b/Sources/System/FilePath/FilePathComponentView.swift index 1b9fd21f..39381b4b 100644 --- a/Sources/System/FilePath/FilePathComponentView.swift +++ b/Sources/System/FilePath/FilePathComponentView.swift @@ -28,7 +28,8 @@ extension FilePath { /// /// path.components.removeAll { $0.kind == .currentDirectory } /// // path is "/home/username/bin/scripts/tree" - public struct ComponentView { + @available(/*System 0.0.2: macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0*/iOS 8, *) + public struct ComponentView: Sendable { internal var _path: FilePath internal var _start: SystemString.Index @@ -66,7 +67,9 @@ extension FilePath { @available(/*System 0.0.2: macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0*/iOS 8, *) extension FilePath.ComponentView: BidirectionalCollection { public typealias Element = FilePath.Component - public struct Index: Comparable, Hashable { + + @available(/*System 0.0.2: macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0*/iOS 8, *) + public struct Index: Sendable, Comparable, Hashable { internal typealias Storage = SystemString.Index internal var _storage: Storage @@ -214,11 +217,3 @@ extension FilePath.ComponentView { #endif // DEBUG } } - -#if compiler(>=5.5) && canImport(_Concurrency) -@available(/*System 0.0.2: macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0*/iOS 8, *) -extension FilePath.ComponentView: Sendable {} - -@available(/*System 0.0.2: macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0*/iOS 8, *) -extension FilePath.ComponentView.Index: Sendable {} -#endif diff --git a/Sources/System/FilePath/FilePathComponents.swift b/Sources/System/FilePath/FilePathComponents.swift index ecd713ea..6b8a9408 100644 --- a/Sources/System/FilePath/FilePathComponents.swift +++ b/Sources/System/FilePath/FilePathComponents.swift @@ -28,7 +28,8 @@ extension FilePath { /// * `\\server\share\` /// * `\\?\UNC\server\share\` /// * `\\?\Volume{12345678-abcd-1111-2222-123445789abc}\` - public struct Root { + @available(/*System 0.0.2: macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0*/iOS 8, *) + public struct Root: Sendable { internal var _path: FilePath internal var _rootEnd: SystemString.Index @@ -54,7 +55,8 @@ extension FilePath { /// file.kind == .regular // true /// file.extension // "txt" /// path.append(file) // path is "/tmp/foo.txt" - public struct Component { + @available(/*System 0.0.2: macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0*/iOS 8, *) + public struct Component: Sendable { internal var _path: FilePath internal var _range: Range @@ -78,7 +80,8 @@ extension FilePath.Component { /// Whether a component is a regular file or directory name, or a special /// directory `.` or `..` @frozen - public enum Kind { + @available(/*System 0.0.2: macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0*/iOS 8, *) + public enum Kind: Sendable { /// The special directory `.`, representing the current directory. case currentDirectory @@ -285,14 +288,3 @@ extension FilePath.Root { #endif } } - -#if compiler(>=5.5) && canImport(_Concurrency) -@available(/*System 0.0.2: macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0*/iOS 8, *) -extension FilePath.Root: Sendable {} - -@available(/*System 0.0.2: macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0*/iOS 8, *) -extension FilePath.Component: Sendable {} - -@available(/*System 0.0.2: macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0*/iOS 8, *) -extension FilePath.Component.Kind: Sendable {} -#endif diff --git a/Sources/System/FilePermissions.swift b/Sources/System/FilePermissions.swift index 9bd6c5fb..2cdb32ff 100644 --- a/Sources/System/FilePermissions.swift +++ b/Sources/System/FilePermissions.swift @@ -18,7 +18,7 @@ /// perms == [.ownerReadWrite, .groupRead, .otherRead] // true @frozen @available(/*System 0.0.1: macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0*/iOS 8, *) -public struct FilePermissions: OptionSet, Hashable, Codable { +public struct FilePermissions: OptionSet, Sendable, Hashable, Codable { /// The raw C file permissions. @_alwaysEmitIntoClient public let rawValue: CModeT @@ -175,8 +175,3 @@ extension FilePermissions /// A textual representation of the file permissions, suitable for debugging. public var debugDescription: String { self.description } } - -#if compiler(>=5.5) && canImport(_Concurrency) -@available(/*System 0.0.1: macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0*/iOS 8, *) -extension FilePermissions: Sendable {} -#endif diff --git a/Sources/System/SystemString.swift b/Sources/System/SystemString.swift index 14651d52..9bfef49f 100644 --- a/Sources/System/SystemString.swift +++ b/Sources/System/SystemString.swift @@ -8,7 +8,8 @@ */ // A platform-native character representation, currently used for file paths -internal struct SystemChar: RawRepresentable, Comparable, Hashable, Codable { +internal struct SystemChar: + RawRepresentable, Sendable, Comparable, Hashable, Codable { internal typealias RawValue = CInterop.PlatformChar internal var rawValue: RawValue @@ -61,7 +62,7 @@ extension SystemChar { // A platform-native string representation, currently for file paths // // Always null-terminated. -internal struct SystemString { +internal struct SystemString: Sendable { internal typealias Storage = [SystemChar] internal var nullTerminatedStorage: Storage } @@ -288,11 +289,6 @@ extension SystemString { } } -#if compiler(>=5.5) && canImport(_Concurrency) -extension SystemChar: Sendable {} -extension SystemString: Sendable {} -#endif - // TODO: SystemString should use a COW-interchangable storage form rather // than array, so you could "borrow" the storage from a non-bridged String // or Data or whatever From faffb9a15a84f5194085d4c853f42651b683e5d0 Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Wed, 16 Aug 2023 13:19:52 -0700 Subject: [PATCH 163/427] Apply suggestions from code review This better communicates the intention. Co-authored-by: loffgren <117697138+loffgren@users.noreply.github.com> --- Tests/SystemTests/MachPortTests.swift | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/Tests/SystemTests/MachPortTests.swift b/Tests/SystemTests/MachPortTests.swift index 07da8bb2..8a6ade6a 100644 --- a/Tests/SystemTests/MachPortTests.swift +++ b/Tests/SystemTests/MachPortTests.swift @@ -188,23 +188,15 @@ final class MachPortTests: XCTestCase { let recv = Mach.Port() let send = recv.makeSendRight() _ = consume recv // and turn `send` into a dead name - do { - let copy = try send.copySendRight() - _ = copy - } - catch Mach.PortRightError.deadName { - // success + XCTAssertThrowsError(try send.copySendRight(), "Copying a dead name should throw") { error in + XCTAssertEqual(error, Mach.PortRightError.deadName) } } func testCopyDeadName2() throws { let send = Mach.Port(name: 0xffffffff) - do { - let copy = try send.copySendRight() - _ = copy - } - catch Mach.PortRightError.deadName { - // success + XCTAssertThrowsError(try send.copySendRight(), "Copying a dead name should throw") { error in + XCTAssertEqual(error, Mach.PortRightError.deadName) } } From ad407249fec4293dbf8b2578ac54b65f98060313 Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Wed, 16 Aug 2023 13:34:41 -0700 Subject: [PATCH 164/427] make test expression return `Void` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - It can’t return `Mach.Port`, because a noncopyable type cannot currently be a generic type argument. --- Tests/SystemTests/MachPortTests.swift | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/Tests/SystemTests/MachPortTests.swift b/Tests/SystemTests/MachPortTests.swift index 8a6ade6a..07b74106 100644 --- a/Tests/SystemTests/MachPortTests.swift +++ b/Tests/SystemTests/MachPortTests.swift @@ -188,15 +188,25 @@ final class MachPortTests: XCTestCase { let recv = Mach.Port() let send = recv.makeSendRight() _ = consume recv // and turn `send` into a dead name - XCTAssertThrowsError(try send.copySendRight(), "Copying a dead name should throw") { error in - XCTAssertEqual(error, Mach.PortRightError.deadName) + XCTAssertThrowsError( + _ = try send.copySendRight(), + "Copying a dead name should throw" + ) { error in + XCTAssertEqual( + error as! Mach.PortRightError, Mach.PortRightError.deadName + ) } } func testCopyDeadName2() throws { let send = Mach.Port(name: 0xffffffff) - XCTAssertThrowsError(try send.copySendRight(), "Copying a dead name should throw") { error in - XCTAssertEqual(error, Mach.PortRightError.deadName) + XCTAssertThrowsError( + _ = try send.copySendRight(), + "Copying a dead name should throw" + ) { error in + XCTAssertEqual( + error as! Mach.PortRightError, Mach.PortRightError.deadName + ) } } From 1ccaee141c106584962f208c45a35892cd1db4fc Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Tue, 5 Sep 2023 11:36:08 -0700 Subject: [PATCH 165/427] Fix typos in renaming message for Errno --- Sources/System/Errno.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/System/Errno.swift b/Sources/System/Errno.swift index d55e52bf..15d9fddc 100644 --- a/Sources/System/Errno.swift +++ b/Sources/System/Errno.swift @@ -142,7 +142,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { public static var execFormatError: Errno { Errno(_ENOEXEC) } @_alwaysEmitIntoClient - @available(*, unavailable, renamed: "noExec") + @available(*, unavailable, renamed: "execFormatError") public static var ENOEXEC: Errno { execFormatError } /// Bad file descriptor. @@ -529,7 +529,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { public static var nowInProgress: Errno { Errno(_EINPROGRESS) } @_alwaysEmitIntoClient - @available(*, unavailable, renamed: "nowInProcess") + @available(*, unavailable, renamed: "nowInProgress") public static var EINPROGRESS: Errno { nowInProgress } /// Operation already in progress. From 7654f14f8cd905933ea55048a919c593aeec8a84 Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Wed, 11 Oct 2023 14:28:43 -0700 Subject: [PATCH 166/427] [gardening] add some more availability annotations --- Sources/System/MachPort.swift | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Sources/System/MachPort.swift b/Sources/System/MachPort.swift index 223757b8..9eb47f8d 100644 --- a/Sources/System/MachPort.swift +++ b/Sources/System/MachPort.swift @@ -11,8 +11,10 @@ import Darwin.Mach +@available(/*System 1.3.0: macOS 9999, iOS 9999, watchOS 9999, tvOS 9999*/iOS 8, *) public protocol MachPortRight {} +@available(/*System 1.3.0: macOS 9999, iOS 9999, watchOS 9999, tvOS 9999*/iOS 8, *) @inlinable internal func _machPrecondition( file: StaticString = #file, @@ -27,6 +29,7 @@ internal func _machPrecondition( @available(/*System 1.3.0: macOS 9999, iOS 9999, watchOS 9999, tvOS 9999*/iOS 8, *) @frozen public enum Mach { + @available(/*System 1.3.0: macOS 9999, iOS 9999, watchOS 9999, tvOS 9999*/iOS 8, *) public struct Port: ~Copyable { @usableFromInline internal var _name: mach_port_name_t From 495f72934758a1c4b2e3fcaa385b67de57e09646 Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Wed, 11 Oct 2023 16:08:59 -0700 Subject: [PATCH 167/427] [noncopyability] explicitly prevent a capture This may be needed because of a bug involving resilient structs. --- Sources/System/MachPort.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Sources/System/MachPort.swift b/Sources/System/MachPort.swift index 9eb47f8d..51dbda6e 100644 --- a/Sources/System/MachPort.swift +++ b/Sources/System/MachPort.swift @@ -279,6 +279,7 @@ extension Mach.Port where RightType == Mach.ReceiveRight { ) withUnsafeMutablePointer(to: &status) { + [ _name = self._name ] in let status = UnsafeMutableBufferPointer(start: $0, count: 1) status.withMemoryRebound(to: integer_t.self) { let info = $0.baseAddress From fb0efe832cb0f0480420ba15005ff47281021cd4 Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Thu, 12 Oct 2023 15:02:31 -0700 Subject: [PATCH 168/427] [gardening] finish cleaning up old test files This should have been removed alongside XCTestManifests.swift --- Tests/LinuxMain.swift | 8 -------- 1 file changed, 8 deletions(-) delete mode 100644 Tests/LinuxMain.swift diff --git a/Tests/LinuxMain.swift b/Tests/LinuxMain.swift deleted file mode 100644 index 695f4e5b..00000000 --- a/Tests/LinuxMain.swift +++ /dev/null @@ -1,8 +0,0 @@ -import XCTest - -import SystemTests - -var tests = [XCTestCaseEntry]() -tests += SystemTests.__allTests() - -XCTMain(tests) From b92be8a02adda71c03576d28a8b4b58b1b6c2edc Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Fri, 13 Oct 2023 10:21:57 -0700 Subject: [PATCH 169/427] Update availability declarations for swift-system 1.1.0 API --- Sources/System/FileOperations.swift | 6 +++--- Utilities/expand-availability.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Sources/System/FileOperations.swift b/Sources/System/FileOperations.swift index 2297aeba..ada80cfe 100644 --- a/Sources/System/FileOperations.swift +++ b/Sources/System/FileOperations.swift @@ -435,7 +435,7 @@ extension FileDescriptor { } #if !os(Windows) -@available(/*System 1.1.0: macOS 9999, iOS 9999, watchOS 9999, tvOS 9999*/iOS 8, *) +@available(/*System 1.1.0: macOS 12.3, iOS 15.4, watchOS 8.5, tvOS 15.4*/iOS 8, *) extension FileDescriptor { /// Creates a unidirectional data channel, which can be used for interprocess communication. /// @@ -443,12 +443,12 @@ extension FileDescriptor { /// /// The corresponding C function is `pipe`. @_alwaysEmitIntoClient - @available(/*System 1.1.0: macOS 9999, iOS 9999, watchOS 9999, tvOS 9999*/iOS 8, *) + @available(/*System 1.1.0: macOS 12.3, iOS 15.4, watchOS 8.5, tvOS 15.4*/iOS 8, *) public static func pipe() throws -> (readEnd: FileDescriptor, writeEnd: FileDescriptor) { try _pipe().get() } - @available(/*System 1.1.0: macOS 9999, iOS 9999, watchOS 9999, tvOS 9999*/iOS 8, *) + @available(/*System 1.1.0: macOS 12.3, iOS 15.4, watchOS 8.5, tvOS 15.4*/iOS 8, *) @usableFromInline internal static func _pipe() -> Result<(readEnd: FileDescriptor, writeEnd: FileDescriptor), Errno> { var fds: (Int32, Int32) = (-1, -1) diff --git a/Utilities/expand-availability.py b/Utilities/expand-availability.py index 81dd97d9..26ec2dea 100755 --- a/Utilities/expand-availability.py +++ b/Utilities/expand-availability.py @@ -54,7 +54,7 @@ versions = { "System 0.0.1": "macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0", "System 0.0.2": "macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0", - "System 1.1.0": "macOS 9999, iOS 9999, watchOS 9999, tvOS 9999", + "System 1.1.0": "macOS 12.3, iOS 15.4, watchOS 8.5, tvOS 15.4", "System 1.2.0": "macOS 9999, iOS 9999, watchOS 9999, tvOS 9999", "System 1.3.0": "macOS 9999, iOS 9999, watchOS 9999, tvOS 9999", } From 882466a7300b15019fac5accf6b661683a6b9274 Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Fri, 13 Oct 2023 18:55:23 -0700 Subject: [PATCH 170/427] apply missing availability attributes to Mach.Port --- Sources/System/MachPort.swift | 13 ++++++++++++- Tests/SystemTests/MachPortTests.swift | 1 + 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/Sources/System/MachPort.swift b/Sources/System/MachPort.swift index 51dbda6e..10538bc0 100644 --- a/Sources/System/MachPort.swift +++ b/Sources/System/MachPort.swift @@ -129,6 +129,7 @@ public enum Mach { public struct SendOnceRight: MachPortRight {} } +@available(/*System 1.3.0: macOS 9999, iOS 9999, watchOS 9999, tvOS 9999*/iOS 8, *) extension Mach.Port where RightType == Mach.ReceiveRight { /// Transfer ownership of an existing, unmanaged, but already guarded, /// Mach port right into a Mach.Port by name. @@ -153,6 +154,7 @@ extension Mach.Port where RightType == Mach.ReceiveRight { /// This initializer will abort if the right could not be created. /// Callers may assert that a valid right is always returned. @inlinable + @available(/*System 1.3.0: macOS 9999, iOS 9999, watchOS 9999, tvOS 9999*/iOS 8, *) public init() { var storage: mach_port_name_t = mach_port_name_t(MACH_PORT_NULL) _machPrecondition( @@ -175,6 +177,7 @@ extension Mach.Port where RightType == Mach.ReceiveRight { /// After this function completes, the Mach.Port is destroyed and no longer /// usable. @inlinable + @available(/*System 1.3.0: macOS 9999, iOS 9999, watchOS 9999, tvOS 9999*/iOS 8, *) public consuming func relinquish( ) -> (name: mach_port_name_t, context: mach_port_context_t) { let destructured = (name: _name, context: _context) @@ -197,6 +200,7 @@ extension Mach.Port where RightType == Mach.ReceiveRight { /// Mach.ReceiveRights. Use relinquish() to avoid the syscall and extract /// the context value along with the port name. @inlinable + @available(/*System 1.3.0: macOS 9999, iOS 9999, watchOS 9999, tvOS 9999*/iOS 8, *) public consuming func unguardAndRelinquish() -> mach_port_name_t { let (name, context) = self.relinquish() _machPrecondition(mach_port_unguard(mach_task_self_, name, context)) @@ -213,6 +217,7 @@ extension Mach.Port where RightType == Mach.ReceiveRight { /// The body block may optionally return something, which will then be /// returned to the caller of withBorrowedName. @inlinable + @available(/*System 1.3.0: macOS 9999, iOS 9999, watchOS 9999, tvOS 9999*/iOS 8, *) public func withBorrowedName( body: (mach_port_name_t, mach_port_context_t) -> ReturnType ) -> ReturnType { @@ -226,6 +231,7 @@ extension Mach.Port where RightType == Mach.ReceiveRight { /// This function will abort if the right could not be created. /// Callers may assert that a valid right is always returned. @inlinable + @available(/*System 1.3.0: macOS 9999, iOS 9999, watchOS 9999, tvOS 9999*/iOS 8, *) public func makeSendOnceRight() -> Mach.Port { // send once rights do not coalesce var newRight: mach_port_name_t = mach_port_name_t(MACH_PORT_NULL) @@ -254,6 +260,7 @@ extension Mach.Port where RightType == Mach.ReceiveRight { /// This function will abort if the right could not be created. /// Callers may assert that a valid right is always returned. @inlinable + @available(/*System 1.3.0: macOS 9999, iOS 9999, watchOS 9999, tvOS 9999*/iOS 8, *) public func makeSendRight() -> Mach.Port { let how = MACH_MSG_TYPE_MAKE_SEND @@ -271,6 +278,7 @@ extension Mach.Port where RightType == Mach.ReceiveRight { /// /// Each get/set of this property makes a syscall. @inlinable + @available(/*System 1.3.0: macOS 9999, iOS 9999, watchOS 9999, tvOS 9999*/iOS 8, *) public var makeSendCount: mach_port_mscount_t { get { var status: mach_port_status = mach_port_status() @@ -299,6 +307,7 @@ extension Mach.Port where RightType == Mach.ReceiveRight { } } +@available(/*System 1.3.0: macOS 9999, iOS 9999, watchOS 9999, tvOS 9999*/iOS 8, *) extension Mach.Port where RightType == Mach.SendRight { /// Transfer ownership of the underlying port right to the caller. /// @@ -324,6 +333,7 @@ extension Mach.Port where RightType == Mach.SendRight { /// receiving side has been deallocated, then copySendRight() will throw /// a Mach.PortRightError.deadName error. @inlinable + @available(/*System 1.3.0: macOS 9999, iOS 9999, watchOS 9999, tvOS 9999*/iOS 8, *) public func copySendRight() throws -> Mach.Port { let how = MACH_MSG_TYPE_COPY_SEND @@ -340,7 +350,7 @@ extension Mach.Port where RightType == Mach.SendRight { } } - +@available(/*System 1.3.0: macOS 9999, iOS 9999, watchOS 9999, tvOS 9999*/iOS 8, *) extension Mach.Port where RightType == Mach.SendOnceRight { /// Transfer ownership of the underlying port right to the caller. /// @@ -352,6 +362,7 @@ extension Mach.Port where RightType == Mach.SendOnceRight { /// After this function completes, the Mach.Port is destroyed and no longer /// usable. @inlinable + @available(/*System 1.3.0: macOS 9999, iOS 9999, watchOS 9999, tvOS 9999*/iOS 8, *) public consuming func relinquish() -> mach_port_name_t { let name = _name discard self diff --git a/Tests/SystemTests/MachPortTests.swift b/Tests/SystemTests/MachPortTests.swift index 07b74106..7129a503 100644 --- a/Tests/SystemTests/MachPortTests.swift +++ b/Tests/SystemTests/MachPortTests.swift @@ -18,6 +18,7 @@ import SystemPackage import System #endif +@available(/*System 1.3.0: macOS 9999, iOS 9999, watchOS 9999, tvOS 9999*/iOS 8, *) final class MachPortTests: XCTestCase { func refCountForMachPortName(name:mach_port_name_t, kind:mach_port_right_t) -> mach_port_urefs_t { var refCount:mach_port_urefs_t = .max From 5c3617b9e9088ecd9b81b02095ba7b41d6766edc Mon Sep 17 00:00:00 2001 From: Finagolfin Date: Fri, 24 Nov 2023 18:59:06 +0530 Subject: [PATCH 171/427] Android: update pread/pwrite for nullability annotations in NDK 26 --- Sources/System/Internals/Syscalls.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/System/Internals/Syscalls.swift b/Sources/System/Internals/Syscalls.swift index 9fcb2f90..3d375a52 100644 --- a/Sources/System/Internals/Syscalls.swift +++ b/Sources/System/Internals/Syscalls.swift @@ -71,7 +71,7 @@ internal func system_pread( #if ENABLE_MOCKING if mockingEnabled { return _mockInt(fd, buf, nbyte, offset) } #endif - return pread(fd, buf, nbyte, offset) + return pread(fd, buf!, nbyte, offset) } // lseek @@ -101,7 +101,7 @@ internal func system_pwrite( #if ENABLE_MOCKING if mockingEnabled { return _mockInt(fd, buf, nbyte, offset) } #endif - return pwrite(fd, buf, nbyte, offset) + return pwrite(fd, buf!, nbyte, offset) } internal func system_dup(_ fd: Int32) -> Int32 { From c2e8f24c7c81c1842af8a556f7940abf96ec3a5f Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Thu, 4 Jan 2024 17:48:48 -0800 Subject: [PATCH 172/427] avoid passing null pointer to pread() or pwrite() --- Sources/System/Internals/Syscalls.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/System/Internals/Syscalls.swift b/Sources/System/Internals/Syscalls.swift index 3d375a52..d4549070 100644 --- a/Sources/System/Internals/Syscalls.swift +++ b/Sources/System/Internals/Syscalls.swift @@ -71,7 +71,7 @@ internal func system_pread( #if ENABLE_MOCKING if mockingEnabled { return _mockInt(fd, buf, nbyte, offset) } #endif - return pread(fd, buf!, nbyte, offset) + return buf.map({ pread(fd, $0, nbyte, offset) }) ?? 0 } // lseek @@ -101,7 +101,7 @@ internal func system_pwrite( #if ENABLE_MOCKING if mockingEnabled { return _mockInt(fd, buf, nbyte, offset) } #endif - return pwrite(fd, buf!, nbyte, offset) + return buf.map({ pwrite(fd, $0, nbyte, offset) }) ?? 0 } internal func system_dup(_ fd: Int32) -> Int32 { From c6b2119c816e16d161f077a8fb9b8be29b3bc8e1 Mon Sep 17 00:00:00 2001 From: Kyle Date: Mon, 22 Jan 2024 17:00:46 +0800 Subject: [PATCH 173/427] Enable SPI documentation and add API doc link --- .spi.yml | 4 ++++ README.md | 2 ++ 2 files changed, 6 insertions(+) create mode 100644 .spi.yml diff --git a/.spi.yml b/.spi.yml new file mode 100644 index 00000000..7535f52d --- /dev/null +++ b/.spi.yml @@ -0,0 +1,4 @@ +version: 1 +builder: + configs: + - documentation_targets: [SystemPackage] \ No newline at end of file diff --git a/README.md b/README.md index 11e57eb2..14edea0b 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,8 @@ try fd.closeAfter { } ``` +[API documentation](https://swiftpackageindex.com/apple/swift-system/main/documentation/SystemPackage) + ## Adding `SystemPackage` as a Dependency To use the `SystemPackage` library in a SwiftPM project, From e49d2b9bd2558fc0ef7780e965cd747f8c7a84e7 Mon Sep 17 00:00:00 2001 From: Kyle Date: Mon, 22 Jan 2024 17:23:31 +0800 Subject: [PATCH 174/427] Update .spi.yml Co-authored-by: Max Desiatov --- .spi.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.spi.yml b/.spi.yml index 7535f52d..b9cbb716 100644 --- a/.spi.yml +++ b/.spi.yml @@ -1,4 +1,4 @@ version: 1 builder: configs: - - documentation_targets: [SystemPackage] \ No newline at end of file + - documentation_targets: [SystemPackage] From 6b156898138b65d49f40ddc04e2090c88d9cbaaa Mon Sep 17 00:00:00 2001 From: Hiroshi Yamauchi Date: Tue, 13 Feb 2024 10:32:39 -0800 Subject: [PATCH 175/427] Support Windows ARM64 --- cmake/modules/SwiftSupport.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/modules/SwiftSupport.cmake b/cmake/modules/SwiftSupport.cmake index 82961db3..a73a4e57 100644 --- a/cmake/modules/SwiftSupport.cmake +++ b/cmake/modules/SwiftSupport.cmake @@ -17,7 +17,7 @@ See https://swift.org/LICENSE.txt for license information function(get_swift_host_arch result_var_name) if("${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "x86_64") set("${result_var_name}" "x86_64" PARENT_SCOPE) - elseif ("${CMAKE_SYSTEM_PROCESSOR}" MATCHES "AArch64|aarch64|arm64") + elseif ("${CMAKE_SYSTEM_PROCESSOR}" MATCHES "AArch64|aarch64|arm64|ARM64") if(CMAKE_SYSTEM_NAME MATCHES Darwin) set("${result_var_name}" "arm64" PARENT_SCOPE) else() From adca1f70b1f67bed7431bdfb8cebc3b544e80d28 Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Wed, 6 Mar 2024 17:45:23 -0800 Subject: [PATCH 176/427] Fix `SystemString.withCSIA()` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - The original implementation simply forwarded to the underlying `nullTerminatedStorage`, which contains one element – a null terminator – more than the actual `String`. - This isn’t in keeping with the sperit of `withContiguousStorageIfAvailable()`, which should present exactly the elements of the collection. --- Sources/System/SystemString.swift | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/Sources/System/SystemString.swift b/Sources/System/SystemString.swift index 9bfef49f..8ca643b0 100644 --- a/Sources/System/SystemString.swift +++ b/Sources/System/SystemString.swift @@ -142,31 +142,38 @@ extension SystemString: RangeReplaceableCollection { nullTerminatedStorage.reserveCapacity(1 + n) } - // TODO: Below include null terminator, is this desired? - internal func withContiguousStorageIfAvailable( _ body: (UnsafeBufferPointer) throws -> R ) rethrows -> R? { - try nullTerminatedStorage.withContiguousStorageIfAvailable(body) + // Do not include the null terminator, it is outside the Collection + try nullTerminatedStorage.withContiguousStorageIfAvailable { + try body(.init(start: $0.baseAddress, count: $0.count-1)) + } } internal mutating func withContiguousMutableStorageIfAvailable( _ body: (inout UnsafeMutableBufferPointer) throws -> R ) rethrows -> R? { defer { _invariantCheck() } - return try nullTerminatedStorage.withContiguousMutableStorageIfAvailable(body) + // Do not include the null terminator, it is outside the Collection + return try nullTerminatedStorage.withContiguousMutableStorageIfAvailable { + var buffer = UnsafeMutableBufferPointer( + start: $0.baseAddress, count: $0.count-1 + ) + return try body(&buffer) + } } } extension SystemString: Hashable, Codable {} extension SystemString { - // TODO: Below include null terminator, is this desired? + // withSystemChars includes the null terminator internal func withSystemChars( _ f: (UnsafeBufferPointer) throws -> T ) rethrows -> T { - try withContiguousStorageIfAvailable(f)! + try nullTerminatedStorage.withContiguousStorageIfAvailable(f)! } internal func withCodeUnits( From f59a0b73ce66bf5277acbf7f6a02c1e298bebd9b Mon Sep 17 00:00:00 2001 From: Sketch <75850871+SketchMaster2001@users.noreply.github.com> Date: Mon, 19 Feb 2024 23:19:22 -0500 Subject: [PATCH 177/427] Add support for visionOS --- Package.swift | 2 ++ Sources/System/Errno.swift | 20 +++++++++--------- Sources/System/FileDescriptor.swift | 10 ++++----- Sources/System/Internals/CInterop.swift | 2 +- Sources/System/Internals/Constants.swift | 26 ++++++++++++------------ Sources/System/Internals/Exports.swift | 4 ++-- Sources/System/Internals/Syscalls.swift | 2 +- Sources/System/MachPort.swift | 2 +- Tests/SystemTests/ErrnoTest.swift | 14 ++++++------- Tests/SystemTests/FileTypesTest.swift | 4 ++-- Tests/SystemTests/MachPortTests.swift | 2 +- 11 files changed, 45 insertions(+), 43 deletions(-) diff --git a/Package.swift b/Package.swift index 5f8efb05..7511a8bc 100644 --- a/Package.swift +++ b/Package.swift @@ -30,6 +30,7 @@ let package = Package( .define("_CRT_SECURE_NO_WARNINGS") ], swiftSettings: [ + .define("SYSTEM_PACKAGE_DARWIN", .when(platforms: [.macOS, .iOS, .watchOS, .tvOS, .visionOS])), .define("SYSTEM_PACKAGE"), .define("ENABLE_MOCKING", .when(configuration: .debug)) ]), @@ -37,6 +38,7 @@ let package = Package( name: "SystemTests", dependencies: ["SystemPackage"], swiftSettings: [ + .define("SYSTEM_PACKAGE_DARWIN", .when(platforms: [.macOS, .iOS, .watchOS, .tvOS, .visionOS])), .define("SYSTEM_PACKAGE") ]), ] diff --git a/Sources/System/Errno.swift b/Sources/System/Errno.swift index 15d9fddc..7f0aabea 100644 --- a/Sources/System/Errno.swift +++ b/Sources/System/Errno.swift @@ -23,7 +23,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { @_alwaysEmitIntoClient private init(_ raw: CInt) { self.init(rawValue: raw) } -#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) +#if SYSTEM_PACKAGE_DARWIN /// Error. Not used. @_alwaysEmitIntoClient public static var notUsed: Errno { Errno(_ERRNO_NOT_USED) } @@ -911,7 +911,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { @available(*, unavailable, renamed: "directoryNotEmpty") public static var ENOTEMPTY: Errno { directoryNotEmpty } -#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) +#if SYSTEM_PACKAGE_DARWIN /// Too many processes. /// /// The corresponding C error is `EPROCLIM`. @@ -968,7 +968,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { public static var ESTALE: Errno { staleNFSFileHandle } // TODO: Add Linux's RPC equivalents -#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) +#if SYSTEM_PACKAGE_DARWIN /// The structure of the remote procedure call (RPC) is bad. /// @@ -1060,7 +1060,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { public static var ENOSYS: Errno { noFunction } // BSD -#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) +#if SYSTEM_PACKAGE_DARWIN /// Inappropriate file type or format. /// /// The file was the wrong type for the operation, @@ -1075,7 +1075,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { public static var EFTYPE: Errno { badFileTypeOrFormat } #endif -#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) +#if SYSTEM_PACKAGE_DARWIN /// Authentication error. /// /// The authentication ticket used to mount an NFS file system was invalid. @@ -1102,7 +1102,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { public static var ENEEDAUTH: Errno { needAuthenticator } #endif -#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) +#if SYSTEM_PACKAGE_DARWIN /// Device power is off. /// /// The corresponding C error is `EPWROFF`. @@ -1142,7 +1142,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { public static var EOVERFLOW: Errno { overflow } #endif -#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) +#if SYSTEM_PACKAGE_DARWIN /// Bad executable or shared library. /// /// The executable or shared library being referenced was malformed. @@ -1246,7 +1246,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { @available(*, unavailable, renamed: "illegalByteSequence") public static var EILSEQ: Errno { illegalByteSequence } -#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) +#if SYSTEM_PACKAGE_DARWIN /// Attribute not found. /// /// The specified extended attribute doesn't exist. @@ -1413,7 +1413,7 @@ extension Errno { @available(*, unavailable, renamed: "tooManyRemoteLevels") public static var EREMOTE: Errno { tooManyRemoteLevels } -#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) +#if SYSTEM_PACKAGE_DARWIN /// No such policy registered. /// /// The corresponding C error is `ENOPOLICY`. @@ -1447,7 +1447,7 @@ extension Errno { public static var EOWNERDEAD: Errno { previousOwnerDied } #endif -#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) +#if SYSTEM_PACKAGE_DARWIN /// Interface output queue is full. /// /// The corresponding C error is `EQFULL`. diff --git a/Sources/System/FileDescriptor.swift b/Sources/System/FileDescriptor.swift index eda4f118..0ac7f4fe 100644 --- a/Sources/System/FileDescriptor.swift +++ b/Sources/System/FileDescriptor.swift @@ -185,7 +185,7 @@ extension FileDescriptor { @available(*, unavailable, renamed: "exclusiveCreate") public static var O_EXCL: OpenOptions { exclusiveCreate } -#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) +#if SYSTEM_PACKAGE_DARWIN /// Indicates that opening the file /// atomically obtains a shared lock on the file. /// @@ -253,7 +253,7 @@ extension FileDescriptor { public static var O_DIRECTORY: OpenOptions { directory } #endif -#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) +#if SYSTEM_PACKAGE_DARWIN /// Indicates that opening the file /// opens symbolic links instead of following them. /// @@ -357,7 +357,7 @@ extension FileDescriptor { // TODO: These are available on some versions of Linux with appropriate // macro defines. -#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) +#if SYSTEM_PACKAGE_DARWIN /// Indicates that the offset should be set /// to the next hole after the specified number of bytes. /// @@ -419,7 +419,7 @@ extension FileDescriptor.SeekOrigin case .start: return "start" case .current: return "current" case .end: return "end" -#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) +#if SYSTEM_PACKAGE_DARWIN case .nextHole: return "nextHole" case .nextData: return "nextData" #endif @@ -438,7 +438,7 @@ extension FileDescriptor.OpenOptions /// A textual representation of the open options. @inline(never) public var description: String { -#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) +#if SYSTEM_PACKAGE_DARWIN let descriptions: [(Element, StaticString)] = [ (.nonBlocking, ".nonBlocking"), (.append, ".append"), diff --git a/Sources/System/Internals/CInterop.swift b/Sources/System/Internals/CInterop.swift index a71ef127..3b4792e8 100644 --- a/Sources/System/Internals/CInterop.swift +++ b/Sources/System/Internals/CInterop.swift @@ -7,7 +7,7 @@ See https://swift.org/LICENSE.txt for license information */ -#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) +#if SYSTEM_PACKAGE_DARWIN import Darwin #elseif os(Windows) import CSystem diff --git a/Sources/System/Internals/Constants.swift b/Sources/System/Internals/Constants.swift index 592e38c5..8d4e4bb1 100644 --- a/Sources/System/Internals/Constants.swift +++ b/Sources/System/Internals/Constants.swift @@ -11,7 +11,7 @@ // they can be used anywhere without imports and without confusion to // unavailable local decls. -#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) +#if SYSTEM_PACKAGE_DARWIN import Darwin #elseif os(Windows) import CSystem @@ -26,7 +26,7 @@ import Musl #endif // MARK: errno -#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) +#if SYSTEM_PACKAGE_DARWIN @_alwaysEmitIntoClient internal var _ERRNO_NOT_USED: CInt { 0 } #endif @@ -272,7 +272,7 @@ internal var _EHOSTUNREACH: CInt { EHOSTUNREACH } @_alwaysEmitIntoClient internal var _ENOTEMPTY: CInt { ENOTEMPTY } -#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) +#if SYSTEM_PACKAGE_DARWIN @_alwaysEmitIntoClient internal var _EPROCLIM: CInt { EPROCLIM } #endif @@ -313,7 +313,7 @@ internal var _EREMOTE: CInt { #endif } -#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) +#if SYSTEM_PACKAGE_DARWIN @_alwaysEmitIntoClient internal var _EBADRPC: CInt { EBADRPC } @@ -336,7 +336,7 @@ internal var _ENOLCK: CInt { ENOLCK } @_alwaysEmitIntoClient internal var _ENOSYS: CInt { ENOSYS } -#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) +#if SYSTEM_PACKAGE_DARWIN @_alwaysEmitIntoClient internal var _EFTYPE: CInt { EFTYPE } @@ -358,7 +358,7 @@ internal var _EDEVERR: CInt { EDEVERR } internal var _EOVERFLOW: CInt { EOVERFLOW } #endif -#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) +#if SYSTEM_PACKAGE_DARWIN @_alwaysEmitIntoClient internal var _EBADEXEC: CInt { EBADEXEC } @@ -386,7 +386,7 @@ internal var _ENOMSG: CInt { ENOMSG } @_alwaysEmitIntoClient internal var _EILSEQ: CInt { EILSEQ } -#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) +#if SYSTEM_PACKAGE_DARWIN @_alwaysEmitIntoClient internal var _ENOATTR: CInt { ENOATTR } #endif @@ -424,7 +424,7 @@ internal var _ETIME: CInt { ETIME } @_alwaysEmitIntoClient internal var _EOPNOTSUPP: CInt { EOPNOTSUPP } -#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) +#if SYSTEM_PACKAGE_DARWIN @_alwaysEmitIntoClient internal var _ENOPOLICY: CInt { ENOPOLICY } #endif @@ -437,7 +437,7 @@ internal var _ENOTRECOVERABLE: CInt { ENOTRECOVERABLE } internal var _EOWNERDEAD: CInt { EOWNERDEAD } #endif -#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) +#if SYSTEM_PACKAGE_DARWIN @_alwaysEmitIntoClient internal var _EQFULL: CInt { EQFULL } @@ -472,7 +472,7 @@ internal var _O_NONBLOCK: CInt { O_NONBLOCK } @_alwaysEmitIntoClient internal var _O_APPEND: CInt { O_APPEND } -#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) +#if SYSTEM_PACKAGE_DARWIN @_alwaysEmitIntoClient internal var _O_SHLOCK: CInt { O_SHLOCK } @@ -498,7 +498,7 @@ internal var _O_TRUNC: CInt { O_TRUNC } @_alwaysEmitIntoClient internal var _O_EXCL: CInt { O_EXCL } -#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) +#if SYSTEM_PACKAGE_DARWIN @_alwaysEmitIntoClient internal var _O_EVTONLY: CInt { O_EVTONLY } #endif @@ -512,7 +512,7 @@ internal var _O_NOCTTY: CInt { O_NOCTTY } internal var _O_DIRECTORY: CInt { O_DIRECTORY } #endif -#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) +#if SYSTEM_PACKAGE_DARWIN @_alwaysEmitIntoClient internal var _O_SYMLINK: CInt { O_SYMLINK } #endif @@ -531,7 +531,7 @@ internal var _SEEK_CUR: CInt { SEEK_CUR } @_alwaysEmitIntoClient internal var _SEEK_END: CInt { SEEK_END } -#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) +#if SYSTEM_PACKAGE_DARWIN @_alwaysEmitIntoClient internal var _SEEK_HOLE: CInt { SEEK_HOLE } diff --git a/Sources/System/Internals/Exports.swift b/Sources/System/Internals/Exports.swift index 57abc6fc..e20454ee 100644 --- a/Sources/System/Internals/Exports.swift +++ b/Sources/System/Internals/Exports.swift @@ -12,7 +12,7 @@ // TODO: Should CSystem just include all the header files we need? -#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) +#if SYSTEM_PACKAGE_DARWIN import Darwin #elseif os(Windows) import CSystem @@ -31,7 +31,7 @@ internal typealias _COffT = off_t // MARK: syscalls and variables -#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) +#if SYSTEM_PACKAGE_DARWIN internal var system_errno: CInt { get { Darwin.errno } set { Darwin.errno = newValue } diff --git a/Sources/System/Internals/Syscalls.swift b/Sources/System/Internals/Syscalls.swift index 9fcb2f90..7c417e03 100644 --- a/Sources/System/Internals/Syscalls.swift +++ b/Sources/System/Internals/Syscalls.swift @@ -7,7 +7,7 @@ See https://swift.org/LICENSE.txt for license information */ -#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) +#if SYSTEM_PACKAGE_DARWIN import Darwin #elseif canImport(Glibc) import Glibc diff --git a/Sources/System/MachPort.swift b/Sources/System/MachPort.swift index 10538bc0..b3eb0a99 100644 --- a/Sources/System/MachPort.swift +++ b/Sources/System/MachPort.swift @@ -7,7 +7,7 @@ See https://swift.org/LICENSE.txt for license information */ -#if swift(>=5.9) && (os(macOS) || os(iOS) || os(watchOS) || os(tvOS)) +#if swift(>=5.9) && SYSTEM_PACKAGE_DARWIN import Darwin.Mach diff --git a/Tests/SystemTests/ErrnoTest.swift b/Tests/SystemTests/ErrnoTest.swift index 05cd5185..80e3e84f 100644 --- a/Tests/SystemTests/ErrnoTest.swift +++ b/Tests/SystemTests/ErrnoTest.swift @@ -106,7 +106,7 @@ final class ErrnoTest: XCTestCase { XCTAssert(EHOSTUNREACH == Errno.noRouteToHost.rawValue) XCTAssert(ENOTEMPTY == Errno.directoryNotEmpty.rawValue) -#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) +#if SYSTEM_PACKAGE_DARWIN XCTAssert(EPROCLIM == Errno.tooManyProcesses.rawValue) #endif @@ -120,7 +120,7 @@ final class ErrnoTest: XCTestCase { XCTAssert(ESTALE == Errno.staleNFSFileHandle.rawValue) #endif -#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) +#if SYSTEM_PACKAGE_DARWIN XCTAssert(EBADRPC == Errno.rpcUnsuccessful.rawValue) XCTAssert(ERPCMISMATCH == Errno.rpcVersionMismatch.rawValue) XCTAssert(EPROGUNAVAIL == Errno.rpcProgramUnavailable.rawValue) @@ -131,7 +131,7 @@ final class ErrnoTest: XCTestCase { XCTAssert(ENOLCK == Errno.noLocks.rawValue) XCTAssert(ENOSYS == Errno.noFunction.rawValue) -#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) +#if SYSTEM_PACKAGE_DARWIN XCTAssert(EFTYPE == Errno.badFileTypeOrFormat.rawValue) XCTAssert(EAUTH == Errno.authenticationError.rawValue) XCTAssert(ENEEDAUTH == Errno.needAuthenticator.rawValue) @@ -143,7 +143,7 @@ final class ErrnoTest: XCTestCase { XCTAssert(EOVERFLOW == Errno.overflow.rawValue) #endif -#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) +#if SYSTEM_PACKAGE_DARWIN XCTAssert(EBADEXEC == Errno.badExecutable.rawValue) XCTAssert(EBADARCH == Errno.badCPUType.rawValue) XCTAssert(ESHLIBVERS == Errno.sharedLibraryVersionMismatch.rawValue) @@ -157,7 +157,7 @@ final class ErrnoTest: XCTestCase { #endif XCTAssert(EILSEQ == Errno.illegalByteSequence.rawValue) -#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) +#if SYSTEM_PACKAGE_DARWIN XCTAssert(ENOATTR == Errno.attributeNotFound.rawValue) #endif @@ -183,7 +183,7 @@ final class ErrnoTest: XCTestCase { XCTAssert(EREMOTE == Errno.tooManyRemoteLevels.rawValue) #endif -#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) +#if SYSTEM_PACKAGE_DARWIN XCTAssert(ENOPOLICY == Errno.noSuchPolicy.rawValue) #endif @@ -192,7 +192,7 @@ final class ErrnoTest: XCTestCase { XCTAssert(EOWNERDEAD == Errno.previousOwnerDied.rawValue) #endif -#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) +#if SYSTEM_PACKAGE_DARWIN XCTAssert(EQFULL == Errno.outputQueueFull.rawValue) XCTAssert(ELAST == Errno.lastErrnoValue.rawValue) #endif diff --git a/Tests/SystemTests/FileTypesTest.swift b/Tests/SystemTests/FileTypesTest.swift index 4bddf410..620cfd70 100644 --- a/Tests/SystemTests/FileTypesTest.swift +++ b/Tests/SystemTests/FileTypesTest.swift @@ -42,7 +42,7 @@ final class FileDescriptorTest: XCTestCase { #endif // BSD only -#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) +#if SYSTEM_PACKAGE_DARWIN XCTAssertEqual(O_SHLOCK, FileDescriptor.OpenOptions.sharedLock.rawValue) XCTAssertEqual(O_EXLOCK, FileDescriptor.OpenOptions.exclusiveLock.rawValue) XCTAssertEqual(O_SYMLINK, FileDescriptor.OpenOptions.symlink.rawValue) @@ -53,7 +53,7 @@ final class FileDescriptorTest: XCTestCase { XCTAssertEqual(SEEK_CUR, FileDescriptor.SeekOrigin.current.rawValue) XCTAssertEqual(SEEK_END, FileDescriptor.SeekOrigin.end.rawValue) -#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) +#if SYSTEM_PACKAGE_DARWIN XCTAssertEqual(SEEK_HOLE, FileDescriptor.SeekOrigin.nextHole.rawValue) XCTAssertEqual(SEEK_DATA, FileDescriptor.SeekOrigin.nextData.rawValue) #endif diff --git a/Tests/SystemTests/MachPortTests.swift b/Tests/SystemTests/MachPortTests.swift index 7129a503..cba558bf 100644 --- a/Tests/SystemTests/MachPortTests.swift +++ b/Tests/SystemTests/MachPortTests.swift @@ -7,7 +7,7 @@ See https://swift.org/LICENSE.txt for license information */ -#if swift(>=5.9) && (os(macOS) || os(iOS) || os(watchOS) || os(tvOS)) +#if swift(>=5.9) && SYSTEM_PACKAGE_DARWIN import XCTest import Darwin.Mach From afb7fa4eef3d709a72c84e108efe7e214fd8e2fe Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Thu, 7 Mar 2024 16:51:12 -0800 Subject: [PATCH 178/427] Generate SystemString.string consistently - SystemString.string and Slice.string should be generated in the same manner. Now they are. --- Sources/System/SystemString.swift | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/Sources/System/SystemString.swift b/Sources/System/SystemString.swift index 8ca643b0..f5c558d7 100644 --- a/Sources/System/SystemString.swift +++ b/Sources/System/SystemString.swift @@ -202,7 +202,9 @@ extension Slice where Base == SystemString { } internal var string: String { - withCodeUnits { String(decoding: $0, as: CInterop.PlatformUnicodeEncoding.self) } + withCodeUnits { + String(decoding: $0, as: CInterop.PlatformUnicodeEncoding.self) + } } internal func withPlatformString( @@ -244,7 +246,11 @@ extension SystemString: ExpressibleByStringLiteral { } extension SystemString: CustomStringConvertible, CustomDebugStringConvertible { - internal var string: String { String(decoding: self) } + internal var string: String { + self.withCodeUnits { + String(decoding: $0, as: CInterop.PlatformUnicodeEncoding.self) + } + } internal var description: String { string } internal var debugDescription: String { description.debugDescription } From c86758849a80ea7f04b2d969aa0c9d2d5cc55d48 Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Thu, 7 Mar 2024 16:53:49 -0800 Subject: [PATCH 179/427] Test SystemString.string property consistency --- Tests/SystemTests/SystemStringTests.swift | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Tests/SystemTests/SystemStringTests.swift b/Tests/SystemTests/SystemStringTests.swift index a1891714..c929171a 100644 --- a/Tests/SystemTests/SystemStringTests.swift +++ b/Tests/SystemTests/SystemStringTests.swift @@ -254,6 +254,11 @@ final class SystemStringTest: XCTestCase { XCTAssert(str == "abcd") } + func testStringProperty() { + let source: [CInterop.PlatformChar] = [0x61, 0x62, 0, 0x63] + let str = SystemString(platformString: source) + XCTAssertEqual(str.string, str[...].string) + } } extension SystemStringTest { From def7b1a70c2fb303693c12eb6852d2e5560deeb5 Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Thu, 7 Mar 2024 16:31:54 -0800 Subject: [PATCH 180/427] Fix behaviour of `withCodeUnits()` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - The original implementation includes a trailing zero with the full collection, but the implementation for a slice does not. As a result, given a `let s: SystemString`, `s.string` was not equal to `s[…].string` --- Sources/System/SystemString.swift | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/Sources/System/SystemString.swift b/Sources/System/SystemString.swift index f5c558d7..67a7ffb7 100644 --- a/Sources/System/SystemString.swift +++ b/Sources/System/SystemString.swift @@ -176,17 +176,14 @@ extension SystemString { try nullTerminatedStorage.withContiguousStorageIfAvailable(f)! } + // withCodeUnits does not include the null terminator internal func withCodeUnits( _ f: (UnsafeBufferPointer) throws -> T ) rethrows -> T { - try withSystemChars { chars in - let length = chars.count * MemoryLayout.stride - let count = length / MemoryLayout.stride - return try chars.baseAddress!.withMemoryRebound( - to: CInterop.PlatformUnicodeEncoding.CodeUnit.self, - capacity: count - ) { pointer in - try f(UnsafeBufferPointer(start: pointer, count: count)) + try withSystemChars { + try $0.withMemoryRebound(to: CInterop.PlatformUnicodeEncoding.CodeUnit.self) { + assert($0.last == .zero) + return try f(.init(start: $0.baseAddress, count: $0.count&-1)) } } } From 17e97907923f650cff90c501ffcf8c5654b14165 Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Thu, 7 Mar 2024 17:00:47 -0800 Subject: [PATCH 181/427] Rename `withSystemChars` - `withNullTerminatedSystemChars` makes it clear that the null-termination is included. Operations where the null-termination is included should be obvious. --- Sources/System/FilePath/FilePathComponents.swift | 2 +- Sources/System/SystemString.swift | 9 ++++----- Tests/SystemTests/SystemStringTests.swift | 2 +- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/Sources/System/FilePath/FilePathComponents.swift b/Sources/System/FilePath/FilePathComponents.swift index 6b8a9408..a19526f2 100644 --- a/Sources/System/FilePath/FilePathComponents.swift +++ b/Sources/System/FilePath/FilePathComponents.swift @@ -146,7 +146,7 @@ extension _StrSlice { internal func _withSystemChars( _ f: (UnsafeBufferPointer) throws -> T ) rethrows -> T { - try _storage.withSystemChars { + try _storage.withNullTerminatedSystemChars { try f(UnsafeBufferPointer(rebasing: $0[_range])) } } diff --git a/Sources/System/SystemString.swift b/Sources/System/SystemString.swift index 67a7ffb7..9a974d5b 100644 --- a/Sources/System/SystemString.swift +++ b/Sources/System/SystemString.swift @@ -169,18 +169,17 @@ extension SystemString: Hashable, Codable {} extension SystemString { - // withSystemChars includes the null terminator - internal func withSystemChars( + internal func withNullTerminatedSystemChars( _ f: (UnsafeBufferPointer) throws -> T ) rethrows -> T { - try nullTerminatedStorage.withContiguousStorageIfAvailable(f)! + try nullTerminatedStorage.withUnsafeBufferPointer(f) } // withCodeUnits does not include the null terminator internal func withCodeUnits( _ f: (UnsafeBufferPointer) throws -> T ) rethrows -> T { - try withSystemChars { + try withNullTerminatedSystemChars { try $0.withMemoryRebound(to: CInterop.PlatformUnicodeEncoding.CodeUnit.self) { assert($0.last == .zero) return try f(.init(start: $0.baseAddress, count: $0.count&-1)) @@ -286,7 +285,7 @@ extension SystemString { internal func withPlatformString( _ f: (UnsafePointer) throws -> T ) rethrows -> T { - try withSystemChars { chars in + try withNullTerminatedSystemChars { chars in let length = chars.count * MemoryLayout.stride return try chars.baseAddress!.withMemoryRebound( to: CInterop.PlatformChar.self, diff --git a/Tests/SystemTests/SystemStringTests.swift b/Tests/SystemTests/SystemStringTests.swift index c929171a..1815392c 100644 --- a/Tests/SystemTests/SystemStringTests.swift +++ b/Tests/SystemTests/SystemStringTests.swift @@ -101,7 +101,7 @@ struct StringTest: TestCase { // Test null insertion let rawChars = raw.lazy.map { SystemChar($0) } expectEqual(sysRaw, SystemString(rawChars.dropLast())) - sysRaw.withSystemChars { // TODO: assuming we want null in withSysChars + sysRaw.withNullTerminatedSystemChars { expectEqualSequence(rawChars, $0, "rawChars") } From 6609a401b93116020590662cfd970480fc59de8d Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Thu, 7 Mar 2024 17:05:54 -0800 Subject: [PATCH 182/427] Update copyright notices --- Sources/System/FilePath/FilePathComponents.swift | 2 +- Sources/System/SystemString.swift | 2 +- Tests/SystemTests/SystemStringTests.swift | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Sources/System/FilePath/FilePathComponents.swift b/Sources/System/FilePath/FilePathComponents.swift index a19526f2..b304a0a7 100644 --- a/Sources/System/FilePath/FilePathComponents.swift +++ b/Sources/System/FilePath/FilePathComponents.swift @@ -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 - 2024 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 diff --git a/Sources/System/SystemString.swift b/Sources/System/SystemString.swift index 9a974d5b..36b68509 100644 --- a/Sources/System/SystemString.swift +++ b/Sources/System/SystemString.swift @@ -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 - 2024 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 diff --git a/Tests/SystemTests/SystemStringTests.swift b/Tests/SystemTests/SystemStringTests.swift index 1815392c..48ca83c1 100644 --- a/Tests/SystemTests/SystemStringTests.swift +++ b/Tests/SystemTests/SystemStringTests.swift @@ -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 - 2024 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 From 14597334dcdad2f0e669909882bddbb6f2762034 Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Fri, 5 Jan 2024 17:57:14 -0800 Subject: [PATCH 183/427] [test] test reading and writing empty, nil-based buffers --- Tests/SystemTests/FileOperationsTest.swift | 24 ++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/Tests/SystemTests/FileOperationsTest.swift b/Tests/SystemTests/FileOperationsTest.swift index 8062aedc..ca178ba5 100644 --- a/Tests/SystemTests/FileOperationsTest.swift +++ b/Tests/SystemTests/FileOperationsTest.swift @@ -86,6 +86,30 @@ final class FileOperationsTest: XCTestCase { for test in syscallTestCases { test.runAllTests() } } + func testWriteFromEmptyBuffer() throws { + let fd = try FileDescriptor.open(FilePath("/dev/null"), .writeOnly) + let written1 = try fd.write(toAbsoluteOffset: 0, .init(start: nil, count: 0)) + XCTAssertEqual(written1, 0) + + let pointer = UnsafeMutableRawPointer.allocate(byteCount: 8, alignment: 8) + defer { pointer.deallocate() } + let empty = UnsafeRawBufferPointer(start: pointer, count: 0) + let written2 = try fd.write(toAbsoluteOffset: 0, empty) + XCTAssertEqual(written2, 0) + } + + func testReadToEmptyBuffer() throws { + let fd = try FileDescriptor.open(FilePath("/dev/random"), .readOnly) + let read1 = try fd.read(fromAbsoluteOffset: 0, into: .init(start: nil, count: 0)) + XCTAssertEqual(read1, 0) + + let pointer = UnsafeMutableRawPointer.allocate(byteCount: 8, alignment: 8) + defer { pointer.deallocate() } + let empty = UnsafeMutableRawBufferPointer(start: pointer, count: 0) + let read2 = try fd.read(fromAbsoluteOffset: 0, into: empty) + XCTAssertEqual(read2, 0) + } + func testHelpers() { // TODO: Test writeAll, writeAll(toAbsoluteOffset), closeAfter } From 5ca37e7d3997b00e19d4a77d45db7830b18b6f56 Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Fri, 8 Mar 2024 12:58:48 -0800 Subject: [PATCH 184/427] Android: more targeted approach to unusual nullabilities --- Sources/System/Internals/Syscalls.swift | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/Sources/System/Internals/Syscalls.swift b/Sources/System/Internals/Syscalls.swift index d4549070..236ee6cd 100644 --- a/Sources/System/Internals/Syscalls.swift +++ b/Sources/System/Internals/Syscalls.swift @@ -71,7 +71,15 @@ internal func system_pread( #if ENABLE_MOCKING if mockingEnabled { return _mockInt(fd, buf, nbyte, offset) } #endif - return buf.map({ pread(fd, $0, nbyte, offset) }) ?? 0 +#if os(Android) + var zero = UInt8.zero + return withUnsafeMutablePointer(to: &zero) { + // this pread has a non-nullable `buf` pointer + pread(fd, buf ?? UnsafeMutableRawPointer($0), nbyte, offset) + } +#else + return pread(fd, buf, nbyte, offset) +#endif } // lseek @@ -101,7 +109,15 @@ internal func system_pwrite( #if ENABLE_MOCKING if mockingEnabled { return _mockInt(fd, buf, nbyte, offset) } #endif - return buf.map({ pwrite(fd, $0, nbyte, offset) }) ?? 0 +#if os(Android) + var zero = UInt8.zero + return withUnsafeMutablePointer(to: &zero) { + // this pwrite has a non-nullable `buf` pointer + pwrite(fd, buf ?? UnsafeRawPointer($0), nbyte, offset) + } +#else + return pwrite(fd, buf, nbyte, offset) +#endif } internal func system_dup(_ fd: Int32) -> Int32 { From 40b6b0ef8c2e21db12180036627b3cdc09a68bbf Mon Sep 17 00:00:00 2001 From: Max Desiatov Date: Tue, 30 Apr 2024 17:11:39 +0100 Subject: [PATCH 185/427] Add support for `WASILibc` module (#159) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Quoting the [wasi.dev landing page](https://wasi.dev): > The WebAssembly System Interface (WASI) is a group of standard API specifications for software compiled to the W3C WebAssembly (Wasm) standard. WASI is designed to provide a secure standard interface for applications that can be compiled to Wasm from any language, and that may run anywhere—from browsers to clouds to embedded devices. Currently support for WASI in Swift is based on [`wasi-libc`](https://github.com/WebAssembly/wasi-libc). Adding support for `wasi-libc` mostly amounted to excluding unsupported errnos, adding TLS dictionary storage shim for single-threaded environment, and adding constants in C headers for macros that Clang importer currently doesn't support. Co-authored-by: Guillaume Lessard --- Sources/CSystem/include/CSystemWASI.h | 31 +++++++ Sources/CSystem/include/module.modulemap | 1 + Sources/System/Errno.swift | 23 +++++- Sources/System/FileOperations.swift | 6 +- Sources/System/Internals/CInterop.swift | 4 +- Sources/System/Internals/Constants.swift | 100 ++++++++++++++++++++--- Sources/System/Internals/Exports.swift | 29 ++++++- Sources/System/Internals/Syscalls.swift | 8 +- 8 files changed, 180 insertions(+), 22 deletions(-) create mode 100644 Sources/CSystem/include/CSystemWASI.h diff --git a/Sources/CSystem/include/CSystemWASI.h b/Sources/CSystem/include/CSystemWASI.h new file mode 100644 index 00000000..9877853e --- /dev/null +++ b/Sources/CSystem/include/CSystemWASI.h @@ -0,0 +1,31 @@ +/* + This source file is part of the Swift System open source project + + Copyright (c) 2024 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 +*/ + +#pragma once + +#if __wasi__ + +#include +#include + +// wasi-libc defines the following constants in a way that Clang Importer can't +// understand, so we need to expose them manually. +static inline int32_t _getConst_O_ACCMODE(void) { return O_ACCMODE; } +static inline int32_t _getConst_O_APPEND(void) { return O_APPEND; } +static inline int32_t _getConst_O_CREAT(void) { return O_CREAT; } +static inline int32_t _getConst_O_DIRECTORY(void) { return O_DIRECTORY; } +static inline int32_t _getConst_O_EXCL(void) { return O_EXCL; } +static inline int32_t _getConst_O_NONBLOCK(void) { return O_NONBLOCK; } +static inline int32_t _getConst_O_TRUNC(void) { return O_TRUNC; } +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; } + +#endif diff --git a/Sources/CSystem/include/module.modulemap b/Sources/CSystem/include/module.modulemap index 776f7766..6e8b89e9 100644 --- a/Sources/CSystem/include/module.modulemap +++ b/Sources/CSystem/include/module.modulemap @@ -1,5 +1,6 @@ module CSystem { header "CSystemLinux.h" + header "CSystemWASI.h" header "CSystemWindows.h" export * } diff --git a/Sources/System/Errno.swift b/Sources/System/Errno.swift index 7f0aabea..1305593f 100644 --- a/Sources/System/Errno.swift +++ b/Sources/System/Errno.swift @@ -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) 2021 - 2024 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 @@ -229,7 +229,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { @available(*, unavailable, renamed: "badAddress") public static var EFAULT: Errno { badAddress } -#if !os(Windows) +#if !os(Windows) && !os(WASI) /// Not a block device. /// /// You attempted a block device operation on a nonblock device or file. @@ -621,6 +621,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { @available(*, unavailable, renamed: "protocolNotSupported") public static var EPROTONOSUPPORT: Errno { protocolNotSupported } +#if !os(WASI) /// Socket type not supported. /// /// Support for the socket type hasn't been configured into the system @@ -633,6 +634,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { @_alwaysEmitIntoClient @available(*, unavailable, renamed: "socketTypeNotSupported") public static var ESOCKTNOSUPPORT: Errno { socketTypeNotSupported } +#endif /// Not supported. /// @@ -647,6 +649,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { @available(*, unavailable, renamed: "notSupported") public static var ENOTSUP: Errno { notSupported } +#if !os(WASI) /// Protocol family not supported. /// /// The protocol family hasn't been configured into the system @@ -659,6 +662,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { @_alwaysEmitIntoClient @available(*, unavailable, renamed: "protocolFamilyNotSupported") public static var EPFNOSUPPORT: Errno { protocolFamilyNotSupported } +#endif /// The address family isn't supported by the protocol family. /// @@ -805,6 +809,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { @available(*, unavailable, renamed: "socketNotConnected") public static var ENOTCONN: Errno { socketNotConnected } +#if !os(WASI) /// Can't send after socket shutdown. /// /// A request to send data wasn't permitted @@ -818,6 +823,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { @_alwaysEmitIntoClient @available(*, unavailable, renamed: "socketShutdown") public static var ESHUTDOWN: Errno { socketShutdown } +#endif /// Operation timed out. /// @@ -874,6 +880,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { @available(*, unavailable, renamed: "fileNameTooLong") public static var ENAMETOOLONG: Errno { fileNameTooLong } +#if !os(WASI) /// The host is down. /// /// A socket operation failed because the destination host was down. @@ -885,6 +892,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { @_alwaysEmitIntoClient @available(*, unavailable, renamed: "hostIsDown") public static var EHOSTDOWN: Errno { hostIsDown } +#endif /// No route to host. /// @@ -923,6 +931,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { public static var EPROCLIM: Errno { tooManyProcesses } #endif +#if !os(WASI) /// Too many users. /// /// The quota system ran out of table entries. @@ -934,6 +943,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { @_alwaysEmitIntoClient @available(*, unavailable, renamed: "tooManyUsers") public static var EUSERS: Errno { tooManyUsers } +#endif /// Disk quota exceeded. /// @@ -1287,6 +1297,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { @available(*, unavailable, renamed: "multiHop") public static var EMULTIHOP: Errno { multiHop } +#if !os(WASI) /// No message available. /// /// No message was available to be received by the requested operation. @@ -1298,6 +1309,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { @_alwaysEmitIntoClient @available(*, unavailable, renamed: "noData") public static var ENODATA: Errno { noData } +#endif /// Reserved. /// @@ -1311,6 +1323,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { @available(*, unavailable, renamed: "noLink") public static var ENOLINK: Errno { noLink } +#if !os(WASI) /// Reserved. /// /// This error is reserved for future use. @@ -1334,6 +1347,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { @_alwaysEmitIntoClient @available(*, unavailable, renamed: "notStream") public static var ENOSTR: Errno { notStream } +#endif #endif /// Protocol error. @@ -1350,7 +1364,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { @available(*, unavailable, renamed: "protocolError") public static var EPROTO: Errno { protocolError } -#if !os(OpenBSD) +#if !os(OpenBSD) && !os(WASI) /// Reserved. /// /// This error is reserved for future use. @@ -1382,7 +1396,6 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { // Constants defined in header but not man page @available(/*System 0.0.1: macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0*/iOS 8, *) extension Errno { - /// Operation would block. /// /// The corresponding C error is `EWOULDBLOCK`. @@ -1393,6 +1406,7 @@ extension Errno { @available(*, unavailable, renamed: "wouldBlock") public static var EWOULDBLOCK: Errno { wouldBlock } +#if !os(WASI) /// Too many references: can't splice. /// /// The corresponding C error is `ETOOMANYREFS`. @@ -1412,6 +1426,7 @@ extension Errno { @_alwaysEmitIntoClient @available(*, unavailable, renamed: "tooManyRemoteLevels") public static var EREMOTE: Errno { tooManyRemoteLevels } +#endif #if SYSTEM_PACKAGE_DARWIN /// No such policy registered. diff --git a/Sources/System/FileOperations.swift b/Sources/System/FileOperations.swift index ada80cfe..a322a9b2 100644 --- a/Sources/System/FileOperations.swift +++ b/Sources/System/FileOperations.swift @@ -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 - 2024 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 @@ -369,6 +369,7 @@ extension FileDescriptor { } } +#if !os(WASI) @available(/*System 0.0.2: macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0*/iOS 8, *) extension FileDescriptor { /// Duplicates this file descriptor and return the newly created copy. @@ -433,8 +434,9 @@ extension FileDescriptor { fatalError("Not implemented") } } +#endif -#if !os(Windows) +#if !os(Windows) && !os(WASI) @available(/*System 1.1.0: macOS 12.3, iOS 15.4, watchOS 8.5, tvOS 15.4*/iOS 8, *) extension FileDescriptor { /// Creates a unidirectional data channel, which can be used for interprocess communication. diff --git a/Sources/System/Internals/CInterop.swift b/Sources/System/Internals/CInterop.swift index 3b4792e8..19cf4d56 100644 --- a/Sources/System/Internals/CInterop.swift +++ b/Sources/System/Internals/CInterop.swift @@ -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 - 2024 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 @@ -18,6 +18,8 @@ import Glibc #elseif canImport(Musl) @_implementationOnly import CSystem import Musl +#elseif canImport(WASILibc) +import WASILibc #else #error("Unsupported Platform") #endif diff --git a/Sources/System/Internals/Constants.swift b/Sources/System/Internals/Constants.swift index 8d4e4bb1..27a145f7 100644 --- a/Sources/System/Internals/Constants.swift +++ b/Sources/System/Internals/Constants.swift @@ -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 - 2024 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 @@ -21,6 +21,9 @@ import Glibc #elseif canImport(Musl) import CSystem import Musl +#elseif canImport(WASILibc) +import CSystem +import WASILibc #else #error("Unsupported Platform") #endif @@ -73,7 +76,7 @@ internal var _EACCES: CInt { EACCES } @_alwaysEmitIntoClient internal var _EFAULT: CInt { EFAULT } -#if !os(Windows) +#if !os(Windows) && !os(WASI) @_alwaysEmitIntoClient internal var _ENOTBLK: CInt { ENOTBLK } #endif @@ -141,7 +144,13 @@ internal var _ERANGE: CInt { ERANGE } internal var _EAGAIN: CInt { EAGAIN } @_alwaysEmitIntoClient -internal var _EWOULDBLOCK: CInt { EWOULDBLOCK } +internal var _EWOULDBLOCK: CInt { +#if os(WASI) + _getConst_EWOULDBLOCK() +#else + EWOULDBLOCK +#endif +} @_alwaysEmitIntoClient internal var _EINPROGRESS: CInt { EINPROGRESS } @@ -167,6 +176,7 @@ internal var _ENOPROTOOPT: CInt { ENOPROTOOPT } @_alwaysEmitIntoClient internal var _EPROTONOSUPPORT: CInt { EPROTONOSUPPORT } +#if !os(WASI) @_alwaysEmitIntoClient internal var _ESOCKTNOSUPPORT: CInt { #if os(Windows) @@ -175,6 +185,7 @@ internal var _ESOCKTNOSUPPORT: CInt { return ESOCKTNOSUPPORT #endif } +#endif @_alwaysEmitIntoClient internal var _ENOTSUP: CInt { @@ -185,6 +196,7 @@ internal var _ENOTSUP: CInt { #endif } +#if !os(WASI) @_alwaysEmitIntoClient internal var _EPFNOSUPPORT: CInt { #if os(Windows) @@ -193,6 +205,7 @@ internal var _EPFNOSUPPORT: CInt { return EPFNOSUPPORT #endif } +#endif @_alwaysEmitIntoClient internal var _EAFNOSUPPORT: CInt { EAFNOSUPPORT } @@ -227,6 +240,7 @@ internal var _EISCONN: CInt { EISCONN } @_alwaysEmitIntoClient internal var _ENOTCONN: CInt { ENOTCONN } +#if !os(WASI) @_alwaysEmitIntoClient internal var _ESHUTDOWN: CInt { #if os(Windows) @@ -244,6 +258,7 @@ internal var _ETOOMANYREFS: CInt { return ETOOMANYREFS #endif } +#endif @_alwaysEmitIntoClient internal var _ETIMEDOUT: CInt { ETIMEDOUT } @@ -257,6 +272,7 @@ internal var _ELOOP: CInt { ELOOP } @_alwaysEmitIntoClient internal var _ENAMETOOLONG: CInt { ENAMETOOLONG } +#if !os(WASI) @_alwaysEmitIntoClient internal var _EHOSTDOWN: CInt { #if os(Windows) @@ -265,6 +281,7 @@ internal var _EHOSTDOWN: CInt { return EHOSTDOWN #endif } +#endif @_alwaysEmitIntoClient internal var _EHOSTUNREACH: CInt { EHOSTUNREACH } @@ -277,6 +294,7 @@ internal var _ENOTEMPTY: CInt { ENOTEMPTY } internal var _EPROCLIM: CInt { EPROCLIM } #endif +#if !os(WASI) @_alwaysEmitIntoClient internal var _EUSERS: CInt { #if os(Windows) @@ -285,6 +303,7 @@ internal var _EUSERS: CInt { return EUSERS #endif } +#endif @_alwaysEmitIntoClient internal var _EDQUOT: CInt { @@ -304,6 +323,7 @@ internal var _ESTALE: CInt { #endif } +#if !os(WASI) @_alwaysEmitIntoClient internal var _EREMOTE: CInt { #if os(Windows) @@ -312,6 +332,7 @@ internal var _EREMOTE: CInt { return EREMOTE #endif } +#endif #if SYSTEM_PACKAGE_DARWIN @_alwaysEmitIntoClient @@ -399,30 +420,41 @@ internal var _EBADMSG: CInt { EBADMSG } @_alwaysEmitIntoClient internal var _EMULTIHOP: CInt { EMULTIHOP } +#if !os(WASI) @_alwaysEmitIntoClient internal var _ENODATA: CInt { ENODATA } +#endif @_alwaysEmitIntoClient internal var _ENOLINK: CInt { ENOLINK } +#if !os(WASI) @_alwaysEmitIntoClient internal var _ENOSR: CInt { ENOSR } @_alwaysEmitIntoClient internal var _ENOSTR: CInt { ENOSTR } #endif +#endif @_alwaysEmitIntoClient internal var _EPROTO: CInt { EPROTO } -#if !os(OpenBSD) +#if !os(OpenBSD) && !os(WASI) @_alwaysEmitIntoClient internal var _ETIME: CInt { ETIME } #endif #endif + @_alwaysEmitIntoClient -internal var _EOPNOTSUPP: CInt { EOPNOTSUPP } +internal var _EOPNOTSUPP: CInt { +#if os(WASI) + _getConst_EOPNOTSUPP() +#else + EOPNOTSUPP +#endif +} #if SYSTEM_PACKAGE_DARWIN @_alwaysEmitIntoClient @@ -462,15 +494,33 @@ internal var _O_ACCMODE: CInt { 0x03|O_SEARCH } #else // TODO: API? @_alwaysEmitIntoClient -internal var _O_ACCMODE: CInt { O_ACCMODE } +internal var _O_ACCMODE: CInt { +#if os(WASI) + _getConst_O_ACCMODE() +#else + O_ACCMODE +#endif +} #endif @_alwaysEmitIntoClient -internal var _O_NONBLOCK: CInt { O_NONBLOCK } +internal var _O_NONBLOCK: CInt { +#if os(WASI) + _getConst_O_NONBLOCK() +#else + O_NONBLOCK +#endif +} #endif @_alwaysEmitIntoClient -internal var _O_APPEND: CInt { O_APPEND } +internal var _O_APPEND: CInt { +#if os(WASI) + _getConst_O_APPEND() +#else + O_APPEND +#endif +} #if SYSTEM_PACKAGE_DARWIN @_alwaysEmitIntoClient @@ -481,22 +531,42 @@ internal var _O_EXLOCK: CInt { O_EXLOCK } #endif #if !os(Windows) +#if !os(WASI) // TODO: API? @_alwaysEmitIntoClient internal var _O_ASYNC: CInt { O_ASYNC } +#endif @_alwaysEmitIntoClient internal var _O_NOFOLLOW: CInt { O_NOFOLLOW } #endif @_alwaysEmitIntoClient -internal var _O_CREAT: CInt { O_CREAT } +internal var _O_CREAT: CInt { +#if os(WASI) + _getConst_O_CREAT() +#else + O_CREAT +#endif +} @_alwaysEmitIntoClient -internal var _O_TRUNC: CInt { O_TRUNC } +internal var _O_TRUNC: CInt { +#if os(WASI) + _getConst_O_TRUNC() +#else + O_TRUNC +#endif +} @_alwaysEmitIntoClient -internal var _O_EXCL: CInt { O_EXCL } +internal var _O_EXCL: CInt { +#if os(WASI) + _getConst_O_EXCL() +#else + O_EXCL +#endif +} #if SYSTEM_PACKAGE_DARWIN @_alwaysEmitIntoClient @@ -509,7 +579,13 @@ internal var _O_EVTONLY: CInt { O_EVTONLY } internal var _O_NOCTTY: CInt { O_NOCTTY } @_alwaysEmitIntoClient -internal var _O_DIRECTORY: CInt { O_DIRECTORY } +internal var _O_DIRECTORY: CInt { +#if os(WASI) + _getConst_O_DIRECTORY() +#else + O_DIRECTORY +#endif +} #endif #if SYSTEM_PACKAGE_DARWIN diff --git a/Sources/System/Internals/Exports.swift b/Sources/System/Internals/Exports.swift index e20454ee..f4358c5b 100644 --- a/Sources/System/Internals/Exports.swift +++ b/Sources/System/Internals/Exports.swift @@ -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 - 2024 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 @@ -23,6 +23,8 @@ import Glibc #elseif canImport(Musl) @_implementationOnly import CSystem import Musl +#elseif canImport(WASILibc) +import WASILibc #else #error("Unsupported Platform") #endif @@ -58,6 +60,11 @@ internal var system_errno: CInt { get { Musl.errno } set { Musl.errno = newValue } } +#elseif canImport(WASILibc) +internal var system_errno: CInt { + get { WASILibc.errno } + set { WASILibc.errno = newValue } +} #endif // MARK: C stdlib decls @@ -149,6 +156,24 @@ extension String { // TLS #if os(Windows) internal typealias _PlatformTLSKey = DWORD +#elseif os(WASI) && (swift(<6.1) || !_runtime(_multithreaded)) +// Mock TLS storage for single-threaded WASI +internal final class _PlatformTLSKey { + fileprivate init() {} +} +private final class TLSStorage: @unchecked Sendable { + var storage = [ObjectIdentifier: UnsafeMutableRawPointer]() +} +private let sharedTLSStorage = TLSStorage() + +func pthread_setspecific(_ key: _PlatformTLSKey, _ p: UnsafeMutableRawPointer?) -> Int { + sharedTLSStorage.storage[ObjectIdentifier(key)] = p + return 0 +} + +func pthread_getspecific(_ key: _PlatformTLSKey) -> UnsafeMutableRawPointer? { + sharedTLSStorage.storage[ObjectIdentifier(key)] +} #else internal typealias _PlatformTLSKey = pthread_key_t #endif @@ -160,6 +185,8 @@ internal func makeTLSKey() -> _PlatformTLSKey { fatalError("Unable to create key") } return raw + #elseif os(WASI) && (swift(<6.1) || !_runtime(_multithreaded)) + return _PlatformTLSKey() #else var raw = pthread_key_t() guard 0 == pthread_key_create(&raw, nil) else { diff --git a/Sources/System/Internals/Syscalls.swift b/Sources/System/Internals/Syscalls.swift index 793fdfdb..69931b3a 100644 --- a/Sources/System/Internals/Syscalls.swift +++ b/Sources/System/Internals/Syscalls.swift @@ -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 - 2024 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 @@ -13,6 +13,8 @@ import Darwin import Glibc #elseif canImport(Musl) import Musl +#elseif canImport(WASILibc) +import WASILibc #elseif os(Windows) import ucrt #else @@ -120,6 +122,7 @@ internal func system_pwrite( #endif } +#if !os(WASI) internal func system_dup(_ fd: Int32) -> Int32 { #if ENABLE_MOCKING if mockingEnabled { return _mock(fd) } @@ -133,8 +136,9 @@ internal func system_dup2(_ fd: Int32, _ fd2: Int32) -> Int32 { #endif return dup2(fd, fd2) } +#endif -#if !os(Windows) +#if !os(Windows) && !os(WASI) internal func system_pipe(_ fds: UnsafeMutablePointer) -> CInt { #if ENABLE_MOCKING if mockingEnabled { return _mock(fds) } From dd45c800623379ac6b1c2a6eff79ada5fd5e225a Mon Sep 17 00:00:00 2001 From: Jeremy Schonfeld Date: Thu, 2 May 2024 13:07:27 -0700 Subject: [PATCH 186/427] Disfavor String.init(platformString: UnsafePointer) --- Sources/System/PlatformString.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Sources/System/PlatformString.swift b/Sources/System/PlatformString.swift index 35fab597..3aecf977 100644 --- a/Sources/System/PlatformString.swift +++ b/Sources/System/PlatformString.swift @@ -20,6 +20,7 @@ extension String { /// This means that, depending on the semantics of the specific platform, /// conversion to a string and back might result in a value that's different /// from the original platform string. + @_disfavoredOverload public init(platformString: UnsafePointer) { self.init(_errorCorrectingPlatformString: platformString) } From cfd3f9da906cf62efafd162d524df6334cc85da6 Mon Sep 17 00:00:00 2001 From: Alastair Houghton Date: Wed, 20 Mar 2024 14:46:10 +0000 Subject: [PATCH 187/427] Fix swift-system to work on Windows. The swift-system tests were not passing, for various reasons, mostly to do with path related confusion. As part of this, add a function to create a temporary directory, as that was one of the problems. Also add Windows support for pipe() and ftruncate(), which was missing. rdar://125087707 --- Package.swift | 4 +- Sources/System/ErrnoWindows.swift | 20 ++ Sources/System/FileOperations.swift | 4 +- Sources/System/FilePath/FilePathParsing.swift | 10 +- Sources/System/FilePath/FilePathTemp.swift | 256 ++++++++++++++++++ Sources/System/Internals/Mocking.swift | 16 +- Sources/System/Internals/Syscalls.swift | 76 +++++- .../Internals/WindowsSyscallAdapters.swift | 133 ++++++++- Tests/SystemTests/FileOperationsTest.swift | 124 ++++----- .../FilePathComponentsTest.swift | 20 +- .../FilePathTests/FilePathSyntaxTest.swift | 3 + .../FilePathTests/FilePathTempTest.swift | 49 ++++ .../FilePathTests/FilePathTest.swift | 10 + 13 files changed, 635 insertions(+), 90 deletions(-) create mode 100644 Sources/System/ErrnoWindows.swift create mode 100644 Sources/System/FilePath/FilePathTemp.swift create mode 100644 Tests/SystemTests/FilePathTests/FilePathTempTest.swift diff --git a/Package.swift b/Package.swift index 7511a8bc..26bd4e63 100644 --- a/Package.swift +++ b/Package.swift @@ -15,7 +15,7 @@ import PackageDescription let package = Package( name: "swift-system", products: [ - .library(name: "SystemPackage", targets: ["SystemPackage"]), + .library(name: "SystemPackage", targets: ["SystemPackage"]) ], dependencies: [], targets: [ @@ -40,6 +40,6 @@ let package = Package( swiftSettings: [ .define("SYSTEM_PACKAGE_DARWIN", .when(platforms: [.macOS, .iOS, .watchOS, .tvOS, .visionOS])), .define("SYSTEM_PACKAGE") - ]), + ]) ] ) diff --git a/Sources/System/ErrnoWindows.swift b/Sources/System/ErrnoWindows.swift new file mode 100644 index 00000000..98fda433 --- /dev/null +++ b/Sources/System/ErrnoWindows.swift @@ -0,0 +1,20 @@ +/* + This source file is part of the Swift System open source project + + Copyright (c) 2024 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 +*/ + +#if os(Windows) + +import WinSDK + +extension Errno { + public init(windowsError: DWORD) { + self.init(rawValue: mapWindowsErrorToErrno(windowsError)) + } +} + +#endif diff --git a/Sources/System/FileOperations.swift b/Sources/System/FileOperations.swift index a322a9b2..d2e8d657 100644 --- a/Sources/System/FileOperations.swift +++ b/Sources/System/FileOperations.swift @@ -436,7 +436,7 @@ extension FileDescriptor { } #endif -#if !os(Windows) && !os(WASI) +#if !os(WASI) @available(/*System 1.1.0: macOS 12.3, iOS 15.4, watchOS 8.5, tvOS 15.4*/iOS 8, *) extension FileDescriptor { /// Creates a unidirectional data channel, which can be used for interprocess communication. @@ -465,7 +465,6 @@ extension FileDescriptor { } #endif -#if !os(Windows) @available(/*System 1.2.0: macOS 9999, iOS 9999, watchOS 9999, tvOS 9999*/iOS 8, *) extension FileDescriptor { /// Truncates or extends the file referenced by this file descriptor. @@ -511,4 +510,3 @@ extension FileDescriptor { } } } -#endif diff --git a/Sources/System/FilePath/FilePathParsing.swift b/Sources/System/FilePath/FilePathParsing.swift index 6d014774..0ab4f7ec 100644 --- a/Sources/System/FilePath/FilePathParsing.swift +++ b/Sources/System/FilePath/FilePathParsing.swift @@ -89,6 +89,11 @@ extension SystemString { // `_prenormalizeWindowsRoots` and resume. readIdx = _prenormalizeWindowsRoots() writeIdx = readIdx + + // Skip redundant separators + while readIdx < endIndex && isSeparator(self[readIdx]) { + self.formIndex(after: &readIdx) + } } else { assert(genericSeparator == platformSeparator) } @@ -330,10 +335,13 @@ extension FilePath { // Whether we are providing Windows paths @inline(__always) internal var _windowsPaths: Bool { + if let forceWindowsPaths = forceWindowsPaths { + return forceWindowsPaths + } #if os(Windows) return true #else - return forceWindowsPaths + return false #endif } diff --git a/Sources/System/FilePath/FilePathTemp.swift b/Sources/System/FilePath/FilePathTemp.swift new file mode 100644 index 00000000..9aa65ff9 --- /dev/null +++ b/Sources/System/FilePath/FilePathTemp.swift @@ -0,0 +1,256 @@ +/* + This source file is part of the Swift System open source project + + Copyright (c) 2024 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 +*/ + +// MARK: - API + +public func withTemporaryPath(basename: FilePath.Component, + _ body: (FilePath) throws -> R) throws -> R { + let temporaryDir = try createUniqueTemporaryDirectory(basename: basename) + defer { + try? recursiveRemove(at: temporaryDir) + } + + return try body(temporaryDir) +} + +// MARK: - Internals + +#if os(Windows) +import WinSDK + +internal func getTemporaryDirectory() throws -> FilePath { + return try withUnsafeTemporaryAllocation(of: CInterop.PlatformChar.self, + capacity: Int(MAX_PATH) + 1) { + buffer in + + let count = GetTempPath2W(DWORD(buffer.count), buffer.baseAddress) + if count == 0 { + throw Errno(windowsError: GetLastError()) + } + + return FilePath(SystemString(platformString: buffer.baseAddress!)) + } +} + +internal func forEachFile( + at path: FilePath, + _ body: (WIN32_FIND_DATAW) throws -> () +) rethrows { + let searchPath = path.appending("\\*") + + try searchPath.withPlatformString { szPath in + var findData = WIN32_FIND_DATAW() + let hFind = FindFirstFileW(szPath, &findData) + if hFind == INVALID_HANDLE_VALUE { + throw Errno(windowsError: GetLastError()) + } + defer { + FindClose(hFind) + } + + repeat { + // Skip . and .. + if findData.cFileName.0 == 46 + && (findData.cFileName.1 == 0 + || (findData.cFileName.1 == 46 + && findData.cFileName.2 == 0)) { + continue + } + + try body(findData) + } while FindNextFileW(hFind, &findData) + } +} + +internal func recursiveRemove(at path: FilePath) throws { + // First, deal with subdirectories + try forEachFile(at: path) { findData in + if (findData.dwFileAttributes & DWORD(FILE_ATTRIBUTE_DIRECTORY)) != 0 { + let name = withUnsafeBytes(of: findData.cFileName) { + return SystemString(platformString: $0.assumingMemoryBound( + to: CInterop.PlatformChar.self).baseAddress!) + } + let component = FilePath.Component(name)! + let subpath = path.appending(component) + + try recursiveRemove(at: subpath) + } + } + + // Now delete everything else + try forEachFile(at: path) { findData in + let name = withUnsafeBytes(of: findData.cFileName) { + return SystemString(platformString: $0.assumingMemoryBound( + to: CInterop.PlatformChar.self).baseAddress!) + } + let component = FilePath.Component(name)! + let subpath = path.appending(component) + + if (findData.dwFileAttributes & DWORD(FILE_ATTRIBUTE_DIRECTORY)) == 0 { + try subpath.withPlatformString { + if !DeleteFileW($0) { + throw Errno(windowsError: GetLastError()) + } + } + } + } + + // Finally, delete the parent + try path.withPlatformString { + if !RemoveDirectoryW($0) { + throw Errno(windowsError: GetLastError()) + } + } +} + +#else +internal func getTemporaryDirectory() throws -> FilePath { + return "/tmp" +} + +internal func recursiveRemove(at path: FilePath) throws { + let dirfd = try FileDescriptor.open(path, .readOnly, options: .directory) + defer { + try? dirfd.close() + } + + let dot: (CInterop.PlatformChar, CInterop.PlatformChar) = (46, 0) + try withUnsafeBytes(of: dot) { + try recursiveRemove( + in: dirfd.rawValue, + path: $0.assumingMemoryBound(to: CInterop.PlatformChar.self).baseAddress! + ) + } + + try path.withPlatformString { + if system_rmdir($0) != 0 { + throw Errno.current + } + } +} + +fileprivate func impl_opendirat( + _ dirfd: CInt, + _ path: UnsafePointer +) -> UnsafeMutablePointer? { + let fd = system_openat(dirfd, path, + FileDescriptor.AccessMode.readOnly.rawValue + | FileDescriptor.OpenOptions.directory.rawValue) + if fd < 0 { + return nil + } + return system_fdopendir(fd) +} + +internal func forEachFile( + in dirfd: CInt, path: UnsafePointer, + _ body: (system_dirent) throws -> () +) throws { + guard let dir = impl_opendirat(dirfd, path) else { + throw Errno.current + } + defer { + _ = system_closedir(dir) + } + + while let dirent = system_readdir(dir) { + // Skip . and .. + if dirent.pointee.d_name.0 == 46 + && (dirent.pointee.d_name.1 == 0 + || (dirent.pointee.d_name.1 == 46 + && dirent.pointee.d_name.2 == 0)) { + continue + } + + try body(dirent.pointee) + } +} + +internal func recursiveRemove( + in dirfd: CInt, + path: UnsafePointer +) throws { + // First, deal with subdirectories + try forEachFile(in: dirfd, path: path) { dirent in + if dirent.d_type == SYSTEM_DT_DIR { + try withUnsafeBytes(of: dirent.d_name) { + try recursiveRemove( + in: dirfd, + path: $0.assumingMemoryBound(to: CInterop.PlatformChar.self) + .baseAddress! + ) + } + } + } + + // Now delete the contents of this directory + try forEachFile(in: dirfd, path: path) { dirent in + let flag: CInt + + if dirent.d_type == SYSTEM_DT_DIR { + flag = SYSTEM_AT_REMOVE_DIR + } else { + flag = 0 + } + + let result = withUnsafeBytes(of: dirent.d_name) { + system_unlinkat(dirfd, + $0.assumingMemoryBound(to: CInterop.PlatformChar.self) + .baseAddress!, + flag) + } + + if result != 0 { + throw Errno.current + } + } +} +#endif + +internal let base64 = Array( + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_".utf8 +) + +internal func makeDirectory(at: FilePath) throws -> Bool { + return try at.withPlatformString { + if system_mkdir($0, 0o700) == 0 { + return true + } + let err = system_errno + if err == Errno.fileExists.rawValue { + return false + } else { + throw Errno(rawValue: err) + } + } +} + +internal func createRandomString(length: Int) -> String { + return String( + decoding: (0.. FilePath { + var tempDir = try getTemporaryDirectory() + tempDir.append(basename) + + while true { + tempDir.extension = createRandomString(length: 16) + + if try makeDirectory(at: tempDir) { + return tempDir + } + } +} diff --git a/Sources/System/Internals/Mocking.swift b/Sources/System/Internals/Mocking.swift index 405dc342..08c0f713 100644 --- a/Sources/System/Internals/Mocking.swift +++ b/Sources/System/Internals/Mocking.swift @@ -63,7 +63,7 @@ internal class MockingDriver { // Whether we should pretend to be Windows for syntactic operations // inside FilePath - fileprivate var forceWindowsSyntaxForPaths = false + fileprivate var forceWindowsSyntaxForPaths: Bool? = nil } private let driverKey: _PlatformTLSKey = { makeTLSKey() }() @@ -110,8 +110,8 @@ private var contextualMockingEnabled: Bool { extension MockingDriver { internal static var enabled: Bool { mockingEnabled } - internal static var forceWindowsPaths: Bool { - currentMockingDriver?.forceWindowsSyntaxForPaths ?? false + internal static var forceWindowsPaths: Optional { + currentMockingDriver?.forceWindowsSyntaxForPaths } } @@ -128,7 +128,7 @@ internal var mockingEnabled: Bool { } @inline(__always) -internal var forceWindowsPaths: Bool { +internal var forceWindowsPaths: Optional { #if !ENABLE_MOCKING return false #else @@ -196,15 +196,11 @@ internal func _mockOffT( #endif // ENABLE_MOCKING // Force paths to be treated as Windows syntactically if `enabled` is -// true. +// true, and as POSIX syntactically if not. internal func _withWindowsPaths(enabled: Bool, _ body: () -> ()) { #if ENABLE_MOCKING - guard enabled else { - body() - return - } MockingDriver.withMockingEnabled { driver in - driver.forceWindowsSyntaxForPaths = true + driver.forceWindowsSyntaxForPaths = enabled body() } #else diff --git a/Sources/System/Internals/Syscalls.swift b/Sources/System/Internals/Syscalls.swift index 69931b3a..4669141d 100644 --- a/Sources/System/Internals/Syscalls.swift +++ b/Sources/System/Internals/Syscalls.swift @@ -138,7 +138,7 @@ internal func system_dup2(_ fd: Int32, _ fd2: Int32) -> Int32 { } #endif -#if !os(Windows) && !os(WASI) +#if !os(WASI) internal func system_pipe(_ fds: UnsafeMutablePointer) -> CInt { #if ENABLE_MOCKING if mockingEnabled { return _mock(fds) } @@ -147,11 +147,83 @@ internal func system_pipe(_ fds: UnsafeMutablePointer) -> CInt { } #endif -#if !os(Windows) internal func system_ftruncate(_ fd: Int32, _ length: off_t) -> Int32 { #if ENABLE_MOCKING if mockingEnabled { return _mock(fd, length) } #endif return ftruncate(fd, length) } + +internal func system_mkdir( + _ path: UnsafePointer, + _ mode: CInterop.Mode +) -> CInt { +#if ENABLE_MOCKING + if mockingEnabled { return _mock(path: path, mode) } +#endif + return mkdir(path, mode) +} + +internal func system_rmdir( + _ path: UnsafePointer +) -> CInt { +#if ENABLE_MOCKING + if mockingEnabled { return _mock(path: path) } +#endif + return rmdir(path) +} + +#if !os(Windows) +internal let SYSTEM_AT_REMOVE_DIR = AT_REMOVEDIR +internal let SYSTEM_DT_DIR = DT_DIR +internal typealias system_dirent = dirent +internal typealias system_DIR = DIR + +internal func system_unlinkat( + _ fd: CInt, + _ path: UnsafePointer, + _ flag: CInt +) -> CInt { +#if ENABLE_MOCKING + if mockingEnabled { return _mock(fd, path, flag) } +#endif +return unlinkat(fd, path, flag) +} + +internal func system_fdopendir( + _ fd: CInt +) -> UnsafeMutablePointer? { + return fdopendir(fd) +} + +internal func system_readdir( + _ dir: UnsafeMutablePointer +) -> UnsafeMutablePointer? { + return readdir(dir) +} + +internal func system_rewinddir( + _ dir: UnsafeMutablePointer +) { + return rewinddir(dir) +} + +internal func system_closedir( + _ dir: UnsafeMutablePointer +) -> CInt { + return closedir(dir) +} + +internal func system_openat( + _ fd: CInt, + _ path: UnsafePointer, + _ oflag: Int32 +) -> CInt { +#if ENABLE_MOCKING + if mockingEnabled { + return _mock(fd, path, oflag) + } +#endif + return openat(fd, path, oflag) +} #endif diff --git a/Sources/System/Internals/WindowsSyscallAdapters.swift b/Sources/System/Internals/WindowsSyscallAdapters.swift index 2f3851b6..bc83782d 100644 --- a/Sources/System/Internals/WindowsSyscallAdapters.swift +++ b/Sources/System/Internals/WindowsSyscallAdapters.swift @@ -80,7 +80,7 @@ internal func pread( _ fd: Int32, _ buf: UnsafeMutableRawPointer!, _ nbyte: Int, _ offset: off_t ) -> Int { let handle: intptr_t = _get_osfhandle(fd) - if handle == /* INVALID_HANDLE_VALUE */ -1 { return Int(EBADF) } + if handle == /* INVALID_HANDLE_VALUE */ -1 { ucrt._set_errno(EBADF); return -1 } // NOTE: this is a non-owning handle, do *not* call CloseHandle on it let hFile: HANDLE = HANDLE(bitPattern: handle)! @@ -91,8 +91,7 @@ internal func pread( var nNumberOfBytesRead: DWORD = 0 if !ReadFile(hFile, buf, DWORD(nbyte), &nNumberOfBytesRead, &ovlOverlapped) { - let _ = GetLastError() - // TODO(compnerd) map windows error to errno + ucrt._set_errno(mapWindowsErrorToErrno(GetLastError())) return Int(-1) } return Int(nNumberOfBytesRead) @@ -103,7 +102,7 @@ internal func pwrite( _ fd: Int32, _ buf: UnsafeRawPointer!, _ nbyte: Int, _ offset: off_t ) -> Int { let handle: intptr_t = _get_osfhandle(fd) - if handle == /* INVALID_HANDLE_VALUE */ -1 { return Int(EBADF) } + if handle == /* INVALID_HANDLE_VALUE */ -1 { ucrt._set_errno(EBADF); return -1 } // NOTE: this is a non-owning handle, do *not* call CloseHandle on it let hFile: HANDLE = HANDLE(bitPattern: handle)! @@ -115,11 +114,133 @@ internal func pwrite( var nNumberOfBytesWritten: DWORD = 0 if !WriteFile(hFile, buf, DWORD(nbyte), &nNumberOfBytesWritten, &ovlOverlapped) { - let _ = GetLastError() - // TODO(compnerd) map windows error to errno + ucrt._set_errno(mapWindowsErrorToErrno(GetLastError())) return Int(-1) } return Int(nNumberOfBytesWritten) } +@inline(__always) +internal func pipe(_ fds: UnsafeMutablePointer) -> CInt { + return _pipe(fds, 4096, _O_BINARY | _O_NOINHERIT); +} + +@inline(__always) +internal func ftruncate(_ fd: Int32, _ length: off_t) -> Int32 { + let handle: intptr_t = _get_osfhandle(fd) + if handle == /* INVALID_HANDLE_VALUE */ -1 { ucrt._set_errno(EBADF); return -1 } + + // NOTE: this is a non-owning handle, do *not* call CloseHandle on it + let hFile: HANDLE = HANDLE(bitPattern: handle)! + let liDesiredLength = LARGE_INTEGER(QuadPart: LONGLONG(length)) + var liCurrentOffset = LARGE_INTEGER(QuadPart: 0) + + // Save the current position and restore it when we're done + if !SetFilePointerEx(hFile, liCurrentOffset, &liCurrentOffset, + DWORD(FILE_CURRENT)) { + ucrt._set_errno(mapWindowsErrorToErrno(GetLastError())) + return -1 + } + defer { + _ = SetFilePointerEx(hFile, liCurrentOffset, nil, DWORD(FILE_BEGIN)); + } + + // Truncate (or extend) the file + if !SetFilePointerEx(hFile, liDesiredLength, nil, DWORD(FILE_BEGIN)) + || !SetEndOfFile(hFile) { + ucrt._set_errno(mapWindowsErrorToErrno(GetLastError())) + return -1 + } + + return 0; +} + +@inline(__always) +internal func mkdir( + _ path: UnsafePointer, + _ mode: CInterop.Mode +) -> CInt { + return _wmkdir(path) +} + +@inline(__always) +internal func rmdir( + _ path: UnsafePointer +) -> CInt { + return _wrmdir(path) +} + +@usableFromInline +internal func mapWindowsErrorToErrno(_ errorCode: DWORD) -> CInt { + switch Int32(errorCode) { + case ERROR_SUCCESS: + return 0 + case ERROR_INVALID_FUNCTION, + ERROR_INVALID_ACCESS, + ERROR_INVALID_DATA, + ERROR_INVALID_PARAMETER, + ERROR_NEGATIVE_SEEK: + return EINVAL + case ERROR_FILE_NOT_FOUND, + ERROR_PATH_NOT_FOUND, + ERROR_INVALID_DRIVE, + ERROR_NO_MORE_FILES, + ERROR_BAD_NETPATH, + ERROR_BAD_NET_NAME, + ERROR_BAD_PATHNAME, + ERROR_FILENAME_EXCED_RANGE: + return ENOENT + case ERROR_TOO_MANY_OPEN_FILES: + return EMFILE + case ERROR_ACCESS_DENIED, + ERROR_CURRENT_DIRECTORY, + ERROR_LOCK_VIOLATION, + ERROR_NETWORK_ACCESS_DENIED, + ERROR_CANNOT_MAKE, + ERROR_FAIL_I24, + ERROR_DRIVE_LOCKED, + ERROR_SEEK_ON_DEVICE, + ERROR_NOT_LOCKED, + ERROR_LOCK_FAILED, + ERROR_WRITE_PROTECT...ERROR_SHARING_BUFFER_EXCEEDED: + return EACCES + case ERROR_INVALID_HANDLE, + ERROR_INVALID_TARGET_HANDLE, + ERROR_DIRECT_ACCESS_HANDLE: + return EBADF + case ERROR_ARENA_TRASHED, + ERROR_NOT_ENOUGH_MEMORY, + ERROR_INVALID_BLOCK, + ERROR_NOT_ENOUGH_QUOTA: + return ENOMEM + case ERROR_BAD_ENVIRONMENT: + return E2BIG + case ERROR_BAD_FORMAT, + ERROR_INVALID_STARTING_CODESEG...ERROR_INFLOOP_IN_RELOC_CHAIN: + return ENOEXEC + case ERROR_NOT_SAME_DEVICE: + return EXDEV + case ERROR_FILE_EXISTS, + ERROR_ALREADY_EXISTS: + return EEXIST + case ERROR_NO_PROC_SLOTS, + ERROR_MAX_THRDS_REACHED, + ERROR_NESTING_NOT_ALLOWED: + return EAGAIN + case ERROR_BROKEN_PIPE: + return EPIPE + case ERROR_DISK_FULL: + return ENOSPC + case ERROR_WAIT_NO_CHILDREN, + ERROR_CHILD_NOT_COMPLETE: + return ECHILD + case ERROR_DIR_NOT_EMPTY: + return ENOTEMPTY + case ERROR_NO_UNICODE_TRANSLATION: + return EILSEQ + default: + return EINVAL + } +} + #endif diff --git a/Tests/SystemTests/FileOperationsTest.swift b/Tests/SystemTests/FileOperationsTest.swift index ca178ba5..ee821280 100644 --- a/Tests/SystemTests/FileOperationsTest.swift +++ b/Tests/SystemTests/FileOperationsTest.swift @@ -113,8 +113,7 @@ final class FileOperationsTest: XCTestCase { func testHelpers() { // TODO: Test writeAll, writeAll(toAbsoluteOffset), closeAfter } - -#if !os(Windows) + 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`. @@ -133,33 +132,34 @@ final class FileOperationsTest: XCTestCase { } } } -#endif func testAdHocOpen() { // Ad-hoc test touching a file system. do { // TODO: Test this against a virtual in-memory file system - let fd = try FileDescriptor.open("/tmp/b.txt", .readWrite, options: [.create, .truncate], permissions: .ownerReadWrite) - try fd.closeAfter { - try fd.writeAll("abc".utf8) - var def = "def" - try def.withUTF8 { - _ = try fd.write(UnsafeRawBufferPointer($0)) - } - try fd.seek(offset: 1, from: .start) - - let readLen = 3 - let readBytes = try Array(unsafeUninitializedCapacity: readLen) { (buf, count) in - count = try fd.read(into: UnsafeMutableRawBufferPointer(buf)) + try withTemporaryPath(basename: "testAdhocOpen") { path in + let fd = try FileDescriptor.open(path.appending("b.txt"), .readWrite, options: [.create, .truncate], permissions: .ownerReadWrite) + try fd.closeAfter { + try fd.writeAll("abc".utf8) + var def = "def" + try def.withUTF8 { + _ = try fd.write(UnsafeRawBufferPointer($0)) + } + try fd.seek(offset: 1, from: .start) + + let readLen = 3 + let readBytes = try Array(unsafeUninitializedCapacity: readLen) { (buf, count) in + count = try fd.read(into: UnsafeMutableRawBufferPointer(buf)) + } + let preadBytes = try Array(unsafeUninitializedCapacity: readLen) { (buf, count) in + count = try fd.read(fromAbsoluteOffset: 1, into: UnsafeMutableRawBufferPointer(buf)) + } + + XCTAssertEqual(readBytes.first!, "b".utf8.first!) + XCTAssertEqual(readBytes, preadBytes) + + // TODO: seek } - let preadBytes = try Array(unsafeUninitializedCapacity: readLen) { (buf, count) in - count = try fd.read(fromAbsoluteOffset: 1, into: UnsafeMutableRawBufferPointer(buf)) - } - - XCTAssertEqual(readBytes.first!, "b".utf8.first!) - XCTAssertEqual(readBytes, preadBytes) - - // TODO: seek } } catch let err as Errno { print("caught \(err))") @@ -185,48 +185,48 @@ final class FileOperationsTest: XCTestCase { } -#if !os(Windows) func testResizeFile() throws { - let fd = try FileDescriptor.open("/tmp/\(UUID().uuidString).txt", .readWrite, options: [.create, .truncate], permissions: .ownerReadWrite) - try fd.closeAfter { - // File should be empty initially. - XCTAssertEqual(try fd.fileSize(), 0) - // Write 3 bytes. - try fd.writeAll("abc".utf8) - // File should now be 3 bytes. - XCTAssertEqual(try fd.fileSize(), 3) - // Resize to 6 bytes. - try fd.resize(to: 6) - // File should now be 6 bytes. - XCTAssertEqual(try fd.fileSize(), 6) - // Read in the 6 bytes. - let readBytes = try Array(unsafeUninitializedCapacity: 6) { (buf, count) in - try fd.seek(offset: 0, from: .start) - // Should have read all 6 bytes. - count = try fd.read(into: UnsafeMutableRawBufferPointer(buf)) - XCTAssertEqual(count, 6) - } - // First 3 bytes should be unaffected by resize. - XCTAssertEqual(Array(readBytes[..<3]), Array("abc".utf8)) - // Extension should be padded with zeros. - XCTAssertEqual(Array(readBytes[3...]), Array(repeating: 0, count: 3)) - // File should still be 6 bytes. - XCTAssertEqual(try fd.fileSize(), 6) - // Resize to 2 bytes. - try fd.resize(to: 2) - // File should now be 2 bytes. - XCTAssertEqual(try fd.fileSize(), 2) - // Read in file with a buffer big enough for 6 bytes. - let readBytesAfterTruncation = try Array(unsafeUninitializedCapacity: 6) { (buf, count) in - try fd.seek(offset: 0, from: .start) - count = try fd.read(into: UnsafeMutableRawBufferPointer(buf)) - // Should only have read 2 bytes. - XCTAssertEqual(count, 2) + try withTemporaryPath(basename: "testResizeFile") { path in + let fd = try FileDescriptor.open(path.appending("\(UUID().uuidString).txt"), .readWrite, options: [.create, .truncate], permissions: .ownerReadWrite) + try fd.closeAfter { + // File should be empty initially. + XCTAssertEqual(try fd.fileSize(), 0) + // Write 3 bytes. + try fd.writeAll("abc".utf8) + // File should now be 3 bytes. + XCTAssertEqual(try fd.fileSize(), 3) + // Resize to 6 bytes. + try fd.resize(to: 6) + // File should now be 6 bytes. + XCTAssertEqual(try fd.fileSize(), 6) + // Read in the 6 bytes. + let readBytes = try Array(unsafeUninitializedCapacity: 6) { (buf, count) in + try fd.seek(offset: 0, from: .start) + // Should have read all 6 bytes. + count = try fd.read(into: UnsafeMutableRawBufferPointer(buf)) + XCTAssertEqual(count, 6) + } + // First 3 bytes should be unaffected by resize. + XCTAssertEqual(Array(readBytes[..<3]), Array("abc".utf8)) + // Extension should be padded with zeros. + XCTAssertEqual(Array(readBytes[3...]), Array(repeating: 0, count: 3)) + // File should still be 6 bytes. + XCTAssertEqual(try fd.fileSize(), 6) + // Resize to 2 bytes. + try fd.resize(to: 2) + // File should now be 2 bytes. + XCTAssertEqual(try fd.fileSize(), 2) + // Read in file with a buffer big enough for 6 bytes. + let readBytesAfterTruncation = try Array(unsafeUninitializedCapacity: 6) { (buf, count) in + try fd.seek(offset: 0, from: .start) + count = try fd.read(into: UnsafeMutableRawBufferPointer(buf)) + // Should only have read 2 bytes. + XCTAssertEqual(count, 2) + } + // Written content was trunctated. + XCTAssertEqual(readBytesAfterTruncation, Array("ab".utf8)) } - // Written content was trunctated. - XCTAssertEqual(readBytesAfterTruncation, Array("ab".utf8)) } } -#endif } diff --git a/Tests/SystemTests/FilePathTests/FilePathComponentsTest.swift b/Tests/SystemTests/FilePathTests/FilePathComponentsTest.swift index ad69cb4c..e816bb5a 100644 --- a/Tests/SystemTests/FilePathTests/FilePathComponentsTest.swift +++ b/Tests/SystemTests/FilePathTests/FilePathComponentsTest.swift @@ -231,7 +231,7 @@ final class FilePathComponentsTest: XCTestCase { } func testCases() { - let testPaths: Array = [ + var testPaths: Array = [ TestPathComponents("", root: nil, []), TestPathComponents("/", root: "/", []), TestPathComponents("foo", root: nil, ["foo"]), @@ -240,16 +240,28 @@ final class FilePathComponentsTest: XCTestCase { TestPathComponents("foo/bar", root: nil, ["foo", "bar"]), TestPathComponents("foo/bar/", root: nil, ["foo", "bar"]), TestPathComponents("/foo/bar", root: "/", ["foo", "bar"]), - TestPathComponents("///foo//", root: "/", ["foo"]), TestPathComponents("/foo///bar", root: "/", ["foo", "bar"]), TestPathComponents("foo/bar/", root: nil, ["foo", "bar"]), TestPathComponents("foo///bar/baz/", root: nil, ["foo", "bar", "baz"]), - TestPathComponents("//foo///bar/baz/", root: "/", ["foo", "bar", "baz"]), TestPathComponents("./", root: nil, ["."]), TestPathComponents("./..", root: nil, [".", ".."]), TestPathComponents("/./..//", root: "/", [".", ".."]), ] - testPaths.forEach { $0.runAllTests() } +#if !os(Windows) + testPaths.append(contentsOf:[ + TestPathComponents("///foo//", root: "/", ["foo"]), + TestPathComponents("//foo///bar/baz/", root: "/", ["foo", "bar", "baz"]) + ]) +#else + // On Windows, these are UNC paths + testPaths.append(contentsOf:[ + TestPathComponents("///foo//", root: "///foo//", []), + TestPathComponents("//foo///bar/baz/", root: "//foo//", ["bar", "baz"]) + ]) +#endif + testPaths.forEach { + $0.runAllTests() + } } func testSeparatorNormalization() { diff --git a/Tests/SystemTests/FilePathTests/FilePathSyntaxTest.swift b/Tests/SystemTests/FilePathTests/FilePathSyntaxTest.swift index ccaa7827..d23999a2 100644 --- a/Tests/SystemTests/FilePathTests/FilePathSyntaxTest.swift +++ b/Tests/SystemTests/FilePathTests/FilePathSyntaxTest.swift @@ -1064,7 +1064,10 @@ final class FilePathSyntaxTest: XCTestCase { XCTAssert(path.lexicallyContains("usr")) XCTAssert(path.lexicallyContains("/usr")) XCTAssert(path.lexicallyContains("local/bin")) +#if !os(Windows) + // On Windows, this is a relative path and is still contained XCTAssert(!path.lexicallyContains("/local/bin")) +#endif path.append("..") XCTAssert(!path.lexicallyContains("local/bin")) XCTAssert(path.lexicallyContains("local/bin/..")) diff --git a/Tests/SystemTests/FilePathTests/FilePathTempTest.swift b/Tests/SystemTests/FilePathTests/FilePathTempTest.swift new file mode 100644 index 00000000..0948f78a --- /dev/null +++ b/Tests/SystemTests/FilePathTests/FilePathTempTest.swift @@ -0,0 +1,49 @@ +/* + This source file is part of the Swift System open source project + + Copyright (c) 2024 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 +*/ + +import XCTest + +#if SYSTEM_PACKAGE +import SystemPackage +#else +import System +#endif + +final class TemporaryPathTest: XCTestCase { + func testUnique() throws { + try withTemporaryPath(basename: "test") { path in + let strPath = String(decoding: path) + XCTAssert(strPath.contains("test")) + try withTemporaryPath(basename: "test") { path2 in + let strPath2 = String(decoding: path2) + XCTAssertNotEqual(strPath, strPath2) + } + } + } + + func testCleanup() throws { + var thePath: FilePath? = nil + + try withTemporaryPath(basename: "test") { path in + thePath = path.appending("foo.txt") + let fd = try FileDescriptor.open(thePath!, .readWrite, + options: [.create, .truncate], + permissions: .ownerReadWrite) + _ = try fd.closeAfter { + try fd.writeAll("Hello World".utf8) + } + } + + XCTAssertThrowsError(try FileDescriptor.open(thePath!, .readOnly)) { + error in + + XCTAssertEqual(error as! Errno, Errno.noSuchFileOrDirectory) + } + } +} diff --git a/Tests/SystemTests/FilePathTests/FilePathTest.swift b/Tests/SystemTests/FilePathTests/FilePathTest.swift index 1686faa1..b50cc17e 100644 --- a/Tests/SystemTests/FilePathTests/FilePathTest.swift +++ b/Tests/SystemTests/FilePathTests/FilePathTest.swift @@ -34,6 +34,16 @@ final class FilePathTest: XCTestCase { let filePath: FilePath let string: String let validString: Bool + + init(filePath: FilePath, string: String, validString: Bool) { + self.filePath = filePath + #if os(Windows) + self.string = string.replacingOccurrences(of: "/", with: "\\") + #else + self.string = string + #endif + self.validString = validString + } } #if os(Windows) From c6456a9c03b191677f09828ec1ee492b6fbed026 Mon Sep 17 00:00:00 2001 From: Alastair Houghton Date: Wed, 20 Mar 2024 17:10:30 +0000 Subject: [PATCH 188/427] Use the proper temporary directory on Darwin. It seems we can get the temporary directory from confstr(). rdar://125087707 --- Sources/System/FilePath/FilePathTemp.swift | 26 +++++++++++++++++++ Sources/System/Internals/Syscalls.swift | 12 +++++++++ .../FilePathTests/FilePathTempTest.swift | 9 +++++++ 3 files changed, 47 insertions(+) diff --git a/Sources/System/FilePath/FilePathTemp.swift b/Sources/System/FilePath/FilePathTemp.swift index 9aa65ff9..00286714 100644 --- a/Sources/System/FilePath/FilePathTemp.swift +++ b/Sources/System/FilePath/FilePathTemp.swift @@ -111,7 +111,33 @@ internal func recursiveRemove(at path: FilePath) throws { #else internal func getTemporaryDirectory() throws -> FilePath { + #if SYSTEM_PACKAGE_DARWIN + var capacity = 1024 + while true { + let path: FilePath? = withUnsafeTemporaryAllocation( + of: CInterop.PlatformChar.self, + capacity: 1024) { buffer in + let len = system_confstr(SYSTEM_CS_DARWIN_USER_TEMP_DIR, + buffer.baseAddress!, + buffer.count) + if len == 0 { + // Fall back to "/tmp" if we can't read the temp directory + return "/tmp" + } + // If it was truncated, increase capaciy and try again + if len > buffer.count { + capacity = len + return nil + } + return FilePath(SystemString(platformString: buffer.baseAddress!)) + } + if let path = path { + return path + } + } + #else return "/tmp" + #endif } internal func recursiveRemove(at path: FilePath) throws { diff --git a/Sources/System/Internals/Syscalls.swift b/Sources/System/Internals/Syscalls.swift index 4669141d..cec84137 100644 --- a/Sources/System/Internals/Syscalls.swift +++ b/Sources/System/Internals/Syscalls.swift @@ -173,6 +173,18 @@ internal func system_rmdir( return rmdir(path) } +#if SYSTEM_PACKAGE_DARWIN +internal let SYSTEM_CS_DARWIN_USER_TEMP_DIR = _CS_DARWIN_USER_TEMP_DIR + +internal func system_confstr( + _ name: CInt, + _ buf: UnsafeMutablePointer, + _ len: Int +) -> Int { + return confstr(name, buf, len) +} +#endif + #if !os(Windows) internal let SYSTEM_AT_REMOVE_DIR = AT_REMOVEDIR internal let SYSTEM_DT_DIR = DT_DIR diff --git a/Tests/SystemTests/FilePathTests/FilePathTempTest.swift b/Tests/SystemTests/FilePathTests/FilePathTempTest.swift index 0948f78a..b2b1fa22 100644 --- a/Tests/SystemTests/FilePathTests/FilePathTempTest.swift +++ b/Tests/SystemTests/FilePathTests/FilePathTempTest.swift @@ -16,6 +16,15 @@ import System #endif final class TemporaryPathTest: XCTestCase { + #if SYSTEM_PACKAGE_DARWIN + func testNotInSlashTmp() throws { + try withTemporaryPath(basename: "NotInSlashTmp") { path in + // We shouldn't be using "/tmp" on Darwin + XCTAssertNotEqual(path.components.first!, "tmp") + } + } + #endif + func testUnique() throws { try withTemporaryPath(basename: "test") { path in let strPath = String(decoding: path) From ec7d406cd4c2ac56ec3cb7c859dd56f87e7808f5 Mon Sep 17 00:00:00 2001 From: Alastair Houghton Date: Wed, 20 Mar 2024 17:27:38 +0000 Subject: [PATCH 189/427] Changes after comments from Saleem. Use idiomatic Swift notation, not `Optional`. Prefer Win32 APIs to UCRT APIs. rdar://125087707 --- Sources/System/FilePath/FilePathTemp.swift | 5 ++--- Sources/System/Internals/Mocking.swift | 4 ++-- .../System/Internals/WindowsSyscallAdapters.swift | 15 +++++++++++++-- 3 files changed, 17 insertions(+), 7 deletions(-) diff --git a/Sources/System/FilePath/FilePathTemp.swift b/Sources/System/FilePath/FilePathTemp.swift index 00286714..437d2330 100644 --- a/Sources/System/FilePath/FilePathTemp.swift +++ b/Sources/System/FilePath/FilePathTemp.swift @@ -29,8 +29,7 @@ internal func getTemporaryDirectory() throws -> FilePath { capacity: Int(MAX_PATH) + 1) { buffer in - let count = GetTempPath2W(DWORD(buffer.count), buffer.baseAddress) - if count == 0 { + guard GetTempPath2W(DWORD(buffer.count), buffer.baseAddress) != 0 else { throw Errno(windowsError: GetLastError()) } @@ -116,7 +115,7 @@ internal func getTemporaryDirectory() throws -> FilePath { while true { let path: FilePath? = withUnsafeTemporaryAllocation( of: CInterop.PlatformChar.self, - capacity: 1024) { buffer in + capacity: capacity) { buffer in let len = system_confstr(SYSTEM_CS_DARWIN_USER_TEMP_DIR, buffer.baseAddress!, buffer.count) diff --git a/Sources/System/Internals/Mocking.swift b/Sources/System/Internals/Mocking.swift index 08c0f713..2945651c 100644 --- a/Sources/System/Internals/Mocking.swift +++ b/Sources/System/Internals/Mocking.swift @@ -110,7 +110,7 @@ private var contextualMockingEnabled: Bool { extension MockingDriver { internal static var enabled: Bool { mockingEnabled } - internal static var forceWindowsPaths: Optional { + internal static var forceWindowsPaths: Bool? { currentMockingDriver?.forceWindowsSyntaxForPaths } } @@ -128,7 +128,7 @@ internal var mockingEnabled: Bool { } @inline(__always) -internal var forceWindowsPaths: Optional { +internal var forceWindowsPaths: Bool? { #if !ENABLE_MOCKING return false #else diff --git a/Sources/System/Internals/WindowsSyscallAdapters.swift b/Sources/System/Internals/WindowsSyscallAdapters.swift index bc83782d..6d5c755c 100644 --- a/Sources/System/Internals/WindowsSyscallAdapters.swift +++ b/Sources/System/Internals/WindowsSyscallAdapters.swift @@ -160,14 +160,25 @@ internal func mkdir( _ path: UnsafePointer, _ mode: CInterop.Mode ) -> CInt { - return _wmkdir(path) + // TODO: Read/write permissions (these need mapping to a SECURITY_DESCRIPTOR). + if !CreateDirectoryW(path, nil) { + ucrt._set_errno(mapWindowsErrorToErrno(GetLastError())) + return -1 + } + + return 0; } @inline(__always) internal func rmdir( _ path: UnsafePointer ) -> CInt { - return _wrmdir(path) + if !RemoveDirectoryW(path) { + ucrt._set_errno(mapWindowsErrorToErrno(GetLastError())) + return -1 + } + + return 0; } @usableFromInline From 21638a06b17f5872fe4f2035a5f52e175c05aaf3 Mon Sep 17 00:00:00 2001 From: Alastair Houghton Date: Thu, 21 Mar 2024 16:15:19 +0000 Subject: [PATCH 190/427] Map mode bits to Windows ACLs. This adds support for mapping POSIX-style mode bits to Windows ACLs when calling `open()` or `mkdir()`. rdar://125087707 --- Sources/System/FilePath/FilePathTemp.swift | 23 +- .../Internals/WindowsSyscallAdapters.swift | 392 +++++++++++++++++- 2 files changed, 395 insertions(+), 20 deletions(-) diff --git a/Sources/System/FilePath/FilePathTemp.swift b/Sources/System/FilePath/FilePathTemp.swift index 437d2330..23a08168 100644 --- a/Sources/System/FilePath/FilePathTemp.swift +++ b/Sources/System/FilePath/FilePathTemp.swift @@ -24,7 +24,7 @@ public func withTemporaryPath(basename: FilePath.Component, #if os(Windows) import WinSDK -internal func getTemporaryDirectory() throws -> FilePath { +fileprivate func getTemporaryDirectory() throws -> FilePath { return try withUnsafeTemporaryAllocation(of: CInterop.PlatformChar.self, capacity: Int(MAX_PATH) + 1) { buffer in @@ -37,7 +37,7 @@ internal func getTemporaryDirectory() throws -> FilePath { } } -internal func forEachFile( +fileprivate func forEachFile( at path: FilePath, _ body: (WIN32_FIND_DATAW) throws -> () ) rethrows { @@ -67,7 +67,7 @@ internal func forEachFile( } } -internal func recursiveRemove(at path: FilePath) throws { +fileprivate func recursiveRemove(at path: FilePath) throws { // First, deal with subdirectories try forEachFile(at: path) { findData in if (findData.dwFileAttributes & DWORD(FILE_ATTRIBUTE_DIRECTORY)) != 0 { @@ -109,13 +109,14 @@ internal func recursiveRemove(at path: FilePath) throws { } #else -internal func getTemporaryDirectory() throws -> FilePath { +fileprivate func getTemporaryDirectory() throws -> FilePath { #if SYSTEM_PACKAGE_DARWIN var capacity = 1024 while true { let path: FilePath? = withUnsafeTemporaryAllocation( of: CInterop.PlatformChar.self, - capacity: capacity) { buffer in + capacity: capacity + ) { buffer in let len = system_confstr(SYSTEM_CS_DARWIN_USER_TEMP_DIR, buffer.baseAddress!, buffer.count) @@ -139,7 +140,7 @@ internal func getTemporaryDirectory() throws -> FilePath { #endif } -internal func recursiveRemove(at path: FilePath) throws { +fileprivate func recursiveRemove(at path: FilePath) throws { let dirfd = try FileDescriptor.open(path, .readOnly, options: .directory) defer { try? dirfd.close() @@ -173,7 +174,7 @@ fileprivate func impl_opendirat( return system_fdopendir(fd) } -internal func forEachFile( +fileprivate func forEachFile( in dirfd: CInt, path: UnsafePointer, _ body: (system_dirent) throws -> () ) throws { @@ -238,11 +239,11 @@ internal func recursiveRemove( } #endif -internal let base64 = Array( +fileprivate let base64 = Array( "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_".utf8 ) -internal func makeDirectory(at: FilePath) throws -> Bool { +fileprivate func makeTempDirectory(at: FilePath) throws -> Bool { return try at.withPlatformString { if system_mkdir($0, 0o700) == 0 { return true @@ -256,7 +257,7 @@ internal func makeDirectory(at: FilePath) throws -> Bool { } } -internal func createRandomString(length: Int) -> String { +fileprivate func createRandomString(length: Int) -> String { return String( decoding: (0.., _ oflag: Int32 ) -> CInt { - var fh: CInt = -1 - _ = _wsopen_s(&fh, path, oflag, _SH_DENYNO, _S_IREAD | _S_IWRITE) - return fh + if (oflag & _O_CREAT) != 0 { + ucrt._set_errno(EINVAL) + return -1 + } + + let decodedFlags = DecodedOpenFlags(oflag) + + var saAttrs = SECURITY_ATTRIBUTES( + nLength: DWORD(MemoryLayout.size), + lpSecurityDescriptor: nil, + bInheritHandle: decodedFlags.bInheritHandle + ) + + let hFile = CreateFileW(path, + decodedFlags.dwDesiredAccess, + DWORD(FILE_SHARE_DELETE + | FILE_SHARE_READ + | FILE_SHARE_WRITE), + &saAttrs, + decodedFlags.dwCreationDisposition, + decodedFlags.dwFlagsAndAttributes, + nil) + + if hFile == INVALID_HANDLE_VALUE { + ucrt._set_errno(mapWindowsErrorToErrno(GetLastError())) + return -1 + } + + return _open_osfhandle(intptr_t(bitPattern: hFile), oflag); } @inline(__always) @@ -26,10 +52,39 @@ internal func open( _ path: UnsafePointer, _ oflag: Int32, _ mode: CInterop.Mode ) -> CInt { - // TODO(compnerd): Apply read/write permissions - var fh: CInt = -1 - _ = _wsopen_s(&fh, path, oflag, _SH_DENYNO, _S_IREAD | _S_IWRITE) - return fh + guard let pSD = createSecurityDescriptor(from: mode) else { + ucrt._set_errno(mapWindowsErrorToErrno(GetLastError())) + return -1 + } + + defer { + pSD.deallocate() + } + + let decodedFlags = DecodedOpenFlags(oflag) + + var saAttrs = SECURITY_ATTRIBUTES( + nLength: DWORD(MemoryLayout.size), + lpSecurityDescriptor: pSD, + bInheritHandle: decodedFlags.bInheritHandle + ) + + let hFile = CreateFileW(path, + decodedFlags.dwDesiredAccess, + DWORD(FILE_SHARE_DELETE + | FILE_SHARE_READ + | FILE_SHARE_WRITE), + &saAttrs, + decodedFlags.dwCreationDisposition, + decodedFlags.dwFlagsAndAttributes, + nil) + + if hFile == INVALID_HANDLE_VALUE { + ucrt._set_errno(mapWindowsErrorToErrno(GetLastError())) + return -1 + } + + return _open_osfhandle(intptr_t(bitPattern: hFile), oflag); } @inline(__always) @@ -160,8 +215,21 @@ internal func mkdir( _ path: UnsafePointer, _ mode: CInterop.Mode ) -> CInt { - // TODO: Read/write permissions (these need mapping to a SECURITY_DESCRIPTOR). - if !CreateDirectoryW(path, nil) { + guard let pSD = createSecurityDescriptor(from: mode) else { + ucrt._set_errno(mapWindowsErrorToErrno(GetLastError())) + return -1 + } + defer { + pSD.deallocate() + } + + var saAttrs = SECURITY_ATTRIBUTES( + nLength: DWORD(MemoryLayout.size), + lpSecurityDescriptor: pSD, + bInheritHandle: false + ) + + if !CreateDirectoryW(path, &saAttrs) { ucrt._set_errno(mapWindowsErrorToErrno(GetLastError())) return -1 } @@ -254,4 +322,310 @@ internal func mapWindowsErrorToErrno(_ errorCode: DWORD) -> CInt { } } +fileprivate func rightsFromModeBits( + _ bits: Int, sticky: Bool = false +) -> DWORD { + var rights: DWORD = 0 + + if (bits & 0o4) != 0 { + rights |= DWORD(FILE_READ_ATTRIBUTES + | FILE_READ_DATA + | FILE_READ_EA + | STANDARD_RIGHTS_READ + | SYNCHRONIZE) + } + if (bits & 0o2) != 0 { + rights |= DWORD(FILE_APPEND_DATA + | FILE_WRITE_ATTRIBUTES + | FILE_WRITE_DATA + | FILE_WRITE_EA + | STANDARD_RIGHTS_WRITE + | SYNCHRONIZE) + if !sticky { + rights |= DWORD(FILE_DELETE_CHILD) + } + } + if (bits & 0o1) != 0 { + rights |= DWORD(FILE_EXECUTE + | FILE_READ_ATTRIBUTES + | STANDARD_RIGHTS_EXECUTE + | SYNCHRONIZE) + } + + return rights +} + +fileprivate func getTokenInformation( + of: T.Type, + hToken: HANDLE, + ticTokenClass: TOKEN_INFORMATION_CLASS +) -> UnsafePointer? { + var capacity = 1024 + for _ in 0..<2 { + let buffer = UnsafeMutableRawPointer.allocate( + byteCount: capacity, + alignment: MemoryLayout.alignment + ) + + var dwLength = DWORD(0) + + if GetTokenInformation(hToken, + ticTokenClass, + buffer, + DWORD(capacity), + &dwLength) { + return UnsafePointer(buffer.assumingMemoryBound(to: T.self)) + } + + buffer.deallocate() + + capacity = Int(dwLength) + } + return nil +} + +/// Build a SECURITY_DESCRIPTOR from UNIX-style "mode" bits. This only +/// takes account of the rwx and sticky bits; there's really nothing that +/// we can do about setuid/setgid. +@usableFromInline +internal func createSecurityDescriptor(from mode: CInterop.Mode) + -> PSECURITY_DESCRIPTOR? { + let ownerPerm = (Int(mode) >> 6) & 0o7 + let groupPerm = (Int(mode) >> 3) & 0o7 + let otherPerm = Int(mode) & 0o7 + + let ownerRights = rightsFromModeBits(ownerPerm) + let groupRights = rightsFromModeBits(groupPerm, sticky: (mode & 0o1000) != 0) + let otherRights = rightsFromModeBits(otherPerm, sticky: (mode & 0o1000) != 0) + + // If group or other permissions are *more* permissive, then we need + // some DENY ACEs as well to implement the expected semantics + let ownerDenyRights = ((ownerRights ^ groupRights) & groupRights) | + ((ownerRights ^ otherRights) & otherRights) + let groupDenyRights = (groupRights ^ otherRights) & otherRights + + var SIDAuthWorld = SID_IDENTIFIER_AUTHORITY(Value: (0, 0, 0, 0, 0, 1)) + var everyone: PSID? = nil + + guard AllocateAndInitializeSid(&SIDAuthWorld, 1, + DWORD(SECURITY_WORLD_RID), + 0, 0, 0, 0, 0, 0, 0, + &everyone) else { + return nil + } + guard let everyone = everyone else { + return nil + } + defer { + FreeSid(everyone) + } + + let hToken = GetCurrentThreadEffectiveToken()! + + guard let pTokenUser = getTokenInformation(of: TOKEN_USER.self, + hToken: hToken, + ticTokenClass: TokenUser) else { + return nil + } + defer { + pTokenUser.deallocate() + } + + guard let pTokenPrimaryGroup = getTokenInformation( + of: TOKEN_PRIMARY_GROUP.self, + hToken: hToken, + ticTokenClass: TokenPrimaryGroup + ) else { + return nil + } + defer { + pTokenPrimaryGroup.deallocate() + } + + let user = pTokenUser.pointee.User.Sid! + let group = pTokenPrimaryGroup.pointee.PrimaryGroup! + + var eas = [ + EXPLICIT_ACCESS_W( + grfAccessPermissions: ownerRights, + grfAccessMode: GRANT_ACCESS, + grfInheritance: DWORD(NO_INHERITANCE), + Trustee: TRUSTEE_W( + pMultipleTrustee: nil, + MultipleTrusteeOperation: NO_MULTIPLE_TRUSTEE, + TrusteeForm: TRUSTEE_IS_SID, + TrusteeType: TRUSTEE_IS_USER, + ptstrName: + user.assumingMemoryBound(to: CInterop.PlatformChar.self) + ) + ), + EXPLICIT_ACCESS_W( + grfAccessPermissions: groupRights, + grfAccessMode: GRANT_ACCESS, + grfInheritance: DWORD(NO_INHERITANCE), + Trustee: TRUSTEE_W( + pMultipleTrustee: nil, + MultipleTrusteeOperation: NO_MULTIPLE_TRUSTEE, + TrusteeForm: TRUSTEE_IS_SID, + TrusteeType: TRUSTEE_IS_GROUP, + ptstrName: + group.assumingMemoryBound(to: CInterop.PlatformChar.self) + ) + ), + EXPLICIT_ACCESS_W( + grfAccessPermissions: otherRights, + grfAccessMode: GRANT_ACCESS, + grfInheritance: DWORD(NO_INHERITANCE), + Trustee: TRUSTEE_W( + pMultipleTrustee: nil, + MultipleTrusteeOperation: NO_MULTIPLE_TRUSTEE, + TrusteeForm: TRUSTEE_IS_SID, + TrusteeType: TRUSTEE_IS_GROUP, + ptstrName: + everyone.assumingMemoryBound(to: CInterop.PlatformChar.self) + ) + ) + ] + + if ownerDenyRights != 0 { + eas.append( + EXPLICIT_ACCESS_W( + grfAccessPermissions: ownerDenyRights, + grfAccessMode: DENY_ACCESS, + grfInheritance: DWORD(NO_INHERITANCE), + Trustee: TRUSTEE_W( + pMultipleTrustee: nil, + MultipleTrusteeOperation: NO_MULTIPLE_TRUSTEE, + TrusteeForm: TRUSTEE_IS_SID, + TrusteeType: TRUSTEE_IS_USER, + ptstrName: + user.assumingMemoryBound(to: CInterop.PlatformChar.self) + ) + ) + ) + } + + if groupDenyRights != 0 { + eas.append( + EXPLICIT_ACCESS_W( + grfAccessPermissions: groupDenyRights, + grfAccessMode: DENY_ACCESS, + grfInheritance: DWORD(NO_INHERITANCE), + Trustee: TRUSTEE_W( + pMultipleTrustee: nil, + MultipleTrusteeOperation: NO_MULTIPLE_TRUSTEE, + TrusteeForm: TRUSTEE_IS_SID, + TrusteeType: TRUSTEE_IS_GROUP, + ptstrName: + group.assumingMemoryBound(to: CInterop.PlatformChar.self) + ) + ) + ) + } + + var pACL: PACL? = nil + guard SetEntriesInAclW(ULONG(eas.count), + &eas, + nil, + &pACL) == ERROR_SUCCESS else { + return nil + } + defer { + LocalFree(pACL) + } + + // Create the security descriptor, making sure that inherited ACEs don't + // take effect, since that wouldn't match the behaviour of mode bits. + var descriptor = SECURITY_DESCRIPTOR() + + guard InitializeSecurityDescriptor(&descriptor, + DWORD(SECURITY_DESCRIPTOR_REVISION)) else { + return nil + } + + guard SetSecurityDescriptorControl(&descriptor, + SECURITY_DESCRIPTOR_CONTROL(SE_DACL_PROTECTED), + SECURITY_DESCRIPTOR_CONTROL(SE_DACL_PROTECTED)) + && SetSecurityDescriptorOwner(&descriptor, user, false) + && SetSecurityDescriptorGroup(&descriptor, group, false) + && SetSecurityDescriptorDacl(&descriptor, + true, + pACL, + false) else { + return nil + } + + // Make it self-contained (up to this point it uses pointers) + var dwRelativeSize = DWORD(0) + + guard !MakeSelfRelativeSD(&descriptor, nil, &dwRelativeSize) + && GetLastError() == ERROR_INSUFFICIENT_BUFFER else { + return nil + } + + let pDescriptor = UnsafeMutableRawPointer.allocate( + byteCount: Int(dwRelativeSize), + alignment: MemoryLayout.alignment + ).assumingMemoryBound(to: SECURITY_DESCRIPTOR.self) + + guard MakeSelfRelativeSD(&descriptor, pDescriptor, &dwRelativeSize) else { + pDescriptor.deallocate() + return nil + } + + return UnsafeMutableRawPointer(pDescriptor) +} + +fileprivate struct DecodedOpenFlags { + var dwDesiredAccess: DWORD + var dwCreationDisposition: DWORD + var bInheritHandle: WindowsBool + var dwFlagsAndAttributes: DWORD + + init(_ oflag: Int32) { + switch oflag & (_O_CREAT | _O_EXCL | _O_TRUNC) { + case _O_CREAT | _O_EXCL, _O_CREAT | _O_EXCL | _O_TRUNC: + dwCreationDisposition = DWORD(CREATE_NEW) + case _O_CREAT: + dwCreationDisposition = DWORD(OPEN_ALWAYS) + case _O_CREAT | _O_TRUNC: + dwCreationDisposition = DWORD(CREATE_ALWAYS) + case _O_TRUNC: + dwCreationDisposition = DWORD(TRUNCATE_EXISTING) + default: + dwCreationDisposition = DWORD(OPEN_EXISTING) + } + + dwDesiredAccess = 0 + if (oflag & _O_RDONLY) != 0 { + dwDesiredAccess |= DWORD(GENERIC_READ) + } + if (oflag & _O_WRONLY) != 0 { + dwDesiredAccess |= DWORD(GENERIC_WRITE) + } + if (oflag & _O_RDWR) != 0 { + dwDesiredAccess |= DWORD(GENERIC_READ) | DWORD(GENERIC_WRITE) + } + + bInheritHandle = WindowsBool((oflag & _O_NOINHERIT) == 0) + + dwFlagsAndAttributes = 0 + if (oflag & _O_SEQUENTIAL) != 0 { + dwFlagsAndAttributes |= DWORD(FILE_FLAG_SEQUENTIAL_SCAN) + } + if (oflag & _O_RANDOM) != 0 { + dwFlagsAndAttributes |= DWORD(FILE_FLAG_RANDOM_ACCESS) + } + if (oflag & _O_TEMPORARY) != 0 { + dwFlagsAndAttributes |= DWORD(FILE_FLAG_DELETE_ON_CLOSE) + } + + if (oflag & _O_SHORT_LIVED) != 0 { + dwFlagsAndAttributes |= DWORD(FILE_ATTRIBUTE_TEMPORARY) + } else { + dwFlagsAndAttributes |= DWORD(FILE_ATTRIBUTE_NORMAL) + } + } +} + #endif From 33773a8eb9b9da85bae0e86fd21342482de593a0 Mon Sep 17 00:00:00 2001 From: Alastair Houghton Date: Thu, 21 Mar 2024 16:32:19 +0000 Subject: [PATCH 191/427] Update Sources/System/FilePath/FilePathTemp.swift Co-authored-by: Karoy Lorentey --- Sources/System/FilePath/FilePathTemp.swift | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Sources/System/FilePath/FilePathTemp.swift b/Sources/System/FilePath/FilePathTemp.swift index 23a08168..ea67428d 100644 --- a/Sources/System/FilePath/FilePathTemp.swift +++ b/Sources/System/FilePath/FilePathTemp.swift @@ -9,8 +9,10 @@ // MARK: - API -public func withTemporaryPath(basename: FilePath.Component, - _ body: (FilePath) throws -> R) throws -> R { +public func withTemporaryPath( + basename: FilePath.Component, + _ body: (FilePath) throws -> R +) throws -> R { let temporaryDir = try createUniqueTemporaryDirectory(basename: basename) defer { try? recursiveRemove(at: temporaryDir) From 2357d6932c9f8058562a84ca2146e2519809997e Mon Sep 17 00:00:00 2001 From: Alastair Houghton Date: Mon, 25 Mar 2024 18:28:18 +0000 Subject: [PATCH 192/427] Refactor FilePathTemp, add tests for permission code. Broke `FilePathTemp.swift` into separate files for POSIX and Windows. Added tests for permissions code on Windows. Added leading underscores to some function names. Made some other functions `fileprivate`. rdar://125087707 --- Sources/System/ErrnoWindows.swift | 2 +- Sources/System/FileOperations.swift | 46 ++- Sources/System/FilePath/FilePathTemp.swift | 263 +++--------------- .../System/FilePath/FilePathTempPosix.swift | 153 ++++++++++ .../System/FilePath/FilePathTempWindows.swift | 114 ++++++++ Sources/System/Internals/Syscalls.swift | 12 + .../Internals/WindowsSyscallAdapters.swift | 72 +++-- Tests/SystemTests/FileOperationsTest.swift | 4 +- .../FileOperationsTestWindows.swift | 241 ++++++++++++++++ .../FilePathTests/FilePathTempTest.swift | 8 +- 10 files changed, 657 insertions(+), 258 deletions(-) create mode 100644 Sources/System/FilePath/FilePathTempPosix.swift create mode 100644 Sources/System/FilePath/FilePathTempWindows.swift create mode 100644 Tests/SystemTests/FileOperationsTestWindows.swift diff --git a/Sources/System/ErrnoWindows.swift b/Sources/System/ErrnoWindows.swift index 98fda433..b94b5778 100644 --- a/Sources/System/ErrnoWindows.swift +++ b/Sources/System/ErrnoWindows.swift @@ -13,7 +13,7 @@ import WinSDK extension Errno { public init(windowsError: DWORD) { - self.init(rawValue: mapWindowsErrorToErrno(windowsError)) + self.init(rawValue: _mapWindowsErrorToErrno(windowsError)) } } diff --git a/Sources/System/FileOperations.swift b/Sources/System/FileOperations.swift index d2e8d657..ba70e875 100644 --- a/Sources/System/FileOperations.swift +++ b/Sources/System/FileOperations.swift @@ -135,8 +135,6 @@ extension FileDescriptor { if let permissions = permissions { return system_open(path, oFlag, permissions.rawValue) } - precondition(!options.contains(.create), - "Create must be given permissions") return system_open(path, oFlag) } return descOrError.map { FileDescriptor(rawValue: $0) } @@ -510,3 +508,47 @@ extension FileDescriptor { } } } + +extension FilePermissions { + /// The file creation permission mask (aka "umask"). + /// + /// Permissions set in this mask will be cleared by functions that create + /// files or directories. Note that this mask is process-wide, and that + /// *getting* it is not thread safe. + @_alwaysEmitIntoClient + public static var creationMask: FilePermissions { + get { + let oldMask = _umask(0o22) + _ = _umask(oldMask) + return FilePermissions(rawValue: oldMask) + } + set { + _ = _umask(newValue.rawValue) + } + } + + /// Change the file creation permission mask, run some code, then + /// restore it to its original value. + /// + /// - Parameters: + /// - permissions: The new permission mask. + /// + /// This is more efficient than reading `creationMask` and restoring it + /// afterwards, because of the way reading the creation mask works. + @_alwaysEmitIntoClient + public static func withCreationMask( + _ permissions: FilePermissions, + body: () throws -> R + ) rethrows -> R { + let oldMask = _umask(permissions.rawValue) + defer { + _ = _umask(oldMask) + } + return try body() + } + + @usableFromInline + internal static func _umask(_ mode: CModeT) -> CModeT { + return system_umask(mode) + } +} diff --git a/Sources/System/FilePath/FilePathTemp.swift b/Sources/System/FilePath/FilePathTemp.swift index ea67428d..7b41d0b8 100644 --- a/Sources/System/FilePath/FilePathTemp.swift +++ b/Sources/System/FilePath/FilePathTemp.swift @@ -9,13 +9,22 @@ // MARK: - API -public func withTemporaryPath( +/// Create a temporary path for the duration of the closure. +/// +/// - Parameters: +/// - basename: The base name for the temporary path. +/// - body: The closure to execute. +/// +/// Creates a temporary directory with a name based on the given `basename`, +/// executes `body`, passing in the path of the created directory, then +/// deletes the directory and all of its contents before returning. +public func withTemporaryFilePath( basename: FilePath.Component, _ body: (FilePath) throws -> R ) throws -> R { let temporaryDir = try createUniqueTemporaryDirectory(basename: basename) defer { - try? recursiveRemove(at: temporaryDir) + try? _recursiveRemove(at: temporaryDir) } return try body(temporaryDir) @@ -23,230 +32,20 @@ public func withTemporaryPath( // MARK: - Internals -#if os(Windows) -import WinSDK - -fileprivate func getTemporaryDirectory() throws -> FilePath { - return try withUnsafeTemporaryAllocation(of: CInterop.PlatformChar.self, - capacity: Int(MAX_PATH) + 1) { - buffer in - - guard GetTempPath2W(DWORD(buffer.count), buffer.baseAddress) != 0 else { - throw Errno(windowsError: GetLastError()) - } - - return FilePath(SystemString(platformString: buffer.baseAddress!)) - } -} - -fileprivate func forEachFile( - at path: FilePath, - _ body: (WIN32_FIND_DATAW) throws -> () -) rethrows { - let searchPath = path.appending("\\*") - - try searchPath.withPlatformString { szPath in - var findData = WIN32_FIND_DATAW() - let hFind = FindFirstFileW(szPath, &findData) - if hFind == INVALID_HANDLE_VALUE { - throw Errno(windowsError: GetLastError()) - } - defer { - FindClose(hFind) - } - - repeat { - // Skip . and .. - if findData.cFileName.0 == 46 - && (findData.cFileName.1 == 0 - || (findData.cFileName.1 == 46 - && findData.cFileName.2 == 0)) { - continue - } - - try body(findData) - } while FindNextFileW(hFind, &findData) - } -} - -fileprivate func recursiveRemove(at path: FilePath) throws { - // First, deal with subdirectories - try forEachFile(at: path) { findData in - if (findData.dwFileAttributes & DWORD(FILE_ATTRIBUTE_DIRECTORY)) != 0 { - let name = withUnsafeBytes(of: findData.cFileName) { - return SystemString(platformString: $0.assumingMemoryBound( - to: CInterop.PlatformChar.self).baseAddress!) - } - let component = FilePath.Component(name)! - let subpath = path.appending(component) - - try recursiveRemove(at: subpath) - } - } - - // Now delete everything else - try forEachFile(at: path) { findData in - let name = withUnsafeBytes(of: findData.cFileName) { - return SystemString(platformString: $0.assumingMemoryBound( - to: CInterop.PlatformChar.self).baseAddress!) - } - let component = FilePath.Component(name)! - let subpath = path.appending(component) - - if (findData.dwFileAttributes & DWORD(FILE_ATTRIBUTE_DIRECTORY)) == 0 { - try subpath.withPlatformString { - if !DeleteFileW($0) { - throw Errno(windowsError: GetLastError()) - } - } - } - } - - // Finally, delete the parent - try path.withPlatformString { - if !RemoveDirectoryW($0) { - throw Errno(windowsError: GetLastError()) - } - } -} - -#else -fileprivate func getTemporaryDirectory() throws -> FilePath { - #if SYSTEM_PACKAGE_DARWIN - var capacity = 1024 - while true { - let path: FilePath? = withUnsafeTemporaryAllocation( - of: CInterop.PlatformChar.self, - capacity: capacity - ) { buffer in - let len = system_confstr(SYSTEM_CS_DARWIN_USER_TEMP_DIR, - buffer.baseAddress!, - buffer.count) - if len == 0 { - // Fall back to "/tmp" if we can't read the temp directory - return "/tmp" - } - // If it was truncated, increase capaciy and try again - if len > buffer.count { - capacity = len - return nil - } - return FilePath(SystemString(platformString: buffer.baseAddress!)) - } - if let path = path { - return path - } - } - #else - return "/tmp" - #endif -} - -fileprivate func recursiveRemove(at path: FilePath) throws { - let dirfd = try FileDescriptor.open(path, .readOnly, options: .directory) - defer { - try? dirfd.close() - } - - let dot: (CInterop.PlatformChar, CInterop.PlatformChar) = (46, 0) - try withUnsafeBytes(of: dot) { - try recursiveRemove( - in: dirfd.rawValue, - path: $0.assumingMemoryBound(to: CInterop.PlatformChar.self).baseAddress! - ) - } - - try path.withPlatformString { - if system_rmdir($0) != 0 { - throw Errno.current - } - } -} - -fileprivate func impl_opendirat( - _ dirfd: CInt, - _ path: UnsafePointer -) -> UnsafeMutablePointer? { - let fd = system_openat(dirfd, path, - FileDescriptor.AccessMode.readOnly.rawValue - | FileDescriptor.OpenOptions.directory.rawValue) - if fd < 0 { - return nil - } - return system_fdopendir(fd) -} - -fileprivate func forEachFile( - in dirfd: CInt, path: UnsafePointer, - _ body: (system_dirent) throws -> () -) throws { - guard let dir = impl_opendirat(dirfd, path) else { - throw Errno.current - } - defer { - _ = system_closedir(dir) - } - - while let dirent = system_readdir(dir) { - // Skip . and .. - if dirent.pointee.d_name.0 == 46 - && (dirent.pointee.d_name.1 == 0 - || (dirent.pointee.d_name.1 == 46 - && dirent.pointee.d_name.2 == 0)) { - continue - } - - try body(dirent.pointee) - } -} - -internal func recursiveRemove( - in dirfd: CInt, - path: UnsafePointer -) throws { - // First, deal with subdirectories - try forEachFile(in: dirfd, path: path) { dirent in - if dirent.d_type == SYSTEM_DT_DIR { - try withUnsafeBytes(of: dirent.d_name) { - try recursiveRemove( - in: dirfd, - path: $0.assumingMemoryBound(to: CInterop.PlatformChar.self) - .baseAddress! - ) - } - } - } - - // Now delete the contents of this directory - try forEachFile(in: dirfd, path: path) { dirent in - let flag: CInt - - if dirent.d_type == SYSTEM_DT_DIR { - flag = SYSTEM_AT_REMOVE_DIR - } else { - flag = 0 - } - - let result = withUnsafeBytes(of: dirent.d_name) { - system_unlinkat(dirfd, - $0.assumingMemoryBound(to: CInterop.PlatformChar.self) - .baseAddress!, - flag) - } - - if result != 0 { - throw Errno.current - } - } -} -#endif - fileprivate let base64 = Array( "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_".utf8 ) -fileprivate func makeTempDirectory(at: FilePath) throws -> Bool { - return try at.withPlatformString { +/// Create a directory that is only accessible to the current user. +/// +/// - Parameters: +/// - path: The path of the directory to create. +/// - Returns: `true` if a new directory was created. +/// +/// This function will throw if there is an error, except if the error +/// is that the directory exists, in which case it returns `false`. +fileprivate func makeLockedDownDirectory(at path: FilePath) throws -> Bool { + return try path.withPlatformString { if system_mkdir($0, 0o700) == 0 { return true } @@ -259,6 +58,11 @@ fileprivate func makeTempDirectory(at: FilePath) throws -> Bool { } } +/// Generate a random string of base64 filename safe characters. +/// +/// - Parameters: +/// - length: The number of characters in the returned string. +/// - Returns: A random string of length `length`. fileprivate func createRandomString(length: Int) -> String { return String( decoding: (0.. String { ) } -internal func createUniqueTemporaryDirectory( +/// Given a base name, create a uniquely named temporary directory. +/// +/// - Parameters: +/// - basename: The base name for the new directory. +/// - Returns: The path to the new directory. +/// +/// Creates a directory in the system temporary directory whose name +/// starts with `basename`, followed by a `.` and then a random +/// string of characters. +fileprivate func createUniqueTemporaryDirectory( basename: FilePath.Component ) throws -> FilePath { - var tempDir = try getTemporaryDirectory() + var tempDir = try _getTemporaryDirectory() tempDir.append(basename) while true { tempDir.extension = createRandomString(length: 16) - if try makeTempDirectory(at: tempDir) { + if try makeLockedDownDirectory(at: tempDir) { return tempDir } } diff --git a/Sources/System/FilePath/FilePathTempPosix.swift b/Sources/System/FilePath/FilePathTempPosix.swift new file mode 100644 index 00000000..623ccae4 --- /dev/null +++ b/Sources/System/FilePath/FilePathTempPosix.swift @@ -0,0 +1,153 @@ +/* + This source file is part of the Swift System open source project + + Copyright (c) 2024 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 + */ + +#if !os(Windows) + +/// Get the path to the system temporary directory. +internal func _getTemporaryDirectory() throws -> FilePath { + guard let tmp = system_getenv("TMPDIR") else { + return "/tmp" + } + + return FilePath(SystemString(platformString: tmp)) +} + +/// Delete the entire contents of a directory, including its subdirectories. +/// +/// - Parameters: +/// - path: The directory to be deleted. +/// +/// Removes a directory completely, including all of its contents. +internal func _recursiveRemove( + at path: FilePath +) throws { + let dirfd = try FileDescriptor.open(path, .readOnly, options: .directory) + defer { + try? dirfd.close() + } + + let dot: (CInterop.PlatformChar, CInterop.PlatformChar) = (46, 0) + try withUnsafeBytes(of: dot) { + try recursiveRemove( + in: dirfd.rawValue, + name: $0.assumingMemoryBound(to: CInterop.PlatformChar.self).baseAddress! + ) + } + + try path.withPlatformString { + if system_rmdir($0) != 0 { + throw Errno.current + } + } +} + +/// Open a directory by reference to its parent and name. +/// +/// - Parameters: +/// - dirfd: An open file descriptor for the parent directory. +/// - name: The name of the directory to open. +/// - Returns: A pointer to a `DIR` structure. +/// +/// This is like `opendir()`, but instead of taking a path, it uses a +/// file descriptor pointing at the parent, thus avoiding path length +/// limits. +fileprivate func impl_opendirat( + _ dirfd: CInt, + _ name: UnsafePointer +) -> UnsafeMutablePointer? { + let fd = system_openat(dirfd, name, + FileDescriptor.AccessMode.readOnly.rawValue + | FileDescriptor.OpenOptions.directory.rawValue) + if fd < 0 { + return nil + } + return system_fdopendir(fd) +} + +/// Invoke a closure for each file within a particular directory. +/// +/// - Parameters: +/// - dirfd: The parent of the directory to be enumerated. +/// - subdir: The subdirectory to be enumerated. +/// - body: The closure that will be invoked. +/// +/// We skip the `.` and `..` pseudo-entries. +fileprivate func forEachFile( + in dirfd: CInt, + subdir: UnsafePointer, + _ body: (system_dirent) throws -> () +) throws { + guard let dir = impl_opendirat(dirfd, subdir) else { + throw Errno.current + } + defer { + _ = system_closedir(dir) + } + + while let dirent = system_readdir(dir) { + // Skip . and .. + if dirent.pointee.d_name.0 == 46 + && (dirent.pointee.d_name.1 == 0 + || (dirent.pointee.d_name.1 == 46 + && dirent.pointee.d_name.2 == 0)) { + continue + } + + try body(dirent.pointee) + } +} + +/// Delete the entire contents of a directory, including its subdirectories. +/// +/// - Parameters: +/// - dirfd: The parent of the directory to be removed. +/// - name: The name of the directory to be removed. +/// +/// Removes a directory completely, including all of its contents. +fileprivate func recursiveRemove( + in dirfd: CInt, + name: UnsafePointer +) throws { + // First, deal with subdirectories + try forEachFile(in: dirfd, subdir: name) { dirent in + if dirent.d_type == SYSTEM_DT_DIR { + try withUnsafeBytes(of: dirent.d_name) { + try recursiveRemove( + in: dirfd, + name: $0.assumingMemoryBound(to: CInterop.PlatformChar.self) + .baseAddress! + ) + } + } + } + + // Now delete the contents of this directory + try forEachFile(in: dirfd, subdir: name) { dirent in + let flag: CInt + + if dirent.d_type == SYSTEM_DT_DIR { + flag = SYSTEM_AT_REMOVE_DIR + } else { + flag = 0 + } + + let result = withUnsafeBytes(of: dirent.d_name) { + system_unlinkat(dirfd, + $0.assumingMemoryBound(to: CInterop.PlatformChar.self) + .baseAddress!, + flag) + } + + if result != 0 { + throw Errno.current + } + } +} + +#endif // !os(Windows) diff --git a/Sources/System/FilePath/FilePathTempWindows.swift b/Sources/System/FilePath/FilePathTempWindows.swift new file mode 100644 index 00000000..0d97edcb --- /dev/null +++ b/Sources/System/FilePath/FilePathTempWindows.swift @@ -0,0 +1,114 @@ +/* + This source file is part of the Swift System open source project + + Copyright (c) 2024 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 +*/ + +#if os(Windows) + +import WinSDK + +/// Get the path to the system temporary directory. +internal func _getTemporaryDirectory() throws -> FilePath { + return try withUnsafeTemporaryAllocation(of: CInterop.PlatformChar.self, + capacity: Int(MAX_PATH) + 1) { + buffer in + + guard GetTempPath2W(DWORD(buffer.count), buffer.baseAddress) != 0 else { + throw Errno(windowsError: GetLastError()) + } + + return FilePath(SystemString(platformString: buffer.baseAddress!)) + } +} + +/// Invoke a closure for each file within a particular directory. +/// +/// - Parameters: +/// - path: The path at which we should enumerate items. +/// - body: The closure that will be invoked. +/// +/// We skip the `.` and `..` pseudo-entries. +fileprivate func forEachFile( + at path: FilePath, + _ body: (WIN32_FIND_DATAW) throws -> () +) rethrows { + let searchPath = path.appending("\\*") + + try searchPath.withPlatformString { szPath in + var findData = WIN32_FIND_DATAW() + let hFind = FindFirstFileW(szPath, &findData) + if hFind == INVALID_HANDLE_VALUE { + throw Errno(windowsError: GetLastError()) + } + defer { + FindClose(hFind) + } + + repeat { + // Skip . and .. + if findData.cFileName.0 == 46 + && (findData.cFileName.1 == 0 + || (findData.cFileName.1 == 46 + && findData.cFileName.2 == 0)) { + continue + } + + try body(findData) + } while FindNextFileW(hFind, &findData) + } +} + +/// Delete the entire contents of a directory, including its subdirectories. +/// +/// - Parameters: +/// - path: The directory to be deleted. +/// +/// Removes a directory completely, including all of its contents. +internal func _recursiveRemove( + at path: FilePath +) throws { + // First, deal with subdirectories + try forEachFile(at: path) { findData in + if (findData.dwFileAttributes & DWORD(FILE_ATTRIBUTE_DIRECTORY)) != 0 { + let name = withUnsafeBytes(of: findData.cFileName) { + return SystemString(platformString: $0.assumingMemoryBound( + to: CInterop.PlatformChar.self).baseAddress!) + } + let component = FilePath.Component(name)! + let subpath = path.appending(component) + + try _recursiveRemove(at: subpath) + } + } + + // Now delete everything else + try forEachFile(at: path) { findData in + let name = withUnsafeBytes(of: findData.cFileName) { + return SystemString(platformString: $0.assumingMemoryBound( + to: CInterop.PlatformChar.self).baseAddress!) + } + let component = FilePath.Component(name)! + let subpath = path.appending(component) + + if (findData.dwFileAttributes & DWORD(FILE_ATTRIBUTE_DIRECTORY)) == 0 { + try subpath.withPlatformString { + if !DeleteFileW($0) { + throw Errno(windowsError: GetLastError()) + } + } + } + } + + // Finally, delete the parent + try path.withPlatformString { + if !RemoveDirectoryW($0) { + throw Errno(windowsError: GetLastError()) + } + } +} + +#endif // os(Windows) diff --git a/Sources/System/Internals/Syscalls.swift b/Sources/System/Internals/Syscalls.swift index cec84137..1c6ed724 100644 --- a/Sources/System/Internals/Syscalls.swift +++ b/Sources/System/Internals/Syscalls.swift @@ -239,3 +239,15 @@ internal func system_openat( return openat(fd, path, oflag) } #endif + +internal func system_umask( + _ mode: CInterop.Mode +) -> CInterop.Mode { + return umask(mode) +} + +internal func system_getenv( + _ name: UnsafePointer +) -> UnsafeMutablePointer? { + return getenv(name) +} diff --git a/Sources/System/Internals/WindowsSyscallAdapters.swift b/Sources/System/Internals/WindowsSyscallAdapters.swift index fa88cbe2..3e3f9c69 100644 --- a/Sources/System/Internals/WindowsSyscallAdapters.swift +++ b/Sources/System/Internals/WindowsSyscallAdapters.swift @@ -12,15 +12,21 @@ import ucrt import WinSDK +fileprivate var _umask: CInterop.Mode = 0o22 + +@inline(__always) +func umask( + _ mode: CInterop.Mode +) -> CInterop.Mode { + let oldMask = _umask + _umask = mode + return oldMask +} + @inline(__always) internal func open( _ path: UnsafePointer, _ oflag: Int32 ) -> CInt { - if (oflag & _O_CREAT) != 0 { - ucrt._set_errno(EINVAL) - return -1 - } - let decodedFlags = DecodedOpenFlags(oflag) var saAttrs = SECURITY_ATTRIBUTES( @@ -40,7 +46,7 @@ internal func open( nil) if hFile == INVALID_HANDLE_VALUE { - ucrt._set_errno(mapWindowsErrorToErrno(GetLastError())) + ucrt._set_errno(_mapWindowsErrorToErrno(GetLastError())) return -1 } @@ -52,8 +58,10 @@ internal func open( _ path: UnsafePointer, _ oflag: Int32, _ mode: CInterop.Mode ) -> CInt { - guard let pSD = createSecurityDescriptor(from: mode) else { - ucrt._set_errno(mapWindowsErrorToErrno(GetLastError())) + let actualMode = mode & ~_umask + + guard let pSD = _createSecurityDescriptor(from: actualMode, for: .file) else { + ucrt._set_errno(_mapWindowsErrorToErrno(GetLastError())) return -1 } @@ -80,7 +88,7 @@ internal func open( nil) if hFile == INVALID_HANDLE_VALUE { - ucrt._set_errno(mapWindowsErrorToErrno(GetLastError())) + ucrt._set_errno(_mapWindowsErrorToErrno(GetLastError())) return -1 } @@ -146,7 +154,7 @@ internal func pread( var nNumberOfBytesRead: DWORD = 0 if !ReadFile(hFile, buf, DWORD(nbyte), &nNumberOfBytesRead, &ovlOverlapped) { - ucrt._set_errno(mapWindowsErrorToErrno(GetLastError())) + ucrt._set_errno(_mapWindowsErrorToErrno(GetLastError())) return Int(-1) } return Int(nNumberOfBytesRead) @@ -169,7 +177,7 @@ internal func pwrite( var nNumberOfBytesWritten: DWORD = 0 if !WriteFile(hFile, buf, DWORD(nbyte), &nNumberOfBytesWritten, &ovlOverlapped) { - ucrt._set_errno(mapWindowsErrorToErrno(GetLastError())) + ucrt._set_errno(_mapWindowsErrorToErrno(GetLastError())) return Int(-1) } return Int(nNumberOfBytesWritten) @@ -193,7 +201,7 @@ internal func ftruncate(_ fd: Int32, _ length: off_t) -> Int32 { // Save the current position and restore it when we're done if !SetFilePointerEx(hFile, liCurrentOffset, &liCurrentOffset, DWORD(FILE_CURRENT)) { - ucrt._set_errno(mapWindowsErrorToErrno(GetLastError())) + ucrt._set_errno(_mapWindowsErrorToErrno(GetLastError())) return -1 } defer { @@ -203,7 +211,7 @@ internal func ftruncate(_ fd: Int32, _ length: off_t) -> Int32 { // Truncate (or extend) the file if !SetFilePointerEx(hFile, liDesiredLength, nil, DWORD(FILE_BEGIN)) || !SetEndOfFile(hFile) { - ucrt._set_errno(mapWindowsErrorToErrno(GetLastError())) + ucrt._set_errno(_mapWindowsErrorToErrno(GetLastError())) return -1 } @@ -215,8 +223,11 @@ internal func mkdir( _ path: UnsafePointer, _ mode: CInterop.Mode ) -> CInt { - guard let pSD = createSecurityDescriptor(from: mode) else { - ucrt._set_errno(mapWindowsErrorToErrno(GetLastError())) + let actualMode = mode & ~_umask + + guard let pSD = _createSecurityDescriptor(from: actualMode, + for: .directory) else { + ucrt._set_errno(_mapWindowsErrorToErrno(GetLastError())) return -1 } defer { @@ -230,7 +241,7 @@ internal func mkdir( ) if !CreateDirectoryW(path, &saAttrs) { - ucrt._set_errno(mapWindowsErrorToErrno(GetLastError())) + ucrt._set_errno(_mapWindowsErrorToErrno(GetLastError())) return -1 } @@ -242,7 +253,7 @@ internal func rmdir( _ path: UnsafePointer ) -> CInt { if !RemoveDirectoryW(path) { - ucrt._set_errno(mapWindowsErrorToErrno(GetLastError())) + ucrt._set_errno(_mapWindowsErrorToErrno(GetLastError())) return -1 } @@ -250,7 +261,7 @@ internal func rmdir( } @usableFromInline -internal func mapWindowsErrorToErrno(_ errorCode: DWORD) -> CInt { +internal func _mapWindowsErrorToErrno(_ errorCode: DWORD) -> CInt { switch Int32(errorCode) { case ERROR_SUCCESS: return 0 @@ -323,7 +334,9 @@ internal func mapWindowsErrorToErrno(_ errorCode: DWORD) -> CInt { } fileprivate func rightsFromModeBits( - _ bits: Int, sticky: Bool = false + _ bits: Int, + sticky: Bool = false, + for fileOrDirectory: _FileOrDirectory ) -> DWORD { var rights: DWORD = 0 @@ -341,7 +354,7 @@ fileprivate func rightsFromModeBits( | FILE_WRITE_EA | STANDARD_RIGHTS_WRITE | SYNCHRONIZE) - if !sticky { + if fileOrDirectory == .directory && !sticky { rights |= DWORD(FILE_DELETE_CHILD) } } @@ -384,19 +397,30 @@ fileprivate func getTokenInformation( return nil } +@usableFromInline +internal enum _FileOrDirectory { + case file + case directory +} + /// Build a SECURITY_DESCRIPTOR from UNIX-style "mode" bits. This only /// takes account of the rwx and sticky bits; there's really nothing that /// we can do about setuid/setgid. @usableFromInline -internal func createSecurityDescriptor(from mode: CInterop.Mode) +internal func _createSecurityDescriptor(from mode: CInterop.Mode, + for fileOrDirectory: _FileOrDirectory) -> PSECURITY_DESCRIPTOR? { let ownerPerm = (Int(mode) >> 6) & 0o7 let groupPerm = (Int(mode) >> 3) & 0o7 let otherPerm = Int(mode) & 0o7 - let ownerRights = rightsFromModeBits(ownerPerm) - let groupRights = rightsFromModeBits(groupPerm, sticky: (mode & 0o1000) != 0) - let otherRights = rightsFromModeBits(otherPerm, sticky: (mode & 0o1000) != 0) + let ownerRights = rightsFromModeBits(ownerPerm, for: fileOrDirectory) + let groupRights = rightsFromModeBits(groupPerm, + sticky: (mode & 0o1000) != 0, + for: fileOrDirectory) + let otherRights = rightsFromModeBits(otherPerm, + sticky: (mode & 0o1000) != 0, + for: fileOrDirectory) // If group or other permissions are *more* permissive, then we need // some DENY ACEs as well to implement the expected semantics diff --git a/Tests/SystemTests/FileOperationsTest.swift b/Tests/SystemTests/FileOperationsTest.swift index ee821280..be5a67d4 100644 --- a/Tests/SystemTests/FileOperationsTest.swift +++ b/Tests/SystemTests/FileOperationsTest.swift @@ -137,7 +137,7 @@ final class FileOperationsTest: XCTestCase { // Ad-hoc test touching a file system. do { // TODO: Test this against a virtual in-memory file system - try withTemporaryPath(basename: "testAdhocOpen") { path in + try withTemporaryFilePath(basename: "testAdhocOpen") { path in let fd = try FileDescriptor.open(path.appending("b.txt"), .readWrite, options: [.create, .truncate], permissions: .ownerReadWrite) try fd.closeAfter { try fd.writeAll("abc".utf8) @@ -186,7 +186,7 @@ final class FileOperationsTest: XCTestCase { } func testResizeFile() throws { - try withTemporaryPath(basename: "testResizeFile") { path in + try withTemporaryFilePath(basename: "testResizeFile") { path in let fd = try FileDescriptor.open(path.appending("\(UUID().uuidString).txt"), .readWrite, options: [.create, .truncate], permissions: .ownerReadWrite) try fd.closeAfter { // File should be empty initially. diff --git a/Tests/SystemTests/FileOperationsTestWindows.swift b/Tests/SystemTests/FileOperationsTestWindows.swift new file mode 100644 index 00000000..35df7312 --- /dev/null +++ b/Tests/SystemTests/FileOperationsTestWindows.swift @@ -0,0 +1,241 @@ +/* + This source file is part of the Swift System open source project + + Copyright (c) 2024 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 +*/ + +import XCTest + +#if os(Windows) + +#if SYSTEM_PACKAGE +import SystemPackage +#else +import System +#endif + +import WinSDK + +@available(iOS 8, *) +final class FileOperationsTestWindows: XCTestCase { + private let r = ACCESS_MASK( + FILE_READ_ATTRIBUTES + | FILE_READ_DATA + | FILE_READ_EA + | STANDARD_RIGHTS_READ + | SYNCHRONIZE + ) + private let w = ACCESS_MASK( + FILE_APPEND_DATA + | FILE_WRITE_ATTRIBUTES + | FILE_WRITE_DATA + | FILE_WRITE_EA + | STANDARD_RIGHTS_WRITE + | SYNCHRONIZE + ) + private let x = ACCESS_MASK( + FILE_EXECUTE + | FILE_READ_ATTRIBUTES + | STANDARD_RIGHTS_EXECUTE + | SYNCHRONIZE + ) + + private struct Test { + var permissions: CModeT + var ownerAccess: ACCESS_MASK + var groupAccess: ACCESS_MASK + var otherAccess: ACCESS_MASK + + init(_ permissions: CModeT, + _ ownerAccess: ACCESS_MASK, + _ groupAccess: ACCESS_MASK, + _ otherAccess: ACCESS_MASK) { + self.permissions = permissions + self.ownerAccess = ownerAccess + self.groupAccess = groupAccess + self.otherAccess = otherAccess + } + } + + /// Retrieve the owner, group and other access masks for a given file. + /// + /// - Parameters: + /// - path: The path to the file to inspect + /// - Returns: A tuple of ACCESS_MASK values. + func getAccessMasks( + path: FilePath + ) -> (ACCESS_MASK, ACCESS_MASK, ACCESS_MASK) { + var SIDAuthWorld = SID_IDENTIFIER_AUTHORITY(Value: (0, 0, 0, 0, 0, 1)) + var psidEveryone: PSID? = nil + + XCTAssert(AllocateAndInitializeSid(&SIDAuthWorld, 1, + DWORD(SECURITY_WORLD_RID), + 0, 0, 0, 0, 0, 0, 0, + &psidEveryone)) + defer { + FreeSid(psidEveryone) + } + + var everyone = TRUSTEE_W( + pMultipleTrustee: nil, + MultipleTrusteeOperation: NO_MULTIPLE_TRUSTEE, + TrusteeForm: TRUSTEE_IS_SID, + TrusteeType: TRUSTEE_IS_GROUP, + ptstrName: + psidEveryone!.assumingMemoryBound(to: CInterop.PlatformChar.self) + ) + + return path.withPlatformString { objectName in + var psidOwner: PSID? = nil + var psidGroup: PSID? = nil + var pDacl: PACL? = nil + var pSD: PSECURITY_DESCRIPTOR? = nil + + XCTAssertEqual(GetNamedSecurityInfoW( + objectName, + SE_FILE_OBJECT, + SECURITY_INFORMATION( + DACL_SECURITY_INFORMATION + | GROUP_SECURITY_INFORMATION + | OWNER_SECURITY_INFORMATION + ), + &psidOwner, + &psidGroup, + &pDacl, + nil, + &pSD), DWORD(ERROR_SUCCESS)) + defer { + LocalFree(pSD) + } + + var owner = TRUSTEE_W( + pMultipleTrustee: nil, + MultipleTrusteeOperation: NO_MULTIPLE_TRUSTEE, + TrusteeForm: TRUSTEE_IS_SID, + TrusteeType: TRUSTEE_IS_USER, + ptstrName: + psidOwner!.assumingMemoryBound(to: CInterop.PlatformChar.self) + ) + var group = TRUSTEE_W( + pMultipleTrustee: nil, + MultipleTrusteeOperation: NO_MULTIPLE_TRUSTEE, + TrusteeForm: TRUSTEE_IS_SID, + TrusteeType: TRUSTEE_IS_GROUP, + ptstrName: + psidGroup!.assumingMemoryBound(to: CInterop.PlatformChar.self) + ) + + var ownerAccess = ACCESS_MASK(0) + var groupAccess = ACCESS_MASK(0) + var otherAccess = ACCESS_MASK(0) + + XCTAssertEqual(GetEffectiveRightsFromAclW( + pDacl, + &owner, + &ownerAccess), DWORD(ERROR_SUCCESS)) + XCTAssertEqual(GetEffectiveRightsFromAclW( + pDacl, + &group, + &groupAccess), DWORD(ERROR_SUCCESS)) + XCTAssertEqual(GetEffectiveRightsFromAclW( + pDacl, + &everyone, + &otherAccess), DWORD(ERROR_SUCCESS)) + + return (ownerAccess, groupAccess, otherAccess) + } + } + + private func runTests(_ tests: [Test], at path: FilePath) throws { + for test in tests { + let octal = String(test.permissions, radix: 8) + let testPath = path.appending("test-\(octal).txt") + let fd = try FileDescriptor.open( + testPath, + .readWrite, + options: [.create, .truncate], + permissions: FilePermissions(rawValue: test.permissions) + ) + _ = try fd.closeAfter { + try fd.writeAll("Hello World".utf8) + } + + let (ownerAccess, groupAccess, otherAccess) + = getAccessMasks(path: testPath) + + XCTAssertEqual(ownerAccess, test.ownerAccess) + XCTAssertEqual(groupAccess, test.groupAccess) + XCTAssertEqual(otherAccess, test.otherAccess) + } + } + + /// Test that the umask works properly + func testUmask() throws { + // Default mask should be 0o022 + XCTAssertEqual(FilePermissions.creationMask, [.groupWrite, .otherWrite]) + + try withTemporaryFilePath(basename: "testUmask") { path in + let tests = [ + Test(0o000, 0, 0, 0), + Test(0o700, r|w|x, 0, 0), + Test(0o770, r|w|x, r|x, 0), + Test(0o777, r|w|x, r|x, r|x) + ] + + try runTests(tests, at: path) + } + + try FilePermissions.withCreationMask([.groupWrite, .groupExecute, + .otherWrite, .otherExecute]) { + try withTemporaryFilePath(basename: "testUmask") { path in + let tests = [ + Test(0o000, 0, 0, 0), + Test(0o700, r|w|x, 0, 0), + Test(0o770, r|w|x, r, 0), + Test(0o777, r|w|x, r, r) + ] + + try runTests(tests, at: path) + } + } + } + + /// Test that setting permissions on a file works as expected + func testPermissions() throws { + try FilePermissions.withCreationMask([]) { + try withTemporaryFilePath(basename: "testPermissions") { path in + let tests = [ + Test(0o000, 0, 0, 0), + + Test(0o400, r, 0, 0), + Test(0o200, w, 0, 0), + Test(0o100, x, 0, 0), + Test(0o040, 0, r, 0), + Test(0o020, 0, w, 0), + Test(0o010, 0, x, 0), + Test(0o004, 0, 0, r), + Test(0o002, 0, 0, w), + Test(0o001, 0, 0, x), + + Test(0o700, r|w|x, 0, 0), + Test(0o770, r|w|x, r|w|x, 0), + Test(0o777, r|w|x, r|w|x, r|w|x), + + Test(0o755, r|w|x, r|x, r|x), + Test(0o644, r|w, r, r), + + Test(0o007, 0, 0, r|w|x), + Test(0o070, 0, r|w|x, 0), + Test(0o077, 0, r|w|x, r|w|x), + ] + + try runTests(tests, at: path) + } + } + } +} + +#endif // os(Windows) diff --git a/Tests/SystemTests/FilePathTests/FilePathTempTest.swift b/Tests/SystemTests/FilePathTests/FilePathTempTest.swift index b2b1fa22..dc8597bb 100644 --- a/Tests/SystemTests/FilePathTests/FilePathTempTest.swift +++ b/Tests/SystemTests/FilePathTests/FilePathTempTest.swift @@ -18,7 +18,7 @@ import System final class TemporaryPathTest: XCTestCase { #if SYSTEM_PACKAGE_DARWIN func testNotInSlashTmp() throws { - try withTemporaryPath(basename: "NotInSlashTmp") { path in + try withTemporaryFilePath(basename: "NotInSlashTmp") { path in // We shouldn't be using "/tmp" on Darwin XCTAssertNotEqual(path.components.first!, "tmp") } @@ -26,10 +26,10 @@ final class TemporaryPathTest: XCTestCase { #endif func testUnique() throws { - try withTemporaryPath(basename: "test") { path in + try withTemporaryFilePath(basename: "test") { path in let strPath = String(decoding: path) XCTAssert(strPath.contains("test")) - try withTemporaryPath(basename: "test") { path2 in + try withTemporaryFilePath(basename: "test") { path2 in let strPath2 = String(decoding: path2) XCTAssertNotEqual(strPath, strPath2) } @@ -39,7 +39,7 @@ final class TemporaryPathTest: XCTestCase { func testCleanup() throws { var thePath: FilePath? = nil - try withTemporaryPath(basename: "test") { path in + try withTemporaryFilePath(basename: "test") { path in thePath = path.appending("foo.txt") let fd = try FileDescriptor.open(thePath!, .readWrite, options: [.create, .truncate], From f5ad5e57fb7f600d6f644d8b3836ee6dfbcfdd9a Mon Sep 17 00:00:00 2001 From: Alastair Houghton Date: Mon, 8 Apr 2024 17:01:42 +0100 Subject: [PATCH 193/427] [Linux] Glibc's DIR doesn't get imported into Swift. Swift ends up with OpaquePointer instead of a typed pointer. rdar://125087707 --- Sources/System/FilePath/FilePathTempPosix.swift | 2 +- Sources/System/Internals/Syscalls.swift | 14 +++++++++----- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/Sources/System/FilePath/FilePathTempPosix.swift b/Sources/System/FilePath/FilePathTempPosix.swift index 623ccae4..e8713c2b 100644 --- a/Sources/System/FilePath/FilePathTempPosix.swift +++ b/Sources/System/FilePath/FilePathTempPosix.swift @@ -60,7 +60,7 @@ internal func _recursiveRemove( fileprivate func impl_opendirat( _ dirfd: CInt, _ name: UnsafePointer -) -> UnsafeMutablePointer? { +) -> system_DIRPtr? { let fd = system_openat(dirfd, name, FileDescriptor.AccessMode.readOnly.rawValue | FileDescriptor.OpenOptions.directory.rawValue) diff --git a/Sources/System/Internals/Syscalls.swift b/Sources/System/Internals/Syscalls.swift index 1c6ed724..3794d6e0 100644 --- a/Sources/System/Internals/Syscalls.swift +++ b/Sources/System/Internals/Syscalls.swift @@ -189,7 +189,11 @@ internal func system_confstr( internal let SYSTEM_AT_REMOVE_DIR = AT_REMOVEDIR internal let SYSTEM_DT_DIR = DT_DIR internal typealias system_dirent = dirent -internal typealias system_DIR = DIR +#if os(Linux) +internal typealias system_DIRPtr = OpaquePointer +#else +internal typealias system_DIRPtr = UnsafeMutablePointer +#endif internal func system_unlinkat( _ fd: CInt, @@ -204,24 +208,24 @@ return unlinkat(fd, path, flag) internal func system_fdopendir( _ fd: CInt -) -> UnsafeMutablePointer? { +) -> system_DIRPtr? { return fdopendir(fd) } internal func system_readdir( - _ dir: UnsafeMutablePointer + _ dir: system_DIRPtr ) -> UnsafeMutablePointer? { return readdir(dir) } internal func system_rewinddir( - _ dir: UnsafeMutablePointer + _ dir: system_DIRPtr ) { return rewinddir(dir) } internal func system_closedir( - _ dir: UnsafeMutablePointer + _ dir: system_DIRPtr ) -> CInt { return closedir(dir) } From 84077b3b4bbfb372789622df91173484f33b4e3c Mon Sep 17 00:00:00 2001 From: Mishal Shah Date: Wed, 8 May 2024 15:21:09 -0700 Subject: [PATCH 194/427] Add Swift CI Support --- .swiftci/5_10_ubuntu2204 | 5 +++++ .swiftci/5_7_ubuntu2204 | 5 +++++ .swiftci/5_8_ubuntu2204 | 5 +++++ .swiftci/5_9_ubuntu2204 | 5 +++++ .swiftci/nightly_6_0_macos | 5 +++++ .swiftci/nightly_6_0_ubuntu2204 | 5 +++++ .swiftci/nightly_main_macos | 5 +++++ .swiftci/nightly_main_ubuntu2204 | 5 +++++ .swiftci/nightly_main_windows | 7 +++++++ 9 files changed, 47 insertions(+) create mode 100644 .swiftci/5_10_ubuntu2204 create mode 100644 .swiftci/5_7_ubuntu2204 create mode 100644 .swiftci/5_8_ubuntu2204 create mode 100644 .swiftci/5_9_ubuntu2204 create mode 100644 .swiftci/nightly_6_0_macos create mode 100644 .swiftci/nightly_6_0_ubuntu2204 create mode 100644 .swiftci/nightly_main_macos create mode 100644 .swiftci/nightly_main_ubuntu2204 create mode 100644 .swiftci/nightly_main_windows diff --git a/.swiftci/5_10_ubuntu2204 b/.swiftci/5_10_ubuntu2204 new file mode 100644 index 00000000..0c65ec16 --- /dev/null +++ b/.swiftci/5_10_ubuntu2204 @@ -0,0 +1,5 @@ +LinuxSwiftPackageJob { + swift_version_tag = "5.10-jammy" + repo = "swift-system" + branch = "main" +} diff --git a/.swiftci/5_7_ubuntu2204 b/.swiftci/5_7_ubuntu2204 new file mode 100644 index 00000000..8550458a --- /dev/null +++ b/.swiftci/5_7_ubuntu2204 @@ -0,0 +1,5 @@ +LinuxSwiftPackageJob { + swift_version_tag = "5.7-jammy" + repo = "swift-system" + branch = "main" +} diff --git a/.swiftci/5_8_ubuntu2204 b/.swiftci/5_8_ubuntu2204 new file mode 100644 index 00000000..0daee625 --- /dev/null +++ b/.swiftci/5_8_ubuntu2204 @@ -0,0 +1,5 @@ +LinuxSwiftPackageJob { + swift_version_tag = "5.8-jammy" + repo = "swift-system" + branch = "main" +} diff --git a/.swiftci/5_9_ubuntu2204 b/.swiftci/5_9_ubuntu2204 new file mode 100644 index 00000000..1ede34f1 --- /dev/null +++ b/.swiftci/5_9_ubuntu2204 @@ -0,0 +1,5 @@ +LinuxSwiftPackageJob { + swift_version_tag = "5.9-jammy" + repo = "swift-system" + branch = "main" +} diff --git a/.swiftci/nightly_6_0_macos b/.swiftci/nightly_6_0_macos new file mode 100644 index 00000000..9acdfa20 --- /dev/null +++ b/.swiftci/nightly_6_0_macos @@ -0,0 +1,5 @@ +macOSSwiftPackageJob { + swift_version = "6.0" + repo = "swift-system" + branch = "main" +} diff --git a/.swiftci/nightly_6_0_ubuntu2204 b/.swiftci/nightly_6_0_ubuntu2204 new file mode 100644 index 00000000..aaa4671a --- /dev/null +++ b/.swiftci/nightly_6_0_ubuntu2204 @@ -0,0 +1,5 @@ +LinuxSwiftPackageJob { + nightly_docker_tag = "nightly-6.0-jammy" + repo = "swift-system" + branch = "main" +} diff --git a/.swiftci/nightly_main_macos b/.swiftci/nightly_main_macos new file mode 100644 index 00000000..329aef79 --- /dev/null +++ b/.swiftci/nightly_main_macos @@ -0,0 +1,5 @@ +macOSSwiftPackageJob { + swift_version = "main" + repo = "swift-system" + branch = "main" +} diff --git a/.swiftci/nightly_main_ubuntu2204 b/.swiftci/nightly_main_ubuntu2204 new file mode 100644 index 00000000..7e03ac06 --- /dev/null +++ b/.swiftci/nightly_main_ubuntu2204 @@ -0,0 +1,5 @@ +LinuxSwiftPackageJob { + nightly_docker_tag = "nightly-jammy" + repo = "swift-system" + branch = "main" +} diff --git a/.swiftci/nightly_main_windows b/.swiftci/nightly_main_windows new file mode 100644 index 00000000..b3d52fd5 --- /dev/null +++ b/.swiftci/nightly_main_windows @@ -0,0 +1,7 @@ +WindowsSwiftPackageWithDockerImageJob { + docker_image = "swiftlang/swift:nightly-windowsservercore-1809" + repo = "swift-system" + branch = "main" + sub_dir = "swift-system" + label = "windows-server-2019" +} From 7fe8e6cfc2e9b6df649d3968a394db0609e867c8 Mon Sep 17 00:00:00 2001 From: Finagolfin Date: Thu, 9 May 2024 19:29:36 +0530 Subject: [PATCH 195/427] Android: DIR* is an OpaquePointer in Bionic too --- Sources/System/Internals/Syscalls.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/System/Internals/Syscalls.swift b/Sources/System/Internals/Syscalls.swift index 3794d6e0..f40b1685 100644 --- a/Sources/System/Internals/Syscalls.swift +++ b/Sources/System/Internals/Syscalls.swift @@ -189,7 +189,7 @@ internal func system_confstr( internal let SYSTEM_AT_REMOVE_DIR = AT_REMOVEDIR internal let SYSTEM_DT_DIR = DT_DIR internal typealias system_dirent = dirent -#if os(Linux) +#if os(Linux) || os(Android) internal typealias system_DIRPtr = OpaquePointer #else internal typealias system_DIRPtr = UnsafeMutablePointer From 73baf1113d5084ed3bb60d39599a5b7f6d639215 Mon Sep 17 00:00:00 2001 From: Karoy Lorentey Date: Thu, 9 May 2024 14:31:11 -0700 Subject: [PATCH 196/427] Retarget pending changes to 1.4.0 We decided to spend the 1.3.0 version number on a small interim release; retarget the currently pending changes to 1.4.0. --- Sources/System/MachPort.swift | 32 +++++++++++++-------------- Tests/SystemTests/MachPortTests.swift | 2 +- Utilities/expand-availability.py | 1 + 3 files changed, 18 insertions(+), 17 deletions(-) diff --git a/Sources/System/MachPort.swift b/Sources/System/MachPort.swift index b3eb0a99..b3567898 100644 --- a/Sources/System/MachPort.swift +++ b/Sources/System/MachPort.swift @@ -11,10 +11,10 @@ import Darwin.Mach -@available(/*System 1.3.0: macOS 9999, iOS 9999, watchOS 9999, tvOS 9999*/iOS 8, *) +@available(/*System 1.4.0: macOS 9999, iOS 9999, watchOS 9999, tvOS 9999*/iOS 8, *) public protocol MachPortRight {} -@available(/*System 1.3.0: macOS 9999, iOS 9999, watchOS 9999, tvOS 9999*/iOS 8, *) +@available(/*System 1.4.0: macOS 9999, iOS 9999, watchOS 9999, tvOS 9999*/iOS 8, *) @inlinable internal func _machPrecondition( file: StaticString = #file, @@ -26,10 +26,10 @@ internal func _machPrecondition( precondition(kr == expected, file: file, line: line) } -@available(/*System 1.3.0: macOS 9999, iOS 9999, watchOS 9999, tvOS 9999*/iOS 8, *) +@available(/*System 1.4.0: macOS 9999, iOS 9999, watchOS 9999, tvOS 9999*/iOS 8, *) @frozen public enum Mach { - @available(/*System 1.3.0: macOS 9999, iOS 9999, watchOS 9999, tvOS 9999*/iOS 8, *) + @available(/*System 1.4.0: macOS 9999, iOS 9999, watchOS 9999, tvOS 9999*/iOS 8, *) public struct Port: ~Copyable { @usableFromInline internal var _name: mach_port_name_t @@ -129,7 +129,7 @@ public enum Mach { public struct SendOnceRight: MachPortRight {} } -@available(/*System 1.3.0: macOS 9999, iOS 9999, watchOS 9999, tvOS 9999*/iOS 8, *) +@available(/*System 1.4.0: macOS 9999, iOS 9999, watchOS 9999, tvOS 9999*/iOS 8, *) extension Mach.Port where RightType == Mach.ReceiveRight { /// Transfer ownership of an existing, unmanaged, but already guarded, /// Mach port right into a Mach.Port by name. @@ -154,7 +154,7 @@ extension Mach.Port where RightType == Mach.ReceiveRight { /// This initializer will abort if the right could not be created. /// Callers may assert that a valid right is always returned. @inlinable - @available(/*System 1.3.0: macOS 9999, iOS 9999, watchOS 9999, tvOS 9999*/iOS 8, *) + @available(/*System 1.4.0: macOS 9999, iOS 9999, watchOS 9999, tvOS 9999*/iOS 8, *) public init() { var storage: mach_port_name_t = mach_port_name_t(MACH_PORT_NULL) _machPrecondition( @@ -177,7 +177,7 @@ extension Mach.Port where RightType == Mach.ReceiveRight { /// After this function completes, the Mach.Port is destroyed and no longer /// usable. @inlinable - @available(/*System 1.3.0: macOS 9999, iOS 9999, watchOS 9999, tvOS 9999*/iOS 8, *) + @available(/*System 1.4.0: macOS 9999, iOS 9999, watchOS 9999, tvOS 9999*/iOS 8, *) public consuming func relinquish( ) -> (name: mach_port_name_t, context: mach_port_context_t) { let destructured = (name: _name, context: _context) @@ -200,7 +200,7 @@ extension Mach.Port where RightType == Mach.ReceiveRight { /// Mach.ReceiveRights. Use relinquish() to avoid the syscall and extract /// the context value along with the port name. @inlinable - @available(/*System 1.3.0: macOS 9999, iOS 9999, watchOS 9999, tvOS 9999*/iOS 8, *) + @available(/*System 1.4.0: macOS 9999, iOS 9999, watchOS 9999, tvOS 9999*/iOS 8, *) public consuming func unguardAndRelinquish() -> mach_port_name_t { let (name, context) = self.relinquish() _machPrecondition(mach_port_unguard(mach_task_self_, name, context)) @@ -217,7 +217,7 @@ extension Mach.Port where RightType == Mach.ReceiveRight { /// The body block may optionally return something, which will then be /// returned to the caller of withBorrowedName. @inlinable - @available(/*System 1.3.0: macOS 9999, iOS 9999, watchOS 9999, tvOS 9999*/iOS 8, *) + @available(/*System 1.4.0: macOS 9999, iOS 9999, watchOS 9999, tvOS 9999*/iOS 8, *) public func withBorrowedName( body: (mach_port_name_t, mach_port_context_t) -> ReturnType ) -> ReturnType { @@ -231,7 +231,7 @@ extension Mach.Port where RightType == Mach.ReceiveRight { /// This function will abort if the right could not be created. /// Callers may assert that a valid right is always returned. @inlinable - @available(/*System 1.3.0: macOS 9999, iOS 9999, watchOS 9999, tvOS 9999*/iOS 8, *) + @available(/*System 1.4.0: macOS 9999, iOS 9999, watchOS 9999, tvOS 9999*/iOS 8, *) public func makeSendOnceRight() -> Mach.Port { // send once rights do not coalesce var newRight: mach_port_name_t = mach_port_name_t(MACH_PORT_NULL) @@ -260,7 +260,7 @@ extension Mach.Port where RightType == Mach.ReceiveRight { /// This function will abort if the right could not be created. /// Callers may assert that a valid right is always returned. @inlinable - @available(/*System 1.3.0: macOS 9999, iOS 9999, watchOS 9999, tvOS 9999*/iOS 8, *) + @available(/*System 1.4.0: macOS 9999, iOS 9999, watchOS 9999, tvOS 9999*/iOS 8, *) public func makeSendRight() -> Mach.Port { let how = MACH_MSG_TYPE_MAKE_SEND @@ -278,7 +278,7 @@ extension Mach.Port where RightType == Mach.ReceiveRight { /// /// Each get/set of this property makes a syscall. @inlinable - @available(/*System 1.3.0: macOS 9999, iOS 9999, watchOS 9999, tvOS 9999*/iOS 8, *) + @available(/*System 1.4.0: macOS 9999, iOS 9999, watchOS 9999, tvOS 9999*/iOS 8, *) public var makeSendCount: mach_port_mscount_t { get { var status: mach_port_status = mach_port_status() @@ -307,7 +307,7 @@ extension Mach.Port where RightType == Mach.ReceiveRight { } } -@available(/*System 1.3.0: macOS 9999, iOS 9999, watchOS 9999, tvOS 9999*/iOS 8, *) +@available(/*System 1.4.0: macOS 9999, iOS 9999, watchOS 9999, tvOS 9999*/iOS 8, *) extension Mach.Port where RightType == Mach.SendRight { /// Transfer ownership of the underlying port right to the caller. /// @@ -333,7 +333,7 @@ extension Mach.Port where RightType == Mach.SendRight { /// receiving side has been deallocated, then copySendRight() will throw /// a Mach.PortRightError.deadName error. @inlinable - @available(/*System 1.3.0: macOS 9999, iOS 9999, watchOS 9999, tvOS 9999*/iOS 8, *) + @available(/*System 1.4.0: macOS 9999, iOS 9999, watchOS 9999, tvOS 9999*/iOS 8, *) public func copySendRight() throws -> Mach.Port { let how = MACH_MSG_TYPE_COPY_SEND @@ -350,7 +350,7 @@ extension Mach.Port where RightType == Mach.SendRight { } } -@available(/*System 1.3.0: macOS 9999, iOS 9999, watchOS 9999, tvOS 9999*/iOS 8, *) +@available(/*System 1.4.0: macOS 9999, iOS 9999, watchOS 9999, tvOS 9999*/iOS 8, *) extension Mach.Port where RightType == Mach.SendOnceRight { /// Transfer ownership of the underlying port right to the caller. /// @@ -362,7 +362,7 @@ extension Mach.Port where RightType == Mach.SendOnceRight { /// After this function completes, the Mach.Port is destroyed and no longer /// usable. @inlinable - @available(/*System 1.3.0: macOS 9999, iOS 9999, watchOS 9999, tvOS 9999*/iOS 8, *) + @available(/*System 1.4.0: macOS 9999, iOS 9999, watchOS 9999, tvOS 9999*/iOS 8, *) public consuming func relinquish() -> mach_port_name_t { let name = _name discard self diff --git a/Tests/SystemTests/MachPortTests.swift b/Tests/SystemTests/MachPortTests.swift index cba558bf..073da9a5 100644 --- a/Tests/SystemTests/MachPortTests.swift +++ b/Tests/SystemTests/MachPortTests.swift @@ -18,7 +18,7 @@ import SystemPackage import System #endif -@available(/*System 1.3.0: macOS 9999, iOS 9999, watchOS 9999, tvOS 9999*/iOS 8, *) +@available(/*System 1.4.0: macOS 9999, iOS 9999, watchOS 9999, tvOS 9999*/iOS 8, *) final class MachPortTests: XCTestCase { func refCountForMachPortName(name:mach_port_name_t, kind:mach_port_right_t) -> mach_port_urefs_t { var refCount:mach_port_urefs_t = .max diff --git a/Utilities/expand-availability.py b/Utilities/expand-availability.py index 26ec2dea..4cb1a1be 100755 --- a/Utilities/expand-availability.py +++ b/Utilities/expand-availability.py @@ -57,6 +57,7 @@ "System 1.1.0": "macOS 12.3, iOS 15.4, watchOS 8.5, tvOS 15.4", "System 1.2.0": "macOS 9999, iOS 9999, watchOS 9999, tvOS 9999", "System 1.3.0": "macOS 9999, iOS 9999, watchOS 9999, tvOS 9999", + "System 1.4.0": "macOS 9999, iOS 9999, watchOS 9999, tvOS 9999", } parser = argparse.ArgumentParser(description="Expand availability macros.") From e5a56da50e50f0aabf1778f08be679617ef328f7 Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Thu, 9 May 2024 15:57:29 -0700 Subject: [PATCH 197/427] Cherry-pick changes from PR154 --- Sources/System/SystemString.swift | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/Sources/System/SystemString.swift b/Sources/System/SystemString.swift index a918a929..c26d904d 100644 --- a/Sources/System/SystemString.swift +++ b/Sources/System/SystemString.swift @@ -141,31 +141,38 @@ extension SystemString: RangeReplaceableCollection { nullTerminatedStorage.reserveCapacity(1 + n) } - // TODO: Below include null terminator, is this desired? - internal func withContiguousStorageIfAvailable( _ body: (UnsafeBufferPointer) throws -> R ) rethrows -> R? { - try nullTerminatedStorage.withContiguousStorageIfAvailable(body) + // Do not include the null terminator, it is outside the Collection + try nullTerminatedStorage.withContiguousStorageIfAvailable { + try body(.init(start: $0.baseAddress, count: $0.count-1)) + } } internal mutating func withContiguousMutableStorageIfAvailable( _ body: (inout UnsafeMutableBufferPointer) throws -> R ) rethrows -> R? { defer { _invariantCheck() } - return try nullTerminatedStorage.withContiguousMutableStorageIfAvailable(body) + // Do not include the null terminator, it is outside the Collection + return try nullTerminatedStorage.withContiguousMutableStorageIfAvailable { + var buffer = UnsafeMutableBufferPointer( + start: $0.baseAddress, count: $0.count-1 + ) + return try body(&buffer) + } } } extension SystemString: Hashable, Codable {} extension SystemString { - // TODO: Below include null terminator, is this desired? + // withSystemChars includes the null terminator internal func withSystemChars( _ f: (UnsafeBufferPointer) throws -> T ) rethrows -> T { - try withContiguousStorageIfAvailable(f)! + try nullTerminatedStorage.withContiguousStorageIfAvailable(f)! } internal func withCodeUnits( From c3a7235bf7cf5e92e8286e82773c91118734cfcf Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Thu, 9 May 2024 15:32:45 -0700 Subject: [PATCH 198/427] Cherry-pick changes from PR133 Co-authored-by: Max Desiatov --- Sources/System/Internals/CInterop.swift | 9 ++++++--- Sources/System/Internals/Constants.swift | 11 +++++++++-- Sources/System/Internals/Exports.swift | 16 ++++++++++++---- Sources/System/Internals/Syscalls.swift | 4 +++- 4 files changed, 30 insertions(+), 10 deletions(-) diff --git a/Sources/System/Internals/CInterop.swift b/Sources/System/Internals/CInterop.swift index cb84a590..e3cb3f2b 100644 --- a/Sources/System/Internals/CInterop.swift +++ b/Sources/System/Internals/CInterop.swift @@ -16,12 +16,15 @@ public typealias CModeT = CInterop.Mode #if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) import Darwin -#elseif os(Linux) || os(FreeBSD) || os(Android) -@_implementationOnly import CSystem -import Glibc #elseif os(Windows) import CSystem import ucrt +#elseif canImport(Glibc) +@_implementationOnly import CSystem +import Glibc +#elseif canImport(Musl) +@_implementationOnly import CSystem +import Musl #else #error("Unsupported Platform") #endif diff --git a/Sources/System/Internals/Constants.swift b/Sources/System/Internals/Constants.swift index 5489a550..12e42bed 100644 --- a/Sources/System/Internals/Constants.swift +++ b/Sources/System/Internals/Constants.swift @@ -13,11 +13,14 @@ #if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) import Darwin -#elseif os(Linux) || os(FreeBSD) || os(Android) -import Glibc #elseif os(Windows) import CSystem import ucrt +#elseif canImport(Glibc) +import Glibc +#elseif canImport(Musl) +import CSystem +import Musl #else #error("Unsupported Platform") #endif @@ -450,9 +453,13 @@ internal var _O_WRONLY: CInt { O_WRONLY } internal var _O_RDWR: CInt { O_RDWR } #if !os(Windows) +#if canImport(Musl) +internal var _O_ACCMODE: CInt { 0x03|O_SEARCH } +#else // TODO: API? @_alwaysEmitIntoClient internal var _O_ACCMODE: CInt { O_ACCMODE } +#endif @_alwaysEmitIntoClient internal var _O_NONBLOCK: CInt { O_NONBLOCK } diff --git a/Sources/System/Internals/Exports.swift b/Sources/System/Internals/Exports.swift index 5c0a58a6..2879e134 100644 --- a/Sources/System/Internals/Exports.swift +++ b/Sources/System/Internals/Exports.swift @@ -14,12 +14,15 @@ #if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) import Darwin -#elseif os(Linux) || os(FreeBSD) || os(Android) -@_implementationOnly import CSystem -import Glibc #elseif os(Windows) import CSystem import ucrt +#elseif canImport(Glibc) +@_implementationOnly import CSystem +import Glibc +#elseif canImport(Musl) +@_implementationOnly import CSystem +import Musl #else #error("Unsupported Platform") #endif @@ -45,11 +48,16 @@ internal var system_errno: CInt { _ = ucrt._set_errno(newValue) } } -#else +#elseif canImport(Glibc) internal var system_errno: CInt { get { Glibc.errno } set { Glibc.errno = newValue } } +#elseif canImport(Musl) +internal var system_errno: CInt { + get { Musl.errno } + set { Musl.errno = newValue } +} #endif // MARK: C stdlib decls diff --git a/Sources/System/Internals/Syscalls.swift b/Sources/System/Internals/Syscalls.swift index b22d6a3f..85d0e1c9 100644 --- a/Sources/System/Internals/Syscalls.swift +++ b/Sources/System/Internals/Syscalls.swift @@ -9,8 +9,10 @@ #if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) import Darwin -#elseif os(Linux) || os(FreeBSD) || os(Android) +#elseif canImport(Glibc) import Glibc +#elseif canImport(Musl) +import Musl #elseif os(Windows) import ucrt #else From c7259554b5d70f2474d9495b256023a8dec50b89 Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Thu, 9 May 2024 22:00:27 -0700 Subject: [PATCH 199/427] Cherry-pick changes from PR151 Co-authored-by: Hiroshi Yamauchi --- cmake/modules/SwiftSupport.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/modules/SwiftSupport.cmake b/cmake/modules/SwiftSupport.cmake index 82961db3..a73a4e57 100644 --- a/cmake/modules/SwiftSupport.cmake +++ b/cmake/modules/SwiftSupport.cmake @@ -17,7 +17,7 @@ See https://swift.org/LICENSE.txt for license information function(get_swift_host_arch result_var_name) if("${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "x86_64") set("${result_var_name}" "x86_64" PARENT_SCOPE) - elseif ("${CMAKE_SYSTEM_PROCESSOR}" MATCHES "AArch64|aarch64|arm64") + elseif ("${CMAKE_SYSTEM_PROCESSOR}" MATCHES "AArch64|aarch64|arm64|ARM64") if(CMAKE_SYSTEM_NAME MATCHES Darwin) set("${result_var_name}" "arm64" PARENT_SCOPE) else() From 9620c1d30eb666e4b1d15f7c5efa8dfc0bd33b77 Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Thu, 9 May 2024 22:24:04 -0700 Subject: [PATCH 200/427] Cherry-pick changes from PR148 Co-authored-by: Finagolfin --- Sources/System/Internals/Syscalls.swift | 16 +++++++++++++++ Tests/SystemTests/FileOperationsTest.swift | 24 ++++++++++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/Sources/System/Internals/Syscalls.swift b/Sources/System/Internals/Syscalls.swift index 85d0e1c9..04d12db3 100644 --- a/Sources/System/Internals/Syscalls.swift +++ b/Sources/System/Internals/Syscalls.swift @@ -71,7 +71,15 @@ internal func system_pread( #if ENABLE_MOCKING if mockingEnabled { return _mockInt(fd, buf, nbyte, offset) } #endif +#if os(Android) + var zero = UInt8.zero + return withUnsafeMutablePointer(to: &zero) { + // this pread has a non-nullable `buf` pointer + pread(fd, buf ?? UnsafeMutableRawPointer($0), nbyte, offset) + } +#else return pread(fd, buf, nbyte, offset) +#endif } // lseek @@ -101,7 +109,15 @@ internal func system_pwrite( #if ENABLE_MOCKING if mockingEnabled { return _mockInt(fd, buf, nbyte, offset) } #endif +#if os(Android) + var zero = UInt8.zero + return withUnsafeMutablePointer(to: &zero) { + // this pwrite has a non-nullable `buf` pointer + pwrite(fd, buf ?? UnsafeRawPointer($0), nbyte, offset) + } +#else return pwrite(fd, buf, nbyte, offset) +#endif } internal func system_dup(_ fd: Int32) -> Int32 { diff --git a/Tests/SystemTests/FileOperationsTest.swift b/Tests/SystemTests/FileOperationsTest.swift index 419e1c97..fce119fe 100644 --- a/Tests/SystemTests/FileOperationsTest.swift +++ b/Tests/SystemTests/FileOperationsTest.swift @@ -86,6 +86,30 @@ final class FileOperationsTest: XCTestCase { for test in syscallTestCases { test.runAllTests() } } + func testWriteFromEmptyBuffer() throws { + let fd = try FileDescriptor.open(FilePath("/dev/null"), .writeOnly) + let written1 = try fd.write(toAbsoluteOffset: 0, .init(start: nil, count: 0)) + XCTAssertEqual(written1, 0) + + let pointer = UnsafeMutableRawPointer.allocate(byteCount: 8, alignment: 8) + defer { pointer.deallocate() } + let empty = UnsafeRawBufferPointer(start: pointer, count: 0) + let written2 = try fd.write(toAbsoluteOffset: 0, empty) + XCTAssertEqual(written2, 0) + } + + func testReadToEmptyBuffer() throws { + let fd = try FileDescriptor.open(FilePath("/dev/random"), .readOnly) + let read1 = try fd.read(fromAbsoluteOffset: 0, into: .init(start: nil, count: 0)) + XCTAssertEqual(read1, 0) + + let pointer = UnsafeMutableRawPointer.allocate(byteCount: 8, alignment: 8) + defer { pointer.deallocate() } + let empty = UnsafeMutableRawBufferPointer(start: pointer, count: 0) + let read2 = try fd.read(fromAbsoluteOffset: 0, into: empty) + XCTAssertEqual(read2, 0) + } + func testHelpers() { // TODO: Test writeAll, writeAll(toAbsoluteOffset), closeAfter } From b0e2a510edc693e65dd021ad5033bf16f18521b9 Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Fri, 10 May 2024 15:28:43 -0700 Subject: [PATCH 201/427] Make a platform-specific flag explicitly platform-specific --- Package.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Package.swift b/Package.swift index 26bd4e63..fee91e0f 100644 --- a/Package.swift +++ b/Package.swift @@ -27,7 +27,7 @@ let package = Package( dependencies: ["CSystem"], path: "Sources/System", cSettings: [ - .define("_CRT_SECURE_NO_WARNINGS") + .define("_CRT_SECURE_NO_WARNINGS", .when(platforms: [.windows])) ], swiftSettings: [ .define("SYSTEM_PACKAGE_DARWIN", .when(platforms: [.macOS, .iOS, .watchOS, .tvOS, .visionOS])), From 63b05e24e8dac45d075aa4897251918559b53477 Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Fri, 10 May 2024 15:35:29 -0700 Subject: [PATCH 202/427] Improved compatibility for visionOS support --- Package.swift | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/Package.swift b/Package.swift index 26bd4e63..4a3c2c43 100644 --- a/Package.swift +++ b/Package.swift @@ -12,6 +12,13 @@ import PackageDescription +let DarwinPlatforms: [Platform] +#if swift(<5.9) +DarwinPlatforms = [.macOS, .iOS, .watchOS, .tvOS] +#else +DarwinPlatforms = [.macOS, .iOS, .watchOS, .tvOS, .visionOS] +#endif + let package = Package( name: "swift-system", products: [ @@ -30,16 +37,16 @@ let package = Package( .define("_CRT_SECURE_NO_WARNINGS") ], swiftSettings: [ - .define("SYSTEM_PACKAGE_DARWIN", .when(platforms: [.macOS, .iOS, .watchOS, .tvOS, .visionOS])), .define("SYSTEM_PACKAGE"), + .define("SYSTEM_PACKAGE_DARWIN", .when(platforms: DarwinPlatforms)), .define("ENABLE_MOCKING", .when(configuration: .debug)) ]), .testTarget( name: "SystemTests", dependencies: ["SystemPackage"], swiftSettings: [ - .define("SYSTEM_PACKAGE_DARWIN", .when(platforms: [.macOS, .iOS, .watchOS, .tvOS, .visionOS])), - .define("SYSTEM_PACKAGE") + .define("SYSTEM_PACKAGE"), + .define("SYSTEM_PACKAGE_DARWIN", .when(platforms: DarwinPlatforms)), ]) ] ) From 37a5bbb965369c2e1f236729db69b195867fc8f0 Mon Sep 17 00:00:00 2001 From: Karoy Lorentey Date: Fri, 10 May 2024 15:54:56 -0700 Subject: [PATCH 203/427] Require Swift 5.8 or better --- Package.swift | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Package.swift b/Package.swift index b081a872..60976ce2 100644 --- a/Package.swift +++ b/Package.swift @@ -1,10 +1,9 @@ -// swift-tools-version:5.2 -// The swift-tools-version declares the minimum version of Swift required to build this package. +// swift-tools-version:5.8 /* 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-2024 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 From f1f59e148f1706caac25341123af4c45c617b1cc Mon Sep 17 00:00:00 2001 From: Karoy Lorentey Date: Fri, 10 May 2024 15:55:04 -0700 Subject: [PATCH 204/427] Update gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 36bbb6fb..3ca0750e 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ /*.xcodeproj xcuserdata/ .*.sw? +/.swiftpm From c456f4186e21b43e9a9f39f1a3231d5ffd44492e Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Fri, 10 May 2024 16:01:36 -0700 Subject: [PATCH 205/427] Change the package manifest to support visionOS --- Package.swift | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/Package.swift b/Package.swift index 60976ce2..e902aaa4 100644 --- a/Package.swift +++ b/Package.swift @@ -11,6 +11,13 @@ import PackageDescription +let DarwinPlatforms: [Platform] +#if swift(<5.9) +DarwinPlatforms = [.macOS, .iOS, .watchOS, .tvOS] +#else +DarwinPlatforms = [.macOS, .iOS, .watchOS, .tvOS, .visionOS] +#endif + let package = Package( name: "swift-system", products: [ @@ -30,13 +37,15 @@ let package = Package( ], swiftSettings: [ .define("SYSTEM_PACKAGE"), + .define("SYSTEM_PACKAGE_DARWIN", .when(platforms: DarwinPlatforms)), .define("ENABLE_MOCKING", .when(configuration: .debug)) ]), .testTarget( name: "SystemTests", dependencies: ["SystemPackage"], swiftSettings: [ - .define("SYSTEM_PACKAGE") + .define("SYSTEM_PACKAGE"), + .define("SYSTEM_PACKAGE_DARWIN", .when(platforms: DarwinPlatforms)), ]), ] ) From 7b14e48d0dc485b114efaa4129a33cb97286cdd9 Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Fri, 10 May 2024 16:09:22 -0700 Subject: [PATCH 206/427] Remove the repeated list of Darwin-based platforms Replaced with a constant defined in the package manifest. --- Sources/System/Errno.swift | 20 +++++++++--------- Sources/System/FileDescriptor.swift | 10 ++++----- Sources/System/Internals/CInterop.swift | 2 +- Sources/System/Internals/Constants.swift | 26 ++++++++++++------------ Sources/System/Internals/Exports.swift | 4 ++-- Sources/System/Internals/Syscalls.swift | 2 +- Tests/SystemTests/ErrnoTest.swift | 14 ++++++------- Tests/SystemTests/FileTypesTest.swift | 4 ++-- 8 files changed, 41 insertions(+), 41 deletions(-) diff --git a/Sources/System/Errno.swift b/Sources/System/Errno.swift index e2ebadb3..0000f501 100644 --- a/Sources/System/Errno.swift +++ b/Sources/System/Errno.swift @@ -23,7 +23,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { @_alwaysEmitIntoClient private init(_ raw: CInt) { self.init(rawValue: raw) } -#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) +#if SYSTEM_PACKAGE_DARWIN /// Error. Not used. @_alwaysEmitIntoClient public static var notUsed: Errno { Errno(_ERRNO_NOT_USED) } @@ -911,7 +911,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { @available(*, unavailable, renamed: "directoryNotEmpty") public static var ENOTEMPTY: Errno { directoryNotEmpty } -#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) +#if SYSTEM_PACKAGE_DARWIN /// Too many processes. /// /// The corresponding C error is `EPROCLIM`. @@ -968,7 +968,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { public static var ESTALE: Errno { staleNFSFileHandle } // TODO: Add Linux's RPC equivalents -#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) +#if SYSTEM_PACKAGE_DARWIN /// The structure of the remote procedure call (RPC) is bad. /// @@ -1060,7 +1060,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { public static var ENOSYS: Errno { noFunction } // BSD -#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) +#if SYSTEM_PACKAGE_DARWIN /// Inappropriate file type or format. /// /// The file was the wrong type for the operation, @@ -1075,7 +1075,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { public static var EFTYPE: Errno { badFileTypeOrFormat } #endif -#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) +#if SYSTEM_PACKAGE_DARWIN /// Authentication error. /// /// The authentication ticket used to mount an NFS file system was invalid. @@ -1102,7 +1102,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { public static var ENEEDAUTH: Errno { needAuthenticator } #endif -#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) +#if SYSTEM_PACKAGE_DARWIN /// Device power is off. /// /// The corresponding C error is `EPWROFF`. @@ -1142,7 +1142,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { public static var EOVERFLOW: Errno { overflow } #endif -#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) +#if SYSTEM_PACKAGE_DARWIN /// Bad executable or shared library. /// /// The executable or shared library being referenced was malformed. @@ -1246,7 +1246,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { @available(*, unavailable, renamed: "illegalByteSequence") public static var EILSEQ: Errno { illegalByteSequence } -#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) +#if SYSTEM_PACKAGE_DARWIN /// Attribute not found. /// /// The specified extended attribute doesn't exist. @@ -1409,7 +1409,7 @@ extension Errno { @available(*, unavailable, renamed: "tooManyRemoteLevels") public static var EREMOTE: Errno { tooManyRemoteLevels } -#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) +#if SYSTEM_PACKAGE_DARWIN /// No such policy registered. /// /// The corresponding C error is `ENOPOLICY`. @@ -1443,7 +1443,7 @@ extension Errno { public static var EOWNERDEAD: Errno { previousOwnerDied } #endif -#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) +#if SYSTEM_PACKAGE_DARWIN /// Interface output queue is full. /// /// The corresponding C error is `EQFULL`. diff --git a/Sources/System/FileDescriptor.swift b/Sources/System/FileDescriptor.swift index fbb7df30..5ef5ad87 100644 --- a/Sources/System/FileDescriptor.swift +++ b/Sources/System/FileDescriptor.swift @@ -182,7 +182,7 @@ extension FileDescriptor { @available(*, unavailable, renamed: "exclusiveCreate") public static var O_EXCL: OpenOptions { exclusiveCreate } -#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) +#if SYSTEM_PACKAGE_DARWIN /// Indicates that opening the file /// atomically obtains a shared lock on the file. /// @@ -250,7 +250,7 @@ extension FileDescriptor { public static var O_DIRECTORY: OpenOptions { directory } #endif -#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) +#if SYSTEM_PACKAGE_DARWIN /// Indicates that opening the file /// opens symbolic links instead of following them. /// @@ -353,7 +353,7 @@ extension FileDescriptor { // TODO: These are available on some versions of Linux with appropriate // macro defines. -#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) +#if SYSTEM_PACKAGE_DARWIN /// Indicates that the offset should be set /// to the next hole after the specified number of bytes. /// @@ -415,7 +415,7 @@ extension FileDescriptor.SeekOrigin case .start: return "start" case .current: return "current" case .end: return "end" -#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) +#if SYSTEM_PACKAGE_DARWIN case .nextHole: return "nextHole" case .nextData: return "nextData" #endif @@ -434,7 +434,7 @@ extension FileDescriptor.OpenOptions /// A textual representation of the open options. @inline(never) public var description: String { -#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) +#if SYSTEM_PACKAGE_DARWIN let descriptions: [(Element, StaticString)] = [ (.nonBlocking, ".nonBlocking"), (.append, ".append"), diff --git a/Sources/System/Internals/CInterop.swift b/Sources/System/Internals/CInterop.swift index e3cb3f2b..13abc752 100644 --- a/Sources/System/Internals/CInterop.swift +++ b/Sources/System/Internals/CInterop.swift @@ -14,7 +14,7 @@ @available(*, deprecated, renamed: "CInterop.Mode") public typealias CModeT = CInterop.Mode -#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) +#if SYSTEM_PACKAGE_DARWIN import Darwin #elseif os(Windows) import CSystem diff --git a/Sources/System/Internals/Constants.swift b/Sources/System/Internals/Constants.swift index 12e42bed..53e215f7 100644 --- a/Sources/System/Internals/Constants.swift +++ b/Sources/System/Internals/Constants.swift @@ -11,7 +11,7 @@ // they can be used anywhere without imports and without confusion to // unavailable local decls. -#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) +#if SYSTEM_PACKAGE_DARWIN import Darwin #elseif os(Windows) import CSystem @@ -26,7 +26,7 @@ import Musl #endif // MARK: errno -#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) +#if SYSTEM_PACKAGE_DARWIN @_alwaysEmitIntoClient internal var _ERRNO_NOT_USED: CInt { 0 } #endif @@ -272,7 +272,7 @@ internal var _EHOSTUNREACH: CInt { EHOSTUNREACH } @_alwaysEmitIntoClient internal var _ENOTEMPTY: CInt { ENOTEMPTY } -#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) +#if SYSTEM_PACKAGE_DARWIN @_alwaysEmitIntoClient internal var _EPROCLIM: CInt { EPROCLIM } #endif @@ -313,7 +313,7 @@ internal var _EREMOTE: CInt { #endif } -#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) +#if SYSTEM_PACKAGE_DARWIN @_alwaysEmitIntoClient internal var _EBADRPC: CInt { EBADRPC } @@ -336,7 +336,7 @@ internal var _ENOLCK: CInt { ENOLCK } @_alwaysEmitIntoClient internal var _ENOSYS: CInt { ENOSYS } -#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) +#if SYSTEM_PACKAGE_DARWIN @_alwaysEmitIntoClient internal var _EFTYPE: CInt { EFTYPE } @@ -358,7 +358,7 @@ internal var _EDEVERR: CInt { EDEVERR } internal var _EOVERFLOW: CInt { EOVERFLOW } #endif -#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) +#if SYSTEM_PACKAGE_DARWIN @_alwaysEmitIntoClient internal var _EBADEXEC: CInt { EBADEXEC } @@ -386,7 +386,7 @@ internal var _ENOMSG: CInt { ENOMSG } @_alwaysEmitIntoClient internal var _EILSEQ: CInt { EILSEQ } -#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) +#if SYSTEM_PACKAGE_DARWIN @_alwaysEmitIntoClient internal var _ENOATTR: CInt { ENOATTR } #endif @@ -420,7 +420,7 @@ internal var _ETIME: CInt { ETIME } @_alwaysEmitIntoClient internal var _EOPNOTSUPP: CInt { EOPNOTSUPP } -#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) +#if SYSTEM_PACKAGE_DARWIN @_alwaysEmitIntoClient internal var _ENOPOLICY: CInt { ENOPOLICY } #endif @@ -433,7 +433,7 @@ internal var _ENOTRECOVERABLE: CInt { ENOTRECOVERABLE } internal var _EOWNERDEAD: CInt { EOWNERDEAD } #endif -#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) +#if SYSTEM_PACKAGE_DARWIN @_alwaysEmitIntoClient internal var _EQFULL: CInt { EQFULL } @@ -468,7 +468,7 @@ internal var _O_NONBLOCK: CInt { O_NONBLOCK } @_alwaysEmitIntoClient internal var _O_APPEND: CInt { O_APPEND } -#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) +#if SYSTEM_PACKAGE_DARWIN @_alwaysEmitIntoClient internal var _O_SHLOCK: CInt { O_SHLOCK } @@ -494,7 +494,7 @@ internal var _O_TRUNC: CInt { O_TRUNC } @_alwaysEmitIntoClient internal var _O_EXCL: CInt { O_EXCL } -#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) +#if SYSTEM_PACKAGE_DARWIN @_alwaysEmitIntoClient internal var _O_EVTONLY: CInt { O_EVTONLY } #endif @@ -508,7 +508,7 @@ internal var _O_NOCTTY: CInt { O_NOCTTY } internal var _O_DIRECTORY: CInt { O_DIRECTORY } #endif -#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) +#if SYSTEM_PACKAGE_DARWIN @_alwaysEmitIntoClient internal var _O_SYMLINK: CInt { O_SYMLINK } #endif @@ -527,7 +527,7 @@ internal var _SEEK_CUR: CInt { SEEK_CUR } @_alwaysEmitIntoClient internal var _SEEK_END: CInt { SEEK_END } -#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) +#if SYSTEM_PACKAGE_DARWIN @_alwaysEmitIntoClient internal var _SEEK_HOLE: CInt { SEEK_HOLE } diff --git a/Sources/System/Internals/Exports.swift b/Sources/System/Internals/Exports.swift index 2879e134..5b08725c 100644 --- a/Sources/System/Internals/Exports.swift +++ b/Sources/System/Internals/Exports.swift @@ -12,7 +12,7 @@ // TODO: Should CSystem just include all the header files we need? -#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) +#if SYSTEM_PACKAGE_DARWIN import Darwin #elseif os(Windows) import CSystem @@ -31,7 +31,7 @@ internal typealias _COffT = off_t // MARK: syscalls and variables -#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) +#if SYSTEM_PACKAGE_DARWIN internal var system_errno: CInt { get { Darwin.errno } set { Darwin.errno = newValue } diff --git a/Sources/System/Internals/Syscalls.swift b/Sources/System/Internals/Syscalls.swift index 04d12db3..555f63b4 100644 --- a/Sources/System/Internals/Syscalls.swift +++ b/Sources/System/Internals/Syscalls.swift @@ -7,7 +7,7 @@ See https://swift.org/LICENSE.txt for license information */ -#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) +#if SYSTEM_PACKAGE_DARWIN import Darwin #elseif canImport(Glibc) import Glibc diff --git a/Tests/SystemTests/ErrnoTest.swift b/Tests/SystemTests/ErrnoTest.swift index 4bbe88f6..96e7df32 100644 --- a/Tests/SystemTests/ErrnoTest.swift +++ b/Tests/SystemTests/ErrnoTest.swift @@ -84,7 +84,7 @@ final class ErrnoTest: XCTestCase { XCTAssert(EHOSTUNREACH == Errno.noRouteToHost.rawValue) XCTAssert(ENOTEMPTY == Errno.directoryNotEmpty.rawValue) -#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) +#if SYSTEM_PACKAGE_DARWIN XCTAssert(EPROCLIM == Errno.tooManyProcesses.rawValue) #endif @@ -92,7 +92,7 @@ final class ErrnoTest: XCTestCase { XCTAssert(EDQUOT == Errno.diskQuotaExceeded.rawValue) XCTAssert(ESTALE == Errno.staleNFSFileHandle.rawValue) -#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) +#if SYSTEM_PACKAGE_DARWIN XCTAssert(EBADRPC == Errno.rpcUnsuccessful.rawValue) XCTAssert(ERPCMISMATCH == Errno.rpcVersionMismatch.rawValue) XCTAssert(EPROGUNAVAIL == Errno.rpcProgramUnavailable.rawValue) @@ -103,7 +103,7 @@ final class ErrnoTest: XCTestCase { XCTAssert(ENOLCK == Errno.noLocks.rawValue) XCTAssert(ENOSYS == Errno.noFunction.rawValue) -#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) +#if SYSTEM_PACKAGE_DARWIN XCTAssert(EFTYPE == Errno.badFileTypeOrFormat.rawValue) XCTAssert(EAUTH == Errno.authenticationError.rawValue) XCTAssert(ENEEDAUTH == Errno.needAuthenticator.rawValue) @@ -113,7 +113,7 @@ final class ErrnoTest: XCTestCase { XCTAssert(EOVERFLOW == Errno.overflow.rawValue) -#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) +#if SYSTEM_PACKAGE_DARWIN XCTAssert(EBADEXEC == Errno.badExecutable.rawValue) XCTAssert(EBADARCH == Errno.badCPUType.rawValue) XCTAssert(ESHLIBVERS == Errno.sharedLibraryVersionMismatch.rawValue) @@ -125,7 +125,7 @@ final class ErrnoTest: XCTestCase { XCTAssert(ENOMSG == Errno.noMessage.rawValue) XCTAssert(EILSEQ == Errno.illegalByteSequence.rawValue) -#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) +#if SYSTEM_PACKAGE_DARWIN XCTAssert(ENOATTR == Errno.attributeNotFound.rawValue) #endif @@ -144,14 +144,14 @@ final class ErrnoTest: XCTestCase { XCTAssert(ETOOMANYREFS == Errno.tooManyReferences.rawValue) XCTAssert(EREMOTE == Errno.tooManyRemoteLevels.rawValue) -#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) +#if SYSTEM_PACKAGE_DARWIN XCTAssert(ENOPOLICY == Errno.noSuchPolicy.rawValue) #endif XCTAssert(ENOTRECOVERABLE == Errno.notRecoverable.rawValue) XCTAssert(EOWNERDEAD == Errno.previousOwnerDied.rawValue) -#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) +#if SYSTEM_PACKAGE_DARWIN XCTAssert(EQFULL == Errno.outputQueueFull.rawValue) XCTAssert(ELAST == Errno.lastErrnoValue.rawValue) #endif diff --git a/Tests/SystemTests/FileTypesTest.swift b/Tests/SystemTests/FileTypesTest.swift index b8cf4969..8498cf5a 100644 --- a/Tests/SystemTests/FileTypesTest.swift +++ b/Tests/SystemTests/FileTypesTest.swift @@ -38,7 +38,7 @@ final class FileDescriptorTest: XCTestCase { XCTAssertEqual(O_CLOEXEC, FileDescriptor.OpenOptions.closeOnExec.rawValue) // BSD only -#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) +#if SYSTEM_PACKAGE_DARWIN XCTAssertEqual(O_SHLOCK, FileDescriptor.OpenOptions.sharedLock.rawValue) XCTAssertEqual(O_EXLOCK, FileDescriptor.OpenOptions.exclusiveLock.rawValue) XCTAssertEqual(O_SYMLINK, FileDescriptor.OpenOptions.symlink.rawValue) @@ -49,7 +49,7 @@ final class FileDescriptorTest: XCTestCase { XCTAssertEqual(SEEK_CUR, FileDescriptor.SeekOrigin.current.rawValue) XCTAssertEqual(SEEK_END, FileDescriptor.SeekOrigin.end.rawValue) -#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) +#if SYSTEM_PACKAGE_DARWIN XCTAssertEqual(SEEK_HOLE, FileDescriptor.SeekOrigin.nextHole.rawValue) XCTAssertEqual(SEEK_DATA, FileDescriptor.SeekOrigin.nextData.rawValue) #endif From 0368ce547bf0193955fe538eea624233e7815cd1 Mon Sep 17 00:00:00 2001 From: Mishal Shah Date: Fri, 10 May 2024 16:18:08 -0700 Subject: [PATCH 207/427] Add support for Swift CI on release/1.3.0 --- .swiftci/5_10_ubuntu2204 | 5 +++++ .swiftci/5_7_ubuntu2204 | 5 +++++ .swiftci/5_8_ubuntu2204 | 5 +++++ .swiftci/5_9_ubuntu2204 | 5 +++++ .swiftci/nightly_6_0_macos | 5 +++++ .swiftci/nightly_6_0_ubuntu2204 | 5 +++++ .swiftci/nightly_main_macos | 5 +++++ .swiftci/nightly_main_ubuntu2204 | 5 +++++ .swiftci/nightly_main_windows | 7 +++++++ 9 files changed, 47 insertions(+) create mode 100644 .swiftci/5_10_ubuntu2204 create mode 100644 .swiftci/5_7_ubuntu2204 create mode 100644 .swiftci/5_8_ubuntu2204 create mode 100644 .swiftci/5_9_ubuntu2204 create mode 100644 .swiftci/nightly_6_0_macos create mode 100644 .swiftci/nightly_6_0_ubuntu2204 create mode 100644 .swiftci/nightly_main_macos create mode 100644 .swiftci/nightly_main_ubuntu2204 create mode 100644 .swiftci/nightly_main_windows diff --git a/.swiftci/5_10_ubuntu2204 b/.swiftci/5_10_ubuntu2204 new file mode 100644 index 00000000..05546359 --- /dev/null +++ b/.swiftci/5_10_ubuntu2204 @@ -0,0 +1,5 @@ +LinuxSwiftPackageJob { + swift_version_tag = "5.10-jammy" + repo = "swift-system" + branch = "release/1.3.0" +} diff --git a/.swiftci/5_7_ubuntu2204 b/.swiftci/5_7_ubuntu2204 new file mode 100644 index 00000000..b1568a38 --- /dev/null +++ b/.swiftci/5_7_ubuntu2204 @@ -0,0 +1,5 @@ +LinuxSwiftPackageJob { + swift_version_tag = "5.7-jammy" + repo = "swift-system" + branch = "release/1.3.0" +} diff --git a/.swiftci/5_8_ubuntu2204 b/.swiftci/5_8_ubuntu2204 new file mode 100644 index 00000000..141a58ee --- /dev/null +++ b/.swiftci/5_8_ubuntu2204 @@ -0,0 +1,5 @@ +LinuxSwiftPackageJob { + swift_version_tag = "5.8-jammy" + repo = "swift-system" + branch = "release/1.3.0" +} diff --git a/.swiftci/5_9_ubuntu2204 b/.swiftci/5_9_ubuntu2204 new file mode 100644 index 00000000..e834c959 --- /dev/null +++ b/.swiftci/5_9_ubuntu2204 @@ -0,0 +1,5 @@ +LinuxSwiftPackageJob { + swift_version_tag = "5.9-jammy" + repo = "swift-system" + branch = "release/1.3.0" +} diff --git a/.swiftci/nightly_6_0_macos b/.swiftci/nightly_6_0_macos new file mode 100644 index 00000000..62683c2b --- /dev/null +++ b/.swiftci/nightly_6_0_macos @@ -0,0 +1,5 @@ +macOSSwiftPackageJob { + swift_version = "6.0" + repo = "swift-system" + branch = "release/1.3.0" +} diff --git a/.swiftci/nightly_6_0_ubuntu2204 b/.swiftci/nightly_6_0_ubuntu2204 new file mode 100644 index 00000000..431f07cb --- /dev/null +++ b/.swiftci/nightly_6_0_ubuntu2204 @@ -0,0 +1,5 @@ +LinuxSwiftPackageJob { + nightly_docker_tag = "nightly-6.0-jammy" + repo = "swift-system" + branch = "release/1.3.0" +} diff --git a/.swiftci/nightly_main_macos b/.swiftci/nightly_main_macos new file mode 100644 index 00000000..7b3dcd78 --- /dev/null +++ b/.swiftci/nightly_main_macos @@ -0,0 +1,5 @@ +macOSSwiftPackageJob { + swift_version = "main" + repo = "swift-system" + branch = "release/1.3.0" +} diff --git a/.swiftci/nightly_main_ubuntu2204 b/.swiftci/nightly_main_ubuntu2204 new file mode 100644 index 00000000..58c94d57 --- /dev/null +++ b/.swiftci/nightly_main_ubuntu2204 @@ -0,0 +1,5 @@ +LinuxSwiftPackageJob { + nightly_docker_tag = "nightly-jammy" + repo = "swift-system" + branch = "release/1.3.0" +} diff --git a/.swiftci/nightly_main_windows b/.swiftci/nightly_main_windows new file mode 100644 index 00000000..e2d3bf26 --- /dev/null +++ b/.swiftci/nightly_main_windows @@ -0,0 +1,7 @@ +WindowsSwiftPackageWithDockerImageJob { + docker_image = "swiftlang/swift:nightly-windowsservercore-1809" + repo = "swift-system" + branch = "release/1.3.0" + sub_dir = "swift-system" + label = "windows-server-2019" +} From 08800445eb169b9d6ed154d9b8b8e785070342fa Mon Sep 17 00:00:00 2001 From: Mishal Shah Date: Fri, 10 May 2024 16:27:47 -0700 Subject: [PATCH 208/427] Add support for Swift CI on release/1.4.0 --- .swiftci/5_10_ubuntu2204 | 5 +++++ .swiftci/5_7_ubuntu2204 | 5 +++++ .swiftci/5_8_ubuntu2204 | 5 +++++ .swiftci/5_9_ubuntu2204 | 5 +++++ .swiftci/nightly_6_0_macos | 5 +++++ .swiftci/nightly_6_0_ubuntu2204 | 5 +++++ .swiftci/nightly_main_macos | 5 +++++ .swiftci/nightly_main_ubuntu2204 | 5 +++++ .swiftci/nightly_main_windows | 7 +++++++ 9 files changed, 47 insertions(+) create mode 100644 .swiftci/5_10_ubuntu2204 create mode 100644 .swiftci/5_7_ubuntu2204 create mode 100644 .swiftci/5_8_ubuntu2204 create mode 100644 .swiftci/5_9_ubuntu2204 create mode 100644 .swiftci/nightly_6_0_macos create mode 100644 .swiftci/nightly_6_0_ubuntu2204 create mode 100644 .swiftci/nightly_main_macos create mode 100644 .swiftci/nightly_main_ubuntu2204 create mode 100644 .swiftci/nightly_main_windows diff --git a/.swiftci/5_10_ubuntu2204 b/.swiftci/5_10_ubuntu2204 new file mode 100644 index 00000000..deb8d0b3 --- /dev/null +++ b/.swiftci/5_10_ubuntu2204 @@ -0,0 +1,5 @@ +LinuxSwiftPackageJob { + swift_version_tag = "5.10-jammy" + repo = "swift-system" + branch = "release/1.4.0" +} diff --git a/.swiftci/5_7_ubuntu2204 b/.swiftci/5_7_ubuntu2204 new file mode 100644 index 00000000..f9b509b8 --- /dev/null +++ b/.swiftci/5_7_ubuntu2204 @@ -0,0 +1,5 @@ +LinuxSwiftPackageJob { + swift_version_tag = "5.7-jammy" + repo = "swift-system" + branch = "release/1.4.0" +} diff --git a/.swiftci/5_8_ubuntu2204 b/.swiftci/5_8_ubuntu2204 new file mode 100644 index 00000000..2c259fad --- /dev/null +++ b/.swiftci/5_8_ubuntu2204 @@ -0,0 +1,5 @@ +LinuxSwiftPackageJob { + swift_version_tag = "5.8-jammy" + repo = "swift-system" + branch = "release/1.4.0" +} diff --git a/.swiftci/5_9_ubuntu2204 b/.swiftci/5_9_ubuntu2204 new file mode 100644 index 00000000..d61e746b --- /dev/null +++ b/.swiftci/5_9_ubuntu2204 @@ -0,0 +1,5 @@ +LinuxSwiftPackageJob { + swift_version_tag = "5.9-jammy" + repo = "swift-system" + branch = "release/1.4.0" +} diff --git a/.swiftci/nightly_6_0_macos b/.swiftci/nightly_6_0_macos new file mode 100644 index 00000000..55323841 --- /dev/null +++ b/.swiftci/nightly_6_0_macos @@ -0,0 +1,5 @@ +macOSSwiftPackageJob { + swift_version = "6.0" + repo = "swift-system" + branch = "release/1.4.0" +} diff --git a/.swiftci/nightly_6_0_ubuntu2204 b/.swiftci/nightly_6_0_ubuntu2204 new file mode 100644 index 00000000..8499ebb9 --- /dev/null +++ b/.swiftci/nightly_6_0_ubuntu2204 @@ -0,0 +1,5 @@ +LinuxSwiftPackageJob { + nightly_docker_tag = "nightly-6.0-jammy" + repo = "swift-system" + branch = "release/1.4.0" +} diff --git a/.swiftci/nightly_main_macos b/.swiftci/nightly_main_macos new file mode 100644 index 00000000..c4db7230 --- /dev/null +++ b/.swiftci/nightly_main_macos @@ -0,0 +1,5 @@ +macOSSwiftPackageJob { + swift_version = "main" + repo = "swift-system" + branch = "release/1.4.0" +} diff --git a/.swiftci/nightly_main_ubuntu2204 b/.swiftci/nightly_main_ubuntu2204 new file mode 100644 index 00000000..7e1594bc --- /dev/null +++ b/.swiftci/nightly_main_ubuntu2204 @@ -0,0 +1,5 @@ +LinuxSwiftPackageJob { + nightly_docker_tag = "nightly-jammy" + repo = "swift-system" + branch = "release/1.4.0" +} diff --git a/.swiftci/nightly_main_windows b/.swiftci/nightly_main_windows new file mode 100644 index 00000000..be713444 --- /dev/null +++ b/.swiftci/nightly_main_windows @@ -0,0 +1,7 @@ +WindowsSwiftPackageWithDockerImageJob { + docker_image = "swiftlang/swift:nightly-windowsservercore-1809" + repo = "swift-system" + branch = "release/1.4.0" + sub_dir = "swift-system" + label = "windows-server-2019" +} From d18980c9600139046428cae45a7a96e33136a9d0 Mon Sep 17 00:00:00 2001 From: Mishal Shah Date: Fri, 10 May 2024 16:30:41 -0700 Subject: [PATCH 209/427] [CI] Remove support for Swift 5.7 --- .swiftci/5_7_ubuntu2204 | 5 ----- 1 file changed, 5 deletions(-) delete mode 100644 .swiftci/5_7_ubuntu2204 diff --git a/.swiftci/5_7_ubuntu2204 b/.swiftci/5_7_ubuntu2204 deleted file mode 100644 index f9b509b8..00000000 --- a/.swiftci/5_7_ubuntu2204 +++ /dev/null @@ -1,5 +0,0 @@ -LinuxSwiftPackageJob { - swift_version_tag = "5.7-jammy" - repo = "swift-system" - branch = "release/1.4.0" -} From 52f603c6b38d924d63a950aacb6d630979f4b258 Mon Sep 17 00:00:00 2001 From: Mishal Shah Date: Fri, 10 May 2024 16:31:13 -0700 Subject: [PATCH 210/427] [CI] Remove support for Swift 5.7 --- .swiftci/5_7_ubuntu2204 | 5 ----- 1 file changed, 5 deletions(-) delete mode 100644 .swiftci/5_7_ubuntu2204 diff --git a/.swiftci/5_7_ubuntu2204 b/.swiftci/5_7_ubuntu2204 deleted file mode 100644 index b1568a38..00000000 --- a/.swiftci/5_7_ubuntu2204 +++ /dev/null @@ -1,5 +0,0 @@ -LinuxSwiftPackageJob { - swift_version_tag = "5.7-jammy" - repo = "swift-system" - branch = "release/1.3.0" -} From 3433699e6e75dc3a8973f687be99f64ef01a11f2 Mon Sep 17 00:00:00 2001 From: Mishal Shah Date: Fri, 10 May 2024 16:32:21 -0700 Subject: [PATCH 211/427] [CI] Remove support for Swift 5.7 --- .swiftci/5_7_ubuntu2204 | 5 ----- 1 file changed, 5 deletions(-) delete mode 100644 .swiftci/5_7_ubuntu2204 diff --git a/.swiftci/5_7_ubuntu2204 b/.swiftci/5_7_ubuntu2204 deleted file mode 100644 index 8550458a..00000000 --- a/.swiftci/5_7_ubuntu2204 +++ /dev/null @@ -1,5 +0,0 @@ -LinuxSwiftPackageJob { - swift_version_tag = "5.7-jammy" - repo = "swift-system" - branch = "main" -} From 0cf079efeaf1889534f0366546a7fe2116d7918d Mon Sep 17 00:00:00 2001 From: Karoy Lorentey Date: Fri, 10 May 2024 17:05:37 -0700 Subject: [PATCH 212/427] Add CODEOWNERS --- .github/CODEOWNERS | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 .github/CODEOWNERS diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 00000000..70e4f6ca --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,13 @@ +# Lines starting with '#' are comments. +# Each line is a case-sensitive file pattern followed by one or more owners. +# Order is important. The last matching pattern has the most precedence. +# More information: https://docs.github.com/en/articles/about-code-owners +# +# Please mirror the repository's file hierarchy in case-sensitive lexicographic +# order. + +# Default owners +* @glessard @lorentey @milseman + +# Swift CI configuration files +.swiftci/ @shahmishal From a6fd8ba9baeac821c711015fbedc8f21a4b507b6 Mon Sep 17 00:00:00 2001 From: Karoy Lorentey Date: Fri, 10 May 2024 17:05:37 -0700 Subject: [PATCH 213/427] Add CODEOWNERS --- .github/CODEOWNERS | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 .github/CODEOWNERS diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 00000000..70e4f6ca --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,13 @@ +# Lines starting with '#' are comments. +# Each line is a case-sensitive file pattern followed by one or more owners. +# Order is important. The last matching pattern has the most precedence. +# More information: https://docs.github.com/en/articles/about-code-owners +# +# Please mirror the repository's file hierarchy in case-sensitive lexicographic +# order. + +# Default owners +* @glessard @lorentey @milseman + +# Swift CI configuration files +.swiftci/ @shahmishal From 62e62b3a7c47c65e16816fbe6feabb56ae10ea3d Mon Sep 17 00:00:00 2001 From: Karoy Lorentey Date: Fri, 10 May 2024 17:05:37 -0700 Subject: [PATCH 214/427] Add CODEOWNERS --- .github/CODEOWNERS | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 .github/CODEOWNERS diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 00000000..70e4f6ca --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,13 @@ +# Lines starting with '#' are comments. +# Each line is a case-sensitive file pattern followed by one or more owners. +# Order is important. The last matching pattern has the most precedence. +# More information: https://docs.github.com/en/articles/about-code-owners +# +# Please mirror the repository's file hierarchy in case-sensitive lexicographic +# order. + +# Default owners +* @glessard @lorentey @milseman + +# Swift CI configuration files +.swiftci/ @shahmishal From 7d45e0dd46dc505cff02b9a44e24243d2b4354a3 Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Sun, 12 May 2024 11:40:14 -0700 Subject: [PATCH 215/427] Update README.md to refer to 1.3.0 --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 11e57eb2..4c74e3bf 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ To use the `SystemPackage` library in a SwiftPM project, add the following line to the dependencies in your `Package.swift` file: ```swift -.package(url: "https://github.com/apple/swift-system", from: "1.0.0"), +.package(url: "https://github.com/apple/swift-system", from: "1.3.0"), ``` Finally, include `"SystemPackage"` as a dependency for your executable target: @@ -37,7 +37,7 @@ Finally, include `"SystemPackage"` as a dependency for your executable target: let package = Package( // name, platforms, products, etc. dependencies: [ - .package(url: "https://github.com/apple/swift-system", from: "1.0.0"), + .package(url: "https://github.com/apple/swift-system", from: "1.3.0"), // other dependencies ], targets: [ From 25c4b1ad75f842680346035ea42ccabf46fccd2e Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Sun, 12 May 2024 14:07:11 -0700 Subject: [PATCH 216/427] fix a 32-bit overflow issue - ensures compatibility with watchOS --- Sources/System/MachPort.swift | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/Sources/System/MachPort.swift b/Sources/System/MachPort.swift index b3eb0a99..62b5547a 100644 --- a/Sources/System/MachPort.swift +++ b/Sources/System/MachPort.swift @@ -56,8 +56,10 @@ public enum Mach { self._name = name if RightType.self == ReceiveRight.self { - precondition(name != 0xFFFFFFFF /* MACH_PORT_DEAD */, - "Receive rights cannot be dead names") + precondition( + _name != (0xFFFFFFFF as mach_port_name_t) /* MACH_PORT_DEAD */, + "Receive rights cannot be dead names" + ) let secret = mach_port_context_t(arc4random()) _machPrecondition(mach_port_guard(mach_task_self_, name, secret, 0)) @@ -87,8 +89,10 @@ public enum Mach { deinit { if RightType.self == ReceiveRight.self { - precondition(_name != 0xFFFFFFFF /* MACH_PORT_DEAD */, - "Receive rights cannot be dead names") + precondition( + _name != (0xFFFFFFFF as mach_port_name_t) /* MACH_PORT_DEAD */, + "Receive rights cannot be dead names" + ) _machPrecondition( mach_port_destruct(mach_task_self_, _name, 0, _context) ) From 8e09ede76d975ff5aa5edde7043c017c43b20b84 Mon Sep 17 00:00:00 2001 From: Alastair Houghton Date: Mon, 13 May 2024 11:19:43 +0100 Subject: [PATCH 217/427] Remove accidentally public functions. These can't be new API as they haven't been reviewed. --- Sources/System/ErrnoWindows.swift | 2 +- Sources/System/FileOperations.swift | 4 ++-- Sources/System/FilePath/FilePathTemp.swift | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Sources/System/ErrnoWindows.swift b/Sources/System/ErrnoWindows.swift index b94b5778..cc481cb7 100644 --- a/Sources/System/ErrnoWindows.swift +++ b/Sources/System/ErrnoWindows.swift @@ -12,7 +12,7 @@ import WinSDK extension Errno { - public init(windowsError: DWORD) { + internal init(windowsError: DWORD) { self.init(rawValue: _mapWindowsErrorToErrno(windowsError)) } } diff --git a/Sources/System/FileOperations.swift b/Sources/System/FileOperations.swift index ba70e875..34128344 100644 --- a/Sources/System/FileOperations.swift +++ b/Sources/System/FileOperations.swift @@ -516,7 +516,7 @@ extension FilePermissions { /// files or directories. Note that this mask is process-wide, and that /// *getting* it is not thread safe. @_alwaysEmitIntoClient - public static var creationMask: FilePermissions { + internal static var creationMask: FilePermissions { get { let oldMask = _umask(0o22) _ = _umask(oldMask) @@ -536,7 +536,7 @@ extension FilePermissions { /// This is more efficient than reading `creationMask` and restoring it /// afterwards, because of the way reading the creation mask works. @_alwaysEmitIntoClient - public static func withCreationMask( + internal static func withCreationMask( _ permissions: FilePermissions, body: () throws -> R ) rethrows -> R { diff --git a/Sources/System/FilePath/FilePathTemp.swift b/Sources/System/FilePath/FilePathTemp.swift index 7b41d0b8..c0edf95d 100644 --- a/Sources/System/FilePath/FilePathTemp.swift +++ b/Sources/System/FilePath/FilePathTemp.swift @@ -18,7 +18,7 @@ /// Creates a temporary directory with a name based on the given `basename`, /// executes `body`, passing in the path of the created directory, then /// deletes the directory and all of its contents before returning. -public func withTemporaryFilePath( +internal func withTemporaryFilePath( basename: FilePath.Component, _ body: (FilePath) throws -> R ) throws -> R { From c3559ecd6184216b77f46c472b7d247573d42371 Mon Sep 17 00:00:00 2001 From: Alastair Houghton Date: Mon, 13 May 2024 14:18:17 +0100 Subject: [PATCH 218/427] [Windows] Fix a problem with opening files read only. `_O_RDONLY` is zero, so we can't test for it by AND-ing. --- Sources/System/Internals/WindowsSyscallAdapters.swift | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/Sources/System/Internals/WindowsSyscallAdapters.swift b/Sources/System/Internals/WindowsSyscallAdapters.swift index 3e3f9c69..1e35ee97 100644 --- a/Sources/System/Internals/WindowsSyscallAdapters.swift +++ b/Sources/System/Internals/WindowsSyscallAdapters.swift @@ -621,14 +621,15 @@ fileprivate struct DecodedOpenFlags { } dwDesiredAccess = 0 - if (oflag & _O_RDONLY) != 0 { + switch (oflag & (_O_RDONLY|_O_WRONLY|_O_RDWR)) { + case _O_RDONLY: dwDesiredAccess |= DWORD(GENERIC_READ) - } - if (oflag & _O_WRONLY) != 0 { + case _O_WRONLY: dwDesiredAccess |= DWORD(GENERIC_WRITE) - } - if (oflag & _O_RDWR) != 0 { + case _O_RDWR: dwDesiredAccess |= DWORD(GENERIC_READ) | DWORD(GENERIC_WRITE) + default: + break } bInheritHandle = WindowsBool((oflag & _O_NOINHERIT) == 0) From abc0a6d26ffb34d3d4a71244f300648f34f64313 Mon Sep 17 00:00:00 2001 From: Alastair Houghton Date: Mon, 13 May 2024 14:19:33 +0100 Subject: [PATCH 219/427] [Windows] Fix tests. We need to use `@testable` when importing `SystemPackage` in order to let us test the `internal` functions. Also, use `NUL` instead of `/dev/null`, and generate random bytes in a file instead of reading from `/dev/random`. --- Tests/SystemTests/FileOperationsTest.swift | 56 +++++++++++++++---- .../FileOperationsTestWindows.swift | 4 +- .../FilePathTests/FilePathTempTest.swift | 4 +- 3 files changed, 50 insertions(+), 14 deletions(-) diff --git a/Tests/SystemTests/FileOperationsTest.swift b/Tests/SystemTests/FileOperationsTest.swift index be5a67d4..ed05dcf4 100644 --- a/Tests/SystemTests/FileOperationsTest.swift +++ b/Tests/SystemTests/FileOperationsTest.swift @@ -10,9 +10,9 @@ import XCTest #if SYSTEM_PACKAGE -import SystemPackage +@testable import SystemPackage #else -import System +@testable import System #endif @available(/*System 0.0.1: macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0*/iOS 8, *) @@ -87,7 +87,11 @@ final class FileOperationsTest: XCTestCase { } func testWriteFromEmptyBuffer() throws { + #if os(Windows) + let fd = try FileDescriptor.open(FilePath("NUL"), .writeOnly) + #else let fd = try FileDescriptor.open(FilePath("/dev/null"), .writeOnly) + #endif let written1 = try fd.write(toAbsoluteOffset: 0, .init(start: nil, count: 0)) XCTAssertEqual(written1, 0) @@ -98,16 +102,48 @@ final class FileOperationsTest: XCTestCase { XCTAssertEqual(written2, 0) } + #if os(Windows) + // Generate a file containing random bytes; this should not be used + // for cryptography, it's just for testing. + func generateRandomData(at path: FilePath, count: Int) throws { + let fd = try FileDescriptor.open(path, .readWrite, + options: [.create, .truncate]) + defer { + try! fd.close() + } + let data = [UInt8]( + sequence(first: 0, + next: { + _ in UInt8.random(in: UInt8.min...UInt8.max) + }).dropFirst().prefix(count) + ) + + try data.withUnsafeBytes { + _ = try fd.write($0) + } + } + #endif + func testReadToEmptyBuffer() throws { - let fd = try FileDescriptor.open(FilePath("/dev/random"), .readOnly) - let read1 = try fd.read(fromAbsoluteOffset: 0, into: .init(start: nil, count: 0)) - XCTAssertEqual(read1, 0) + try withTemporaryFilePath(basename: "testReadToEmptyBuffer") { path in + #if os(Windows) + // Windows doesn't have an equivalent to /dev/random, so generate + // some random bytes and write them to a file for the next step. + let randomPath = path.appending("random.txt") + try generateRandomData(at: randomPath, count: 16) + let fd = try FileDescriptor.open(randomPath, .readOnly) + #else // !os(Windows) + let fd = try FileDescriptor.open(FilePath("/dev/random"), .readOnly) + #endif + let read1 = try fd.read(fromAbsoluteOffset: 0, into: .init(start: nil, count: 0)) + XCTAssertEqual(read1, 0) - let pointer = UnsafeMutableRawPointer.allocate(byteCount: 8, alignment: 8) - defer { pointer.deallocate() } - let empty = UnsafeMutableRawBufferPointer(start: pointer, count: 0) - let read2 = try fd.read(fromAbsoluteOffset: 0, into: empty) - XCTAssertEqual(read2, 0) + let pointer = UnsafeMutableRawPointer.allocate(byteCount: 8, alignment: 8) + defer { pointer.deallocate() } + let empty = UnsafeMutableRawBufferPointer(start: pointer, count: 0) + let read2 = try fd.read(fromAbsoluteOffset: 0, into: empty) + XCTAssertEqual(read2, 0) + } } func testHelpers() { diff --git a/Tests/SystemTests/FileOperationsTestWindows.swift b/Tests/SystemTests/FileOperationsTestWindows.swift index 35df7312..82030516 100644 --- a/Tests/SystemTests/FileOperationsTestWindows.swift +++ b/Tests/SystemTests/FileOperationsTestWindows.swift @@ -12,9 +12,9 @@ import XCTest #if os(Windows) #if SYSTEM_PACKAGE -import SystemPackage +@testable import SystemPackage #else -import System +@testable import System #endif import WinSDK diff --git a/Tests/SystemTests/FilePathTests/FilePathTempTest.swift b/Tests/SystemTests/FilePathTests/FilePathTempTest.swift index dc8597bb..30d58261 100644 --- a/Tests/SystemTests/FilePathTests/FilePathTempTest.swift +++ b/Tests/SystemTests/FilePathTests/FilePathTempTest.swift @@ -10,9 +10,9 @@ import XCTest #if SYSTEM_PACKAGE -import SystemPackage +@testable import SystemPackage #else -import System +@testable import System #endif final class TemporaryPathTest: XCTestCase { From 082adfe732db95083b238e6eb787a68a4141f593 Mon Sep 17 00:00:00 2001 From: Alastair Houghton Date: Tue, 14 May 2024 14:42:57 +0100 Subject: [PATCH 220/427] [Windows] Add a comment to explain the O_RDONLY/O_WRONLY/O_RDWR logic. The values of these flags on Windows do not overlap, and more particularly, `O_RDONLY` is actually zero on Windows, which means we cannot test for it by AND-ing. --- Sources/System/Internals/WindowsSyscallAdapters.swift | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Sources/System/Internals/WindowsSyscallAdapters.swift b/Sources/System/Internals/WindowsSyscallAdapters.swift index 1e35ee97..048377b1 100644 --- a/Sources/System/Internals/WindowsSyscallAdapters.swift +++ b/Sources/System/Internals/WindowsSyscallAdapters.swift @@ -620,6 +620,9 @@ fileprivate struct DecodedOpenFlags { dwCreationDisposition = DWORD(OPEN_EXISTING) } + // The _O_RDONLY, _O_WRONLY and _O_RDWR flags are non-overlapping + // on Windows; in particular, _O_RDONLY is zero, which means we can't + // test for it by AND-ing. dwDesiredAccess = 0 switch (oflag & (_O_RDONLY|_O_WRONLY|_O_RDWR)) { case _O_RDONLY: From b7dc8e8e41b30079ec09602e44088295599645bd Mon Sep 17 00:00:00 2001 From: Karoy Lorentey Date: Wed, 15 May 2024 14:30:14 -0700 Subject: [PATCH 221/427] [CMake] Restore to working order on Apple platforms --- Sources/System/CMakeLists.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Sources/System/CMakeLists.txt b/Sources/System/CMakeLists.txt index 43958d31..4335200c 100644 --- a/Sources/System/CMakeLists.txt +++ b/Sources/System/CMakeLists.txt @@ -37,6 +37,10 @@ target_sources(SystemPackage PRIVATE target_link_libraries(SystemPackage PUBLIC CSystem) +set(SWIFT_SYSTEM_APPLE_PLATFORMS "Darwin" "iOS" "watchOS" "tvOS" "visionOS") +if(CMAKE_SYSTEM_NAME IN_LIST SWIFT_SYSTEM_APPLE_PLATFORMS) + target_compile_definitions(SystemPackage PRIVATE SYSTEM_PACKAGE_DARWIN) +endif() _install_target(SystemPackage) set_property(GLOBAL APPEND PROPERTY SWIFT_SYSTEM_EXPORTS SystemPackage) From 9c17c8562d0d05b08de19285797c312d6ab8cd3d Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Mon, 3 Jun 2024 02:15:05 +0000 Subject: [PATCH 222/427] Suppress unhandled CMakeLists.txt warning by SwiftPM --- Package.swift | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Package.swift b/Package.swift index 4a3c2c43..5c1e2604 100644 --- a/Package.swift +++ b/Package.swift @@ -28,11 +28,13 @@ let package = Package( targets: [ .target( name: "CSystem", - dependencies: []), + dependencies: [], + exclude: ["CMakeLists.txt"]), .target( name: "SystemPackage", dependencies: ["CSystem"], path: "Sources/System", + exclude: ["CMakeLists.txt"], cSettings: [ .define("_CRT_SECURE_NO_WARNINGS") ], From f9d6b48027fe819a72e50d972431259bd99431cd Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Mon, 3 Jun 2024 02:15:05 +0000 Subject: [PATCH 223/427] Suppress unhandled CMakeLists.txt warning by SwiftPM --- Package.swift | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Package.swift b/Package.swift index e902aaa4..7d02fca0 100644 --- a/Package.swift +++ b/Package.swift @@ -27,11 +27,13 @@ let package = Package( targets: [ .target( name: "CSystem", - dependencies: []), + dependencies: [], + exclude: ["CMakeLists.txt"]), .target( name: "SystemPackage", dependencies: ["CSystem"], path: "Sources/System", + exclude: ["CMakeLists.txt"], cSettings: [ .define("_CRT_SECURE_NO_WARNINGS") ], From 1a8d472f35136451f61612f6e30b962cce2968d6 Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Mon, 3 Jun 2024 02:15:05 +0000 Subject: [PATCH 224/427] Suppress unhandled CMakeLists.txt warning by SwiftPM --- Package.swift | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Package.swift b/Package.swift index 7511a8bc..b135a356 100644 --- a/Package.swift +++ b/Package.swift @@ -21,11 +21,13 @@ let package = Package( targets: [ .target( name: "CSystem", - dependencies: []), + dependencies: [], + exclude: ["CMakeLists.txt"]), .target( name: "SystemPackage", dependencies: ["CSystem"], path: "Sources/System", + exclude: ["CMakeLists.txt"], cSettings: [ .define("_CRT_SECURE_NO_WARNINGS") ], From c1789e0525dfd8f7a1512fcaa0c6917184f136ba Mon Sep 17 00:00:00 2001 From: Neil Jones Date: Tue, 18 Jun 2024 10:45:08 +0900 Subject: [PATCH 225/427] add support for riscv64 --- cmake/modules/SwiftSupport.cmake | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cmake/modules/SwiftSupport.cmake b/cmake/modules/SwiftSupport.cmake index a73a4e57..8f1e2c4d 100644 --- a/cmake/modules/SwiftSupport.cmake +++ b/cmake/modules/SwiftSupport.cmake @@ -45,6 +45,8 @@ function(get_swift_host_arch result_var_name) set("${result_var_name}" "i686" PARENT_SCOPE) elseif("${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "i686") set("${result_var_name}" "i686" PARENT_SCOPE) + elseif("${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "riscv64") + set("${result_var_name}" "riscv64" PARENT_SCOPE) else() message(FATAL_ERROR "Unrecognized architecture on host system: ${CMAKE_SYSTEM_PROCESSOR}") endif() From 02811ea6b428b82523a42e6deb8abce5011ec582 Mon Sep 17 00:00:00 2001 From: Finagolfin Date: Thu, 4 Jul 2024 23:37:25 +0530 Subject: [PATCH 226/427] Import new Android overlay Use Bionic module instead where possible --- Sources/System/Internals/CInterop.swift | 3 +++ Sources/System/Internals/Constants.swift | 2 ++ Sources/System/Internals/Exports.swift | 8 ++++++++ Sources/System/Internals/Syscalls.swift | 2 ++ 4 files changed, 15 insertions(+) diff --git a/Sources/System/Internals/CInterop.swift b/Sources/System/Internals/CInterop.swift index 19cf4d56..80d37d05 100644 --- a/Sources/System/Internals/CInterop.swift +++ b/Sources/System/Internals/CInterop.swift @@ -20,6 +20,9 @@ import Glibc import Musl #elseif canImport(WASILibc) import WASILibc +#elseif canImport(Bionic) +@_implementationOnly import CSystem +import Bionic #else #error("Unsupported Platform") #endif diff --git a/Sources/System/Internals/Constants.swift b/Sources/System/Internals/Constants.swift index 27a145f7..904e5b22 100644 --- a/Sources/System/Internals/Constants.swift +++ b/Sources/System/Internals/Constants.swift @@ -24,6 +24,8 @@ import Musl #elseif canImport(WASILibc) import CSystem import WASILibc +#elseif canImport(Android) +import Android #else #error("Unsupported Platform") #endif diff --git a/Sources/System/Internals/Exports.swift b/Sources/System/Internals/Exports.swift index f4358c5b..d18102a2 100644 --- a/Sources/System/Internals/Exports.swift +++ b/Sources/System/Internals/Exports.swift @@ -25,6 +25,9 @@ import Glibc import Musl #elseif canImport(WASILibc) import WASILibc +#elseif canImport(Android) +@_implementationOnly import CSystem +import Android #else #error("Unsupported Platform") #endif @@ -65,6 +68,11 @@ internal var system_errno: CInt { get { WASILibc.errno } set { WASILibc.errno = newValue } } +#elseif canImport(Android) +internal var system_errno: CInt { + get { Android.errno } + set { Android.errno = newValue } +} #endif // MARK: C stdlib decls diff --git a/Sources/System/Internals/Syscalls.swift b/Sources/System/Internals/Syscalls.swift index f40b1685..6f885be5 100644 --- a/Sources/System/Internals/Syscalls.swift +++ b/Sources/System/Internals/Syscalls.swift @@ -17,6 +17,8 @@ import Musl import WASILibc #elseif os(Windows) import ucrt +#elseif canImport(Android) +import Android #else #error("Unsupported Platform") #endif From 4c96faa6d8c34dcca54944ba37a0582867f358d5 Mon Sep 17 00:00:00 2001 From: Stephen Canon Date: Wed, 17 Jul 2024 12:14:43 -0400 Subject: [PATCH 227/427] Insure that FilePath and SystemString's invariants are enforced when decoding. --- Sources/System/FilePath/FilePath.swift | 24 +++- Sources/System/FilePath/FilePathParsing.swift | 15 ++- Sources/System/SystemString.swift | 28 ++++- .../FilePathTests/FilePathDecodable.swift | 114 ++++++++++++++++++ 4 files changed, 172 insertions(+), 9 deletions(-) create mode 100644 Tests/SystemTests/FilePathTests/FilePathDecodable.swift diff --git a/Sources/System/FilePath/FilePath.swift b/Sources/System/FilePath/FilePath.swift index 13064b5a..1b9dfc20 100644 --- a/Sources/System/FilePath/FilePath.swift +++ b/Sources/System/FilePath/FilePath.swift @@ -59,6 +59,15 @@ public struct FilePath: Sendable { } } +/* + extension FilePath: Hashable, Codable { + + + precondition(_hasRoot == (self.root != nil)) + } + } + */ + @available(/*System 0.0.1: macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0*/iOS 8, *) extension FilePath { /// The length of the file path, excluding the null terminator. @@ -66,4 +75,17 @@ extension FilePath { } @available(/*System 0.0.1: macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0*/iOS 8, *) -extension FilePath: Hashable, Codable {} +extension FilePath: Hashable, Codable { + public init(from decoder: any Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + self._storage = try container.decode(SystemString.self, forKey: ._storage) + guard _invariantsSatisfied() else { + throw DecodingError.dataCorruptedError( + forKey: ._storage, + in: container, + debugDescription: + "Encoding does not satisfy the invariants of FilePath" + ) + } + } +} diff --git a/Sources/System/FilePath/FilePathParsing.swift b/Sources/System/FilePath/FilePathParsing.swift index 0ab4f7ec..c31e6a5b 100644 --- a/Sources/System/FilePath/FilePathParsing.swift +++ b/Sources/System/FilePath/FilePathParsing.swift @@ -375,13 +375,18 @@ extension FilePath { // MARK: - Invariants @available(/*System 0.0.1: macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0*/iOS 8, *) extension FilePath { - internal func _invariantCheck() { - #if DEBUG + internal func _invariantsSatisfied() -> Bool { var normal = self normal._normalizeSeparators() - precondition(self == normal) - precondition(!self._storage._hasTrailingSeparator()) - precondition(_hasRoot == (self.root != nil)) + guard self == normal else { return false } + guard !self._storage._hasTrailingSeparator() else { return false } + guard _hasRoot == (self.root != nil) else { return false } + return true + } + + internal func _invariantCheck() { + #if DEBUG + precondition(_invariantsSatisfied()) #endif // DEBUG } } diff --git a/Sources/System/SystemString.swift b/Sources/System/SystemString.swift index 36b68509..7b629e37 100644 --- a/Sources/System/SystemString.swift +++ b/Sources/System/SystemString.swift @@ -96,10 +96,17 @@ extension SystemString { } extension SystemString { + fileprivate func _invariantsSatisfied() -> Bool { + guard nullTerminatedStorage.last! == .null else { return false } + guard nullTerminatedStorage.firstIndex(of: .null) == length else { + return false + } + return true + } + fileprivate func _invariantCheck() { #if DEBUG - precondition(nullTerminatedStorage.last! == .null) - precondition(nullTerminatedStorage.firstIndex(of: .null) == length) + precondition(_invariantsSatisfied()) #endif // DEBUG } } @@ -165,7 +172,22 @@ extension SystemString: RangeReplaceableCollection { } } -extension SystemString: Hashable, Codable {} +extension SystemString: Hashable, Codable { + public init(from decoder: any Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + self.nullTerminatedStorage = try container.decode( + Storage.self, forKey: .nullTerminatedStorage + ) + guard _invariantsSatisfied() else { + throw DecodingError.dataCorruptedError( + forKey: .nullTerminatedStorage, + in: container, + debugDescription: + "Encoding does not satisfy the invariants of SystemString" + ) + } + } +} extension SystemString { diff --git a/Tests/SystemTests/FilePathTests/FilePathDecodable.swift b/Tests/SystemTests/FilePathTests/FilePathDecodable.swift new file mode 100644 index 00000000..a1a46d93 --- /dev/null +++ b/Tests/SystemTests/FilePathTests/FilePathDecodable.swift @@ -0,0 +1,114 @@ +/* + This source file is part of the Swift System open source project + + Copyright (c)2024 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 + */ + +import XCTest + +#if SYSTEM_PACKAGE +@testable import SystemPackage +#else +@testable import System +#endif + +@available(/*System 0.0.1: macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0*/iOS 8, *) +final class FilePathDecodableTest: XCTestCase { + func testInvalidFilePath() { + // _storage is a valid SystemString, but the invariants of FilePath are + // violated. + let input: [UInt8] = [ + 123, 34, 95,115,116,111,114, 97,103,101, 34, 58,123, 34,110,117,108,108, + 84,101,114,109,105,110, 97,116,101,100, 83,116,111,114, 97,103,101, 34, + 58, 91, 49, 48, 57, 44, 45, 55, 54, 44, 53, 53, 44, 55, 49, 44, 49, 52, + 44, 53, 57, 44, 45, 49, 49, 50, 44, 45, 56, 52, 44, 52, 50, 44, 45, 55, + 48, 44, 45, 49, 48, 52, 44, 55, 51, 44, 45, 54, 44, 50, 44, 53, 55, 44, + 54, 50, 44, 45, 56, 55, 44, 45, 53, 44, 45, 54, 53, 44, 45, 51, 57, 44, + 45, 49, 48, 57, 44, 45, 55, 54, 44, 51, 48, 44, 53, 50, 44, 45, 56, 50, + 44, 45, 54, 48, 44, 45, 50, 44, 56, 53, 44, 49, 50, 51, 44, 45, 56, 52, + 44, 45, 53, 56, 44, 49, 49, 52, 44, 49, 44, 45, 49, 49, 54, 44, 56, 48, + 44, 49, 48, 52, 44, 45, 55, 56, 44, 45, 52, 53, 44, 49, 54, 44, 45, 52, + 54, 44, 55, 44, 49, 49, 56, 44, 45, 50, 52, 44, 54, 50, 44, 54, 52, 44, + 45, 52, 49, 44, 45, 49, 48, 51, 44, 53, 44, 45, 55, 53, 44, 50, 50, 44, + 45, 49, 48, 53, 44, 45, 49, 54, 44, 52, 55, 44, 52, 55, 44, 49, 50, 52, + 44, 45, 53, 55, 44, 53, 51, 44, 49, 49, 49, 44, 49, 53, 44, 45, 50, 55, + 44, 54, 54, 44, 45, 49, 54, 44, 49, 48, 50, 44, 49, 48, 54, 44, 49, 51, + 44, 49, 48, 53, 44, 45, 49, 49, 50, 44, 55, 56, 44, 45, 53, 48, 44, 50, + 48, 44, 56, 44, 45, 50, 55, 44, 52, 52, 44, 52, 44, 56, 44, 54, 53, 44, + 50, 51, 44, 57, 55, 44, 45, 50, 56, 44, 56, 56, 44, 52, 50, 44, 45, 51, + 54, 44, 45, 50, 51, 44, 49, 48, 51, 44, 57, 57, 44, 45, 53, 56, 44, 45, + 49, 49, 48, 44, 45, 53, 52, 44, 45, 49, 49, 55, 44, 45, 57, 52, 44, 45, + 55, 50, 44, 50, 57, 44, 45, 50, 52, 44, 45, 56, 52, 44, 53, 55, 44, 45, + 49, 50, 54, 44, 52, 52, 44, 55, 53, 44, 55, 54, 44, 52, 57, 44, 45, 52, + 49, 44, 45, 50, 53, 44, 50, 52, 44, 45, 49, 50, 54, 44, 55, 44, 50, 56, + 44, 45, 52, 56, 44, 56, 55, 44, 51, 49, 44, 45, 49, 49, 53, 44, 55, 44, + 45, 54, 48, 44, 53, 57, 44, 49, 51, 44, 55, 57, 44, 53, 48, 44, 45, 57, + 54, 44, 45, 50, 44, 45, 50, 52, 44, 45, 57, 49, 44, 55, 49, 44, 45, 49, + 50, 53, 44, 52, 50, 44, 45, 56, 52, 44, 52, 44, 53, 57, 44, 49, 50, 53, + 44, 49, 50, 49, 44, 45, 50, 54, 44, 45, 49, 50, 44, 45, 49, 48, 53, 44, + 53, 54, 44, 49, 49, 48, 44, 49, 52, 44, 45, 49, 48, 52, 44, 45, 53, 50, + 44, 45, 53, 56, 44, 45, 54, 44, 45, 50, 54, 44, 45, 52, 55, 44, 53, 57, + 44, 52, 50, 44, 49, 50, 51, 44, 52, 52, 44, 45, 57, 50, 44, 45, 50, 57, + 44, 45, 51, 54, 44, 45, 54, 50, 44, 50, 54, 44, 45, 49, 55, 44, 45, 49, + 48, 44, 45, 56, 49, 44, 54, 49, 44, 52, 55, 44, 45, 57, 52, 44, 45, 49, + 48, 54, 44, 49, 53, 44, 49, 48, 48, 44, 45, 49, 50, 49, 44, 45, 49, 49, + 49, 44, 51, 44, 45, 57, 44, 52, 54, 44, 45, 55, 48, 44, 45, 49, 57, 44, + 52, 56, 44, 45, 49, 50, 44, 45, 57, 49, 44, 45, 50, 48, 44, 49, 51, 44, + 54, 53, 44, 45, 55, 48, 44, 52, 49, 44, 45, 57, 53, 44, 49, 48, 52, 44, + 45, 55, 53, 44, 45, 49, 49, 53, 44, 49, 48, 49, 44, 45, 57, 52, 44, 45, + 49, 50, 51, 44, 45, 51, 53, 44, 45, 50, 49, 44, 45, 52, 50, 44, 45, 51, + 48, 44, 45, 55, 49, 44, 45, 49, 49, 57, 44, 52, 52, 44, 49, 49, 49, 44, + 49, 48, 53, 44, 54, 54, 44, 45, 49, 50, 54, 44, 55, 50, 44, 45, 52, 48, + 44, 49, 50, 49, 44, 45, 50, 49, 44, 52, 50, 44, 45, 55, 56, 44, 49, 50, + 54, 44, 56, 49, 44, 45, 57, 52, 44, 55, 52, 44, 49, 49, 50, 44, 45, 56, + 54, 44, 51, 50, 44, 55, 54, 44, 49, 49, 55, 44, 45, 56, 44, 56, 54, 44, + 49, 48, 51, 44, 54, 50, 44, 49, 49, 55, 44, 54, 55, 44, 45, 56, 54, 44, + 45, 49, 48, 48, 44, 45, 49, 48, 57, 44, 45, 53, 52, 44, 45, 51, 49, 44, + 45, 56, 57, 44, 48, 93,125,125, + ] + + XCTAssertThrowsError(try JSONDecoder().decode( + FilePath.self, + from: Data(input) + )) + } + + func testInvalidSystemString() { + // _storage is a SystemString whose invariants are violated + let input: [UInt8] = [ + 123, 34, 95,115,116,111,114, 97,103,101, 34, 58,123, 34,110,117,108,108, + 84,101,114,109,105,110, 97,116,101,100, 83,116,111,114, 97,103,101, 34, + 58, 91, 49, 49, 49, 44, 48, 44, 45, 49, 54, 44, 57, 49, 44, 52, 54, 44, + 45, 49, 48, 50, 44, 49, 49, 53, 44, 45, 50, 49, 44, 45, 49, 49, 56, 44, + 52, 57, 44, 57, 50, 44, 45, 49, 48, 44, 53, 56, 44, 45, 55, 48, 44, 57, + 55, 44, 56, 44, 57, 57, 44, 48, 93,125, 125 + ] + + XCTAssertThrowsError(try JSONDecoder().decode( + FilePath.self, + from: Data(input) + )) + } + + func testInvalidExample() { + // Another misformed example from Johannes that violates FilePath's + // invariants + let input: [UInt8] = [ + 123, 34, 95,115,116,111,114, 97,103,101, 34, 58,123, 34,110,117,108,108, + 84,101,114,109,105,110, 97,116,101,100, 83,116,111,114, 97,103,101, 34, + 58, 91, 56, 55, 44, 50, 52, 44, 45, 49, 49, 53, 44, 45, 49, 57, 44, 49, + 50, 50, 44, 45, 54, 56, 44, 57, 49, 44, 45, 49, 48, 54, 44, 45, 49, 48, + 48, 44, 45, 49, 49, 52, 44, 53, 54, 44, 45, 54, 53, 44, 49, 49, 56, 44, + 45, 54, 48, 44, 54, 54, 44, 45, 52, 50, 44, 55, 55, 44, 45, 54, 44, 45, + 52, 50, 44, 45, 56, 56, 44, 52, 55, 44, 48, 93,125, 125 + ] + + XCTAssertThrowsError(try JSONDecoder().decode( + FilePath.self, + from: Data(input) + )) + } +} From c64775ef71100699f67aac928b3ec487d9f1a06f Mon Sep 17 00:00:00 2001 From: Stephen Canon Date: Wed, 17 Jul 2024 12:25:34 -0400 Subject: [PATCH 228/427] Another invariant to check --- Sources/System/SystemString.swift | 1 + .../FilePathTests/FilePathDecodable.swift | 13 +++++++++++++ 2 files changed, 14 insertions(+) diff --git a/Sources/System/SystemString.swift b/Sources/System/SystemString.swift index 7b629e37..ab703347 100644 --- a/Sources/System/SystemString.swift +++ b/Sources/System/SystemString.swift @@ -97,6 +97,7 @@ extension SystemString { extension SystemString { fileprivate func _invariantsSatisfied() -> Bool { + guard !nullTerminatedStorage.isEmpty else { return false } guard nullTerminatedStorage.last! == .null else { return false } guard nullTerminatedStorage.firstIndex(of: .null) == length else { return false diff --git a/Tests/SystemTests/FilePathTests/FilePathDecodable.swift b/Tests/SystemTests/FilePathTests/FilePathDecodable.swift index a1a46d93..a9d687da 100644 --- a/Tests/SystemTests/FilePathTests/FilePathDecodable.swift +++ b/Tests/SystemTests/FilePathTests/FilePathDecodable.swift @@ -111,4 +111,17 @@ final class FilePathDecodableTest: XCTestCase { from: Data(input) )) } + + func testEmptyString() { + let input: [UInt8] = [ + 123, 34, 95,115,116,111,114, 97,103,101, 34, 58,123, 34,110,117,108,108, + 84,101,114,109,105,110, 97,116,101,100, 83,116,111,114, 97,103,101, 34, + 58, 91, 93,125,125 + ] + + XCTAssertThrowsError(try JSONDecoder().decode( + FilePath.self, + from: Data(input) + )) + } } From 3b43b391b5b9a2bf86f14aa8d4b34d37fbbbd0d1 Mon Sep 17 00:00:00 2001 From: Stephen Canon Date: Wed, 17 Jul 2024 12:49:37 -0400 Subject: [PATCH 229/427] Add some comments and remove leftover WIP noise. --- Sources/System/FilePath/FilePath.swift | 14 +++++--------- Sources/System/SystemString.swift | 5 +++++ 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/Sources/System/FilePath/FilePath.swift b/Sources/System/FilePath/FilePath.swift index 1b9dfc20..67752c06 100644 --- a/Sources/System/FilePath/FilePath.swift +++ b/Sources/System/FilePath/FilePath.swift @@ -59,15 +59,6 @@ public struct FilePath: Sendable { } } -/* - extension FilePath: Hashable, Codable { - - - precondition(_hasRoot == (self.root != nil)) - } - } - */ - @available(/*System 0.0.1: macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0*/iOS 8, *) extension FilePath { /// The length of the file path, excluding the null terminator. @@ -76,6 +67,11 @@ extension FilePath { @available(/*System 0.0.1: macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0*/iOS 8, *) extension FilePath: Hashable, Codable { + // Encoder is synthesized; it probably should have been explicit and used + // a single-value container, but making that change now is somewhat risky. + + // Decoder is written explicitly to ensure that we validate invariants on + // untrusted input. public init(from decoder: any Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) self._storage = try container.decode(SystemString.self, forKey: ._storage) diff --git a/Sources/System/SystemString.swift b/Sources/System/SystemString.swift index ab703347..57abc625 100644 --- a/Sources/System/SystemString.swift +++ b/Sources/System/SystemString.swift @@ -174,6 +174,11 @@ extension SystemString: RangeReplaceableCollection { } extension SystemString: Hashable, Codable { + // Encoder is synthesized; it probably should have been explicit and used + // a single-value container, but making that change now is somewhat risky. + + // Decoder is written explicitly to ensure that we validate invariants on + // untrusted input. public init(from decoder: any Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) self.nullTerminatedStorage = try container.decode( From 3841a7531f8a1f177dc6825fee207fcf83ff69fc Mon Sep 17 00:00:00 2001 From: Stephen Canon Date: Wed, 17 Jul 2024 13:49:15 -0400 Subject: [PATCH 230/427] Add some notes explaining what invariants are violated. --- Tests/SystemTests/FilePathTests/FilePathDecodable.swift | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Tests/SystemTests/FilePathTests/FilePathDecodable.swift b/Tests/SystemTests/FilePathTests/FilePathDecodable.swift index a9d687da..290bef2f 100644 --- a/Tests/SystemTests/FilePathTests/FilePathDecodable.swift +++ b/Tests/SystemTests/FilePathTests/FilePathDecodable.swift @@ -19,7 +19,7 @@ import XCTest final class FilePathDecodableTest: XCTestCase { func testInvalidFilePath() { // _storage is a valid SystemString, but the invariants of FilePath are - // violated. + // violated (specifically, _storage is not normal). let input: [UInt8] = [ 123, 34, 95,115,116,111,114, 97,103,101, 34, 58,123, 34,110,117,108,108, 84,101,114,109,105,110, 97,116,101,100, 83,116,111,114, 97,103,101, 34, @@ -77,7 +77,8 @@ final class FilePathDecodableTest: XCTestCase { } func testInvalidSystemString() { - // _storage is a SystemString whose invariants are violated + // _storage is a SystemString whose invariants are violated; it contains + // a non-terminating null byte. let input: [UInt8] = [ 123, 34, 95,115,116,111,114, 97,103,101, 34, 58,123, 34,110,117,108,108, 84,101,114,109,105,110, 97,116,101,100, 83,116,111,114, 97,103,101, 34, @@ -95,7 +96,7 @@ final class FilePathDecodableTest: XCTestCase { func testInvalidExample() { // Another misformed example from Johannes that violates FilePath's - // invariants + // invariants by virtue of not being normalized. let input: [UInt8] = [ 123, 34, 95,115,116,111,114, 97,103,101, 34, 58,123, 34,110,117,108,108, 84,101,114,109,105,110, 97,116,101,100, 83,116,111,114, 97,103,101, 34, @@ -113,6 +114,7 @@ final class FilePathDecodableTest: XCTestCase { } func testEmptyString() { + // FilePath with an empty (and hence not null-terminated) SystemString. let input: [UInt8] = [ 123, 34, 95,115,116,111,114, 97,103,101, 34, 58,123, 34,110,117,108,108, 84,101,114,109,105,110, 97,116,101,100, 83,116,111,114, 97,103,101, 34, From a1f77d0e1ab7c70871fb175091efc248b557a46b Mon Sep 17 00:00:00 2001 From: Stephen Canon Date: Wed, 17 Jul 2024 14:49:19 -0400 Subject: [PATCH 231/427] Cherry-pick: ensure invariants are enforced when decoding Ensure that FilePath and SystemString's invariants are enforced when decoding. Cherry-pick of merge-commit 9c42c54644f7bd78b8f2f3715060a01356e2a21d from PR #189 # Conflicts: # Sources/System/FilePath/FilePath.swift --- Sources/System/FilePath/FilePath.swift | 21 ++- Sources/System/FilePath/FilePathParsing.swift | 15 +- Sources/System/SystemString.swift | 34 ++++- .../FilePathTests/FilePathDecodable.swift | 129 ++++++++++++++++++ 4 files changed, 189 insertions(+), 10 deletions(-) create mode 100644 Tests/SystemTests/FilePathTests/FilePathDecodable.swift diff --git a/Sources/System/FilePath/FilePath.swift b/Sources/System/FilePath/FilePath.swift index 1aaa27ec..54eed38f 100644 --- a/Sources/System/FilePath/FilePath.swift +++ b/Sources/System/FilePath/FilePath.swift @@ -67,5 +67,22 @@ extension FilePath { } /*System 0.0.1, @available(macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0, *)*/ -extension FilePath: Hashable, Codable {} - +extension FilePath: Hashable, Codable { + // Encoder is synthesized; it probably should have been explicit and used + // a single-value container, but making that change now is somewhat risky. + + // Decoder is written explicitly to ensure that we validate invariants on + // untrusted input. + public init(from decoder: any Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + self._storage = try container.decode(SystemString.self, forKey: ._storage) + guard _invariantsSatisfied() else { + throw DecodingError.dataCorruptedError( + forKey: ._storage, + in: container, + debugDescription: + "Encoding does not satisfy the invariants of FilePath" + ) + } + } +} diff --git a/Sources/System/FilePath/FilePathParsing.swift b/Sources/System/FilePath/FilePathParsing.swift index 26753989..dff0409a 100644 --- a/Sources/System/FilePath/FilePathParsing.swift +++ b/Sources/System/FilePath/FilePathParsing.swift @@ -359,13 +359,18 @@ extension FilePath { // MARK: - Invariants extension FilePath { - internal func _invariantCheck() { - #if DEBUG + internal func _invariantsSatisfied() -> Bool { var normal = self normal._normalizeSeparators() - precondition(self == normal) - precondition(!self._storage._hasTrailingSeparator()) - precondition(_hasRoot == (self.root != nil)) + guard self == normal else { return false } + guard !self._storage._hasTrailingSeparator() else { return false } + guard _hasRoot == (self.root != nil) else { return false } + return true + } + + internal func _invariantCheck() { + #if DEBUG + precondition(_invariantsSatisfied()) #endif // DEBUG } } diff --git a/Sources/System/SystemString.swift b/Sources/System/SystemString.swift index c26d904d..54d12699 100644 --- a/Sources/System/SystemString.swift +++ b/Sources/System/SystemString.swift @@ -95,10 +95,18 @@ extension SystemString { } extension SystemString { + fileprivate func _invariantsSatisfied() -> Bool { + guard !nullTerminatedStorage.isEmpty else { return false } + guard nullTerminatedStorage.last! == .null else { return false } + guard nullTerminatedStorage.firstIndex(of: .null) == length else { + return false + } + return true + } + fileprivate func _invariantCheck() { #if DEBUG - precondition(nullTerminatedStorage.last! == .null) - precondition(nullTerminatedStorage.firstIndex(of: .null) == length) + precondition(_invariantsSatisfied()) #endif // DEBUG } } @@ -164,7 +172,27 @@ extension SystemString: RangeReplaceableCollection { } } -extension SystemString: Hashable, Codable {} +extension SystemString: Hashable, Codable { + // Encoder is synthesized; it probably should have been explicit and used + // a single-value container, but making that change now is somewhat risky. + + // Decoder is written explicitly to ensure that we validate invariants on + // untrusted input. + public init(from decoder: any Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + self.nullTerminatedStorage = try container.decode( + Storage.self, forKey: .nullTerminatedStorage + ) + guard _invariantsSatisfied() else { + throw DecodingError.dataCorruptedError( + forKey: .nullTerminatedStorage, + in: container, + debugDescription: + "Encoding does not satisfy the invariants of SystemString" + ) + } + } +} extension SystemString { diff --git a/Tests/SystemTests/FilePathTests/FilePathDecodable.swift b/Tests/SystemTests/FilePathTests/FilePathDecodable.swift new file mode 100644 index 00000000..290bef2f --- /dev/null +++ b/Tests/SystemTests/FilePathTests/FilePathDecodable.swift @@ -0,0 +1,129 @@ +/* + This source file is part of the Swift System open source project + + Copyright (c)2024 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 + */ + +import XCTest + +#if SYSTEM_PACKAGE +@testable import SystemPackage +#else +@testable import System +#endif + +@available(/*System 0.0.1: macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0*/iOS 8, *) +final class FilePathDecodableTest: XCTestCase { + func testInvalidFilePath() { + // _storage is a valid SystemString, but the invariants of FilePath are + // violated (specifically, _storage is not normal). + let input: [UInt8] = [ + 123, 34, 95,115,116,111,114, 97,103,101, 34, 58,123, 34,110,117,108,108, + 84,101,114,109,105,110, 97,116,101,100, 83,116,111,114, 97,103,101, 34, + 58, 91, 49, 48, 57, 44, 45, 55, 54, 44, 53, 53, 44, 55, 49, 44, 49, 52, + 44, 53, 57, 44, 45, 49, 49, 50, 44, 45, 56, 52, 44, 52, 50, 44, 45, 55, + 48, 44, 45, 49, 48, 52, 44, 55, 51, 44, 45, 54, 44, 50, 44, 53, 55, 44, + 54, 50, 44, 45, 56, 55, 44, 45, 53, 44, 45, 54, 53, 44, 45, 51, 57, 44, + 45, 49, 48, 57, 44, 45, 55, 54, 44, 51, 48, 44, 53, 50, 44, 45, 56, 50, + 44, 45, 54, 48, 44, 45, 50, 44, 56, 53, 44, 49, 50, 51, 44, 45, 56, 52, + 44, 45, 53, 56, 44, 49, 49, 52, 44, 49, 44, 45, 49, 49, 54, 44, 56, 48, + 44, 49, 48, 52, 44, 45, 55, 56, 44, 45, 52, 53, 44, 49, 54, 44, 45, 52, + 54, 44, 55, 44, 49, 49, 56, 44, 45, 50, 52, 44, 54, 50, 44, 54, 52, 44, + 45, 52, 49, 44, 45, 49, 48, 51, 44, 53, 44, 45, 55, 53, 44, 50, 50, 44, + 45, 49, 48, 53, 44, 45, 49, 54, 44, 52, 55, 44, 52, 55, 44, 49, 50, 52, + 44, 45, 53, 55, 44, 53, 51, 44, 49, 49, 49, 44, 49, 53, 44, 45, 50, 55, + 44, 54, 54, 44, 45, 49, 54, 44, 49, 48, 50, 44, 49, 48, 54, 44, 49, 51, + 44, 49, 48, 53, 44, 45, 49, 49, 50, 44, 55, 56, 44, 45, 53, 48, 44, 50, + 48, 44, 56, 44, 45, 50, 55, 44, 52, 52, 44, 52, 44, 56, 44, 54, 53, 44, + 50, 51, 44, 57, 55, 44, 45, 50, 56, 44, 56, 56, 44, 52, 50, 44, 45, 51, + 54, 44, 45, 50, 51, 44, 49, 48, 51, 44, 57, 57, 44, 45, 53, 56, 44, 45, + 49, 49, 48, 44, 45, 53, 52, 44, 45, 49, 49, 55, 44, 45, 57, 52, 44, 45, + 55, 50, 44, 50, 57, 44, 45, 50, 52, 44, 45, 56, 52, 44, 53, 55, 44, 45, + 49, 50, 54, 44, 52, 52, 44, 55, 53, 44, 55, 54, 44, 52, 57, 44, 45, 52, + 49, 44, 45, 50, 53, 44, 50, 52, 44, 45, 49, 50, 54, 44, 55, 44, 50, 56, + 44, 45, 52, 56, 44, 56, 55, 44, 51, 49, 44, 45, 49, 49, 53, 44, 55, 44, + 45, 54, 48, 44, 53, 57, 44, 49, 51, 44, 55, 57, 44, 53, 48, 44, 45, 57, + 54, 44, 45, 50, 44, 45, 50, 52, 44, 45, 57, 49, 44, 55, 49, 44, 45, 49, + 50, 53, 44, 52, 50, 44, 45, 56, 52, 44, 52, 44, 53, 57, 44, 49, 50, 53, + 44, 49, 50, 49, 44, 45, 50, 54, 44, 45, 49, 50, 44, 45, 49, 48, 53, 44, + 53, 54, 44, 49, 49, 48, 44, 49, 52, 44, 45, 49, 48, 52, 44, 45, 53, 50, + 44, 45, 53, 56, 44, 45, 54, 44, 45, 50, 54, 44, 45, 52, 55, 44, 53, 57, + 44, 52, 50, 44, 49, 50, 51, 44, 52, 52, 44, 45, 57, 50, 44, 45, 50, 57, + 44, 45, 51, 54, 44, 45, 54, 50, 44, 50, 54, 44, 45, 49, 55, 44, 45, 49, + 48, 44, 45, 56, 49, 44, 54, 49, 44, 52, 55, 44, 45, 57, 52, 44, 45, 49, + 48, 54, 44, 49, 53, 44, 49, 48, 48, 44, 45, 49, 50, 49, 44, 45, 49, 49, + 49, 44, 51, 44, 45, 57, 44, 52, 54, 44, 45, 55, 48, 44, 45, 49, 57, 44, + 52, 56, 44, 45, 49, 50, 44, 45, 57, 49, 44, 45, 50, 48, 44, 49, 51, 44, + 54, 53, 44, 45, 55, 48, 44, 52, 49, 44, 45, 57, 53, 44, 49, 48, 52, 44, + 45, 55, 53, 44, 45, 49, 49, 53, 44, 49, 48, 49, 44, 45, 57, 52, 44, 45, + 49, 50, 51, 44, 45, 51, 53, 44, 45, 50, 49, 44, 45, 52, 50, 44, 45, 51, + 48, 44, 45, 55, 49, 44, 45, 49, 49, 57, 44, 52, 52, 44, 49, 49, 49, 44, + 49, 48, 53, 44, 54, 54, 44, 45, 49, 50, 54, 44, 55, 50, 44, 45, 52, 48, + 44, 49, 50, 49, 44, 45, 50, 49, 44, 52, 50, 44, 45, 55, 56, 44, 49, 50, + 54, 44, 56, 49, 44, 45, 57, 52, 44, 55, 52, 44, 49, 49, 50, 44, 45, 56, + 54, 44, 51, 50, 44, 55, 54, 44, 49, 49, 55, 44, 45, 56, 44, 56, 54, 44, + 49, 48, 51, 44, 54, 50, 44, 49, 49, 55, 44, 54, 55, 44, 45, 56, 54, 44, + 45, 49, 48, 48, 44, 45, 49, 48, 57, 44, 45, 53, 52, 44, 45, 51, 49, 44, + 45, 56, 57, 44, 48, 93,125,125, + ] + + XCTAssertThrowsError(try JSONDecoder().decode( + FilePath.self, + from: Data(input) + )) + } + + func testInvalidSystemString() { + // _storage is a SystemString whose invariants are violated; it contains + // a non-terminating null byte. + let input: [UInt8] = [ + 123, 34, 95,115,116,111,114, 97,103,101, 34, 58,123, 34,110,117,108,108, + 84,101,114,109,105,110, 97,116,101,100, 83,116,111,114, 97,103,101, 34, + 58, 91, 49, 49, 49, 44, 48, 44, 45, 49, 54, 44, 57, 49, 44, 52, 54, 44, + 45, 49, 48, 50, 44, 49, 49, 53, 44, 45, 50, 49, 44, 45, 49, 49, 56, 44, + 52, 57, 44, 57, 50, 44, 45, 49, 48, 44, 53, 56, 44, 45, 55, 48, 44, 57, + 55, 44, 56, 44, 57, 57, 44, 48, 93,125, 125 + ] + + XCTAssertThrowsError(try JSONDecoder().decode( + FilePath.self, + from: Data(input) + )) + } + + func testInvalidExample() { + // Another misformed example from Johannes that violates FilePath's + // invariants by virtue of not being normalized. + let input: [UInt8] = [ + 123, 34, 95,115,116,111,114, 97,103,101, 34, 58,123, 34,110,117,108,108, + 84,101,114,109,105,110, 97,116,101,100, 83,116,111,114, 97,103,101, 34, + 58, 91, 56, 55, 44, 50, 52, 44, 45, 49, 49, 53, 44, 45, 49, 57, 44, 49, + 50, 50, 44, 45, 54, 56, 44, 57, 49, 44, 45, 49, 48, 54, 44, 45, 49, 48, + 48, 44, 45, 49, 49, 52, 44, 53, 54, 44, 45, 54, 53, 44, 49, 49, 56, 44, + 45, 54, 48, 44, 54, 54, 44, 45, 52, 50, 44, 55, 55, 44, 45, 54, 44, 45, + 52, 50, 44, 45, 56, 56, 44, 52, 55, 44, 48, 93,125, 125 + ] + + XCTAssertThrowsError(try JSONDecoder().decode( + FilePath.self, + from: Data(input) + )) + } + + func testEmptyString() { + // FilePath with an empty (and hence not null-terminated) SystemString. + let input: [UInt8] = [ + 123, 34, 95,115,116,111,114, 97,103,101, 34, 58,123, 34,110,117,108,108, + 84,101,114,109,105,110, 97,116,101,100, 83,116,111,114, 97,103,101, 34, + 58, 91, 93,125,125 + ] + + XCTAssertThrowsError(try JSONDecoder().decode( + FilePath.self, + from: Data(input) + )) + } +} From edbc9e4fdc7a4d2706e7ada7626622c21453ef52 Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Wed, 31 Jul 2024 14:56:04 -0700 Subject: [PATCH 232/427] Remove private-and-@_aEIC initializers These were just passthroughs. --- Sources/System/Errno.swift | 221 +++++++++++++-------------- Sources/System/FileDescriptor.swift | 27 ++-- Sources/System/FilePermissions.swift | 51 +++---- 3 files changed, 145 insertions(+), 154 deletions(-) diff --git a/Sources/System/Errno.swift b/Sources/System/Errno.swift index 1305593f..093023d0 100644 --- a/Sources/System/Errno.swift +++ b/Sources/System/Errno.swift @@ -20,13 +20,10 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { @_alwaysEmitIntoClient public init(rawValue: CInt) { self.rawValue = rawValue } - @_alwaysEmitIntoClient - private init(_ raw: CInt) { self.init(rawValue: raw) } - #if SYSTEM_PACKAGE_DARWIN /// Error. Not used. @_alwaysEmitIntoClient - public static var notUsed: Errno { Errno(_ERRNO_NOT_USED) } + public static var notUsed: Errno { .init(rawValue: _ERRNO_NOT_USED) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "notUsed") @@ -41,7 +38,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `EPERM`. @_alwaysEmitIntoClient - public static var notPermitted: Errno { Errno(_EPERM) } + public static var notPermitted: Errno { .init(rawValue: _EPERM) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "notPermitted") @@ -54,7 +51,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `ENOENT`. @_alwaysEmitIntoClient - public static var noSuchFileOrDirectory: Errno { Errno(_ENOENT) } + public static var noSuchFileOrDirectory: Errno { .init(rawValue: _ENOENT) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "noSuchFileOrDirectory") @@ -66,7 +63,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `ESRCH`. @_alwaysEmitIntoClient - public static var noSuchProcess: Errno { Errno(_ESRCH) } + public static var noSuchProcess: Errno { .init(rawValue: _ESRCH) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "noSuchProcess") @@ -81,7 +78,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `EINTR`. @_alwaysEmitIntoClient - public static var interrupted: Errno { Errno(_EINTR) } + public static var interrupted: Errno { .init(rawValue: _EINTR) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "interrupted") @@ -96,7 +93,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `EIO`. @_alwaysEmitIntoClient - public static var ioError: Errno { Errno(_EIO) } + public static var ioError: Errno { .init(rawValue: _EIO) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "ioError") @@ -111,7 +108,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `ENXIO`. @_alwaysEmitIntoClient - public static var noSuchAddressOrDevice: Errno { Errno(_ENXIO) } + public static var noSuchAddressOrDevice: Errno { .init(rawValue: _ENXIO) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "noSuchAddressOrDevice") @@ -125,7 +122,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `E2BIG`. @_alwaysEmitIntoClient - public static var argListTooLong: Errno { Errno(_E2BIG) } + public static var argListTooLong: Errno { .init(rawValue: _E2BIG) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "argListTooLong") @@ -139,7 +136,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `ENOEXEC`. @_alwaysEmitIntoClient - public static var execFormatError: Errno { Errno(_ENOEXEC) } + public static var execFormatError: Errno { .init(rawValue: _ENOEXEC) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "execFormatError") @@ -154,7 +151,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `EBADF`. @_alwaysEmitIntoClient - public static var badFileDescriptor: Errno { Errno(_EBADF) } + public static var badFileDescriptor: Errno { .init(rawValue: _EBADF) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "badFileDescriptor") @@ -168,7 +165,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `ECHILD`. @_alwaysEmitIntoClient - public static var noChildProcess: Errno { Errno(_ECHILD) } + public static var noChildProcess: Errno { .init(rawValue: _ECHILD) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "noChildProcess") @@ -181,7 +178,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `EDEADLK`. @_alwaysEmitIntoClient - public static var deadlock: Errno { Errno(_EDEADLK) } + public static var deadlock: Errno { .init(rawValue: _EDEADLK) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "deadlock") @@ -198,7 +195,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `ENOMEM`. @_alwaysEmitIntoClient - public static var noMemory: Errno { Errno(_ENOMEM) } + public static var noMemory: Errno { .init(rawValue: _ENOMEM) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "noMemory") @@ -211,7 +208,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `EACCES`. @_alwaysEmitIntoClient - public static var permissionDenied: Errno { Errno(_EACCES) } + public static var permissionDenied: Errno { .init(rawValue: _EACCES) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "permissionDenied") @@ -223,7 +220,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `EFAULT`. @_alwaysEmitIntoClient - public static var badAddress: Errno { Errno(_EFAULT) } + public static var badAddress: Errno { .init(rawValue: _EFAULT) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "badAddress") @@ -236,7 +233,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `ENOTBLK`. @_alwaysEmitIntoClient - public static var notBlockDevice: Errno { Errno(_ENOTBLK) } + public static var notBlockDevice: Errno { .init(rawValue: _ENOTBLK) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "notBlockDevice") @@ -250,7 +247,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `EBUSY`. @_alwaysEmitIntoClient - public static var resourceBusy: Errno { Errno(_EBUSY) } + public static var resourceBusy: Errno { .init(rawValue: _EBUSY) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "resourceBusy") @@ -263,7 +260,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `EEXIST`. @_alwaysEmitIntoClient - public static var fileExists: Errno { Errno(_EEXIST) } + public static var fileExists: Errno { .init(rawValue: _EEXIST) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "fileExists") @@ -275,7 +272,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `EXDEV`. @_alwaysEmitIntoClient - public static var improperLink: Errno { Errno(_EXDEV) } + public static var improperLink: Errno { .init(rawValue: _EXDEV) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "improperLink") @@ -288,7 +285,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `ENODEV`. @_alwaysEmitIntoClient - public static var operationNotSupportedByDevice: Errno { Errno(_ENODEV) } + public static var operationNotSupportedByDevice: Errno { .init(rawValue: _ENODEV) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "operationNotSupportedByDevice") @@ -302,7 +299,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `ENOTDIR`. @_alwaysEmitIntoClient - public static var notDirectory: Errno { Errno(_ENOTDIR) } + public static var notDirectory: Errno { .init(rawValue: _ENOTDIR) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "notDirectory") @@ -315,7 +312,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `EISDIR`. @_alwaysEmitIntoClient - public static var isDirectory: Errno { Errno(_EISDIR) } + public static var isDirectory: Errno { .init(rawValue: _EISDIR) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "isDirectory") @@ -328,7 +325,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `EINVAL`. @_alwaysEmitIntoClient - public static var invalidArgument: Errno { Errno(_EINVAL) } + public static var invalidArgument: Errno { .init(rawValue: _EINVAL) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "invalidArgument") @@ -343,7 +340,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `ENFILE`. @_alwaysEmitIntoClient - public static var tooManyOpenFilesInSystem: Errno { Errno(_ENFILE) } + public static var tooManyOpenFilesInSystem: Errno { .init(rawValue: _ENFILE) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "tooManyOpenFilesInSystem") @@ -356,7 +353,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `EMFILE`. @_alwaysEmitIntoClient - public static var tooManyOpenFiles: Errno { Errno(_EMFILE) } + public static var tooManyOpenFiles: Errno { .init(rawValue: _EMFILE) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "tooManyOpenFiles") @@ -371,7 +368,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `ENOTTY`. @_alwaysEmitIntoClient - public static var inappropriateIOCTLForDevice: Errno { Errno(_ENOTTY) } + public static var inappropriateIOCTLForDevice: Errno { .init(rawValue: _ENOTTY) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "inappropriateIOCTLForDevice") @@ -386,7 +383,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `ETXTBSY`. @_alwaysEmitIntoClient - public static var textFileBusy: Errno { Errno(_ETXTBSY) } + public static var textFileBusy: Errno { .init(rawValue: _ETXTBSY) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "textFileBusy") @@ -401,7 +398,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `EFBIG`. @_alwaysEmitIntoClient - public static var fileTooLarge: Errno { Errno(_EFBIG) } + public static var fileTooLarge: Errno { .init(rawValue: _EFBIG) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "fileTooLarge") @@ -418,7 +415,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `ENOSPC`. @_alwaysEmitIntoClient - public static var noSpace: Errno { Errno(_ENOSPC) } + public static var noSpace: Errno { .init(rawValue: _ENOSPC) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "noSpace") @@ -430,7 +427,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `ESPIPE`. @_alwaysEmitIntoClient - public static var illegalSeek: Errno { Errno(_ESPIPE) } + public static var illegalSeek: Errno { .init(rawValue: _ESPIPE) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "illegalSeek") @@ -443,7 +440,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `EROFS`. @_alwaysEmitIntoClient - public static var readOnlyFileSystem: Errno { Errno(_EROFS) } + public static var readOnlyFileSystem: Errno { .init(rawValue: _EROFS) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "readOnlyFileSystem") @@ -456,7 +453,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `EMLINK`. @_alwaysEmitIntoClient - public static var tooManyLinks: Errno { Errno(_EMLINK) } + public static var tooManyLinks: Errno { .init(rawValue: _EMLINK) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "tooManyLinks") @@ -469,7 +466,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `EPIPE`. @_alwaysEmitIntoClient - public static var brokenPipe: Errno { Errno(_EPIPE) } + public static var brokenPipe: Errno { .init(rawValue: _EPIPE) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "brokenPipe") @@ -482,7 +479,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `EDOM`. @_alwaysEmitIntoClient - public static var outOfDomain: Errno { Errno(_EDOM) } + public static var outOfDomain: Errno { .init(rawValue: _EDOM) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "outOfDomain") @@ -497,7 +494,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `ERANGE`. @_alwaysEmitIntoClient - public static var outOfRange: Errno { Errno(_ERANGE) } + public static var outOfRange: Errno { .init(rawValue: _ERANGE) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "outOfRange") @@ -511,7 +508,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `EAGAIN`. @_alwaysEmitIntoClient - public static var resourceTemporarilyUnavailable: Errno { Errno(_EAGAIN) } + public static var resourceTemporarilyUnavailable: Errno { .init(rawValue: _EAGAIN) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "resourceTemporarilyUnavailable") @@ -526,7 +523,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `EINPROGRESS`. @_alwaysEmitIntoClient - public static var nowInProgress: Errno { Errno(_EINPROGRESS) } + public static var nowInProgress: Errno { .init(rawValue: _EINPROGRESS) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "nowInProgress") @@ -539,7 +536,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `EALREADY`. @_alwaysEmitIntoClient - public static var alreadyInProcess: Errno { Errno(_EALREADY) } + public static var alreadyInProcess: Errno { .init(rawValue: _EALREADY) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "alreadyInProcess") @@ -549,7 +546,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `ENOTSOCK`. @_alwaysEmitIntoClient - public static var notSocket: Errno { Errno(_ENOTSOCK) } + public static var notSocket: Errno { .init(rawValue: _ENOTSOCK) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "notSocket") @@ -561,7 +558,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `EDESTADDRREQ`. @_alwaysEmitIntoClient - public static var addressRequired: Errno { Errno(_EDESTADDRREQ) } + public static var addressRequired: Errno { .init(rawValue: _EDESTADDRREQ) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "addressRequired") @@ -574,7 +571,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `EMSGSIZE`. @_alwaysEmitIntoClient - public static var messageTooLong: Errno { Errno(_EMSGSIZE) } + public static var messageTooLong: Errno { .init(rawValue: _EMSGSIZE) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "messageTooLong") @@ -589,7 +586,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `EPROTOTYPE`. @_alwaysEmitIntoClient - public static var protocolWrongTypeForSocket: Errno { Errno(_EPROTOTYPE) } + public static var protocolWrongTypeForSocket: Errno { .init(rawValue: _EPROTOTYPE) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "protocolWrongTypeForSocket") @@ -602,7 +599,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `ENOPROTOOPT`. @_alwaysEmitIntoClient - public static var protocolNotAvailable: Errno { Errno(_ENOPROTOOPT) } + public static var protocolNotAvailable: Errno { .init(rawValue: _ENOPROTOOPT) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "protocolNotAvailable") @@ -615,7 +612,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `EPROTONOSUPPORT`. @_alwaysEmitIntoClient - public static var protocolNotSupported: Errno { Errno(_EPROTONOSUPPORT) } + public static var protocolNotSupported: Errno { .init(rawValue: _EPROTONOSUPPORT) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "protocolNotSupported") @@ -629,7 +626,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `ESOCKTNOSUPPORT`. @_alwaysEmitIntoClient - public static var socketTypeNotSupported: Errno { Errno(_ESOCKTNOSUPPORT) } + public static var socketTypeNotSupported: Errno { .init(rawValue: _ESOCKTNOSUPPORT) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "socketTypeNotSupported") @@ -643,7 +640,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `ENOTSUP`. @_alwaysEmitIntoClient - public static var notSupported: Errno { Errno(_ENOTSUP) } + public static var notSupported: Errno { .init(rawValue: _ENOTSUP) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "notSupported") @@ -657,7 +654,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `EPFNOSUPPORT`. @_alwaysEmitIntoClient - public static var protocolFamilyNotSupported: Errno { Errno(_EPFNOSUPPORT) } + public static var protocolFamilyNotSupported: Errno { .init(rawValue: _EPFNOSUPPORT) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "protocolFamilyNotSupported") @@ -672,7 +669,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `EAFNOSUPPORT`. @_alwaysEmitIntoClient - public static var addressFamilyNotSupported: Errno { Errno(_EAFNOSUPPORT) } + public static var addressFamilyNotSupported: Errno { .init(rawValue: _EAFNOSUPPORT) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "addressFamilyNotSupported") @@ -684,7 +681,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `EADDRINUSE`. @_alwaysEmitIntoClient - public static var addressInUse: Errno { Errno(_EADDRINUSE) } + public static var addressInUse: Errno { .init(rawValue: _EADDRINUSE) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "addressInUse") @@ -697,7 +694,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `EADDRNOTAVAIL`. @_alwaysEmitIntoClient - public static var addressNotAvailable: Errno { Errno(_EADDRNOTAVAIL) } + public static var addressNotAvailable: Errno { .init(rawValue: _EADDRNOTAVAIL) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "addressNotAvailable") @@ -709,7 +706,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `ENETDOWN`. @_alwaysEmitIntoClient - public static var networkDown: Errno { Errno(_ENETDOWN) } + public static var networkDown: Errno { .init(rawValue: _ENETDOWN) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "networkDown") @@ -721,7 +718,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `ENETUNREACH`. @_alwaysEmitIntoClient - public static var networkUnreachable: Errno { Errno(_ENETUNREACH) } + public static var networkUnreachable: Errno { .init(rawValue: _ENETUNREACH) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "networkUnreachable") @@ -733,7 +730,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `ENETRESET`. @_alwaysEmitIntoClient - public static var networkReset: Errno { Errno(_ENETRESET) } + public static var networkReset: Errno { .init(rawValue: _ENETRESET) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "networkReset") @@ -745,7 +742,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `ECONNABORTED`. @_alwaysEmitIntoClient - public static var connectionAbort: Errno { Errno(_ECONNABORTED) } + public static var connectionAbort: Errno { .init(rawValue: _ECONNABORTED) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "connectionAbort") @@ -759,7 +756,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `ECONNRESET`. @_alwaysEmitIntoClient - public static var connectionReset: Errno { Errno(_ECONNRESET) } + public static var connectionReset: Errno { .init(rawValue: _ECONNRESET) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "connectionReset") @@ -773,7 +770,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `ENOBUFS`. @_alwaysEmitIntoClient - public static var noBufferSpace: Errno { Errno(_ENOBUFS) } + public static var noBufferSpace: Errno { .init(rawValue: _ENOBUFS) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "noBufferSpace") @@ -788,7 +785,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `EISCONN`. @_alwaysEmitIntoClient - public static var socketIsConnected: Errno { Errno(_EISCONN) } + public static var socketIsConnected: Errno { .init(rawValue: _EISCONN) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "socketIsConnected") @@ -803,7 +800,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `ENOTCONN`. @_alwaysEmitIntoClient - public static var socketNotConnected: Errno { Errno(_ENOTCONN) } + public static var socketNotConnected: Errno { .init(rawValue: _ENOTCONN) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "socketNotConnected") @@ -818,7 +815,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `ESHUTDOWN`. @_alwaysEmitIntoClient - public static var socketShutdown: Errno { Errno(_ESHUTDOWN) } + public static var socketShutdown: Errno { .init(rawValue: _ESHUTDOWN) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "socketShutdown") @@ -834,7 +831,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `ETIMEDOUT`. @_alwaysEmitIntoClient - public static var timedOut: Errno { Errno(_ETIMEDOUT) } + public static var timedOut: Errno { .init(rawValue: _ETIMEDOUT) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "timedOut") @@ -849,7 +846,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `ECONNREFUSED`. @_alwaysEmitIntoClient - public static var connectionRefused: Errno { Errno(_ECONNREFUSED) } + public static var connectionRefused: Errno { .init(rawValue: _ECONNREFUSED) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "connectionRefused") @@ -861,7 +858,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `ELOOP`. @_alwaysEmitIntoClient - public static var tooManySymbolicLinkLevels: Errno { Errno(_ELOOP) } + public static var tooManySymbolicLinkLevels: Errno { .init(rawValue: _ELOOP) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "tooManySymbolicLinkLevels") @@ -874,7 +871,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `ENAMETOOLONG`. @_alwaysEmitIntoClient - public static var fileNameTooLong: Errno { Errno(_ENAMETOOLONG) } + public static var fileNameTooLong: Errno { .init(rawValue: _ENAMETOOLONG) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "fileNameTooLong") @@ -887,7 +884,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `EHOSTDOWN`. @_alwaysEmitIntoClient - public static var hostIsDown: Errno { Errno(_EHOSTDOWN) } + public static var hostIsDown: Errno { .init(rawValue: _EHOSTDOWN) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "hostIsDown") @@ -900,7 +897,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `EHOSTUNREACH`. @_alwaysEmitIntoClient - public static var noRouteToHost: Errno { Errno(_EHOSTUNREACH) } + public static var noRouteToHost: Errno { .init(rawValue: _EHOSTUNREACH) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "noRouteToHost") @@ -913,7 +910,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `ENOTEMPTY`. @_alwaysEmitIntoClient - public static var directoryNotEmpty: Errno { Errno(_ENOTEMPTY) } + public static var directoryNotEmpty: Errno { .init(rawValue: _ENOTEMPTY) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "directoryNotEmpty") @@ -924,7 +921,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `EPROCLIM`. @_alwaysEmitIntoClient - public static var tooManyProcesses: Errno { Errno(_EPROCLIM) } + public static var tooManyProcesses: Errno { .init(rawValue: _EPROCLIM) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "tooManyProcesses") @@ -938,7 +935,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `EUSERS`. @_alwaysEmitIntoClient - public static var tooManyUsers: Errno { Errno(_EUSERS) } + public static var tooManyUsers: Errno { .init(rawValue: _EUSERS) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "tooManyUsers") @@ -956,7 +953,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `EDQUOT`. @_alwaysEmitIntoClient - public static var diskQuotaExceeded: Errno { Errno(_EDQUOT) } + public static var diskQuotaExceeded: Errno { .init(rawValue: _EDQUOT) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "diskQuotaExceeded") @@ -971,7 +968,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `ESTALE`. @_alwaysEmitIntoClient - public static var staleNFSFileHandle: Errno { Errno(_ESTALE) } + public static var staleNFSFileHandle: Errno { .init(rawValue: _ESTALE) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "staleNFSFileHandle") @@ -986,7 +983,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `EBADRPC`. @_alwaysEmitIntoClient - public static var rpcUnsuccessful: Errno { Errno(_EBADRPC) } + public static var rpcUnsuccessful: Errno { .init(rawValue: _EBADRPC) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "rpcUnsuccessful") @@ -999,7 +996,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `ERPCMISMATCH`. @_alwaysEmitIntoClient - public static var rpcVersionMismatch: Errno { Errno(_ERPCMISMATCH) } + public static var rpcVersionMismatch: Errno { .init(rawValue: _ERPCMISMATCH) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "rpcVersionMismatch") @@ -1011,7 +1008,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `EPROGUNAVAIL`. @_alwaysEmitIntoClient - public static var rpcProgramUnavailable: Errno { Errno(_EPROGUNAVAIL) } + public static var rpcProgramUnavailable: Errno { .init(rawValue: _EPROGUNAVAIL) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "rpcProgramUnavailable") @@ -1024,7 +1021,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `EPROGMISMATCH`. @_alwaysEmitIntoClient - public static var rpcProgramVersionMismatch: Errno { Errno(_EPROGMISMATCH) } + public static var rpcProgramVersionMismatch: Errno { .init(rawValue: _EPROGMISMATCH) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "rpcProgramVersionMismatch") @@ -1037,7 +1034,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `EPROCUNAVAIL`. @_alwaysEmitIntoClient - public static var rpcProcedureUnavailable: Errno { Errno(_EPROCUNAVAIL) } + public static var rpcProcedureUnavailable: Errno { .init(rawValue: _EPROCUNAVAIL) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "rpcProcedureUnavailable") @@ -1051,7 +1048,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `ENOLCK`. @_alwaysEmitIntoClient - public static var noLocks: Errno { Errno(_ENOLCK) } + public static var noLocks: Errno { .init(rawValue: _ENOLCK) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "noLocks") @@ -1063,7 +1060,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `ENOSYS`. @_alwaysEmitIntoClient - public static var noFunction: Errno { Errno(_ENOSYS) } + public static var noFunction: Errno { .init(rawValue: _ENOSYS) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "noFunction") @@ -1078,7 +1075,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `EFTYPE`. @_alwaysEmitIntoClient - public static var badFileTypeOrFormat: Errno { Errno(_EFTYPE) } + public static var badFileTypeOrFormat: Errno { .init(rawValue: _EFTYPE) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "badFileTypeOrFormat") @@ -1092,7 +1089,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `EAUTH`. @_alwaysEmitIntoClient - public static var authenticationError: Errno { Errno(_EAUTH) } + public static var authenticationError: Errno { .init(rawValue: _EAUTH) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "authenticationError") @@ -1105,7 +1102,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `ENEEDAUTH`. @_alwaysEmitIntoClient - public static var needAuthenticator: Errno { Errno(_ENEEDAUTH) } + public static var needAuthenticator: Errno { .init(rawValue: _ENEEDAUTH) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "needAuthenticator") @@ -1117,7 +1114,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `EPWROFF`. @_alwaysEmitIntoClient - public static var devicePowerIsOff: Errno { Errno(_EPWROFF) } + public static var devicePowerIsOff: Errno { .init(rawValue: _EPWROFF) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "devicePowerIsOff") @@ -1130,7 +1127,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `EDEVERR`. @_alwaysEmitIntoClient - public static var deviceError: Errno { Errno(_EDEVERR) } + public static var deviceError: Errno { .init(rawValue: _EDEVERR) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "deviceError") @@ -1145,7 +1142,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `EOVERFLOW`. @_alwaysEmitIntoClient - public static var overflow: Errno { Errno(_EOVERFLOW) } + public static var overflow: Errno { .init(rawValue: _EOVERFLOW) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "overflow") @@ -1159,7 +1156,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `EBADEXEC`. @_alwaysEmitIntoClient - public static var badExecutable: Errno { Errno(_EBADEXEC) } + public static var badExecutable: Errno { .init(rawValue: _EBADEXEC) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "badExecutable") @@ -1171,7 +1168,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `EBADARCH`. @_alwaysEmitIntoClient - public static var badCPUType: Errno { Errno(_EBADARCH) } + public static var badCPUType: Errno { .init(rawValue: _EBADARCH) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "badCPUType") @@ -1184,7 +1181,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `ESHLIBVERS`. @_alwaysEmitIntoClient - public static var sharedLibraryVersionMismatch: Errno { Errno(_ESHLIBVERS) } + public static var sharedLibraryVersionMismatch: Errno { .init(rawValue: _ESHLIBVERS) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "sharedLibraryVersionMismatch") @@ -1196,7 +1193,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `EBADMACHO`. @_alwaysEmitIntoClient - public static var malformedMachO: Errno { Errno(_EBADMACHO) } + public static var malformedMachO: Errno { .init(rawValue: _EBADMACHO) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "malformedMachO") @@ -1209,7 +1206,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `ECANCELED`. @_alwaysEmitIntoClient - public static var canceled: Errno { Errno(_ECANCELED) } + public static var canceled: Errno { .init(rawValue: _ECANCELED) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "canceled") @@ -1222,7 +1219,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `EIDRM`. @_alwaysEmitIntoClient - public static var identifierRemoved: Errno { Errno(_EIDRM) } + public static var identifierRemoved: Errno { .init(rawValue: _EIDRM) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "identifierRemoved") @@ -1235,7 +1232,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `ENOMSG`. @_alwaysEmitIntoClient - public static var noMessage: Errno { Errno(_ENOMSG) } + public static var noMessage: Errno { .init(rawValue: _ENOMSG) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "noMessage") @@ -1250,7 +1247,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `EILSEQ`. @_alwaysEmitIntoClient - public static var illegalByteSequence: Errno { Errno(_EILSEQ) } + public static var illegalByteSequence: Errno { .init(rawValue: _EILSEQ) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "illegalByteSequence") @@ -1263,7 +1260,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `ENOATTR`. @_alwaysEmitIntoClient - public static var attributeNotFound: Errno { Errno(_ENOATTR) } + public static var attributeNotFound: Errno { .init(rawValue: _ENOATTR) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "attributeNotFound") @@ -1278,7 +1275,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `EBADMSG`. @_alwaysEmitIntoClient - public static var badMessage: Errno { Errno(_EBADMSG) } + public static var badMessage: Errno { .init(rawValue: _EBADMSG) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "badMessage") @@ -1291,7 +1288,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `EMULTIHOP`. @_alwaysEmitIntoClient - public static var multiHop: Errno { Errno(_EMULTIHOP) } + public static var multiHop: Errno { .init(rawValue: _EMULTIHOP) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "multiHop") @@ -1304,7 +1301,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `ENODATA`. @_alwaysEmitIntoClient - public static var noData: Errno { Errno(_ENODATA) } + public static var noData: Errno { .init(rawValue: _ENODATA) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "noData") @@ -1317,7 +1314,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `ENOLINK`. @_alwaysEmitIntoClient - public static var noLink: Errno { Errno(_ENOLINK) } + public static var noLink: Errno { .init(rawValue: _ENOLINK) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "noLink") @@ -1330,7 +1327,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `ENOSR`. @_alwaysEmitIntoClient - public static var noStreamResources: Errno { Errno(_ENOSR) } + public static var noStreamResources: Errno { .init(rawValue: _ENOSR) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "noStreamResources") @@ -1342,7 +1339,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `ENOSTR`. @_alwaysEmitIntoClient - public static var notStream: Errno { Errno(_ENOSTR) } + public static var notStream: Errno { .init(rawValue: _ENOSTR) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "notStream") @@ -1358,7 +1355,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `EPROTO`. @_alwaysEmitIntoClient - public static var protocolError: Errno { Errno(_EPROTO) } + public static var protocolError: Errno { .init(rawValue: _EPROTO) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "protocolError") @@ -1371,7 +1368,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `ETIME`. @_alwaysEmitIntoClient - public static var timeout: Errno { Errno(_ETIME) } + public static var timeout: Errno { .init(rawValue: _ETIME) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "timeout") @@ -1386,7 +1383,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `EOPNOTSUPP`. @_alwaysEmitIntoClient - public static var notSupportedOnSocket: Errno { Errno(_EOPNOTSUPP) } + public static var notSupportedOnSocket: Errno { .init(rawValue: _EOPNOTSUPP) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "notSupportedOnSocket") @@ -1400,7 +1397,7 @@ extension Errno { /// /// The corresponding C error is `EWOULDBLOCK`. @_alwaysEmitIntoClient - public static var wouldBlock: Errno { Errno(_EWOULDBLOCK) } + public static var wouldBlock: Errno { .init(rawValue: _EWOULDBLOCK) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "wouldBlock") @@ -1411,7 +1408,7 @@ extension Errno { /// /// The corresponding C error is `ETOOMANYREFS`. @_alwaysEmitIntoClient - public static var tooManyReferences: Errno { Errno(_ETOOMANYREFS) } + public static var tooManyReferences: Errno { .init(rawValue: _ETOOMANYREFS) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "tooManyReferences") @@ -1421,7 +1418,7 @@ extension Errno { /// /// The corresponding C error is `EREMOTE`. @_alwaysEmitIntoClient - public static var tooManyRemoteLevels: Errno { Errno(_EREMOTE) } + public static var tooManyRemoteLevels: Errno { .init(rawValue: _EREMOTE) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "tooManyRemoteLevels") @@ -1433,7 +1430,7 @@ extension Errno { /// /// The corresponding C error is `ENOPOLICY`. @_alwaysEmitIntoClient - public static var noSuchPolicy: Errno { Errno(_ENOPOLICY) } + public static var noSuchPolicy: Errno { .init(rawValue: _ENOPOLICY) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "noSuchPolicy") @@ -1445,7 +1442,7 @@ extension Errno { /// /// The corresponding C error is `ENOTRECOVERABLE`. @_alwaysEmitIntoClient - public static var notRecoverable: Errno { Errno(_ENOTRECOVERABLE) } + public static var notRecoverable: Errno { .init(rawValue: _ENOTRECOVERABLE) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "notRecoverable") @@ -1455,7 +1452,7 @@ extension Errno { /// /// The corresponding C error is `EOWNERDEAD`. @_alwaysEmitIntoClient - public static var previousOwnerDied: Errno { Errno(_EOWNERDEAD) } + public static var previousOwnerDied: Errno { .init(rawValue: _EOWNERDEAD) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "previousOwnerDied") @@ -1467,7 +1464,7 @@ extension Errno { /// /// The corresponding C error is `EQFULL`. @_alwaysEmitIntoClient - public static var outputQueueFull: Errno { Errno(_EQFULL) } + public static var outputQueueFull: Errno { .init(rawValue: _EQFULL) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "outputQueueFull") @@ -1481,7 +1478,7 @@ extension Errno { /// /// The corresponding C error is `ELAST`. @_alwaysEmitIntoClient - public static var lastErrnoValue: Errno { Errno(_ELAST) } + public static var lastErrnoValue: Errno { .init(rawValue: _ELAST) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "lastErrnoValue") diff --git a/Sources/System/FileDescriptor.swift b/Sources/System/FileDescriptor.swift index 0ac7f4fe..d7b931eb 100644 --- a/Sources/System/FileDescriptor.swift +++ b/Sources/System/FileDescriptor.swift @@ -98,9 +98,6 @@ extension FileDescriptor { @_alwaysEmitIntoClient public init(rawValue: CInt) { self.rawValue = rawValue } - @_alwaysEmitIntoClient - private init(_ raw: CInt) { self.init(rawValue: raw) } - #if !os(Windows) /// Indicates that opening the file doesn't /// wait for the file or device to become available. @@ -117,7 +114,7 @@ extension FileDescriptor { /// /// The corresponding C constant is `O_NONBLOCK`. @_alwaysEmitIntoClient - public static var nonBlocking: OpenOptions { OpenOptions(_O_NONBLOCK) } + public static var nonBlocking: OpenOptions { .init(rawValue: _O_NONBLOCK) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "nonBlocking") @@ -133,7 +130,7 @@ extension FileDescriptor { /// /// The corresponding C constant is `O_APPEND`. @_alwaysEmitIntoClient - public static var append: OpenOptions { OpenOptions(_O_APPEND) } + public static var append: OpenOptions { .init(rawValue: _O_APPEND) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "append") @@ -143,7 +140,7 @@ extension FileDescriptor { /// /// The corresponding C constant is `O_CREAT`. @_alwaysEmitIntoClient - public static var create: OpenOptions { OpenOptions(_O_CREAT) } + public static var create: OpenOptions { .init(rawValue: _O_CREAT) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "create") @@ -157,7 +154,7 @@ extension FileDescriptor { /// /// The corresponding C constant is `O_TRUNC`. @_alwaysEmitIntoClient - public static var truncate: OpenOptions { OpenOptions(_O_TRUNC) } + public static var truncate: OpenOptions { .init(rawValue: _O_TRUNC) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "truncate") @@ -179,7 +176,7 @@ extension FileDescriptor { /// /// The corresponding C constant is `O_EXCL`. @_alwaysEmitIntoClient - public static var exclusiveCreate: OpenOptions { OpenOptions(_O_EXCL) } + public static var exclusiveCreate: OpenOptions { .init(rawValue: _O_EXCL) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "exclusiveCreate") @@ -197,7 +194,7 @@ extension FileDescriptor { /// /// The corresponding C constant is `O_SHLOCK`. @_alwaysEmitIntoClient - public static var sharedLock: OpenOptions { OpenOptions(_O_SHLOCK) } + public static var sharedLock: OpenOptions { .init(rawValue: _O_SHLOCK) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "sharedLock") @@ -214,7 +211,7 @@ extension FileDescriptor { /// /// The corresponding C constant is `O_EXLOCK`. @_alwaysEmitIntoClient - public static var exclusiveLock: OpenOptions { OpenOptions(_O_EXLOCK) } + public static var exclusiveLock: OpenOptions { .init(rawValue: _O_EXLOCK) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "exclusiveLock") @@ -232,7 +229,7 @@ extension FileDescriptor { /// /// The corresponding C constant is `O_NOFOLLOW`. @_alwaysEmitIntoClient - public static var noFollow: OpenOptions { OpenOptions(_O_NOFOLLOW) } + public static var noFollow: OpenOptions { .init(rawValue: _O_NOFOLLOW) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "noFollow") @@ -246,7 +243,7 @@ extension FileDescriptor { /// /// The corresponding C constant is `O_DIRECTORY`. @_alwaysEmitIntoClient - public static var directory: OpenOptions { OpenOptions(_O_DIRECTORY) } + public static var directory: OpenOptions { .init(rawValue: _O_DIRECTORY) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "directory") @@ -265,7 +262,7 @@ extension FileDescriptor { /// /// The corresponding C constant is `O_SYMLINK`. @_alwaysEmitIntoClient - public static var symlink: OpenOptions { OpenOptions(_O_SYMLINK) } + public static var symlink: OpenOptions { .init(rawValue: _O_SYMLINK) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "symlink") @@ -281,7 +278,7 @@ extension FileDescriptor { /// /// The corresponding C constant is `O_EVTONLY`. @_alwaysEmitIntoClient - public static var eventOnly: OpenOptions { OpenOptions(_O_EVTONLY) } + public static var eventOnly: OpenOptions { .init(rawValue: _O_EVTONLY) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "eventOnly") @@ -303,7 +300,7 @@ extension FileDescriptor { /// /// The corresponding C constant is `O_CLOEXEC`. @_alwaysEmitIntoClient - public static var closeOnExec: OpenOptions { OpenOptions(_O_CLOEXEC) } + public static var closeOnExec: OpenOptions { .init(rawValue: _O_CLOEXEC) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "closeOnExec") diff --git a/Sources/System/FilePermissions.swift b/Sources/System/FilePermissions.swift index 2cdb32ff..918246ef 100644 --- a/Sources/System/FilePermissions.swift +++ b/Sources/System/FilePermissions.swift @@ -27,104 +27,101 @@ public struct FilePermissions: OptionSet, Sendable, Hashable, Codable { @_alwaysEmitIntoClient public init(rawValue: CModeT) { self.rawValue = rawValue } - @_alwaysEmitIntoClient - private init(_ raw: CModeT) { self.init(rawValue: raw) } - /// Indicates that other users have read-only permission. @_alwaysEmitIntoClient - public static var otherRead: FilePermissions { FilePermissions(0o4) } + public static var otherRead: FilePermissions { .init(rawValue: 0o4) } /// Indicates that other users have write-only permission. @_alwaysEmitIntoClient - public static var otherWrite: FilePermissions { FilePermissions(0o2) } + public static var otherWrite: FilePermissions { .init(rawValue: 0o2) } /// Indicates that other users have execute-only permission. @_alwaysEmitIntoClient - public static var otherExecute: FilePermissions { FilePermissions(0o1) } + public static var otherExecute: FilePermissions { .init(rawValue: 0o1) } /// Indicates that other users have read-write permission. @_alwaysEmitIntoClient - public static var otherReadWrite: FilePermissions { FilePermissions(0o6) } + public static var otherReadWrite: FilePermissions { .init(rawValue: 0o6) } /// Indicates that other users have read-execute permission. @_alwaysEmitIntoClient - public static var otherReadExecute: FilePermissions { FilePermissions(0o5) } + public static var otherReadExecute: FilePermissions { .init(rawValue: 0o5) } /// Indicates that other users have write-execute permission. @_alwaysEmitIntoClient - public static var otherWriteExecute: FilePermissions { FilePermissions(0o3) } + public static var otherWriteExecute: FilePermissions { .init(rawValue: 0o3) } /// Indicates that other users have read, write, and execute permission. @_alwaysEmitIntoClient - public static var otherReadWriteExecute: FilePermissions { FilePermissions(0o7) } + public static var otherReadWriteExecute: FilePermissions { .init(rawValue: 0o7) } /// Indicates that the group has read-only permission. @_alwaysEmitIntoClient - public static var groupRead: FilePermissions { FilePermissions(0o40) } + public static var groupRead: FilePermissions { .init(rawValue: 0o40) } /// Indicates that the group has write-only permission. @_alwaysEmitIntoClient - public static var groupWrite: FilePermissions { FilePermissions(0o20) } + public static var groupWrite: FilePermissions { .init(rawValue: 0o20) } /// Indicates that the group has execute-only permission. @_alwaysEmitIntoClient - public static var groupExecute: FilePermissions { FilePermissions(0o10) } + public static var groupExecute: FilePermissions { .init(rawValue: 0o10) } /// Indicates that the group has read-write permission. @_alwaysEmitIntoClient - public static var groupReadWrite: FilePermissions { FilePermissions(0o60) } + public static var groupReadWrite: FilePermissions { .init(rawValue: 0o60) } /// Indicates that the group has read-execute permission. @_alwaysEmitIntoClient - public static var groupReadExecute: FilePermissions { FilePermissions(0o50) } + public static var groupReadExecute: FilePermissions { .init(rawValue: 0o50) } /// Indicates that the group has write-execute permission. @_alwaysEmitIntoClient - public static var groupWriteExecute: FilePermissions { FilePermissions(0o30) } + public static var groupWriteExecute: FilePermissions { .init(rawValue: 0o30) } /// Indicates that the group has read, write, and execute permission. @_alwaysEmitIntoClient - public static var groupReadWriteExecute: FilePermissions { FilePermissions(0o70) } + public static var groupReadWriteExecute: FilePermissions { .init(rawValue: 0o70) } /// Indicates that the owner has read-only permission. @_alwaysEmitIntoClient - public static var ownerRead: FilePermissions { FilePermissions(0o400) } + public static var ownerRead: FilePermissions { .init(rawValue: 0o400) } /// Indicates that the owner has write-only permission. @_alwaysEmitIntoClient - public static var ownerWrite: FilePermissions { FilePermissions(0o200) } + public static var ownerWrite: FilePermissions { .init(rawValue: 0o200) } /// Indicates that the owner has execute-only permission. @_alwaysEmitIntoClient - public static var ownerExecute: FilePermissions { FilePermissions(0o100) } + public static var ownerExecute: FilePermissions { .init(rawValue: 0o100) } /// Indicates that the owner has read-write permission. @_alwaysEmitIntoClient - public static var ownerReadWrite: FilePermissions { FilePermissions(0o600) } + public static var ownerReadWrite: FilePermissions { .init(rawValue: 0o600) } /// Indicates that the owner has read-execute permission. @_alwaysEmitIntoClient - public static var ownerReadExecute: FilePermissions { FilePermissions(0o500) } + public static var ownerReadExecute: FilePermissions { .init(rawValue: 0o500) } /// Indicates that the owner has write-execute permission. @_alwaysEmitIntoClient - public static var ownerWriteExecute: FilePermissions { FilePermissions(0o300) } + public static var ownerWriteExecute: FilePermissions { .init(rawValue: 0o300) } /// Indicates that the owner has read, write, and execute permission. @_alwaysEmitIntoClient - public static var ownerReadWriteExecute: FilePermissions { FilePermissions(0o700) } + public static var ownerReadWriteExecute: FilePermissions { .init(rawValue: 0o700) } /// Indicates that the file is executed as the owner. /// /// For more information, see the `setuid(2)` man page. @_alwaysEmitIntoClient - public static var setUserID: FilePermissions { FilePermissions(0o4000) } + public static var setUserID: FilePermissions { .init(rawValue: 0o4000) } /// Indicates that the file is executed as the group. /// /// For more information, see the `setgid(2)` man page. @_alwaysEmitIntoClient - public static var setGroupID: FilePermissions { FilePermissions(0o2000) } + public static var setGroupID: FilePermissions { .init(rawValue: 0o2000) } /// Indicates that executable's text segment /// should be kept in swap space even after it exits. @@ -132,7 +129,7 @@ public struct FilePermissions: OptionSet, Sendable, Hashable, Codable { /// For more information, see the `chmod(2)` man page's /// discussion of `S_ISVTX` (the sticky bit). @_alwaysEmitIntoClient - public static var saveText: FilePermissions { FilePermissions(0o1000) } + public static var saveText: FilePermissions { .init(rawValue: 0o1000) } } @available(/*System 0.0.1: macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0*/iOS 8, *) From 2662793bd69ff7bcda6975c36e86a8ae5127deb7 Mon Sep 17 00:00:00 2001 From: George Barnett Date: Mon, 19 Aug 2024 10:18:30 +0100 Subject: [PATCH 233/427] Add support for macCatalyst Building swift-system for macCatalyst fails as there is no import of the Darwin module. --- Package.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Package.swift b/Package.swift index 3d30cd90..c0847e4f 100644 --- a/Package.swift +++ b/Package.swift @@ -13,9 +13,9 @@ import PackageDescription let DarwinPlatforms: [Platform] #if swift(<5.9) -DarwinPlatforms = [.macOS, .iOS, .watchOS, .tvOS] +DarwinPlatforms = [.macOS, .macCatalyst, .iOS, .watchOS, .tvOS] #else -DarwinPlatforms = [.macOS, .iOS, .watchOS, .tvOS, .visionOS] +DarwinPlatforms = [.macOS, .macCatalyst, .iOS, .watchOS, .tvOS, .visionOS] #endif let package = Package( From 603c45181816ca05e702fb130ce239a580405e97 Mon Sep 17 00:00:00 2001 From: Karoy Lorentey Date: Thu, 19 Sep 2024 12:49:08 -0700 Subject: [PATCH 234/427] Add code of conduct --- CODE_OF_CONDUCT.md | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 CODE_OF_CONDUCT.md diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 00000000..95c80e78 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,38 @@ +# Code of Conduct + +To be a truly great community, Swift.org needs to welcome developers from all walks of life, with different backgrounds, and with a wide range of experience. A diverse and friendly community will have more great ideas, more unique perspectives, and produce more great code. We will work diligently to make the Swift community welcoming to everyone. + +To give clarity of what is expected of our members, this code of conduct is based on [contributor-covenant.org](http://contributor-covenant.org). This document is used across many open source communities, and we think it articulates our values well. + +### Contributor Code of Conduct v1.4 + +In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to make participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation. + +Examples of behavior that contributes to creating a positive environment include: + +* Using welcoming and inclusive language (e.g., prefer non-gendered words like “folks” to “guys”, non-ableist words like “soundness check” to “sanity check”, etc.) +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others’ private information, such as a physical or electronic address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a professional setting + +Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. + +This Code of Conduct applies within all project spaces managed by Swift.org, including (but not limited to) source code repositories, bug trackers, web sites, documentation, and online forums. It also applies when an individual is representing the project or its community in public spaces. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. + +Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting a member of the [Swift Core Team](https://swift.org/community/#community-structure) or by flagging the behavior for moderation (e.g., in the Forums), whether you are the target of that behavior or not. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. Maintainers are obligated to maintain confidentiality with regard to the reporter of an incident. The site of the disputed behavior is usually not an acceptable place to discuss moderation decisions, and moderators may move or remove any such discussion. + +Project maintainers are held to a higher standard, and project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project’s leadership. +If you disagree with a moderation action, you can appeal to the Core Team (or individual Core Team members) privately. + +This policy is adapted from the Contributor Code of Conduct [version 1.4](https://www.contributor-covenant.org/version/1/4/code-of-conduct/). From 4c612a1e29a0eb3d8aa1def29db51d7500c283fb Mon Sep 17 00:00:00 2001 From: Karoy Lorentey Date: Thu, 19 Sep 2024 12:57:57 -0700 Subject: [PATCH 235/427] Bump required toolchain version to 5.9. Sanitize Package manifest MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Don’t define _CRT_SECURE_NO_WARNINGS outside Windows - Let all targets share the same settings --- Package.swift | 88 +++++++++++++++++++++++++++------------------------ 1 file changed, 46 insertions(+), 42 deletions(-) diff --git a/Package.swift b/Package.swift index b135a356..411a2959 100644 --- a/Package.swift +++ b/Package.swift @@ -1,47 +1,51 @@ -// swift-tools-version:5.6 -// The swift-tools-version declares the minimum version of Swift required to build this package. +// swift-tools-version:5.9 +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift System open source project +// +// Copyright (c) 2020 - 2024 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 +// +//===----------------------------------------------------------------------===// -/* - This source file is part of the Swift System open source project - - Copyright (c) 2020 Apple Inc. and the Swift System project authors - Licensed under Apache License v2.0 with Runtime Library Exception +import PackageDescription - See https://swift.org/LICENSE.txt for license information -*/ +let cSettings: [CSetting] = [ + .define("_CRT_SECURE_NO_WARNINGS", .when(platforms: [.windows])), +] -import PackageDescription +let swiftSettings: [SwiftSetting] = [ + .define( + "SYSTEM_PACKAGE_DARWIN", + .when(platforms: [.macOS, .iOS, .watchOS, .tvOS, .visionOS])), + .define("SYSTEM_PACKAGE"), + .define("ENABLE_MOCKING", .when(configuration: .debug)), +] let package = Package( - name: "swift-system", - products: [ - .library(name: "SystemPackage", targets: ["SystemPackage"]), - ], - dependencies: [], - targets: [ - .target( - name: "CSystem", - dependencies: [], - exclude: ["CMakeLists.txt"]), - .target( - name: "SystemPackage", - dependencies: ["CSystem"], - path: "Sources/System", - exclude: ["CMakeLists.txt"], - cSettings: [ - .define("_CRT_SECURE_NO_WARNINGS") - ], - swiftSettings: [ - .define("SYSTEM_PACKAGE_DARWIN", .when(platforms: [.macOS, .iOS, .watchOS, .tvOS, .visionOS])), - .define("SYSTEM_PACKAGE"), - .define("ENABLE_MOCKING", .when(configuration: .debug)) - ]), - .testTarget( - name: "SystemTests", - dependencies: ["SystemPackage"], - swiftSettings: [ - .define("SYSTEM_PACKAGE_DARWIN", .when(platforms: [.macOS, .iOS, .watchOS, .tvOS, .visionOS])), - .define("SYSTEM_PACKAGE") - ]), - ] -) + name: "swift-system", + products: [ + .library(name: "SystemPackage", targets: ["SystemPackage"]), + ], + dependencies: [], + targets: [ + .target( + name: "CSystem", + dependencies: [], + exclude: ["CMakeLists.txt"], + cSettings: cSettings), + .target( + name: "SystemPackage", + dependencies: ["CSystem"], + path: "Sources/System", + exclude: ["CMakeLists.txt"], + cSettings: cSettings, + swiftSettings: swiftSettings), + .testTarget( + name: "SystemTests", + dependencies: ["SystemPackage"], + cSettings: cSettings, + swiftSettings: swiftSettings), + ]) From 88a60e08b3692d49a992429897c78f895b104804 Mon Sep 17 00:00:00 2001 From: Karoy Lorentey Date: Thu, 19 Sep 2024 13:01:48 -0700 Subject: [PATCH 236/427] Update README MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Clarify swift-system’s stance on cross-platform abstractions. Stop using the term “multi-platform”. - Expand source stability section. - Add a section on our branching strategy. - Link to the Code of Conduct document. --- README.md | 66 ++++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 58 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 14edea0b..452ad86d 100644 --- a/README.md +++ b/README.md @@ -2,11 +2,13 @@ Swift System provides idiomatic interfaces to system calls and low-level currency types. Our vision is for System to act as the single home for low-level system interfaces for all supported Swift platforms. -## Multi-platform not Cross-platform +## No Cross-platform Abstractions -System is a multi-platform library, not a cross-platform one. It provides a separate set of APIs and behaviors on every supported platform, closely reflecting the underlying OS interfaces. A single import will pull in the native platform interfaces specific for the targeted OS. +Swift System is not a cross-platform library. It provides a separate set of APIs and behaviors on every supported platform, closely reflecting the underlying OS interfaces. A single import will pull in the native platform interfaces specific for the targeted OS. -Our immediate goal is to simplify building cross-platform libraries and applications such as SwiftNIO and SwiftPM. System does not eliminate the need for `#if os()` conditionals to implement cross-platform abstractions, but it does make it safer and more expressive to fill out the platform-specific parts. +Our immediate goal is to simplify building cross-platform libraries and applications such as SwiftNIO and SwiftPM. It is not a design goal for System to eliminate the need for `#if os()` conditionals to implement cross-platform abstractions; rather, our goal is to make it safer and more expressive to fill out the platform-specific parts. + +(That said, it is desirable to avoid unnecessary differences -- for example, when two operating systems share the same C name for a system call, ideally Swift System would expose them using the same Swift name. This is a particularly obvious expectation for system interfaces that implement an industry standard, such as POSIX.) ## Usage @@ -30,7 +32,7 @@ To use the `SystemPackage` library in a SwiftPM project, add the following line to the dependencies in your `Package.swift` file: ```swift -.package(url: "https://github.com/apple/swift-system", from: "1.0.0"), +.package(url: "https://github.com/apple/swift-system", from: "1.3.0"), ``` Finally, include `"SystemPackage"` as a dependency for your executable target: @@ -39,7 +41,7 @@ Finally, include `"SystemPackage"` as a dependency for your executable target: let package = Package( // name, platforms, products, etc. dependencies: [ - .package(url: "https://github.com/apple/swift-system", from: "1.0.0"), + .package(url: "https://github.com/apple/swift-system", from: "1.3.0"), // other dependencies ], targets: [ @@ -57,10 +59,58 @@ The Swift System package is source stable. The version numbers follow [Semantic [semver]: https://semver.org -## Contributing +The public API of the swift-system package consists of non-underscored declarations that are marked `public` in the `SystemPackage` module. + +By "underscored declarations" we mean declarations that have a leading underscore anywhere in their fully qualified name. For instance, here are some names that wouldn't be considered part of the public API, even if they were technically marked public: + +- `FooModule.Bar._someMember(value:)` (underscored member) +- `FooModule._Bar.someMember` (underscored type) +- `_FooModule.Bar` (underscored module) +- `FooModule.Bar.init(_value:)` (underscored initializer) + +Interfaces that aren't part of the public API may continue to change in any release, including patch releases. If you have a use case that requires using non-public APIs, please submit a Feature Request describing it! We'd like the public interface to be as useful as possible -- although preferably without compromising safety or limiting future evolution. + +Future minor versions of the package may update these rules as needed. -Before contributing, please read [CONTRIBUTING.md](CONTRIBUTING.md). +## Toolchain Requirements -## LICENSE +The following table maps existing package releases to their minimum required Swift toolchain release: + +| Package version | Swift version | Xcode release | +| ----------------------- | --------------- | ------------- | +| swift-system 1.3.x | >= Swift 5.8 | >= Xcode 14.3 | +| swift-system 1.4.x (unreleased) | >= Swift 5.9 | >= Xcode 15.0 | + +We'd like this package to quickly embrace Swift language and toolchain improvements that are relevant to its mandate. Accordingly, from time to time, new versions of this package require clients to upgrade to a more recent Swift toolchain release. (This allows the package to make use of new language/stdlib features, build on compiler bug fixes, and adopt new package manager functionality as soon as they are available.) Patch (i.e., bugfix) releases will not increase the required toolchain version, but any minor (i.e., new feature) release may do so. + +(Note: the package has no minimum deployment target, so while it does require clients to use a recent Swift toolchain to build it, the code itself is able to run on any OS release that supports running Swift code.) + +## Licensing See [LICENSE](LICENSE.txt) for license information. + +## Contributing + +Before contributing, please read [CONTRIBUTING.md](CONTRIBUTING.md). + +### Branching Strategy + +We maintain separate branches for each active minor version of the package: + +| Package version | Branch | +| ----------------------- | ----------- | +| swift-system 1.3.x | release/1.3 | +| swift-system 1.4.x (unreleased) | release/1.4 | +| swift-system 1.5.x (unreleased) | main | + +Changes must land on the branch corresponding to the earliest release that they will need to ship on. They are periodically propagated to subsequent branches, in the following direction: + +`release/1.3` → `release/1.4` → `main` + +For example, anything landing on `release/1.3` will eventually appear on `release/1.4` and then `main` too; there is no need to file standalone PRs for each release line. (Change propagation currently requires manual work -- it is performed by project maintainers.) + +### Code of Conduct + +Like all Swift.org projects, we would like the Swift System project to foster a diverse and friendly community. We expect contributors to adhere to the [Swift.org Code of Conduct](https://swift.org/code-of-conduct/). A copy of this document is [available in this repository][coc]. + +[coc]: CODE_OF_CONDUCT.md From d4e2b09c37184249630bad72bf7f44dd5bde689a Mon Sep 17 00:00:00 2001 From: Alastair Houghton Date: Wed, 20 Mar 2024 14:46:10 +0000 Subject: [PATCH 237/427] Fix swift-system to work on Windows. The swift-system tests were not passing, for various reasons, mostly to do with path related confusion. As part of this, add a function to create a temporary directory, as that was one of the problems. Also add Windows support for pipe() and ftruncate(), which was missing. rdar://125087707 --- Sources/System/ErrnoWindows.swift | 20 ++ Sources/System/FileOperations.swift | 8 +- Sources/System/FilePath/FilePathParsing.swift | 10 +- Sources/System/FilePath/FilePathTemp.swift | 256 ++++++++++++++++++ Sources/System/Internals/Mocking.swift | 16 +- Sources/System/Internals/Syscalls.swift | 82 +++++- .../Internals/WindowsSyscallAdapters.swift | 133 ++++++++- Tests/SystemTests/FileOperationsTest.swift | 124 ++++----- .../FilePathComponentsTest.swift | 20 +- .../FilePathTests/FilePathSyntaxTest.swift | 3 + .../FilePathTests/FilePathTempTest.swift | 49 ++++ .../FilePathTests/FilePathTest.swift | 10 + 12 files changed, 641 insertions(+), 90 deletions(-) create mode 100644 Sources/System/ErrnoWindows.swift create mode 100644 Sources/System/FilePath/FilePathTemp.swift create mode 100644 Tests/SystemTests/FilePathTests/FilePathTempTest.swift diff --git a/Sources/System/ErrnoWindows.swift b/Sources/System/ErrnoWindows.swift new file mode 100644 index 00000000..98fda433 --- /dev/null +++ b/Sources/System/ErrnoWindows.swift @@ -0,0 +1,20 @@ +/* + This source file is part of the Swift System open source project + + Copyright (c) 2024 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 +*/ + +#if os(Windows) + +import WinSDK + +extension Errno { + public init(windowsError: DWORD) { + self.init(rawValue: mapWindowsErrorToErrno(windowsError)) + } +} + +#endif diff --git a/Sources/System/FileOperations.swift b/Sources/System/FileOperations.swift index ada80cfe..d2e8d657 100644 --- a/Sources/System/FileOperations.swift +++ b/Sources/System/FileOperations.swift @@ -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 - 2024 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 @@ -369,6 +369,7 @@ extension FileDescriptor { } } +#if !os(WASI) @available(/*System 0.0.2: macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0*/iOS 8, *) extension FileDescriptor { /// Duplicates this file descriptor and return the newly created copy. @@ -433,8 +434,9 @@ extension FileDescriptor { fatalError("Not implemented") } } +#endif -#if !os(Windows) +#if !os(WASI) @available(/*System 1.1.0: macOS 12.3, iOS 15.4, watchOS 8.5, tvOS 15.4*/iOS 8, *) extension FileDescriptor { /// Creates a unidirectional data channel, which can be used for interprocess communication. @@ -463,7 +465,6 @@ extension FileDescriptor { } #endif -#if !os(Windows) @available(/*System 1.2.0: macOS 9999, iOS 9999, watchOS 9999, tvOS 9999*/iOS 8, *) extension FileDescriptor { /// Truncates or extends the file referenced by this file descriptor. @@ -509,4 +510,3 @@ extension FileDescriptor { } } } -#endif diff --git a/Sources/System/FilePath/FilePathParsing.swift b/Sources/System/FilePath/FilePathParsing.swift index 79482bd8..c31e6a5b 100644 --- a/Sources/System/FilePath/FilePathParsing.swift +++ b/Sources/System/FilePath/FilePathParsing.swift @@ -89,6 +89,11 @@ extension SystemString { // `_prenormalizeWindowsRoots` and resume. readIdx = _prenormalizeWindowsRoots() writeIdx = readIdx + + // Skip redundant separators + while readIdx < endIndex && isSeparator(self[readIdx]) { + self.formIndex(after: &readIdx) + } } else { assert(genericSeparator == platformSeparator) } @@ -330,10 +335,13 @@ extension FilePath { // Whether we are providing Windows paths @inline(__always) internal var _windowsPaths: Bool { + if let forceWindowsPaths = forceWindowsPaths { + return forceWindowsPaths + } #if os(Windows) return true #else - return forceWindowsPaths + return false #endif } diff --git a/Sources/System/FilePath/FilePathTemp.swift b/Sources/System/FilePath/FilePathTemp.swift new file mode 100644 index 00000000..9aa65ff9 --- /dev/null +++ b/Sources/System/FilePath/FilePathTemp.swift @@ -0,0 +1,256 @@ +/* + This source file is part of the Swift System open source project + + Copyright (c) 2024 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 +*/ + +// MARK: - API + +public func withTemporaryPath(basename: FilePath.Component, + _ body: (FilePath) throws -> R) throws -> R { + let temporaryDir = try createUniqueTemporaryDirectory(basename: basename) + defer { + try? recursiveRemove(at: temporaryDir) + } + + return try body(temporaryDir) +} + +// MARK: - Internals + +#if os(Windows) +import WinSDK + +internal func getTemporaryDirectory() throws -> FilePath { + return try withUnsafeTemporaryAllocation(of: CInterop.PlatformChar.self, + capacity: Int(MAX_PATH) + 1) { + buffer in + + let count = GetTempPath2W(DWORD(buffer.count), buffer.baseAddress) + if count == 0 { + throw Errno(windowsError: GetLastError()) + } + + return FilePath(SystemString(platformString: buffer.baseAddress!)) + } +} + +internal func forEachFile( + at path: FilePath, + _ body: (WIN32_FIND_DATAW) throws -> () +) rethrows { + let searchPath = path.appending("\\*") + + try searchPath.withPlatformString { szPath in + var findData = WIN32_FIND_DATAW() + let hFind = FindFirstFileW(szPath, &findData) + if hFind == INVALID_HANDLE_VALUE { + throw Errno(windowsError: GetLastError()) + } + defer { + FindClose(hFind) + } + + repeat { + // Skip . and .. + if findData.cFileName.0 == 46 + && (findData.cFileName.1 == 0 + || (findData.cFileName.1 == 46 + && findData.cFileName.2 == 0)) { + continue + } + + try body(findData) + } while FindNextFileW(hFind, &findData) + } +} + +internal func recursiveRemove(at path: FilePath) throws { + // First, deal with subdirectories + try forEachFile(at: path) { findData in + if (findData.dwFileAttributes & DWORD(FILE_ATTRIBUTE_DIRECTORY)) != 0 { + let name = withUnsafeBytes(of: findData.cFileName) { + return SystemString(platformString: $0.assumingMemoryBound( + to: CInterop.PlatformChar.self).baseAddress!) + } + let component = FilePath.Component(name)! + let subpath = path.appending(component) + + try recursiveRemove(at: subpath) + } + } + + // Now delete everything else + try forEachFile(at: path) { findData in + let name = withUnsafeBytes(of: findData.cFileName) { + return SystemString(platformString: $0.assumingMemoryBound( + to: CInterop.PlatformChar.self).baseAddress!) + } + let component = FilePath.Component(name)! + let subpath = path.appending(component) + + if (findData.dwFileAttributes & DWORD(FILE_ATTRIBUTE_DIRECTORY)) == 0 { + try subpath.withPlatformString { + if !DeleteFileW($0) { + throw Errno(windowsError: GetLastError()) + } + } + } + } + + // Finally, delete the parent + try path.withPlatformString { + if !RemoveDirectoryW($0) { + throw Errno(windowsError: GetLastError()) + } + } +} + +#else +internal func getTemporaryDirectory() throws -> FilePath { + return "/tmp" +} + +internal func recursiveRemove(at path: FilePath) throws { + let dirfd = try FileDescriptor.open(path, .readOnly, options: .directory) + defer { + try? dirfd.close() + } + + let dot: (CInterop.PlatformChar, CInterop.PlatformChar) = (46, 0) + try withUnsafeBytes(of: dot) { + try recursiveRemove( + in: dirfd.rawValue, + path: $0.assumingMemoryBound(to: CInterop.PlatformChar.self).baseAddress! + ) + } + + try path.withPlatformString { + if system_rmdir($0) != 0 { + throw Errno.current + } + } +} + +fileprivate func impl_opendirat( + _ dirfd: CInt, + _ path: UnsafePointer +) -> UnsafeMutablePointer? { + let fd = system_openat(dirfd, path, + FileDescriptor.AccessMode.readOnly.rawValue + | FileDescriptor.OpenOptions.directory.rawValue) + if fd < 0 { + return nil + } + return system_fdopendir(fd) +} + +internal func forEachFile( + in dirfd: CInt, path: UnsafePointer, + _ body: (system_dirent) throws -> () +) throws { + guard let dir = impl_opendirat(dirfd, path) else { + throw Errno.current + } + defer { + _ = system_closedir(dir) + } + + while let dirent = system_readdir(dir) { + // Skip . and .. + if dirent.pointee.d_name.0 == 46 + && (dirent.pointee.d_name.1 == 0 + || (dirent.pointee.d_name.1 == 46 + && dirent.pointee.d_name.2 == 0)) { + continue + } + + try body(dirent.pointee) + } +} + +internal func recursiveRemove( + in dirfd: CInt, + path: UnsafePointer +) throws { + // First, deal with subdirectories + try forEachFile(in: dirfd, path: path) { dirent in + if dirent.d_type == SYSTEM_DT_DIR { + try withUnsafeBytes(of: dirent.d_name) { + try recursiveRemove( + in: dirfd, + path: $0.assumingMemoryBound(to: CInterop.PlatformChar.self) + .baseAddress! + ) + } + } + } + + // Now delete the contents of this directory + try forEachFile(in: dirfd, path: path) { dirent in + let flag: CInt + + if dirent.d_type == SYSTEM_DT_DIR { + flag = SYSTEM_AT_REMOVE_DIR + } else { + flag = 0 + } + + let result = withUnsafeBytes(of: dirent.d_name) { + system_unlinkat(dirfd, + $0.assumingMemoryBound(to: CInterop.PlatformChar.self) + .baseAddress!, + flag) + } + + if result != 0 { + throw Errno.current + } + } +} +#endif + +internal let base64 = Array( + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_".utf8 +) + +internal func makeDirectory(at: FilePath) throws -> Bool { + return try at.withPlatformString { + if system_mkdir($0, 0o700) == 0 { + return true + } + let err = system_errno + if err == Errno.fileExists.rawValue { + return false + } else { + throw Errno(rawValue: err) + } + } +} + +internal func createRandomString(length: Int) -> String { + return String( + decoding: (0.. FilePath { + var tempDir = try getTemporaryDirectory() + tempDir.append(basename) + + while true { + tempDir.extension = createRandomString(length: 16) + + if try makeDirectory(at: tempDir) { + return tempDir + } + } +} diff --git a/Sources/System/Internals/Mocking.swift b/Sources/System/Internals/Mocking.swift index 405dc342..08c0f713 100644 --- a/Sources/System/Internals/Mocking.swift +++ b/Sources/System/Internals/Mocking.swift @@ -63,7 +63,7 @@ internal class MockingDriver { // Whether we should pretend to be Windows for syntactic operations // inside FilePath - fileprivate var forceWindowsSyntaxForPaths = false + fileprivate var forceWindowsSyntaxForPaths: Bool? = nil } private let driverKey: _PlatformTLSKey = { makeTLSKey() }() @@ -110,8 +110,8 @@ private var contextualMockingEnabled: Bool { extension MockingDriver { internal static var enabled: Bool { mockingEnabled } - internal static var forceWindowsPaths: Bool { - currentMockingDriver?.forceWindowsSyntaxForPaths ?? false + internal static var forceWindowsPaths: Optional { + currentMockingDriver?.forceWindowsSyntaxForPaths } } @@ -128,7 +128,7 @@ internal var mockingEnabled: Bool { } @inline(__always) -internal var forceWindowsPaths: Bool { +internal var forceWindowsPaths: Optional { #if !ENABLE_MOCKING return false #else @@ -196,15 +196,11 @@ internal func _mockOffT( #endif // ENABLE_MOCKING // Force paths to be treated as Windows syntactically if `enabled` is -// true. +// true, and as POSIX syntactically if not. internal func _withWindowsPaths(enabled: Bool, _ body: () -> ()) { #if ENABLE_MOCKING - guard enabled else { - body() - return - } MockingDriver.withMockingEnabled { driver in - driver.forceWindowsSyntaxForPaths = true + driver.forceWindowsSyntaxForPaths = enabled body() } #else diff --git a/Sources/System/Internals/Syscalls.swift b/Sources/System/Internals/Syscalls.swift index 793fdfdb..4669141d 100644 --- a/Sources/System/Internals/Syscalls.swift +++ b/Sources/System/Internals/Syscalls.swift @@ -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 - 2024 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 @@ -13,6 +13,8 @@ import Darwin import Glibc #elseif canImport(Musl) import Musl +#elseif canImport(WASILibc) +import WASILibc #elseif os(Windows) import ucrt #else @@ -120,6 +122,7 @@ internal func system_pwrite( #endif } +#if !os(WASI) internal func system_dup(_ fd: Int32) -> Int32 { #if ENABLE_MOCKING if mockingEnabled { return _mock(fd) } @@ -133,8 +136,9 @@ internal func system_dup2(_ fd: Int32, _ fd2: Int32) -> Int32 { #endif return dup2(fd, fd2) } +#endif -#if !os(Windows) +#if !os(WASI) internal func system_pipe(_ fds: UnsafeMutablePointer) -> CInt { #if ENABLE_MOCKING if mockingEnabled { return _mock(fds) } @@ -143,11 +147,83 @@ internal func system_pipe(_ fds: UnsafeMutablePointer) -> CInt { } #endif -#if !os(Windows) internal func system_ftruncate(_ fd: Int32, _ length: off_t) -> Int32 { #if ENABLE_MOCKING if mockingEnabled { return _mock(fd, length) } #endif return ftruncate(fd, length) } + +internal func system_mkdir( + _ path: UnsafePointer, + _ mode: CInterop.Mode +) -> CInt { +#if ENABLE_MOCKING + if mockingEnabled { return _mock(path: path, mode) } +#endif + return mkdir(path, mode) +} + +internal func system_rmdir( + _ path: UnsafePointer +) -> CInt { +#if ENABLE_MOCKING + if mockingEnabled { return _mock(path: path) } +#endif + return rmdir(path) +} + +#if !os(Windows) +internal let SYSTEM_AT_REMOVE_DIR = AT_REMOVEDIR +internal let SYSTEM_DT_DIR = DT_DIR +internal typealias system_dirent = dirent +internal typealias system_DIR = DIR + +internal func system_unlinkat( + _ fd: CInt, + _ path: UnsafePointer, + _ flag: CInt +) -> CInt { +#if ENABLE_MOCKING + if mockingEnabled { return _mock(fd, path, flag) } +#endif +return unlinkat(fd, path, flag) +} + +internal func system_fdopendir( + _ fd: CInt +) -> UnsafeMutablePointer? { + return fdopendir(fd) +} + +internal func system_readdir( + _ dir: UnsafeMutablePointer +) -> UnsafeMutablePointer? { + return readdir(dir) +} + +internal func system_rewinddir( + _ dir: UnsafeMutablePointer +) { + return rewinddir(dir) +} + +internal func system_closedir( + _ dir: UnsafeMutablePointer +) -> CInt { + return closedir(dir) +} + +internal func system_openat( + _ fd: CInt, + _ path: UnsafePointer, + _ oflag: Int32 +) -> CInt { +#if ENABLE_MOCKING + if mockingEnabled { + return _mock(fd, path, oflag) + } +#endif + return openat(fd, path, oflag) +} #endif diff --git a/Sources/System/Internals/WindowsSyscallAdapters.swift b/Sources/System/Internals/WindowsSyscallAdapters.swift index 2f3851b6..bc83782d 100644 --- a/Sources/System/Internals/WindowsSyscallAdapters.swift +++ b/Sources/System/Internals/WindowsSyscallAdapters.swift @@ -80,7 +80,7 @@ internal func pread( _ fd: Int32, _ buf: UnsafeMutableRawPointer!, _ nbyte: Int, _ offset: off_t ) -> Int { let handle: intptr_t = _get_osfhandle(fd) - if handle == /* INVALID_HANDLE_VALUE */ -1 { return Int(EBADF) } + if handle == /* INVALID_HANDLE_VALUE */ -1 { ucrt._set_errno(EBADF); return -1 } // NOTE: this is a non-owning handle, do *not* call CloseHandle on it let hFile: HANDLE = HANDLE(bitPattern: handle)! @@ -91,8 +91,7 @@ internal func pread( var nNumberOfBytesRead: DWORD = 0 if !ReadFile(hFile, buf, DWORD(nbyte), &nNumberOfBytesRead, &ovlOverlapped) { - let _ = GetLastError() - // TODO(compnerd) map windows error to errno + ucrt._set_errno(mapWindowsErrorToErrno(GetLastError())) return Int(-1) } return Int(nNumberOfBytesRead) @@ -103,7 +102,7 @@ internal func pwrite( _ fd: Int32, _ buf: UnsafeRawPointer!, _ nbyte: Int, _ offset: off_t ) -> Int { let handle: intptr_t = _get_osfhandle(fd) - if handle == /* INVALID_HANDLE_VALUE */ -1 { return Int(EBADF) } + if handle == /* INVALID_HANDLE_VALUE */ -1 { ucrt._set_errno(EBADF); return -1 } // NOTE: this is a non-owning handle, do *not* call CloseHandle on it let hFile: HANDLE = HANDLE(bitPattern: handle)! @@ -115,11 +114,133 @@ internal func pwrite( var nNumberOfBytesWritten: DWORD = 0 if !WriteFile(hFile, buf, DWORD(nbyte), &nNumberOfBytesWritten, &ovlOverlapped) { - let _ = GetLastError() - // TODO(compnerd) map windows error to errno + ucrt._set_errno(mapWindowsErrorToErrno(GetLastError())) return Int(-1) } return Int(nNumberOfBytesWritten) } +@inline(__always) +internal func pipe(_ fds: UnsafeMutablePointer) -> CInt { + return _pipe(fds, 4096, _O_BINARY | _O_NOINHERIT); +} + +@inline(__always) +internal func ftruncate(_ fd: Int32, _ length: off_t) -> Int32 { + let handle: intptr_t = _get_osfhandle(fd) + if handle == /* INVALID_HANDLE_VALUE */ -1 { ucrt._set_errno(EBADF); return -1 } + + // NOTE: this is a non-owning handle, do *not* call CloseHandle on it + let hFile: HANDLE = HANDLE(bitPattern: handle)! + let liDesiredLength = LARGE_INTEGER(QuadPart: LONGLONG(length)) + var liCurrentOffset = LARGE_INTEGER(QuadPart: 0) + + // Save the current position and restore it when we're done + if !SetFilePointerEx(hFile, liCurrentOffset, &liCurrentOffset, + DWORD(FILE_CURRENT)) { + ucrt._set_errno(mapWindowsErrorToErrno(GetLastError())) + return -1 + } + defer { + _ = SetFilePointerEx(hFile, liCurrentOffset, nil, DWORD(FILE_BEGIN)); + } + + // Truncate (or extend) the file + if !SetFilePointerEx(hFile, liDesiredLength, nil, DWORD(FILE_BEGIN)) + || !SetEndOfFile(hFile) { + ucrt._set_errno(mapWindowsErrorToErrno(GetLastError())) + return -1 + } + + return 0; +} + +@inline(__always) +internal func mkdir( + _ path: UnsafePointer, + _ mode: CInterop.Mode +) -> CInt { + return _wmkdir(path) +} + +@inline(__always) +internal func rmdir( + _ path: UnsafePointer +) -> CInt { + return _wrmdir(path) +} + +@usableFromInline +internal func mapWindowsErrorToErrno(_ errorCode: DWORD) -> CInt { + switch Int32(errorCode) { + case ERROR_SUCCESS: + return 0 + case ERROR_INVALID_FUNCTION, + ERROR_INVALID_ACCESS, + ERROR_INVALID_DATA, + ERROR_INVALID_PARAMETER, + ERROR_NEGATIVE_SEEK: + return EINVAL + case ERROR_FILE_NOT_FOUND, + ERROR_PATH_NOT_FOUND, + ERROR_INVALID_DRIVE, + ERROR_NO_MORE_FILES, + ERROR_BAD_NETPATH, + ERROR_BAD_NET_NAME, + ERROR_BAD_PATHNAME, + ERROR_FILENAME_EXCED_RANGE: + return ENOENT + case ERROR_TOO_MANY_OPEN_FILES: + return EMFILE + case ERROR_ACCESS_DENIED, + ERROR_CURRENT_DIRECTORY, + ERROR_LOCK_VIOLATION, + ERROR_NETWORK_ACCESS_DENIED, + ERROR_CANNOT_MAKE, + ERROR_FAIL_I24, + ERROR_DRIVE_LOCKED, + ERROR_SEEK_ON_DEVICE, + ERROR_NOT_LOCKED, + ERROR_LOCK_FAILED, + ERROR_WRITE_PROTECT...ERROR_SHARING_BUFFER_EXCEEDED: + return EACCES + case ERROR_INVALID_HANDLE, + ERROR_INVALID_TARGET_HANDLE, + ERROR_DIRECT_ACCESS_HANDLE: + return EBADF + case ERROR_ARENA_TRASHED, + ERROR_NOT_ENOUGH_MEMORY, + ERROR_INVALID_BLOCK, + ERROR_NOT_ENOUGH_QUOTA: + return ENOMEM + case ERROR_BAD_ENVIRONMENT: + return E2BIG + case ERROR_BAD_FORMAT, + ERROR_INVALID_STARTING_CODESEG...ERROR_INFLOOP_IN_RELOC_CHAIN: + return ENOEXEC + case ERROR_NOT_SAME_DEVICE: + return EXDEV + case ERROR_FILE_EXISTS, + ERROR_ALREADY_EXISTS: + return EEXIST + case ERROR_NO_PROC_SLOTS, + ERROR_MAX_THRDS_REACHED, + ERROR_NESTING_NOT_ALLOWED: + return EAGAIN + case ERROR_BROKEN_PIPE: + return EPIPE + case ERROR_DISK_FULL: + return ENOSPC + case ERROR_WAIT_NO_CHILDREN, + ERROR_CHILD_NOT_COMPLETE: + return ECHILD + case ERROR_DIR_NOT_EMPTY: + return ENOTEMPTY + case ERROR_NO_UNICODE_TRANSLATION: + return EILSEQ + default: + return EINVAL + } +} + #endif diff --git a/Tests/SystemTests/FileOperationsTest.swift b/Tests/SystemTests/FileOperationsTest.swift index ca178ba5..ee821280 100644 --- a/Tests/SystemTests/FileOperationsTest.swift +++ b/Tests/SystemTests/FileOperationsTest.swift @@ -113,8 +113,7 @@ final class FileOperationsTest: XCTestCase { func testHelpers() { // TODO: Test writeAll, writeAll(toAbsoluteOffset), closeAfter } - -#if !os(Windows) + 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`. @@ -133,33 +132,34 @@ final class FileOperationsTest: XCTestCase { } } } -#endif func testAdHocOpen() { // Ad-hoc test touching a file system. do { // TODO: Test this against a virtual in-memory file system - let fd = try FileDescriptor.open("/tmp/b.txt", .readWrite, options: [.create, .truncate], permissions: .ownerReadWrite) - try fd.closeAfter { - try fd.writeAll("abc".utf8) - var def = "def" - try def.withUTF8 { - _ = try fd.write(UnsafeRawBufferPointer($0)) - } - try fd.seek(offset: 1, from: .start) - - let readLen = 3 - let readBytes = try Array(unsafeUninitializedCapacity: readLen) { (buf, count) in - count = try fd.read(into: UnsafeMutableRawBufferPointer(buf)) + try withTemporaryPath(basename: "testAdhocOpen") { path in + let fd = try FileDescriptor.open(path.appending("b.txt"), .readWrite, options: [.create, .truncate], permissions: .ownerReadWrite) + try fd.closeAfter { + try fd.writeAll("abc".utf8) + var def = "def" + try def.withUTF8 { + _ = try fd.write(UnsafeRawBufferPointer($0)) + } + try fd.seek(offset: 1, from: .start) + + let readLen = 3 + let readBytes = try Array(unsafeUninitializedCapacity: readLen) { (buf, count) in + count = try fd.read(into: UnsafeMutableRawBufferPointer(buf)) + } + let preadBytes = try Array(unsafeUninitializedCapacity: readLen) { (buf, count) in + count = try fd.read(fromAbsoluteOffset: 1, into: UnsafeMutableRawBufferPointer(buf)) + } + + XCTAssertEqual(readBytes.first!, "b".utf8.first!) + XCTAssertEqual(readBytes, preadBytes) + + // TODO: seek } - let preadBytes = try Array(unsafeUninitializedCapacity: readLen) { (buf, count) in - count = try fd.read(fromAbsoluteOffset: 1, into: UnsafeMutableRawBufferPointer(buf)) - } - - XCTAssertEqual(readBytes.first!, "b".utf8.first!) - XCTAssertEqual(readBytes, preadBytes) - - // TODO: seek } } catch let err as Errno { print("caught \(err))") @@ -185,48 +185,48 @@ final class FileOperationsTest: XCTestCase { } -#if !os(Windows) func testResizeFile() throws { - let fd = try FileDescriptor.open("/tmp/\(UUID().uuidString).txt", .readWrite, options: [.create, .truncate], permissions: .ownerReadWrite) - try fd.closeAfter { - // File should be empty initially. - XCTAssertEqual(try fd.fileSize(), 0) - // Write 3 bytes. - try fd.writeAll("abc".utf8) - // File should now be 3 bytes. - XCTAssertEqual(try fd.fileSize(), 3) - // Resize to 6 bytes. - try fd.resize(to: 6) - // File should now be 6 bytes. - XCTAssertEqual(try fd.fileSize(), 6) - // Read in the 6 bytes. - let readBytes = try Array(unsafeUninitializedCapacity: 6) { (buf, count) in - try fd.seek(offset: 0, from: .start) - // Should have read all 6 bytes. - count = try fd.read(into: UnsafeMutableRawBufferPointer(buf)) - XCTAssertEqual(count, 6) - } - // First 3 bytes should be unaffected by resize. - XCTAssertEqual(Array(readBytes[..<3]), Array("abc".utf8)) - // Extension should be padded with zeros. - XCTAssertEqual(Array(readBytes[3...]), Array(repeating: 0, count: 3)) - // File should still be 6 bytes. - XCTAssertEqual(try fd.fileSize(), 6) - // Resize to 2 bytes. - try fd.resize(to: 2) - // File should now be 2 bytes. - XCTAssertEqual(try fd.fileSize(), 2) - // Read in file with a buffer big enough for 6 bytes. - let readBytesAfterTruncation = try Array(unsafeUninitializedCapacity: 6) { (buf, count) in - try fd.seek(offset: 0, from: .start) - count = try fd.read(into: UnsafeMutableRawBufferPointer(buf)) - // Should only have read 2 bytes. - XCTAssertEqual(count, 2) + try withTemporaryPath(basename: "testResizeFile") { path in + let fd = try FileDescriptor.open(path.appending("\(UUID().uuidString).txt"), .readWrite, options: [.create, .truncate], permissions: .ownerReadWrite) + try fd.closeAfter { + // File should be empty initially. + XCTAssertEqual(try fd.fileSize(), 0) + // Write 3 bytes. + try fd.writeAll("abc".utf8) + // File should now be 3 bytes. + XCTAssertEqual(try fd.fileSize(), 3) + // Resize to 6 bytes. + try fd.resize(to: 6) + // File should now be 6 bytes. + XCTAssertEqual(try fd.fileSize(), 6) + // Read in the 6 bytes. + let readBytes = try Array(unsafeUninitializedCapacity: 6) { (buf, count) in + try fd.seek(offset: 0, from: .start) + // Should have read all 6 bytes. + count = try fd.read(into: UnsafeMutableRawBufferPointer(buf)) + XCTAssertEqual(count, 6) + } + // First 3 bytes should be unaffected by resize. + XCTAssertEqual(Array(readBytes[..<3]), Array("abc".utf8)) + // Extension should be padded with zeros. + XCTAssertEqual(Array(readBytes[3...]), Array(repeating: 0, count: 3)) + // File should still be 6 bytes. + XCTAssertEqual(try fd.fileSize(), 6) + // Resize to 2 bytes. + try fd.resize(to: 2) + // File should now be 2 bytes. + XCTAssertEqual(try fd.fileSize(), 2) + // Read in file with a buffer big enough for 6 bytes. + let readBytesAfterTruncation = try Array(unsafeUninitializedCapacity: 6) { (buf, count) in + try fd.seek(offset: 0, from: .start) + count = try fd.read(into: UnsafeMutableRawBufferPointer(buf)) + // Should only have read 2 bytes. + XCTAssertEqual(count, 2) + } + // Written content was trunctated. + XCTAssertEqual(readBytesAfterTruncation, Array("ab".utf8)) } - // Written content was trunctated. - XCTAssertEqual(readBytesAfterTruncation, Array("ab".utf8)) } } -#endif } diff --git a/Tests/SystemTests/FilePathTests/FilePathComponentsTest.swift b/Tests/SystemTests/FilePathTests/FilePathComponentsTest.swift index ad69cb4c..e816bb5a 100644 --- a/Tests/SystemTests/FilePathTests/FilePathComponentsTest.swift +++ b/Tests/SystemTests/FilePathTests/FilePathComponentsTest.swift @@ -231,7 +231,7 @@ final class FilePathComponentsTest: XCTestCase { } func testCases() { - let testPaths: Array = [ + var testPaths: Array = [ TestPathComponents("", root: nil, []), TestPathComponents("/", root: "/", []), TestPathComponents("foo", root: nil, ["foo"]), @@ -240,16 +240,28 @@ final class FilePathComponentsTest: XCTestCase { TestPathComponents("foo/bar", root: nil, ["foo", "bar"]), TestPathComponents("foo/bar/", root: nil, ["foo", "bar"]), TestPathComponents("/foo/bar", root: "/", ["foo", "bar"]), - TestPathComponents("///foo//", root: "/", ["foo"]), TestPathComponents("/foo///bar", root: "/", ["foo", "bar"]), TestPathComponents("foo/bar/", root: nil, ["foo", "bar"]), TestPathComponents("foo///bar/baz/", root: nil, ["foo", "bar", "baz"]), - TestPathComponents("//foo///bar/baz/", root: "/", ["foo", "bar", "baz"]), TestPathComponents("./", root: nil, ["."]), TestPathComponents("./..", root: nil, [".", ".."]), TestPathComponents("/./..//", root: "/", [".", ".."]), ] - testPaths.forEach { $0.runAllTests() } +#if !os(Windows) + testPaths.append(contentsOf:[ + TestPathComponents("///foo//", root: "/", ["foo"]), + TestPathComponents("//foo///bar/baz/", root: "/", ["foo", "bar", "baz"]) + ]) +#else + // On Windows, these are UNC paths + testPaths.append(contentsOf:[ + TestPathComponents("///foo//", root: "///foo//", []), + TestPathComponents("//foo///bar/baz/", root: "//foo//", ["bar", "baz"]) + ]) +#endif + testPaths.forEach { + $0.runAllTests() + } } func testSeparatorNormalization() { diff --git a/Tests/SystemTests/FilePathTests/FilePathSyntaxTest.swift b/Tests/SystemTests/FilePathTests/FilePathSyntaxTest.swift index ccaa7827..d23999a2 100644 --- a/Tests/SystemTests/FilePathTests/FilePathSyntaxTest.swift +++ b/Tests/SystemTests/FilePathTests/FilePathSyntaxTest.swift @@ -1064,7 +1064,10 @@ final class FilePathSyntaxTest: XCTestCase { XCTAssert(path.lexicallyContains("usr")) XCTAssert(path.lexicallyContains("/usr")) XCTAssert(path.lexicallyContains("local/bin")) +#if !os(Windows) + // On Windows, this is a relative path and is still contained XCTAssert(!path.lexicallyContains("/local/bin")) +#endif path.append("..") XCTAssert(!path.lexicallyContains("local/bin")) XCTAssert(path.lexicallyContains("local/bin/..")) diff --git a/Tests/SystemTests/FilePathTests/FilePathTempTest.swift b/Tests/SystemTests/FilePathTests/FilePathTempTest.swift new file mode 100644 index 00000000..0948f78a --- /dev/null +++ b/Tests/SystemTests/FilePathTests/FilePathTempTest.swift @@ -0,0 +1,49 @@ +/* + This source file is part of the Swift System open source project + + Copyright (c) 2024 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 +*/ + +import XCTest + +#if SYSTEM_PACKAGE +import SystemPackage +#else +import System +#endif + +final class TemporaryPathTest: XCTestCase { + func testUnique() throws { + try withTemporaryPath(basename: "test") { path in + let strPath = String(decoding: path) + XCTAssert(strPath.contains("test")) + try withTemporaryPath(basename: "test") { path2 in + let strPath2 = String(decoding: path2) + XCTAssertNotEqual(strPath, strPath2) + } + } + } + + func testCleanup() throws { + var thePath: FilePath? = nil + + try withTemporaryPath(basename: "test") { path in + thePath = path.appending("foo.txt") + let fd = try FileDescriptor.open(thePath!, .readWrite, + options: [.create, .truncate], + permissions: .ownerReadWrite) + _ = try fd.closeAfter { + try fd.writeAll("Hello World".utf8) + } + } + + XCTAssertThrowsError(try FileDescriptor.open(thePath!, .readOnly)) { + error in + + XCTAssertEqual(error as! Errno, Errno.noSuchFileOrDirectory) + } + } +} diff --git a/Tests/SystemTests/FilePathTests/FilePathTest.swift b/Tests/SystemTests/FilePathTests/FilePathTest.swift index 1686faa1..b50cc17e 100644 --- a/Tests/SystemTests/FilePathTests/FilePathTest.swift +++ b/Tests/SystemTests/FilePathTests/FilePathTest.swift @@ -34,6 +34,16 @@ final class FilePathTest: XCTestCase { let filePath: FilePath let string: String let validString: Bool + + init(filePath: FilePath, string: String, validString: Bool) { + self.filePath = filePath + #if os(Windows) + self.string = string.replacingOccurrences(of: "/", with: "\\") + #else + self.string = string + #endif + self.validString = validString + } } #if os(Windows) From 85de4e51e22070056e89dfbc396f23131bb711a5 Mon Sep 17 00:00:00 2001 From: Alastair Houghton Date: Wed, 20 Mar 2024 17:10:30 +0000 Subject: [PATCH 238/427] Use the proper temporary directory on Darwin. It seems we can get the temporary directory from confstr(). rdar://125087707 --- Sources/System/FilePath/FilePathTemp.swift | 26 +++++++++++++++++++ Sources/System/Internals/Syscalls.swift | 12 +++++++++ .../FilePathTests/FilePathTempTest.swift | 9 +++++++ 3 files changed, 47 insertions(+) diff --git a/Sources/System/FilePath/FilePathTemp.swift b/Sources/System/FilePath/FilePathTemp.swift index 9aa65ff9..00286714 100644 --- a/Sources/System/FilePath/FilePathTemp.swift +++ b/Sources/System/FilePath/FilePathTemp.swift @@ -111,7 +111,33 @@ internal func recursiveRemove(at path: FilePath) throws { #else internal func getTemporaryDirectory() throws -> FilePath { + #if SYSTEM_PACKAGE_DARWIN + var capacity = 1024 + while true { + let path: FilePath? = withUnsafeTemporaryAllocation( + of: CInterop.PlatformChar.self, + capacity: 1024) { buffer in + let len = system_confstr(SYSTEM_CS_DARWIN_USER_TEMP_DIR, + buffer.baseAddress!, + buffer.count) + if len == 0 { + // Fall back to "/tmp" if we can't read the temp directory + return "/tmp" + } + // If it was truncated, increase capaciy and try again + if len > buffer.count { + capacity = len + return nil + } + return FilePath(SystemString(platformString: buffer.baseAddress!)) + } + if let path = path { + return path + } + } + #else return "/tmp" + #endif } internal func recursiveRemove(at path: FilePath) throws { diff --git a/Sources/System/Internals/Syscalls.swift b/Sources/System/Internals/Syscalls.swift index 4669141d..cec84137 100644 --- a/Sources/System/Internals/Syscalls.swift +++ b/Sources/System/Internals/Syscalls.swift @@ -173,6 +173,18 @@ internal func system_rmdir( return rmdir(path) } +#if SYSTEM_PACKAGE_DARWIN +internal let SYSTEM_CS_DARWIN_USER_TEMP_DIR = _CS_DARWIN_USER_TEMP_DIR + +internal func system_confstr( + _ name: CInt, + _ buf: UnsafeMutablePointer, + _ len: Int +) -> Int { + return confstr(name, buf, len) +} +#endif + #if !os(Windows) internal let SYSTEM_AT_REMOVE_DIR = AT_REMOVEDIR internal let SYSTEM_DT_DIR = DT_DIR diff --git a/Tests/SystemTests/FilePathTests/FilePathTempTest.swift b/Tests/SystemTests/FilePathTests/FilePathTempTest.swift index 0948f78a..b2b1fa22 100644 --- a/Tests/SystemTests/FilePathTests/FilePathTempTest.swift +++ b/Tests/SystemTests/FilePathTests/FilePathTempTest.swift @@ -16,6 +16,15 @@ import System #endif final class TemporaryPathTest: XCTestCase { + #if SYSTEM_PACKAGE_DARWIN + func testNotInSlashTmp() throws { + try withTemporaryPath(basename: "NotInSlashTmp") { path in + // We shouldn't be using "/tmp" on Darwin + XCTAssertNotEqual(path.components.first!, "tmp") + } + } + #endif + func testUnique() throws { try withTemporaryPath(basename: "test") { path in let strPath = String(decoding: path) From 4cafcc96f6aa635e1bdbe05a4c9b858b752f08cf Mon Sep 17 00:00:00 2001 From: Alastair Houghton Date: Wed, 20 Mar 2024 17:27:38 +0000 Subject: [PATCH 239/427] Changes after comments from Saleem. Use idiomatic Swift notation, not `Optional`. Prefer Win32 APIs to UCRT APIs. rdar://125087707 --- Sources/System/FilePath/FilePathTemp.swift | 5 ++--- Sources/System/Internals/Mocking.swift | 4 ++-- .../System/Internals/WindowsSyscallAdapters.swift | 15 +++++++++++++-- 3 files changed, 17 insertions(+), 7 deletions(-) diff --git a/Sources/System/FilePath/FilePathTemp.swift b/Sources/System/FilePath/FilePathTemp.swift index 00286714..437d2330 100644 --- a/Sources/System/FilePath/FilePathTemp.swift +++ b/Sources/System/FilePath/FilePathTemp.swift @@ -29,8 +29,7 @@ internal func getTemporaryDirectory() throws -> FilePath { capacity: Int(MAX_PATH) + 1) { buffer in - let count = GetTempPath2W(DWORD(buffer.count), buffer.baseAddress) - if count == 0 { + guard GetTempPath2W(DWORD(buffer.count), buffer.baseAddress) != 0 else { throw Errno(windowsError: GetLastError()) } @@ -116,7 +115,7 @@ internal func getTemporaryDirectory() throws -> FilePath { while true { let path: FilePath? = withUnsafeTemporaryAllocation( of: CInterop.PlatformChar.self, - capacity: 1024) { buffer in + capacity: capacity) { buffer in let len = system_confstr(SYSTEM_CS_DARWIN_USER_TEMP_DIR, buffer.baseAddress!, buffer.count) diff --git a/Sources/System/Internals/Mocking.swift b/Sources/System/Internals/Mocking.swift index 08c0f713..2945651c 100644 --- a/Sources/System/Internals/Mocking.swift +++ b/Sources/System/Internals/Mocking.swift @@ -110,7 +110,7 @@ private var contextualMockingEnabled: Bool { extension MockingDriver { internal static var enabled: Bool { mockingEnabled } - internal static var forceWindowsPaths: Optional { + internal static var forceWindowsPaths: Bool? { currentMockingDriver?.forceWindowsSyntaxForPaths } } @@ -128,7 +128,7 @@ internal var mockingEnabled: Bool { } @inline(__always) -internal var forceWindowsPaths: Optional { +internal var forceWindowsPaths: Bool? { #if !ENABLE_MOCKING return false #else diff --git a/Sources/System/Internals/WindowsSyscallAdapters.swift b/Sources/System/Internals/WindowsSyscallAdapters.swift index bc83782d..6d5c755c 100644 --- a/Sources/System/Internals/WindowsSyscallAdapters.swift +++ b/Sources/System/Internals/WindowsSyscallAdapters.swift @@ -160,14 +160,25 @@ internal func mkdir( _ path: UnsafePointer, _ mode: CInterop.Mode ) -> CInt { - return _wmkdir(path) + // TODO: Read/write permissions (these need mapping to a SECURITY_DESCRIPTOR). + if !CreateDirectoryW(path, nil) { + ucrt._set_errno(mapWindowsErrorToErrno(GetLastError())) + return -1 + } + + return 0; } @inline(__always) internal func rmdir( _ path: UnsafePointer ) -> CInt { - return _wrmdir(path) + if !RemoveDirectoryW(path) { + ucrt._set_errno(mapWindowsErrorToErrno(GetLastError())) + return -1 + } + + return 0; } @usableFromInline From e293300430327554476a38ead2a022be3a2c2315 Mon Sep 17 00:00:00 2001 From: Alastair Houghton Date: Thu, 21 Mar 2024 16:15:19 +0000 Subject: [PATCH 240/427] Map mode bits to Windows ACLs. This adds support for mapping POSIX-style mode bits to Windows ACLs when calling `open()` or `mkdir()`. rdar://125087707 --- Sources/System/FilePath/FilePathTemp.swift | 23 +- .../Internals/WindowsSyscallAdapters.swift | 392 +++++++++++++++++- 2 files changed, 395 insertions(+), 20 deletions(-) diff --git a/Sources/System/FilePath/FilePathTemp.swift b/Sources/System/FilePath/FilePathTemp.swift index 437d2330..23a08168 100644 --- a/Sources/System/FilePath/FilePathTemp.swift +++ b/Sources/System/FilePath/FilePathTemp.swift @@ -24,7 +24,7 @@ public func withTemporaryPath(basename: FilePath.Component, #if os(Windows) import WinSDK -internal func getTemporaryDirectory() throws -> FilePath { +fileprivate func getTemporaryDirectory() throws -> FilePath { return try withUnsafeTemporaryAllocation(of: CInterop.PlatformChar.self, capacity: Int(MAX_PATH) + 1) { buffer in @@ -37,7 +37,7 @@ internal func getTemporaryDirectory() throws -> FilePath { } } -internal func forEachFile( +fileprivate func forEachFile( at path: FilePath, _ body: (WIN32_FIND_DATAW) throws -> () ) rethrows { @@ -67,7 +67,7 @@ internal func forEachFile( } } -internal func recursiveRemove(at path: FilePath) throws { +fileprivate func recursiveRemove(at path: FilePath) throws { // First, deal with subdirectories try forEachFile(at: path) { findData in if (findData.dwFileAttributes & DWORD(FILE_ATTRIBUTE_DIRECTORY)) != 0 { @@ -109,13 +109,14 @@ internal func recursiveRemove(at path: FilePath) throws { } #else -internal func getTemporaryDirectory() throws -> FilePath { +fileprivate func getTemporaryDirectory() throws -> FilePath { #if SYSTEM_PACKAGE_DARWIN var capacity = 1024 while true { let path: FilePath? = withUnsafeTemporaryAllocation( of: CInterop.PlatformChar.self, - capacity: capacity) { buffer in + capacity: capacity + ) { buffer in let len = system_confstr(SYSTEM_CS_DARWIN_USER_TEMP_DIR, buffer.baseAddress!, buffer.count) @@ -139,7 +140,7 @@ internal func getTemporaryDirectory() throws -> FilePath { #endif } -internal func recursiveRemove(at path: FilePath) throws { +fileprivate func recursiveRemove(at path: FilePath) throws { let dirfd = try FileDescriptor.open(path, .readOnly, options: .directory) defer { try? dirfd.close() @@ -173,7 +174,7 @@ fileprivate func impl_opendirat( return system_fdopendir(fd) } -internal func forEachFile( +fileprivate func forEachFile( in dirfd: CInt, path: UnsafePointer, _ body: (system_dirent) throws -> () ) throws { @@ -238,11 +239,11 @@ internal func recursiveRemove( } #endif -internal let base64 = Array( +fileprivate let base64 = Array( "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_".utf8 ) -internal func makeDirectory(at: FilePath) throws -> Bool { +fileprivate func makeTempDirectory(at: FilePath) throws -> Bool { return try at.withPlatformString { if system_mkdir($0, 0o700) == 0 { return true @@ -256,7 +257,7 @@ internal func makeDirectory(at: FilePath) throws -> Bool { } } -internal func createRandomString(length: Int) -> String { +fileprivate func createRandomString(length: Int) -> String { return String( decoding: (0.., _ oflag: Int32 ) -> CInt { - var fh: CInt = -1 - _ = _wsopen_s(&fh, path, oflag, _SH_DENYNO, _S_IREAD | _S_IWRITE) - return fh + if (oflag & _O_CREAT) != 0 { + ucrt._set_errno(EINVAL) + return -1 + } + + let decodedFlags = DecodedOpenFlags(oflag) + + var saAttrs = SECURITY_ATTRIBUTES( + nLength: DWORD(MemoryLayout.size), + lpSecurityDescriptor: nil, + bInheritHandle: decodedFlags.bInheritHandle + ) + + let hFile = CreateFileW(path, + decodedFlags.dwDesiredAccess, + DWORD(FILE_SHARE_DELETE + | FILE_SHARE_READ + | FILE_SHARE_WRITE), + &saAttrs, + decodedFlags.dwCreationDisposition, + decodedFlags.dwFlagsAndAttributes, + nil) + + if hFile == INVALID_HANDLE_VALUE { + ucrt._set_errno(mapWindowsErrorToErrno(GetLastError())) + return -1 + } + + return _open_osfhandle(intptr_t(bitPattern: hFile), oflag); } @inline(__always) @@ -26,10 +52,39 @@ internal func open( _ path: UnsafePointer, _ oflag: Int32, _ mode: CInterop.Mode ) -> CInt { - // TODO(compnerd): Apply read/write permissions - var fh: CInt = -1 - _ = _wsopen_s(&fh, path, oflag, _SH_DENYNO, _S_IREAD | _S_IWRITE) - return fh + guard let pSD = createSecurityDescriptor(from: mode) else { + ucrt._set_errno(mapWindowsErrorToErrno(GetLastError())) + return -1 + } + + defer { + pSD.deallocate() + } + + let decodedFlags = DecodedOpenFlags(oflag) + + var saAttrs = SECURITY_ATTRIBUTES( + nLength: DWORD(MemoryLayout.size), + lpSecurityDescriptor: pSD, + bInheritHandle: decodedFlags.bInheritHandle + ) + + let hFile = CreateFileW(path, + decodedFlags.dwDesiredAccess, + DWORD(FILE_SHARE_DELETE + | FILE_SHARE_READ + | FILE_SHARE_WRITE), + &saAttrs, + decodedFlags.dwCreationDisposition, + decodedFlags.dwFlagsAndAttributes, + nil) + + if hFile == INVALID_HANDLE_VALUE { + ucrt._set_errno(mapWindowsErrorToErrno(GetLastError())) + return -1 + } + + return _open_osfhandle(intptr_t(bitPattern: hFile), oflag); } @inline(__always) @@ -160,8 +215,21 @@ internal func mkdir( _ path: UnsafePointer, _ mode: CInterop.Mode ) -> CInt { - // TODO: Read/write permissions (these need mapping to a SECURITY_DESCRIPTOR). - if !CreateDirectoryW(path, nil) { + guard let pSD = createSecurityDescriptor(from: mode) else { + ucrt._set_errno(mapWindowsErrorToErrno(GetLastError())) + return -1 + } + defer { + pSD.deallocate() + } + + var saAttrs = SECURITY_ATTRIBUTES( + nLength: DWORD(MemoryLayout.size), + lpSecurityDescriptor: pSD, + bInheritHandle: false + ) + + if !CreateDirectoryW(path, &saAttrs) { ucrt._set_errno(mapWindowsErrorToErrno(GetLastError())) return -1 } @@ -254,4 +322,310 @@ internal func mapWindowsErrorToErrno(_ errorCode: DWORD) -> CInt { } } +fileprivate func rightsFromModeBits( + _ bits: Int, sticky: Bool = false +) -> DWORD { + var rights: DWORD = 0 + + if (bits & 0o4) != 0 { + rights |= DWORD(FILE_READ_ATTRIBUTES + | FILE_READ_DATA + | FILE_READ_EA + | STANDARD_RIGHTS_READ + | SYNCHRONIZE) + } + if (bits & 0o2) != 0 { + rights |= DWORD(FILE_APPEND_DATA + | FILE_WRITE_ATTRIBUTES + | FILE_WRITE_DATA + | FILE_WRITE_EA + | STANDARD_RIGHTS_WRITE + | SYNCHRONIZE) + if !sticky { + rights |= DWORD(FILE_DELETE_CHILD) + } + } + if (bits & 0o1) != 0 { + rights |= DWORD(FILE_EXECUTE + | FILE_READ_ATTRIBUTES + | STANDARD_RIGHTS_EXECUTE + | SYNCHRONIZE) + } + + return rights +} + +fileprivate func getTokenInformation( + of: T.Type, + hToken: HANDLE, + ticTokenClass: TOKEN_INFORMATION_CLASS +) -> UnsafePointer? { + var capacity = 1024 + for _ in 0..<2 { + let buffer = UnsafeMutableRawPointer.allocate( + byteCount: capacity, + alignment: MemoryLayout.alignment + ) + + var dwLength = DWORD(0) + + if GetTokenInformation(hToken, + ticTokenClass, + buffer, + DWORD(capacity), + &dwLength) { + return UnsafePointer(buffer.assumingMemoryBound(to: T.self)) + } + + buffer.deallocate() + + capacity = Int(dwLength) + } + return nil +} + +/// Build a SECURITY_DESCRIPTOR from UNIX-style "mode" bits. This only +/// takes account of the rwx and sticky bits; there's really nothing that +/// we can do about setuid/setgid. +@usableFromInline +internal func createSecurityDescriptor(from mode: CInterop.Mode) + -> PSECURITY_DESCRIPTOR? { + let ownerPerm = (Int(mode) >> 6) & 0o7 + let groupPerm = (Int(mode) >> 3) & 0o7 + let otherPerm = Int(mode) & 0o7 + + let ownerRights = rightsFromModeBits(ownerPerm) + let groupRights = rightsFromModeBits(groupPerm, sticky: (mode & 0o1000) != 0) + let otherRights = rightsFromModeBits(otherPerm, sticky: (mode & 0o1000) != 0) + + // If group or other permissions are *more* permissive, then we need + // some DENY ACEs as well to implement the expected semantics + let ownerDenyRights = ((ownerRights ^ groupRights) & groupRights) | + ((ownerRights ^ otherRights) & otherRights) + let groupDenyRights = (groupRights ^ otherRights) & otherRights + + var SIDAuthWorld = SID_IDENTIFIER_AUTHORITY(Value: (0, 0, 0, 0, 0, 1)) + var everyone: PSID? = nil + + guard AllocateAndInitializeSid(&SIDAuthWorld, 1, + DWORD(SECURITY_WORLD_RID), + 0, 0, 0, 0, 0, 0, 0, + &everyone) else { + return nil + } + guard let everyone = everyone else { + return nil + } + defer { + FreeSid(everyone) + } + + let hToken = GetCurrentThreadEffectiveToken()! + + guard let pTokenUser = getTokenInformation(of: TOKEN_USER.self, + hToken: hToken, + ticTokenClass: TokenUser) else { + return nil + } + defer { + pTokenUser.deallocate() + } + + guard let pTokenPrimaryGroup = getTokenInformation( + of: TOKEN_PRIMARY_GROUP.self, + hToken: hToken, + ticTokenClass: TokenPrimaryGroup + ) else { + return nil + } + defer { + pTokenPrimaryGroup.deallocate() + } + + let user = pTokenUser.pointee.User.Sid! + let group = pTokenPrimaryGroup.pointee.PrimaryGroup! + + var eas = [ + EXPLICIT_ACCESS_W( + grfAccessPermissions: ownerRights, + grfAccessMode: GRANT_ACCESS, + grfInheritance: DWORD(NO_INHERITANCE), + Trustee: TRUSTEE_W( + pMultipleTrustee: nil, + MultipleTrusteeOperation: NO_MULTIPLE_TRUSTEE, + TrusteeForm: TRUSTEE_IS_SID, + TrusteeType: TRUSTEE_IS_USER, + ptstrName: + user.assumingMemoryBound(to: CInterop.PlatformChar.self) + ) + ), + EXPLICIT_ACCESS_W( + grfAccessPermissions: groupRights, + grfAccessMode: GRANT_ACCESS, + grfInheritance: DWORD(NO_INHERITANCE), + Trustee: TRUSTEE_W( + pMultipleTrustee: nil, + MultipleTrusteeOperation: NO_MULTIPLE_TRUSTEE, + TrusteeForm: TRUSTEE_IS_SID, + TrusteeType: TRUSTEE_IS_GROUP, + ptstrName: + group.assumingMemoryBound(to: CInterop.PlatformChar.self) + ) + ), + EXPLICIT_ACCESS_W( + grfAccessPermissions: otherRights, + grfAccessMode: GRANT_ACCESS, + grfInheritance: DWORD(NO_INHERITANCE), + Trustee: TRUSTEE_W( + pMultipleTrustee: nil, + MultipleTrusteeOperation: NO_MULTIPLE_TRUSTEE, + TrusteeForm: TRUSTEE_IS_SID, + TrusteeType: TRUSTEE_IS_GROUP, + ptstrName: + everyone.assumingMemoryBound(to: CInterop.PlatformChar.self) + ) + ) + ] + + if ownerDenyRights != 0 { + eas.append( + EXPLICIT_ACCESS_W( + grfAccessPermissions: ownerDenyRights, + grfAccessMode: DENY_ACCESS, + grfInheritance: DWORD(NO_INHERITANCE), + Trustee: TRUSTEE_W( + pMultipleTrustee: nil, + MultipleTrusteeOperation: NO_MULTIPLE_TRUSTEE, + TrusteeForm: TRUSTEE_IS_SID, + TrusteeType: TRUSTEE_IS_USER, + ptstrName: + user.assumingMemoryBound(to: CInterop.PlatformChar.self) + ) + ) + ) + } + + if groupDenyRights != 0 { + eas.append( + EXPLICIT_ACCESS_W( + grfAccessPermissions: groupDenyRights, + grfAccessMode: DENY_ACCESS, + grfInheritance: DWORD(NO_INHERITANCE), + Trustee: TRUSTEE_W( + pMultipleTrustee: nil, + MultipleTrusteeOperation: NO_MULTIPLE_TRUSTEE, + TrusteeForm: TRUSTEE_IS_SID, + TrusteeType: TRUSTEE_IS_GROUP, + ptstrName: + group.assumingMemoryBound(to: CInterop.PlatformChar.self) + ) + ) + ) + } + + var pACL: PACL? = nil + guard SetEntriesInAclW(ULONG(eas.count), + &eas, + nil, + &pACL) == ERROR_SUCCESS else { + return nil + } + defer { + LocalFree(pACL) + } + + // Create the security descriptor, making sure that inherited ACEs don't + // take effect, since that wouldn't match the behaviour of mode bits. + var descriptor = SECURITY_DESCRIPTOR() + + guard InitializeSecurityDescriptor(&descriptor, + DWORD(SECURITY_DESCRIPTOR_REVISION)) else { + return nil + } + + guard SetSecurityDescriptorControl(&descriptor, + SECURITY_DESCRIPTOR_CONTROL(SE_DACL_PROTECTED), + SECURITY_DESCRIPTOR_CONTROL(SE_DACL_PROTECTED)) + && SetSecurityDescriptorOwner(&descriptor, user, false) + && SetSecurityDescriptorGroup(&descriptor, group, false) + && SetSecurityDescriptorDacl(&descriptor, + true, + pACL, + false) else { + return nil + } + + // Make it self-contained (up to this point it uses pointers) + var dwRelativeSize = DWORD(0) + + guard !MakeSelfRelativeSD(&descriptor, nil, &dwRelativeSize) + && GetLastError() == ERROR_INSUFFICIENT_BUFFER else { + return nil + } + + let pDescriptor = UnsafeMutableRawPointer.allocate( + byteCount: Int(dwRelativeSize), + alignment: MemoryLayout.alignment + ).assumingMemoryBound(to: SECURITY_DESCRIPTOR.self) + + guard MakeSelfRelativeSD(&descriptor, pDescriptor, &dwRelativeSize) else { + pDescriptor.deallocate() + return nil + } + + return UnsafeMutableRawPointer(pDescriptor) +} + +fileprivate struct DecodedOpenFlags { + var dwDesiredAccess: DWORD + var dwCreationDisposition: DWORD + var bInheritHandle: WindowsBool + var dwFlagsAndAttributes: DWORD + + init(_ oflag: Int32) { + switch oflag & (_O_CREAT | _O_EXCL | _O_TRUNC) { + case _O_CREAT | _O_EXCL, _O_CREAT | _O_EXCL | _O_TRUNC: + dwCreationDisposition = DWORD(CREATE_NEW) + case _O_CREAT: + dwCreationDisposition = DWORD(OPEN_ALWAYS) + case _O_CREAT | _O_TRUNC: + dwCreationDisposition = DWORD(CREATE_ALWAYS) + case _O_TRUNC: + dwCreationDisposition = DWORD(TRUNCATE_EXISTING) + default: + dwCreationDisposition = DWORD(OPEN_EXISTING) + } + + dwDesiredAccess = 0 + if (oflag & _O_RDONLY) != 0 { + dwDesiredAccess |= DWORD(GENERIC_READ) + } + if (oflag & _O_WRONLY) != 0 { + dwDesiredAccess |= DWORD(GENERIC_WRITE) + } + if (oflag & _O_RDWR) != 0 { + dwDesiredAccess |= DWORD(GENERIC_READ) | DWORD(GENERIC_WRITE) + } + + bInheritHandle = WindowsBool((oflag & _O_NOINHERIT) == 0) + + dwFlagsAndAttributes = 0 + if (oflag & _O_SEQUENTIAL) != 0 { + dwFlagsAndAttributes |= DWORD(FILE_FLAG_SEQUENTIAL_SCAN) + } + if (oflag & _O_RANDOM) != 0 { + dwFlagsAndAttributes |= DWORD(FILE_FLAG_RANDOM_ACCESS) + } + if (oflag & _O_TEMPORARY) != 0 { + dwFlagsAndAttributes |= DWORD(FILE_FLAG_DELETE_ON_CLOSE) + } + + if (oflag & _O_SHORT_LIVED) != 0 { + dwFlagsAndAttributes |= DWORD(FILE_ATTRIBUTE_TEMPORARY) + } else { + dwFlagsAndAttributes |= DWORD(FILE_ATTRIBUTE_NORMAL) + } + } +} + #endif From bcc7f6d5693d913acc42539ea5765f0fe89ccef8 Mon Sep 17 00:00:00 2001 From: Alastair Houghton Date: Thu, 21 Mar 2024 16:32:19 +0000 Subject: [PATCH 241/427] Update Sources/System/FilePath/FilePathTemp.swift Co-authored-by: Karoy Lorentey --- Sources/System/FilePath/FilePathTemp.swift | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Sources/System/FilePath/FilePathTemp.swift b/Sources/System/FilePath/FilePathTemp.swift index 23a08168..ea67428d 100644 --- a/Sources/System/FilePath/FilePathTemp.swift +++ b/Sources/System/FilePath/FilePathTemp.swift @@ -9,8 +9,10 @@ // MARK: - API -public func withTemporaryPath(basename: FilePath.Component, - _ body: (FilePath) throws -> R) throws -> R { +public func withTemporaryPath( + basename: FilePath.Component, + _ body: (FilePath) throws -> R +) throws -> R { let temporaryDir = try createUniqueTemporaryDirectory(basename: basename) defer { try? recursiveRemove(at: temporaryDir) From 5ae5d5a3afd5428511f906fcb8bf341803befc9a Mon Sep 17 00:00:00 2001 From: Alastair Houghton Date: Mon, 25 Mar 2024 18:28:18 +0000 Subject: [PATCH 242/427] Refactor FilePathTemp, add tests for permission code. Broke `FilePathTemp.swift` into separate files for POSIX and Windows. Added tests for permissions code on Windows. Added leading underscores to some function names. Made some other functions `fileprivate`. rdar://125087707 --- Sources/System/ErrnoWindows.swift | 2 +- Sources/System/FileOperations.swift | 46 ++- Sources/System/FilePath/FilePathTemp.swift | 263 +++--------------- .../System/FilePath/FilePathTempPosix.swift | 153 ++++++++++ .../System/FilePath/FilePathTempWindows.swift | 114 ++++++++ Sources/System/Internals/Syscalls.swift | 12 + .../Internals/WindowsSyscallAdapters.swift | 72 +++-- Tests/SystemTests/FileOperationsTest.swift | 4 +- .../FileOperationsTestWindows.swift | 241 ++++++++++++++++ .../FilePathTests/FilePathTempTest.swift | 8 +- 10 files changed, 657 insertions(+), 258 deletions(-) create mode 100644 Sources/System/FilePath/FilePathTempPosix.swift create mode 100644 Sources/System/FilePath/FilePathTempWindows.swift create mode 100644 Tests/SystemTests/FileOperationsTestWindows.swift diff --git a/Sources/System/ErrnoWindows.swift b/Sources/System/ErrnoWindows.swift index 98fda433..b94b5778 100644 --- a/Sources/System/ErrnoWindows.swift +++ b/Sources/System/ErrnoWindows.swift @@ -13,7 +13,7 @@ import WinSDK extension Errno { public init(windowsError: DWORD) { - self.init(rawValue: mapWindowsErrorToErrno(windowsError)) + self.init(rawValue: _mapWindowsErrorToErrno(windowsError)) } } diff --git a/Sources/System/FileOperations.swift b/Sources/System/FileOperations.swift index d2e8d657..ba70e875 100644 --- a/Sources/System/FileOperations.swift +++ b/Sources/System/FileOperations.swift @@ -135,8 +135,6 @@ extension FileDescriptor { if let permissions = permissions { return system_open(path, oFlag, permissions.rawValue) } - precondition(!options.contains(.create), - "Create must be given permissions") return system_open(path, oFlag) } return descOrError.map { FileDescriptor(rawValue: $0) } @@ -510,3 +508,47 @@ extension FileDescriptor { } } } + +extension FilePermissions { + /// The file creation permission mask (aka "umask"). + /// + /// Permissions set in this mask will be cleared by functions that create + /// files or directories. Note that this mask is process-wide, and that + /// *getting* it is not thread safe. + @_alwaysEmitIntoClient + public static var creationMask: FilePermissions { + get { + let oldMask = _umask(0o22) + _ = _umask(oldMask) + return FilePermissions(rawValue: oldMask) + } + set { + _ = _umask(newValue.rawValue) + } + } + + /// Change the file creation permission mask, run some code, then + /// restore it to its original value. + /// + /// - Parameters: + /// - permissions: The new permission mask. + /// + /// This is more efficient than reading `creationMask` and restoring it + /// afterwards, because of the way reading the creation mask works. + @_alwaysEmitIntoClient + public static func withCreationMask( + _ permissions: FilePermissions, + body: () throws -> R + ) rethrows -> R { + let oldMask = _umask(permissions.rawValue) + defer { + _ = _umask(oldMask) + } + return try body() + } + + @usableFromInline + internal static func _umask(_ mode: CModeT) -> CModeT { + return system_umask(mode) + } +} diff --git a/Sources/System/FilePath/FilePathTemp.swift b/Sources/System/FilePath/FilePathTemp.swift index ea67428d..7b41d0b8 100644 --- a/Sources/System/FilePath/FilePathTemp.swift +++ b/Sources/System/FilePath/FilePathTemp.swift @@ -9,13 +9,22 @@ // MARK: - API -public func withTemporaryPath( +/// Create a temporary path for the duration of the closure. +/// +/// - Parameters: +/// - basename: The base name for the temporary path. +/// - body: The closure to execute. +/// +/// Creates a temporary directory with a name based on the given `basename`, +/// executes `body`, passing in the path of the created directory, then +/// deletes the directory and all of its contents before returning. +public func withTemporaryFilePath( basename: FilePath.Component, _ body: (FilePath) throws -> R ) throws -> R { let temporaryDir = try createUniqueTemporaryDirectory(basename: basename) defer { - try? recursiveRemove(at: temporaryDir) + try? _recursiveRemove(at: temporaryDir) } return try body(temporaryDir) @@ -23,230 +32,20 @@ public func withTemporaryPath( // MARK: - Internals -#if os(Windows) -import WinSDK - -fileprivate func getTemporaryDirectory() throws -> FilePath { - return try withUnsafeTemporaryAllocation(of: CInterop.PlatformChar.self, - capacity: Int(MAX_PATH) + 1) { - buffer in - - guard GetTempPath2W(DWORD(buffer.count), buffer.baseAddress) != 0 else { - throw Errno(windowsError: GetLastError()) - } - - return FilePath(SystemString(platformString: buffer.baseAddress!)) - } -} - -fileprivate func forEachFile( - at path: FilePath, - _ body: (WIN32_FIND_DATAW) throws -> () -) rethrows { - let searchPath = path.appending("\\*") - - try searchPath.withPlatformString { szPath in - var findData = WIN32_FIND_DATAW() - let hFind = FindFirstFileW(szPath, &findData) - if hFind == INVALID_HANDLE_VALUE { - throw Errno(windowsError: GetLastError()) - } - defer { - FindClose(hFind) - } - - repeat { - // Skip . and .. - if findData.cFileName.0 == 46 - && (findData.cFileName.1 == 0 - || (findData.cFileName.1 == 46 - && findData.cFileName.2 == 0)) { - continue - } - - try body(findData) - } while FindNextFileW(hFind, &findData) - } -} - -fileprivate func recursiveRemove(at path: FilePath) throws { - // First, deal with subdirectories - try forEachFile(at: path) { findData in - if (findData.dwFileAttributes & DWORD(FILE_ATTRIBUTE_DIRECTORY)) != 0 { - let name = withUnsafeBytes(of: findData.cFileName) { - return SystemString(platformString: $0.assumingMemoryBound( - to: CInterop.PlatformChar.self).baseAddress!) - } - let component = FilePath.Component(name)! - let subpath = path.appending(component) - - try recursiveRemove(at: subpath) - } - } - - // Now delete everything else - try forEachFile(at: path) { findData in - let name = withUnsafeBytes(of: findData.cFileName) { - return SystemString(platformString: $0.assumingMemoryBound( - to: CInterop.PlatformChar.self).baseAddress!) - } - let component = FilePath.Component(name)! - let subpath = path.appending(component) - - if (findData.dwFileAttributes & DWORD(FILE_ATTRIBUTE_DIRECTORY)) == 0 { - try subpath.withPlatformString { - if !DeleteFileW($0) { - throw Errno(windowsError: GetLastError()) - } - } - } - } - - // Finally, delete the parent - try path.withPlatformString { - if !RemoveDirectoryW($0) { - throw Errno(windowsError: GetLastError()) - } - } -} - -#else -fileprivate func getTemporaryDirectory() throws -> FilePath { - #if SYSTEM_PACKAGE_DARWIN - var capacity = 1024 - while true { - let path: FilePath? = withUnsafeTemporaryAllocation( - of: CInterop.PlatformChar.self, - capacity: capacity - ) { buffer in - let len = system_confstr(SYSTEM_CS_DARWIN_USER_TEMP_DIR, - buffer.baseAddress!, - buffer.count) - if len == 0 { - // Fall back to "/tmp" if we can't read the temp directory - return "/tmp" - } - // If it was truncated, increase capaciy and try again - if len > buffer.count { - capacity = len - return nil - } - return FilePath(SystemString(platformString: buffer.baseAddress!)) - } - if let path = path { - return path - } - } - #else - return "/tmp" - #endif -} - -fileprivate func recursiveRemove(at path: FilePath) throws { - let dirfd = try FileDescriptor.open(path, .readOnly, options: .directory) - defer { - try? dirfd.close() - } - - let dot: (CInterop.PlatformChar, CInterop.PlatformChar) = (46, 0) - try withUnsafeBytes(of: dot) { - try recursiveRemove( - in: dirfd.rawValue, - path: $0.assumingMemoryBound(to: CInterop.PlatformChar.self).baseAddress! - ) - } - - try path.withPlatformString { - if system_rmdir($0) != 0 { - throw Errno.current - } - } -} - -fileprivate func impl_opendirat( - _ dirfd: CInt, - _ path: UnsafePointer -) -> UnsafeMutablePointer? { - let fd = system_openat(dirfd, path, - FileDescriptor.AccessMode.readOnly.rawValue - | FileDescriptor.OpenOptions.directory.rawValue) - if fd < 0 { - return nil - } - return system_fdopendir(fd) -} - -fileprivate func forEachFile( - in dirfd: CInt, path: UnsafePointer, - _ body: (system_dirent) throws -> () -) throws { - guard let dir = impl_opendirat(dirfd, path) else { - throw Errno.current - } - defer { - _ = system_closedir(dir) - } - - while let dirent = system_readdir(dir) { - // Skip . and .. - if dirent.pointee.d_name.0 == 46 - && (dirent.pointee.d_name.1 == 0 - || (dirent.pointee.d_name.1 == 46 - && dirent.pointee.d_name.2 == 0)) { - continue - } - - try body(dirent.pointee) - } -} - -internal func recursiveRemove( - in dirfd: CInt, - path: UnsafePointer -) throws { - // First, deal with subdirectories - try forEachFile(in: dirfd, path: path) { dirent in - if dirent.d_type == SYSTEM_DT_DIR { - try withUnsafeBytes(of: dirent.d_name) { - try recursiveRemove( - in: dirfd, - path: $0.assumingMemoryBound(to: CInterop.PlatformChar.self) - .baseAddress! - ) - } - } - } - - // Now delete the contents of this directory - try forEachFile(in: dirfd, path: path) { dirent in - let flag: CInt - - if dirent.d_type == SYSTEM_DT_DIR { - flag = SYSTEM_AT_REMOVE_DIR - } else { - flag = 0 - } - - let result = withUnsafeBytes(of: dirent.d_name) { - system_unlinkat(dirfd, - $0.assumingMemoryBound(to: CInterop.PlatformChar.self) - .baseAddress!, - flag) - } - - if result != 0 { - throw Errno.current - } - } -} -#endif - fileprivate let base64 = Array( "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_".utf8 ) -fileprivate func makeTempDirectory(at: FilePath) throws -> Bool { - return try at.withPlatformString { +/// Create a directory that is only accessible to the current user. +/// +/// - Parameters: +/// - path: The path of the directory to create. +/// - Returns: `true` if a new directory was created. +/// +/// This function will throw if there is an error, except if the error +/// is that the directory exists, in which case it returns `false`. +fileprivate func makeLockedDownDirectory(at path: FilePath) throws -> Bool { + return try path.withPlatformString { if system_mkdir($0, 0o700) == 0 { return true } @@ -259,6 +58,11 @@ fileprivate func makeTempDirectory(at: FilePath) throws -> Bool { } } +/// Generate a random string of base64 filename safe characters. +/// +/// - Parameters: +/// - length: The number of characters in the returned string. +/// - Returns: A random string of length `length`. fileprivate func createRandomString(length: Int) -> String { return String( decoding: (0.. String { ) } -internal func createUniqueTemporaryDirectory( +/// Given a base name, create a uniquely named temporary directory. +/// +/// - Parameters: +/// - basename: The base name for the new directory. +/// - Returns: The path to the new directory. +/// +/// Creates a directory in the system temporary directory whose name +/// starts with `basename`, followed by a `.` and then a random +/// string of characters. +fileprivate func createUniqueTemporaryDirectory( basename: FilePath.Component ) throws -> FilePath { - var tempDir = try getTemporaryDirectory() + var tempDir = try _getTemporaryDirectory() tempDir.append(basename) while true { tempDir.extension = createRandomString(length: 16) - if try makeTempDirectory(at: tempDir) { + if try makeLockedDownDirectory(at: tempDir) { return tempDir } } diff --git a/Sources/System/FilePath/FilePathTempPosix.swift b/Sources/System/FilePath/FilePathTempPosix.swift new file mode 100644 index 00000000..623ccae4 --- /dev/null +++ b/Sources/System/FilePath/FilePathTempPosix.swift @@ -0,0 +1,153 @@ +/* + This source file is part of the Swift System open source project + + Copyright (c) 2024 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 + */ + +#if !os(Windows) + +/// Get the path to the system temporary directory. +internal func _getTemporaryDirectory() throws -> FilePath { + guard let tmp = system_getenv("TMPDIR") else { + return "/tmp" + } + + return FilePath(SystemString(platformString: tmp)) +} + +/// Delete the entire contents of a directory, including its subdirectories. +/// +/// - Parameters: +/// - path: The directory to be deleted. +/// +/// Removes a directory completely, including all of its contents. +internal func _recursiveRemove( + at path: FilePath +) throws { + let dirfd = try FileDescriptor.open(path, .readOnly, options: .directory) + defer { + try? dirfd.close() + } + + let dot: (CInterop.PlatformChar, CInterop.PlatformChar) = (46, 0) + try withUnsafeBytes(of: dot) { + try recursiveRemove( + in: dirfd.rawValue, + name: $0.assumingMemoryBound(to: CInterop.PlatformChar.self).baseAddress! + ) + } + + try path.withPlatformString { + if system_rmdir($0) != 0 { + throw Errno.current + } + } +} + +/// Open a directory by reference to its parent and name. +/// +/// - Parameters: +/// - dirfd: An open file descriptor for the parent directory. +/// - name: The name of the directory to open. +/// - Returns: A pointer to a `DIR` structure. +/// +/// This is like `opendir()`, but instead of taking a path, it uses a +/// file descriptor pointing at the parent, thus avoiding path length +/// limits. +fileprivate func impl_opendirat( + _ dirfd: CInt, + _ name: UnsafePointer +) -> UnsafeMutablePointer? { + let fd = system_openat(dirfd, name, + FileDescriptor.AccessMode.readOnly.rawValue + | FileDescriptor.OpenOptions.directory.rawValue) + if fd < 0 { + return nil + } + return system_fdopendir(fd) +} + +/// Invoke a closure for each file within a particular directory. +/// +/// - Parameters: +/// - dirfd: The parent of the directory to be enumerated. +/// - subdir: The subdirectory to be enumerated. +/// - body: The closure that will be invoked. +/// +/// We skip the `.` and `..` pseudo-entries. +fileprivate func forEachFile( + in dirfd: CInt, + subdir: UnsafePointer, + _ body: (system_dirent) throws -> () +) throws { + guard let dir = impl_opendirat(dirfd, subdir) else { + throw Errno.current + } + defer { + _ = system_closedir(dir) + } + + while let dirent = system_readdir(dir) { + // Skip . and .. + if dirent.pointee.d_name.0 == 46 + && (dirent.pointee.d_name.1 == 0 + || (dirent.pointee.d_name.1 == 46 + && dirent.pointee.d_name.2 == 0)) { + continue + } + + try body(dirent.pointee) + } +} + +/// Delete the entire contents of a directory, including its subdirectories. +/// +/// - Parameters: +/// - dirfd: The parent of the directory to be removed. +/// - name: The name of the directory to be removed. +/// +/// Removes a directory completely, including all of its contents. +fileprivate func recursiveRemove( + in dirfd: CInt, + name: UnsafePointer +) throws { + // First, deal with subdirectories + try forEachFile(in: dirfd, subdir: name) { dirent in + if dirent.d_type == SYSTEM_DT_DIR { + try withUnsafeBytes(of: dirent.d_name) { + try recursiveRemove( + in: dirfd, + name: $0.assumingMemoryBound(to: CInterop.PlatformChar.self) + .baseAddress! + ) + } + } + } + + // Now delete the contents of this directory + try forEachFile(in: dirfd, subdir: name) { dirent in + let flag: CInt + + if dirent.d_type == SYSTEM_DT_DIR { + flag = SYSTEM_AT_REMOVE_DIR + } else { + flag = 0 + } + + let result = withUnsafeBytes(of: dirent.d_name) { + system_unlinkat(dirfd, + $0.assumingMemoryBound(to: CInterop.PlatformChar.self) + .baseAddress!, + flag) + } + + if result != 0 { + throw Errno.current + } + } +} + +#endif // !os(Windows) diff --git a/Sources/System/FilePath/FilePathTempWindows.swift b/Sources/System/FilePath/FilePathTempWindows.swift new file mode 100644 index 00000000..0d97edcb --- /dev/null +++ b/Sources/System/FilePath/FilePathTempWindows.swift @@ -0,0 +1,114 @@ +/* + This source file is part of the Swift System open source project + + Copyright (c) 2024 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 +*/ + +#if os(Windows) + +import WinSDK + +/// Get the path to the system temporary directory. +internal func _getTemporaryDirectory() throws -> FilePath { + return try withUnsafeTemporaryAllocation(of: CInterop.PlatformChar.self, + capacity: Int(MAX_PATH) + 1) { + buffer in + + guard GetTempPath2W(DWORD(buffer.count), buffer.baseAddress) != 0 else { + throw Errno(windowsError: GetLastError()) + } + + return FilePath(SystemString(platformString: buffer.baseAddress!)) + } +} + +/// Invoke a closure for each file within a particular directory. +/// +/// - Parameters: +/// - path: The path at which we should enumerate items. +/// - body: The closure that will be invoked. +/// +/// We skip the `.` and `..` pseudo-entries. +fileprivate func forEachFile( + at path: FilePath, + _ body: (WIN32_FIND_DATAW) throws -> () +) rethrows { + let searchPath = path.appending("\\*") + + try searchPath.withPlatformString { szPath in + var findData = WIN32_FIND_DATAW() + let hFind = FindFirstFileW(szPath, &findData) + if hFind == INVALID_HANDLE_VALUE { + throw Errno(windowsError: GetLastError()) + } + defer { + FindClose(hFind) + } + + repeat { + // Skip . and .. + if findData.cFileName.0 == 46 + && (findData.cFileName.1 == 0 + || (findData.cFileName.1 == 46 + && findData.cFileName.2 == 0)) { + continue + } + + try body(findData) + } while FindNextFileW(hFind, &findData) + } +} + +/// Delete the entire contents of a directory, including its subdirectories. +/// +/// - Parameters: +/// - path: The directory to be deleted. +/// +/// Removes a directory completely, including all of its contents. +internal func _recursiveRemove( + at path: FilePath +) throws { + // First, deal with subdirectories + try forEachFile(at: path) { findData in + if (findData.dwFileAttributes & DWORD(FILE_ATTRIBUTE_DIRECTORY)) != 0 { + let name = withUnsafeBytes(of: findData.cFileName) { + return SystemString(platformString: $0.assumingMemoryBound( + to: CInterop.PlatformChar.self).baseAddress!) + } + let component = FilePath.Component(name)! + let subpath = path.appending(component) + + try _recursiveRemove(at: subpath) + } + } + + // Now delete everything else + try forEachFile(at: path) { findData in + let name = withUnsafeBytes(of: findData.cFileName) { + return SystemString(platformString: $0.assumingMemoryBound( + to: CInterop.PlatformChar.self).baseAddress!) + } + let component = FilePath.Component(name)! + let subpath = path.appending(component) + + if (findData.dwFileAttributes & DWORD(FILE_ATTRIBUTE_DIRECTORY)) == 0 { + try subpath.withPlatformString { + if !DeleteFileW($0) { + throw Errno(windowsError: GetLastError()) + } + } + } + } + + // Finally, delete the parent + try path.withPlatformString { + if !RemoveDirectoryW($0) { + throw Errno(windowsError: GetLastError()) + } + } +} + +#endif // os(Windows) diff --git a/Sources/System/Internals/Syscalls.swift b/Sources/System/Internals/Syscalls.swift index cec84137..1c6ed724 100644 --- a/Sources/System/Internals/Syscalls.swift +++ b/Sources/System/Internals/Syscalls.swift @@ -239,3 +239,15 @@ internal func system_openat( return openat(fd, path, oflag) } #endif + +internal func system_umask( + _ mode: CInterop.Mode +) -> CInterop.Mode { + return umask(mode) +} + +internal func system_getenv( + _ name: UnsafePointer +) -> UnsafeMutablePointer? { + return getenv(name) +} diff --git a/Sources/System/Internals/WindowsSyscallAdapters.swift b/Sources/System/Internals/WindowsSyscallAdapters.swift index fa88cbe2..3e3f9c69 100644 --- a/Sources/System/Internals/WindowsSyscallAdapters.swift +++ b/Sources/System/Internals/WindowsSyscallAdapters.swift @@ -12,15 +12,21 @@ import ucrt import WinSDK +fileprivate var _umask: CInterop.Mode = 0o22 + +@inline(__always) +func umask( + _ mode: CInterop.Mode +) -> CInterop.Mode { + let oldMask = _umask + _umask = mode + return oldMask +} + @inline(__always) internal func open( _ path: UnsafePointer, _ oflag: Int32 ) -> CInt { - if (oflag & _O_CREAT) != 0 { - ucrt._set_errno(EINVAL) - return -1 - } - let decodedFlags = DecodedOpenFlags(oflag) var saAttrs = SECURITY_ATTRIBUTES( @@ -40,7 +46,7 @@ internal func open( nil) if hFile == INVALID_HANDLE_VALUE { - ucrt._set_errno(mapWindowsErrorToErrno(GetLastError())) + ucrt._set_errno(_mapWindowsErrorToErrno(GetLastError())) return -1 } @@ -52,8 +58,10 @@ internal func open( _ path: UnsafePointer, _ oflag: Int32, _ mode: CInterop.Mode ) -> CInt { - guard let pSD = createSecurityDescriptor(from: mode) else { - ucrt._set_errno(mapWindowsErrorToErrno(GetLastError())) + let actualMode = mode & ~_umask + + guard let pSD = _createSecurityDescriptor(from: actualMode, for: .file) else { + ucrt._set_errno(_mapWindowsErrorToErrno(GetLastError())) return -1 } @@ -80,7 +88,7 @@ internal func open( nil) if hFile == INVALID_HANDLE_VALUE { - ucrt._set_errno(mapWindowsErrorToErrno(GetLastError())) + ucrt._set_errno(_mapWindowsErrorToErrno(GetLastError())) return -1 } @@ -146,7 +154,7 @@ internal func pread( var nNumberOfBytesRead: DWORD = 0 if !ReadFile(hFile, buf, DWORD(nbyte), &nNumberOfBytesRead, &ovlOverlapped) { - ucrt._set_errno(mapWindowsErrorToErrno(GetLastError())) + ucrt._set_errno(_mapWindowsErrorToErrno(GetLastError())) return Int(-1) } return Int(nNumberOfBytesRead) @@ -169,7 +177,7 @@ internal func pwrite( var nNumberOfBytesWritten: DWORD = 0 if !WriteFile(hFile, buf, DWORD(nbyte), &nNumberOfBytesWritten, &ovlOverlapped) { - ucrt._set_errno(mapWindowsErrorToErrno(GetLastError())) + ucrt._set_errno(_mapWindowsErrorToErrno(GetLastError())) return Int(-1) } return Int(nNumberOfBytesWritten) @@ -193,7 +201,7 @@ internal func ftruncate(_ fd: Int32, _ length: off_t) -> Int32 { // Save the current position and restore it when we're done if !SetFilePointerEx(hFile, liCurrentOffset, &liCurrentOffset, DWORD(FILE_CURRENT)) { - ucrt._set_errno(mapWindowsErrorToErrno(GetLastError())) + ucrt._set_errno(_mapWindowsErrorToErrno(GetLastError())) return -1 } defer { @@ -203,7 +211,7 @@ internal func ftruncate(_ fd: Int32, _ length: off_t) -> Int32 { // Truncate (or extend) the file if !SetFilePointerEx(hFile, liDesiredLength, nil, DWORD(FILE_BEGIN)) || !SetEndOfFile(hFile) { - ucrt._set_errno(mapWindowsErrorToErrno(GetLastError())) + ucrt._set_errno(_mapWindowsErrorToErrno(GetLastError())) return -1 } @@ -215,8 +223,11 @@ internal func mkdir( _ path: UnsafePointer, _ mode: CInterop.Mode ) -> CInt { - guard let pSD = createSecurityDescriptor(from: mode) else { - ucrt._set_errno(mapWindowsErrorToErrno(GetLastError())) + let actualMode = mode & ~_umask + + guard let pSD = _createSecurityDescriptor(from: actualMode, + for: .directory) else { + ucrt._set_errno(_mapWindowsErrorToErrno(GetLastError())) return -1 } defer { @@ -230,7 +241,7 @@ internal func mkdir( ) if !CreateDirectoryW(path, &saAttrs) { - ucrt._set_errno(mapWindowsErrorToErrno(GetLastError())) + ucrt._set_errno(_mapWindowsErrorToErrno(GetLastError())) return -1 } @@ -242,7 +253,7 @@ internal func rmdir( _ path: UnsafePointer ) -> CInt { if !RemoveDirectoryW(path) { - ucrt._set_errno(mapWindowsErrorToErrno(GetLastError())) + ucrt._set_errno(_mapWindowsErrorToErrno(GetLastError())) return -1 } @@ -250,7 +261,7 @@ internal func rmdir( } @usableFromInline -internal func mapWindowsErrorToErrno(_ errorCode: DWORD) -> CInt { +internal func _mapWindowsErrorToErrno(_ errorCode: DWORD) -> CInt { switch Int32(errorCode) { case ERROR_SUCCESS: return 0 @@ -323,7 +334,9 @@ internal func mapWindowsErrorToErrno(_ errorCode: DWORD) -> CInt { } fileprivate func rightsFromModeBits( - _ bits: Int, sticky: Bool = false + _ bits: Int, + sticky: Bool = false, + for fileOrDirectory: _FileOrDirectory ) -> DWORD { var rights: DWORD = 0 @@ -341,7 +354,7 @@ fileprivate func rightsFromModeBits( | FILE_WRITE_EA | STANDARD_RIGHTS_WRITE | SYNCHRONIZE) - if !sticky { + if fileOrDirectory == .directory && !sticky { rights |= DWORD(FILE_DELETE_CHILD) } } @@ -384,19 +397,30 @@ fileprivate func getTokenInformation( return nil } +@usableFromInline +internal enum _FileOrDirectory { + case file + case directory +} + /// Build a SECURITY_DESCRIPTOR from UNIX-style "mode" bits. This only /// takes account of the rwx and sticky bits; there's really nothing that /// we can do about setuid/setgid. @usableFromInline -internal func createSecurityDescriptor(from mode: CInterop.Mode) +internal func _createSecurityDescriptor(from mode: CInterop.Mode, + for fileOrDirectory: _FileOrDirectory) -> PSECURITY_DESCRIPTOR? { let ownerPerm = (Int(mode) >> 6) & 0o7 let groupPerm = (Int(mode) >> 3) & 0o7 let otherPerm = Int(mode) & 0o7 - let ownerRights = rightsFromModeBits(ownerPerm) - let groupRights = rightsFromModeBits(groupPerm, sticky: (mode & 0o1000) != 0) - let otherRights = rightsFromModeBits(otherPerm, sticky: (mode & 0o1000) != 0) + let ownerRights = rightsFromModeBits(ownerPerm, for: fileOrDirectory) + let groupRights = rightsFromModeBits(groupPerm, + sticky: (mode & 0o1000) != 0, + for: fileOrDirectory) + let otherRights = rightsFromModeBits(otherPerm, + sticky: (mode & 0o1000) != 0, + for: fileOrDirectory) // If group or other permissions are *more* permissive, then we need // some DENY ACEs as well to implement the expected semantics diff --git a/Tests/SystemTests/FileOperationsTest.swift b/Tests/SystemTests/FileOperationsTest.swift index ee821280..be5a67d4 100644 --- a/Tests/SystemTests/FileOperationsTest.swift +++ b/Tests/SystemTests/FileOperationsTest.swift @@ -137,7 +137,7 @@ final class FileOperationsTest: XCTestCase { // Ad-hoc test touching a file system. do { // TODO: Test this against a virtual in-memory file system - try withTemporaryPath(basename: "testAdhocOpen") { path in + try withTemporaryFilePath(basename: "testAdhocOpen") { path in let fd = try FileDescriptor.open(path.appending("b.txt"), .readWrite, options: [.create, .truncate], permissions: .ownerReadWrite) try fd.closeAfter { try fd.writeAll("abc".utf8) @@ -186,7 +186,7 @@ final class FileOperationsTest: XCTestCase { } func testResizeFile() throws { - try withTemporaryPath(basename: "testResizeFile") { path in + try withTemporaryFilePath(basename: "testResizeFile") { path in let fd = try FileDescriptor.open(path.appending("\(UUID().uuidString).txt"), .readWrite, options: [.create, .truncate], permissions: .ownerReadWrite) try fd.closeAfter { // File should be empty initially. diff --git a/Tests/SystemTests/FileOperationsTestWindows.swift b/Tests/SystemTests/FileOperationsTestWindows.swift new file mode 100644 index 00000000..35df7312 --- /dev/null +++ b/Tests/SystemTests/FileOperationsTestWindows.swift @@ -0,0 +1,241 @@ +/* + This source file is part of the Swift System open source project + + Copyright (c) 2024 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 +*/ + +import XCTest + +#if os(Windows) + +#if SYSTEM_PACKAGE +import SystemPackage +#else +import System +#endif + +import WinSDK + +@available(iOS 8, *) +final class FileOperationsTestWindows: XCTestCase { + private let r = ACCESS_MASK( + FILE_READ_ATTRIBUTES + | FILE_READ_DATA + | FILE_READ_EA + | STANDARD_RIGHTS_READ + | SYNCHRONIZE + ) + private let w = ACCESS_MASK( + FILE_APPEND_DATA + | FILE_WRITE_ATTRIBUTES + | FILE_WRITE_DATA + | FILE_WRITE_EA + | STANDARD_RIGHTS_WRITE + | SYNCHRONIZE + ) + private let x = ACCESS_MASK( + FILE_EXECUTE + | FILE_READ_ATTRIBUTES + | STANDARD_RIGHTS_EXECUTE + | SYNCHRONIZE + ) + + private struct Test { + var permissions: CModeT + var ownerAccess: ACCESS_MASK + var groupAccess: ACCESS_MASK + var otherAccess: ACCESS_MASK + + init(_ permissions: CModeT, + _ ownerAccess: ACCESS_MASK, + _ groupAccess: ACCESS_MASK, + _ otherAccess: ACCESS_MASK) { + self.permissions = permissions + self.ownerAccess = ownerAccess + self.groupAccess = groupAccess + self.otherAccess = otherAccess + } + } + + /// Retrieve the owner, group and other access masks for a given file. + /// + /// - Parameters: + /// - path: The path to the file to inspect + /// - Returns: A tuple of ACCESS_MASK values. + func getAccessMasks( + path: FilePath + ) -> (ACCESS_MASK, ACCESS_MASK, ACCESS_MASK) { + var SIDAuthWorld = SID_IDENTIFIER_AUTHORITY(Value: (0, 0, 0, 0, 0, 1)) + var psidEveryone: PSID? = nil + + XCTAssert(AllocateAndInitializeSid(&SIDAuthWorld, 1, + DWORD(SECURITY_WORLD_RID), + 0, 0, 0, 0, 0, 0, 0, + &psidEveryone)) + defer { + FreeSid(psidEveryone) + } + + var everyone = TRUSTEE_W( + pMultipleTrustee: nil, + MultipleTrusteeOperation: NO_MULTIPLE_TRUSTEE, + TrusteeForm: TRUSTEE_IS_SID, + TrusteeType: TRUSTEE_IS_GROUP, + ptstrName: + psidEveryone!.assumingMemoryBound(to: CInterop.PlatformChar.self) + ) + + return path.withPlatformString { objectName in + var psidOwner: PSID? = nil + var psidGroup: PSID? = nil + var pDacl: PACL? = nil + var pSD: PSECURITY_DESCRIPTOR? = nil + + XCTAssertEqual(GetNamedSecurityInfoW( + objectName, + SE_FILE_OBJECT, + SECURITY_INFORMATION( + DACL_SECURITY_INFORMATION + | GROUP_SECURITY_INFORMATION + | OWNER_SECURITY_INFORMATION + ), + &psidOwner, + &psidGroup, + &pDacl, + nil, + &pSD), DWORD(ERROR_SUCCESS)) + defer { + LocalFree(pSD) + } + + var owner = TRUSTEE_W( + pMultipleTrustee: nil, + MultipleTrusteeOperation: NO_MULTIPLE_TRUSTEE, + TrusteeForm: TRUSTEE_IS_SID, + TrusteeType: TRUSTEE_IS_USER, + ptstrName: + psidOwner!.assumingMemoryBound(to: CInterop.PlatformChar.self) + ) + var group = TRUSTEE_W( + pMultipleTrustee: nil, + MultipleTrusteeOperation: NO_MULTIPLE_TRUSTEE, + TrusteeForm: TRUSTEE_IS_SID, + TrusteeType: TRUSTEE_IS_GROUP, + ptstrName: + psidGroup!.assumingMemoryBound(to: CInterop.PlatformChar.self) + ) + + var ownerAccess = ACCESS_MASK(0) + var groupAccess = ACCESS_MASK(0) + var otherAccess = ACCESS_MASK(0) + + XCTAssertEqual(GetEffectiveRightsFromAclW( + pDacl, + &owner, + &ownerAccess), DWORD(ERROR_SUCCESS)) + XCTAssertEqual(GetEffectiveRightsFromAclW( + pDacl, + &group, + &groupAccess), DWORD(ERROR_SUCCESS)) + XCTAssertEqual(GetEffectiveRightsFromAclW( + pDacl, + &everyone, + &otherAccess), DWORD(ERROR_SUCCESS)) + + return (ownerAccess, groupAccess, otherAccess) + } + } + + private func runTests(_ tests: [Test], at path: FilePath) throws { + for test in tests { + let octal = String(test.permissions, radix: 8) + let testPath = path.appending("test-\(octal).txt") + let fd = try FileDescriptor.open( + testPath, + .readWrite, + options: [.create, .truncate], + permissions: FilePermissions(rawValue: test.permissions) + ) + _ = try fd.closeAfter { + try fd.writeAll("Hello World".utf8) + } + + let (ownerAccess, groupAccess, otherAccess) + = getAccessMasks(path: testPath) + + XCTAssertEqual(ownerAccess, test.ownerAccess) + XCTAssertEqual(groupAccess, test.groupAccess) + XCTAssertEqual(otherAccess, test.otherAccess) + } + } + + /// Test that the umask works properly + func testUmask() throws { + // Default mask should be 0o022 + XCTAssertEqual(FilePermissions.creationMask, [.groupWrite, .otherWrite]) + + try withTemporaryFilePath(basename: "testUmask") { path in + let tests = [ + Test(0o000, 0, 0, 0), + Test(0o700, r|w|x, 0, 0), + Test(0o770, r|w|x, r|x, 0), + Test(0o777, r|w|x, r|x, r|x) + ] + + try runTests(tests, at: path) + } + + try FilePermissions.withCreationMask([.groupWrite, .groupExecute, + .otherWrite, .otherExecute]) { + try withTemporaryFilePath(basename: "testUmask") { path in + let tests = [ + Test(0o000, 0, 0, 0), + Test(0o700, r|w|x, 0, 0), + Test(0o770, r|w|x, r, 0), + Test(0o777, r|w|x, r, r) + ] + + try runTests(tests, at: path) + } + } + } + + /// Test that setting permissions on a file works as expected + func testPermissions() throws { + try FilePermissions.withCreationMask([]) { + try withTemporaryFilePath(basename: "testPermissions") { path in + let tests = [ + Test(0o000, 0, 0, 0), + + Test(0o400, r, 0, 0), + Test(0o200, w, 0, 0), + Test(0o100, x, 0, 0), + Test(0o040, 0, r, 0), + Test(0o020, 0, w, 0), + Test(0o010, 0, x, 0), + Test(0o004, 0, 0, r), + Test(0o002, 0, 0, w), + Test(0o001, 0, 0, x), + + Test(0o700, r|w|x, 0, 0), + Test(0o770, r|w|x, r|w|x, 0), + Test(0o777, r|w|x, r|w|x, r|w|x), + + Test(0o755, r|w|x, r|x, r|x), + Test(0o644, r|w, r, r), + + Test(0o007, 0, 0, r|w|x), + Test(0o070, 0, r|w|x, 0), + Test(0o077, 0, r|w|x, r|w|x), + ] + + try runTests(tests, at: path) + } + } + } +} + +#endif // os(Windows) diff --git a/Tests/SystemTests/FilePathTests/FilePathTempTest.swift b/Tests/SystemTests/FilePathTests/FilePathTempTest.swift index b2b1fa22..dc8597bb 100644 --- a/Tests/SystemTests/FilePathTests/FilePathTempTest.swift +++ b/Tests/SystemTests/FilePathTests/FilePathTempTest.swift @@ -18,7 +18,7 @@ import System final class TemporaryPathTest: XCTestCase { #if SYSTEM_PACKAGE_DARWIN func testNotInSlashTmp() throws { - try withTemporaryPath(basename: "NotInSlashTmp") { path in + try withTemporaryFilePath(basename: "NotInSlashTmp") { path in // We shouldn't be using "/tmp" on Darwin XCTAssertNotEqual(path.components.first!, "tmp") } @@ -26,10 +26,10 @@ final class TemporaryPathTest: XCTestCase { #endif func testUnique() throws { - try withTemporaryPath(basename: "test") { path in + try withTemporaryFilePath(basename: "test") { path in let strPath = String(decoding: path) XCTAssert(strPath.contains("test")) - try withTemporaryPath(basename: "test") { path2 in + try withTemporaryFilePath(basename: "test") { path2 in let strPath2 = String(decoding: path2) XCTAssertNotEqual(strPath, strPath2) } @@ -39,7 +39,7 @@ final class TemporaryPathTest: XCTestCase { func testCleanup() throws { var thePath: FilePath? = nil - try withTemporaryPath(basename: "test") { path in + try withTemporaryFilePath(basename: "test") { path in thePath = path.appending("foo.txt") let fd = try FileDescriptor.open(thePath!, .readWrite, options: [.create, .truncate], From cf7530848ef64c9b9dae808d90415ad2f8bca77d Mon Sep 17 00:00:00 2001 From: Alastair Houghton Date: Mon, 8 Apr 2024 17:01:42 +0100 Subject: [PATCH 243/427] [Linux] Glibc's DIR doesn't get imported into Swift. Swift ends up with OpaquePointer instead of a typed pointer. rdar://125087707 --- Sources/System/FilePath/FilePathTempPosix.swift | 2 +- Sources/System/Internals/Syscalls.swift | 14 +++++++++----- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/Sources/System/FilePath/FilePathTempPosix.swift b/Sources/System/FilePath/FilePathTempPosix.swift index 623ccae4..e8713c2b 100644 --- a/Sources/System/FilePath/FilePathTempPosix.swift +++ b/Sources/System/FilePath/FilePathTempPosix.swift @@ -60,7 +60,7 @@ internal func _recursiveRemove( fileprivate func impl_opendirat( _ dirfd: CInt, _ name: UnsafePointer -) -> UnsafeMutablePointer? { +) -> system_DIRPtr? { let fd = system_openat(dirfd, name, FileDescriptor.AccessMode.readOnly.rawValue | FileDescriptor.OpenOptions.directory.rawValue) diff --git a/Sources/System/Internals/Syscalls.swift b/Sources/System/Internals/Syscalls.swift index 1c6ed724..3794d6e0 100644 --- a/Sources/System/Internals/Syscalls.swift +++ b/Sources/System/Internals/Syscalls.swift @@ -189,7 +189,11 @@ internal func system_confstr( internal let SYSTEM_AT_REMOVE_DIR = AT_REMOVEDIR internal let SYSTEM_DT_DIR = DT_DIR internal typealias system_dirent = dirent -internal typealias system_DIR = DIR +#if os(Linux) +internal typealias system_DIRPtr = OpaquePointer +#else +internal typealias system_DIRPtr = UnsafeMutablePointer +#endif internal func system_unlinkat( _ fd: CInt, @@ -204,24 +208,24 @@ return unlinkat(fd, path, flag) internal func system_fdopendir( _ fd: CInt -) -> UnsafeMutablePointer? { +) -> system_DIRPtr? { return fdopendir(fd) } internal func system_readdir( - _ dir: UnsafeMutablePointer + _ dir: system_DIRPtr ) -> UnsafeMutablePointer? { return readdir(dir) } internal func system_rewinddir( - _ dir: UnsafeMutablePointer + _ dir: system_DIRPtr ) { return rewinddir(dir) } internal func system_closedir( - _ dir: UnsafeMutablePointer + _ dir: system_DIRPtr ) -> CInt { return closedir(dir) } From f1d7eb52c6b2073a7ee0264a65c49b32d406f5e3 Mon Sep 17 00:00:00 2001 From: Alastair Houghton Date: Mon, 13 May 2024 11:19:43 +0100 Subject: [PATCH 244/427] Remove accidentally public functions. These can't be new API as they haven't been reviewed. --- Sources/System/ErrnoWindows.swift | 2 +- Sources/System/FileOperations.swift | 4 ++-- Sources/System/FilePath/FilePathTemp.swift | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Sources/System/ErrnoWindows.swift b/Sources/System/ErrnoWindows.swift index b94b5778..cc481cb7 100644 --- a/Sources/System/ErrnoWindows.swift +++ b/Sources/System/ErrnoWindows.swift @@ -12,7 +12,7 @@ import WinSDK extension Errno { - public init(windowsError: DWORD) { + internal init(windowsError: DWORD) { self.init(rawValue: _mapWindowsErrorToErrno(windowsError)) } } diff --git a/Sources/System/FileOperations.swift b/Sources/System/FileOperations.swift index ba70e875..34128344 100644 --- a/Sources/System/FileOperations.swift +++ b/Sources/System/FileOperations.swift @@ -516,7 +516,7 @@ extension FilePermissions { /// files or directories. Note that this mask is process-wide, and that /// *getting* it is not thread safe. @_alwaysEmitIntoClient - public static var creationMask: FilePermissions { + internal static var creationMask: FilePermissions { get { let oldMask = _umask(0o22) _ = _umask(oldMask) @@ -536,7 +536,7 @@ extension FilePermissions { /// This is more efficient than reading `creationMask` and restoring it /// afterwards, because of the way reading the creation mask works. @_alwaysEmitIntoClient - public static func withCreationMask( + internal static func withCreationMask( _ permissions: FilePermissions, body: () throws -> R ) rethrows -> R { diff --git a/Sources/System/FilePath/FilePathTemp.swift b/Sources/System/FilePath/FilePathTemp.swift index 7b41d0b8..c0edf95d 100644 --- a/Sources/System/FilePath/FilePathTemp.swift +++ b/Sources/System/FilePath/FilePathTemp.swift @@ -18,7 +18,7 @@ /// Creates a temporary directory with a name based on the given `basename`, /// executes `body`, passing in the path of the created directory, then /// deletes the directory and all of its contents before returning. -public func withTemporaryFilePath( +internal func withTemporaryFilePath( basename: FilePath.Component, _ body: (FilePath) throws -> R ) throws -> R { From 53471ce33bf15d7981092bdfb35bd75f03efaf03 Mon Sep 17 00:00:00 2001 From: Alastair Houghton Date: Mon, 13 May 2024 14:18:17 +0100 Subject: [PATCH 245/427] [Windows] Fix a problem with opening files read only. `_O_RDONLY` is zero, so we can't test for it by AND-ing. --- Sources/System/Internals/WindowsSyscallAdapters.swift | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/Sources/System/Internals/WindowsSyscallAdapters.swift b/Sources/System/Internals/WindowsSyscallAdapters.swift index 3e3f9c69..1e35ee97 100644 --- a/Sources/System/Internals/WindowsSyscallAdapters.swift +++ b/Sources/System/Internals/WindowsSyscallAdapters.swift @@ -621,14 +621,15 @@ fileprivate struct DecodedOpenFlags { } dwDesiredAccess = 0 - if (oflag & _O_RDONLY) != 0 { + switch (oflag & (_O_RDONLY|_O_WRONLY|_O_RDWR)) { + case _O_RDONLY: dwDesiredAccess |= DWORD(GENERIC_READ) - } - if (oflag & _O_WRONLY) != 0 { + case _O_WRONLY: dwDesiredAccess |= DWORD(GENERIC_WRITE) - } - if (oflag & _O_RDWR) != 0 { + case _O_RDWR: dwDesiredAccess |= DWORD(GENERIC_READ) | DWORD(GENERIC_WRITE) + default: + break } bInheritHandle = WindowsBool((oflag & _O_NOINHERIT) == 0) From 3a4e2f8071478c2542833da3ba692ca3717f96a8 Mon Sep 17 00:00:00 2001 From: Alastair Houghton Date: Mon, 13 May 2024 14:19:33 +0100 Subject: [PATCH 246/427] [Windows] Fix tests. We need to use `@testable` when importing `SystemPackage` in order to let us test the `internal` functions. Also, use `NUL` instead of `/dev/null`, and generate random bytes in a file instead of reading from `/dev/random`. --- Tests/SystemTests/FileOperationsTest.swift | 56 +++++++++++++++---- .../FileOperationsTestWindows.swift | 4 +- .../FilePathTests/FilePathTempTest.swift | 4 +- 3 files changed, 50 insertions(+), 14 deletions(-) diff --git a/Tests/SystemTests/FileOperationsTest.swift b/Tests/SystemTests/FileOperationsTest.swift index be5a67d4..ed05dcf4 100644 --- a/Tests/SystemTests/FileOperationsTest.swift +++ b/Tests/SystemTests/FileOperationsTest.swift @@ -10,9 +10,9 @@ import XCTest #if SYSTEM_PACKAGE -import SystemPackage +@testable import SystemPackage #else -import System +@testable import System #endif @available(/*System 0.0.1: macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0*/iOS 8, *) @@ -87,7 +87,11 @@ final class FileOperationsTest: XCTestCase { } func testWriteFromEmptyBuffer() throws { + #if os(Windows) + let fd = try FileDescriptor.open(FilePath("NUL"), .writeOnly) + #else let fd = try FileDescriptor.open(FilePath("/dev/null"), .writeOnly) + #endif let written1 = try fd.write(toAbsoluteOffset: 0, .init(start: nil, count: 0)) XCTAssertEqual(written1, 0) @@ -98,16 +102,48 @@ final class FileOperationsTest: XCTestCase { XCTAssertEqual(written2, 0) } + #if os(Windows) + // Generate a file containing random bytes; this should not be used + // for cryptography, it's just for testing. + func generateRandomData(at path: FilePath, count: Int) throws { + let fd = try FileDescriptor.open(path, .readWrite, + options: [.create, .truncate]) + defer { + try! fd.close() + } + let data = [UInt8]( + sequence(first: 0, + next: { + _ in UInt8.random(in: UInt8.min...UInt8.max) + }).dropFirst().prefix(count) + ) + + try data.withUnsafeBytes { + _ = try fd.write($0) + } + } + #endif + func testReadToEmptyBuffer() throws { - let fd = try FileDescriptor.open(FilePath("/dev/random"), .readOnly) - let read1 = try fd.read(fromAbsoluteOffset: 0, into: .init(start: nil, count: 0)) - XCTAssertEqual(read1, 0) + try withTemporaryFilePath(basename: "testReadToEmptyBuffer") { path in + #if os(Windows) + // Windows doesn't have an equivalent to /dev/random, so generate + // some random bytes and write them to a file for the next step. + let randomPath = path.appending("random.txt") + try generateRandomData(at: randomPath, count: 16) + let fd = try FileDescriptor.open(randomPath, .readOnly) + #else // !os(Windows) + let fd = try FileDescriptor.open(FilePath("/dev/random"), .readOnly) + #endif + let read1 = try fd.read(fromAbsoluteOffset: 0, into: .init(start: nil, count: 0)) + XCTAssertEqual(read1, 0) - let pointer = UnsafeMutableRawPointer.allocate(byteCount: 8, alignment: 8) - defer { pointer.deallocate() } - let empty = UnsafeMutableRawBufferPointer(start: pointer, count: 0) - let read2 = try fd.read(fromAbsoluteOffset: 0, into: empty) - XCTAssertEqual(read2, 0) + let pointer = UnsafeMutableRawPointer.allocate(byteCount: 8, alignment: 8) + defer { pointer.deallocate() } + let empty = UnsafeMutableRawBufferPointer(start: pointer, count: 0) + let read2 = try fd.read(fromAbsoluteOffset: 0, into: empty) + XCTAssertEqual(read2, 0) + } } func testHelpers() { diff --git a/Tests/SystemTests/FileOperationsTestWindows.swift b/Tests/SystemTests/FileOperationsTestWindows.swift index 35df7312..82030516 100644 --- a/Tests/SystemTests/FileOperationsTestWindows.swift +++ b/Tests/SystemTests/FileOperationsTestWindows.swift @@ -12,9 +12,9 @@ import XCTest #if os(Windows) #if SYSTEM_PACKAGE -import SystemPackage +@testable import SystemPackage #else -import System +@testable import System #endif import WinSDK diff --git a/Tests/SystemTests/FilePathTests/FilePathTempTest.swift b/Tests/SystemTests/FilePathTests/FilePathTempTest.swift index dc8597bb..30d58261 100644 --- a/Tests/SystemTests/FilePathTests/FilePathTempTest.swift +++ b/Tests/SystemTests/FilePathTests/FilePathTempTest.swift @@ -10,9 +10,9 @@ import XCTest #if SYSTEM_PACKAGE -import SystemPackage +@testable import SystemPackage #else -import System +@testable import System #endif final class TemporaryPathTest: XCTestCase { From 6c64372b85f4b46af3e5b9e3519860cc0ab534dc Mon Sep 17 00:00:00 2001 From: Alastair Houghton Date: Tue, 14 May 2024 14:42:57 +0100 Subject: [PATCH 247/427] [Windows] Add a comment to explain the O_RDONLY/O_WRONLY/O_RDWR logic. The values of these flags on Windows do not overlap, and more particularly, `O_RDONLY` is actually zero on Windows, which means we cannot test for it by AND-ing. --- Sources/System/Internals/WindowsSyscallAdapters.swift | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Sources/System/Internals/WindowsSyscallAdapters.swift b/Sources/System/Internals/WindowsSyscallAdapters.swift index 1e35ee97..048377b1 100644 --- a/Sources/System/Internals/WindowsSyscallAdapters.swift +++ b/Sources/System/Internals/WindowsSyscallAdapters.swift @@ -620,6 +620,9 @@ fileprivate struct DecodedOpenFlags { dwCreationDisposition = DWORD(OPEN_EXISTING) } + // The _O_RDONLY, _O_WRONLY and _O_RDWR flags are non-overlapping + // on Windows; in particular, _O_RDONLY is zero, which means we can't + // test for it by AND-ing. dwDesiredAccess = 0 switch (oflag & (_O_RDONLY|_O_WRONLY|_O_RDWR)) { case _O_RDONLY: From 9eb1f34cbfe6dc99c894f6cd3550fe6aa512666b Mon Sep 17 00:00:00 2001 From: George Barnett Date: Mon, 19 Aug 2024 10:18:30 +0100 Subject: [PATCH 248/427] Add support for macCatalyst Building swift-system for macCatalyst fails as there is no import of the Darwin module. # Conflicts: # Package.swift --- Package.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Package.swift b/Package.swift index 411a2959..b71ade66 100644 --- a/Package.swift +++ b/Package.swift @@ -19,7 +19,7 @@ let cSettings: [CSetting] = [ let swiftSettings: [SwiftSetting] = [ .define( "SYSTEM_PACKAGE_DARWIN", - .when(platforms: [.macOS, .iOS, .watchOS, .tvOS, .visionOS])), + .when(platforms: [.macOS, .macCatalyst, .iOS, .watchOS, .tvOS, .visionOS])), .define("SYSTEM_PACKAGE"), .define("ENABLE_MOCKING", .when(configuration: .debug)), ] From b9e4d2b2baca22f537da2004532b2586d207d1f6 Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Wed, 16 Oct 2024 16:25:48 -0700 Subject: [PATCH 249/427] Remove more accidentally public functions --- Sources/System/FileOperations.swift | 3 --- Sources/System/Internals/WindowsSyscallAdapters.swift | 3 --- 2 files changed, 6 deletions(-) diff --git a/Sources/System/FileOperations.swift b/Sources/System/FileOperations.swift index 34128344..fc9fc932 100644 --- a/Sources/System/FileOperations.swift +++ b/Sources/System/FileOperations.swift @@ -515,7 +515,6 @@ extension FilePermissions { /// Permissions set in this mask will be cleared by functions that create /// files or directories. Note that this mask is process-wide, and that /// *getting* it is not thread safe. - @_alwaysEmitIntoClient internal static var creationMask: FilePermissions { get { let oldMask = _umask(0o22) @@ -535,7 +534,6 @@ extension FilePermissions { /// /// This is more efficient than reading `creationMask` and restoring it /// afterwards, because of the way reading the creation mask works. - @_alwaysEmitIntoClient internal static func withCreationMask( _ permissions: FilePermissions, body: () throws -> R @@ -547,7 +545,6 @@ extension FilePermissions { return try body() } - @usableFromInline internal static func _umask(_ mode: CModeT) -> CModeT { return system_umask(mode) } diff --git a/Sources/System/Internals/WindowsSyscallAdapters.swift b/Sources/System/Internals/WindowsSyscallAdapters.swift index 048377b1..d470bd1f 100644 --- a/Sources/System/Internals/WindowsSyscallAdapters.swift +++ b/Sources/System/Internals/WindowsSyscallAdapters.swift @@ -260,7 +260,6 @@ internal func rmdir( return 0; } -@usableFromInline internal func _mapWindowsErrorToErrno(_ errorCode: DWORD) -> CInt { switch Int32(errorCode) { case ERROR_SUCCESS: @@ -397,7 +396,6 @@ fileprivate func getTokenInformation( return nil } -@usableFromInline internal enum _FileOrDirectory { case file case directory @@ -406,7 +404,6 @@ internal enum _FileOrDirectory { /// Build a SECURITY_DESCRIPTOR from UNIX-style "mode" bits. This only /// takes account of the rwx and sticky bits; there's really nothing that /// we can do about setuid/setgid. -@usableFromInline internal func _createSecurityDescriptor(from mode: CInterop.Mode, for fileOrDirectory: _FileOrDirectory) -> PSECURITY_DESCRIPTOR? { From e7ffa45882f8bdc7e21b94448fa0f225e0de883d Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Wed, 31 Jul 2024 14:56:04 -0700 Subject: [PATCH 250/427] Remove private-and-@_aEIC initializers These were just passthroughs. --- Sources/System/Errno.swift | 221 +++++++++++++-------------- Sources/System/FileDescriptor.swift | 27 ++-- Sources/System/FilePermissions.swift | 51 +++---- 3 files changed, 145 insertions(+), 154 deletions(-) diff --git a/Sources/System/Errno.swift b/Sources/System/Errno.swift index 7f0aabea..498ccbcf 100644 --- a/Sources/System/Errno.swift +++ b/Sources/System/Errno.swift @@ -20,13 +20,10 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { @_alwaysEmitIntoClient public init(rawValue: CInt) { self.rawValue = rawValue } - @_alwaysEmitIntoClient - private init(_ raw: CInt) { self.init(rawValue: raw) } - #if SYSTEM_PACKAGE_DARWIN /// Error. Not used. @_alwaysEmitIntoClient - public static var notUsed: Errno { Errno(_ERRNO_NOT_USED) } + public static var notUsed: Errno { .init(rawValue: _ERRNO_NOT_USED) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "notUsed") @@ -41,7 +38,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `EPERM`. @_alwaysEmitIntoClient - public static var notPermitted: Errno { Errno(_EPERM) } + public static var notPermitted: Errno { .init(rawValue: _EPERM) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "notPermitted") @@ -54,7 +51,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `ENOENT`. @_alwaysEmitIntoClient - public static var noSuchFileOrDirectory: Errno { Errno(_ENOENT) } + public static var noSuchFileOrDirectory: Errno { .init(rawValue: _ENOENT) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "noSuchFileOrDirectory") @@ -66,7 +63,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `ESRCH`. @_alwaysEmitIntoClient - public static var noSuchProcess: Errno { Errno(_ESRCH) } + public static var noSuchProcess: Errno { .init(rawValue: _ESRCH) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "noSuchProcess") @@ -81,7 +78,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `EINTR`. @_alwaysEmitIntoClient - public static var interrupted: Errno { Errno(_EINTR) } + public static var interrupted: Errno { .init(rawValue: _EINTR) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "interrupted") @@ -96,7 +93,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `EIO`. @_alwaysEmitIntoClient - public static var ioError: Errno { Errno(_EIO) } + public static var ioError: Errno { .init(rawValue: _EIO) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "ioError") @@ -111,7 +108,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `ENXIO`. @_alwaysEmitIntoClient - public static var noSuchAddressOrDevice: Errno { Errno(_ENXIO) } + public static var noSuchAddressOrDevice: Errno { .init(rawValue: _ENXIO) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "noSuchAddressOrDevice") @@ -125,7 +122,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `E2BIG`. @_alwaysEmitIntoClient - public static var argListTooLong: Errno { Errno(_E2BIG) } + public static var argListTooLong: Errno { .init(rawValue: _E2BIG) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "argListTooLong") @@ -139,7 +136,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `ENOEXEC`. @_alwaysEmitIntoClient - public static var execFormatError: Errno { Errno(_ENOEXEC) } + public static var execFormatError: Errno { .init(rawValue: _ENOEXEC) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "execFormatError") @@ -154,7 +151,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `EBADF`. @_alwaysEmitIntoClient - public static var badFileDescriptor: Errno { Errno(_EBADF) } + public static var badFileDescriptor: Errno { .init(rawValue: _EBADF) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "badFileDescriptor") @@ -168,7 +165,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `ECHILD`. @_alwaysEmitIntoClient - public static var noChildProcess: Errno { Errno(_ECHILD) } + public static var noChildProcess: Errno { .init(rawValue: _ECHILD) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "noChildProcess") @@ -181,7 +178,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `EDEADLK`. @_alwaysEmitIntoClient - public static var deadlock: Errno { Errno(_EDEADLK) } + public static var deadlock: Errno { .init(rawValue: _EDEADLK) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "deadlock") @@ -198,7 +195,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `ENOMEM`. @_alwaysEmitIntoClient - public static var noMemory: Errno { Errno(_ENOMEM) } + public static var noMemory: Errno { .init(rawValue: _ENOMEM) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "noMemory") @@ -211,7 +208,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `EACCES`. @_alwaysEmitIntoClient - public static var permissionDenied: Errno { Errno(_EACCES) } + public static var permissionDenied: Errno { .init(rawValue: _EACCES) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "permissionDenied") @@ -223,7 +220,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `EFAULT`. @_alwaysEmitIntoClient - public static var badAddress: Errno { Errno(_EFAULT) } + public static var badAddress: Errno { .init(rawValue: _EFAULT) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "badAddress") @@ -236,7 +233,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `ENOTBLK`. @_alwaysEmitIntoClient - public static var notBlockDevice: Errno { Errno(_ENOTBLK) } + public static var notBlockDevice: Errno { .init(rawValue: _ENOTBLK) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "notBlockDevice") @@ -250,7 +247,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `EBUSY`. @_alwaysEmitIntoClient - public static var resourceBusy: Errno { Errno(_EBUSY) } + public static var resourceBusy: Errno { .init(rawValue: _EBUSY) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "resourceBusy") @@ -263,7 +260,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `EEXIST`. @_alwaysEmitIntoClient - public static var fileExists: Errno { Errno(_EEXIST) } + public static var fileExists: Errno { .init(rawValue: _EEXIST) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "fileExists") @@ -275,7 +272,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `EXDEV`. @_alwaysEmitIntoClient - public static var improperLink: Errno { Errno(_EXDEV) } + public static var improperLink: Errno { .init(rawValue: _EXDEV) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "improperLink") @@ -288,7 +285,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `ENODEV`. @_alwaysEmitIntoClient - public static var operationNotSupportedByDevice: Errno { Errno(_ENODEV) } + public static var operationNotSupportedByDevice: Errno { .init(rawValue: _ENODEV) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "operationNotSupportedByDevice") @@ -302,7 +299,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `ENOTDIR`. @_alwaysEmitIntoClient - public static var notDirectory: Errno { Errno(_ENOTDIR) } + public static var notDirectory: Errno { .init(rawValue: _ENOTDIR) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "notDirectory") @@ -315,7 +312,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `EISDIR`. @_alwaysEmitIntoClient - public static var isDirectory: Errno { Errno(_EISDIR) } + public static var isDirectory: Errno { .init(rawValue: _EISDIR) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "isDirectory") @@ -328,7 +325,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `EINVAL`. @_alwaysEmitIntoClient - public static var invalidArgument: Errno { Errno(_EINVAL) } + public static var invalidArgument: Errno { .init(rawValue: _EINVAL) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "invalidArgument") @@ -343,7 +340,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `ENFILE`. @_alwaysEmitIntoClient - public static var tooManyOpenFilesInSystem: Errno { Errno(_ENFILE) } + public static var tooManyOpenFilesInSystem: Errno { .init(rawValue: _ENFILE) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "tooManyOpenFilesInSystem") @@ -356,7 +353,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `EMFILE`. @_alwaysEmitIntoClient - public static var tooManyOpenFiles: Errno { Errno(_EMFILE) } + public static var tooManyOpenFiles: Errno { .init(rawValue: _EMFILE) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "tooManyOpenFiles") @@ -371,7 +368,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `ENOTTY`. @_alwaysEmitIntoClient - public static var inappropriateIOCTLForDevice: Errno { Errno(_ENOTTY) } + public static var inappropriateIOCTLForDevice: Errno { .init(rawValue: _ENOTTY) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "inappropriateIOCTLForDevice") @@ -386,7 +383,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `ETXTBSY`. @_alwaysEmitIntoClient - public static var textFileBusy: Errno { Errno(_ETXTBSY) } + public static var textFileBusy: Errno { .init(rawValue: _ETXTBSY) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "textFileBusy") @@ -401,7 +398,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `EFBIG`. @_alwaysEmitIntoClient - public static var fileTooLarge: Errno { Errno(_EFBIG) } + public static var fileTooLarge: Errno { .init(rawValue: _EFBIG) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "fileTooLarge") @@ -418,7 +415,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `ENOSPC`. @_alwaysEmitIntoClient - public static var noSpace: Errno { Errno(_ENOSPC) } + public static var noSpace: Errno { .init(rawValue: _ENOSPC) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "noSpace") @@ -430,7 +427,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `ESPIPE`. @_alwaysEmitIntoClient - public static var illegalSeek: Errno { Errno(_ESPIPE) } + public static var illegalSeek: Errno { .init(rawValue: _ESPIPE) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "illegalSeek") @@ -443,7 +440,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `EROFS`. @_alwaysEmitIntoClient - public static var readOnlyFileSystem: Errno { Errno(_EROFS) } + public static var readOnlyFileSystem: Errno { .init(rawValue: _EROFS) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "readOnlyFileSystem") @@ -456,7 +453,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `EMLINK`. @_alwaysEmitIntoClient - public static var tooManyLinks: Errno { Errno(_EMLINK) } + public static var tooManyLinks: Errno { .init(rawValue: _EMLINK) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "tooManyLinks") @@ -469,7 +466,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `EPIPE`. @_alwaysEmitIntoClient - public static var brokenPipe: Errno { Errno(_EPIPE) } + public static var brokenPipe: Errno { .init(rawValue: _EPIPE) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "brokenPipe") @@ -482,7 +479,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `EDOM`. @_alwaysEmitIntoClient - public static var outOfDomain: Errno { Errno(_EDOM) } + public static var outOfDomain: Errno { .init(rawValue: _EDOM) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "outOfDomain") @@ -497,7 +494,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `ERANGE`. @_alwaysEmitIntoClient - public static var outOfRange: Errno { Errno(_ERANGE) } + public static var outOfRange: Errno { .init(rawValue: _ERANGE) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "outOfRange") @@ -511,7 +508,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `EAGAIN`. @_alwaysEmitIntoClient - public static var resourceTemporarilyUnavailable: Errno { Errno(_EAGAIN) } + public static var resourceTemporarilyUnavailable: Errno { .init(rawValue: _EAGAIN) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "resourceTemporarilyUnavailable") @@ -526,7 +523,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `EINPROGRESS`. @_alwaysEmitIntoClient - public static var nowInProgress: Errno { Errno(_EINPROGRESS) } + public static var nowInProgress: Errno { .init(rawValue: _EINPROGRESS) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "nowInProgress") @@ -539,7 +536,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `EALREADY`. @_alwaysEmitIntoClient - public static var alreadyInProcess: Errno { Errno(_EALREADY) } + public static var alreadyInProcess: Errno { .init(rawValue: _EALREADY) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "alreadyInProcess") @@ -549,7 +546,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `ENOTSOCK`. @_alwaysEmitIntoClient - public static var notSocket: Errno { Errno(_ENOTSOCK) } + public static var notSocket: Errno { .init(rawValue: _ENOTSOCK) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "notSocket") @@ -561,7 +558,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `EDESTADDRREQ`. @_alwaysEmitIntoClient - public static var addressRequired: Errno { Errno(_EDESTADDRREQ) } + public static var addressRequired: Errno { .init(rawValue: _EDESTADDRREQ) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "addressRequired") @@ -574,7 +571,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `EMSGSIZE`. @_alwaysEmitIntoClient - public static var messageTooLong: Errno { Errno(_EMSGSIZE) } + public static var messageTooLong: Errno { .init(rawValue: _EMSGSIZE) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "messageTooLong") @@ -589,7 +586,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `EPROTOTYPE`. @_alwaysEmitIntoClient - public static var protocolWrongTypeForSocket: Errno { Errno(_EPROTOTYPE) } + public static var protocolWrongTypeForSocket: Errno { .init(rawValue: _EPROTOTYPE) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "protocolWrongTypeForSocket") @@ -602,7 +599,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `ENOPROTOOPT`. @_alwaysEmitIntoClient - public static var protocolNotAvailable: Errno { Errno(_ENOPROTOOPT) } + public static var protocolNotAvailable: Errno { .init(rawValue: _ENOPROTOOPT) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "protocolNotAvailable") @@ -615,7 +612,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `EPROTONOSUPPORT`. @_alwaysEmitIntoClient - public static var protocolNotSupported: Errno { Errno(_EPROTONOSUPPORT) } + public static var protocolNotSupported: Errno { .init(rawValue: _EPROTONOSUPPORT) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "protocolNotSupported") @@ -628,7 +625,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `ESOCKTNOSUPPORT`. @_alwaysEmitIntoClient - public static var socketTypeNotSupported: Errno { Errno(_ESOCKTNOSUPPORT) } + public static var socketTypeNotSupported: Errno { .init(rawValue: _ESOCKTNOSUPPORT) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "socketTypeNotSupported") @@ -641,7 +638,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `ENOTSUP`. @_alwaysEmitIntoClient - public static var notSupported: Errno { Errno(_ENOTSUP) } + public static var notSupported: Errno { .init(rawValue: _ENOTSUP) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "notSupported") @@ -654,7 +651,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `EPFNOSUPPORT`. @_alwaysEmitIntoClient - public static var protocolFamilyNotSupported: Errno { Errno(_EPFNOSUPPORT) } + public static var protocolFamilyNotSupported: Errno { .init(rawValue: _EPFNOSUPPORT) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "protocolFamilyNotSupported") @@ -668,7 +665,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `EAFNOSUPPORT`. @_alwaysEmitIntoClient - public static var addressFamilyNotSupported: Errno { Errno(_EAFNOSUPPORT) } + public static var addressFamilyNotSupported: Errno { .init(rawValue: _EAFNOSUPPORT) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "addressFamilyNotSupported") @@ -680,7 +677,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `EADDRINUSE`. @_alwaysEmitIntoClient - public static var addressInUse: Errno { Errno(_EADDRINUSE) } + public static var addressInUse: Errno { .init(rawValue: _EADDRINUSE) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "addressInUse") @@ -693,7 +690,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `EADDRNOTAVAIL`. @_alwaysEmitIntoClient - public static var addressNotAvailable: Errno { Errno(_EADDRNOTAVAIL) } + public static var addressNotAvailable: Errno { .init(rawValue: _EADDRNOTAVAIL) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "addressNotAvailable") @@ -705,7 +702,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `ENETDOWN`. @_alwaysEmitIntoClient - public static var networkDown: Errno { Errno(_ENETDOWN) } + public static var networkDown: Errno { .init(rawValue: _ENETDOWN) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "networkDown") @@ -717,7 +714,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `ENETUNREACH`. @_alwaysEmitIntoClient - public static var networkUnreachable: Errno { Errno(_ENETUNREACH) } + public static var networkUnreachable: Errno { .init(rawValue: _ENETUNREACH) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "networkUnreachable") @@ -729,7 +726,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `ENETRESET`. @_alwaysEmitIntoClient - public static var networkReset: Errno { Errno(_ENETRESET) } + public static var networkReset: Errno { .init(rawValue: _ENETRESET) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "networkReset") @@ -741,7 +738,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `ECONNABORTED`. @_alwaysEmitIntoClient - public static var connectionAbort: Errno { Errno(_ECONNABORTED) } + public static var connectionAbort: Errno { .init(rawValue: _ECONNABORTED) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "connectionAbort") @@ -755,7 +752,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `ECONNRESET`. @_alwaysEmitIntoClient - public static var connectionReset: Errno { Errno(_ECONNRESET) } + public static var connectionReset: Errno { .init(rawValue: _ECONNRESET) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "connectionReset") @@ -769,7 +766,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `ENOBUFS`. @_alwaysEmitIntoClient - public static var noBufferSpace: Errno { Errno(_ENOBUFS) } + public static var noBufferSpace: Errno { .init(rawValue: _ENOBUFS) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "noBufferSpace") @@ -784,7 +781,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `EISCONN`. @_alwaysEmitIntoClient - public static var socketIsConnected: Errno { Errno(_EISCONN) } + public static var socketIsConnected: Errno { .init(rawValue: _EISCONN) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "socketIsConnected") @@ -799,7 +796,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `ENOTCONN`. @_alwaysEmitIntoClient - public static var socketNotConnected: Errno { Errno(_ENOTCONN) } + public static var socketNotConnected: Errno { .init(rawValue: _ENOTCONN) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "socketNotConnected") @@ -813,7 +810,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `ESHUTDOWN`. @_alwaysEmitIntoClient - public static var socketShutdown: Errno { Errno(_ESHUTDOWN) } + public static var socketShutdown: Errno { .init(rawValue: _ESHUTDOWN) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "socketShutdown") @@ -828,7 +825,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `ETIMEDOUT`. @_alwaysEmitIntoClient - public static var timedOut: Errno { Errno(_ETIMEDOUT) } + public static var timedOut: Errno { .init(rawValue: _ETIMEDOUT) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "timedOut") @@ -843,7 +840,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `ECONNREFUSED`. @_alwaysEmitIntoClient - public static var connectionRefused: Errno { Errno(_ECONNREFUSED) } + public static var connectionRefused: Errno { .init(rawValue: _ECONNREFUSED) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "connectionRefused") @@ -855,7 +852,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `ELOOP`. @_alwaysEmitIntoClient - public static var tooManySymbolicLinkLevels: Errno { Errno(_ELOOP) } + public static var tooManySymbolicLinkLevels: Errno { .init(rawValue: _ELOOP) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "tooManySymbolicLinkLevels") @@ -868,7 +865,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `ENAMETOOLONG`. @_alwaysEmitIntoClient - public static var fileNameTooLong: Errno { Errno(_ENAMETOOLONG) } + public static var fileNameTooLong: Errno { .init(rawValue: _ENAMETOOLONG) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "fileNameTooLong") @@ -880,7 +877,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `EHOSTDOWN`. @_alwaysEmitIntoClient - public static var hostIsDown: Errno { Errno(_EHOSTDOWN) } + public static var hostIsDown: Errno { .init(rawValue: _EHOSTDOWN) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "hostIsDown") @@ -892,7 +889,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `EHOSTUNREACH`. @_alwaysEmitIntoClient - public static var noRouteToHost: Errno { Errno(_EHOSTUNREACH) } + public static var noRouteToHost: Errno { .init(rawValue: _EHOSTUNREACH) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "noRouteToHost") @@ -905,7 +902,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `ENOTEMPTY`. @_alwaysEmitIntoClient - public static var directoryNotEmpty: Errno { Errno(_ENOTEMPTY) } + public static var directoryNotEmpty: Errno { .init(rawValue: _ENOTEMPTY) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "directoryNotEmpty") @@ -916,7 +913,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `EPROCLIM`. @_alwaysEmitIntoClient - public static var tooManyProcesses: Errno { Errno(_EPROCLIM) } + public static var tooManyProcesses: Errno { .init(rawValue: _EPROCLIM) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "tooManyProcesses") @@ -929,7 +926,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `EUSERS`. @_alwaysEmitIntoClient - public static var tooManyUsers: Errno { Errno(_EUSERS) } + public static var tooManyUsers: Errno { .init(rawValue: _EUSERS) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "tooManyUsers") @@ -946,7 +943,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `EDQUOT`. @_alwaysEmitIntoClient - public static var diskQuotaExceeded: Errno { Errno(_EDQUOT) } + public static var diskQuotaExceeded: Errno { .init(rawValue: _EDQUOT) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "diskQuotaExceeded") @@ -961,7 +958,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `ESTALE`. @_alwaysEmitIntoClient - public static var staleNFSFileHandle: Errno { Errno(_ESTALE) } + public static var staleNFSFileHandle: Errno { .init(rawValue: _ESTALE) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "staleNFSFileHandle") @@ -976,7 +973,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `EBADRPC`. @_alwaysEmitIntoClient - public static var rpcUnsuccessful: Errno { Errno(_EBADRPC) } + public static var rpcUnsuccessful: Errno { .init(rawValue: _EBADRPC) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "rpcUnsuccessful") @@ -989,7 +986,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `ERPCMISMATCH`. @_alwaysEmitIntoClient - public static var rpcVersionMismatch: Errno { Errno(_ERPCMISMATCH) } + public static var rpcVersionMismatch: Errno { .init(rawValue: _ERPCMISMATCH) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "rpcVersionMismatch") @@ -1001,7 +998,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `EPROGUNAVAIL`. @_alwaysEmitIntoClient - public static var rpcProgramUnavailable: Errno { Errno(_EPROGUNAVAIL) } + public static var rpcProgramUnavailable: Errno { .init(rawValue: _EPROGUNAVAIL) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "rpcProgramUnavailable") @@ -1014,7 +1011,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `EPROGMISMATCH`. @_alwaysEmitIntoClient - public static var rpcProgramVersionMismatch: Errno { Errno(_EPROGMISMATCH) } + public static var rpcProgramVersionMismatch: Errno { .init(rawValue: _EPROGMISMATCH) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "rpcProgramVersionMismatch") @@ -1027,7 +1024,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `EPROCUNAVAIL`. @_alwaysEmitIntoClient - public static var rpcProcedureUnavailable: Errno { Errno(_EPROCUNAVAIL) } + public static var rpcProcedureUnavailable: Errno { .init(rawValue: _EPROCUNAVAIL) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "rpcProcedureUnavailable") @@ -1041,7 +1038,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `ENOLCK`. @_alwaysEmitIntoClient - public static var noLocks: Errno { Errno(_ENOLCK) } + public static var noLocks: Errno { .init(rawValue: _ENOLCK) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "noLocks") @@ -1053,7 +1050,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `ENOSYS`. @_alwaysEmitIntoClient - public static var noFunction: Errno { Errno(_ENOSYS) } + public static var noFunction: Errno { .init(rawValue: _ENOSYS) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "noFunction") @@ -1068,7 +1065,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `EFTYPE`. @_alwaysEmitIntoClient - public static var badFileTypeOrFormat: Errno { Errno(_EFTYPE) } + public static var badFileTypeOrFormat: Errno { .init(rawValue: _EFTYPE) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "badFileTypeOrFormat") @@ -1082,7 +1079,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `EAUTH`. @_alwaysEmitIntoClient - public static var authenticationError: Errno { Errno(_EAUTH) } + public static var authenticationError: Errno { .init(rawValue: _EAUTH) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "authenticationError") @@ -1095,7 +1092,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `ENEEDAUTH`. @_alwaysEmitIntoClient - public static var needAuthenticator: Errno { Errno(_ENEEDAUTH) } + public static var needAuthenticator: Errno { .init(rawValue: _ENEEDAUTH) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "needAuthenticator") @@ -1107,7 +1104,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `EPWROFF`. @_alwaysEmitIntoClient - public static var devicePowerIsOff: Errno { Errno(_EPWROFF) } + public static var devicePowerIsOff: Errno { .init(rawValue: _EPWROFF) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "devicePowerIsOff") @@ -1120,7 +1117,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `EDEVERR`. @_alwaysEmitIntoClient - public static var deviceError: Errno { Errno(_EDEVERR) } + public static var deviceError: Errno { .init(rawValue: _EDEVERR) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "deviceError") @@ -1135,7 +1132,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `EOVERFLOW`. @_alwaysEmitIntoClient - public static var overflow: Errno { Errno(_EOVERFLOW) } + public static var overflow: Errno { .init(rawValue: _EOVERFLOW) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "overflow") @@ -1149,7 +1146,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `EBADEXEC`. @_alwaysEmitIntoClient - public static var badExecutable: Errno { Errno(_EBADEXEC) } + public static var badExecutable: Errno { .init(rawValue: _EBADEXEC) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "badExecutable") @@ -1161,7 +1158,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `EBADARCH`. @_alwaysEmitIntoClient - public static var badCPUType: Errno { Errno(_EBADARCH) } + public static var badCPUType: Errno { .init(rawValue: _EBADARCH) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "badCPUType") @@ -1174,7 +1171,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `ESHLIBVERS`. @_alwaysEmitIntoClient - public static var sharedLibraryVersionMismatch: Errno { Errno(_ESHLIBVERS) } + public static var sharedLibraryVersionMismatch: Errno { .init(rawValue: _ESHLIBVERS) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "sharedLibraryVersionMismatch") @@ -1186,7 +1183,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `EBADMACHO`. @_alwaysEmitIntoClient - public static var malformedMachO: Errno { Errno(_EBADMACHO) } + public static var malformedMachO: Errno { .init(rawValue: _EBADMACHO) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "malformedMachO") @@ -1199,7 +1196,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `ECANCELED`. @_alwaysEmitIntoClient - public static var canceled: Errno { Errno(_ECANCELED) } + public static var canceled: Errno { .init(rawValue: _ECANCELED) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "canceled") @@ -1212,7 +1209,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `EIDRM`. @_alwaysEmitIntoClient - public static var identifierRemoved: Errno { Errno(_EIDRM) } + public static var identifierRemoved: Errno { .init(rawValue: _EIDRM) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "identifierRemoved") @@ -1225,7 +1222,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `ENOMSG`. @_alwaysEmitIntoClient - public static var noMessage: Errno { Errno(_ENOMSG) } + public static var noMessage: Errno { .init(rawValue: _ENOMSG) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "noMessage") @@ -1240,7 +1237,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `EILSEQ`. @_alwaysEmitIntoClient - public static var illegalByteSequence: Errno { Errno(_EILSEQ) } + public static var illegalByteSequence: Errno { .init(rawValue: _EILSEQ) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "illegalByteSequence") @@ -1253,7 +1250,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `ENOATTR`. @_alwaysEmitIntoClient - public static var attributeNotFound: Errno { Errno(_ENOATTR) } + public static var attributeNotFound: Errno { .init(rawValue: _ENOATTR) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "attributeNotFound") @@ -1268,7 +1265,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `EBADMSG`. @_alwaysEmitIntoClient - public static var badMessage: Errno { Errno(_EBADMSG) } + public static var badMessage: Errno { .init(rawValue: _EBADMSG) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "badMessage") @@ -1281,7 +1278,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `EMULTIHOP`. @_alwaysEmitIntoClient - public static var multiHop: Errno { Errno(_EMULTIHOP) } + public static var multiHop: Errno { .init(rawValue: _EMULTIHOP) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "multiHop") @@ -1293,7 +1290,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `ENODATA`. @_alwaysEmitIntoClient - public static var noData: Errno { Errno(_ENODATA) } + public static var noData: Errno { .init(rawValue: _ENODATA) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "noData") @@ -1305,7 +1302,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `ENOLINK`. @_alwaysEmitIntoClient - public static var noLink: Errno { Errno(_ENOLINK) } + public static var noLink: Errno { .init(rawValue: _ENOLINK) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "noLink") @@ -1317,7 +1314,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `ENOSR`. @_alwaysEmitIntoClient - public static var noStreamResources: Errno { Errno(_ENOSR) } + public static var noStreamResources: Errno { .init(rawValue: _ENOSR) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "noStreamResources") @@ -1329,7 +1326,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `ENOSTR`. @_alwaysEmitIntoClient - public static var notStream: Errno { Errno(_ENOSTR) } + public static var notStream: Errno { .init(rawValue: _ENOSTR) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "notStream") @@ -1344,7 +1341,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `EPROTO`. @_alwaysEmitIntoClient - public static var protocolError: Errno { Errno(_EPROTO) } + public static var protocolError: Errno { .init(rawValue: _EPROTO) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "protocolError") @@ -1357,7 +1354,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `ETIME`. @_alwaysEmitIntoClient - public static var timeout: Errno { Errno(_ETIME) } + public static var timeout: Errno { .init(rawValue: _ETIME) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "timeout") @@ -1372,7 +1369,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// /// The corresponding C error is `EOPNOTSUPP`. @_alwaysEmitIntoClient - public static var notSupportedOnSocket: Errno { Errno(_EOPNOTSUPP) } + public static var notSupportedOnSocket: Errno { .init(rawValue: _EOPNOTSUPP) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "notSupportedOnSocket") @@ -1387,7 +1384,7 @@ extension Errno { /// /// The corresponding C error is `EWOULDBLOCK`. @_alwaysEmitIntoClient - public static var wouldBlock: Errno { Errno(_EWOULDBLOCK) } + public static var wouldBlock: Errno { .init(rawValue: _EWOULDBLOCK) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "wouldBlock") @@ -1397,7 +1394,7 @@ extension Errno { /// /// The corresponding C error is `ETOOMANYREFS`. @_alwaysEmitIntoClient - public static var tooManyReferences: Errno { Errno(_ETOOMANYREFS) } + public static var tooManyReferences: Errno { .init(rawValue: _ETOOMANYREFS) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "tooManyReferences") @@ -1407,7 +1404,7 @@ extension Errno { /// /// The corresponding C error is `EREMOTE`. @_alwaysEmitIntoClient - public static var tooManyRemoteLevels: Errno { Errno(_EREMOTE) } + public static var tooManyRemoteLevels: Errno { .init(rawValue: _EREMOTE) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "tooManyRemoteLevels") @@ -1418,7 +1415,7 @@ extension Errno { /// /// The corresponding C error is `ENOPOLICY`. @_alwaysEmitIntoClient - public static var noSuchPolicy: Errno { Errno(_ENOPOLICY) } + public static var noSuchPolicy: Errno { .init(rawValue: _ENOPOLICY) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "noSuchPolicy") @@ -1430,7 +1427,7 @@ extension Errno { /// /// The corresponding C error is `ENOTRECOVERABLE`. @_alwaysEmitIntoClient - public static var notRecoverable: Errno { Errno(_ENOTRECOVERABLE) } + public static var notRecoverable: Errno { .init(rawValue: _ENOTRECOVERABLE) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "notRecoverable") @@ -1440,7 +1437,7 @@ extension Errno { /// /// The corresponding C error is `EOWNERDEAD`. @_alwaysEmitIntoClient - public static var previousOwnerDied: Errno { Errno(_EOWNERDEAD) } + public static var previousOwnerDied: Errno { .init(rawValue: _EOWNERDEAD) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "previousOwnerDied") @@ -1452,7 +1449,7 @@ extension Errno { /// /// The corresponding C error is `EQFULL`. @_alwaysEmitIntoClient - public static var outputQueueFull: Errno { Errno(_EQFULL) } + public static var outputQueueFull: Errno { .init(rawValue: _EQFULL) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "outputQueueFull") @@ -1466,7 +1463,7 @@ extension Errno { /// /// The corresponding C error is `ELAST`. @_alwaysEmitIntoClient - public static var lastErrnoValue: Errno { Errno(_ELAST) } + public static var lastErrnoValue: Errno { .init(rawValue: _ELAST) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "lastErrnoValue") diff --git a/Sources/System/FileDescriptor.swift b/Sources/System/FileDescriptor.swift index 0ac7f4fe..d7b931eb 100644 --- a/Sources/System/FileDescriptor.swift +++ b/Sources/System/FileDescriptor.swift @@ -98,9 +98,6 @@ extension FileDescriptor { @_alwaysEmitIntoClient public init(rawValue: CInt) { self.rawValue = rawValue } - @_alwaysEmitIntoClient - private init(_ raw: CInt) { self.init(rawValue: raw) } - #if !os(Windows) /// Indicates that opening the file doesn't /// wait for the file or device to become available. @@ -117,7 +114,7 @@ extension FileDescriptor { /// /// The corresponding C constant is `O_NONBLOCK`. @_alwaysEmitIntoClient - public static var nonBlocking: OpenOptions { OpenOptions(_O_NONBLOCK) } + public static var nonBlocking: OpenOptions { .init(rawValue: _O_NONBLOCK) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "nonBlocking") @@ -133,7 +130,7 @@ extension FileDescriptor { /// /// The corresponding C constant is `O_APPEND`. @_alwaysEmitIntoClient - public static var append: OpenOptions { OpenOptions(_O_APPEND) } + public static var append: OpenOptions { .init(rawValue: _O_APPEND) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "append") @@ -143,7 +140,7 @@ extension FileDescriptor { /// /// The corresponding C constant is `O_CREAT`. @_alwaysEmitIntoClient - public static var create: OpenOptions { OpenOptions(_O_CREAT) } + public static var create: OpenOptions { .init(rawValue: _O_CREAT) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "create") @@ -157,7 +154,7 @@ extension FileDescriptor { /// /// The corresponding C constant is `O_TRUNC`. @_alwaysEmitIntoClient - public static var truncate: OpenOptions { OpenOptions(_O_TRUNC) } + public static var truncate: OpenOptions { .init(rawValue: _O_TRUNC) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "truncate") @@ -179,7 +176,7 @@ extension FileDescriptor { /// /// The corresponding C constant is `O_EXCL`. @_alwaysEmitIntoClient - public static var exclusiveCreate: OpenOptions { OpenOptions(_O_EXCL) } + public static var exclusiveCreate: OpenOptions { .init(rawValue: _O_EXCL) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "exclusiveCreate") @@ -197,7 +194,7 @@ extension FileDescriptor { /// /// The corresponding C constant is `O_SHLOCK`. @_alwaysEmitIntoClient - public static var sharedLock: OpenOptions { OpenOptions(_O_SHLOCK) } + public static var sharedLock: OpenOptions { .init(rawValue: _O_SHLOCK) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "sharedLock") @@ -214,7 +211,7 @@ extension FileDescriptor { /// /// The corresponding C constant is `O_EXLOCK`. @_alwaysEmitIntoClient - public static var exclusiveLock: OpenOptions { OpenOptions(_O_EXLOCK) } + public static var exclusiveLock: OpenOptions { .init(rawValue: _O_EXLOCK) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "exclusiveLock") @@ -232,7 +229,7 @@ extension FileDescriptor { /// /// The corresponding C constant is `O_NOFOLLOW`. @_alwaysEmitIntoClient - public static var noFollow: OpenOptions { OpenOptions(_O_NOFOLLOW) } + public static var noFollow: OpenOptions { .init(rawValue: _O_NOFOLLOW) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "noFollow") @@ -246,7 +243,7 @@ extension FileDescriptor { /// /// The corresponding C constant is `O_DIRECTORY`. @_alwaysEmitIntoClient - public static var directory: OpenOptions { OpenOptions(_O_DIRECTORY) } + public static var directory: OpenOptions { .init(rawValue: _O_DIRECTORY) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "directory") @@ -265,7 +262,7 @@ extension FileDescriptor { /// /// The corresponding C constant is `O_SYMLINK`. @_alwaysEmitIntoClient - public static var symlink: OpenOptions { OpenOptions(_O_SYMLINK) } + public static var symlink: OpenOptions { .init(rawValue: _O_SYMLINK) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "symlink") @@ -281,7 +278,7 @@ extension FileDescriptor { /// /// The corresponding C constant is `O_EVTONLY`. @_alwaysEmitIntoClient - public static var eventOnly: OpenOptions { OpenOptions(_O_EVTONLY) } + public static var eventOnly: OpenOptions { .init(rawValue: _O_EVTONLY) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "eventOnly") @@ -303,7 +300,7 @@ extension FileDescriptor { /// /// The corresponding C constant is `O_CLOEXEC`. @_alwaysEmitIntoClient - public static var closeOnExec: OpenOptions { OpenOptions(_O_CLOEXEC) } + public static var closeOnExec: OpenOptions { .init(rawValue: _O_CLOEXEC) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "closeOnExec") diff --git a/Sources/System/FilePermissions.swift b/Sources/System/FilePermissions.swift index 2cdb32ff..918246ef 100644 --- a/Sources/System/FilePermissions.swift +++ b/Sources/System/FilePermissions.swift @@ -27,104 +27,101 @@ public struct FilePermissions: OptionSet, Sendable, Hashable, Codable { @_alwaysEmitIntoClient public init(rawValue: CModeT) { self.rawValue = rawValue } - @_alwaysEmitIntoClient - private init(_ raw: CModeT) { self.init(rawValue: raw) } - /// Indicates that other users have read-only permission. @_alwaysEmitIntoClient - public static var otherRead: FilePermissions { FilePermissions(0o4) } + public static var otherRead: FilePermissions { .init(rawValue: 0o4) } /// Indicates that other users have write-only permission. @_alwaysEmitIntoClient - public static var otherWrite: FilePermissions { FilePermissions(0o2) } + public static var otherWrite: FilePermissions { .init(rawValue: 0o2) } /// Indicates that other users have execute-only permission. @_alwaysEmitIntoClient - public static var otherExecute: FilePermissions { FilePermissions(0o1) } + public static var otherExecute: FilePermissions { .init(rawValue: 0o1) } /// Indicates that other users have read-write permission. @_alwaysEmitIntoClient - public static var otherReadWrite: FilePermissions { FilePermissions(0o6) } + public static var otherReadWrite: FilePermissions { .init(rawValue: 0o6) } /// Indicates that other users have read-execute permission. @_alwaysEmitIntoClient - public static var otherReadExecute: FilePermissions { FilePermissions(0o5) } + public static var otherReadExecute: FilePermissions { .init(rawValue: 0o5) } /// Indicates that other users have write-execute permission. @_alwaysEmitIntoClient - public static var otherWriteExecute: FilePermissions { FilePermissions(0o3) } + public static var otherWriteExecute: FilePermissions { .init(rawValue: 0o3) } /// Indicates that other users have read, write, and execute permission. @_alwaysEmitIntoClient - public static var otherReadWriteExecute: FilePermissions { FilePermissions(0o7) } + public static var otherReadWriteExecute: FilePermissions { .init(rawValue: 0o7) } /// Indicates that the group has read-only permission. @_alwaysEmitIntoClient - public static var groupRead: FilePermissions { FilePermissions(0o40) } + public static var groupRead: FilePermissions { .init(rawValue: 0o40) } /// Indicates that the group has write-only permission. @_alwaysEmitIntoClient - public static var groupWrite: FilePermissions { FilePermissions(0o20) } + public static var groupWrite: FilePermissions { .init(rawValue: 0o20) } /// Indicates that the group has execute-only permission. @_alwaysEmitIntoClient - public static var groupExecute: FilePermissions { FilePermissions(0o10) } + public static var groupExecute: FilePermissions { .init(rawValue: 0o10) } /// Indicates that the group has read-write permission. @_alwaysEmitIntoClient - public static var groupReadWrite: FilePermissions { FilePermissions(0o60) } + public static var groupReadWrite: FilePermissions { .init(rawValue: 0o60) } /// Indicates that the group has read-execute permission. @_alwaysEmitIntoClient - public static var groupReadExecute: FilePermissions { FilePermissions(0o50) } + public static var groupReadExecute: FilePermissions { .init(rawValue: 0o50) } /// Indicates that the group has write-execute permission. @_alwaysEmitIntoClient - public static var groupWriteExecute: FilePermissions { FilePermissions(0o30) } + public static var groupWriteExecute: FilePermissions { .init(rawValue: 0o30) } /// Indicates that the group has read, write, and execute permission. @_alwaysEmitIntoClient - public static var groupReadWriteExecute: FilePermissions { FilePermissions(0o70) } + public static var groupReadWriteExecute: FilePermissions { .init(rawValue: 0o70) } /// Indicates that the owner has read-only permission. @_alwaysEmitIntoClient - public static var ownerRead: FilePermissions { FilePermissions(0o400) } + public static var ownerRead: FilePermissions { .init(rawValue: 0o400) } /// Indicates that the owner has write-only permission. @_alwaysEmitIntoClient - public static var ownerWrite: FilePermissions { FilePermissions(0o200) } + public static var ownerWrite: FilePermissions { .init(rawValue: 0o200) } /// Indicates that the owner has execute-only permission. @_alwaysEmitIntoClient - public static var ownerExecute: FilePermissions { FilePermissions(0o100) } + public static var ownerExecute: FilePermissions { .init(rawValue: 0o100) } /// Indicates that the owner has read-write permission. @_alwaysEmitIntoClient - public static var ownerReadWrite: FilePermissions { FilePermissions(0o600) } + public static var ownerReadWrite: FilePermissions { .init(rawValue: 0o600) } /// Indicates that the owner has read-execute permission. @_alwaysEmitIntoClient - public static var ownerReadExecute: FilePermissions { FilePermissions(0o500) } + public static var ownerReadExecute: FilePermissions { .init(rawValue: 0o500) } /// Indicates that the owner has write-execute permission. @_alwaysEmitIntoClient - public static var ownerWriteExecute: FilePermissions { FilePermissions(0o300) } + public static var ownerWriteExecute: FilePermissions { .init(rawValue: 0o300) } /// Indicates that the owner has read, write, and execute permission. @_alwaysEmitIntoClient - public static var ownerReadWriteExecute: FilePermissions { FilePermissions(0o700) } + public static var ownerReadWriteExecute: FilePermissions { .init(rawValue: 0o700) } /// Indicates that the file is executed as the owner. /// /// For more information, see the `setuid(2)` man page. @_alwaysEmitIntoClient - public static var setUserID: FilePermissions { FilePermissions(0o4000) } + public static var setUserID: FilePermissions { .init(rawValue: 0o4000) } /// Indicates that the file is executed as the group. /// /// For more information, see the `setgid(2)` man page. @_alwaysEmitIntoClient - public static var setGroupID: FilePermissions { FilePermissions(0o2000) } + public static var setGroupID: FilePermissions { .init(rawValue: 0o2000) } /// Indicates that executable's text segment /// should be kept in swap space even after it exits. @@ -132,7 +129,7 @@ public struct FilePermissions: OptionSet, Sendable, Hashable, Codable { /// For more information, see the `chmod(2)` man page's /// discussion of `S_ISVTX` (the sticky bit). @_alwaysEmitIntoClient - public static var saveText: FilePermissions { FilePermissions(0o1000) } + public static var saveText: FilePermissions { .init(rawValue: 0o1000) } } @available(/*System 0.0.1: macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0*/iOS 8, *) From 8df901cb2495f4300f20749785af1a4440064dda Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Sun, 12 May 2024 14:07:11 -0700 Subject: [PATCH 251/427] fix a 32-bit overflow issue - ensures compatibility with watchOS --- Sources/System/MachPort.swift | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/Sources/System/MachPort.swift b/Sources/System/MachPort.swift index b3567898..8cc05096 100644 --- a/Sources/System/MachPort.swift +++ b/Sources/System/MachPort.swift @@ -56,8 +56,10 @@ public enum Mach { self._name = name if RightType.self == ReceiveRight.self { - precondition(name != 0xFFFFFFFF /* MACH_PORT_DEAD */, - "Receive rights cannot be dead names") + precondition( + _name != (0xFFFFFFFF as mach_port_name_t) /* MACH_PORT_DEAD */, + "Receive rights cannot be dead names" + ) let secret = mach_port_context_t(arc4random()) _machPrecondition(mach_port_guard(mach_task_self_, name, secret, 0)) @@ -87,8 +89,10 @@ public enum Mach { deinit { if RightType.self == ReceiveRight.self { - precondition(_name != 0xFFFFFFFF /* MACH_PORT_DEAD */, - "Receive rights cannot be dead names") + precondition( + _name != (0xFFFFFFFF as mach_port_name_t) /* MACH_PORT_DEAD */, + "Receive rights cannot be dead names" + ) _machPrecondition( mach_port_destruct(mach_task_self_, _name, 0, _context) ) From e11703d91539fdb3290d7cf1858f6fdcdddbc14c Mon Sep 17 00:00:00 2001 From: Max Desiatov Date: Tue, 30 Apr 2024 17:11:39 +0100 Subject: [PATCH 252/427] Add support for `WASILibc` module (#159) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Quoting the [wasi.dev landing page](https://wasi.dev): > The WebAssembly System Interface (WASI) is a group of standard API specifications for software compiled to the W3C WebAssembly (Wasm) standard. WASI is designed to provide a secure standard interface for applications that can be compiled to Wasm from any language, and that may run anywhere—from browsers to clouds to embedded devices. Currently support for WASI in Swift is based on [`wasi-libc`](https://github.com/WebAssembly/wasi-libc). Adding support for `wasi-libc` mostly amounted to excluding unsupported errnos, adding TLS dictionary storage shim for single-threaded environment, and adding constants in C headers for macros that Clang importer currently doesn't support. Co-authored-by: Guillaume Lessard # Conflicts: # Sources/System/FileOperations.swift # Sources/System/Internals/Syscalls.swift --- Sources/CSystem/include/CSystemWASI.h | 31 +++++++ Sources/CSystem/include/module.modulemap | 1 + Sources/System/Errno.swift | 23 +++++- Sources/System/Internals/CInterop.swift | 4 +- Sources/System/Internals/Constants.swift | 100 ++++++++++++++++++++--- Sources/System/Internals/Exports.swift | 29 ++++++- 6 files changed, 170 insertions(+), 18 deletions(-) create mode 100644 Sources/CSystem/include/CSystemWASI.h diff --git a/Sources/CSystem/include/CSystemWASI.h b/Sources/CSystem/include/CSystemWASI.h new file mode 100644 index 00000000..9877853e --- /dev/null +++ b/Sources/CSystem/include/CSystemWASI.h @@ -0,0 +1,31 @@ +/* + This source file is part of the Swift System open source project + + Copyright (c) 2024 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 +*/ + +#pragma once + +#if __wasi__ + +#include +#include + +// wasi-libc defines the following constants in a way that Clang Importer can't +// understand, so we need to expose them manually. +static inline int32_t _getConst_O_ACCMODE(void) { return O_ACCMODE; } +static inline int32_t _getConst_O_APPEND(void) { return O_APPEND; } +static inline int32_t _getConst_O_CREAT(void) { return O_CREAT; } +static inline int32_t _getConst_O_DIRECTORY(void) { return O_DIRECTORY; } +static inline int32_t _getConst_O_EXCL(void) { return O_EXCL; } +static inline int32_t _getConst_O_NONBLOCK(void) { return O_NONBLOCK; } +static inline int32_t _getConst_O_TRUNC(void) { return O_TRUNC; } +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; } + +#endif diff --git a/Sources/CSystem/include/module.modulemap b/Sources/CSystem/include/module.modulemap index 776f7766..6e8b89e9 100644 --- a/Sources/CSystem/include/module.modulemap +++ b/Sources/CSystem/include/module.modulemap @@ -1,5 +1,6 @@ module CSystem { header "CSystemLinux.h" + header "CSystemWASI.h" header "CSystemWindows.h" export * } diff --git a/Sources/System/Errno.swift b/Sources/System/Errno.swift index 498ccbcf..093023d0 100644 --- a/Sources/System/Errno.swift +++ b/Sources/System/Errno.swift @@ -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) 2021 - 2024 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 @@ -226,7 +226,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { @available(*, unavailable, renamed: "badAddress") public static var EFAULT: Errno { badAddress } -#if !os(Windows) +#if !os(Windows) && !os(WASI) /// Not a block device. /// /// You attempted a block device operation on a nonblock device or file. @@ -618,6 +618,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { @available(*, unavailable, renamed: "protocolNotSupported") public static var EPROTONOSUPPORT: Errno { protocolNotSupported } +#if !os(WASI) /// Socket type not supported. /// /// Support for the socket type hasn't been configured into the system @@ -630,6 +631,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { @_alwaysEmitIntoClient @available(*, unavailable, renamed: "socketTypeNotSupported") public static var ESOCKTNOSUPPORT: Errno { socketTypeNotSupported } +#endif /// Not supported. /// @@ -644,6 +646,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { @available(*, unavailable, renamed: "notSupported") public static var ENOTSUP: Errno { notSupported } +#if !os(WASI) /// Protocol family not supported. /// /// The protocol family hasn't been configured into the system @@ -656,6 +659,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { @_alwaysEmitIntoClient @available(*, unavailable, renamed: "protocolFamilyNotSupported") public static var EPFNOSUPPORT: Errno { protocolFamilyNotSupported } +#endif /// The address family isn't supported by the protocol family. /// @@ -802,6 +806,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { @available(*, unavailable, renamed: "socketNotConnected") public static var ENOTCONN: Errno { socketNotConnected } +#if !os(WASI) /// Can't send after socket shutdown. /// /// A request to send data wasn't permitted @@ -815,6 +820,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { @_alwaysEmitIntoClient @available(*, unavailable, renamed: "socketShutdown") public static var ESHUTDOWN: Errno { socketShutdown } +#endif /// Operation timed out. /// @@ -871,6 +877,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { @available(*, unavailable, renamed: "fileNameTooLong") public static var ENAMETOOLONG: Errno { fileNameTooLong } +#if !os(WASI) /// The host is down. /// /// A socket operation failed because the destination host was down. @@ -882,6 +889,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { @_alwaysEmitIntoClient @available(*, unavailable, renamed: "hostIsDown") public static var EHOSTDOWN: Errno { hostIsDown } +#endif /// No route to host. /// @@ -920,6 +928,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { public static var EPROCLIM: Errno { tooManyProcesses } #endif +#if !os(WASI) /// Too many users. /// /// The quota system ran out of table entries. @@ -931,6 +940,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { @_alwaysEmitIntoClient @available(*, unavailable, renamed: "tooManyUsers") public static var EUSERS: Errno { tooManyUsers } +#endif /// Disk quota exceeded. /// @@ -1284,6 +1294,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { @available(*, unavailable, renamed: "multiHop") public static var EMULTIHOP: Errno { multiHop } +#if !os(WASI) /// No message available. /// /// No message was available to be received by the requested operation. @@ -1295,6 +1306,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { @_alwaysEmitIntoClient @available(*, unavailable, renamed: "noData") public static var ENODATA: Errno { noData } +#endif /// Reserved. /// @@ -1308,6 +1320,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { @available(*, unavailable, renamed: "noLink") public static var ENOLINK: Errno { noLink } +#if !os(WASI) /// Reserved. /// /// This error is reserved for future use. @@ -1331,6 +1344,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { @_alwaysEmitIntoClient @available(*, unavailable, renamed: "notStream") public static var ENOSTR: Errno { notStream } +#endif #endif /// Protocol error. @@ -1347,7 +1361,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { @available(*, unavailable, renamed: "protocolError") public static var EPROTO: Errno { protocolError } -#if !os(OpenBSD) +#if !os(OpenBSD) && !os(WASI) /// Reserved. /// /// This error is reserved for future use. @@ -1379,7 +1393,6 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { // Constants defined in header but not man page @available(/*System 0.0.1: macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0*/iOS 8, *) extension Errno { - /// Operation would block. /// /// The corresponding C error is `EWOULDBLOCK`. @@ -1390,6 +1403,7 @@ extension Errno { @available(*, unavailable, renamed: "wouldBlock") public static var EWOULDBLOCK: Errno { wouldBlock } +#if !os(WASI) /// Too many references: can't splice. /// /// The corresponding C error is `ETOOMANYREFS`. @@ -1409,6 +1423,7 @@ extension Errno { @_alwaysEmitIntoClient @available(*, unavailable, renamed: "tooManyRemoteLevels") public static var EREMOTE: Errno { tooManyRemoteLevels } +#endif #if SYSTEM_PACKAGE_DARWIN /// No such policy registered. diff --git a/Sources/System/Internals/CInterop.swift b/Sources/System/Internals/CInterop.swift index 3b4792e8..19cf4d56 100644 --- a/Sources/System/Internals/CInterop.swift +++ b/Sources/System/Internals/CInterop.swift @@ -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 - 2024 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 @@ -18,6 +18,8 @@ import Glibc #elseif canImport(Musl) @_implementationOnly import CSystem import Musl +#elseif canImport(WASILibc) +import WASILibc #else #error("Unsupported Platform") #endif diff --git a/Sources/System/Internals/Constants.swift b/Sources/System/Internals/Constants.swift index 8d4e4bb1..27a145f7 100644 --- a/Sources/System/Internals/Constants.swift +++ b/Sources/System/Internals/Constants.swift @@ -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 - 2024 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 @@ -21,6 +21,9 @@ import Glibc #elseif canImport(Musl) import CSystem import Musl +#elseif canImport(WASILibc) +import CSystem +import WASILibc #else #error("Unsupported Platform") #endif @@ -73,7 +76,7 @@ internal var _EACCES: CInt { EACCES } @_alwaysEmitIntoClient internal var _EFAULT: CInt { EFAULT } -#if !os(Windows) +#if !os(Windows) && !os(WASI) @_alwaysEmitIntoClient internal var _ENOTBLK: CInt { ENOTBLK } #endif @@ -141,7 +144,13 @@ internal var _ERANGE: CInt { ERANGE } internal var _EAGAIN: CInt { EAGAIN } @_alwaysEmitIntoClient -internal var _EWOULDBLOCK: CInt { EWOULDBLOCK } +internal var _EWOULDBLOCK: CInt { +#if os(WASI) + _getConst_EWOULDBLOCK() +#else + EWOULDBLOCK +#endif +} @_alwaysEmitIntoClient internal var _EINPROGRESS: CInt { EINPROGRESS } @@ -167,6 +176,7 @@ internal var _ENOPROTOOPT: CInt { ENOPROTOOPT } @_alwaysEmitIntoClient internal var _EPROTONOSUPPORT: CInt { EPROTONOSUPPORT } +#if !os(WASI) @_alwaysEmitIntoClient internal var _ESOCKTNOSUPPORT: CInt { #if os(Windows) @@ -175,6 +185,7 @@ internal var _ESOCKTNOSUPPORT: CInt { return ESOCKTNOSUPPORT #endif } +#endif @_alwaysEmitIntoClient internal var _ENOTSUP: CInt { @@ -185,6 +196,7 @@ internal var _ENOTSUP: CInt { #endif } +#if !os(WASI) @_alwaysEmitIntoClient internal var _EPFNOSUPPORT: CInt { #if os(Windows) @@ -193,6 +205,7 @@ internal var _EPFNOSUPPORT: CInt { return EPFNOSUPPORT #endif } +#endif @_alwaysEmitIntoClient internal var _EAFNOSUPPORT: CInt { EAFNOSUPPORT } @@ -227,6 +240,7 @@ internal var _EISCONN: CInt { EISCONN } @_alwaysEmitIntoClient internal var _ENOTCONN: CInt { ENOTCONN } +#if !os(WASI) @_alwaysEmitIntoClient internal var _ESHUTDOWN: CInt { #if os(Windows) @@ -244,6 +258,7 @@ internal var _ETOOMANYREFS: CInt { return ETOOMANYREFS #endif } +#endif @_alwaysEmitIntoClient internal var _ETIMEDOUT: CInt { ETIMEDOUT } @@ -257,6 +272,7 @@ internal var _ELOOP: CInt { ELOOP } @_alwaysEmitIntoClient internal var _ENAMETOOLONG: CInt { ENAMETOOLONG } +#if !os(WASI) @_alwaysEmitIntoClient internal var _EHOSTDOWN: CInt { #if os(Windows) @@ -265,6 +281,7 @@ internal var _EHOSTDOWN: CInt { return EHOSTDOWN #endif } +#endif @_alwaysEmitIntoClient internal var _EHOSTUNREACH: CInt { EHOSTUNREACH } @@ -277,6 +294,7 @@ internal var _ENOTEMPTY: CInt { ENOTEMPTY } internal var _EPROCLIM: CInt { EPROCLIM } #endif +#if !os(WASI) @_alwaysEmitIntoClient internal var _EUSERS: CInt { #if os(Windows) @@ -285,6 +303,7 @@ internal var _EUSERS: CInt { return EUSERS #endif } +#endif @_alwaysEmitIntoClient internal var _EDQUOT: CInt { @@ -304,6 +323,7 @@ internal var _ESTALE: CInt { #endif } +#if !os(WASI) @_alwaysEmitIntoClient internal var _EREMOTE: CInt { #if os(Windows) @@ -312,6 +332,7 @@ internal var _EREMOTE: CInt { return EREMOTE #endif } +#endif #if SYSTEM_PACKAGE_DARWIN @_alwaysEmitIntoClient @@ -399,30 +420,41 @@ internal var _EBADMSG: CInt { EBADMSG } @_alwaysEmitIntoClient internal var _EMULTIHOP: CInt { EMULTIHOP } +#if !os(WASI) @_alwaysEmitIntoClient internal var _ENODATA: CInt { ENODATA } +#endif @_alwaysEmitIntoClient internal var _ENOLINK: CInt { ENOLINK } +#if !os(WASI) @_alwaysEmitIntoClient internal var _ENOSR: CInt { ENOSR } @_alwaysEmitIntoClient internal var _ENOSTR: CInt { ENOSTR } #endif +#endif @_alwaysEmitIntoClient internal var _EPROTO: CInt { EPROTO } -#if !os(OpenBSD) +#if !os(OpenBSD) && !os(WASI) @_alwaysEmitIntoClient internal var _ETIME: CInt { ETIME } #endif #endif + @_alwaysEmitIntoClient -internal var _EOPNOTSUPP: CInt { EOPNOTSUPP } +internal var _EOPNOTSUPP: CInt { +#if os(WASI) + _getConst_EOPNOTSUPP() +#else + EOPNOTSUPP +#endif +} #if SYSTEM_PACKAGE_DARWIN @_alwaysEmitIntoClient @@ -462,15 +494,33 @@ internal var _O_ACCMODE: CInt { 0x03|O_SEARCH } #else // TODO: API? @_alwaysEmitIntoClient -internal var _O_ACCMODE: CInt { O_ACCMODE } +internal var _O_ACCMODE: CInt { +#if os(WASI) + _getConst_O_ACCMODE() +#else + O_ACCMODE +#endif +} #endif @_alwaysEmitIntoClient -internal var _O_NONBLOCK: CInt { O_NONBLOCK } +internal var _O_NONBLOCK: CInt { +#if os(WASI) + _getConst_O_NONBLOCK() +#else + O_NONBLOCK +#endif +} #endif @_alwaysEmitIntoClient -internal var _O_APPEND: CInt { O_APPEND } +internal var _O_APPEND: CInt { +#if os(WASI) + _getConst_O_APPEND() +#else + O_APPEND +#endif +} #if SYSTEM_PACKAGE_DARWIN @_alwaysEmitIntoClient @@ -481,22 +531,42 @@ internal var _O_EXLOCK: CInt { O_EXLOCK } #endif #if !os(Windows) +#if !os(WASI) // TODO: API? @_alwaysEmitIntoClient internal var _O_ASYNC: CInt { O_ASYNC } +#endif @_alwaysEmitIntoClient internal var _O_NOFOLLOW: CInt { O_NOFOLLOW } #endif @_alwaysEmitIntoClient -internal var _O_CREAT: CInt { O_CREAT } +internal var _O_CREAT: CInt { +#if os(WASI) + _getConst_O_CREAT() +#else + O_CREAT +#endif +} @_alwaysEmitIntoClient -internal var _O_TRUNC: CInt { O_TRUNC } +internal var _O_TRUNC: CInt { +#if os(WASI) + _getConst_O_TRUNC() +#else + O_TRUNC +#endif +} @_alwaysEmitIntoClient -internal var _O_EXCL: CInt { O_EXCL } +internal var _O_EXCL: CInt { +#if os(WASI) + _getConst_O_EXCL() +#else + O_EXCL +#endif +} #if SYSTEM_PACKAGE_DARWIN @_alwaysEmitIntoClient @@ -509,7 +579,13 @@ internal var _O_EVTONLY: CInt { O_EVTONLY } internal var _O_NOCTTY: CInt { O_NOCTTY } @_alwaysEmitIntoClient -internal var _O_DIRECTORY: CInt { O_DIRECTORY } +internal var _O_DIRECTORY: CInt { +#if os(WASI) + _getConst_O_DIRECTORY() +#else + O_DIRECTORY +#endif +} #endif #if SYSTEM_PACKAGE_DARWIN diff --git a/Sources/System/Internals/Exports.swift b/Sources/System/Internals/Exports.swift index e20454ee..f4358c5b 100644 --- a/Sources/System/Internals/Exports.swift +++ b/Sources/System/Internals/Exports.swift @@ -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 - 2024 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 @@ -23,6 +23,8 @@ import Glibc #elseif canImport(Musl) @_implementationOnly import CSystem import Musl +#elseif canImport(WASILibc) +import WASILibc #else #error("Unsupported Platform") #endif @@ -58,6 +60,11 @@ internal var system_errno: CInt { get { Musl.errno } set { Musl.errno = newValue } } +#elseif canImport(WASILibc) +internal var system_errno: CInt { + get { WASILibc.errno } + set { WASILibc.errno = newValue } +} #endif // MARK: C stdlib decls @@ -149,6 +156,24 @@ extension String { // TLS #if os(Windows) internal typealias _PlatformTLSKey = DWORD +#elseif os(WASI) && (swift(<6.1) || !_runtime(_multithreaded)) +// Mock TLS storage for single-threaded WASI +internal final class _PlatformTLSKey { + fileprivate init() {} +} +private final class TLSStorage: @unchecked Sendable { + var storage = [ObjectIdentifier: UnsafeMutableRawPointer]() +} +private let sharedTLSStorage = TLSStorage() + +func pthread_setspecific(_ key: _PlatformTLSKey, _ p: UnsafeMutableRawPointer?) -> Int { + sharedTLSStorage.storage[ObjectIdentifier(key)] = p + return 0 +} + +func pthread_getspecific(_ key: _PlatformTLSKey) -> UnsafeMutableRawPointer? { + sharedTLSStorage.storage[ObjectIdentifier(key)] +} #else internal typealias _PlatformTLSKey = pthread_key_t #endif @@ -160,6 +185,8 @@ internal func makeTLSKey() -> _PlatformTLSKey { fatalError("Unable to create key") } return raw + #elseif os(WASI) && (swift(<6.1) || !_runtime(_multithreaded)) + return _PlatformTLSKey() #else var raw = pthread_key_t() guard 0 == pthread_key_create(&raw, nil) else { From b2b60a6b9495f7e06b3aece461cc1b9ccaf917c5 Mon Sep 17 00:00:00 2001 From: Finagolfin Date: Thu, 9 May 2024 19:29:36 +0530 Subject: [PATCH 253/427] Android: DIR* is an OpaquePointer in Bionic too --- Sources/System/Internals/Syscalls.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/System/Internals/Syscalls.swift b/Sources/System/Internals/Syscalls.swift index 3794d6e0..f40b1685 100644 --- a/Sources/System/Internals/Syscalls.swift +++ b/Sources/System/Internals/Syscalls.swift @@ -189,7 +189,7 @@ internal func system_confstr( internal let SYSTEM_AT_REMOVE_DIR = AT_REMOVEDIR internal let SYSTEM_DT_DIR = DT_DIR internal typealias system_dirent = dirent -#if os(Linux) +#if os(Linux) || os(Android) internal typealias system_DIRPtr = OpaquePointer #else internal typealias system_DIRPtr = UnsafeMutablePointer From 209dfeacb8e052a3425b40c9da83fa1bda8f73ee Mon Sep 17 00:00:00 2001 From: Finagolfin Date: Thu, 4 Jul 2024 23:37:25 +0530 Subject: [PATCH 254/427] Import new Android overlay Use Bionic module instead where possible --- Sources/System/Internals/CInterop.swift | 3 +++ Sources/System/Internals/Constants.swift | 2 ++ Sources/System/Internals/Exports.swift | 8 ++++++++ Sources/System/Internals/Syscalls.swift | 2 ++ 4 files changed, 15 insertions(+) diff --git a/Sources/System/Internals/CInterop.swift b/Sources/System/Internals/CInterop.swift index 19cf4d56..80d37d05 100644 --- a/Sources/System/Internals/CInterop.swift +++ b/Sources/System/Internals/CInterop.swift @@ -20,6 +20,9 @@ import Glibc import Musl #elseif canImport(WASILibc) import WASILibc +#elseif canImport(Bionic) +@_implementationOnly import CSystem +import Bionic #else #error("Unsupported Platform") #endif diff --git a/Sources/System/Internals/Constants.swift b/Sources/System/Internals/Constants.swift index 27a145f7..904e5b22 100644 --- a/Sources/System/Internals/Constants.swift +++ b/Sources/System/Internals/Constants.swift @@ -24,6 +24,8 @@ import Musl #elseif canImport(WASILibc) import CSystem import WASILibc +#elseif canImport(Android) +import Android #else #error("Unsupported Platform") #endif diff --git a/Sources/System/Internals/Exports.swift b/Sources/System/Internals/Exports.swift index f4358c5b..d18102a2 100644 --- a/Sources/System/Internals/Exports.swift +++ b/Sources/System/Internals/Exports.swift @@ -25,6 +25,9 @@ import Glibc import Musl #elseif canImport(WASILibc) import WASILibc +#elseif canImport(Android) +@_implementationOnly import CSystem +import Android #else #error("Unsupported Platform") #endif @@ -65,6 +68,11 @@ internal var system_errno: CInt { get { WASILibc.errno } set { WASILibc.errno = newValue } } +#elseif canImport(Android) +internal var system_errno: CInt { + get { Android.errno } + set { Android.errno = newValue } +} #endif // MARK: C stdlib decls diff --git a/Sources/System/Internals/Syscalls.swift b/Sources/System/Internals/Syscalls.swift index f40b1685..6f885be5 100644 --- a/Sources/System/Internals/Syscalls.swift +++ b/Sources/System/Internals/Syscalls.swift @@ -17,6 +17,8 @@ import Musl import WASILibc #elseif os(Windows) import ucrt +#elseif canImport(Android) +import Android #else #error("Unsupported Platform") #endif From 69e737de3db0a8d5426e8ef9ee966aa0ac3abb9a Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Thu, 7 Mar 2024 16:51:12 -0800 Subject: [PATCH 255/427] Generate SystemString.string consistently - SystemString.string and Slice.string should be generated in the same manner. Now they are. --- Sources/System/SystemString.swift | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/Sources/System/SystemString.swift b/Sources/System/SystemString.swift index 01de8e83..2c734113 100644 --- a/Sources/System/SystemString.swift +++ b/Sources/System/SystemString.swift @@ -230,7 +230,9 @@ extension Slice where Base == SystemString { } internal var string: String { - withCodeUnits { String(decoding: $0, as: CInterop.PlatformUnicodeEncoding.self) } + withCodeUnits { + String(decoding: $0, as: CInterop.PlatformUnicodeEncoding.self) + } } internal func withPlatformString( @@ -272,7 +274,11 @@ extension SystemString: ExpressibleByStringLiteral { } extension SystemString: CustomStringConvertible, CustomDebugStringConvertible { - internal var string: String { String(decoding: self) } + internal var string: String { + self.withCodeUnits { + String(decoding: $0, as: CInterop.PlatformUnicodeEncoding.self) + } + } internal var description: String { string } internal var debugDescription: String { description.debugDescription } From 448bef7c38cac332679d47902ccca839050714e7 Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Thu, 7 Mar 2024 16:53:49 -0800 Subject: [PATCH 256/427] Test SystemString.string property consistency --- Tests/SystemTests/SystemStringTests.swift | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Tests/SystemTests/SystemStringTests.swift b/Tests/SystemTests/SystemStringTests.swift index a1891714..c929171a 100644 --- a/Tests/SystemTests/SystemStringTests.swift +++ b/Tests/SystemTests/SystemStringTests.swift @@ -254,6 +254,11 @@ final class SystemStringTest: XCTestCase { XCTAssert(str == "abcd") } + func testStringProperty() { + let source: [CInterop.PlatformChar] = [0x61, 0x62, 0, 0x63] + let str = SystemString(platformString: source) + XCTAssertEqual(str.string, str[...].string) + } } extension SystemStringTest { From 95784d018f93d5994dbf40446bbe834ac761a25d Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Thu, 7 Mar 2024 16:31:54 -0800 Subject: [PATCH 257/427] Fix behaviour of `withCodeUnits()` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - The original implementation includes a trailing zero with the full collection, but the implementation for a slice does not. As a result, given a `let s: SystemString`, `s.string` was not equal to `s[…].string` --- Sources/System/SystemString.swift | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/Sources/System/SystemString.swift b/Sources/System/SystemString.swift index 2c734113..1229d16b 100644 --- a/Sources/System/SystemString.swift +++ b/Sources/System/SystemString.swift @@ -204,17 +204,14 @@ extension SystemString { try nullTerminatedStorage.withContiguousStorageIfAvailable(f)! } + // withCodeUnits does not include the null terminator internal func withCodeUnits( _ f: (UnsafeBufferPointer) throws -> T ) rethrows -> T { - try withSystemChars { chars in - let length = chars.count * MemoryLayout.stride - let count = length / MemoryLayout.stride - return try chars.baseAddress!.withMemoryRebound( - to: CInterop.PlatformUnicodeEncoding.CodeUnit.self, - capacity: count - ) { pointer in - try f(UnsafeBufferPointer(start: pointer, count: count)) + try withSystemChars { + try $0.withMemoryRebound(to: CInterop.PlatformUnicodeEncoding.CodeUnit.self) { + assert($0.last == .zero) + return try f(.init(start: $0.baseAddress, count: $0.count&-1)) } } } From d96435c72b5ab39825dc56d6f300d5f0adfe4c6b Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Thu, 7 Mar 2024 17:00:47 -0800 Subject: [PATCH 258/427] Rename `withSystemChars` - `withNullTerminatedSystemChars` makes it clear that the null-termination is included. Operations where the null-termination is included should be obvious. --- Sources/System/FilePath/FilePathComponents.swift | 2 +- Sources/System/SystemString.swift | 9 ++++----- Tests/SystemTests/SystemStringTests.swift | 2 +- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/Sources/System/FilePath/FilePathComponents.swift b/Sources/System/FilePath/FilePathComponents.swift index 6b8a9408..a19526f2 100644 --- a/Sources/System/FilePath/FilePathComponents.swift +++ b/Sources/System/FilePath/FilePathComponents.swift @@ -146,7 +146,7 @@ extension _StrSlice { internal func _withSystemChars( _ f: (UnsafeBufferPointer) throws -> T ) rethrows -> T { - try _storage.withSystemChars { + try _storage.withNullTerminatedSystemChars { try f(UnsafeBufferPointer(rebasing: $0[_range])) } } diff --git a/Sources/System/SystemString.swift b/Sources/System/SystemString.swift index 1229d16b..16057185 100644 --- a/Sources/System/SystemString.swift +++ b/Sources/System/SystemString.swift @@ -197,18 +197,17 @@ extension SystemString: Hashable, Codable { extension SystemString { - // withSystemChars includes the null terminator - internal func withSystemChars( + internal func withNullTerminatedSystemChars( _ f: (UnsafeBufferPointer) throws -> T ) rethrows -> T { - try nullTerminatedStorage.withContiguousStorageIfAvailable(f)! + try nullTerminatedStorage.withUnsafeBufferPointer(f) } // withCodeUnits does not include the null terminator internal func withCodeUnits( _ f: (UnsafeBufferPointer) throws -> T ) rethrows -> T { - try withSystemChars { + try withNullTerminatedSystemChars { try $0.withMemoryRebound(to: CInterop.PlatformUnicodeEncoding.CodeUnit.self) { assert($0.last == .zero) return try f(.init(start: $0.baseAddress, count: $0.count&-1)) @@ -314,7 +313,7 @@ extension SystemString { internal func withPlatformString( _ f: (UnsafePointer) throws -> T ) rethrows -> T { - try withSystemChars { chars in + try withNullTerminatedSystemChars { chars in let length = chars.count * MemoryLayout.stride return try chars.baseAddress!.withMemoryRebound( to: CInterop.PlatformChar.self, diff --git a/Tests/SystemTests/SystemStringTests.swift b/Tests/SystemTests/SystemStringTests.swift index c929171a..1815392c 100644 --- a/Tests/SystemTests/SystemStringTests.swift +++ b/Tests/SystemTests/SystemStringTests.swift @@ -101,7 +101,7 @@ struct StringTest: TestCase { // Test null insertion let rawChars = raw.lazy.map { SystemChar($0) } expectEqual(sysRaw, SystemString(rawChars.dropLast())) - sysRaw.withSystemChars { // TODO: assuming we want null in withSysChars + sysRaw.withNullTerminatedSystemChars { expectEqualSequence(rawChars, $0, "rawChars") } From b2d4917ebe1ef8a4fe28d39521878a752c1fb393 Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Thu, 7 Mar 2024 17:05:54 -0800 Subject: [PATCH 259/427] Update copyright notices --- Sources/System/FilePath/FilePathComponents.swift | 2 +- Sources/System/SystemString.swift | 2 +- Tests/SystemTests/SystemStringTests.swift | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Sources/System/FilePath/FilePathComponents.swift b/Sources/System/FilePath/FilePathComponents.swift index a19526f2..b304a0a7 100644 --- a/Sources/System/FilePath/FilePathComponents.swift +++ b/Sources/System/FilePath/FilePathComponents.swift @@ -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 - 2024 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 diff --git a/Sources/System/SystemString.swift b/Sources/System/SystemString.swift index 16057185..57abc625 100644 --- a/Sources/System/SystemString.swift +++ b/Sources/System/SystemString.swift @@ -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 - 2024 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 diff --git a/Tests/SystemTests/SystemStringTests.swift b/Tests/SystemTests/SystemStringTests.swift index 1815392c..48ca83c1 100644 --- a/Tests/SystemTests/SystemStringTests.swift +++ b/Tests/SystemTests/SystemStringTests.swift @@ -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 - 2024 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 From 2e8f58ac417ac4a6ffb458c39b601c823cc295ff Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Thu, 17 Oct 2024 08:49:29 -0700 Subject: [PATCH 260/427] [1.4.0] Swift 5.8 is not supported on this branch --- .swiftci/5_8_ubuntu2204 | 5 ----- 1 file changed, 5 deletions(-) delete mode 100644 .swiftci/5_8_ubuntu2204 diff --git a/.swiftci/5_8_ubuntu2204 b/.swiftci/5_8_ubuntu2204 deleted file mode 100644 index 2c259fad..00000000 --- a/.swiftci/5_8_ubuntu2204 +++ /dev/null @@ -1,5 +0,0 @@ -LinuxSwiftPackageJob { - swift_version_tag = "5.8-jammy" - repo = "swift-system" - branch = "release/1.4.0" -} From 11d481252db8972e3921a00fa03633effd3388bb Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Thu, 17 Oct 2024 13:17:08 -0700 Subject: [PATCH 261/427] [1.4.0] Updates to the README file --- README.md | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 452ad86d..30b60f70 100644 --- a/README.md +++ b/README.md @@ -55,7 +55,15 @@ let package = Package( ## Source Stability -The Swift System package is source stable. The version numbers follow [Semantic Versioning][semver] -- source breaking changes to public API can only land in a new major version. +The Swift System package supports three types of operating systems: Darwin, POSIX, and Windows. The source-stability status of the package differs according to the platform: + +| Platform type | Stability | +| ----------------- | --------------- | +| Darwin | Source stable | +| POSIX | Source stable | +| Windows | Source unstable | + +The package version numbers follow [Semantic Versioning][semver] -- source breaking changes to public API can only land in a new major version. However, platforms for which support has not reached source stability can have source-breaking changes in a new minor version. [semver]: https://semver.org @@ -79,7 +87,7 @@ The following table maps existing package releases to their minimum required Swi | Package version | Swift version | Xcode release | | ----------------------- | --------------- | ------------- | | swift-system 1.3.x | >= Swift 5.8 | >= Xcode 14.3 | -| swift-system 1.4.x (unreleased) | >= Swift 5.9 | >= Xcode 15.0 | +| swift-system 1.4.x | >= Swift 5.9 | >= Xcode 15.0 | We'd like this package to quickly embrace Swift language and toolchain improvements that are relevant to its mandate. Accordingly, from time to time, new versions of this package require clients to upgrade to a more recent Swift toolchain release. (This allows the package to make use of new language/stdlib features, build on compiler bug fixes, and adopt new package manager functionality as soon as they are available.) Patch (i.e., bugfix) releases will not increase the required toolchain version, but any minor (i.e., new feature) release may do so. From 3f4e7cb45c350319cd1d26668f15f808bcd80736 Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Thu, 17 Oct 2024 14:27:41 -0700 Subject: [PATCH 262/427] =?UTF-8?q?[windows]=20expose=20pipe=E2=80=99s=20b?= =?UTF-8?q?uffer=20size=20parameter?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Sources/System/Internals/WindowsSyscallAdapters.swift | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Sources/System/Internals/WindowsSyscallAdapters.swift b/Sources/System/Internals/WindowsSyscallAdapters.swift index d470bd1f..d56d33e4 100644 --- a/Sources/System/Internals/WindowsSyscallAdapters.swift +++ b/Sources/System/Internals/WindowsSyscallAdapters.swift @@ -184,8 +184,10 @@ internal func pwrite( } @inline(__always) -internal func pipe(_ fds: UnsafeMutablePointer) -> CInt { - return _pipe(fds, 4096, _O_BINARY | _O_NOINHERIT); +internal func pipe( + _ fds: UnsafeMutablePointer, bytesReserved: UInt32 = 4096 +) -> CInt { +  return _pipe(fds, bytesReserved, _O_BINARY | _O_NOINHERIT); } @inline(__always) From eeeb81d0cda13798cefcd51348f08a32c233284e Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Thu, 17 Oct 2024 15:14:38 -0700 Subject: [PATCH 263/427] [readme] update usage example, reword stability --- README.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 30b60f70..b54e7359 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ To use the `SystemPackage` library in a SwiftPM project, add the following line to the dependencies in your `Package.swift` file: ```swift -.package(url: "https://github.com/apple/swift-system", from: "1.3.0"), +.package(url: "https://github.com/apple/swift-system", from: "1.4.0"), ``` Finally, include `"SystemPackage"` as a dependency for your executable target: @@ -41,7 +41,7 @@ Finally, include `"SystemPackage"` as a dependency for your executable target: let package = Package( // name, platforms, products, etc. dependencies: [ - .package(url: "https://github.com/apple/swift-system", from: "1.3.0"), + .package(url: "https://github.com/apple/swift-system", from: "1.4.0"), // other dependencies ], targets: [ @@ -55,15 +55,15 @@ let package = Package( ## Source Stability -The Swift System package supports three types of operating systems: Darwin, POSIX, and Windows. The source-stability status of the package differs according to the platform: +The Swift System package supports three types of operating systems: Darwin-based, POSIX-like, and Windows. The source-stability status of the package differs according to the platform: -| Platform type | Stability | +| Platform type | Source Stability | | ----------------- | --------------- | -| Darwin | Source stable | -| POSIX | Source stable | -| Windows | Source unstable | +| Darwin (macOS, iOS, etc.) | Stable | +| POSIX (Linux, WASI, etc.) | Stable | +| Windows | Unstable | -The package version numbers follow [Semantic Versioning][semver] -- source breaking changes to public API can only land in a new major version. However, platforms for which support has not reached source stability can have source-breaking changes in a new minor version. +The package version numbers follow [Semantic Versioning][semver] -- source breaking changes to source-stable public API can only land in a new major version. However, platforms for which support has not reached source stability may see source-breaking changes in a new minor version. [semver]: https://semver.org From b80d743dabbf04af5a3c186f1397434356846977 Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Thu, 17 Oct 2024 15:52:41 -0700 Subject: [PATCH 264/427] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b54e7359..8fa266b5 100644 --- a/README.md +++ b/README.md @@ -55,7 +55,7 @@ let package = Package( ## Source Stability -The Swift System package supports three types of operating systems: Darwin-based, POSIX-like, and Windows. The source-stability status of the package differs according to the platform: +At this time, the Swift System package supports three types of operating systems: Darwin-based, POSIX-like, and Windows. The source-stability status of the package differs according to the platform: | Platform type | Source Stability | | ----------------- | --------------- | From baab9b26b77bca2622e7f3741ceddc62a51e00fe Mon Sep 17 00:00:00 2001 From: David Smith Date: Thu, 24 Oct 2024 21:21:07 +0000 Subject: [PATCH 265/427] Starting to move to noncopyable structs, and away from swift-atomics --- Sources/System/IORing.swift | 72 ++++++++++++++--------------- Tests/SystemTests/IORingTests.swift | 2 +- 2 files changed, 37 insertions(+), 37 deletions(-) diff --git a/Sources/System/IORing.swift b/Sources/System/IORing.swift index f91dfc04..0ac783af 100644 --- a/Sources/System/IORing.swift +++ b/Sources/System/IORing.swift @@ -1,7 +1,7 @@ @_implementationOnly import CSystem import struct CSystem.io_uring_sqe -@_implementationOnly import Atomics +@_implementationOnly import Synchronization import Glibc // needed for mmap // XXX: this *really* shouldn't be here. oh well. @@ -12,9 +12,9 @@ extension UnsafeMutableRawPointer { } // all pointers in this struct reference kernel-visible memory -@usableFromInline struct SQRing { - let kernelHead: UnsafeAtomic - let kernelTail: UnsafeAtomic +@usableFromInline struct SQRing: ~Copyable { + let kernelHead: UnsafePointer> + let kernelTail: UnsafePointer> var userTail: UInt32 // from liburing: the kernel should never change these @@ -25,7 +25,7 @@ extension UnsafeMutableRawPointer { // ring flags bitfield // currently used by the kernel only in SQPOLL mode to indicate // when the polling thread needs to be woken up - let flags: UnsafeAtomic + let flags: UnsafePointer> // ring array // maps indexes between the actual ring and the submissionQueueEntries list, @@ -34,9 +34,9 @@ extension UnsafeMutableRawPointer { let array: UnsafeMutableBufferPointer } -struct CQRing { - let kernelHead: UnsafeAtomic - let kernelTail: UnsafeAtomic +struct CQRing: ~Copyable { + let kernelHead: UnsafePointer> + let kernelTail: UnsafePointer> // TODO: determine if this is actually used var userHead: UInt32 @@ -124,7 +124,7 @@ extension IORingBuffer { // XXX: This should be a non-copyable type (?) // demo only runs on Swift 5.8.1 -public final class IORing: @unchecked Sendable { +public struct IORing: @unchecked Sendable, ~Copyable { let ringFlags: UInt32 let ringDescriptor: Int32 @@ -187,20 +187,20 @@ public final class IORing: @unchecked Sendable { } submissionRing = SQRing( - kernelHead: UnsafeAtomic( - at: ringPtr.advanced(by: params.sq_off.head) - .assumingMemoryBound(to: UInt32.AtomicRepresentation.self) + kernelHead: UnsafePointer>( + ringPtr.advanced(by: params.sq_off.head) + .assumingMemoryBound(to: Atomic.self) ), - kernelTail: UnsafeAtomic( - at: ringPtr.advanced(by: params.sq_off.tail) - .assumingMemoryBound(to: UInt32.AtomicRepresentation.self) + kernelTail: UnsafePointer>( + ringPtr.advanced(by: params.sq_off.tail) + .assumingMemoryBound(to: Atomic.self) ), userTail: 0, // no requests yet ringMask: ringPtr.advanced(by: params.sq_off.ring_mask) .assumingMemoryBound(to: UInt32.self).pointee, - flags: UnsafeAtomic( - at: ringPtr.advanced(by: params.sq_off.flags) - .assumingMemoryBound(to: UInt32.AtomicRepresentation.self) + flags: UnsafePointer>( + ringPtr.advanced(by: params.sq_off.flags) + .assumingMemoryBound(to: Atomic.self) ), array: UnsafeMutableBufferPointer( start: ringPtr.advanced(by: params.sq_off.array) @@ -237,13 +237,13 @@ public final class IORing: @unchecked Sendable { ) completionRing = CQRing( - kernelHead: UnsafeAtomic( - at: ringPtr.advanced(by: params.cq_off.head) - .assumingMemoryBound(to: UInt32.AtomicRepresentation.self) + kernelHead: UnsafePointer>( + ringPtr.advanced(by: params.cq_off.head) + .assumingMemoryBound(to: Atomic.self) ), - kernelTail: UnsafeAtomic( - at: ringPtr.advanced(by: params.cq_off.tail) - .assumingMemoryBound(to: UInt32.AtomicRepresentation.self) + kernelTail: UnsafePointer>( + ringPtr.advanced(by: params.cq_off.tail) + .assumingMemoryBound(to: Atomic.self) ), userHead: 0, // no completions yet ringMask: ringPtr.advanced(by: params.cq_off.ring_mask) @@ -299,20 +299,20 @@ public final class IORing: @unchecked Sendable { } func _tryConsumeCompletion() -> IOCompletion? { - let tail = completionRing.kernelTail.load(ordering: .acquiring) - let head = completionRing.kernelHead.load(ordering: .relaxed) + let tail = completionRing.kernelTail.pointee.load(ordering: .acquiring) + let head = completionRing.kernelHead.pointee.load(ordering: .relaxed) if tail != head { // 32 byte copy - oh well let res = completionRing.cqes[Int(head & completionRing.ringMask)] - completionRing.kernelHead.store(head + 1, ordering: .relaxed) + completionRing.kernelHead.pointee.store(head + 1, ordering: .relaxed) return IOCompletion(rawValue: res) } return nil } - public func registerFiles(count: UInt32) { + public mutating func registerFiles(count: UInt32) { guard self.registeredFiles == nil else { fatalError() } let fileBuf = UnsafeMutableBufferPointer.allocate(capacity: Int(count)) fileBuf.initialize(repeating: UInt32.max) @@ -334,7 +334,7 @@ public final class IORing: @unchecked Sendable { return self.registeredFiles?.getResource() } - public func registerBuffers(bufSize: UInt32, count: UInt32) { + public mutating func registerBuffers(bufSize: UInt32, count: UInt32) { let iovecs = UnsafeMutableBufferPointer.allocate(capacity: Int(count)) let intBufSize = Int(bufSize) for i in 0.. UInt32 { - self.submissionRing.kernelTail.store( + self.submissionRing.kernelTail.pointee.store( self.submissionRing.userTail, ordering: .relaxed ) return self.submissionRing.userTail - - self.submissionRing.kernelHead.load(ordering: .relaxed) + self.submissionRing.kernelHead.pointee.load(ordering: .relaxed) } @inlinable @inline(__always) - public func writeRequest(_ request: __owned IORequest) -> Bool { + public mutating func writeRequest(_ request: __owned IORequest) -> Bool { self.submissionMutex.lock() defer { self.submissionMutex.unlock() } return _writeRequest(request.makeRawRequest()) } @inlinable @inline(__always) - internal func _writeRequest(_ request: __owned RawIORequest) -> Bool { + internal mutating func _writeRequest(_ request: __owned RawIORequest) -> Bool { let entry = _blockingGetSubmissionEntry() entry.pointee = request.rawValue return true } @inlinable @inline(__always) - internal func _blockingGetSubmissionEntry() -> UnsafeMutablePointer { + internal mutating func _blockingGetSubmissionEntry() -> UnsafeMutablePointer { while true { if let entry = _getSubmissionEntry() { return entry @@ -427,11 +427,11 @@ public final class IORing: @unchecked Sendable { } @usableFromInline @inline(__always) - internal func _getSubmissionEntry() -> UnsafeMutablePointer? { + internal mutating func _getSubmissionEntry() -> UnsafeMutablePointer? { let next = self.submissionRing.userTail + 1 // FEAT: smp load when SQPOLL in use (not in MVP) - let kernelHead = self.submissionRing.kernelHead.load(ordering: .relaxed) + let kernelHead = self.submissionRing.kernelHead.pointee.load(ordering: .relaxed) // FEAT: 128-bit event support (not in MVP) if (next - kernelHead <= self.submissionRing.array.count) { diff --git a/Tests/SystemTests/IORingTests.swift b/Tests/SystemTests/IORingTests.swift index 78baa984..4604f500 100644 --- a/Tests/SystemTests/IORingTests.swift +++ b/Tests/SystemTests/IORingTests.swift @@ -12,7 +12,7 @@ final class IORingTests: XCTestCase { } func testNop() throws { - let ring = try IORing(queueDepth: 32) + var ring = try IORing(queueDepth: 32) ring.writeRequest(.nop) ring.submitRequests() let completion = ring.blockingConsumeCompletion() From 60299362ed85e53915891a562b60d621afc608df Mon Sep 17 00:00:00 2001 From: David Smith Date: Thu, 24 Oct 2024 21:27:22 +0000 Subject: [PATCH 266/427] One more noncopyable struct --- Sources/System/IORing.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/System/IORing.swift b/Sources/System/IORing.swift index 0ac783af..656e1683 100644 --- a/Sources/System/IORing.swift +++ b/Sources/System/IORing.swift @@ -79,7 +79,7 @@ internal class ResourceManager: @unchecked Sendable { } } -public class IOResource { +public struct IOResource: ~Copyable { typealias Resource = T @usableFromInline let resource: T @usableFromInline let index: Int From 10c070abd648bb66da947c77b5a5c645ef1d53fd Mon Sep 17 00:00:00 2001 From: David Smith Date: Mon, 28 Oct 2024 20:00:07 +0000 Subject: [PATCH 267/427] WIP, more ~Copyable adoption --- Sources/System/IORequest.swift | 145 ++++--- Sources/System/IORing.swift | 368 +++++++++--------- Sources/System/Lock.swift | 37 -- Sources/System/ManagedIORing.swift | 25 +- .../AsyncFileDescriptorTests.swift | 2 +- 5 files changed, 295 insertions(+), 282 deletions(-) delete mode 100644 Sources/System/Lock.swift diff --git a/Sources/System/IORequest.swift b/Sources/System/IORequest.swift index 9548b3fb..2ecd3f46 100644 --- a/Sources/System/IORequest.swift +++ b/Sources/System/IORequest.swift @@ -1,9 +1,9 @@ import struct CSystem.io_uring_sqe -public enum IORequest { +public enum IORequest: ~Copyable { case nop // nothing here case openat( - atDirectory: FileDescriptor, + atDirectory: FileDescriptor, path: UnsafePointer, FileDescriptor.AccessMode, options: FileDescriptor.OpenOptions = FileDescriptor.OpenOptions(), @@ -22,71 +22,106 @@ public enum IORequest { ) case close(File) - public enum Buffer { + public enum Buffer: ~Copyable { case registered(IORingBuffer) case unregistered(UnsafeMutableRawBufferPointer) } - public enum File { + public enum File: ~Copyable { case registered(IORingFileSlot) case unregistered(FileDescriptor) } } +@inlinable @inline(__always) +internal func makeRawRequest_readWrite_registered( + file: consuming IORequest.File, + buffer: consuming IORingBuffer, + offset: UInt64, + request: consuming RawIORequest +) -> RawIORequest { + switch file { + case .registered(let regFile): + request.rawValue.fd = Int32(exactly: regFile.index)! + request.flags = .fixedFile + case .unregistered(let fd): + request.fileDescriptor = fd + } + request.buffer = buffer.unsafeBuffer + request.rawValue.buf_index = UInt16(exactly: buffer.index)! + request.offset = offset + return request +} + +@inlinable @inline(__always) +internal func makeRawRequest_readWrite_unregistered( + file: consuming IORequest.File, + buffer: UnsafeMutableRawBufferPointer, + offset: UInt64, + request: consuming RawIORequest +) -> RawIORequest { + switch file { + case .registered(let regFile): + request.rawValue.fd = Int32(exactly: regFile.index)! + request.flags = .fixedFile + case .unregistered(let fd): + request.fileDescriptor = fd + } + request.buffer = buffer + request.offset = offset + return request +} + extension IORequest { @inlinable @inline(__always) - public func makeRawRequest() -> RawIORequest { + public consuming func makeRawRequest() -> RawIORequest { var request = RawIORequest() - switch self { - case .nop: - request.operation = .nop - case .openat(let atDirectory, let path, let mode, let options, let permissions, let slot): - // TODO: use rawValue less - request.operation = .openAt - request.fileDescriptor = atDirectory - request.rawValue.addr = unsafeBitCast(path, to: UInt64.self) - request.rawValue.open_flags = UInt32(bitPattern: options.rawValue | mode.rawValue) - request.rawValue.len = permissions?.rawValue ?? 0 - if let fileSlot = slot { - request.rawValue.file_index = UInt32(fileSlot.index + 1) - } - case .read(let file, let buffer, let offset), .write(let file, let buffer, let offset): - if case .read = self { - if case .registered = buffer { - request.operation = .readFixed - } else { - request.operation = .read - } - } else { - if case .registered = buffer { - request.operation = .writeFixed - } else { - request.operation = .write - } - } - switch file { - case .registered(let regFile): - request.rawValue.fd = Int32(exactly: regFile.index)! - request.flags = .fixedFile - case .unregistered(let fd): - request.fileDescriptor = fd - } - switch buffer { - case .registered(let regBuf): - request.buffer = regBuf.unsafeBuffer - request.rawValue.buf_index = UInt16(exactly: regBuf.index)! - case .unregistered(let buf): - request.buffer = buf - } - request.offset = offset - case .close(let file): - request.operation = .close - switch file { - case .registered(let regFile): - request.rawValue.file_index = UInt32(regFile.index + 1) - case .unregistered(let normalFile): - request.fileDescriptor = normalFile - } + switch consume self { + case .nop: + request.operation = .nop + case .openat(let atDirectory, let path, let mode, let options, let permissions, let slot): + // TODO: use rawValue less + request.operation = .openAt + request.fileDescriptor = atDirectory + request.rawValue.addr = unsafeBitCast(path, to: UInt64.self) + request.rawValue.open_flags = UInt32(bitPattern: options.rawValue | mode.rawValue) + request.rawValue.len = permissions?.rawValue ?? 0 + if let fileSlot = slot { + request.rawValue.file_index = UInt32(fileSlot.index + 1) + } + case .write(let file, let buffer, let offset): + switch consume buffer { + case .registered(let buffer): + request.operation = .writeFixed + return makeRawRequest_readWrite_registered( + file: file, buffer: buffer, offset: offset, request: request) + + case .unregistered(let buffer): + request.operation = .write + return makeRawRequest_readWrite_unregistered( + file: file, buffer: buffer, offset: offset, request: request) + } + case .read(let file, let buffer, let offset): + + switch consume buffer { + case .registered(let buffer): + request.operation = .readFixed + return makeRawRequest_readWrite_registered( + file: file, buffer: buffer, offset: offset, request: request) + + case .unregistered(let buffer): + request.operation = .read + return makeRawRequest_readWrite_unregistered( + file: file, buffer: buffer, offset: offset, request: request) + } + case .close(let file): + request.operation = .close + switch file { + case .registered(let regFile): + request.rawValue.file_index = UInt32(regFile.index + 1) + case .unregistered(let normalFile): + request.fileDescriptor = normalFile + } } return request } diff --git a/Sources/System/IORing.swift b/Sources/System/IORing.swift index 656e1683..3e24408f 100644 --- a/Sources/System/IORing.swift +++ b/Sources/System/IORing.swift @@ -1,8 +1,8 @@ @_implementationOnly import CSystem -import struct CSystem.io_uring_sqe +import Glibc // needed for mmap +import Synchronization -@_implementationOnly import Synchronization -import Glibc // needed for mmap +import struct CSystem.io_uring_sqe // XXX: this *really* shouldn't be here. oh well. extension UnsafeMutableRawPointer { @@ -26,7 +26,7 @@ extension UnsafeMutableRawPointer { // currently used by the kernel only in SQPOLL mode to indicate // when the polling thread needs to be woken up let flags: UnsafePointer> - + // ring array // maps indexes between the actual ring and the submissionQueueEntries list, // allowing the latter to be used as a kind of freelist with enough work? @@ -48,34 +48,40 @@ struct CQRing: ~Copyable { internal class ResourceManager: @unchecked Sendable { typealias Resource = T - let resourceList: UnsafeMutableBufferPointer - var freeList: [Int] - let mutex: Mutex + + struct Resources { + let resourceList: UnsafeMutableBufferPointer + var freeList: [Int] + } + + let mutex: Mutex init(_ res: UnsafeMutableBufferPointer) { - self.resourceList = res - self.freeList = [Int](resourceList.indices) - self.mutex = Mutex() + mutex = Mutex( + Resources( + resourceList: res, + freeList: [Int](res.indices) + )) } func getResource() -> IOResource? { - self.mutex.lock() - defer { self.mutex.unlock() } - if let index = freeList.popLast() { - return IOResource( - rescource: resourceList[index], - index: index, - manager: self - ) - } else { - return nil + mutex.withLock { resources in + if let index = resources.freeList.popLast() { + return IOResource( + resource: resources.resourceList[index], + index: index, + manager: self + ) + } else { + return nil + } } } func releaseResource(index: Int) { - self.mutex.lock() - defer { self.mutex.unlock() } - self.freeList.append(index) + mutex.withLock { resources in + resources.freeList.append(index) + } } } @@ -86,11 +92,11 @@ public struct IOResource: ~Copyable { let manager: ResourceManager internal init( - rescource: T, + resource: T, index: Int, manager: ResourceManager ) { - self.resource = rescource + self.resource = resource self.index = index self.manager = manager } @@ -114,13 +120,89 @@ extension IORingFileSlot { } extension IORingBuffer { public var unsafeBuffer: UnsafeMutableRawBufferPointer { - get { - return .init(start: resource.iov_base, count: resource.iov_len) + return .init(start: resource.iov_base, count: resource.iov_len) + } +} + +@inlinable @inline(__always) +internal func _writeRequest(_ request: __owned RawIORequest, ring: inout SQRing, submissionQueueEntries: UnsafeMutableBufferPointer) + -> Bool +{ + let entry = _blockingGetSubmissionEntry(ring: &ring, submissionQueueEntries: submissionQueueEntries) + entry.pointee = request.rawValue + return true +} + +@inlinable @inline(__always) +internal func _blockingGetSubmissionEntry(ring: inout SQRing, submissionQueueEntries: UnsafeMutableBufferPointer) -> UnsafeMutablePointer< + io_uring_sqe +> { + while true { + if let entry = _getSubmissionEntry(ring: &ring, submissionQueueEntries: submissionQueueEntries) { + return entry } + // TODO: actually block here instead of spinning } + +} + +internal func _submitRequests(ring: inout SQRing, ringDescriptor: Int32) { + let flushedEvents = _flushQueue(ring: &ring) + + // Ring always needs enter right now; + // TODO: support SQPOLL here + while true { + let ret = io_uring_enter(ringDescriptor, flushedEvents, 0, 0, nil) + // error handling: + // EAGAIN / EINTR (try again), + // EBADF / EBADFD / EOPNOTSUPP / ENXIO + // (failure in ring lifetime management, fatal), + // EINVAL (bad constant flag?, fatal), + // EFAULT (bad address for argument from library, fatal) + if ret == -EAGAIN || ret == -EINTR { + continue + } else if ret < 0 { + fatalError( + "fatal error in submitting requests: " + Errno(rawValue: -ret).debugDescription + ) + } else { + break + } + } +} + +internal func _flushQueue(ring: inout SQRing) -> UInt32 { + ring.kernelTail.pointee.store( + ring.userTail, ordering: .relaxed + ) + return ring.userTail - ring.kernelHead.pointee.load(ordering: .relaxed) } +@usableFromInline @inline(__always) +internal func _getSubmissionEntry(ring: inout SQRing, submissionQueueEntries: UnsafeMutableBufferPointer) -> UnsafeMutablePointer< + io_uring_sqe +>? { + let next = ring.userTail + 1 + // FEAT: smp load when SQPOLL in use (not in MVP) + let kernelHead = ring.kernelHead.pointee.load(ordering: .relaxed) + + // FEAT: 128-bit event support (not in MVP) + if next - kernelHead <= ring.array.count { + // let sqe = &sq->sqes[(sq->sqe_tail & sq->ring_mask) << shift]; + let sqeIndex = Int( + ring.userTail & ring.ringMask + ) + + let sqe = submissionQueueEntries + .baseAddress.unsafelyUnwrapped + .advanced(by: sqeIndex) + + ring.userTail = next + return sqe + } + return nil +} // XXX: This should be a non-copyable type (?) // demo only runs on Swift 5.8.1 @@ -128,15 +210,13 @@ public struct IORing: @unchecked Sendable, ~Copyable { let ringFlags: UInt32 let ringDescriptor: Int32 - @usableFromInline var submissionRing: SQRing - @usableFromInline var submissionMutex: Mutex + @usableFromInline let submissionMutex: Mutex // FEAT: set this eventually let submissionPolling: Bool = false - var completionRing: CQRing - var completionMutex: Mutex + let completionMutex: Mutex - let submissionQueueEntries: UnsafeMutableBufferPointer + @usableFromInline let submissionQueueEntries: UnsafeMutableBufferPointer // kept around for unmap / cleanup let ringSize: Int @@ -149,28 +229,31 @@ public struct IORing: @unchecked Sendable, ~Copyable { var params = io_uring_params() ringDescriptor = withUnsafeMutablePointer(to: ¶ms) { - return io_uring_setup(queueDepth, $0); + return io_uring_setup(queueDepth, $0) } - if (params.features & IORING_FEAT_SINGLE_MMAP == 0 - || params.features & IORING_FEAT_NODROP == 0) { + if params.features & IORING_FEAT_SINGLE_MMAP == 0 + || params.features & IORING_FEAT_NODROP == 0 + { close(ringDescriptor) // TODO: error handling throw IORingError.missingRequiredFeatures } - - if (ringDescriptor < 0) { + + if ringDescriptor < 0 { // TODO: error handling } - let submitRingSize = params.sq_off.array + let submitRingSize = + params.sq_off.array + params.sq_entries * UInt32(MemoryLayout.size) - - let completionRingSize = params.cq_off.cqes + + let completionRingSize = + params.cq_off.cqes + params.cq_entries * UInt32(MemoryLayout.size) ringSize = Int(max(submitRingSize, completionRingSize)) - + ringPtr = mmap( /* addr: */ nil, /* len: */ ringSize, @@ -178,35 +261,36 @@ public struct IORing: @unchecked Sendable, ~Copyable { /* flags: */ MAP_SHARED | MAP_POPULATE, /* fd: */ ringDescriptor, /* offset: */ __off_t(IORING_OFF_SQ_RING) - ); + ) - if (ringPtr == MAP_FAILED) { - perror("mmap"); + if ringPtr == MAP_FAILED { + perror("mmap") // TODO: error handling fatalError("mmap failed in ring setup") } - submissionRing = SQRing( + let submissionRing = SQRing( kernelHead: UnsafePointer>( ringPtr.advanced(by: params.sq_off.head) - .assumingMemoryBound(to: Atomic.self) + .assumingMemoryBound(to: Atomic.self) ), kernelTail: UnsafePointer>( ringPtr.advanced(by: params.sq_off.tail) - .assumingMemoryBound(to: Atomic.self) + .assumingMemoryBound(to: Atomic.self) ), - userTail: 0, // no requests yet + userTail: 0, // no requests yet ringMask: ringPtr.advanced(by: params.sq_off.ring_mask) .assumingMemoryBound(to: UInt32.self).pointee, flags: UnsafePointer>( ringPtr.advanced(by: params.sq_off.flags) - .assumingMemoryBound(to: Atomic.self) + .assumingMemoryBound(to: Atomic.self) ), array: UnsafeMutableBufferPointer( start: ringPtr.advanced(by: params.sq_off.array) .assumingMemoryBound(to: UInt32.self), - count: Int(ringPtr.advanced(by: params.sq_off.ring_entries) - .assumingMemoryBound(to: UInt32.self).pointee) + count: Int( + ringPtr.advanced(by: params.sq_off.ring_entries) + .assumingMemoryBound(to: UInt32.self).pointee) ) ) @@ -223,10 +307,10 @@ public struct IORing: @unchecked Sendable, ~Copyable { /* flags: */ MAP_SHARED | MAP_POPULATE, /* fd: */ ringDescriptor, /* offset: */ __off_t(IORING_OFF_SQES) - ); + ) - if (sqes == MAP_FAILED) { - perror("mmap"); + if sqes == MAP_FAILED { + perror("mmap") // TODO: error handling fatalError("sqe mmap failed in ring setup") } @@ -236,76 +320,77 @@ public struct IORing: @unchecked Sendable, ~Copyable { count: Int(params.sq_entries) ) - completionRing = CQRing( + let completionRing = CQRing( kernelHead: UnsafePointer>( ringPtr.advanced(by: params.cq_off.head) - .assumingMemoryBound(to: Atomic.self) + .assumingMemoryBound(to: Atomic.self) ), kernelTail: UnsafePointer>( ringPtr.advanced(by: params.cq_off.tail) - .assumingMemoryBound(to: Atomic.self) + .assumingMemoryBound(to: Atomic.self) ), - userHead: 0, // no completions yet + userHead: 0, // no completions yet ringMask: ringPtr.advanced(by: params.cq_off.ring_mask) .assumingMemoryBound(to: UInt32.self).pointee, cqes: UnsafeBufferPointer( start: ringPtr.advanced(by: params.cq_off.cqes) .assumingMemoryBound(to: io_uring_cqe.self), - count: Int(ringPtr.advanced(by: params.cq_off.ring_entries) - .assumingMemoryBound(to: UInt32.self).pointee) + count: Int( + ringPtr.advanced(by: params.cq_off.ring_entries) + .assumingMemoryBound(to: UInt32.self).pointee) ) ) - self.submissionMutex = Mutex() - self.completionMutex = Mutex() + self.submissionMutex = Mutex(submissionRing) + self.completionMutex = Mutex(completionRing) self.ringFlags = params.flags } public func blockingConsumeCompletion() -> IOCompletion { - self.completionMutex.lock() - defer { self.completionMutex.unlock() } - - if let completion = _tryConsumeCompletion() { - return completion - } else { - while true { - let res = io_uring_enter(ringDescriptor, 0, 1, IORING_ENTER_GETEVENTS, nil) - // error handling: - // EAGAIN / EINTR (try again), - // EBADF / EBADFD / EOPNOTSUPP / ENXIO - // (failure in ring lifetime management, fatal), - // EINVAL (bad constant flag?, fatal), - // EFAULT (bad address for argument from library, fatal) - // EBUSY (not enough space for events; implies events filled - // by kernel between kernelTail load and now) - if res >= 0 || res == -EBUSY { - break - } else if res == -EAGAIN || res == -EINTR { - continue + completionMutex.withLock { ring in + if let completion = _tryConsumeCompletion(ring: &ring) { + return completion + } else { + while true { + let res = io_uring_enter(ringDescriptor, 0, 1, IORING_ENTER_GETEVENTS, nil) + // error handling: + // EAGAIN / EINTR (try again), + // EBADF / EBADFD / EOPNOTSUPP / ENXIO + // (failure in ring lifetime management, fatal), + // EINVAL (bad constant flag?, fatal), + // EFAULT (bad address for argument from library, fatal) + // EBUSY (not enough space for events; implies events filled + // by kernel between kernelTail load and now) + if res >= 0 || res == -EBUSY { + break + } else if res == -EAGAIN || res == -EINTR { + continue + } + fatalError( + "fatal error in receiving requests: " + + Errno(rawValue: -res).debugDescription + ) } - fatalError("fatal error in receiving requests: " + - Errno(rawValue: -res).debugDescription - ) + return _tryConsumeCompletion(ring: &ring).unsafelyUnwrapped } - return _tryConsumeCompletion().unsafelyUnwrapped } } public func tryConsumeCompletion() -> IOCompletion? { - self.completionMutex.lock() - defer { self.completionMutex.unlock() } - return _tryConsumeCompletion() + completionMutex.withLock { ring in + return _tryConsumeCompletion(ring: &ring) + } } - func _tryConsumeCompletion() -> IOCompletion? { - let tail = completionRing.kernelTail.pointee.load(ordering: .acquiring) - let head = completionRing.kernelHead.pointee.load(ordering: .relaxed) - + func _tryConsumeCompletion(ring: inout CQRing) -> IOCompletion? { + let tail = ring.kernelTail.pointee.load(ordering: .acquiring) + let head = ring.kernelHead.pointee.load(ordering: .relaxed) + if tail != head { // 32 byte copy - oh well - let res = completionRing.cqes[Int(head & completionRing.ringMask)] - completionRing.kernelHead.pointee.store(head + 1, ordering: .relaxed) + let res = ring.cqes[Int(head & ring.ringMask)] + ring.kernelHead.pointee.store(head + 1, ordering: .relaxed) return IOCompletion(rawValue: res) } @@ -340,7 +425,8 @@ public struct IORing: @unchecked Sendable, ~Copyable { for i in 0.. UInt32 { - self.submissionRing.kernelTail.pointee.store( - self.submissionRing.userTail, ordering: .relaxed - ) - return self.submissionRing.userTail - - self.submissionRing.kernelHead.pointee.load(ordering: .relaxed) - } - - @inlinable @inline(__always) public mutating func writeRequest(_ request: __owned IORequest) -> Bool { - self.submissionMutex.lock() - defer { self.submissionMutex.unlock() } - return _writeRequest(request.makeRawRequest()) - } - - @inlinable @inline(__always) - internal mutating func _writeRequest(_ request: __owned RawIORequest) -> Bool { - let entry = _blockingGetSubmissionEntry() - entry.pointee = request.rawValue - return true - } - - @inlinable @inline(__always) - internal mutating func _blockingGetSubmissionEntry() -> UnsafeMutablePointer { - while true { - if let entry = _getSubmissionEntry() { - return entry - } - // TODO: actually block here instead of spinning + let raw = request.makeRawRequest() + return submissionMutex.withLock { ring in + return _writeRequest(raw, ring: &ring, submissionQueueEntries: submissionQueueEntries) } - - } - - @usableFromInline @inline(__always) - internal mutating func _getSubmissionEntry() -> UnsafeMutablePointer? { - let next = self.submissionRing.userTail + 1 - - // FEAT: smp load when SQPOLL in use (not in MVP) - let kernelHead = self.submissionRing.kernelHead.pointee.load(ordering: .relaxed) - - // FEAT: 128-bit event support (not in MVP) - if (next - kernelHead <= self.submissionRing.array.count) { - // let sqe = &sq->sqes[(sq->sqe_tail & sq->ring_mask) << shift]; - let sqeIndex = Int( - self.submissionRing.userTail & self.submissionRing.ringMask - ) - - let sqe = self.submissionQueueEntries - .baseAddress.unsafelyUnwrapped - .advanced(by: sqeIndex) - - self.submissionRing.userTail = next; - return sqe - } - return nil } deinit { - munmap(ringPtr, ringSize); + munmap(ringPtr, ringSize) munmap( UnsafeMutableRawPointer(submissionQueueEntries.baseAddress!), submissionQueueEntries.count * MemoryLayout.size ) close(ringDescriptor) } -}; - +} diff --git a/Sources/System/Lock.swift b/Sources/System/Lock.swift deleted file mode 100644 index fd20c641..00000000 --- a/Sources/System/Lock.swift +++ /dev/null @@ -1,37 +0,0 @@ -// TODO: write against kernel APIs directly? -import Glibc - -@usableFromInline final class Mutex { - @usableFromInline let mutex: UnsafeMutablePointer - - @inlinable init() { - self.mutex = UnsafeMutablePointer.allocate(capacity: 1) - self.mutex.initialize(to: pthread_mutex_t()) - pthread_mutex_init(self.mutex, nil) - } - - @inlinable deinit { - defer { mutex.deallocate() } - guard pthread_mutex_destroy(mutex) == 0 else { - preconditionFailure("unable to destroy mutex") - } - } - - // XXX: this is because we need to lock the mutex in the context of a submit() function - // and unlock *before* the UnsafeContinuation returns. - // Code looks like: { - // // prepare request - // io_uring_get_sqe() - // io_uring_prep_foo(...) - // return await withUnsafeContinuation { - // sqe->user_data = ...; io_uring_submit(); unlock(); - // } - // } - @inlinable @inline(__always) public func lock() { - pthread_mutex_lock(mutex) - } - - @inlinable @inline(__always) public func unlock() { - pthread_mutex_unlock(mutex) - } -} diff --git a/Sources/System/ManagedIORing.swift b/Sources/System/ManagedIORing.swift index 7f5bec22..88fb72e7 100644 --- a/Sources/System/ManagedIORing.swift +++ b/Sources/System/ManagedIORing.swift @@ -5,15 +5,16 @@ final public class ManagedIORing: @unchecked Sendable { self.internalRing = try IORing(queueDepth: queueDepth) self.internalRing.registerBuffers(bufSize: 655336, count: 4) self.internalRing.registerFiles(count: 32) - self.startWaiter() + self.startWaiter() } private func startWaiter() { Task.detached { - while (!Task.isCancelled) { + while !Task.isCancelled { let cqe = self.internalRing.blockingConsumeCompletion() - let cont = unsafeBitCast(cqe.userData, to: UnsafeContinuation.self) + let cont = unsafeBitCast( + cqe.userData, to: UnsafeContinuation.self) cont.resume(returning: cqe) } } @@ -21,14 +22,18 @@ final public class ManagedIORing: @unchecked Sendable { @_unsafeInheritExecutor public func submitAndWait(_ request: __owned IORequest) async -> IOCompletion { - self.internalRing.submissionMutex.lock() + var consumeOnceWorkaround: IORequest? = request return await withUnsafeContinuation { cont in - let entry = internalRing._blockingGetSubmissionEntry() - entry.pointee = request.makeRawRequest().rawValue - entry.pointee.user_data = unsafeBitCast(cont, to: UInt64.self) - self.internalRing._submitRequests() - self.internalRing.submissionMutex.unlock() + return internalRing.submissionMutex.withLock { ring in + let request = consumeOnceWorkaround.take()! + let entry = _blockingGetSubmissionEntry( + ring: &ring, submissionQueueEntries: internalRing.submissionQueueEntries) + entry.pointee = request.makeRawRequest().rawValue + entry.pointee.user_data = unsafeBitCast(cont, to: UInt64.self) + _submitRequests(ring: &ring, ringDescriptor: internalRing.ringDescriptor) + } } + } internal func getFileSlot() -> IORingFileSlot? { @@ -39,4 +44,4 @@ final public class ManagedIORing: @unchecked Sendable { self.internalRing.getBuffer() } -} \ No newline at end of file +} diff --git a/Tests/SystemTests/AsyncFileDescriptorTests.swift b/Tests/SystemTests/AsyncFileDescriptorTests.swift index 0f1c103c..1baba962 100644 --- a/Tests/SystemTests/AsyncFileDescriptorTests.swift +++ b/Tests/SystemTests/AsyncFileDescriptorTests.swift @@ -23,7 +23,7 @@ final class AsyncFileDescriptorTests: XCTestCase { .readOnly, onRing: ring ) - await try file.close() + try await file.close() } func testDevNullEmpty() async throws { From 32966f975d8d4285059c29eaccd1cee46c56b84f Mon Sep 17 00:00:00 2001 From: David Smith Date: Mon, 28 Oct 2024 23:43:59 +0000 Subject: [PATCH 268/427] It builds again! With some horrible hacks --- Sources/System/AsyncFileDescriptor.swift | 117 +++++++++++------- Sources/System/IOCompletion.swift | 1 + Sources/System/IORequest.swift | 58 ++++----- Sources/System/IORing.swift | 22 ++-- Sources/System/ManagedIORing.swift | 4 +- Sources/System/RawIORequest.swift | 2 +- .../AsyncFileDescriptorTests.swift | 2 +- Tests/SystemTests/IORequestTests.swift | 4 +- 8 files changed, 124 insertions(+), 86 deletions(-) diff --git a/Sources/System/AsyncFileDescriptor.swift b/Sources/System/AsyncFileDescriptor.swift index 504aec9f..a00f41de 100644 --- a/Sources/System/AsyncFileDescriptor.swift +++ b/Sources/System/AsyncFileDescriptor.swift @@ -1,13 +1,12 @@ @_implementationOnly import CSystem - -public final class AsyncFileDescriptor { +public struct AsyncFileDescriptor: ~Copyable { @usableFromInline var open: Bool = true @usableFromInline let fileSlot: IORingFileSlot @usableFromInline let ring: ManagedIORing - + public static func openat( - atDirectory: FileDescriptor = FileDescriptor(rawValue: -100), + atDirectory: FileDescriptor = FileDescriptor(rawValue: -100), path: FilePath, _ mode: FileDescriptor.AccessMode, options: FileDescriptor.OpenOptions = FileDescriptor.OpenOptions(), @@ -19,35 +18,37 @@ public final class AsyncFileDescriptor { throw IORingError.missingRequiredFeatures } let cstr = path.withCString { - return $0 // bad + return $0 // bad } - let res = await ring.submitAndWait(.openat( - atDirectory: atDirectory, - path: cstr, - mode, - options: options, - permissions: permissions, - intoSlot: fileSlot - )) + let res = await ring.submitAndWait( + .openat( + atDirectory: atDirectory, + path: cstr, + mode, + options: options, + permissions: permissions, + intoSlot: fileSlot.borrow() + )) if res.result < 0 { throw Errno(rawValue: -res.result) } - + return AsyncFileDescriptor( fileSlot, ring: ring ) } - internal init(_ fileSlot: IORingFileSlot, ring: ManagedIORing) { - self.fileSlot = fileSlot + internal init(_ fileSlot: consuming IORingFileSlot, ring: ManagedIORing) { + self.fileSlot = consume fileSlot self.ring = ring } @inlinable @inline(__always) @_unsafeInheritExecutor - public func close() async throws { - let res = await ring.submitAndWait(.close( - .registered(self.fileSlot) - )) + public consuming func close() async throws { + let res = await ring.submitAndWait( + .close( + .registered(self.fileSlot) + )) if res.result < 0 { throw Errno(rawValue: -res.result) } @@ -56,14 +57,16 @@ public final class AsyncFileDescriptor { @inlinable @inline(__always) @_unsafeInheritExecutor public func read( - into buffer: IORequest.Buffer, + into buffer: inout UnsafeMutableRawBufferPointer, atAbsoluteOffset offset: UInt64 = UInt64.max ) async throws -> UInt32 { - let res = await ring.submitAndWait(.read( - file: .registered(self.fileSlot), - buffer: buffer, - offset: offset - )) + let file = fileSlot.borrow() + let res = await ring.submitAndWait( + .readUnregistered( + file: .registered(file), + buffer: buffer, + offset: offset + )) if res.result < 0 { throw Errno(rawValue: -res.result) } else { @@ -71,23 +74,54 @@ public final class AsyncFileDescriptor { } } - deinit { - if (self.open) { - // TODO: close or error? TBD + @inlinable @inline(__always) @_unsafeInheritExecutor + public func read( + into buffer: borrowing IORingBuffer, //TODO: should be inout? + atAbsoluteOffset offset: UInt64 = UInt64.max + ) async throws -> UInt32 { + let res = await ring.submitAndWait( + .read( + file: .registered(self.fileSlot.borrow()), + buffer: buffer.borrow(), + offset: offset + )) + if res.result < 0 { + throw Errno(rawValue: -res.result) + } else { + return UInt32(bitPattern: res.result) } } + + //TODO: temporary workaround until AsyncSequence supports ~Copyable + public consuming func toBytes() -> AsyncFileDescriptorSequence { + AsyncFileDescriptorSequence(self) + } + + //TODO: can we do the linear types thing and error if they don't consume it manually? + // deinit { + // if self.open { + // TODO: close or error? TBD + // } + // } } -extension AsyncFileDescriptor: AsyncSequence { +public class AsyncFileDescriptorSequence: AsyncSequence { + var descriptor: AsyncFileDescriptor? + public func makeAsyncIterator() -> FileIterator { - return .init(self) + return .init(descriptor.take()!) + } + + internal init(_ descriptor: consuming AsyncFileDescriptor) { + self.descriptor = consume descriptor } public typealias AsyncIterator = FileIterator public typealias Element = UInt8 } -public struct FileIterator: AsyncIteratorProtocol { +//TODO: only a class due to ~Copyable limitations +public class FileIterator: AsyncIteratorProtocol { @usableFromInline let file: AsyncFileDescriptor @usableFromInline var buffer: IORingBuffer @usableFromInline var done: Bool @@ -95,28 +129,27 @@ public struct FileIterator: AsyncIteratorProtocol { @usableFromInline internal var currentByte: UnsafeRawPointer? @usableFromInline internal var lastByte: UnsafeRawPointer? - init(_ file: AsyncFileDescriptor) { - self.file = file + init(_ file: consuming AsyncFileDescriptor) { self.buffer = file.ring.getBuffer()! + self.file = file self.done = false } @inlinable @inline(__always) - public mutating func nextBuffer() async throws { - let buffer = self.buffer - - let bytesRead = try await file.read(into: .registered(buffer)) + public func nextBuffer() async throws { + let bytesRead = Int(try await file.read(into: buffer)) if _fastPath(bytesRead != 0) { - let bufPointer = buffer.unsafeBuffer.baseAddress.unsafelyUnwrapped + let unsafeBuffer = buffer.unsafeBuffer + let bufPointer = unsafeBuffer.baseAddress.unsafelyUnwrapped self.currentByte = UnsafeRawPointer(bufPointer) - self.lastByte = UnsafeRawPointer(bufPointer.advanced(by: Int(bytesRead))) + self.lastByte = UnsafeRawPointer(bufPointer.advanced(by: bytesRead)) } else { - self.done = true + done = true } } @inlinable @inline(__always) @_unsafeInheritExecutor - public mutating func next() async throws -> UInt8? { + public func next() async throws -> UInt8? { if _fastPath(currentByte != lastByte) { // SAFETY: both pointers should be non-nil if they're not equal let byte = currentByte.unsafelyUnwrapped.load(as: UInt8.self) diff --git a/Sources/System/IOCompletion.swift b/Sources/System/IOCompletion.swift index 5e226322..8bf173c9 100644 --- a/Sources/System/IOCompletion.swift +++ b/Sources/System/IOCompletion.swift @@ -1,5 +1,6 @@ @_implementationOnly import CSystem +//TODO: should be ~Copyable, but requires UnsafeContinuation add ~Copyable support public struct IOCompletion { let rawValue: io_uring_cqe } diff --git a/Sources/System/IORequest.swift b/Sources/System/IORequest.swift index 2ecd3f46..2a47c62e 100644 --- a/Sources/System/IORequest.swift +++ b/Sources/System/IORequest.swift @@ -1,7 +1,7 @@ import struct CSystem.io_uring_sqe public enum IORequest: ~Copyable { - case nop // nothing here + case nop // nothing here case openat( atDirectory: FileDescriptor, path: UnsafePointer, @@ -12,21 +12,26 @@ public enum IORequest: ~Copyable { ) case read( file: File, - buffer: Buffer, + buffer: IORingBuffer, + offset: UInt64 = 0 + ) + case readUnregistered( + file: File, + buffer: UnsafeMutableRawBufferPointer, offset: UInt64 = 0 ) case write( file: File, - buffer: Buffer, + buffer: IORingBuffer, + offset: UInt64 = 0 + ) + case writeUnregistered( + file: File, + buffer: UnsafeMutableRawBufferPointer, offset: UInt64 = 0 ) case close(File) - public enum Buffer: ~Copyable { - case registered(IORingBuffer) - case unregistered(UnsafeMutableRawBufferPointer) - } - public enum File: ~Copyable { case registered(IORingFileSlot) case unregistered(FileDescriptor) @@ -90,30 +95,21 @@ extension IORequest { request.rawValue.file_index = UInt32(fileSlot.index + 1) } case .write(let file, let buffer, let offset): - switch consume buffer { - case .registered(let buffer): - request.operation = .writeFixed - return makeRawRequest_readWrite_registered( - file: file, buffer: buffer, offset: offset, request: request) - - case .unregistered(let buffer): - request.operation = .write - return makeRawRequest_readWrite_unregistered( - file: file, buffer: buffer, offset: offset, request: request) - } + request.operation = .writeFixed + return makeRawRequest_readWrite_registered( + file: file, buffer: buffer, offset: offset, request: request) + case .writeUnregistered(let file, let buffer, let offset): + request.operation = .write + return makeRawRequest_readWrite_unregistered( + file: file, buffer: buffer, offset: offset, request: request) case .read(let file, let buffer, let offset): - - switch consume buffer { - case .registered(let buffer): - request.operation = .readFixed - return makeRawRequest_readWrite_registered( - file: file, buffer: buffer, offset: offset, request: request) - - case .unregistered(let buffer): - request.operation = .read - return makeRawRequest_readWrite_unregistered( - file: file, buffer: buffer, offset: offset, request: request) - } + request.operation = .readFixed + return makeRawRequest_readWrite_registered( + file: file, buffer: buffer, offset: offset, request: request) + case .readUnregistered(let file, let buffer, let offset): + request.operation = .read + return makeRawRequest_readWrite_unregistered( + file: file, buffer: buffer, offset: offset, request: request) case .close(let file): request.operation = .close switch file { diff --git a/Sources/System/IORing.swift b/Sources/System/IORing.swift index 3e24408f..2f18cb59 100644 --- a/Sources/System/IORing.swift +++ b/Sources/System/IORing.swift @@ -51,7 +51,7 @@ internal class ResourceManager: @unchecked Sendable { struct Resources { let resourceList: UnsafeMutableBufferPointer - var freeList: [Int] + var freeList: [Int] //TODO: bitvector? } let mutex: Mutex @@ -90,23 +90,33 @@ public struct IOResource: ~Copyable { @usableFromInline let resource: T @usableFromInline let index: Int let manager: ResourceManager + let isBorrow: Bool //TODO: this is a workaround for lifetime issues and should be removed internal init( resource: T, index: Int, - manager: ResourceManager + manager: ResourceManager, + isBorrow: Bool = false ) { self.resource = resource self.index = index self.manager = manager + self.isBorrow = isBorrow } func withResource() { } + //TODO: this is a workaround for lifetime issues and should be removed + @usableFromInline func borrow() -> IOResource { + IOResource(resource: resource, index: index, manager: manager, isBorrow: true) + } + deinit { - self.manager.releaseResource(index: self.index) + if !isBorrow { + manager.releaseResource(index: self.index) + } } } @@ -204,8 +214,6 @@ internal func _getSubmissionEntry(ring: inout SQRing, submissionQueueEntries: Un return nil } -// XXX: This should be a non-copyable type (?) -// demo only runs on Swift 5.8.1 public struct IORing: @unchecked Sendable, ~Copyable { let ringFlags: UInt32 let ringDescriptor: Int32 @@ -455,9 +463,9 @@ public struct IORing: @unchecked Sendable, ~Copyable { @inlinable @inline(__always) public mutating func writeRequest(_ request: __owned IORequest) -> Bool { - let raw = request.makeRawRequest() + var raw: RawIORequest? = request.makeRawRequest() return submissionMutex.withLock { ring in - return _writeRequest(raw, ring: &ring, submissionQueueEntries: submissionQueueEntries) + return _writeRequest(raw.take()!, ring: &ring, submissionQueueEntries: submissionQueueEntries) } } diff --git a/Sources/System/ManagedIORing.swift b/Sources/System/ManagedIORing.swift index 88fb72e7..73ea2314 100644 --- a/Sources/System/ManagedIORing.swift +++ b/Sources/System/ManagedIORing.swift @@ -37,11 +37,11 @@ final public class ManagedIORing: @unchecked Sendable { } internal func getFileSlot() -> IORingFileSlot? { - self.internalRing.getFile() + internalRing.getFile() } internal func getBuffer() -> IORingBuffer? { - self.internalRing.getBuffer() + internalRing.getBuffer() } } diff --git a/Sources/System/RawIORequest.swift b/Sources/System/RawIORequest.swift index 520cb85c..52407b03 100644 --- a/Sources/System/RawIORequest.swift +++ b/Sources/System/RawIORequest.swift @@ -2,7 +2,7 @@ @_implementationOnly import CSystem import struct CSystem.io_uring_sqe -public struct RawIORequest { +public struct RawIORequest: ~Copyable { @usableFromInline var rawValue: io_uring_sqe public init() { diff --git a/Tests/SystemTests/AsyncFileDescriptorTests.swift b/Tests/SystemTests/AsyncFileDescriptorTests.swift index 1baba962..7de22724 100644 --- a/Tests/SystemTests/AsyncFileDescriptorTests.swift +++ b/Tests/SystemTests/AsyncFileDescriptorTests.swift @@ -33,7 +33,7 @@ final class AsyncFileDescriptorTests: XCTestCase { .readOnly, onRing: ring ) - for try await _ in file { + for try await _ in file.toBytes() { XCTFail("/dev/null should be empty") } } diff --git a/Tests/SystemTests/IORequestTests.swift b/Tests/SystemTests/IORequestTests.swift index a44a607e..a1e14533 100644 --- a/Tests/SystemTests/IORequestTests.swift +++ b/Tests/SystemTests/IORequestTests.swift @@ -6,7 +6,7 @@ import XCTest import System #endif -func requestBytes(_ request: RawIORequest) -> [UInt8] { +func requestBytes(_ request: consuming RawIORequest) -> [UInt8] { return withUnsafePointer(to: request) { let requestBuf = UnsafeBufferPointer(start: $0, count: 1) let rawBytes = UnsafeRawBufferPointer(requestBuf) @@ -40,7 +40,7 @@ final class IORequestTests: XCTestCase { .readOnly, options: [], permissions: nil, - intoSlot: fileSlot + intoSlot: fileSlot.borrow() ) let expectedRequest: [UInt8] = { From 822e4814d18de785c06fe4edbe31f750c3898f1b Mon Sep 17 00:00:00 2001 From: David Smith Date: Mon, 28 Oct 2024 23:49:37 +0000 Subject: [PATCH 269/427] Adopt isolation parameters --- Sources/System/AsyncFileDescriptor.swift | 16 +++++++++------- Sources/System/ManagedIORing.swift | 3 +-- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/Sources/System/AsyncFileDescriptor.swift b/Sources/System/AsyncFileDescriptor.swift index a00f41de..68d9e33c 100644 --- a/Sources/System/AsyncFileDescriptor.swift +++ b/Sources/System/AsyncFileDescriptor.swift @@ -43,8 +43,8 @@ public struct AsyncFileDescriptor: ~Copyable { self.ring = ring } - @inlinable @inline(__always) @_unsafeInheritExecutor - public consuming func close() async throws { + @inlinable @inline(__always) + public consuming func close(isolation actor: isolated (any Actor)? = #isolation) async throws { let res = await ring.submitAndWait( .close( .registered(self.fileSlot) @@ -55,10 +55,11 @@ public struct AsyncFileDescriptor: ~Copyable { self.open = false } - @inlinable @inline(__always) @_unsafeInheritExecutor + @inlinable @inline(__always) public func read( into buffer: inout UnsafeMutableRawBufferPointer, - atAbsoluteOffset offset: UInt64 = UInt64.max + atAbsoluteOffset offset: UInt64 = UInt64.max, + isolation actor: isolated (any Actor)? = #isolation ) async throws -> UInt32 { let file = fileSlot.borrow() let res = await ring.submitAndWait( @@ -74,10 +75,11 @@ public struct AsyncFileDescriptor: ~Copyable { } } - @inlinable @inline(__always) @_unsafeInheritExecutor + @inlinable @inline(__always) public func read( into buffer: borrowing IORingBuffer, //TODO: should be inout? - atAbsoluteOffset offset: UInt64 = UInt64.max + atAbsoluteOffset offset: UInt64 = UInt64.max, + isolation actor: isolated (any Actor)? = #isolation ) async throws -> UInt32 { let res = await ring.submitAndWait( .read( @@ -148,7 +150,7 @@ public class FileIterator: AsyncIteratorProtocol { } } - @inlinable @inline(__always) @_unsafeInheritExecutor + @inlinable @inline(__always) public func next() async throws -> UInt8? { if _fastPath(currentByte != lastByte) { // SAFETY: both pointers should be non-nil if they're not equal diff --git a/Sources/System/ManagedIORing.swift b/Sources/System/ManagedIORing.swift index 73ea2314..30ea5ed6 100644 --- a/Sources/System/ManagedIORing.swift +++ b/Sources/System/ManagedIORing.swift @@ -20,8 +20,7 @@ final public class ManagedIORing: @unchecked Sendable { } } - @_unsafeInheritExecutor - public func submitAndWait(_ request: __owned IORequest) async -> IOCompletion { + public func submitAndWait(_ request: __owned IORequest, isolation actor: isolated (any Actor)? = #isolation) async -> IOCompletion { var consumeOnceWorkaround: IORequest? = request return await withUnsafeContinuation { cont in return internalRing.submissionMutex.withLock { ring in From a9f92a6e316826c6f80d3963f3207bae4d573818 Mon Sep 17 00:00:00 2001 From: David Smith Date: Mon, 28 Oct 2024 17:23:25 -0700 Subject: [PATCH 270/427] Delete stray Package.resolved changes --- Package.resolved | 16 ---------------- 1 file changed, 16 deletions(-) delete mode 100644 Package.resolved diff --git a/Package.resolved b/Package.resolved deleted file mode 100644 index b10a9832..00000000 --- a/Package.resolved +++ /dev/null @@ -1,16 +0,0 @@ -{ - "object": { - "pins": [ - { - "package": "swift-atomics", - "repositoryURL": "https://github.com/apple/swift-atomics", - "state": { - "branch": null, - "revision": "6c89474e62719ddcc1e9614989fff2f68208fe10", - "version": "1.1.0" - } - } - ] - }, - "version": 1 -} From 1a3e37d7919aba47d376200d7c6f998e31aa84ba Mon Sep 17 00:00:00 2001 From: David Smith Date: Mon, 28 Oct 2024 17:24:46 -0700 Subject: [PATCH 271/427] Fix mismerge --- Sources/CSystem/{ => include}/CSystemLinux.h | 0 Sources/CSystem/{ => include}/CSystemWASI.h | 0 Sources/CSystem/{ => include}/CSystemWindows.h | 0 Sources/CSystem/{ => include}/io_uring.h | 0 4 files changed, 0 insertions(+), 0 deletions(-) rename Sources/CSystem/{ => include}/CSystemLinux.h (100%) rename Sources/CSystem/{ => include}/CSystemWASI.h (100%) rename Sources/CSystem/{ => include}/CSystemWindows.h (100%) rename Sources/CSystem/{ => include}/io_uring.h (100%) diff --git a/Sources/CSystem/CSystemLinux.h b/Sources/CSystem/include/CSystemLinux.h similarity index 100% rename from Sources/CSystem/CSystemLinux.h rename to Sources/CSystem/include/CSystemLinux.h diff --git a/Sources/CSystem/CSystemWASI.h b/Sources/CSystem/include/CSystemWASI.h similarity index 100% rename from Sources/CSystem/CSystemWASI.h rename to Sources/CSystem/include/CSystemWASI.h diff --git a/Sources/CSystem/CSystemWindows.h b/Sources/CSystem/include/CSystemWindows.h similarity index 100% rename from Sources/CSystem/CSystemWindows.h rename to Sources/CSystem/include/CSystemWindows.h diff --git a/Sources/CSystem/io_uring.h b/Sources/CSystem/include/io_uring.h similarity index 100% rename from Sources/CSystem/io_uring.h rename to Sources/CSystem/include/io_uring.h From f369347b73ccbd40740cb905a12b82d09083757e Mon Sep 17 00:00:00 2001 From: David Smith Date: Mon, 28 Oct 2024 17:25:31 -0700 Subject: [PATCH 272/427] Fix mismerge --- Sources/CSystem/{ => include}/module.modulemap | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename Sources/CSystem/{ => include}/module.modulemap (100%) diff --git a/Sources/CSystem/module.modulemap b/Sources/CSystem/include/module.modulemap similarity index 100% rename from Sources/CSystem/module.modulemap rename to Sources/CSystem/include/module.modulemap From f1393b76f37049380f4714cb2c50c6feb5190daa Mon Sep 17 00:00:00 2001 From: Andrew Kaster Date: Thu, 21 Nov 2024 14:12:06 -0700 Subject: [PATCH 273/427] CSystem: Set build and install interfaces on included headers --- Sources/CSystem/CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/CSystem/CMakeLists.txt b/Sources/CSystem/CMakeLists.txt index 0f5d8e8a..faf730e3 100644 --- a/Sources/CSystem/CMakeLists.txt +++ b/Sources/CSystem/CMakeLists.txt @@ -9,8 +9,8 @@ See https://swift.org/LICENSE.txt for license information add_library(CSystem INTERFACE) target_include_directories(CSystem INTERFACE - include) - + "$" + $) install(FILES include/CSystemLinux.h From eb3b1fa54bc890cc3d69a8fb34fdfd54fb3aadc7 Mon Sep 17 00:00:00 2001 From: Andrew Kaster Date: Thu, 21 Nov 2024 15:03:28 -0700 Subject: [PATCH 274/427] Internals: Remove non-breaking spaces from Windows syscall adapter This was causing a warning with Swift 6.0.2 --- Sources/System/Internals/WindowsSyscallAdapters.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/System/Internals/WindowsSyscallAdapters.swift b/Sources/System/Internals/WindowsSyscallAdapters.swift index d56d33e4..706881ec 100644 --- a/Sources/System/Internals/WindowsSyscallAdapters.swift +++ b/Sources/System/Internals/WindowsSyscallAdapters.swift @@ -187,7 +187,7 @@ internal func pwrite( internal func pipe( _ fds: UnsafeMutablePointer, bytesReserved: UInt32 = 4096 ) -> CInt { -  return _pipe(fds, bytesReserved, _O_BINARY | _O_NOINHERIT); + return _pipe(fds, bytesReserved, _O_BINARY | _O_NOINHERIT); } @inline(__always) From ef94a3719363a97cc8b7e6fa30a096a7d74e9f3c Mon Sep 17 00:00:00 2001 From: David Smith Date: Thu, 5 Dec 2024 03:11:11 +0000 Subject: [PATCH 275/427] Refactoring, and give up on resources being noncopyable structs --- Sources/System/AsyncFileDescriptor.swift | 60 ++-- Sources/System/IOCompletion.swift | 2 +- Sources/System/IORequest.swift | 273 +++++++++++++++--- Sources/System/IORing.swift | 18 +- .../Internals/WindowsSyscallAdapters.swift | 2 +- .../AsyncFileDescriptorTests.swift | 36 ++- Tests/SystemTests/IORequestTests.swift | 14 +- Tests/SystemTests/IORingTests.swift | 4 +- Tests/SystemTests/ManagedIORingTests.swift | 4 +- 9 files changed, 305 insertions(+), 108 deletions(-) diff --git a/Sources/System/AsyncFileDescriptor.swift b/Sources/System/AsyncFileDescriptor.swift index 68d9e33c..f8075d6c 100644 --- a/Sources/System/AsyncFileDescriptor.swift +++ b/Sources/System/AsyncFileDescriptor.swift @@ -5,29 +5,30 @@ public struct AsyncFileDescriptor: ~Copyable { @usableFromInline let fileSlot: IORingFileSlot @usableFromInline let ring: ManagedIORing - public static func openat( - atDirectory: FileDescriptor = FileDescriptor(rawValue: -100), + public static func open( path: FilePath, - _ mode: FileDescriptor.AccessMode, + in directory: FileDescriptor = FileDescriptor(rawValue: -100), + on ring: ManagedIORing, + mode: FileDescriptor.AccessMode, options: FileDescriptor.OpenOptions = FileDescriptor.OpenOptions(), - permissions: FilePermissions? = nil, - onRing ring: ManagedIORing + permissions: FilePermissions? = nil ) async throws -> AsyncFileDescriptor { // todo; real error type guard let fileSlot = ring.getFileSlot() else { throw IORingError.missingRequiredFeatures } + //TODO: need an async-friendly withCString let cstr = path.withCString { return $0 // bad } let res = await ring.submitAndWait( - .openat( - atDirectory: atDirectory, - path: cstr, - mode, - options: options, - permissions: permissions, - intoSlot: fileSlot.borrow() + IORequest( + opening: cstr, + in: directory, + into: fileSlot, + mode: mode, + options: options, + permissions: permissions )) if res.result < 0 { throw Errno(rawValue: -res.result) @@ -45,10 +46,7 @@ public struct AsyncFileDescriptor: ~Copyable { @inlinable @inline(__always) public consuming func close(isolation actor: isolated (any Actor)? = #isolation) async throws { - let res = await ring.submitAndWait( - .close( - .registered(self.fileSlot) - )) + let res = await ring.submitAndWait(IORequest(closing: fileSlot)) if res.result < 0 { throw Errno(rawValue: -res.result) } @@ -61,12 +59,11 @@ public struct AsyncFileDescriptor: ~Copyable { atAbsoluteOffset offset: UInt64 = UInt64.max, isolation actor: isolated (any Actor)? = #isolation ) async throws -> UInt32 { - let file = fileSlot.borrow() let res = await ring.submitAndWait( - .readUnregistered( - file: .registered(file), - buffer: buffer, - offset: offset + IORequest( + reading: fileSlot, + into: buffer, + at: offset )) if res.result < 0 { throw Errno(rawValue: -res.result) @@ -77,15 +74,15 @@ public struct AsyncFileDescriptor: ~Copyable { @inlinable @inline(__always) public func read( - into buffer: borrowing IORingBuffer, //TODO: should be inout? + into buffer: IORingBuffer, //TODO: should be inout? atAbsoluteOffset offset: UInt64 = UInt64.max, isolation actor: isolated (any Actor)? = #isolation ) async throws -> UInt32 { let res = await ring.submitAndWait( - .read( - file: .registered(self.fileSlot.borrow()), - buffer: buffer.borrow(), - offset: offset + IORequest( + reading: fileSlot, + into: buffer, + at: offset )) if res.result < 0 { throw Errno(rawValue: -res.result) @@ -100,11 +97,12 @@ public struct AsyncFileDescriptor: ~Copyable { } //TODO: can we do the linear types thing and error if they don't consume it manually? - // deinit { - // if self.open { - // TODO: close or error? TBD - // } - // } + // deinit { + // if self.open { + // close() + // // TODO: close or error? TBD + // } + // } } public class AsyncFileDescriptorSequence: AsyncSequence { diff --git a/Sources/System/IOCompletion.swift b/Sources/System/IOCompletion.swift index 8bf173c9..1702f9e8 100644 --- a/Sources/System/IOCompletion.swift +++ b/Sources/System/IOCompletion.swift @@ -21,7 +21,7 @@ extension IOCompletion { } extension IOCompletion { - public var userData: UInt64 { + public var userData: UInt64 { //TODO: naming? get { return rawValue.user_data } diff --git a/Sources/System/IORequest.swift b/Sources/System/IORequest.swift index 2a47c62e..0af87176 100644 --- a/Sources/System/IORequest.swift +++ b/Sources/System/IORequest.swift @@ -1,57 +1,90 @@ import struct CSystem.io_uring_sqe -public enum IORequest: ~Copyable { +@usableFromInline +internal enum IORequestCore: ~Copyable { case nop // nothing here case openat( + atDirectory: FileDescriptor, + path: UnsafePointer, + FileDescriptor.AccessMode, + options: FileDescriptor.OpenOptions = FileDescriptor.OpenOptions(), + permissions: FilePermissions? = nil + ) + case openatSlot( atDirectory: FileDescriptor, path: UnsafePointer, FileDescriptor.AccessMode, options: FileDescriptor.OpenOptions = FileDescriptor.OpenOptions(), permissions: FilePermissions? = nil, - intoSlot: IORingFileSlot? = nil + intoSlot: IORingFileSlot ) case read( - file: File, + file: FileDescriptor, buffer: IORingBuffer, offset: UInt64 = 0 ) case readUnregistered( - file: File, + file: FileDescriptor, + buffer: UnsafeMutableRawBufferPointer, + offset: UInt64 = 0 + ) + case readSlot( + file: IORingFileSlot, + buffer: IORingBuffer, + offset: UInt64 = 0 + ) + case readUnregisteredSlot( + file: IORingFileSlot, buffer: UnsafeMutableRawBufferPointer, offset: UInt64 = 0 ) case write( - file: File, + file: FileDescriptor, buffer: IORingBuffer, offset: UInt64 = 0 ) case writeUnregistered( - file: File, + file: FileDescriptor, buffer: UnsafeMutableRawBufferPointer, offset: UInt64 = 0 ) - case close(File) - - public enum File: ~Copyable { - case registered(IORingFileSlot) - case unregistered(FileDescriptor) - } + case writeSlot( + file: IORingFileSlot, + buffer: IORingBuffer, + offset: UInt64 = 0 + ) + case writeUnregisteredSlot( + file: IORingFileSlot, + buffer: UnsafeMutableRawBufferPointer, + offset: UInt64 = 0 + ) + case close(FileDescriptor) + case closeSlot(IORingFileSlot) } @inlinable @inline(__always) internal func makeRawRequest_readWrite_registered( - file: consuming IORequest.File, - buffer: consuming IORingBuffer, + file: FileDescriptor, + buffer: IORingBuffer, offset: UInt64, request: consuming RawIORequest ) -> RawIORequest { - switch file { - case .registered(let regFile): - request.rawValue.fd = Int32(exactly: regFile.index)! - request.flags = .fixedFile - case .unregistered(let fd): - request.fileDescriptor = fd - } + request.fileDescriptor = file + request.buffer = buffer.unsafeBuffer + request.rawValue.buf_index = UInt16(exactly: buffer.index)! + request.offset = offset + return request +} + +@inlinable @inline(__always) +internal func makeRawRequest_readWrite_registered_slot( + file: IORingFileSlot, + buffer: IORingBuffer, + offset: UInt64, + request: consuming RawIORequest +) -> RawIORequest { + request.rawValue.fd = Int32(exactly: file.index)! + request.flags = .fixedFile request.buffer = buffer.unsafeBuffer request.rawValue.buf_index = UInt16(exactly: buffer.index)! request.offset = offset @@ -60,64 +93,222 @@ internal func makeRawRequest_readWrite_registered( @inlinable @inline(__always) internal func makeRawRequest_readWrite_unregistered( - file: consuming IORequest.File, + file: FileDescriptor, buffer: UnsafeMutableRawBufferPointer, offset: UInt64, request: consuming RawIORequest ) -> RawIORequest { - switch file { - case .registered(let regFile): - request.rawValue.fd = Int32(exactly: regFile.index)! - request.flags = .fixedFile - case .unregistered(let fd): - request.fileDescriptor = fd - } + request.fileDescriptor = file request.buffer = buffer request.offset = offset return request } +@inlinable @inline(__always) +internal func makeRawRequest_readWrite_unregistered_slot( + file: IORingFileSlot, + buffer: UnsafeMutableRawBufferPointer, + offset: UInt64, + request: consuming RawIORequest +) -> RawIORequest { + request.rawValue.fd = Int32(exactly: file.index)! + request.flags = .fixedFile + request.buffer = buffer + request.offset = offset + return request +} + +public struct IORequest : ~Copyable { + @usableFromInline var core: IORequestCore + + @inlinable internal consuming func extractCore() -> IORequestCore { + return core + } +} + extension IORequest { + public init() { //TODO: why do we have nop? + core = .nop + } + + public init( + reading file: IORingFileSlot, + into buffer: IORingBuffer, + at offset: UInt64 = 0 + ) { + core = .readSlot(file: file, buffer: buffer, offset: offset) + } + + public init( + reading file: FileDescriptor, + into buffer: IORingBuffer, + at offset: UInt64 = 0 + ) { + core = .read(file: file, buffer: buffer, offset: offset) + } + + public init( + reading file: IORingFileSlot, + into buffer: UnsafeMutableRawBufferPointer, + at offset: UInt64 = 0 + ) { + core = .readUnregisteredSlot(file: file, buffer: buffer, offset: offset) + } + + public init( + reading file: FileDescriptor, + into buffer: UnsafeMutableRawBufferPointer, + at offset: UInt64 = 0 + ) { + core = .readUnregistered(file: file, buffer: buffer, offset: offset) + } + + public init( + writing buffer: IORingBuffer, + into file: IORingFileSlot, + at offset: UInt64 = 0 + ) { + core = .writeSlot(file: file, buffer: buffer, offset: offset) + } + + public init( + writing buffer: IORingBuffer, + into file: FileDescriptor, + at offset: UInt64 = 0 + ) { + core = .write(file: file, buffer: buffer, offset: offset) + } + + public init( + writing buffer: UnsafeMutableRawBufferPointer, + into file: IORingFileSlot, + at offset: UInt64 = 0 + ) { + core = .writeUnregisteredSlot(file: file, buffer: buffer, offset: offset) + } + + public init( + writing buffer: UnsafeMutableRawBufferPointer, + into file: FileDescriptor, + at offset: UInt64 = 0 + ) { + core = .writeUnregistered(file: file, buffer: buffer, offset: offset) + } + + public init( + closing file: FileDescriptor + ) { + core = .close(file) + } + + public init( + closing file: IORingFileSlot + ) { + core = .closeSlot(file) + } + + + public init( + opening path: UnsafePointer, + in directory: FileDescriptor, + into slot: IORingFileSlot, + mode: FileDescriptor.AccessMode, + options: FileDescriptor.OpenOptions = FileDescriptor.OpenOptions(), + permissions: FilePermissions? = nil + ) { + core = .openatSlot(atDirectory: directory, path: path, mode, options: options, permissions: permissions, intoSlot: slot) + } + + public init( + opening path: UnsafePointer, + in directory: FileDescriptor, + mode: FileDescriptor.AccessMode, + options: FileDescriptor.OpenOptions = FileDescriptor.OpenOptions(), + permissions: FilePermissions? = nil + ) { + core = .openat(atDirectory: directory, path: path, mode, options: options, permissions: permissions) + } + + + public init( + opening path: FilePath, + in directory: FileDescriptor, + into slot: IORingFileSlot, + mode: FileDescriptor.AccessMode, + options: FileDescriptor.OpenOptions = FileDescriptor.OpenOptions(), + permissions: FilePermissions? = nil + ) { + fatalError("Implement me") + } + + public init( + opening path: FilePath, + in directory: FileDescriptor, + mode: FileDescriptor.AccessMode, + options: FileDescriptor.OpenOptions = FileDescriptor.OpenOptions(), + permissions: FilePermissions? = nil + ) { + fatalError("Implement me") + } + @inlinable @inline(__always) public consuming func makeRawRequest() -> RawIORequest { var request = RawIORequest() - switch consume self { + switch extractCore() { case .nop: request.operation = .nop - case .openat(let atDirectory, let path, let mode, let options, let permissions, let slot): + case .openatSlot(let atDirectory, let path, let mode, let options, let permissions, let fileSlot): // TODO: use rawValue less request.operation = .openAt request.fileDescriptor = atDirectory - request.rawValue.addr = unsafeBitCast(path, to: UInt64.self) + request.rawValue.addr = UInt64(UInt(bitPattern: path)) + request.rawValue.open_flags = UInt32(bitPattern: options.rawValue | mode.rawValue) + request.rawValue.len = permissions?.rawValue ?? 0 + request.rawValue.file_index = UInt32(fileSlot.index + 1) + case .openat(let atDirectory, let path, let mode, let options, let permissions): + request.operation = .openAt + request.fileDescriptor = atDirectory + request.rawValue.addr = UInt64(UInt(bitPattern: path)) request.rawValue.open_flags = UInt32(bitPattern: options.rawValue | mode.rawValue) request.rawValue.len = permissions?.rawValue ?? 0 - if let fileSlot = slot { - request.rawValue.file_index = UInt32(fileSlot.index + 1) - } case .write(let file, let buffer, let offset): request.operation = .writeFixed return makeRawRequest_readWrite_registered( file: file, buffer: buffer, offset: offset, request: request) + case .writeSlot(let file, let buffer, let offset): + request.operation = .writeFixed + return makeRawRequest_readWrite_registered_slot( + file: file, buffer: buffer, offset: offset, request: request) case .writeUnregistered(let file, let buffer, let offset): request.operation = .write return makeRawRequest_readWrite_unregistered( file: file, buffer: buffer, offset: offset, request: request) + case .writeUnregisteredSlot(let file, let buffer, let offset): + request.operation = .write + return makeRawRequest_readWrite_unregistered_slot( + file: file, buffer: buffer, offset: offset, request: request) case .read(let file, let buffer, let offset): request.operation = .readFixed return makeRawRequest_readWrite_registered( file: file, buffer: buffer, offset: offset, request: request) + case .readSlot(let file, let buffer, let offset): + request.operation = .readFixed + return makeRawRequest_readWrite_registered_slot( + file: file, buffer: buffer, offset: offset, request: request) case .readUnregistered(let file, let buffer, let offset): request.operation = .read return makeRawRequest_readWrite_unregistered( file: file, buffer: buffer, offset: offset, request: request) + case .readUnregisteredSlot(let file, let buffer, let offset): + request.operation = .read + return makeRawRequest_readWrite_unregistered_slot( + file: file, buffer: buffer, offset: offset, request: request) case .close(let file): request.operation = .close - switch file { - case .registered(let regFile): - request.rawValue.file_index = UInt32(regFile.index + 1) - case .unregistered(let normalFile): - request.fileDescriptor = normalFile - } + request.fileDescriptor = file + case .closeSlot(let file): + request.operation = .close + request.rawValue.file_index = UInt32(file.index + 1) } return request } diff --git a/Sources/System/IORing.swift b/Sources/System/IORing.swift index 2f18cb59..57c21611 100644 --- a/Sources/System/IORing.swift +++ b/Sources/System/IORing.swift @@ -46,7 +46,7 @@ struct CQRing: ~Copyable { let cqes: UnsafeBufferPointer } -internal class ResourceManager: @unchecked Sendable { +internal final class ResourceManager: @unchecked Sendable { typealias Resource = T struct Resources { @@ -85,38 +85,28 @@ internal class ResourceManager: @unchecked Sendable { } } -public struct IOResource: ~Copyable { +public class IOResource { typealias Resource = T @usableFromInline let resource: T @usableFromInline let index: Int let manager: ResourceManager - let isBorrow: Bool //TODO: this is a workaround for lifetime issues and should be removed internal init( resource: T, index: Int, - manager: ResourceManager, - isBorrow: Bool = false + manager: ResourceManager ) { self.resource = resource self.index = index self.manager = manager - self.isBorrow = isBorrow } func withResource() { } - //TODO: this is a workaround for lifetime issues and should be removed - @usableFromInline func borrow() -> IOResource { - IOResource(resource: resource, index: index, manager: manager, isBorrow: true) - } - deinit { - if !isBorrow { - manager.releaseResource(index: self.index) - } + manager.releaseResource(index: self.index) } } diff --git a/Sources/System/Internals/WindowsSyscallAdapters.swift b/Sources/System/Internals/WindowsSyscallAdapters.swift index d56d33e4..706881ec 100644 --- a/Sources/System/Internals/WindowsSyscallAdapters.swift +++ b/Sources/System/Internals/WindowsSyscallAdapters.swift @@ -187,7 +187,7 @@ internal func pwrite( internal func pipe( _ fds: UnsafeMutablePointer, bytesReserved: UInt32 = 4096 ) -> CInt { -  return _pipe(fds, bytesReserved, _O_BINARY | _O_NOINHERIT); + return _pipe(fds, bytesReserved, _O_BINARY | _O_NOINHERIT); } @inline(__always) diff --git a/Tests/SystemTests/AsyncFileDescriptorTests.swift b/Tests/SystemTests/AsyncFileDescriptorTests.swift index 7de22724..ebd308fc 100644 --- a/Tests/SystemTests/AsyncFileDescriptorTests.swift +++ b/Tests/SystemTests/AsyncFileDescriptorTests.swift @@ -9,32 +9,50 @@ import System final class AsyncFileDescriptorTests: XCTestCase { func testOpen() async throws { let ring = try ManagedIORing(queueDepth: 32) - let file = try await AsyncFileDescriptor.openat( + _ = try await AsyncFileDescriptor.open( path: "/dev/zero", - .readOnly, - onRing: ring + on: ring, + mode: .readOnly ) } func testOpenClose() async throws { let ring = try ManagedIORing(queueDepth: 32) - let file = try await AsyncFileDescriptor.openat( + let file = try await AsyncFileDescriptor.open( path: "/dev/zero", - .readOnly, - onRing: ring + on: ring, + mode: .readOnly ) try await file.close() } func testDevNullEmpty() async throws { let ring = try ManagedIORing(queueDepth: 32) - let file = try await AsyncFileDescriptor.openat( + let file = try await AsyncFileDescriptor.open( path: "/dev/null", - .readOnly, - onRing: ring + on: ring, + mode: .readOnly ) for try await _ in file.toBytes() { XCTFail("/dev/null should be empty") } } + + func testRead() async throws { + let ring = try ManagedIORing(queueDepth: 32) + let file = try await AsyncFileDescriptor.open( + path: "/dev/zero", + on: ring, + mode: .readOnly + ) + let bytes = file.toBytes() + var counter = 0 + for try await byte in bytes { + XCTAssert(byte == 0) + counter &+= 1 + if counter > 16384 { + break + } + } + } } diff --git a/Tests/SystemTests/IORequestTests.swift b/Tests/SystemTests/IORequestTests.swift index a1e14533..78be4cf7 100644 --- a/Tests/SystemTests/IORequestTests.swift +++ b/Tests/SystemTests/IORequestTests.swift @@ -19,7 +19,7 @@ func requestBytes(_ request: consuming RawIORequest) -> [UInt8] { // which are known to work correctly. final class IORequestTests: XCTestCase { func testNop() { - let req = IORequest.nop.makeRawRequest() + let req = IORequest().makeRawRequest() let sourceBytes = requestBytes(req) // convenient property of nop: it's all zeros! // for some unknown reason, liburing sets the fd field to -1. @@ -34,13 +34,13 @@ final class IORequestTests: XCTestCase { let pathPtr = UnsafePointer(bitPattern: 0x414141410badf00d)! let fileSlot = resmgr.getResource()! - let req = IORequest.openat( - atDirectory: FileDescriptor(rawValue: -100), - path: pathPtr, - .readOnly, + let req = IORequest( + opening: pathPtr, + in: FileDescriptor(rawValue: -100), + into: fileSlot, + mode: .readOnly, options: [], - permissions: nil, - intoSlot: fileSlot.borrow() + permissions: nil ) let expectedRequest: [UInt8] = { diff --git a/Tests/SystemTests/IORingTests.swift b/Tests/SystemTests/IORingTests.swift index 4604f500..85846d3e 100644 --- a/Tests/SystemTests/IORingTests.swift +++ b/Tests/SystemTests/IORingTests.swift @@ -8,12 +8,12 @@ import System final class IORingTests: XCTestCase { func testInit() throws { - let ring = try IORing(queueDepth: 32) + _ = try IORing(queueDepth: 32) } func testNop() throws { var ring = try IORing(queueDepth: 32) - ring.writeRequest(.nop) + ring.writeRequest(IORequest()) ring.submitRequests() let completion = ring.blockingConsumeCompletion() XCTAssertEqual(completion.result, 0) diff --git a/Tests/SystemTests/ManagedIORingTests.swift b/Tests/SystemTests/ManagedIORingTests.swift index e7ad3f59..4b8ea28b 100644 --- a/Tests/SystemTests/ManagedIORingTests.swift +++ b/Tests/SystemTests/ManagedIORingTests.swift @@ -8,12 +8,12 @@ import System final class ManagedIORingTests: XCTestCase { func testInit() throws { - let ring = try ManagedIORing(queueDepth: 32) + _ = try ManagedIORing(queueDepth: 32) } func testNop() async throws { let ring = try ManagedIORing(queueDepth: 32) - let completion = await ring.submitAndWait(.nop) + let completion = await ring.submitAndWait(IORequest()) XCTAssertEqual(completion.result, 0) } } From 7ea32aefce1f716cb0147f43b64eacf184f1914f Mon Sep 17 00:00:00 2001 From: David Smith Date: Wed, 11 Dec 2024 23:00:50 +0000 Subject: [PATCH 276/427] More refactoring, and working timeout support on ManagedIORing --- Sources/System/AsyncFileDescriptor.swift | 11 ++- Sources/System/IORing.swift | 82 +++++++++++++++++----- Sources/System/IORingError.swift | 5 +- Sources/System/ManagedIORing.swift | 74 ++++++++++++++++--- Sources/System/RawIORequest.swift | 42 ++++++++++- Tests/SystemTests/IORingTests.swift | 2 +- Tests/SystemTests/ManagedIORingTests.swift | 31 +++++++- 7 files changed, 208 insertions(+), 39 deletions(-) diff --git a/Sources/System/AsyncFileDescriptor.swift b/Sources/System/AsyncFileDescriptor.swift index f8075d6c..a76d90e5 100644 --- a/Sources/System/AsyncFileDescriptor.swift +++ b/Sources/System/AsyncFileDescriptor.swift @@ -21,8 +21,7 @@ public struct AsyncFileDescriptor: ~Copyable { let cstr = path.withCString { return $0 // bad } - let res = await ring.submitAndWait( - IORequest( + let res = try await ring.submit(request: IORequest( opening: cstr, in: directory, into: fileSlot, @@ -46,7 +45,7 @@ public struct AsyncFileDescriptor: ~Copyable { @inlinable @inline(__always) public consuming func close(isolation actor: isolated (any Actor)? = #isolation) async throws { - let res = await ring.submitAndWait(IORequest(closing: fileSlot)) + let res = try await ring.submit(request: IORequest(closing: fileSlot)) if res.result < 0 { throw Errno(rawValue: -res.result) } @@ -59,8 +58,7 @@ public struct AsyncFileDescriptor: ~Copyable { atAbsoluteOffset offset: UInt64 = UInt64.max, isolation actor: isolated (any Actor)? = #isolation ) async throws -> UInt32 { - let res = await ring.submitAndWait( - IORequest( + let res = try await ring.submit(request: IORequest( reading: fileSlot, into: buffer, at: offset @@ -78,8 +76,7 @@ public struct AsyncFileDescriptor: ~Copyable { atAbsoluteOffset offset: UInt64 = UInt64.max, isolation actor: isolated (any Actor)? = #isolation ) async throws -> UInt32 { - let res = await ring.submitAndWait( - IORequest( + let res = try await ring.submit(request: IORequest( reading: fileSlot, into: buffer, at: offset diff --git a/Sources/System/IORing.swift b/Sources/System/IORing.swift index 57c21611..00461d14 100644 --- a/Sources/System/IORing.swift +++ b/Sources/System/IORing.swift @@ -138,7 +138,10 @@ internal func _blockingGetSubmissionEntry(ring: inout SQRing, submissionQueueEnt io_uring_sqe > { while true { - if let entry = _getSubmissionEntry(ring: &ring, submissionQueueEntries: submissionQueueEntries) { + if let entry = _getSubmissionEntry( + ring: &ring, + submissionQueueEntries: submissionQueueEntries + ) { return entry } // TODO: actually block here instead of spinning @@ -146,13 +149,18 @@ internal func _blockingGetSubmissionEntry(ring: inout SQRing, submissionQueueEnt } -internal func _submitRequests(ring: inout SQRing, ringDescriptor: Int32) { - let flushedEvents = _flushQueue(ring: &ring) - - // Ring always needs enter right now; +//TODO: omitting signal mask for now +//Tell the kernel that we've submitted requests and/or are waiting for completions +internal func _enter( + ringDescriptor: Int32, + numEvents: UInt32, + minCompletions: UInt32, + flags: UInt32 +) throws -> Int32 { + // Ring always needs enter right now; // TODO: support SQPOLL here while true { - let ret = io_uring_enter(ringDescriptor, flushedEvents, 0, 0, nil) + let ret = io_uring_enter(ringDescriptor, numEvents, minCompletions, flags, nil) // error handling: // EAGAIN / EINTR (try again), // EBADF / EBADFD / EOPNOTSUPP / ENXIO @@ -160,32 +168,47 @@ internal func _submitRequests(ring: inout SQRing, ringDescriptor: Int32) { // EINVAL (bad constant flag?, fatal), // EFAULT (bad address for argument from library, fatal) if ret == -EAGAIN || ret == -EINTR { + //TODO: should we wait a bit on AGAIN? continue } else if ret < 0 { fatalError( "fatal error in submitting requests: " + Errno(rawValue: -ret).debugDescription ) } else { - break + return ret } } } +internal func _submitRequests(ring: inout SQRing, ringDescriptor: Int32) throws { + let flushedEvents = _flushQueue(ring: &ring) + _ = try _enter(ringDescriptor: ringDescriptor, numEvents: flushedEvents, minCompletions: 0, flags: 0) +} + +internal func _getUnconsumedSubmissionCount(ring: inout SQRing) -> UInt32 { + return ring.userTail - ring.kernelHead.pointee.load(ordering: .acquiring) +} + +internal func _getUnconsumedCompletionCount(ring: inout CQRing) -> UInt32 { + return ring.kernelTail.pointee.load(ordering: .acquiring) - ring.kernelHead.pointee.load(ordering: .acquiring) +} + +//TODO: pretty sure this is supposed to do more than it does internal func _flushQueue(ring: inout SQRing) -> UInt32 { ring.kernelTail.pointee.store( - ring.userTail, ordering: .relaxed + ring.userTail, ordering: .releasing ) - return ring.userTail - ring.kernelHead.pointee.load(ordering: .relaxed) + return _getUnconsumedSubmissionCount(ring: &ring) } @usableFromInline @inline(__always) internal func _getSubmissionEntry(ring: inout SQRing, submissionQueueEntries: UnsafeMutableBufferPointer) -> UnsafeMutablePointer< io_uring_sqe >? { - let next = ring.userTail + 1 + let next = ring.userTail &+ 1 //this is expected to wrap // FEAT: smp load when SQPOLL in use (not in MVP) - let kernelHead = ring.kernelHead.pointee.load(ordering: .relaxed) + let kernelHead = ring.kernelHead.pointee.load(ordering: .acquiring) // FEAT: 128-bit event support (not in MVP) if next - kernelHead <= ring.array.count { @@ -383,18 +406,45 @@ public struct IORing: @unchecked Sendable, ~Copyable { func _tryConsumeCompletion(ring: inout CQRing) -> IOCompletion? { let tail = ring.kernelTail.pointee.load(ordering: .acquiring) - let head = ring.kernelHead.pointee.load(ordering: .relaxed) + let head = ring.kernelHead.pointee.load(ordering: .acquiring) if tail != head { // 32 byte copy - oh well let res = ring.cqes[Int(head & ring.ringMask)] - ring.kernelHead.pointee.store(head + 1, ordering: .relaxed) + ring.kernelHead.pointee.store(head &+ 1, ordering: .releasing) return IOCompletion(rawValue: res) } return nil } + internal func handleRegistrationResult(_ result: Int32) throws { + //TODO: error handling + } + + public mutating func registerEventFD(ring: inout IORing, _ descriptor: FileDescriptor) throws { + var rawfd = descriptor.rawValue + let result = withUnsafePointer(to: &rawfd) { fdptr in + return io_uring_register( + ring.ringDescriptor, + IORING_REGISTER_EVENTFD, + UnsafeMutableRawPointer(mutating: fdptr), + 1 + ) + } + try handleRegistrationResult(result) + } + + public mutating func unregisterEventFD(ring: inout IORing) throws { + let result = io_uring_register( + ring.ringDescriptor, + IORING_UNREGISTER_EVENTFD, + nil, + 0 + ) + try handleRegistrationResult(result) + } + public mutating func registerFiles(count: UInt32) { guard self.registeredFiles == nil else { fatalError() } let fileBuf = UnsafeMutableBufferPointer.allocate(capacity: Int(count)) @@ -445,9 +495,9 @@ public struct IORing: @unchecked Sendable, ~Copyable { fatalError("failed to unregister buffers: TODO") } - public func submitRequests() { - submissionMutex.withLock { ring in - _submitRequests(ring: &ring, ringDescriptor: ringDescriptor) + public func submitRequests() throws { + try submissionMutex.withLock { ring in + try _submitRequests(ring: &ring, ringDescriptor: ringDescriptor) } } diff --git a/Sources/System/IORingError.swift b/Sources/System/IORingError.swift index d87b2938..fda58bcb 100644 --- a/Sources/System/IORingError.swift +++ b/Sources/System/IORingError.swift @@ -1,3 +1,6 @@ -enum IORingError: Error { +//TODO: make this not an enum +public enum IORingError: Error, Equatable { case missingRequiredFeatures + case operationCanceled + case unknown } diff --git a/Sources/System/ManagedIORing.swift b/Sources/System/ManagedIORing.swift index 30ea5ed6..99c112d6 100644 --- a/Sources/System/ManagedIORing.swift +++ b/Sources/System/ManagedIORing.swift @@ -1,3 +1,16 @@ +fileprivate func handleCompletionError( + _ result: Int32, + for continuation: UnsafeContinuation) { + var error: IORingError = .unknown + switch result { + case -(_ECANCELED): + error = .operationCanceled + default: + error = .unknown + } + continuation.resume(throwing: error) +} + final public class ManagedIORing: @unchecked Sendable { var internalRing: IORing @@ -11,25 +24,64 @@ final public class ManagedIORing: @unchecked Sendable { private func startWaiter() { Task.detached { while !Task.isCancelled { + //TODO: should timeout handling be sunk into IORing? let cqe = self.internalRing.blockingConsumeCompletion() + if cqe.userData == 0 { + continue + } let cont = unsafeBitCast( - cqe.userData, to: UnsafeContinuation.self) - cont.resume(returning: cqe) + cqe.userData, to: UnsafeContinuation.self) + + if cqe.result < 0 { + var err = system_strerror(cqe.result * -1) + let len = system_strlen(err!) + err!.withMemoryRebound(to: UInt8.self, capacity: len) { + let errStr = String(decoding: UnsafeBufferPointer(start: $0, count: len), as: UTF8.self) + print("\(errStr)") + } + handleCompletionError(cqe.result, for: cont) + } else { + cont.resume(returning: cqe) + } } } } - public func submitAndWait(_ request: __owned IORequest, isolation actor: isolated (any Actor)? = #isolation) async -> IOCompletion { + public func submit( + request: __owned IORequest, + timeout: Duration? = nil, + isolation actor: isolated (any Actor)? = #isolation + ) async throws -> IOCompletion { var consumeOnceWorkaround: IORequest? = request - return await withUnsafeContinuation { cont in - return internalRing.submissionMutex.withLock { ring in - let request = consumeOnceWorkaround.take()! - let entry = _blockingGetSubmissionEntry( - ring: &ring, submissionQueueEntries: internalRing.submissionQueueEntries) - entry.pointee = request.makeRawRequest().rawValue - entry.pointee.user_data = unsafeBitCast(cont, to: UInt64.self) - _submitRequests(ring: &ring, ringDescriptor: internalRing.ringDescriptor) + return try await withUnsafeThrowingContinuation { cont in + do { + try internalRing.submissionMutex.withLock { ring in + let request = consumeOnceWorkaround.take()! + let entry = _blockingGetSubmissionEntry( + ring: &ring, submissionQueueEntries: internalRing.submissionQueueEntries) + entry.pointee = request.makeRawRequest().rawValue + entry.pointee.user_data = unsafeBitCast(cont, to: UInt64.self) + if let timeout { + //TODO: if IORING_FEAT_MIN_TIMEOUT is supported we can do this more efficiently + let timeoutEntry = _blockingGetSubmissionEntry( + ring: &ring, + submissionQueueEntries: internalRing.submissionQueueEntries + ) + try RawIORequest.withTimeoutRequest( + linkedTo: entry, + in: timeoutEntry, + duration: timeout, + flags: .relativeTime + ) { + try _submitRequests(ring: &ring, ringDescriptor: internalRing.ringDescriptor) + } + } else { + try _submitRequests(ring: &ring, ringDescriptor: internalRing.ringDescriptor) + } + } + } catch (let e) { + cont.resume(throwing: e) } } diff --git a/Sources/System/RawIORequest.swift b/Sources/System/RawIORequest.swift index 52407b03..5a1fd03d 100644 --- a/Sources/System/RawIORequest.swift +++ b/Sources/System/RawIORequest.swift @@ -24,6 +24,8 @@ extension RawIORequest { case sendMessage = 9 case receiveMessage = 10 // ... + case link_timeout = 15 + // ... case openAt = 18 case close = 19 case filesUpdate = 20 @@ -103,7 +105,7 @@ extension RawIORequest { // poll32_events // sync_range_flags // msg_flags - // timeout_flags + case timeoutFlags(TimeOutFlags) // accept_flags // cancel_flags case openFlags(FileDescriptor.OpenOptions) @@ -132,12 +134,48 @@ extension RawIORequest { // append to end of the file public static let append = ReadWriteFlags(rawValue: 1 << 4) } + + public struct TimeOutFlags: OptionSet, Hashable, Codable { + public var rawValue: UInt32 + + public init(rawValue: UInt32) { + self.rawValue = rawValue + } + + public static let relativeTime: RawIORequest.TimeOutFlags = TimeOutFlags(rawValue: 0) + public static let absoluteTime: RawIORequest.TimeOutFlags = TimeOutFlags(rawValue: 1 << 0) + } } extension RawIORequest { static func nop() -> RawIORequest { - var req = RawIORequest() + var req: RawIORequest = RawIORequest() req.operation = .nop return req } + + //TODO: typed errors + static func withTimeoutRequest( + linkedTo opEntry: UnsafeMutablePointer, + in timeoutEntry: UnsafeMutablePointer, + duration: Duration, + flags: TimeOutFlags, + work: () throws -> R) rethrows -> R { + + opEntry.pointee.flags |= Flags.linkRequest.rawValue + opEntry.pointee.off = 1 + var ts = __kernel_timespec( + tv_sec: duration.components.seconds, + tv_nsec: duration.components.attoseconds / 1_000_000_000 + ) + return try withUnsafePointer(to: &ts) { tsPtr in + var req: RawIORequest = RawIORequest() + req.operation = .link_timeout + req.rawValue.timeout_flags = flags.rawValue + req.rawValue.len = 1 + req.rawValue.addr = UInt64(UInt(bitPattern: tsPtr)) + timeoutEntry.pointee = req.rawValue + return try work() + } + } } \ No newline at end of file diff --git a/Tests/SystemTests/IORingTests.swift b/Tests/SystemTests/IORingTests.swift index 85846d3e..d0102a0b 100644 --- a/Tests/SystemTests/IORingTests.swift +++ b/Tests/SystemTests/IORingTests.swift @@ -14,7 +14,7 @@ final class IORingTests: XCTestCase { func testNop() throws { var ring = try IORing(queueDepth: 32) ring.writeRequest(IORequest()) - ring.submitRequests() + try ring.submitRequests() let completion = ring.blockingConsumeCompletion() XCTAssertEqual(completion.result, 0) } diff --git a/Tests/SystemTests/ManagedIORingTests.swift b/Tests/SystemTests/ManagedIORingTests.swift index 4b8ea28b..24324037 100644 --- a/Tests/SystemTests/ManagedIORingTests.swift +++ b/Tests/SystemTests/ManagedIORingTests.swift @@ -13,7 +13,36 @@ final class ManagedIORingTests: XCTestCase { func testNop() async throws { let ring = try ManagedIORing(queueDepth: 32) - let completion = await ring.submitAndWait(IORequest()) + let completion = try await ring.submit(request: IORequest()) XCTAssertEqual(completion.result, 0) } + + func testTimeout() async throws { + let ring = try ManagedIORing(queueDepth: 32) + var pipes: (Int32, Int32) = (0, 0) + withUnsafeMutableBytes(of: &pipes) { ptr in + ptr.withMemoryRebound(to: UInt32.self) { tptr in + let res = pipe(tptr.baseAddress!) + XCTAssertEqual(res, 0) + } + } + let buffer = UnsafeMutableRawBufferPointer.allocate(byteCount: 128, alignment: 16) + do { + let completion = try await ring.submit( + request: IORequest(reading: FileDescriptor(rawValue: pipes.0), into: buffer), + timeout: .seconds(0.1) + ) + print("\(completion)") + XCTFail("An error should be thrown") + } catch (let e) { + if let err = e as? IORingError { + XCTAssertEqual(err, .operationCanceled) + } else { + XCTFail() + } + buffer.deallocate() + close(pipes.0) + close(pipes.1) + } + } } From 55fd6e7150cb6f48fa0203c1df29746be35b1a00 Mon Sep 17 00:00:00 2001 From: David Smith Date: Thu, 12 Dec 2024 01:20:24 +0000 Subject: [PATCH 277/427] Add support for timeout-on-wait to IORing, don't have tests yet --- Sources/System/IORing.swift | 37 ++++++++++++++++++++-- Sources/System/ManagedIORing.swift | 33 ++++++++++--------- Tests/SystemTests/IORingTests.swift | 2 +- Tests/SystemTests/ManagedIORingTests.swift | 2 +- 4 files changed, 53 insertions(+), 21 deletions(-) diff --git a/Sources/System/IORing.swift b/Sources/System/IORing.swift index 00461d14..872d9960 100644 --- a/Sources/System/IORing.swift +++ b/Sources/System/IORing.swift @@ -368,13 +368,26 @@ public struct IORing: @unchecked Sendable, ~Copyable { self.ringFlags = params.flags } - public func blockingConsumeCompletion() -> IOCompletion { + private func _blockingConsumeCompletionGuts( + extraArgs: UnsafeMutablePointer? = nil + ) throws(IORingError) -> IOCompletion { completionMutex.withLock { ring in if let completion = _tryConsumeCompletion(ring: &ring) { return completion } else { while true { - let res = io_uring_enter(ringDescriptor, 0, 1, IORING_ENTER_GETEVENTS, nil) + var sz = 0 + if extraArgs != nil { + sz = MemoryLayout.size + } + let res = io_uring_enter2( + ringDescriptor, + 0, + 1, + IORING_ENTER_GETEVENTS, + extraArgs, + sz + ) // error handling: // EAGAIN / EINTR (try again), // EBADF / EBADFD / EOPNOTSUPP / ENXIO @@ -398,6 +411,26 @@ public struct IORing: @unchecked Sendable, ~Copyable { } } + public func blockingConsumeCompletion(timeout: Duration? = nil) throws -> IOCompletion { + if let timeout { + var ts = __kernel_timespec( + tv_sec: timeout.components.seconds, + tv_nsec: timeout.components.attoseconds / 1_000_000_000 + ) + return try withUnsafePointer(to: &ts) { tsPtr in + var args = io_uring_getevents_arg( + sigmask: 0, + sigmask_sz: 0, + pad: 0, + ts: UInt64(UInt(bitPattern: tsPtr)) + ) + return try _blockingConsumeCompletionGuts(extraArgs: &args) + } + } else { + return try _blockingConsumeCompletionGuts() + } + } + public func tryConsumeCompletion() -> IOCompletion? { completionMutex.withLock { ring in return _tryConsumeCompletion(ring: &ring) diff --git a/Sources/System/ManagedIORing.swift b/Sources/System/ManagedIORing.swift index 99c112d6..b6df140d 100644 --- a/Sources/System/ManagedIORing.swift +++ b/Sources/System/ManagedIORing.swift @@ -25,7 +25,8 @@ final public class ManagedIORing: @unchecked Sendable { Task.detached { while !Task.isCancelled { //TODO: should timeout handling be sunk into IORing? - let cqe = self.internalRing.blockingConsumeCompletion() + //TODO: sort out the error handling here + let cqe = try! self.internalRing.blockingConsumeCompletion() if cqe.userData == 0 { continue @@ -34,12 +35,6 @@ final public class ManagedIORing: @unchecked Sendable { cqe.userData, to: UnsafeContinuation.self) if cqe.result < 0 { - var err = system_strerror(cqe.result * -1) - let len = system_strlen(err!) - err!.withMemoryRebound(to: UInt8.self, capacity: len) { - let errStr = String(decoding: UnsafeBufferPointer(start: $0, count: len), as: UTF8.self) - print("\(errStr)") - } handleCompletionError(cqe.result, for: cont) } else { cont.resume(returning: cqe) @@ -64,17 +59,21 @@ final public class ManagedIORing: @unchecked Sendable { entry.pointee.user_data = unsafeBitCast(cont, to: UInt64.self) if let timeout { //TODO: if IORING_FEAT_MIN_TIMEOUT is supported we can do this more efficiently - let timeoutEntry = _blockingGetSubmissionEntry( - ring: &ring, - submissionQueueEntries: internalRing.submissionQueueEntries - ) - try RawIORequest.withTimeoutRequest( - linkedTo: entry, - in: timeoutEntry, - duration: timeout, - flags: .relativeTime - ) { + if true { //replace with IORING_FEAT_MIN_TIMEOUT feature check try _submitRequests(ring: &ring, ringDescriptor: internalRing.ringDescriptor) + } else { + let timeoutEntry = _blockingGetSubmissionEntry( + ring: &ring, + submissionQueueEntries: internalRing.submissionQueueEntries + ) + try RawIORequest.withTimeoutRequest( + linkedTo: entry, + in: timeoutEntry, + duration: timeout, + flags: .relativeTime + ) { + try _submitRequests(ring: &ring, ringDescriptor: internalRing.ringDescriptor) + } } } else { try _submitRequests(ring: &ring, ringDescriptor: internalRing.ringDescriptor) diff --git a/Tests/SystemTests/IORingTests.swift b/Tests/SystemTests/IORingTests.swift index d0102a0b..2c25e2b3 100644 --- a/Tests/SystemTests/IORingTests.swift +++ b/Tests/SystemTests/IORingTests.swift @@ -15,7 +15,7 @@ final class IORingTests: XCTestCase { var ring = try IORing(queueDepth: 32) ring.writeRequest(IORequest()) try ring.submitRequests() - let completion = ring.blockingConsumeCompletion() + let completion = try ring.blockingConsumeCompletion() XCTAssertEqual(completion.result, 0) } } diff --git a/Tests/SystemTests/ManagedIORingTests.swift b/Tests/SystemTests/ManagedIORingTests.swift index 24324037..a499f6c6 100644 --- a/Tests/SystemTests/ManagedIORingTests.swift +++ b/Tests/SystemTests/ManagedIORingTests.swift @@ -17,7 +17,7 @@ final class ManagedIORingTests: XCTestCase { XCTAssertEqual(completion.result, 0) } - func testTimeout() async throws { + func testSubmitTimeout() async throws { let ring = try ManagedIORing(queueDepth: 32) var pipes: (Int32, Int32) = (0, 0) withUnsafeMutableBytes(of: &pipes) { ptr in From 85b83fddb16c6ac0254f6e23279e6fd395d20b98 Mon Sep 17 00:00:00 2001 From: xuty Date: Tue, 17 Dec 2024 15:13:15 +0800 Subject: [PATCH 278/427] Use GetTempPathW for better compatibility --- Sources/System/FilePath/FilePathTempWindows.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/System/FilePath/FilePathTempWindows.swift b/Sources/System/FilePath/FilePathTempWindows.swift index 0d97edcb..d6e45f4f 100644 --- a/Sources/System/FilePath/FilePathTempWindows.swift +++ b/Sources/System/FilePath/FilePathTempWindows.swift @@ -17,7 +17,7 @@ internal func _getTemporaryDirectory() throws -> FilePath { capacity: Int(MAX_PATH) + 1) { buffer in - guard GetTempPath2W(DWORD(buffer.count), buffer.baseAddress) != 0 else { + guard GetTempPathW(DWORD(buffer.count), buffer.baseAddress) != 0 else { throw Errno(windowsError: GetLastError()) } From 7e73a336fb73f94558083d3f64baf71ad74f59ee Mon Sep 17 00:00:00 2001 From: Michael Chiu Date: Fri, 17 Jan 2025 14:30:51 -0800 Subject: [PATCH 279/427] Guard errnos not available on FreeBSD --- Sources/System/Errno.swift | 6 +++--- Sources/System/Internals/Constants.swift | 6 +++--- Sources/System/Internals/Syscalls.swift | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Sources/System/Errno.swift b/Sources/System/Errno.swift index 093023d0..62233c04 100644 --- a/Sources/System/Errno.swift +++ b/Sources/System/Errno.swift @@ -1294,7 +1294,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { @available(*, unavailable, renamed: "multiHop") public static var EMULTIHOP: Errno { multiHop } -#if !os(WASI) +#if !os(WASI) && !os(FreeBSD) /// No message available. /// /// No message was available to be received by the requested operation. @@ -1320,7 +1320,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { @available(*, unavailable, renamed: "noLink") public static var ENOLINK: Errno { noLink } -#if !os(WASI) +#if !os(WASI) && !os(FreeBSD) /// Reserved. /// /// This error is reserved for future use. @@ -1361,7 +1361,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { @available(*, unavailable, renamed: "protocolError") public static var EPROTO: Errno { protocolError } -#if !os(OpenBSD) && !os(WASI) +#if !os(FreeBSD) && !os(OpenBSD) && !os(WASI) /// Reserved. /// /// This error is reserved for future use. diff --git a/Sources/System/Internals/Constants.swift b/Sources/System/Internals/Constants.swift index 904e5b22..171d01dc 100644 --- a/Sources/System/Internals/Constants.swift +++ b/Sources/System/Internals/Constants.swift @@ -422,7 +422,7 @@ internal var _EBADMSG: CInt { EBADMSG } @_alwaysEmitIntoClient internal var _EMULTIHOP: CInt { EMULTIHOP } -#if !os(WASI) +#if !os(WASI) && !os(FreeBSD) @_alwaysEmitIntoClient internal var _ENODATA: CInt { ENODATA } #endif @@ -430,7 +430,7 @@ internal var _ENODATA: CInt { ENODATA } @_alwaysEmitIntoClient internal var _ENOLINK: CInt { ENOLINK } -#if !os(WASI) +#if !os(WASI) && !os(FreeBSD) @_alwaysEmitIntoClient internal var _ENOSR: CInt { ENOSR } @@ -442,7 +442,7 @@ internal var _ENOSTR: CInt { ENOSTR } @_alwaysEmitIntoClient internal var _EPROTO: CInt { EPROTO } -#if !os(OpenBSD) && !os(WASI) +#if !os(OpenBSD) && !os(WASI) && !os(FreeBSD) @_alwaysEmitIntoClient internal var _ETIME: CInt { ETIME } #endif diff --git a/Sources/System/Internals/Syscalls.swift b/Sources/System/Internals/Syscalls.swift index 6f885be5..e90e1d19 100644 --- a/Sources/System/Internals/Syscalls.swift +++ b/Sources/System/Internals/Syscalls.swift @@ -191,7 +191,7 @@ internal func system_confstr( internal let SYSTEM_AT_REMOVE_DIR = AT_REMOVEDIR internal let SYSTEM_DT_DIR = DT_DIR internal typealias system_dirent = dirent -#if os(Linux) || os(Android) +#if os(Linux) || os(Android) || os(FreeBSD) internal typealias system_DIRPtr = OpaquePointer #else internal typealias system_DIRPtr = UnsafeMutablePointer From a4fb6cb206d4c6b44db3dabaca760015a9f9b2fb Mon Sep 17 00:00:00 2001 From: Michael Chiu Date: Fri, 17 Jan 2025 14:43:34 -0800 Subject: [PATCH 280/427] Keep order consistent --- Sources/System/Errno.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/System/Errno.swift b/Sources/System/Errno.swift index 62233c04..d9fe1c81 100644 --- a/Sources/System/Errno.swift +++ b/Sources/System/Errno.swift @@ -1361,7 +1361,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { @available(*, unavailable, renamed: "protocolError") public static var EPROTO: Errno { protocolError } -#if !os(FreeBSD) && !os(OpenBSD) && !os(WASI) +#if !os(OpenBSD) && !os(WASI) && !os(FreeBSD) /// Reserved. /// /// This error is reserved for future use. From eb566de2d35ddcf3fc485070f2b03def8e8c1506 Mon Sep 17 00:00:00 2001 From: Michael Chiu Date: Fri, 17 Jan 2025 14:43:40 -0800 Subject: [PATCH 281/427] Fix tests on FreeBSD --- Tests/SystemTests/ErrnoTest.swift | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Tests/SystemTests/ErrnoTest.swift b/Tests/SystemTests/ErrnoTest.swift index 80e3e84f..c900f907 100644 --- a/Tests/SystemTests/ErrnoTest.swift +++ b/Tests/SystemTests/ErrnoTest.swift @@ -164,12 +164,14 @@ final class ErrnoTest: XCTestCase { #if !os(Windows) XCTAssert(EBADMSG == Errno.badMessage.rawValue) XCTAssert(EMULTIHOP == Errno.multiHop.rawValue) - XCTAssert(ENODATA == Errno.noData.rawValue) XCTAssert(ENOLINK == Errno.noLink.rawValue) + XCTAssert(EPROTO == Errno.protocolError.rawValue) +#if !os(FreeBSD) + XCTAssert(ENODATA == Errno.noData.rawValue) XCTAssert(ENOSR == Errno.noStreamResources.rawValue) XCTAssert(ENOSTR == Errno.notStream.rawValue) - XCTAssert(EPROTO == Errno.protocolError.rawValue) XCTAssert(ETIME == Errno.timeout.rawValue) +#endif #endif XCTAssert(EOPNOTSUPP == Errno.notSupportedOnSocket.rawValue) From 0d824eee86e9b42cb11cca3617898808600bc2a9 Mon Sep 17 00:00:00 2001 From: Michael Chiu Date: Fri, 17 Jan 2025 15:06:28 -0800 Subject: [PATCH 282/427] refactor ifdef in test --- Tests/SystemTests/ErrnoTest.swift | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Tests/SystemTests/ErrnoTest.swift b/Tests/SystemTests/ErrnoTest.swift index c900f907..3724e144 100644 --- a/Tests/SystemTests/ErrnoTest.swift +++ b/Tests/SystemTests/ErrnoTest.swift @@ -166,13 +166,15 @@ final class ErrnoTest: XCTestCase { XCTAssert(EMULTIHOP == Errno.multiHop.rawValue) XCTAssert(ENOLINK == Errno.noLink.rawValue) XCTAssert(EPROTO == Errno.protocolError.rawValue) -#if !os(FreeBSD) +#endif + +#if !os(Windows) && !os(FreeBSD) XCTAssert(ENODATA == Errno.noData.rawValue) XCTAssert(ENOSR == Errno.noStreamResources.rawValue) XCTAssert(ENOSTR == Errno.notStream.rawValue) XCTAssert(ETIME == Errno.timeout.rawValue) #endif -#endif + XCTAssert(EOPNOTSUPP == Errno.notSupportedOnSocket.rawValue) // From headers but not man page From bebd7c146e1c692b1fcbec9da8306ee2777d034a Mon Sep 17 00:00:00 2001 From: Fabrice de Gans Date: Thu, 23 Jan 2025 11:45:17 -0800 Subject: [PATCH 283/427] [cmake] Install libraries in standard directories Previously, libs were installed under `lib/swift/${os}`. They should be installed in the default library directory for the relevant target system. In addition, swiftmodules were installed in the older layout format. This changes to use the standard modern layout format for swiftmodules. --- CMakeLists.txt | 2 ++ cmake/modules/SwiftSupport.cmake | 36 ++++++++++++++++++++++++-------- 2 files changed, 29 insertions(+), 9 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 2ed4f5d1..11347f59 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -11,6 +11,8 @@ cmake_minimum_required(VERSION 3.16.0) project(SwiftSystem LANGUAGES C Swift) +include(GNUInstallDirs) + list(APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake/modules) set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) diff --git a/cmake/modules/SwiftSupport.cmake b/cmake/modules/SwiftSupport.cmake index 8f1e2c4d..cd6c1806 100644 --- a/cmake/modules/SwiftSupport.cmake +++ b/cmake/modules/SwiftSupport.cmake @@ -69,6 +69,28 @@ function(get_swift_host_os result_var_name) endif() endfunction() +if(NOT Swift_MODULE_TRIPLE) + # Attempt to get the module triple from the Swift compiler. + set(module_triple_command "${CMAKE_Swift_COMPILER}" -print-target-info) + if(CMAKE_Swift_COMPILER_TARGET) + list(APPEND module_triple_command -target ${CMAKE_Swift_COMPILER_TARGET}) + endif() + execute_process(COMMAND ${module_triple_command} + OUTPUT_VARIABLE target_info_json) + string(JSON module_triple GET "${target_info_json}" "target" "moduleTriple") + + # Exit now if we failed to infer the triple. + if(NOT module_triple) + message(FATAL_ERROR + "Failed to get module triple from Swift compiler. " + "Compiler output: ${target_info_json}") + endif() + + # Cache the module triple for future use. + set(Swift_MODULE_TRIPLE "${module_triple}" CACHE STRING "swift module triple used for installed swiftmodule and swiftinterface files") + mark_as_advanced(Swift_MODULE_TRIPLE) +endif() + function(_install_target module) get_swift_host_os(swift_os) get_target_property(type ${module} TYPE) @@ -79,24 +101,20 @@ function(_install_target module) set(swift swift) endif() - install(TARGETS ${module} - ARCHIVE DESTINATION lib/${swift}/${swift_os} - LIBRARY DESTINATION lib/${swift}/${swift_os} - RUNTIME DESTINATION bin) + install(TARGETS ${module}) if(type STREQUAL EXECUTABLE) return() endif() - get_swift_host_arch(swift_arch) get_target_property(module_name ${module} Swift_MODULE_NAME) if(NOT module_name) set(module_name ${module}) endif() install(FILES $/${module_name}.swiftdoc - DESTINATION lib/${swift}/${swift_os}/${module_name}.swiftmodule - RENAME ${swift_arch}.swiftdoc) + DESTINATION ${CMAKE_INSTALL_LIBDIR}/${swift}/${swift_os}/${module_name}.swiftmodule + RENAME ${Swift_MODULE_TRIPLE}.swiftdoc) install(FILES $/${module_name}.swiftmodule - DESTINATION lib/${swift}/${swift_os}/${module_name}.swiftmodule - RENAME ${swift_arch}.swiftmodule) + DESTINATION ${CMAKE_INSTALL_LIBDIR}/${swift}/${swift_os}/${module_name}.swiftmodule + RENAME ${Swift_MODULE_TRIPLE}.swiftmodule) endfunction() From 4d735a663154321e9f2fb609146fae979ab86ee9 Mon Sep 17 00:00:00 2001 From: Michael Chiu Date: Sun, 26 Jan 2025 21:29:13 -0800 Subject: [PATCH 284/427] Add FreeBSD errnos and open options --- Sources/System/Errno.swift | 40 ++++++++++++++++++++++-- Sources/System/FileDescriptor.swift | 33 +++++++++++++++++-- Sources/System/Internals/Constants.swift | 31 +++++++++++++++--- Tests/SystemTests/ErrnoTest.swift | 11 ++++++- Tests/SystemTests/FileTypesTest.swift | 11 +++++-- 5 files changed, 114 insertions(+), 12 deletions(-) diff --git a/Sources/System/Errno.swift b/Sources/System/Errno.swift index d9fe1c81..e88b96d9 100644 --- a/Sources/System/Errno.swift +++ b/Sources/System/Errno.swift @@ -1067,7 +1067,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { public static var ENOSYS: Errno { noFunction } // BSD -#if SYSTEM_PACKAGE_DARWIN +#if SYSTEM_PACKAGE_DARWIN || os(FreeBSD) /// Inappropriate file type or format. /// /// The file was the wrong type for the operation, @@ -1082,7 +1082,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { public static var EFTYPE: Errno { badFileTypeOrFormat } #endif -#if SYSTEM_PACKAGE_DARWIN +#if SYSTEM_PACKAGE_DARWIN || os(FreeBSD) /// Authentication error. /// /// The authentication ticket used to mount an NFS file system was invalid. @@ -1253,7 +1253,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { @available(*, unavailable, renamed: "illegalByteSequence") public static var EILSEQ: Errno { illegalByteSequence } -#if SYSTEM_PACKAGE_DARWIN +#if SYSTEM_PACKAGE_DARWIN || os(FreeBSD) /// Attribute not found. /// /// The specified extended attribute doesn't exist. @@ -1459,6 +1459,38 @@ extension Errno { public static var EOWNERDEAD: Errno { previousOwnerDied } #endif +#if os(FreeBSD) + /// Capabilities insufficient. + /// + /// The corresponding C error is `ENOTCAPABLE`. + @_alwaysEmitIntoClient + public static var notCapable: Errno { .init(rawValue: _ENOTCAPABLE) } + + @_alwaysEmitIntoClient + @available(*, unavailable, renamed: "notCapable") + public static var ENOTCAPABLE: Errno { notCapable } + + /// Not permitted in capability mode. + /// + /// The corresponding C error is `ECAPMODE`. + @_alwaysEmitIntoClient + public static var capabilityMode: Errno { .init(rawValue: _ECAPMODE) } + + @_alwaysEmitIntoClient + @available(*, unavailable, renamed: "capabilityMode") + public static var ECAPMODE: Errno { capabilityMode } + + /// Integrity check failed. + /// + /// The corresponding C error is `EINTEGRITY`. + @_alwaysEmitIntoClient + public static var integrityCheckFailed: Errno { .init(rawValue: _EINTEGRITY) } + + @_alwaysEmitIntoClient + @available(*, unavailable, renamed: "integrityCheckFailed") + public static var EINTEGRITY: Errno { integrityCheckFailed } +#endif + #if SYSTEM_PACKAGE_DARWIN /// Interface output queue is full. /// @@ -1469,7 +1501,9 @@ extension Errno { @_alwaysEmitIntoClient @available(*, unavailable, renamed: "outputQueueFull") public static var EQFULL: Errno { outputQueueFull } +#endif +#if SYSTEM_PACKAGE_DARWIN || os(FreeBSD) /// The largest valid error. /// /// This value is the largest valid value diff --git a/Sources/System/FileDescriptor.swift b/Sources/System/FileDescriptor.swift index d7b931eb..c9953e8b 100644 --- a/Sources/System/FileDescriptor.swift +++ b/Sources/System/FileDescriptor.swift @@ -182,7 +182,7 @@ extension FileDescriptor { @available(*, unavailable, renamed: "exclusiveCreate") public static var O_EXCL: OpenOptions { exclusiveCreate } -#if SYSTEM_PACKAGE_DARWIN +#if SYSTEM_PACKAGE_DARWIN || os(FreeBSD) /// Indicates that opening the file /// atomically obtains a shared lock on the file. /// @@ -250,6 +250,22 @@ extension FileDescriptor { public static var O_DIRECTORY: OpenOptions { directory } #endif +#if os(FreeBSD) + /// Indicates that each write operation is synchronous. + /// + /// If this option is specified, + /// each time you write to the file, + /// the new data is written immediately and synchronously to the disk. + /// + /// The corresponding C constant is `O_SYNC`. + @_alwaysEmitIntoClient + public static var sync: OpenOptions { .init(rawValue: _O_SYNC) } + + @_alwaysEmitIntoClient + @available(*, unavailable, renamed: "sync") + public static var O_SYNC: OpenOptions { sync } +#endif + #if SYSTEM_PACKAGE_DARWIN /// Indicates that opening the file /// opens symbolic links instead of following them. @@ -354,7 +370,7 @@ extension FileDescriptor { // TODO: These are available on some versions of Linux with appropriate // macro defines. -#if SYSTEM_PACKAGE_DARWIN +#if SYSTEM_PACKAGE_DARWIN || os(FreeBSD) /// Indicates that the offset should be set /// to the next hole after the specified number of bytes. /// @@ -456,6 +472,19 @@ extension FileDescriptor.OpenOptions (.truncate, ".truncate"), (.exclusiveCreate, ".exclusiveCreate"), ] +#elseif os(FreeBSD) + let descriptions: [(Element, StaticString)] = [ + (.nonBlocking, ".nonBlocking"), + (.append, ".append"), + (.create, ".create"), + (.truncate, ".truncate"), + (.exclusiveCreate, ".exclusiveCreate"), + (.sharedLock, ".sharedLock"), + (.exclusiveLock, ".exclusiveLock"), + (.sync, ".sync"), + (.noFollow, ".noFollow"), + (.closeOnExec, ".closeOnExec") + ] #else let descriptions: [(Element, StaticString)] = [ (.nonBlocking, ".nonBlocking"), diff --git a/Sources/System/Internals/Constants.swift b/Sources/System/Internals/Constants.swift index 171d01dc..d8cbdcbd 100644 --- a/Sources/System/Internals/Constants.swift +++ b/Sources/System/Internals/Constants.swift @@ -359,7 +359,7 @@ internal var _ENOLCK: CInt { ENOLCK } @_alwaysEmitIntoClient internal var _ENOSYS: CInt { ENOSYS } -#if SYSTEM_PACKAGE_DARWIN +#if SYSTEM_PACKAGE_DARWIN || os(FreeBSD) @_alwaysEmitIntoClient internal var _EFTYPE: CInt { EFTYPE } @@ -368,7 +368,9 @@ internal var _EAUTH: CInt { EAUTH } @_alwaysEmitIntoClient internal var _ENEEDAUTH: CInt { ENEEDAUTH } +#endif +#if SYSTEM_PACKAGE_DARWIN @_alwaysEmitIntoClient internal var _EPWROFF: CInt { EPWROFF } @@ -409,7 +411,7 @@ internal var _ENOMSG: CInt { ENOMSG } @_alwaysEmitIntoClient internal var _EILSEQ: CInt { EILSEQ } -#if SYSTEM_PACKAGE_DARWIN +#if SYSTEM_PACKAGE_DARWIN || os(FreeBSD) @_alwaysEmitIntoClient internal var _ENOATTR: CInt { ENOATTR } #endif @@ -471,10 +473,23 @@ internal var _ENOTRECOVERABLE: CInt { ENOTRECOVERABLE } internal var _EOWNERDEAD: CInt { EOWNERDEAD } #endif +#if os(FreeBSD) +@_alwaysEmitIntoClient +internal var _ENOTCAPABLE: CInt { ENOTCAPABLE } + +@_alwaysEmitIntoClient +internal var _ECAPMODE: CInt { ECAPMODE } + +@_alwaysEmitIntoClient +internal var _EINTEGRITY: CInt { EINTEGRITY } +#endif + #if SYSTEM_PACKAGE_DARWIN @_alwaysEmitIntoClient internal var _EQFULL: CInt { EQFULL } +#endif +#if SYSTEM_PACKAGE_DARWIN || os(FreeBSD) @_alwaysEmitIntoClient internal var _ELAST: CInt { ELAST } #endif @@ -524,7 +539,7 @@ internal var _O_APPEND: CInt { #endif } -#if SYSTEM_PACKAGE_DARWIN +#if SYSTEM_PACKAGE_DARWIN || os(FreeBSD) @_alwaysEmitIntoClient internal var _O_SHLOCK: CInt { O_SHLOCK } @@ -543,6 +558,14 @@ internal var _O_ASYNC: CInt { O_ASYNC } internal var _O_NOFOLLOW: CInt { O_NOFOLLOW } #endif +#if os(FreeBSD) +@_alwaysEmitIntoClient +internal var _O_FSYNC: CInt { O_FSYNC } + +@_alwaysEmitIntoClient +internal var _O_SYNC: CInt { O_SYNC } +#endif + @_alwaysEmitIntoClient internal var _O_CREAT: CInt { #if os(WASI) @@ -609,7 +632,7 @@ internal var _SEEK_CUR: CInt { SEEK_CUR } @_alwaysEmitIntoClient internal var _SEEK_END: CInt { SEEK_END } -#if SYSTEM_PACKAGE_DARWIN +#if SYSTEM_PACKAGE_DARWIN || os(FreeBSD) @_alwaysEmitIntoClient internal var _SEEK_HOLE: CInt { SEEK_HOLE } diff --git a/Tests/SystemTests/ErrnoTest.swift b/Tests/SystemTests/ErrnoTest.swift index 3724e144..da09657e 100644 --- a/Tests/SystemTests/ErrnoTest.swift +++ b/Tests/SystemTests/ErrnoTest.swift @@ -131,10 +131,13 @@ final class ErrnoTest: XCTestCase { XCTAssert(ENOLCK == Errno.noLocks.rawValue) XCTAssert(ENOSYS == Errno.noFunction.rawValue) -#if SYSTEM_PACKAGE_DARWIN +#if SYSTEM_PACKAGE_DARWIN || os(FreeBSD) XCTAssert(EFTYPE == Errno.badFileTypeOrFormat.rawValue) XCTAssert(EAUTH == Errno.authenticationError.rawValue) XCTAssert(ENEEDAUTH == Errno.needAuthenticator.rawValue) +#endif + +#if SYSTEM_PACKAGE_DARWIN XCTAssert(EPWROFF == Errno.devicePowerIsOff.rawValue) XCTAssert(EDEVERR == Errno.deviceError.rawValue) #endif @@ -196,6 +199,12 @@ final class ErrnoTest: XCTestCase { XCTAssert(EOWNERDEAD == Errno.previousOwnerDied.rawValue) #endif +#if os(FreeBSD) + XCTAssert(ENOTCAPABLE == Errno.notCapable.rawValue) + XCTAssert(ECAPMODE == Errno.capabilityMode.rawValue) + XCTAssert(EINTEGRITY == Errno.integrityCheckFailed.rawValue) +#endif + #if SYSTEM_PACKAGE_DARWIN XCTAssert(EQFULL == Errno.outputQueueFull.rawValue) XCTAssert(ELAST == Errno.lastErrnoValue.rawValue) diff --git a/Tests/SystemTests/FileTypesTest.swift b/Tests/SystemTests/FileTypesTest.swift index 620cfd70..58ceac1b 100644 --- a/Tests/SystemTests/FileTypesTest.swift +++ b/Tests/SystemTests/FileTypesTest.swift @@ -42,18 +42,25 @@ final class FileDescriptorTest: XCTestCase { #endif // BSD only -#if SYSTEM_PACKAGE_DARWIN +#if SYSTEM_PACKAGE_DARWIN || os(FreeBSD) XCTAssertEqual(O_SHLOCK, FileDescriptor.OpenOptions.sharedLock.rawValue) XCTAssertEqual(O_EXLOCK, FileDescriptor.OpenOptions.exclusiveLock.rawValue) +#endif + +#if SYSTEM_PACKAGE_DARWIN XCTAssertEqual(O_SYMLINK, FileDescriptor.OpenOptions.symlink.rawValue) XCTAssertEqual(O_EVTONLY, FileDescriptor.OpenOptions.eventOnly.rawValue) #endif +#if os(FreeBSD) + XCTAssertEqual(O_SYNC, FileDescriptor.OpenOptions.sync.rawValue) +#endif + XCTAssertEqual(SEEK_SET, FileDescriptor.SeekOrigin.start.rawValue) XCTAssertEqual(SEEK_CUR, FileDescriptor.SeekOrigin.current.rawValue) XCTAssertEqual(SEEK_END, FileDescriptor.SeekOrigin.end.rawValue) -#if SYSTEM_PACKAGE_DARWIN +#if SYSTEM_PACKAGE_DARWIN || os(FreeBSD) XCTAssertEqual(SEEK_HOLE, FileDescriptor.SeekOrigin.nextHole.rawValue) XCTAssertEqual(SEEK_DATA, FileDescriptor.SeekOrigin.nextData.rawValue) #endif From df7341ee2b553b09dee6f82bb3b0c84e9b6f0441 Mon Sep 17 00:00:00 2001 From: Michael Chiu Date: Sun, 26 Jan 2025 21:29:19 -0800 Subject: [PATCH 285/427] fix cmake x86_64 build --- cmake/modules/SwiftSupport.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/modules/SwiftSupport.cmake b/cmake/modules/SwiftSupport.cmake index 8f1e2c4d..19cf0223 100644 --- a/cmake/modules/SwiftSupport.cmake +++ b/cmake/modules/SwiftSupport.cmake @@ -36,7 +36,7 @@ function(get_swift_host_arch result_var_name) elseif("${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "armv7-a") set("${result_var_name}" "armv7" PARENT_SCOPE) elseif("${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "amd64") - set("${result_var_name}" "amd64" PARENT_SCOPE) + set("${result_var_name}" "x86_64" PARENT_SCOPE) elseif("${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "AMD64") set("${result_var_name}" "x86_64" PARENT_SCOPE) elseif("${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "IA64") From 90702d6d15678f3c3a715b2d678af128b4bc1edb Mon Sep 17 00:00:00 2001 From: 3405691582 Date: Tue, 4 Feb 2025 19:37:18 -0500 Subject: [PATCH 286/427] Typealias `system_DIRPtr` for OpenBSD. --- Sources/System/Internals/Syscalls.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/System/Internals/Syscalls.swift b/Sources/System/Internals/Syscalls.swift index e90e1d19..1627273c 100644 --- a/Sources/System/Internals/Syscalls.swift +++ b/Sources/System/Internals/Syscalls.swift @@ -191,7 +191,7 @@ internal func system_confstr( internal let SYSTEM_AT_REMOVE_DIR = AT_REMOVEDIR internal let SYSTEM_DT_DIR = DT_DIR internal typealias system_dirent = dirent -#if os(Linux) || os(Android) || os(FreeBSD) +#if os(Linux) || os(Android) || os(FreeBSD) || os(OpenBSD) internal typealias system_DIRPtr = OpaquePointer #else internal typealias system_DIRPtr = UnsafeMutablePointer From 3b053e055b2288c8e05b88b6f55fe225d9465195 Mon Sep 17 00:00:00 2001 From: 3405691582 Date: Tue, 4 Feb 2025 19:37:55 -0500 Subject: [PATCH 287/427] ManagedBuffer.capacity is unavailable on OpenBSD. `.capacity` is backed by malloc introspection (`malloc_size`), which is not available on all platforms. The workaround is straightforward here, thankfully: instead, use the minimum capacity already specified. --- Sources/System/Internals/RawBuffer.swift | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Sources/System/Internals/RawBuffer.swift b/Sources/System/Internals/RawBuffer.swift index 7f9461c3..83161c47 100644 --- a/Sources/System/Internals/RawBuffer.swift +++ b/Sources/System/Internals/RawBuffer.swift @@ -80,7 +80,13 @@ extension _RawBuffer { internal static func create(minimumCapacity: Int) -> Storage { Storage.create( minimumCapacity: minimumCapacity, - makingHeaderWith: { $0.capacity } + makingHeaderWith: { +#if os(OpenBSD) + minimumCapacity +#else + $0.capacity +#endif + } ) as! Storage } } From e5fdf9ea96ff918aac8d8f1667909c74c1bc3b7c Mon Sep 17 00:00:00 2001 From: David Smith Date: Thu, 6 Feb 2025 00:41:30 +0000 Subject: [PATCH 288/427] Remove managed abstractions for now --- Sources/System/AsyncFileDescriptor.swift | 161 ------------------ Sources/System/ManagedIORing.swift | 97 ----------- .../AsyncFileDescriptorTests.swift | 58 ------- Tests/SystemTests/ManagedIORingTests.swift | 48 ------ 4 files changed, 364 deletions(-) delete mode 100644 Sources/System/AsyncFileDescriptor.swift delete mode 100644 Sources/System/ManagedIORing.swift delete mode 100644 Tests/SystemTests/AsyncFileDescriptorTests.swift delete mode 100644 Tests/SystemTests/ManagedIORingTests.swift diff --git a/Sources/System/AsyncFileDescriptor.swift b/Sources/System/AsyncFileDescriptor.swift deleted file mode 100644 index a76d90e5..00000000 --- a/Sources/System/AsyncFileDescriptor.swift +++ /dev/null @@ -1,161 +0,0 @@ -@_implementationOnly import CSystem - -public struct AsyncFileDescriptor: ~Copyable { - @usableFromInline var open: Bool = true - @usableFromInline let fileSlot: IORingFileSlot - @usableFromInline let ring: ManagedIORing - - public static func open( - path: FilePath, - in directory: FileDescriptor = FileDescriptor(rawValue: -100), - on ring: ManagedIORing, - mode: FileDescriptor.AccessMode, - options: FileDescriptor.OpenOptions = FileDescriptor.OpenOptions(), - permissions: FilePermissions? = nil - ) async throws -> AsyncFileDescriptor { - // todo; real error type - guard let fileSlot = ring.getFileSlot() else { - throw IORingError.missingRequiredFeatures - } - //TODO: need an async-friendly withCString - let cstr = path.withCString { - return $0 // bad - } - let res = try await ring.submit(request: IORequest( - opening: cstr, - in: directory, - into: fileSlot, - mode: mode, - options: options, - permissions: permissions - )) - if res.result < 0 { - throw Errno(rawValue: -res.result) - } - - return AsyncFileDescriptor( - fileSlot, ring: ring - ) - } - - internal init(_ fileSlot: consuming IORingFileSlot, ring: ManagedIORing) { - self.fileSlot = consume fileSlot - self.ring = ring - } - - @inlinable @inline(__always) - public consuming func close(isolation actor: isolated (any Actor)? = #isolation) async throws { - let res = try await ring.submit(request: IORequest(closing: fileSlot)) - if res.result < 0 { - throw Errno(rawValue: -res.result) - } - self.open = false - } - - @inlinable @inline(__always) - public func read( - into buffer: inout UnsafeMutableRawBufferPointer, - atAbsoluteOffset offset: UInt64 = UInt64.max, - isolation actor: isolated (any Actor)? = #isolation - ) async throws -> UInt32 { - let res = try await ring.submit(request: IORequest( - reading: fileSlot, - into: buffer, - at: offset - )) - if res.result < 0 { - throw Errno(rawValue: -res.result) - } else { - return UInt32(bitPattern: res.result) - } - } - - @inlinable @inline(__always) - public func read( - into buffer: IORingBuffer, //TODO: should be inout? - atAbsoluteOffset offset: UInt64 = UInt64.max, - isolation actor: isolated (any Actor)? = #isolation - ) async throws -> UInt32 { - let res = try await ring.submit(request: IORequest( - reading: fileSlot, - into: buffer, - at: offset - )) - if res.result < 0 { - throw Errno(rawValue: -res.result) - } else { - return UInt32(bitPattern: res.result) - } - } - - //TODO: temporary workaround until AsyncSequence supports ~Copyable - public consuming func toBytes() -> AsyncFileDescriptorSequence { - AsyncFileDescriptorSequence(self) - } - - //TODO: can we do the linear types thing and error if they don't consume it manually? - // deinit { - // if self.open { - // close() - // // TODO: close or error? TBD - // } - // } -} - -public class AsyncFileDescriptorSequence: AsyncSequence { - var descriptor: AsyncFileDescriptor? - - public func makeAsyncIterator() -> FileIterator { - return .init(descriptor.take()!) - } - - internal init(_ descriptor: consuming AsyncFileDescriptor) { - self.descriptor = consume descriptor - } - - public typealias AsyncIterator = FileIterator - public typealias Element = UInt8 -} - -//TODO: only a class due to ~Copyable limitations -public class FileIterator: AsyncIteratorProtocol { - @usableFromInline let file: AsyncFileDescriptor - @usableFromInline var buffer: IORingBuffer - @usableFromInline var done: Bool - - @usableFromInline internal var currentByte: UnsafeRawPointer? - @usableFromInline internal var lastByte: UnsafeRawPointer? - - init(_ file: consuming AsyncFileDescriptor) { - self.buffer = file.ring.getBuffer()! - self.file = file - self.done = false - } - - @inlinable @inline(__always) - public func nextBuffer() async throws { - let bytesRead = Int(try await file.read(into: buffer)) - if _fastPath(bytesRead != 0) { - let unsafeBuffer = buffer.unsafeBuffer - let bufPointer = unsafeBuffer.baseAddress.unsafelyUnwrapped - self.currentByte = UnsafeRawPointer(bufPointer) - self.lastByte = UnsafeRawPointer(bufPointer.advanced(by: bytesRead)) - } else { - done = true - } - } - - @inlinable @inline(__always) - public func next() async throws -> UInt8? { - if _fastPath(currentByte != lastByte) { - // SAFETY: both pointers should be non-nil if they're not equal - let byte = currentByte.unsafelyUnwrapped.load(as: UInt8.self) - currentByte = currentByte.unsafelyUnwrapped + 1 - return byte - } else if done { - return nil - } - try await nextBuffer() - return try await next() - } -} diff --git a/Sources/System/ManagedIORing.swift b/Sources/System/ManagedIORing.swift deleted file mode 100644 index b6df140d..00000000 --- a/Sources/System/ManagedIORing.swift +++ /dev/null @@ -1,97 +0,0 @@ -fileprivate func handleCompletionError( - _ result: Int32, - for continuation: UnsafeContinuation) { - var error: IORingError = .unknown - switch result { - case -(_ECANCELED): - error = .operationCanceled - default: - error = .unknown - } - continuation.resume(throwing: error) -} - -final public class ManagedIORing: @unchecked Sendable { - var internalRing: IORing - - public init(queueDepth: UInt32) throws { - self.internalRing = try IORing(queueDepth: queueDepth) - self.internalRing.registerBuffers(bufSize: 655336, count: 4) - self.internalRing.registerFiles(count: 32) - self.startWaiter() - } - - private func startWaiter() { - Task.detached { - while !Task.isCancelled { - //TODO: should timeout handling be sunk into IORing? - //TODO: sort out the error handling here - let cqe = try! self.internalRing.blockingConsumeCompletion() - - if cqe.userData == 0 { - continue - } - let cont = unsafeBitCast( - cqe.userData, to: UnsafeContinuation.self) - - if cqe.result < 0 { - handleCompletionError(cqe.result, for: cont) - } else { - cont.resume(returning: cqe) - } - } - } - } - - public func submit( - request: __owned IORequest, - timeout: Duration? = nil, - isolation actor: isolated (any Actor)? = #isolation - ) async throws -> IOCompletion { - var consumeOnceWorkaround: IORequest? = request - return try await withUnsafeThrowingContinuation { cont in - do { - try internalRing.submissionMutex.withLock { ring in - let request = consumeOnceWorkaround.take()! - let entry = _blockingGetSubmissionEntry( - ring: &ring, submissionQueueEntries: internalRing.submissionQueueEntries) - entry.pointee = request.makeRawRequest().rawValue - entry.pointee.user_data = unsafeBitCast(cont, to: UInt64.self) - if let timeout { - //TODO: if IORING_FEAT_MIN_TIMEOUT is supported we can do this more efficiently - if true { //replace with IORING_FEAT_MIN_TIMEOUT feature check - try _submitRequests(ring: &ring, ringDescriptor: internalRing.ringDescriptor) - } else { - let timeoutEntry = _blockingGetSubmissionEntry( - ring: &ring, - submissionQueueEntries: internalRing.submissionQueueEntries - ) - try RawIORequest.withTimeoutRequest( - linkedTo: entry, - in: timeoutEntry, - duration: timeout, - flags: .relativeTime - ) { - try _submitRequests(ring: &ring, ringDescriptor: internalRing.ringDescriptor) - } - } - } else { - try _submitRequests(ring: &ring, ringDescriptor: internalRing.ringDescriptor) - } - } - } catch (let e) { - cont.resume(throwing: e) - } - } - - } - - internal func getFileSlot() -> IORingFileSlot? { - internalRing.getFile() - } - - internal func getBuffer() -> IORingBuffer? { - internalRing.getBuffer() - } - -} diff --git a/Tests/SystemTests/AsyncFileDescriptorTests.swift b/Tests/SystemTests/AsyncFileDescriptorTests.swift deleted file mode 100644 index ebd308fc..00000000 --- a/Tests/SystemTests/AsyncFileDescriptorTests.swift +++ /dev/null @@ -1,58 +0,0 @@ -import XCTest - -#if SYSTEM_PACKAGE -import SystemPackage -#else -import System -#endif - -final class AsyncFileDescriptorTests: XCTestCase { - func testOpen() async throws { - let ring = try ManagedIORing(queueDepth: 32) - _ = try await AsyncFileDescriptor.open( - path: "/dev/zero", - on: ring, - mode: .readOnly - ) - } - - func testOpenClose() async throws { - let ring = try ManagedIORing(queueDepth: 32) - let file = try await AsyncFileDescriptor.open( - path: "/dev/zero", - on: ring, - mode: .readOnly - ) - try await file.close() - } - - func testDevNullEmpty() async throws { - let ring = try ManagedIORing(queueDepth: 32) - let file = try await AsyncFileDescriptor.open( - path: "/dev/null", - on: ring, - mode: .readOnly - ) - for try await _ in file.toBytes() { - XCTFail("/dev/null should be empty") - } - } - - func testRead() async throws { - let ring = try ManagedIORing(queueDepth: 32) - let file = try await AsyncFileDescriptor.open( - path: "/dev/zero", - on: ring, - mode: .readOnly - ) - let bytes = file.toBytes() - var counter = 0 - for try await byte in bytes { - XCTAssert(byte == 0) - counter &+= 1 - if counter > 16384 { - break - } - } - } -} diff --git a/Tests/SystemTests/ManagedIORingTests.swift b/Tests/SystemTests/ManagedIORingTests.swift deleted file mode 100644 index a499f6c6..00000000 --- a/Tests/SystemTests/ManagedIORingTests.swift +++ /dev/null @@ -1,48 +0,0 @@ -import XCTest - -#if SYSTEM_PACKAGE -import SystemPackage -#else -import System -#endif - -final class ManagedIORingTests: XCTestCase { - func testInit() throws { - _ = try ManagedIORing(queueDepth: 32) - } - - func testNop() async throws { - let ring = try ManagedIORing(queueDepth: 32) - let completion = try await ring.submit(request: IORequest()) - XCTAssertEqual(completion.result, 0) - } - - func testSubmitTimeout() async throws { - let ring = try ManagedIORing(queueDepth: 32) - var pipes: (Int32, Int32) = (0, 0) - withUnsafeMutableBytes(of: &pipes) { ptr in - ptr.withMemoryRebound(to: UInt32.self) { tptr in - let res = pipe(tptr.baseAddress!) - XCTAssertEqual(res, 0) - } - } - let buffer = UnsafeMutableRawBufferPointer.allocate(byteCount: 128, alignment: 16) - do { - let completion = try await ring.submit( - request: IORequest(reading: FileDescriptor(rawValue: pipes.0), into: buffer), - timeout: .seconds(0.1) - ) - print("\(completion)") - XCTFail("An error should be thrown") - } catch (let e) { - if let err = e as? IORingError { - XCTAssertEqual(err, .operationCanceled) - } else { - XCTFail() - } - buffer.deallocate() - close(pipes.0) - close(pipes.1) - } - } -} From fdbcecab0bf531d5f739f5414969599b9be663aa Mon Sep 17 00:00:00 2001 From: David Smith Date: Thu, 6 Feb 2025 00:42:21 +0000 Subject: [PATCH 289/427] Eliminate internal locking and add multiple consume support --- Sources/CSystem/include/io_uring.h | 10 +- Sources/System/IORing.swift | 223 ++++++++++++++++++----------- 2 files changed, 150 insertions(+), 83 deletions(-) diff --git a/Sources/CSystem/include/io_uring.h b/Sources/CSystem/include/io_uring.h index 5c05ed8b..5cab757c 100644 --- a/Sources/CSystem/include/io_uring.h +++ b/Sources/CSystem/include/io_uring.h @@ -45,11 +45,17 @@ int io_uring_setup(unsigned int entries, struct io_uring_params *p) return syscall(__NR_io_uring_setup, entries, p); } +int io_uring_enter2(int fd, unsigned int to_submit, unsigned int min_complete, + unsigned int flags, void *args, size_t sz) +{ + return syscall(__NR_io_uring_enter, fd, to_submit, min_complete, + flags, args, _NSIG / 8); +} + int io_uring_enter(int fd, unsigned int to_submit, unsigned int min_complete, unsigned int flags, sigset_t *sig) { - return syscall(__NR_io_uring_enter, fd, to_submit, min_complete, - flags, sig, _NSIG / 8); + return io_uring_enter2(fd, to_submit, min_complete, flags, sig, _NSIG / 8); } #endif diff --git a/Sources/System/IORing.swift b/Sources/System/IORing.swift index 872d9960..0328538f 100644 --- a/Sources/System/IORing.swift +++ b/Sources/System/IORing.swift @@ -51,7 +51,7 @@ internal final class ResourceManager: @unchecked Sendable { struct Resources { let resourceList: UnsafeMutableBufferPointer - var freeList: [Int] //TODO: bitvector? + var freeList: [Int] //TODO: bitvector? } let mutex: Mutex @@ -125,21 +125,27 @@ extension IORingBuffer { } @inlinable @inline(__always) -internal func _writeRequest(_ request: __owned RawIORequest, ring: inout SQRing, submissionQueueEntries: UnsafeMutableBufferPointer) +internal func _writeRequest( + _ request: __owned RawIORequest, ring: inout SQRing, + submissionQueueEntries: UnsafeMutableBufferPointer +) -> Bool { - let entry = _blockingGetSubmissionEntry(ring: &ring, submissionQueueEntries: submissionQueueEntries) + let entry = _blockingGetSubmissionEntry( + ring: &ring, submissionQueueEntries: submissionQueueEntries) entry.pointee = request.rawValue return true } @inlinable @inline(__always) -internal func _blockingGetSubmissionEntry(ring: inout SQRing, submissionQueueEntries: UnsafeMutableBufferPointer) -> UnsafeMutablePointer< +internal func _blockingGetSubmissionEntry( + ring: inout SQRing, submissionQueueEntries: UnsafeMutableBufferPointer +) -> UnsafeMutablePointer< io_uring_sqe > { while true { if let entry = _getSubmissionEntry( - ring: &ring, + ring: &ring, submissionQueueEntries: submissionQueueEntries ) { return entry @@ -152,12 +158,12 @@ internal func _blockingGetSubmissionEntry(ring: inout SQRing, submissionQueueEnt //TODO: omitting signal mask for now //Tell the kernel that we've submitted requests and/or are waiting for completions internal func _enter( - ringDescriptor: Int32, + ringDescriptor: Int32, numEvents: UInt32, minCompletions: UInt32, flags: UInt32 ) throws -> Int32 { - // Ring always needs enter right now; + // Ring always needs enter right now; // TODO: support SQPOLL here while true { let ret = io_uring_enter(ringDescriptor, numEvents, minCompletions, flags, nil) @@ -180,32 +186,36 @@ internal func _enter( } } -internal func _submitRequests(ring: inout SQRing, ringDescriptor: Int32) throws { - let flushedEvents = _flushQueue(ring: &ring) - _ = try _enter(ringDescriptor: ringDescriptor, numEvents: flushedEvents, minCompletions: 0, flags: 0) +internal func _submitRequests(ring: borrowing SQRing, ringDescriptor: Int32) throws { + let flushedEvents = _flushQueue(ring: ring) + _ = try _enter( + ringDescriptor: ringDescriptor, numEvents: flushedEvents, minCompletions: 0, flags: 0) } -internal func _getUnconsumedSubmissionCount(ring: inout SQRing) -> UInt32 { +internal func _getUnconsumedSubmissionCount(ring: borrowing SQRing) -> UInt32 { return ring.userTail - ring.kernelHead.pointee.load(ordering: .acquiring) } -internal func _getUnconsumedCompletionCount(ring: inout CQRing) -> UInt32 { - return ring.kernelTail.pointee.load(ordering: .acquiring) - ring.kernelHead.pointee.load(ordering: .acquiring) +internal func _getUnconsumedCompletionCount(ring: borrowing CQRing) -> UInt32 { + return ring.kernelTail.pointee.load(ordering: .acquiring) + - ring.kernelHead.pointee.load(ordering: .acquiring) } //TODO: pretty sure this is supposed to do more than it does -internal func _flushQueue(ring: inout SQRing) -> UInt32 { +internal func _flushQueue(ring: borrowing SQRing) -> UInt32 { ring.kernelTail.pointee.store( ring.userTail, ordering: .releasing ) - return _getUnconsumedSubmissionCount(ring: &ring) + return _getUnconsumedSubmissionCount(ring: ring) } @usableFromInline @inline(__always) -internal func _getSubmissionEntry(ring: inout SQRing, submissionQueueEntries: UnsafeMutableBufferPointer) -> UnsafeMutablePointer< +internal func _getSubmissionEntry( + ring: inout SQRing, submissionQueueEntries: UnsafeMutableBufferPointer +) -> UnsafeMutablePointer< io_uring_sqe >? { - let next = ring.userTail &+ 1 //this is expected to wrap + let next = ring.userTail &+ 1 //this is expected to wrap // FEAT: smp load when SQPOLL in use (not in MVP) let kernelHead = ring.kernelHead.pointee.load(ordering: .acquiring) @@ -227,15 +237,15 @@ internal func _getSubmissionEntry(ring: inout SQRing, submissionQueueEntries: Un return nil } -public struct IORing: @unchecked Sendable, ~Copyable { +public struct IORing: ~Copyable { let ringFlags: UInt32 let ringDescriptor: Int32 - @usableFromInline let submissionMutex: Mutex + @usableFromInline var submissionRing: SQRing // FEAT: set this eventually let submissionPolling: Bool = false - let completionMutex: Mutex + let completionRing: CQRing @usableFromInline let submissionQueueEntries: UnsafeMutableBufferPointer @@ -362,82 +372,136 @@ public struct IORing: @unchecked Sendable, ~Copyable { ) ) - self.submissionMutex = Mutex(submissionRing) - self.completionMutex = Mutex(completionRing) + self.submissionRing = submissionRing + self.completionRing = completionRing self.ringFlags = params.flags } private func _blockingConsumeCompletionGuts( - extraArgs: UnsafeMutablePointer? = nil - ) throws(IORingError) -> IOCompletion { - completionMutex.withLock { ring in - if let completion = _tryConsumeCompletion(ring: &ring) { - return completion - } else { - while true { - var sz = 0 - if extraArgs != nil { - sz = MemoryLayout.size - } - let res = io_uring_enter2( - ringDescriptor, - 0, - 1, - IORING_ENTER_GETEVENTS, - extraArgs, - sz - ) - // error handling: - // EAGAIN / EINTR (try again), - // EBADF / EBADFD / EOPNOTSUPP / ENXIO - // (failure in ring lifetime management, fatal), - // EINVAL (bad constant flag?, fatal), - // EFAULT (bad address for argument from library, fatal) - // EBUSY (not enough space for events; implies events filled - // by kernel between kernelTail load and now) - if res >= 0 || res == -EBUSY { - break - } else if res == -EAGAIN || res == -EINTR { - continue - } - fatalError( - "fatal error in receiving requests: " - + Errno(rawValue: -res).debugDescription - ) + minimumCount: UInt32, + extraArgs: UnsafeMutablePointer? = nil, + consumer: (IOCompletion?, IORingError?, Bool) throws -> Void + ) rethrows { + var count = 0 + while let completion = _tryConsumeCompletion(ring: completionRing) { + count += 1 + try consumer(completion, nil, false) + } + + if count < minimumCount { + while count < minimumCount { + var sz = 0 + if extraArgs != nil { + sz = MemoryLayout.size + } + let res = io_uring_enter2( + ringDescriptor, + 0, + minimumCount, + IORING_ENTER_GETEVENTS, + extraArgs, + sz + ) + // error handling: + // EAGAIN / EINTR (try again), + // EBADF / EBADFD / EOPNOTSUPP / ENXIO + // (failure in ring lifetime management, fatal), + // EINVAL (bad constant flag?, fatal), + // EFAULT (bad address for argument from library, fatal) + // EBUSY (not enough space for events; implies events filled + // by kernel between kernelTail load and now) + if res >= 0 || res == -EBUSY { + break + } else if res == -EAGAIN || res == -EINTR { + continue + } + fatalError( + "fatal error in receiving requests: " + + Errno(rawValue: -res).debugDescription + ) + while let completion = _tryConsumeCompletion(ring: completionRing) { + try consumer(completion, nil, false) } - return _tryConsumeCompletion(ring: &ring).unsafelyUnwrapped } + try consumer(nil, nil, true) } } - public func blockingConsumeCompletion(timeout: Duration? = nil) throws -> IOCompletion { + internal func _blockingConsumeOneCompletion( + extraArgs: UnsafeMutablePointer? = nil + ) throws -> IOCompletion { + var result: IOCompletion? = nil + try _blockingConsumeCompletionGuts(minimumCount: 1, extraArgs: extraArgs) { + (completion, error, done) in + if let error { + throw error + } + if let completion { + result = completion + } + } + return result.unsafelyUnwrapped + } + + public func blockingConsumeCompletion( + timeout: Duration? = nil + ) throws -> IOCompletion { if let timeout { var ts = __kernel_timespec( - tv_sec: timeout.components.seconds, - tv_nsec: timeout.components.attoseconds / 1_000_000_000 + tv_sec: timeout.components.seconds, + tv_nsec: timeout.components.attoseconds / 1_000_000_000 ) return try withUnsafePointer(to: &ts) { tsPtr in var args = io_uring_getevents_arg( - sigmask: 0, - sigmask_sz: 0, - pad: 0, + sigmask: 0, + sigmask_sz: 0, + pad: 0, ts: UInt64(UInt(bitPattern: tsPtr)) ) - return try _blockingConsumeCompletionGuts(extraArgs: &args) + return try _blockingConsumeOneCompletion(extraArgs: &args) } } else { - return try _blockingConsumeCompletionGuts() + return try _blockingConsumeOneCompletion() } } - public func tryConsumeCompletion() -> IOCompletion? { - completionMutex.withLock { ring in - return _tryConsumeCompletion(ring: &ring) + public func blockingConsumeCompletions( + minimumCount: UInt32 = 1, + timeout: Duration? = nil, + consumer: (IOCompletion?, IORingError?, Bool) throws -> Void + ) throws { + var x = Glibc.stat() + let y = x.st_size + if let timeout { + var ts = __kernel_timespec( + tv_sec: timeout.components.seconds, + tv_nsec: timeout.components.attoseconds / 1_000_000_000 + ) + return try withUnsafePointer(to: &ts) { tsPtr in + var args = io_uring_getevents_arg( + sigmask: 0, + sigmask_sz: 0, + pad: 0, + ts: UInt64(UInt(bitPattern: tsPtr)) + ) + try _blockingConsumeCompletionGuts( + minimumCount: minimumCount, extraArgs: &args, consumer: consumer) + } + } else { + try _blockingConsumeCompletionGuts(minimumCount: minimumCount, consumer: consumer) } } - func _tryConsumeCompletion(ring: inout CQRing) -> IOCompletion? { + // public func peekNextCompletion() -> IOCompletion { + + // } + + public func tryConsumeCompletion() -> IOCompletion? { + return _tryConsumeCompletion(ring: completionRing) + } + + func _tryConsumeCompletion(ring: borrowing CQRing) -> IOCompletion? { let tail = ring.kernelTail.pointee.load(ordering: .acquiring) let head = ring.kernelHead.pointee.load(ordering: .acquiring) @@ -459,9 +523,9 @@ public struct IORing: @unchecked Sendable, ~Copyable { var rawfd = descriptor.rawValue let result = withUnsafePointer(to: &rawfd) { fdptr in return io_uring_register( - ring.ringDescriptor, + ring.ringDescriptor, IORING_REGISTER_EVENTFD, - UnsafeMutableRawPointer(mutating: fdptr), + UnsafeMutableRawPointer(mutating: fdptr), 1 ) } @@ -470,9 +534,9 @@ public struct IORing: @unchecked Sendable, ~Copyable { public mutating func unregisterEventFD(ring: inout IORing) throws { let result = io_uring_register( - ring.ringDescriptor, + ring.ringDescriptor, IORING_UNREGISTER_EVENTFD, - nil, + nil, 0 ) try handleRegistrationResult(result) @@ -529,17 +593,14 @@ public struct IORing: @unchecked Sendable, ~Copyable { } public func submitRequests() throws { - try submissionMutex.withLock { ring in - try _submitRequests(ring: &ring, ringDescriptor: ringDescriptor) - } + try _submitRequests(ring: submissionRing, ringDescriptor: ringDescriptor) } @inlinable @inline(__always) public mutating func writeRequest(_ request: __owned IORequest) -> Bool { var raw: RawIORequest? = request.makeRawRequest() - return submissionMutex.withLock { ring in - return _writeRequest(raw.take()!, ring: &ring, submissionQueueEntries: submissionQueueEntries) - } + return _writeRequest( + raw.take()!, ring: &submissionRing, submissionQueueEntries: submissionQueueEntries) } deinit { From 7107a570b4ad1a37a0ff86d510bc8159fb18dbb8 Mon Sep 17 00:00:00 2001 From: David Smith Date: Tue, 11 Feb 2025 21:01:49 +0000 Subject: [PATCH 290/427] Fix import visibility --- Sources/System/IORequest.swift | 10 +++++----- Sources/System/IORing.swift | 2 -- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/Sources/System/IORequest.swift b/Sources/System/IORequest.swift index 0af87176..0e1a3b06 100644 --- a/Sources/System/IORequest.swift +++ b/Sources/System/IORequest.swift @@ -1,4 +1,4 @@ -import struct CSystem.io_uring_sqe +@_implementationOnly import struct CSystem.io_uring_sqe @usableFromInline internal enum IORequestCore: ~Copyable { @@ -62,7 +62,7 @@ internal enum IORequestCore: ~Copyable { case closeSlot(IORingFileSlot) } -@inlinable @inline(__always) +@inline(__always) internal func makeRawRequest_readWrite_registered( file: FileDescriptor, buffer: IORingBuffer, @@ -76,7 +76,7 @@ internal func makeRawRequest_readWrite_registered( return request } -@inlinable @inline(__always) +@inline(__always) internal func makeRawRequest_readWrite_registered_slot( file: IORingFileSlot, buffer: IORingBuffer, @@ -104,7 +104,7 @@ internal func makeRawRequest_readWrite_unregistered( return request } -@inlinable @inline(__always) +@inline(__always) internal func makeRawRequest_readWrite_unregistered_slot( file: IORingFileSlot, buffer: UnsafeMutableRawBufferPointer, @@ -251,7 +251,7 @@ extension IORequest { fatalError("Implement me") } - @inlinable @inline(__always) + @inline(__always) public consuming func makeRawRequest() -> RawIORequest { var request = RawIORequest() switch extractCore() { diff --git a/Sources/System/IORing.swift b/Sources/System/IORing.swift index 0328538f..472a69b2 100644 --- a/Sources/System/IORing.swift +++ b/Sources/System/IORing.swift @@ -471,8 +471,6 @@ public struct IORing: ~Copyable { timeout: Duration? = nil, consumer: (IOCompletion?, IORingError?, Bool) throws -> Void ) throws { - var x = Glibc.stat() - let y = x.st_size if let timeout { var ts = __kernel_timespec( tv_sec: timeout.components.seconds, From 02481e0daabc0fb342c175681347efe8f2d44337 Mon Sep 17 00:00:00 2001 From: David Smith Date: Tue, 11 Feb 2025 21:06:16 +0000 Subject: [PATCH 291/427] More import fixes --- Sources/System/IORing.swift | 12 ++++++------ Sources/System/RawIORequest.swift | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Sources/System/IORing.swift b/Sources/System/IORing.swift index 472a69b2..6dff66f1 100644 --- a/Sources/System/IORing.swift +++ b/Sources/System/IORing.swift @@ -2,7 +2,7 @@ import Glibc // needed for mmap import Synchronization -import struct CSystem.io_uring_sqe +@_implementationOnly import struct CSystem.io_uring_sqe // XXX: this *really* shouldn't be here. oh well. extension UnsafeMutableRawPointer { @@ -124,7 +124,7 @@ extension IORingBuffer { } } -@inlinable @inline(__always) +@inline(__always) internal func _writeRequest( _ request: __owned RawIORequest, ring: inout SQRing, submissionQueueEntries: UnsafeMutableBufferPointer @@ -137,7 +137,7 @@ internal func _writeRequest( return true } -@inlinable @inline(__always) +@inline(__always) internal func _blockingGetSubmissionEntry( ring: inout SQRing, submissionQueueEntries: UnsafeMutableBufferPointer ) -> UnsafeMutablePointer< @@ -209,7 +209,7 @@ internal func _flushQueue(ring: borrowing SQRing) -> UInt32 { return _getUnconsumedSubmissionCount(ring: ring) } -@usableFromInline @inline(__always) +@inline(__always) internal func _getSubmissionEntry( ring: inout SQRing, submissionQueueEntries: UnsafeMutableBufferPointer ) -> UnsafeMutablePointer< @@ -247,7 +247,7 @@ public struct IORing: ~Copyable { let completionRing: CQRing - @usableFromInline let submissionQueueEntries: UnsafeMutableBufferPointer + let submissionQueueEntries: UnsafeMutableBufferPointer // kept around for unmap / cleanup let ringSize: Int @@ -594,7 +594,7 @@ public struct IORing: ~Copyable { try _submitRequests(ring: submissionRing, ringDescriptor: ringDescriptor) } - @inlinable @inline(__always) + @inline(__always) public mutating func writeRequest(_ request: __owned IORequest) -> Bool { var raw: RawIORequest? = request.makeRawRequest() return _writeRequest( diff --git a/Sources/System/RawIORequest.swift b/Sources/System/RawIORequest.swift index 5a1fd03d..5b884993 100644 --- a/Sources/System/RawIORequest.swift +++ b/Sources/System/RawIORequest.swift @@ -1,9 +1,9 @@ // TODO: investigate @usableFromInline / @_implementationOnly dichotomy @_implementationOnly import CSystem -import struct CSystem.io_uring_sqe +@_implementationOnly import struct CSystem.io_uring_sqe public struct RawIORequest: ~Copyable { - @usableFromInline var rawValue: io_uring_sqe + var rawValue: io_uring_sqe public init() { self.rawValue = io_uring_sqe() From bb03f0fba3c480c3a625c9894b2f5dc32e9885c2 Mon Sep 17 00:00:00 2001 From: David Smith Date: Tue, 11 Feb 2025 22:33:10 +0000 Subject: [PATCH 292/427] Redesign registered resources API --- Sources/System/IORing.swift | 148 ++++++++++--------------- Tests/SystemTests/IORequestTests.swift | 6 +- 2 files changed, 60 insertions(+), 94 deletions(-) diff --git a/Sources/System/IORing.swift b/Sources/System/IORing.swift index 6dff66f1..3305a9bd 100644 --- a/Sources/System/IORing.swift +++ b/Sources/System/IORing.swift @@ -11,6 +11,12 @@ extension UnsafeMutableRawPointer { } } +extension UnsafeMutableRawBufferPointer { + func to_iovec() -> iovec { + iovec(iov_base: baseAddress, iov_len: count) + } +} + // all pointers in this struct reference kernel-visible memory @usableFromInline struct SQRing: ~Copyable { let kernelHead: UnsafePointer> @@ -46,67 +52,17 @@ struct CQRing: ~Copyable { let cqes: UnsafeBufferPointer } -internal final class ResourceManager: @unchecked Sendable { - typealias Resource = T - - struct Resources { - let resourceList: UnsafeMutableBufferPointer - var freeList: [Int] //TODO: bitvector? - } - - let mutex: Mutex - - init(_ res: UnsafeMutableBufferPointer) { - mutex = Mutex( - Resources( - resourceList: res, - freeList: [Int](res.indices) - )) - } - - func getResource() -> IOResource? { - mutex.withLock { resources in - if let index = resources.freeList.popLast() { - return IOResource( - resource: resources.resourceList[index], - index: index, - manager: self - ) - } else { - return nil - } - } - } - - func releaseResource(index: Int) { - mutex.withLock { resources in - resources.freeList.append(index) - } - } -} - -public class IOResource { +public struct IOResource { typealias Resource = T @usableFromInline let resource: T @usableFromInline let index: Int - let manager: ResourceManager internal init( resource: T, - index: Int, - manager: ResourceManager + index: Int ) { self.resource = resource self.index = index - self.manager = manager - } - - func withResource() { - - } - - deinit { - manager.releaseResource(index: self.index) } } @@ -253,8 +209,8 @@ public struct IORing: ~Copyable { let ringSize: Int let ringPtr: UnsafeMutableRawPointer - var registeredFiles: ResourceManager? - var registeredBuffers: ResourceManager? + var _registeredFiles: [UInt32]? + var _registeredBuffers: [iovec]? public init(queueDepth: UInt32) throws { var params = io_uring_params() @@ -517,11 +473,11 @@ public struct IORing: ~Copyable { //TODO: error handling } - public mutating func registerEventFD(ring: inout IORing, _ descriptor: FileDescriptor) throws { + public mutating func registerEventFD(_ descriptor: FileDescriptor) throws { var rawfd = descriptor.rawValue let result = withUnsafePointer(to: &rawfd) { fdptr in return io_uring_register( - ring.ringDescriptor, + ringDescriptor, IORING_REGISTER_EVENTFD, UnsafeMutableRawPointer(mutating: fdptr), 1 @@ -530,9 +486,9 @@ public struct IORing: ~Copyable { try handleRegistrationResult(result) } - public mutating func unregisterEventFD(ring: inout IORing) throws { + public mutating func unregisterEventFD() throws { let result = io_uring_register( - ring.ringDescriptor, + ringDescriptor, IORING_UNREGISTER_EVENTFD, nil, 0 @@ -540,50 +496,64 @@ public struct IORing: ~Copyable { try handleRegistrationResult(result) } - public mutating func registerFiles(count: UInt32) { - guard self.registeredFiles == nil else { fatalError() } - let fileBuf = UnsafeMutableBufferPointer.allocate(capacity: Int(count)) - fileBuf.initialize(repeating: UInt32.max) - io_uring_register( - self.ringDescriptor, - IORING_REGISTER_FILES, - fileBuf.baseAddress!, - count - ) + public mutating func registerFileSlots(count: Int) { + precondition(_registeredFiles == nil) + precondition(count < UInt32.max) + let files = [UInt32](repeating: UInt32.max, count: count) + + let regResult = files.withUnsafeBufferPointer { bPtr in + io_uring_register( + self.ringDescriptor, + IORING_REGISTER_FILES, + UnsafeMutableRawPointer(mutating:bPtr.baseAddress!), + UInt32(truncatingIfNeeded: count) + ) + } + // TODO: error handling - self.registeredFiles = ResourceManager(fileBuf) + _registeredFiles = files } public func unregisterFiles() { fatalError("failed to unregister files") } - public func getFile() -> IORingFileSlot? { - return self.registeredFiles?.getResource() + public var registeredFileSlots: some RandomAccessCollection { + RegisteredResources(resources: _registeredFiles ?? []) } - public mutating func registerBuffers(bufSize: UInt32, count: UInt32) { - let iovecs = UnsafeMutableBufferPointer.allocate(capacity: Int(count)) - let intBufSize = Int(bufSize) - for i in 0..: RandomAccessCollection { + let resources: [T] + + var startIndex: Int { 0 } + var endIndex: Int { resources.endIndex } + init(resources: [T]) { + self.resources = resources + } + subscript(position: Int) -> IOResource { + IOResource(resource: resources[position], index: position) + } } - public func getBuffer() -> IORingBuffer? { - return self.registeredBuffers?.getResource() + public var registeredBuffers: some RandomAccessCollection { + RegisteredResources(resources: _registeredBuffers ?? []) } public func unregisterBuffers() { diff --git a/Tests/SystemTests/IORequestTests.swift b/Tests/SystemTests/IORequestTests.swift index 78be4cf7..e553a546 100644 --- a/Tests/SystemTests/IORequestTests.swift +++ b/Tests/SystemTests/IORequestTests.swift @@ -28,12 +28,8 @@ final class IORequestTests: XCTestCase { } func testOpenatFixedFile() throws { - // TODO: come up with a better way of getting a FileSlot. - let buf = UnsafeMutableBufferPointer.allocate(capacity: 2) - let resmgr = ResourceManager.init(buf) - let pathPtr = UnsafePointer(bitPattern: 0x414141410badf00d)! - let fileSlot = resmgr.getResource()! + let fileSlot: IORingFileSlot = IORingFileSlot(resource: UInt32.max, index: 0) let req = IORequest( opening: pathPtr, in: FileDescriptor(rawValue: -100), From a396967e3a77b780a78b733564137be7a8b4b7dc Mon Sep 17 00:00:00 2001 From: David Smith Date: Tue, 11 Feb 2025 22:38:01 +0000 Subject: [PATCH 293/427] More registration tweaks --- Sources/System/IORing.swift | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Sources/System/IORing.swift b/Sources/System/IORing.swift index 3305a9bd..292303b7 100644 --- a/Sources/System/IORing.swift +++ b/Sources/System/IORing.swift @@ -496,7 +496,7 @@ public struct IORing: ~Copyable { try handleRegistrationResult(result) } - public mutating func registerFileSlots(count: Int) { + public mutating func registerFileSlots(count: Int) -> some RandomAccessCollection { precondition(_registeredFiles == nil) precondition(count < UInt32.max) let files = [UInt32](repeating: UInt32.max, count: count) @@ -512,6 +512,7 @@ public struct IORing: ~Copyable { // TODO: error handling _registeredFiles = files + return registeredFileSlots } public func unregisterFiles() { @@ -522,9 +523,10 @@ public struct IORing: ~Copyable { RegisteredResources(resources: _registeredFiles ?? []) } - public mutating func registerBuffers(_ buffers: UnsafeMutableRawBufferPointer...) { + public mutating func registerBuffers(_ buffers: UnsafeMutableRawBufferPointer...) -> some RandomAccessCollection { precondition(buffers.count < UInt32.max) precondition(_registeredBuffers == nil) + //TODO: check if io_uring has preconditions it needs for the buffers (e.g. alignment) let iovecs = buffers.map { $0.to_iovec() } let regResult = iovecs.withUnsafeBufferPointer { bPtr in io_uring_register( @@ -537,6 +539,7 @@ public struct IORing: ~Copyable { // TODO: error handling _registeredBuffers = iovecs + return registeredBuffers } struct RegisteredResources: RandomAccessCollection { From d4ca4129a033c04efb863827e9178101434c44df Mon Sep 17 00:00:00 2001 From: David Smith Date: Tue, 11 Feb 2025 23:01:33 +0000 Subject: [PATCH 294/427] Some renaming, and implement linked requests --- Sources/System/IORequest.swift | 4 ++-- Sources/System/IORing.swift | 19 +++++++++++++++++-- Sources/System/RawIORequest.swift | 9 +++++++-- Tests/SystemTests/IORingTests.swift | 2 +- 4 files changed, 27 insertions(+), 7 deletions(-) diff --git a/Sources/System/IORequest.swift b/Sources/System/IORequest.swift index 0e1a3b06..f406f5a0 100644 --- a/Sources/System/IORequest.swift +++ b/Sources/System/IORequest.swift @@ -1,7 +1,7 @@ @_implementationOnly import struct CSystem.io_uring_sqe @usableFromInline -internal enum IORequestCore: ~Copyable { +internal enum IORequestCore { case nop // nothing here case openat( atDirectory: FileDescriptor, @@ -118,7 +118,7 @@ internal func makeRawRequest_readWrite_unregistered_slot( return request } -public struct IORequest : ~Copyable { +public struct IORequest { @usableFromInline var core: IORequestCore @inlinable internal consuming func extractCore() -> IORequestCore { diff --git a/Sources/System/IORing.swift b/Sources/System/IORing.swift index 292303b7..fab2f8e4 100644 --- a/Sources/System/IORing.swift +++ b/Sources/System/IORing.swift @@ -567,13 +567,28 @@ public struct IORing: ~Copyable { try _submitRequests(ring: submissionRing, ringDescriptor: ringDescriptor) } - @inline(__always) - public mutating func writeRequest(_ request: __owned IORequest) -> Bool { + public mutating func prepare(request: __owned IORequest) -> Bool { var raw: RawIORequest? = request.makeRawRequest() return _writeRequest( raw.take()!, ring: &submissionRing, submissionQueueEntries: submissionQueueEntries) } + //@inlinable //TODO: make sure the array allocation gets optimized out... + public mutating func prepare(linkedRequests: IORequest...) { + guard linkedRequests.count > 0 else { + return + } + let last = linkedRequests.last! + for req in linkedRequests.dropLast() { + var raw = req.makeRawRequest() + raw.linkToNextRequest() + _writeRequest( + raw, ring: &submissionRing, submissionQueueEntries: submissionQueueEntries) + } + _writeRequest( + last.makeRawRequest(), ring: &submissionRing, submissionQueueEntries: submissionQueueEntries) + } + deinit { munmap(ringPtr, ringSize) munmap( diff --git a/Sources/System/RawIORequest.swift b/Sources/System/RawIORequest.swift index 5b884993..40b72366 100644 --- a/Sources/System/RawIORequest.swift +++ b/Sources/System/RawIORequest.swift @@ -2,6 +2,7 @@ @_implementationOnly import CSystem @_implementationOnly import struct CSystem.io_uring_sqe +//TODO: make this internal public struct RawIORequest: ~Copyable { var rawValue: io_uring_sqe @@ -11,7 +12,7 @@ public struct RawIORequest: ~Copyable { } extension RawIORequest { - public enum Operation: UInt8 { + enum Operation: UInt8 { case nop = 0 case readv = 1 case writev = 2 @@ -53,7 +54,7 @@ extension RawIORequest { public static let skipSuccess = Flags(rawValue: 1 << 6) } - public var operation: Operation { + var operation: Operation { get { Operation(rawValue: rawValue.opcode)! } set { rawValue.opcode = newValue.rawValue } } @@ -63,6 +64,10 @@ extension RawIORequest { set { rawValue.flags = newValue.rawValue } } + public mutating func linkToNextRequest() { + flags = Flags(rawValue: flags.rawValue | Flags.linkRequest.rawValue) + } + public var fileDescriptor: FileDescriptor { get { FileDescriptor(rawValue: rawValue.fd) } set { rawValue.fd = newValue.rawValue } diff --git a/Tests/SystemTests/IORingTests.swift b/Tests/SystemTests/IORingTests.swift index 2c25e2b3..4ae12fcf 100644 --- a/Tests/SystemTests/IORingTests.swift +++ b/Tests/SystemTests/IORingTests.swift @@ -13,7 +13,7 @@ final class IORingTests: XCTestCase { func testNop() throws { var ring = try IORing(queueDepth: 32) - ring.writeRequest(IORequest()) + ring.prepare(request: IORequest()) try ring.submitRequests() let completion = try ring.blockingConsumeCompletion() XCTAssertEqual(completion.result, 0) From 5bfed03c971a5440f8c5e8489466352be8c2f298 Mon Sep 17 00:00:00 2001 From: David Smith Date: Tue, 11 Feb 2025 23:27:03 +0000 Subject: [PATCH 295/427] Switch to static methods for constructing requests --- Sources/System/IORequest.swift | 96 +++++++++++--------------- Tests/SystemTests/IORequestTests.swift | 5 +- Tests/SystemTests/IORingTests.swift | 2 +- 3 files changed, 43 insertions(+), 60 deletions(-) diff --git a/Sources/System/IORequest.swift b/Sources/System/IORequest.swift index f406f5a0..4cdda768 100644 --- a/Sources/System/IORequest.swift +++ b/Sources/System/IORequest.swift @@ -127,127 +127,111 @@ public struct IORequest { } extension IORequest { - public init() { //TODO: why do we have nop? - core = .nop + public static func nop() -> IORequest { + IORequest(core: .nop) } - public init( - reading file: IORingFileSlot, + public static func reading(_ file: IORingFileSlot, into buffer: IORingBuffer, at offset: UInt64 = 0 - ) { - core = .readSlot(file: file, buffer: buffer, offset: offset) + ) -> IORequest { + IORequest(core: .readSlot(file: file, buffer: buffer, offset: offset)) } - public init( - reading file: FileDescriptor, + public static func reading(_ file: FileDescriptor, into buffer: IORingBuffer, at offset: UInt64 = 0 - ) { - core = .read(file: file, buffer: buffer, offset: offset) + ) -> IORequest { + IORequest(core: .read(file: file, buffer: buffer, offset: offset)) } - public init( - reading file: IORingFileSlot, + public static func reading(_ file: IORingFileSlot, into buffer: UnsafeMutableRawBufferPointer, at offset: UInt64 = 0 - ) { - core = .readUnregisteredSlot(file: file, buffer: buffer, offset: offset) + ) -> IORequest { + IORequest(core: .readUnregisteredSlot(file: file, buffer: buffer, offset: offset)) } - public init( - reading file: FileDescriptor, + public static func reading(_ file: FileDescriptor, into buffer: UnsafeMutableRawBufferPointer, at offset: UInt64 = 0 - ) { - core = .readUnregistered(file: file, buffer: buffer, offset: offset) + ) -> IORequest { + IORequest(core: .readUnregistered(file: file, buffer: buffer, offset: offset)) } - public init( - writing buffer: IORingBuffer, + public static func writing(_ buffer: IORingBuffer, into file: IORingFileSlot, at offset: UInt64 = 0 - ) { - core = .writeSlot(file: file, buffer: buffer, offset: offset) + ) -> IORequest { + IORequest(core: .writeSlot(file: file, buffer: buffer, offset: offset)) } - public init( - writing buffer: IORingBuffer, + public static func writing(_ buffer: IORingBuffer, into file: FileDescriptor, at offset: UInt64 = 0 - ) { - core = .write(file: file, buffer: buffer, offset: offset) + ) -> IORequest { + IORequest(core: .write(file: file, buffer: buffer, offset: offset)) } - public init( - writing buffer: UnsafeMutableRawBufferPointer, + public static func writing(_ buffer: UnsafeMutableRawBufferPointer, into file: IORingFileSlot, at offset: UInt64 = 0 - ) { - core = .writeUnregisteredSlot(file: file, buffer: buffer, offset: offset) + ) -> IORequest { + IORequest(core: .writeUnregisteredSlot(file: file, buffer: buffer, offset: offset)) } - public init( - writing buffer: UnsafeMutableRawBufferPointer, + public static func writing(_ buffer: UnsafeMutableRawBufferPointer, into file: FileDescriptor, at offset: UInt64 = 0 - ) { - core = .writeUnregistered(file: file, buffer: buffer, offset: offset) + ) -> IORequest { + IORequest(core: .writeUnregistered(file: file, buffer: buffer, offset: offset)) } - public init( - closing file: FileDescriptor - ) { - core = .close(file) + public static func closing(_ file: FileDescriptor) -> IORequest { + IORequest(core: .close(file)) } - public init( - closing file: IORingFileSlot - ) { - core = .closeSlot(file) + public static func closing(_ file: IORingFileSlot) -> IORequest { + IORequest(core: .closeSlot(file)) } - public init( - opening path: UnsafePointer, + public static func opening(_ path: UnsafePointer, in directory: FileDescriptor, into slot: IORingFileSlot, mode: FileDescriptor.AccessMode, options: FileDescriptor.OpenOptions = FileDescriptor.OpenOptions(), permissions: FilePermissions? = nil - ) { - core = .openatSlot(atDirectory: directory, path: path, mode, options: options, permissions: permissions, intoSlot: slot) + ) -> IORequest { + IORequest(core :.openatSlot(atDirectory: directory, path: path, mode, options: options, permissions: permissions, intoSlot: slot)) } - public init( - opening path: UnsafePointer, + public static func opening(_ path: UnsafePointer, in directory: FileDescriptor, mode: FileDescriptor.AccessMode, options: FileDescriptor.OpenOptions = FileDescriptor.OpenOptions(), permissions: FilePermissions? = nil - ) { - core = .openat(atDirectory: directory, path: path, mode, options: options, permissions: permissions) + ) -> IORequest { + IORequest(core: .openat(atDirectory: directory, path: path, mode, options: options, permissions: permissions)) } - public init( - opening path: FilePath, + public static func opening(_ path: FilePath, in directory: FileDescriptor, into slot: IORingFileSlot, mode: FileDescriptor.AccessMode, options: FileDescriptor.OpenOptions = FileDescriptor.OpenOptions(), permissions: FilePermissions? = nil - ) { + ) -> IORequest { fatalError("Implement me") } - public init( - opening path: FilePath, + public static func opening(_ path: FilePath, in directory: FileDescriptor, mode: FileDescriptor.AccessMode, options: FileDescriptor.OpenOptions = FileDescriptor.OpenOptions(), permissions: FilePermissions? = nil - ) { + ) -> IORequest { fatalError("Implement me") } diff --git a/Tests/SystemTests/IORequestTests.swift b/Tests/SystemTests/IORequestTests.swift index e553a546..f9f95803 100644 --- a/Tests/SystemTests/IORequestTests.swift +++ b/Tests/SystemTests/IORequestTests.swift @@ -19,7 +19,7 @@ func requestBytes(_ request: consuming RawIORequest) -> [UInt8] { // which are known to work correctly. final class IORequestTests: XCTestCase { func testNop() { - let req = IORequest().makeRawRequest() + let req = IORequest.nop().makeRawRequest() let sourceBytes = requestBytes(req) // convenient property of nop: it's all zeros! // for some unknown reason, liburing sets the fd field to -1. @@ -30,8 +30,7 @@ final class IORequestTests: XCTestCase { func testOpenatFixedFile() throws { let pathPtr = UnsafePointer(bitPattern: 0x414141410badf00d)! let fileSlot: IORingFileSlot = IORingFileSlot(resource: UInt32.max, index: 0) - let req = IORequest( - opening: pathPtr, + let req = IORequest.opening(pathPtr, in: FileDescriptor(rawValue: -100), into: fileSlot, mode: .readOnly, diff --git a/Tests/SystemTests/IORingTests.swift b/Tests/SystemTests/IORingTests.swift index 4ae12fcf..6421e0ca 100644 --- a/Tests/SystemTests/IORingTests.swift +++ b/Tests/SystemTests/IORingTests.swift @@ -13,7 +13,7 @@ final class IORingTests: XCTestCase { func testNop() throws { var ring = try IORing(queueDepth: 32) - ring.prepare(request: IORequest()) + ring.prepare(request: IORequest.nop()) try ring.submitRequests() let completion = try ring.blockingConsumeCompletion() XCTAssertEqual(completion.result, 0) From 74366c33418fafe8268d225f56046590ef01d083 Mon Sep 17 00:00:00 2001 From: David Smith Date: Wed, 12 Feb 2025 20:18:51 +0000 Subject: [PATCH 296/427] Improve submit API --- Sources/System/IORing.swift | 15 ++++++++++++--- Tests/SystemTests/IORingTests.swift | 3 +-- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/Sources/System/IORing.swift b/Sources/System/IORing.swift index fab2f8e4..e238faba 100644 --- a/Sources/System/IORing.swift +++ b/Sources/System/IORing.swift @@ -563,7 +563,7 @@ public struct IORing: ~Copyable { fatalError("failed to unregister buffers: TODO") } - public func submitRequests() throws { + public func submitPreparedRequests() throws { try _submitRequests(ring: submissionRing, ringDescriptor: ringDescriptor) } @@ -573,8 +573,7 @@ public struct IORing: ~Copyable { raw.take()!, ring: &submissionRing, submissionQueueEntries: submissionQueueEntries) } - //@inlinable //TODO: make sure the array allocation gets optimized out... - public mutating func prepare(linkedRequests: IORequest...) { + mutating func prepare(linkedRequests: some BidirectionalCollection) { guard linkedRequests.count > 0 else { return } @@ -589,6 +588,16 @@ public struct IORing: ~Copyable { last.makeRawRequest(), ring: &submissionRing, submissionQueueEntries: submissionQueueEntries) } + //@inlinable //TODO: make sure the array allocation gets optimized out... + public mutating func prepare(linkedRequests: IORequest...) { + prepare(linkedRequests: linkedRequests) + } + + public mutating func submit(linkedRequests: IORequest...) throws { + prepare(linkedRequests: linkedRequests) + try submitPreparedRequests() + } + deinit { munmap(ringPtr, ringSize) munmap( diff --git a/Tests/SystemTests/IORingTests.swift b/Tests/SystemTests/IORingTests.swift index 6421e0ca..306516be 100644 --- a/Tests/SystemTests/IORingTests.swift +++ b/Tests/SystemTests/IORingTests.swift @@ -13,8 +13,7 @@ final class IORingTests: XCTestCase { func testNop() throws { var ring = try IORing(queueDepth: 32) - ring.prepare(request: IORequest.nop()) - try ring.submitRequests() + try ring.submit(linkedRequests: IORequest.nop()) let completion = try ring.blockingConsumeCompletion() XCTAssertEqual(completion.result, 0) } From 7f6e673750128d8b9a47934b83e0b0c523921b56 Mon Sep 17 00:00:00 2001 From: David Smith Date: Wed, 12 Feb 2025 23:43:29 +0000 Subject: [PATCH 297/427] Adjust registered resources API --- Sources/System/IORing.swift | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/Sources/System/IORing.swift b/Sources/System/IORing.swift index e238faba..0500be09 100644 --- a/Sources/System/IORing.swift +++ b/Sources/System/IORing.swift @@ -53,7 +53,7 @@ struct CQRing: ~Copyable { } public struct IOResource { - typealias Resource = T + public typealias Resource = T @usableFromInline let resource: T @usableFromInline let index: Int @@ -496,7 +496,7 @@ public struct IORing: ~Copyable { try handleRegistrationResult(result) } - public mutating func registerFileSlots(count: Int) -> some RandomAccessCollection { + public mutating func registerFileSlots(count: Int) -> RegisteredResources { precondition(_registeredFiles == nil) precondition(count < UInt32.max) let files = [UInt32](repeating: UInt32.max, count: count) @@ -519,11 +519,11 @@ public struct IORing: ~Copyable { fatalError("failed to unregister files") } - public var registeredFileSlots: some RandomAccessCollection { + public var registeredFileSlots: RegisteredResources { RegisteredResources(resources: _registeredFiles ?? []) } - public mutating func registerBuffers(_ buffers: UnsafeMutableRawBufferPointer...) -> some RandomAccessCollection { + public mutating func registerBuffers(_ buffers: UnsafeMutableRawBufferPointer...) -> RegisteredResources { precondition(buffers.count < UInt32.max) precondition(_registeredBuffers == nil) //TODO: check if io_uring has preconditions it needs for the buffers (e.g. alignment) @@ -542,20 +542,23 @@ public struct IORing: ~Copyable { return registeredBuffers } - struct RegisteredResources: RandomAccessCollection { + public struct RegisteredResources: RandomAccessCollection { let resources: [T] - var startIndex: Int { 0 } - var endIndex: Int { resources.endIndex } + public var startIndex: Int { 0 } + public var endIndex: Int { resources.endIndex } init(resources: [T]) { self.resources = resources } - subscript(position: Int) -> IOResource { + public subscript(position: Int) -> IOResource { IOResource(resource: resources[position], index: position) } + public subscript(position: Int16) -> IOResource { + IOResource(resource: resources[Int(position)], index: Int(position)) + } } - public var registeredBuffers: some RandomAccessCollection { + public var registeredBuffers: RegisteredResources { RegisteredResources(resources: _registeredBuffers ?? []) } From 0c6ef16ca91cd82ab78a52a5cac83b113ba8e67d Mon Sep 17 00:00:00 2001 From: David Smith Date: Wed, 12 Feb 2025 23:45:18 +0000 Subject: [PATCH 298/427] Fix type --- Sources/System/IORing.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/System/IORing.swift b/Sources/System/IORing.swift index 0500be09..fb0c20aa 100644 --- a/Sources/System/IORing.swift +++ b/Sources/System/IORing.swift @@ -553,7 +553,7 @@ public struct IORing: ~Copyable { public subscript(position: Int) -> IOResource { IOResource(resource: resources[position], index: position) } - public subscript(position: Int16) -> IOResource { + public subscript(position: UInt16) -> IOResource { IOResource(resource: resources[Int(position)], index: Int(position)) } } From a22e5f685866614a027ff4a394e2af71d4b412c8 Mon Sep 17 00:00:00 2001 From: David Smith Date: Thu, 13 Feb 2025 22:09:42 +0000 Subject: [PATCH 299/427] Add a version of registerBuffers that isn't varargs --- Sources/System/IORing.swift | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/Sources/System/IORing.swift b/Sources/System/IORing.swift index fb0c20aa..b7189917 100644 --- a/Sources/System/IORing.swift +++ b/Sources/System/IORing.swift @@ -496,16 +496,18 @@ public struct IORing: ~Copyable { try handleRegistrationResult(result) } - public mutating func registerFileSlots(count: Int) -> RegisteredResources { + public mutating func registerFileSlots(count: Int) -> RegisteredResources< + IORingFileSlot.Resource + > { precondition(_registeredFiles == nil) precondition(count < UInt32.max) - let files = [UInt32](repeating: UInt32.max, count: count) + let files = [UInt32](repeating: UInt32.max, count: count) let regResult = files.withUnsafeBufferPointer { bPtr in io_uring_register( self.ringDescriptor, IORING_REGISTER_FILES, - UnsafeMutableRawPointer(mutating:bPtr.baseAddress!), + UnsafeMutableRawPointer(mutating: bPtr.baseAddress!), UInt32(truncatingIfNeeded: count) ) } @@ -523,7 +525,9 @@ public struct IORing: ~Copyable { RegisteredResources(resources: _registeredFiles ?? []) } - public mutating func registerBuffers(_ buffers: UnsafeMutableRawBufferPointer...) -> RegisteredResources { + public mutating func registerBuffers(_ buffers: some Collection) + -> RegisteredResources + { precondition(buffers.count < UInt32.max) precondition(_registeredBuffers == nil) //TODO: check if io_uring has preconditions it needs for the buffers (e.g. alignment) @@ -542,6 +546,12 @@ public struct IORing: ~Copyable { return registeredBuffers } + public mutating func registerBuffers(_ buffers: UnsafeMutableRawBufferPointer...) + -> RegisteredResources + { + registerBuffers(buffers) + } + public struct RegisteredResources: RandomAccessCollection { let resources: [T] @@ -588,7 +598,8 @@ public struct IORing: ~Copyable { raw, ring: &submissionRing, submissionQueueEntries: submissionQueueEntries) } _writeRequest( - last.makeRawRequest(), ring: &submissionRing, submissionQueueEntries: submissionQueueEntries) + last.makeRawRequest(), ring: &submissionRing, + submissionQueueEntries: submissionQueueEntries) } //@inlinable //TODO: make sure the array allocation gets optimized out... From 6983196ad6f980fde158811e13a828d70b7a93f0 Mon Sep 17 00:00:00 2001 From: David Smith Date: Thu, 13 Feb 2025 23:31:06 +0000 Subject: [PATCH 300/427] Add unlinkAt support --- Sources/System/IORequest.swift | 14 ++++++++++++++ Sources/System/RawIORequest.swift | 1 + 2 files changed, 15 insertions(+) diff --git a/Sources/System/IORequest.swift b/Sources/System/IORequest.swift index 4cdda768..8705f0d6 100644 --- a/Sources/System/IORequest.swift +++ b/Sources/System/IORequest.swift @@ -60,6 +60,10 @@ internal enum IORequestCore { ) case close(FileDescriptor) case closeSlot(IORingFileSlot) + case unlinkAt( + atDirectory: FileDescriptor, + path: UnsafePointer + ) } @inline(__always) @@ -235,6 +239,12 @@ extension IORequest { fatalError("Implement me") } + public static func unlinking(_ path: UnsafePointer, + in directory: FileDescriptor + ) -> IORequest { + IORequest(core: .unlinkAt(atDirectory: directory, path: path)) + } + @inline(__always) public consuming func makeRawRequest() -> RawIORequest { var request = RawIORequest() @@ -293,6 +303,10 @@ extension IORequest { case .closeSlot(let file): request.operation = .close request.rawValue.file_index = UInt32(file.index + 1) + case .unlinkAt(let atDirectory, let path): + request.operation = .unlinkAt + request.fileDescriptor = atDirectory + request.rawValue.addr = UInt64(UInt(bitPattern: path)) } return request } diff --git a/Sources/System/RawIORequest.swift b/Sources/System/RawIORequest.swift index 40b72366..8f1a2b1b 100644 --- a/Sources/System/RawIORequest.swift +++ b/Sources/System/RawIORequest.swift @@ -36,6 +36,7 @@ extension RawIORequest { // ... case openAt2 = 28 // ... + case unlinkAt = 36 } public struct Flags: OptionSet, Hashable, Codable { From 5ba137703c63791b5088ac44ef1ebd2662566d0a Mon Sep 17 00:00:00 2001 From: David Smith Date: Thu, 13 Feb 2025 23:43:19 +0000 Subject: [PATCH 301/427] Dubious approach to this, but I want to try it out a bit --- Sources/System/RawIORequest.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Sources/System/RawIORequest.swift b/Sources/System/RawIORequest.swift index 8f1a2b1b..78f4f6de 100644 --- a/Sources/System/RawIORequest.swift +++ b/Sources/System/RawIORequest.swift @@ -101,6 +101,7 @@ extension RawIORequest { // TODO: cleanup? rawValue.addr = UInt64(Int(bitPattern: newValue.baseAddress!)) rawValue.len = UInt32(exactly: newValue.count)! + rawValue.user_data = rawValue.addr //TODO: this is kind of a hack, but I need to decide how best to get the buffer out on the other side } } From 006464963162f26a722f600b975a5ad5145e7150 Mon Sep 17 00:00:00 2001 From: David Smith Date: Fri, 14 Feb 2025 18:24:09 +0000 Subject: [PATCH 302/427] Turn on single issuer as an experiment --- Sources/System/IORing.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Sources/System/IORing.swift b/Sources/System/IORing.swift index b7189917..5dd53d37 100644 --- a/Sources/System/IORing.swift +++ b/Sources/System/IORing.swift @@ -214,6 +214,7 @@ public struct IORing: ~Copyable { public init(queueDepth: UInt32) throws { var params = io_uring_params() + params.flags = IORING_SETUP_SINGLE_ISSUER //TODO make this configurable ringDescriptor = withUnsafeMutablePointer(to: ¶ms) { return io_uring_setup(queueDepth, $0) From 901f4c80c48dd983ae87b115736f0589d6963b73 Mon Sep 17 00:00:00 2001 From: David Smith Date: Fri, 14 Feb 2025 18:25:46 +0000 Subject: [PATCH 303/427] Revert "Turn on single issuer as an experiment" This reverts commit 006464963162f26a722f600b975a5ad5145e7150. --- Sources/System/IORing.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/Sources/System/IORing.swift b/Sources/System/IORing.swift index 5dd53d37..b7189917 100644 --- a/Sources/System/IORing.swift +++ b/Sources/System/IORing.swift @@ -214,7 +214,6 @@ public struct IORing: ~Copyable { public init(queueDepth: UInt32) throws { var params = io_uring_params() - params.flags = IORING_SETUP_SINGLE_ISSUER //TODO make this configurable ringDescriptor = withUnsafeMutablePointer(to: ¶ms) { return io_uring_setup(queueDepth, $0) From 283e8d6b7b747f082ed2b7c9bd763229729d9d76 Mon Sep 17 00:00:00 2001 From: David Smith Date: Fri, 14 Feb 2025 18:26:31 +0000 Subject: [PATCH 304/427] Reapply "Turn on single issuer as an experiment" This reverts commit 901f4c80c48dd983ae87b115736f0589d6963b73. --- Sources/System/IORing.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Sources/System/IORing.swift b/Sources/System/IORing.swift index b7189917..5dd53d37 100644 --- a/Sources/System/IORing.swift +++ b/Sources/System/IORing.swift @@ -214,6 +214,7 @@ public struct IORing: ~Copyable { public init(queueDepth: UInt32) throws { var params = io_uring_params() + params.flags = IORING_SETUP_SINGLE_ISSUER //TODO make this configurable ringDescriptor = withUnsafeMutablePointer(to: ¶ms) { return io_uring_setup(queueDepth, $0) From d895f2a828f6c6c884057f43f0b14aa7fac10aff Mon Sep 17 00:00:00 2001 From: David Smith Date: Fri, 14 Feb 2025 18:27:17 +0000 Subject: [PATCH 305/427] Revert "Reapply "Turn on single issuer as an experiment"" This reverts commit 283e8d6b7b747f082ed2b7c9bd763229729d9d76. --- Sources/System/IORing.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/Sources/System/IORing.swift b/Sources/System/IORing.swift index 5dd53d37..b7189917 100644 --- a/Sources/System/IORing.swift +++ b/Sources/System/IORing.swift @@ -214,7 +214,6 @@ public struct IORing: ~Copyable { public init(queueDepth: UInt32) throws { var params = io_uring_params() - params.flags = IORING_SETUP_SINGLE_ISSUER //TODO make this configurable ringDescriptor = withUnsafeMutablePointer(to: ¶ms) { return io_uring_setup(queueDepth, $0) From f18ed75481746807ed335731e1af46e62ecdf47f Mon Sep 17 00:00:00 2001 From: xuty Date: Tue, 17 Dec 2024 15:13:15 +0800 Subject: [PATCH 306/427] Use GetTempPathW for better compatibility --- Sources/System/FilePath/FilePathTempWindows.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/System/FilePath/FilePathTempWindows.swift b/Sources/System/FilePath/FilePathTempWindows.swift index 0d97edcb..d6e45f4f 100644 --- a/Sources/System/FilePath/FilePathTempWindows.swift +++ b/Sources/System/FilePath/FilePathTempWindows.swift @@ -17,7 +17,7 @@ internal func _getTemporaryDirectory() throws -> FilePath { capacity: Int(MAX_PATH) + 1) { buffer in - guard GetTempPath2W(DWORD(buffer.count), buffer.baseAddress) != 0 else { + guard GetTempPathW(DWORD(buffer.count), buffer.baseAddress) != 0 else { throw Errno(windowsError: GetLastError()) } From f3b8cc473dfb84705aaaa374d8385794ba4a5efe Mon Sep 17 00:00:00 2001 From: David Smith Date: Fri, 28 Feb 2025 00:45:31 +0000 Subject: [PATCH 307/427] Actually consume events we waited for --- Sources/System/IORing.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Sources/System/IORing.swift b/Sources/System/IORing.swift index b7189917..505f3e02 100644 --- a/Sources/System/IORing.swift +++ b/Sources/System/IORing.swift @@ -376,9 +376,9 @@ public struct IORing: ~Copyable { "fatal error in receiving requests: " + Errno(rawValue: -res).debugDescription ) - while let completion = _tryConsumeCompletion(ring: completionRing) { - try consumer(completion, nil, false) - } + } + while let completion = _tryConsumeCompletion(ring: completionRing) { + try consumer(completion, nil, false) } try consumer(nil, nil, true) } From d338de147879b044c636992b6fe892a4b90ac8fc Mon Sep 17 00:00:00 2001 From: David Smith Date: Fri, 28 Feb 2025 00:49:31 +0000 Subject: [PATCH 308/427] Only get one completion if we asked for one completion --- Sources/System/IORing.swift | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/Sources/System/IORing.swift b/Sources/System/IORing.swift index 505f3e02..d459422a 100644 --- a/Sources/System/IORing.swift +++ b/Sources/System/IORing.swift @@ -336,6 +336,7 @@ public struct IORing: ~Copyable { private func _blockingConsumeCompletionGuts( minimumCount: UInt32, + maximumCount: UInt32, extraArgs: UnsafeMutablePointer? = nil, consumer: (IOCompletion?, IORingError?, Bool) throws -> Void ) rethrows { @@ -343,6 +344,10 @@ public struct IORing: ~Copyable { while let completion = _tryConsumeCompletion(ring: completionRing) { count += 1 try consumer(completion, nil, false) + if count == maximumCount { + try consumer(nil, nil, true) + return + } } if count < minimumCount { @@ -377,8 +382,13 @@ public struct IORing: ~Copyable { + Errno(rawValue: -res).debugDescription ) } + var count = 0 while let completion = _tryConsumeCompletion(ring: completionRing) { + count += 1 try consumer(completion, nil, false) + if count == maximumCount { + break + } } try consumer(nil, nil, true) } @@ -388,7 +398,7 @@ public struct IORing: ~Copyable { extraArgs: UnsafeMutablePointer? = nil ) throws -> IOCompletion { var result: IOCompletion? = nil - try _blockingConsumeCompletionGuts(minimumCount: 1, extraArgs: extraArgs) { + try _blockingConsumeCompletionGuts(minimumCount: 1, maximumCount: 1, extraArgs: extraArgs) { (completion, error, done) in if let error { throw error @@ -440,10 +450,10 @@ public struct IORing: ~Copyable { ts: UInt64(UInt(bitPattern: tsPtr)) ) try _blockingConsumeCompletionGuts( - minimumCount: minimumCount, extraArgs: &args, consumer: consumer) + minimumCount: minimumCount, maximumCount: UInt32.max, extraArgs: &args, consumer: consumer) } } else { - try _blockingConsumeCompletionGuts(minimumCount: minimumCount, consumer: consumer) + try _blockingConsumeCompletionGuts(minimumCount: minimumCount, maximumCount: UInt32.max, consumer: consumer) } } From 5e2467325960b37a53b145fab6efc896ce9b1d6f Mon Sep 17 00:00:00 2001 From: David Smith Date: Mon, 3 Mar 2025 21:53:26 +0000 Subject: [PATCH 309/427] Switch from unsafe pointers to FilePaths, using hacks --- Sources/System/IORequest.swift | 97 +++++++++++++++----------- Sources/System/RawIORequest.swift | 3 +- Tests/SystemTests/IORequestTests.swift | 2 +- 3 files changed, 58 insertions(+), 44 deletions(-) diff --git a/Sources/System/IORequest.swift b/Sources/System/IORequest.swift index 8705f0d6..e09bd895 100644 --- a/Sources/System/IORequest.swift +++ b/Sources/System/IORequest.swift @@ -5,14 +5,14 @@ internal enum IORequestCore { case nop // nothing here case openat( atDirectory: FileDescriptor, - path: UnsafePointer, + path: FilePath, FileDescriptor.AccessMode, options: FileDescriptor.OpenOptions = FileDescriptor.OpenOptions(), permissions: FilePermissions? = nil ) case openatSlot( atDirectory: FileDescriptor, - path: UnsafePointer, + path: FilePath, FileDescriptor.AccessMode, options: FileDescriptor.OpenOptions = FileDescriptor.OpenOptions(), permissions: FilePermissions? = nil, @@ -60,9 +60,9 @@ internal enum IORequestCore { ) case close(FileDescriptor) case closeSlot(IORingFileSlot) - case unlinkAt( + case unlinkAt( atDirectory: FileDescriptor, - path: UnsafePointer + path: FilePath ) } @@ -135,56 +135,64 @@ extension IORequest { IORequest(core: .nop) } - public static func reading(_ file: IORingFileSlot, + public static func reading( + _ file: IORingFileSlot, into buffer: IORingBuffer, at offset: UInt64 = 0 ) -> IORequest { IORequest(core: .readSlot(file: file, buffer: buffer, offset: offset)) } - public static func reading(_ file: FileDescriptor, + public static func reading( + _ file: FileDescriptor, into buffer: IORingBuffer, at offset: UInt64 = 0 ) -> IORequest { IORequest(core: .read(file: file, buffer: buffer, offset: offset)) } - public static func reading(_ file: IORingFileSlot, + public static func reading( + _ file: IORingFileSlot, into buffer: UnsafeMutableRawBufferPointer, at offset: UInt64 = 0 ) -> IORequest { IORequest(core: .readUnregisteredSlot(file: file, buffer: buffer, offset: offset)) } - public static func reading(_ file: FileDescriptor, + public static func reading( + _ file: FileDescriptor, into buffer: UnsafeMutableRawBufferPointer, at offset: UInt64 = 0 ) -> IORequest { IORequest(core: .readUnregistered(file: file, buffer: buffer, offset: offset)) } - public static func writing(_ buffer: IORingBuffer, + public static func writing( + _ buffer: IORingBuffer, into file: IORingFileSlot, at offset: UInt64 = 0 ) -> IORequest { IORequest(core: .writeSlot(file: file, buffer: buffer, offset: offset)) } - public static func writing(_ buffer: IORingBuffer, + public static func writing( + _ buffer: IORingBuffer, into file: FileDescriptor, at offset: UInt64 = 0 ) -> IORequest { IORequest(core: .write(file: file, buffer: buffer, offset: offset)) } - public static func writing(_ buffer: UnsafeMutableRawBufferPointer, + public static func writing( + _ buffer: UnsafeMutableRawBufferPointer, into file: IORingFileSlot, at offset: UInt64 = 0 ) -> IORequest { IORequest(core: .writeUnregisteredSlot(file: file, buffer: buffer, offset: offset)) } - public static func writing(_ buffer: UnsafeMutableRawBufferPointer, + public static func writing( + _ buffer: UnsafeMutableRawBufferPointer, into file: FileDescriptor, at offset: UInt64 = 0 ) -> IORequest { @@ -199,47 +207,35 @@ extension IORequest { IORequest(core: .closeSlot(file)) } - - public static func opening(_ path: UnsafePointer, - in directory: FileDescriptor, - into slot: IORingFileSlot, - mode: FileDescriptor.AccessMode, - options: FileDescriptor.OpenOptions = FileDescriptor.OpenOptions(), - permissions: FilePermissions? = nil - ) -> IORequest { - IORequest(core :.openatSlot(atDirectory: directory, path: path, mode, options: options, permissions: permissions, intoSlot: slot)) - } - - public static func opening(_ path: UnsafePointer, - in directory: FileDescriptor, - mode: FileDescriptor.AccessMode, - options: FileDescriptor.OpenOptions = FileDescriptor.OpenOptions(), - permissions: FilePermissions? = nil - ) -> IORequest { - IORequest(core: .openat(atDirectory: directory, path: path, mode, options: options, permissions: permissions)) - } - - - public static func opening(_ path: FilePath, + public static func opening( + _ path: FilePath, in directory: FileDescriptor, into slot: IORingFileSlot, mode: FileDescriptor.AccessMode, options: FileDescriptor.OpenOptions = FileDescriptor.OpenOptions(), permissions: FilePermissions? = nil ) -> IORequest { - fatalError("Implement me") + IORequest( + core: .openatSlot( + atDirectory: directory, path: path, mode, options: options, + permissions: permissions, intoSlot: slot)) } - public static func opening(_ path: FilePath, + public static func opening( + _ path: FilePath, in directory: FileDescriptor, mode: FileDescriptor.AccessMode, options: FileDescriptor.OpenOptions = FileDescriptor.OpenOptions(), permissions: FilePermissions? = nil ) -> IORequest { - fatalError("Implement me") + IORequest( + core: .openat( + atDirectory: directory, path: path, mode, options: options, permissions: permissions + )) } - public static func unlinking(_ path: UnsafePointer, + public static func unlinking( + _ path: FilePath, in directory: FileDescriptor ) -> IORequest { IORequest(core: .unlinkAt(atDirectory: directory, path: path)) @@ -251,20 +247,31 @@ extension IORequest { switch extractCore() { case .nop: request.operation = .nop - case .openatSlot(let atDirectory, let path, let mode, let options, let permissions, let fileSlot): + case .openatSlot( + let atDirectory, let path, let mode, let options, let permissions, let fileSlot): // TODO: use rawValue less request.operation = .openAt request.fileDescriptor = atDirectory - request.rawValue.addr = UInt64(UInt(bitPattern: path)) + request.rawValue.addr = UInt64( + UInt( + bitPattern: path.withPlatformString { ptr in + ptr //this is unsavory, but we keep it alive by storing path alongside it in the request + })) request.rawValue.open_flags = UInt32(bitPattern: options.rawValue | mode.rawValue) request.rawValue.len = permissions?.rawValue ?? 0 request.rawValue.file_index = UInt32(fileSlot.index + 1) + request.path = path case .openat(let atDirectory, let path, let mode, let options, let permissions): request.operation = .openAt request.fileDescriptor = atDirectory - request.rawValue.addr = UInt64(UInt(bitPattern: path)) + request.rawValue.addr = UInt64( + UInt( + bitPattern: path.withPlatformString { ptr in + ptr //this is unsavory, but we keep it alive by storing path alongside it in the request + })) request.rawValue.open_flags = UInt32(bitPattern: options.rawValue | mode.rawValue) request.rawValue.len = permissions?.rawValue ?? 0 + request.path = path case .write(let file, let buffer, let offset): request.operation = .writeFixed return makeRawRequest_readWrite_registered( @@ -306,7 +313,13 @@ extension IORequest { case .unlinkAt(let atDirectory, let path): request.operation = .unlinkAt request.fileDescriptor = atDirectory - request.rawValue.addr = UInt64(UInt(bitPattern: path)) + request.rawValue.addr = UInt64( + UInt( + bitPattern: path.withPlatformString { ptr in + ptr //this is unsavory, but we keep it alive by storing path alongside it in the request + }) + ) + request.path = path } return request } diff --git a/Sources/System/RawIORequest.swift b/Sources/System/RawIORequest.swift index 78f4f6de..980771cc 100644 --- a/Sources/System/RawIORequest.swift +++ b/Sources/System/RawIORequest.swift @@ -4,7 +4,8 @@ //TODO: make this internal public struct RawIORequest: ~Copyable { - var rawValue: io_uring_sqe + var rawValue: io_uring_sqe + var path: FilePath? //buffer owner for the path pointer that the sqe may have public init() { self.rawValue = io_uring_sqe() diff --git a/Tests/SystemTests/IORequestTests.swift b/Tests/SystemTests/IORequestTests.swift index f9f95803..4aaf7543 100644 --- a/Tests/SystemTests/IORequestTests.swift +++ b/Tests/SystemTests/IORequestTests.swift @@ -30,7 +30,7 @@ final class IORequestTests: XCTestCase { func testOpenatFixedFile() throws { let pathPtr = UnsafePointer(bitPattern: 0x414141410badf00d)! let fileSlot: IORingFileSlot = IORingFileSlot(resource: UInt32.max, index: 0) - let req = IORequest.opening(pathPtr, + let req = IORequest.opening(FilePath(platformString: pathPtr), in: FileDescriptor(rawValue: -100), into: fileSlot, mode: .readOnly, From 49dd7977ca34dc0ea40adcbd031586f4269819bc Mon Sep 17 00:00:00 2001 From: David Smith Date: Mon, 3 Mar 2025 22:07:28 +0000 Subject: [PATCH 310/427] Add a combined "submit and consume" operation --- Sources/System/IORing.swift | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/Sources/System/IORing.swift b/Sources/System/IORing.swift index d459422a..1f223fa8 100644 --- a/Sources/System/IORing.swift +++ b/Sources/System/IORing.swift @@ -450,10 +450,12 @@ public struct IORing: ~Copyable { ts: UInt64(UInt(bitPattern: tsPtr)) ) try _blockingConsumeCompletionGuts( - minimumCount: minimumCount, maximumCount: UInt32.max, extraArgs: &args, consumer: consumer) + minimumCount: minimumCount, maximumCount: UInt32.max, extraArgs: &args, + consumer: consumer) } } else { - try _blockingConsumeCompletionGuts(minimumCount: minimumCount, maximumCount: UInt32.max, consumer: consumer) + try _blockingConsumeCompletionGuts( + minimumCount: minimumCount, maximumCount: UInt32.max, consumer: consumer) } } @@ -590,6 +592,20 @@ public struct IORing: ~Copyable { try _submitRequests(ring: submissionRing, ringDescriptor: ringDescriptor) } + public func submitPreparedRequestsAndConsumeCompletions( + minimumCount: UInt32 = 1, + timeout: Duration? = nil, + consumer: (IOCompletion?, IORingError?, Bool) throws -> Void + ) throws { + //TODO: optimize this to one uring_enter + try submitPreparedRequests() + try blockingConsumeCompletions( + minimumCount: minimumCount, + timeout: timeout, + consumer: consumer + ) + } + public mutating func prepare(request: __owned IORequest) -> Bool { var raw: RawIORequest? = request.makeRawRequest() return _writeRequest( From 91155fd52772b5cb1cf6980461f4429f3301f0e9 Mon Sep 17 00:00:00 2001 From: David Smith Date: Mon, 3 Mar 2025 22:15:32 +0000 Subject: [PATCH 311/427] Plumb error handling through completion consumers --- Sources/System/IORing.swift | 12 ++++++++++-- Sources/System/IORingError.swift | 6 +++++- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/Sources/System/IORing.swift b/Sources/System/IORing.swift index 1f223fa8..4525281a 100644 --- a/Sources/System/IORing.swift +++ b/Sources/System/IORing.swift @@ -343,7 +343,11 @@ public struct IORing: ~Copyable { var count = 0 while let completion = _tryConsumeCompletion(ring: completionRing) { count += 1 - try consumer(completion, nil, false) + if completion.result < 0 { + try consumer(nil, IORingError(completionResult: completion.result), false) + } else { + try consumer(completion, nil, false) + } if count == maximumCount { try consumer(nil, nil, true) return @@ -385,8 +389,12 @@ public struct IORing: ~Copyable { var count = 0 while let completion = _tryConsumeCompletion(ring: completionRing) { count += 1 + if completion.result < 0 { + try consumer(nil, IORingError(completionResult: completion.result), false) + } else { try consumer(completion, nil, false) - if count == maximumCount { + } + if count == maximumCount { break } } diff --git a/Sources/System/IORingError.swift b/Sources/System/IORingError.swift index fda58bcb..fbd70bce 100644 --- a/Sources/System/IORingError.swift +++ b/Sources/System/IORingError.swift @@ -2,5 +2,9 @@ public enum IORingError: Error, Equatable { case missingRequiredFeatures case operationCanceled - case unknown + case unknown(errorCode: Int) + + internal init(completionResult: Int32) { + self = .unknown(errorCode: Int(completionResult)) //TODO, flesh this out + } } From 9ad16c0aae9f005202eab849f1e6278179453238 Mon Sep 17 00:00:00 2001 From: David Smith Date: Mon, 3 Mar 2025 22:37:51 +0000 Subject: [PATCH 312/427] Plumb through userData --- Sources/System/IORequest.swift | 175 +++++++++++++++++++----------- Sources/System/RawIORequest.swift | 1 - 2 files changed, 114 insertions(+), 62 deletions(-) diff --git a/Sources/System/IORequest.swift b/Sources/System/IORequest.swift index e09bd895..7b602060 100644 --- a/Sources/System/IORequest.swift +++ b/Sources/System/IORequest.swift @@ -8,7 +8,8 @@ internal enum IORequestCore { path: FilePath, FileDescriptor.AccessMode, options: FileDescriptor.OpenOptions = FileDescriptor.OpenOptions(), - permissions: FilePermissions? = nil + permissions: FilePermissions? = nil, + userData: UInt64 = 0 ) case openatSlot( atDirectory: FileDescriptor, @@ -16,53 +17,69 @@ internal enum IORequestCore { FileDescriptor.AccessMode, options: FileDescriptor.OpenOptions = FileDescriptor.OpenOptions(), permissions: FilePermissions? = nil, - intoSlot: IORingFileSlot + intoSlot: IORingFileSlot, + userData: UInt64 = 0 ) case read( file: FileDescriptor, buffer: IORingBuffer, - offset: UInt64 = 0 + offset: UInt64 = 0, + userData: UInt64 = 0 ) case readUnregistered( file: FileDescriptor, buffer: UnsafeMutableRawBufferPointer, - offset: UInt64 = 0 + offset: UInt64 = 0, + userData: UInt64 = 0 ) case readSlot( file: IORingFileSlot, buffer: IORingBuffer, - offset: UInt64 = 0 + offset: UInt64 = 0, + userData: UInt64 = 0 ) case readUnregisteredSlot( file: IORingFileSlot, buffer: UnsafeMutableRawBufferPointer, - offset: UInt64 = 0 + offset: UInt64 = 0, + userData: UInt64 = 0 ) case write( file: FileDescriptor, buffer: IORingBuffer, - offset: UInt64 = 0 + offset: UInt64 = 0, + userData: UInt64 = 0 ) case writeUnregistered( file: FileDescriptor, buffer: UnsafeMutableRawBufferPointer, - offset: UInt64 = 0 + offset: UInt64 = 0, + userData: UInt64 = 0 ) case writeSlot( file: IORingFileSlot, buffer: IORingBuffer, - offset: UInt64 = 0 + offset: UInt64 = 0, + userData: UInt64 = 0 ) case writeUnregisteredSlot( file: IORingFileSlot, buffer: UnsafeMutableRawBufferPointer, - offset: UInt64 = 0 + offset: UInt64 = 0, + userData: UInt64 = 0 + ) + case close( + FileDescriptor, + userData: UInt64 = 0 + ) + case closeSlot( + IORingFileSlot, + userData: UInt64 = 0 ) - case close(FileDescriptor) - case closeSlot(IORingFileSlot) case unlinkAt( atDirectory: FileDescriptor, - path: FilePath + path: FilePath, + userData: UInt64 = 0 ) } @@ -71,6 +88,7 @@ internal func makeRawRequest_readWrite_registered( file: FileDescriptor, buffer: IORingBuffer, offset: UInt64, + userData: UInt64 = 0, request: consuming RawIORequest ) -> RawIORequest { request.fileDescriptor = file @@ -85,6 +103,7 @@ internal func makeRawRequest_readWrite_registered_slot( file: IORingFileSlot, buffer: IORingBuffer, offset: UInt64, + userData: UInt64 = 0, request: consuming RawIORequest ) -> RawIORequest { request.rawValue.fd = Int32(exactly: file.index)! @@ -100,6 +119,7 @@ internal func makeRawRequest_readWrite_unregistered( file: FileDescriptor, buffer: UnsafeMutableRawBufferPointer, offset: UInt64, + userData: UInt64 = 0, request: consuming RawIORequest ) -> RawIORequest { request.fileDescriptor = file @@ -113,6 +133,7 @@ internal func makeRawRequest_readWrite_unregistered_slot( file: IORingFileSlot, buffer: UnsafeMutableRawBufferPointer, offset: UInt64, + userData: UInt64 = 0, request: consuming RawIORequest ) -> RawIORequest { request.rawValue.fd = Int32(exactly: file.index)! @@ -131,80 +152,101 @@ public struct IORequest { } extension IORequest { - public static func nop() -> IORequest { + public static func nop(userData: UInt64 = 0) -> IORequest { IORequest(core: .nop) } public static func reading( _ file: IORingFileSlot, into buffer: IORingBuffer, - at offset: UInt64 = 0 + at offset: UInt64 = 0, + userData: UInt64 = 0 ) -> IORequest { - IORequest(core: .readSlot(file: file, buffer: buffer, offset: offset)) + IORequest(core: .readSlot(file: file, buffer: buffer, offset: offset, userData: userData)) } public static func reading( _ file: FileDescriptor, into buffer: IORingBuffer, - at offset: UInt64 = 0 + at offset: UInt64 = 0, + userData: UInt64 = 0 ) -> IORequest { - IORequest(core: .read(file: file, buffer: buffer, offset: offset)) + IORequest(core: .read(file: file, buffer: buffer, offset: offset, userData: userData)) } public static func reading( _ file: IORingFileSlot, into buffer: UnsafeMutableRawBufferPointer, - at offset: UInt64 = 0 + at offset: UInt64 = 0, + userData: UInt64 = 0 ) -> IORequest { - IORequest(core: .readUnregisteredSlot(file: file, buffer: buffer, offset: offset)) + IORequest( + core: .readUnregisteredSlot( + file: file, buffer: buffer, offset: offset, userData: userData)) } public static func reading( _ file: FileDescriptor, into buffer: UnsafeMutableRawBufferPointer, - at offset: UInt64 = 0 + at offset: UInt64 = 0, + userData: UInt64 = 0 ) -> IORequest { - IORequest(core: .readUnregistered(file: file, buffer: buffer, offset: offset)) + IORequest( + core: .readUnregistered(file: file, buffer: buffer, offset: offset, userData: userData)) } public static func writing( _ buffer: IORingBuffer, into file: IORingFileSlot, - at offset: UInt64 = 0 + at offset: UInt64 = 0, + userData: UInt64 = 0 ) -> IORequest { - IORequest(core: .writeSlot(file: file, buffer: buffer, offset: offset)) + IORequest(core: .writeSlot(file: file, buffer: buffer, offset: offset, userData: userData)) } public static func writing( _ buffer: IORingBuffer, into file: FileDescriptor, - at offset: UInt64 = 0 + at offset: UInt64 = 0, + userData: UInt64 = 0 ) -> IORequest { - IORequest(core: .write(file: file, buffer: buffer, offset: offset)) + IORequest(core: .write(file: file, buffer: buffer, offset: offset, userData: userData)) } public static func writing( _ buffer: UnsafeMutableRawBufferPointer, into file: IORingFileSlot, - at offset: UInt64 = 0 + at offset: UInt64 = 0, + userData: UInt64 = 0 ) -> IORequest { - IORequest(core: .writeUnregisteredSlot(file: file, buffer: buffer, offset: offset)) + IORequest( + core: .writeUnregisteredSlot( + file: file, buffer: buffer, offset: offset, userData: userData)) } public static func writing( _ buffer: UnsafeMutableRawBufferPointer, into file: FileDescriptor, - at offset: UInt64 = 0 + at offset: UInt64 = 0, + userData: UInt64 = 0 ) -> IORequest { - IORequest(core: .writeUnregistered(file: file, buffer: buffer, offset: offset)) + IORequest( + core: .writeUnregistered(file: file, buffer: buffer, offset: offset, userData: userData) + ) } - public static func closing(_ file: FileDescriptor) -> IORequest { - IORequest(core: .close(file)) + public static func closing( + _ file: FileDescriptor, + userData: UInt64 = 0 + ) -> IORequest { + IORequest(core: .close(file, userData: userData)) } - public static func closing(_ file: IORingFileSlot) -> IORequest { - IORequest(core: .closeSlot(file)) + public static func closing( + _ file: IORingFileSlot, + userData: UInt64 = 0 + ) -> IORequest { + IORequest(core: .closeSlot(file, userData: userData)) } public static func opening( @@ -213,12 +255,13 @@ extension IORequest { into slot: IORingFileSlot, mode: FileDescriptor.AccessMode, options: FileDescriptor.OpenOptions = FileDescriptor.OpenOptions(), - permissions: FilePermissions? = nil + permissions: FilePermissions? = nil, + userData: UInt64 = 0 ) -> IORequest { IORequest( core: .openatSlot( atDirectory: directory, path: path, mode, options: options, - permissions: permissions, intoSlot: slot)) + permissions: permissions, intoSlot: slot, userData: userData)) } public static func opening( @@ -226,19 +269,22 @@ extension IORequest { in directory: FileDescriptor, mode: FileDescriptor.AccessMode, options: FileDescriptor.OpenOptions = FileDescriptor.OpenOptions(), - permissions: FilePermissions? = nil + permissions: FilePermissions? = nil, + userData: UInt64 = 0 ) -> IORequest { IORequest( core: .openat( - atDirectory: directory, path: path, mode, options: options, permissions: permissions + atDirectory: directory, path: path, mode, options: options, + permissions: permissions, userData: userData )) } public static func unlinking( _ path: FilePath, - in directory: FileDescriptor + in directory: FileDescriptor, + userData: UInt64 = 0 ) -> IORequest { - IORequest(core: .unlinkAt(atDirectory: directory, path: path)) + IORequest(core: .unlinkAt(atDirectory: directory, path: path, userData: userData)) } @inline(__always) @@ -248,7 +294,8 @@ extension IORequest { case .nop: request.operation = .nop case .openatSlot( - let atDirectory, let path, let mode, let options, let permissions, let fileSlot): + let atDirectory, let path, let mode, let options, let permissions, let fileSlot, + let userData): // TODO: use rawValue less request.operation = .openAt request.fileDescriptor = atDirectory @@ -261,7 +308,9 @@ extension IORequest { request.rawValue.len = permissions?.rawValue ?? 0 request.rawValue.file_index = UInt32(fileSlot.index + 1) request.path = path - case .openat(let atDirectory, let path, let mode, let options, let permissions): + request.rawValue.user_data = userData + case .openat( + let atDirectory, let path, let mode, let options, let permissions, let userData): request.operation = .openAt request.fileDescriptor = atDirectory request.rawValue.addr = UInt64( @@ -272,45 +321,48 @@ extension IORequest { request.rawValue.open_flags = UInt32(bitPattern: options.rawValue | mode.rawValue) request.rawValue.len = permissions?.rawValue ?? 0 request.path = path - case .write(let file, let buffer, let offset): + request.rawValue.user_data = userData + case .write(let file, let buffer, let offset, let userData): request.operation = .writeFixed return makeRawRequest_readWrite_registered( - file: file, buffer: buffer, offset: offset, request: request) - case .writeSlot(let file, let buffer, let offset): + file: file, buffer: buffer, offset: offset, userData: userData, request: request) + case .writeSlot(let file, let buffer, let offset, let userData): request.operation = .writeFixed return makeRawRequest_readWrite_registered_slot( - file: file, buffer: buffer, offset: offset, request: request) - case .writeUnregistered(let file, let buffer, let offset): + file: file, buffer: buffer, offset: offset, userData: userData, request: request) + case .writeUnregistered(let file, let buffer, let offset, let userData): request.operation = .write return makeRawRequest_readWrite_unregistered( - file: file, buffer: buffer, offset: offset, request: request) - case .writeUnregisteredSlot(let file, let buffer, let offset): + file: file, buffer: buffer, offset: offset, userData: userData, request: request) + case .writeUnregisteredSlot(let file, let buffer, let offset, let userData): request.operation = .write return makeRawRequest_readWrite_unregistered_slot( - file: file, buffer: buffer, offset: offset, request: request) - case .read(let file, let buffer, let offset): + file: file, buffer: buffer, offset: offset, userData: userData, request: request) + case .read(let file, let buffer, let offset, let userData): request.operation = .readFixed return makeRawRequest_readWrite_registered( - file: file, buffer: buffer, offset: offset, request: request) - case .readSlot(let file, let buffer, let offset): + file: file, buffer: buffer, offset: offset, userData: userData, request: request) + case .readSlot(let file, let buffer, let offset, let userData): request.operation = .readFixed return makeRawRequest_readWrite_registered_slot( - file: file, buffer: buffer, offset: offset, request: request) - case .readUnregistered(let file, let buffer, let offset): + file: file, buffer: buffer, offset: offset, userData: userData, request: request) + case .readUnregistered(let file, let buffer, let offset, let userData): request.operation = .read return makeRawRequest_readWrite_unregistered( - file: file, buffer: buffer, offset: offset, request: request) - case .readUnregisteredSlot(let file, let buffer, let offset): + file: file, buffer: buffer, offset: offset, userData: userData, request: request) + case .readUnregisteredSlot(let file, let buffer, let offset, let userData): request.operation = .read return makeRawRequest_readWrite_unregistered_slot( - file: file, buffer: buffer, offset: offset, request: request) - case .close(let file): + file: file, buffer: buffer, offset: offset, userData: userData, request: request) + case .close(let file, let userData): request.operation = .close request.fileDescriptor = file - case .closeSlot(let file): + request.rawValue.user_data = userData + case .closeSlot(let file, let userData): request.operation = .close request.rawValue.file_index = UInt32(file.index + 1) - case .unlinkAt(let atDirectory, let path): + request.rawValue.user_data = userData + case .unlinkAt(let atDirectory, let path, let userData): request.operation = .unlinkAt request.fileDescriptor = atDirectory request.rawValue.addr = UInt64( @@ -320,6 +372,7 @@ extension IORequest { }) ) request.path = path + request.rawValue.user_data = userData } return request } diff --git a/Sources/System/RawIORequest.swift b/Sources/System/RawIORequest.swift index 980771cc..50c97c61 100644 --- a/Sources/System/RawIORequest.swift +++ b/Sources/System/RawIORequest.swift @@ -102,7 +102,6 @@ extension RawIORequest { // TODO: cleanup? rawValue.addr = UInt64(Int(bitPattern: newValue.baseAddress!)) rawValue.len = UInt32(exactly: newValue.count)! - rawValue.user_data = rawValue.addr //TODO: this is kind of a hack, but I need to decide how best to get the buffer out on the other side } } From 880ec9086e0517cd8a90e1f58588cfcebdcec487 Mon Sep 17 00:00:00 2001 From: David Smith Date: Mon, 3 Mar 2025 22:51:35 +0000 Subject: [PATCH 313/427] Add a pointer convenience for getting the user data --- Sources/System/IOCompletion.swift | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/Sources/System/IOCompletion.swift b/Sources/System/IOCompletion.swift index 1702f9e8..9b41214e 100644 --- a/Sources/System/IOCompletion.swift +++ b/Sources/System/IOCompletion.swift @@ -23,19 +23,25 @@ extension IOCompletion { extension IOCompletion { public var userData: UInt64 { //TODO: naming? get { - return rawValue.user_data + rawValue.user_data + } + } + + public var userPointer: UnsafeRawPointer? { + get { + UnsafeRawPointer(bitPattern: UInt(rawValue.user_data)) } } public var result: Int32 { get { - return rawValue.res + rawValue.res } } public var flags: IOCompletion.Flags { get { - return Flags(rawValue: rawValue.flags & 0x0000FFFF) + Flags(rawValue: rawValue.flags & 0x0000FFFF) } } From 48455a913557acf7e7e5f0b7a209e7b6fa9a2c6a Mon Sep 17 00:00:00 2001 From: David Smith Date: Mon, 3 Mar 2025 23:04:52 +0000 Subject: [PATCH 314/427] Fix plumbing --- Sources/System/IORequest.swift | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Sources/System/IORequest.swift b/Sources/System/IORequest.swift index 7b602060..1eed6edf 100644 --- a/Sources/System/IORequest.swift +++ b/Sources/System/IORequest.swift @@ -95,6 +95,7 @@ internal func makeRawRequest_readWrite_registered( request.buffer = buffer.unsafeBuffer request.rawValue.buf_index = UInt16(exactly: buffer.index)! request.offset = offset + request.rawValue.user_data = userData return request } @@ -111,10 +112,11 @@ internal func makeRawRequest_readWrite_registered_slot( request.buffer = buffer.unsafeBuffer request.rawValue.buf_index = UInt16(exactly: buffer.index)! request.offset = offset + request.rawValue.user_data = userData return request } -@inlinable @inline(__always) +@inline(__always) internal func makeRawRequest_readWrite_unregistered( file: FileDescriptor, buffer: UnsafeMutableRawBufferPointer, @@ -125,6 +127,7 @@ internal func makeRawRequest_readWrite_unregistered( request.fileDescriptor = file request.buffer = buffer request.offset = offset + request.rawValue.user_data = userData return request } @@ -140,6 +143,7 @@ internal func makeRawRequest_readWrite_unregistered_slot( request.flags = .fixedFile request.buffer = buffer request.offset = offset + request.rawValue.user_data = userData return request } From 72c316ba86750e2cd3e223afd8ee698795e4efe0 Mon Sep 17 00:00:00 2001 From: David Smith Date: Mon, 3 Mar 2025 23:59:36 +0000 Subject: [PATCH 315/427] Make completions noncopyable again --- Sources/System/IOCompletion.swift | 3 +-- Sources/System/IORing.swift | 14 +++++++------- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/Sources/System/IOCompletion.swift b/Sources/System/IOCompletion.swift index 9b41214e..ee81797e 100644 --- a/Sources/System/IOCompletion.swift +++ b/Sources/System/IOCompletion.swift @@ -1,7 +1,6 @@ @_implementationOnly import CSystem -//TODO: should be ~Copyable, but requires UnsafeContinuation add ~Copyable support -public struct IOCompletion { +public struct IOCompletion: ~Copyable { let rawValue: io_uring_cqe } diff --git a/Sources/System/IORing.swift b/Sources/System/IORing.swift index 4525281a..79840c1c 100644 --- a/Sources/System/IORing.swift +++ b/Sources/System/IORing.swift @@ -338,7 +338,7 @@ public struct IORing: ~Copyable { minimumCount: UInt32, maximumCount: UInt32, extraArgs: UnsafeMutablePointer? = nil, - consumer: (IOCompletion?, IORingError?, Bool) throws -> Void + consumer: (consuming IOCompletion?, IORingError?, Bool) throws -> Void ) rethrows { var count = 0 while let completion = _tryConsumeCompletion(ring: completionRing) { @@ -407,15 +407,15 @@ public struct IORing: ~Copyable { ) throws -> IOCompletion { var result: IOCompletion? = nil try _blockingConsumeCompletionGuts(minimumCount: 1, maximumCount: 1, extraArgs: extraArgs) { - (completion, error, done) in + (completion: consuming IOCompletion?, error, done) in if let error { throw error } - if let completion { - result = completion + if let completion { + result = consume completion } } - return result.unsafelyUnwrapped + return result.take()! } public func blockingConsumeCompletion( @@ -443,7 +443,7 @@ public struct IORing: ~Copyable { public func blockingConsumeCompletions( minimumCount: UInt32 = 1, timeout: Duration? = nil, - consumer: (IOCompletion?, IORingError?, Bool) throws -> Void + consumer: (consuming IOCompletion?, IORingError?, Bool) throws -> Void ) throws { if let timeout { var ts = __kernel_timespec( @@ -603,7 +603,7 @@ public struct IORing: ~Copyable { public func submitPreparedRequestsAndConsumeCompletions( minimumCount: UInt32 = 1, timeout: Duration? = nil, - consumer: (IOCompletion?, IORingError?, Bool) throws -> Void + consumer: (consuming IOCompletion?, IORingError?, Bool) throws -> Void ) throws { //TODO: optimize this to one uring_enter try submitPreparedRequests() From d6b40a72846b5d1027728ad148bbcdea02315096 Mon Sep 17 00:00:00 2001 From: Mishal Shah Date: Tue, 4 Mar 2025 23:22:34 -0800 Subject: [PATCH 316/427] [CI] Add support for GitHub Actions --- .github/workflows/pull_request.yml | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 .github/workflows/pull_request.yml diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml new file mode 100644 index 00000000..6936d99a --- /dev/null +++ b/.github/workflows/pull_request.yml @@ -0,0 +1,17 @@ +name: Pull request + +on: + pull_request: + types: [opened, reopened, synchronize] + +jobs: + tests: + name: Test + uses: swiftlang/github-workflows/.github/workflows/swift_package_test.yml@main + with: + linux_exclude_swift_versions: '[{"swift_version": "5.8"}]' + soundness: + name: Soundness + uses: swiftlang/github-workflows/.github/workflows/soundness.yml@main + with: + license_header_check_project_name: "Swift.org" From 7fff872441b6acb03bb8922300c09ef5f2afae2b Mon Sep 17 00:00:00 2001 From: David Smith Date: Thu, 6 Mar 2025 12:09:57 -0800 Subject: [PATCH 317/427] Add the draft proposal so I can link to it --- NNNN-swift-system-io-uring.md | 434 ++++++++++++++++++++++++++++++++++ 1 file changed, 434 insertions(+) create mode 100644 NNNN-swift-system-io-uring.md diff --git a/NNNN-swift-system-io-uring.md b/NNNN-swift-system-io-uring.md new file mode 100644 index 00000000..9b0eb1dc --- /dev/null +++ b/NNNN-swift-system-io-uring.md @@ -0,0 +1,434 @@ +# IORing, a Swift System API for io_uring + +* Proposal: [SE-NNNN](NNNN-filename.md) +* Authors: [Lucy Satheesan](https://github.com/oxy), [David Smith](https://github.com/Catfish-Man/) +* Review Manager: TBD +* Status: **Awaiting implementation** +* Implementation: [apple/swift-system#208](https://github.com/apple/swift-system/pull/208) + +## Introduction + +`io_uring` is Linux's solution to asynchronous and batched syscalls, with a particular focus on IO. We propose a low-level Swift API for it in Swift System that could either be used directly by projects with unusual needs, or via intermediaries like Swift NIO, to address scalability and thread pool starvation issues. + +## Motivation + +Up until recently, the overwhelmingly dominant file IO syscalls on major Unix platforms have been synchronous, e.g. `read(2)`. This design is very simple and proved sufficient for many uses for decades, but is less than ideal for Swift's needs in a few major ways: + +1. Requiring an entire OS thread for each concurrent operation imposes significant memory overhead +2. Requiring a separate syscall for each operation imposes significant CPU/time overhead to switch into and out of kernel mode repeatedly. This has been exacerbated in recent years by mitigations for the Spectre family of security exploits increasing the cost of syscalls. +3. Swift's N:M coroutine-on-thread-pool concurrency model assumes that threads will not be blocked. Each thread waiting for a syscall means a CPU core being left idle. In practice systems like NIO that deal in highly concurrent IO have had to work around this by providing their own thread pools. + +Non-file IO (network, pipes, etc…) has been in a somewhat better place with `epoll` and `kqueue` for asynchronously waiting for readability, but syscall overhead remains a significant issue for highly scalable systems. + +With the introduction of `io_uring` in 2019, Linux now has the kernel level tools to address these three problems directly. However, `io_uring` is quite complex and maps poorly into Swift. We expect that by providing a Swift interface to it, we can enable Swift on Linux servers to scale better and be more efficient than it has been in the past. + +## Proposed solution + +`struct IORing: ~Copyable` provides facilities for + +* Registering and unregistering resources (files and buffers), an `io_uring` specific variation on Unix file descriptors that improves their efficiency +* Registering and unregistering eventfds, which allow asynchronous waiting for completions +* Enqueueing IO requests +* Dequeueing IO completions + +`class IOResource` represents, via its two typealiases `IORingFileSlot` and `IORingBuffer`, registered file descriptors and buffers. Ideally we'd express the lifetimes of these as being dependent on the lifetime of the ring, but so far that's proven intractable, so we use a reference type. We expect that the up-front overhead of this should be negligible for larger operations, and smaller or one-shot operations can use non-registered buffers and file descriptors. + +`struct IORequest: ~Copyable` represents an IO operation that can be enqueued for the kernel to execute. It supports a wide variety of operations matching traditional unix file and socket operations. + +IORequest operations are expressed as overloaded static methods on `IORequest`, e.g. `openat` is spelled + +```swift + public static func opening( + _ path: FilePath, + in directory: FileDescriptor, + into slot: IORingFileSlot, + mode: FileDescriptor.AccessMode, + options: FileDescriptor.OpenOptions = FileDescriptor.OpenOptions(), + permissions: FilePermissions? = nil, + context: UInt64 = 0 + ) -> IORequest + + public static func opening( + _ path: FilePath, + in directory: FileDescriptor, + mode: FileDescriptor.AccessMode, + options: FileDescriptor.OpenOptions = FileDescriptor.OpenOptions(), + permissions: FilePermissions? = nil, + context: UInt64 = 0 + ) -> IORequest +``` + +which allows clients to decide whether they want to open the file into a slot on the ring, or have it return a file descriptor via a completion. Similarly, read operations have overloads for "use a buffer from the ring" or "read into this `UnsafeMutableBufferPointer`" + +Multiple `IORequests` can be enqueued on a single `IORing` using the `prepare(…)` family of methods, and then submitted together using `submitPreparedRequests`, allowing for things like "open this file, read its contents, and then close it" to be a single syscall. Conveniences are provided for preparing and submitting requests in one call. + +Since IO operations can execute in parallel or out of order by default, linked chains of operations can be established with `prepare(linkedRequests:…)` and related methods. Separate chains can still execute in parallel, and if an operation early in the chain fails, all subsequent operations will deliver cancellation errors as their completion. + +Already-completed results can be retrieved from the ring using `tryConsumeCompletion`, which never waits but may return nil, or `blockingConsumeCompletion(timeout:)`, which synchronously waits (up to an optional timeout) until an operation completes. There's also a bulk version of `blockingConsumeCompletion`, which may reduce the number of syscalls issued. It takes a closure which will be called repeatedly as completions are available (see Future Directions for potential improvements to this API). + +Since neither polling nor synchronously waiting is optimal in many cases, `IORing` also exposes the ability to register an eventfd (see `man eventfd(2)`), which will become readable when completions are available on the ring. This can then be monitored asynchronously with `epoll`, `kqueue`, or for clients who are linking libdispatch, `DispatchSource`. + +`struct IOCompletion: ~Copyable` represents the result of an IO operation and provides + +* Flags indicating various operation-specific metadata about the now-completed syscall +* The context associated with the operation when it was enqueued, as an `UnsafeRawPointer` or a `UInt64` +* The result of the operation, as an `Int32` with operation-specific meaning +* The error, if one occurred + +Unfortunately the underlying kernel API makes it relatively difficult to determine which `IORequest` led to a given `IOCompletion`, so it's expected that users will need to create this association themselves via the context parameter. + +`IORingError` represents failure of an operation. + +`IORing.Features` describes the supported features of the underlying kernel `IORing` implementation, which can be used to provide graceful reduction in functionality when running on older systems. + +## Detailed design + +```swift +public class IOResource { } +public typealias IORingFileSlot = IOResource +public typealias IORingBuffer = IOResource + +extension IORingBuffer { + public var unsafeBuffer: UnsafeMutableRawBufferPointer +} + +// IORing is intentionally not Sendable, to avoid internal locking overhead +public struct IORing: ~Copyable { + + public init(queueDepth: UInt32) throws(IORingError) + + public mutating func registerEventFD(_ descriptor: FileDescriptor) throws(IORingError) + public mutating func unregisterEventFD(_ descriptor: FileDescriptor) throws(IORingError) + + // An IORing.RegisteredResources is a view into the buffers or files registered with the ring, if any + public struct RegisteredResources: RandomAccessCollection { + public subscript(position: Int) -> IOResource + public subscript(position: UInt16) -> IOResource // This is useful because io_uring likes to use UInt16s as indexes + } + + public mutating func registerFileSlots(count: Int) throws(IORingError) -> RegisteredResources + + public func unregisterFiles() + + public var registeredFileSlots: RegisteredResources + + public mutating func registerBuffers( + _ buffers: some Collection + ) throws(IORingError) -> RegisteredResources + + public mutating func registerBuffers( + _ buffers: UnsafeMutableRawBufferPointer... + ) throws(IORingError) -> RegisteredResources + + public func unregisterBuffers() + + public var registeredBuffers: RegisteredResources + + public func prepare(requests: IORequest...) + public func prepare(linkedRequests: IORequest...) + + public func submitPreparedRequests(timeout: Duration? = nil) throws(IORingError) + public func submit(requests: IORequest..., timeout: Duration? = nil) throws(IORingError) + public func submit(linkedRequests: IORequest..., timeout: Duration? = nil) throws(IORingError) + + public func submitPreparedRequests() throws(IORingError) + public func submitPreparedRequestsAndWait(timeout: Duration? = nil) throws(IORingError) + + public func submitPreparedRequestsAndConsumeCompletions( + minimumCount: UInt32 = 1, + timeout: Duration? = nil, + consumer: (consuming IOCompletion?, IORingError?, Bool) throws(E) -> Void + ) throws(E) + + public func blockingConsumeCompletion( + timeout: Duration? = nil + ) throws(IORingError) -> IOCompletion + + public func blockingConsumeCompletions( + minimumCount: UInt32 = 1, + timeout: Duration? = nil, + consumer: (consuming IOCompletion?, IORingError?, Bool) throws(E) -> Void + ) throws(E) + + public func tryConsumeCompletion() -> IOCompletion? + + public struct Features { + //IORING_FEAT_SINGLE_MMAP is handled internally + public var nonDroppingCompletions: Bool //IORING_FEAT_NODROP + public var stableSubmissions: Bool //IORING_FEAT_SUBMIT_STABLE + public var currentFilePosition: Bool //IORING_FEAT_RW_CUR_POS + public var assumingTaskCredentials: Bool //IORING_FEAT_CUR_PERSONALITY + public var fastPolling: Bool //IORING_FEAT_FAST_POLL + public var epoll32BitFlags: Bool //IORING_FEAT_POLL_32BITS + public var pollNonFixedFiles: Bool //IORING_FEAT_SQPOLL_NONFIXED + public var extendedArguments: Bool //IORING_FEAT_EXT_ARG + public var nativeWorkers: Bool //IORING_FEAT_NATIVE_WORKERS + public var resourceTags: Bool //IORING_FEAT_RSRC_TAGS + public var allowsSkippingSuccessfulCompletions: Bool //IORING_FEAT_CQE_SKIP + public var improvedLinkedFiles: Bool //IORING_FEAT_LINKED_FILE + public var registerRegisteredRings: Bool //IORING_FEAT_REG_REG_RING + public var minimumTimeout: Bool //IORING_FEAT_MIN_TIMEOUT + public var bundledSendReceive: Bool //IORING_FEAT_RECVSEND_BUNDLE + } + public static var supportedFeatures: Features +} + +public struct IORequest: ~Copyable { + public static func nop(context: UInt64 = 0) -> IORequest + + // overloads for each combination of registered vs unregistered buffer/descriptor + // Read + public static func reading( + _ file: IORingFileSlot, + into buffer: IORingBuffer, + at offset: UInt64 = 0, + context: UInt64 = 0 + ) -> IORequest + + public static func reading( + _ file: FileDescriptor, + into buffer: IORingBuffer, + at offset: UInt64 = 0, + context: UInt64 = 0 + ) -> IORequest + + public static func reading( + _ file: IORingFileSlot, + into buffer: UnsafeMutableRawBufferPointer, + at offset: UInt64 = 0, + context: UInt64 = 0 + ) -> IORequest + + public static func reading( + _ file: FileDescriptor, + into buffer: UnsafeMutableRawBufferPointer, + at offset: UInt64 = 0, + context: UInt64 = 0 + ) -> IORequest + + // Write + public static func writing( + _ buffer: IORingBuffer, + into file: IORingFileSlot, + at offset: UInt64 = 0, + context: UInt64 = 0 + ) -> IORequest + + public static func writing( + _ buffer: IORingBuffer, + into file: FileDescriptor, + at offset: UInt64 = 0, + context: UInt64 = 0 + ) -> IORequest + + public static func writing( + _ buffer: UnsafeMutableRawBufferPointer, + into file: IORingFileSlot, + at offset: UInt64 = 0, + context: UInt64 = 0 + ) -> IORequest + + public static func writing( + _ buffer: UnsafeMutableRawBufferPointer, + into file: FileDescriptor, + at offset: UInt64 = 0, + context: UInt64 = 0 + ) -> IORequest + + // Close + public static func closing( + _ file: FileDescriptor, + context: UInt64 = 0 + ) -> IORequest + + public static func closing( + _ file: IORingFileSlot, + context: UInt64 = 0 + ) -> IORequest + + // Open At + public static func opening( + _ path: FilePath, + in directory: FileDescriptor, + into slot: IORingFileSlot, + mode: FileDescriptor.AccessMode, + options: FileDescriptor.OpenOptions = FileDescriptor.OpenOptions(), + permissions: FilePermissions? = nil, + context: UInt64 = 0 + ) -> IORequest + + public static func opening( + _ path: FilePath, + in directory: FileDescriptor, + mode: FileDescriptor.AccessMode, + options: FileDescriptor.OpenOptions = FileDescriptor.OpenOptions(), + permissions: FilePermissions? = nil, + context: UInt64 = 0 + ) -> IORequest + + public static func unlinking( + _ path: FilePath, + in directory: FileDescriptor, + context: UInt64 = 0 + ) -> IORequest + + // Other operations follow in the same pattern +} + +public struct IOCompletion { + + public struct Flags: OptionSet, Hashable, Codable { + public let rawValue: UInt32 + + public init(rawValue: UInt32) + + public static let moreCompletions: Flags + public static let socketNotEmpty: Flags + public static let isNotificationEvent: Flags + } + + //These are both the same value, but having both eliminates some ugly casts in client code + public var context: UInt64 + public var contextPointer: UnsafeRawPointer + + public var result: Int32 + + public var error: IORingError? // Convenience wrapper over `result` + + public var flags: Flags +} + +public struct IORingError: Error, Equatable { + static var missingRequiredFeatures: IORingError + static var operationCanceled: IORingError + static var timedOut: IORingError + static var resourceRegistrationFailed: IORingError + // Other error values to be filled out as the set of supported operations expands in the future + static var unknown: IORingError(errorCode: Int) +} + +``` + +## Usage Examples + +### Blocking + +```swift +let ring = try IORing(queueDepth: 2) + +//Make space on the ring for our file (this is optional, but improves performance with repeated use) +let file = ring.registerFiles(count: 1)[0] + +var statInfo = Glibc.stat() // System doesn't have an abstraction for stat() right now +// Build our requests to open the file and find out how big it is +ring.prepare(linkedRequests: + .opening(path, + in: parentDirectory, + into: file, + mode: mode, + options: openOptions, + permissions: nil + ), + .readingMetadataOf(file, + into: &statInfo + ) +) +//batch submit 2 syscalls in 1! +try ring.submitPreparedRequestsAndConsumeCompletions(minimumCount: 2) { (completion: consuming IOCompletion?, error, done) in + if let error { + throw error //or other error handling as desired + } +} + +// We could register our buffer with the ring too, but we're only using it once +let buffer = UnsafeMutableRawBufferPointer.allocate(Int(statInfo.st_size)) + +// Build our requests to read the file and close it +ring.prepare(linkedRequests: + .reading(file, + into: buffer + ), + .closing(file) +) + +//batch submit 2 syscalls in 1! +try ring.submitPreparedRequestsAndConsumeCompletions(minimumCount: 2) { (completion: consuming IOCompletion?, error, done) in + if let error { + throw error //or other error handling as desired + } +} + +processBuffer(buffer) +``` + +### Using libdispatch to wait for the read asynchronously + +```swift +//Initial setup as above up through creating buffer, omitted for brevity + +//Make the read request with a context so we can get the buffer out of it in the completion handler +… +.reading(file, into: buffer, context: UInt64(buffer.baseAddress!)) +… + +// Make an eventfd and register it with the ring +let eventfd = eventfd(0, 0) +ring.registerEventFD(eventfd) + +// Make a read source to monitor the eventfd for readability +let readabilityMonitor = DispatchSource.makeReadSource(fileDescriptor: eventfd) +readabilityMonitor.setEventHandler { + let completion = ring.blockingConsumeCompletion() + if let error = completion.error { + //handle failure to read the file + } + processBuffer(completion.contextPointer) +} +readabilityMonitor.activate() + +ring.submitPreparedRequests //note, not "AndConsumeCompletions" this time +``` + +## Source compatibility + +This is an all-new API in Swift System, so has no backwards compatibility implications. Of note, though, this API is only available on Linux. + +## ABI compatibility + +Swift on Linux does not have a stable ABI, and we will likely take advantage of this to evolve IORing as compiler support improves, as described in Future Directions. + +## Implications on adoption + +This feature is intrinsically linked to Linux kernel support, so constrains the deployment target of anything that adopts it to newer kernels. Exactly which features of the evolving io_uring syscall surface area we need is under consideration. + +## Future directions + +* While most Swift users on Darwin are not limited by IO scalability issues, the thread pool considerations still make introducing something similar to this appealing if and when the relevant OS support is available. We should attempt to the best of our ability to not design this in a way that's gratuitously incompatible with non-Linux OSs, although Swift System does not attempt to have an API that's identical on all platforms. +* The set of syscalls covered by `io_uring` has grown significantly and is still growing. We should leave room for supporting additional operations in the future. +* Once same-element requirements and pack counts as integer generic arguments are supported by the compiler, we should consider adding something along the lines of the following to allow preparing, submitting, and waiting for an entire set of operations at once: + +``` +func submitLinkedRequestsAndWait( + _ requests: repeat each Request +) where Request == IORequest + -> InlineArray<(repeat each Request).count, IOCompletion> +``` +* Once mutable borrows are supported, we should consider replacing the closure-taking bulk completion APIs (e.g. `blockingConsumeCompletions(…)`) with ones that return a sequence of completions instead +* We should consider making more types noncopyable as compiler support improves +* liburing has a "peek next completion" operation that doesn't consume it, and then a "mark consumed" operation. We may want to add something similar +* liburing has support for operations allocating their own buffers and returning them via the completion, we may want to support this +* We may want to provide API for asynchronously waiting, rather than just exposing the eventfd to let people roll their own async waits. Doing this really well has *considerable* implications for the concurrency runtime though. +* We should almost certainly expose API for more of the configuration options in `io_uring_setup` +* The API for feature probing is functional but not especially nice. Finding a better way to present that concept would be desirable. + +## Alternatives considered + +* We could use a NIO-style separate thread pool, but we believe `io_uring` is likely a better option for scalability. We may still want to provide a thread-pool backed version as an option, because many Linux systems currently disable `io_uring` due to security concerns. +* We could multiplex all IO onto a single actor as `AsyncBytes` currently does, but this has a number of downsides that make it entirely unsuitable to server usage. Most notably, it eliminates IO parallelism entirely. +* Using POSIX AIO instead of or as well as io_uring would greatly increase our ability to support older kernels and other Unix systems, but it has well-documented performance and usability issues that have prevented its adoption elsewhere, and apply just as much to Swift. +* Earlier versions of this proposal had higher level "managed" abstractions over IORing. These have been removed due to lack of interest from clients, but could be added back later if needed. +* I considered making any or all of `IORingError`, `IOCompletion`, and `IORequest` nested struct declarations inside `IORing`. The main reason I haven't done so is I was a little concerned about the ambiguity of having a type called `Error`. I'd be particularly interested in feedback on this choice. + +## Acknowledgments + +The NIO team, in particular Cory Benfield and Franz Busch, have provided invaluable feedback and direction on this project. From 2ac80d6f4e53d86c9924eacdba920e6c176932b1 Mon Sep 17 00:00:00 2001 From: Mishal Shah Date: Sun, 9 Mar 2025 18:29:05 -0700 Subject: [PATCH 318/427] Disable a few checks --- .github/workflows/pull_request.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index 6936d99a..6ab954b5 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -10,8 +10,16 @@ jobs: uses: swiftlang/github-workflows/.github/workflows/swift_package_test.yml@main with: linux_exclude_swift_versions: '[{"swift_version": "5.8"}]' + # https://github.com/apple/swift-system/issues/223 + enable_windows_checks: false soundness: name: Soundness uses: swiftlang/github-workflows/.github/workflows/soundness.yml@main with: license_header_check_project_name: "Swift.org" + # https://github.com/apple/swift-system/issues/224 + docs_check_enabled: false + unacceptable_language_check_enabled: false + license_header_check_enabled: false + format_check_enabled: false + python_lint_check_enabled: false From 009b07e140205b3ec1521dfa488ac7a8e8f015d7 Mon Sep 17 00:00:00 2001 From: Marc Prud'hommeaux Date: Thu, 13 Mar 2025 08:59:59 -0400 Subject: [PATCH 319/427] Add Android imports to tests --- Tests/SystemTests/FileOperationsTest.swift | 3 +++ Tests/SystemTests/FileTypesTest.swift | 3 +++ 2 files changed, 6 insertions(+) diff --git a/Tests/SystemTests/FileOperationsTest.swift b/Tests/SystemTests/FileOperationsTest.swift index ed05dcf4..18adde37 100644 --- a/Tests/SystemTests/FileOperationsTest.swift +++ b/Tests/SystemTests/FileOperationsTest.swift @@ -14,6 +14,9 @@ import XCTest #else @testable import System #endif +#if canImport(Android) +import Android +#endif @available(/*System 0.0.1: macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0*/iOS 8, *) final class FileOperationsTest: XCTestCase { diff --git a/Tests/SystemTests/FileTypesTest.swift b/Tests/SystemTests/FileTypesTest.swift index 58ceac1b..5258709a 100644 --- a/Tests/SystemTests/FileTypesTest.swift +++ b/Tests/SystemTests/FileTypesTest.swift @@ -14,6 +14,9 @@ import SystemPackage #else import System #endif +#if canImport(Android) +import Android +#endif @available(/*System 0.0.1: macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0*/iOS 8, *) final class FileDescriptorTest: XCTestCase { From 0e652c4d25adfda3e40e1001357261cfabbc93ef Mon Sep 17 00:00:00 2001 From: David Smith Date: Fri, 4 Apr 2025 16:42:33 -0700 Subject: [PATCH 320/427] Naming updates, cancellation support, IOResources are structs now, other editorial updates --- NNNN-swift-system-io-uring.md | 117 ++++++++++++++++++++++------------ 1 file changed, 77 insertions(+), 40 deletions(-) diff --git a/NNNN-swift-system-io-uring.md b/NNNN-swift-system-io-uring.md index 9b0eb1dc..5c08588b 100644 --- a/NNNN-swift-system-io-uring.md +++ b/NNNN-swift-system-io-uring.md @@ -15,7 +15,7 @@ Up until recently, the overwhelmingly dominant file IO syscalls on major Unix platforms have been synchronous, e.g. `read(2)`. This design is very simple and proved sufficient for many uses for decades, but is less than ideal for Swift's needs in a few major ways: 1. Requiring an entire OS thread for each concurrent operation imposes significant memory overhead -2. Requiring a separate syscall for each operation imposes significant CPU/time overhead to switch into and out of kernel mode repeatedly. This has been exacerbated in recent years by mitigations for the Spectre family of security exploits increasing the cost of syscalls. +2. Requiring a separate syscall for each operation imposes significant CPU/time overhead to switch into and out of kernel mode repeatedly. This has been exacerbated in recent years by mitigations for the Meltdown family of security exploits increasing the cost of syscalls. 3. Swift's N:M coroutine-on-thread-pool concurrency model assumes that threads will not be blocked. Each thread waiting for a syscall means a CPU core being left idle. In practice systems like NIO that deal in highly concurrent IO have had to work around this by providing their own thread pools. Non-file IO (network, pipes, etc…) has been in a somewhat better place with `epoll` and `kqueue` for asynchronously waiting for readability, but syscall overhead remains a significant issue for highly scalable systems. @@ -24,6 +24,8 @@ With the introduction of `io_uring` in 2019, Linux now has the kernel level tool ## Proposed solution +We propose a *low level, unopinionated* Swift interface for io_uring on Linux (see Future Directions for discussion of possible more abstract interfaces). + `struct IORing: ~Copyable` provides facilities for * Registering and unregistering resources (files and buffers), an `io_uring` specific variation on Unix file descriptors that improves their efficiency @@ -31,14 +33,14 @@ With the introduction of `io_uring` in 2019, Linux now has the kernel level tool * Enqueueing IO requests * Dequeueing IO completions -`class IOResource` represents, via its two typealiases `IORingFileSlot` and `IORingBuffer`, registered file descriptors and buffers. Ideally we'd express the lifetimes of these as being dependent on the lifetime of the ring, but so far that's proven intractable, so we use a reference type. We expect that the up-front overhead of this should be negligible for larger operations, and smaller or one-shot operations can use non-registered buffers and file descriptors. +`struct IOResource` represents, via its two typealiases `IORingFileSlot` and `IORingBuffer`, registered file descriptors and buffers. `struct IORequest: ~Copyable` represents an IO operation that can be enqueued for the kernel to execute. It supports a wide variety of operations matching traditional unix file and socket operations. IORequest operations are expressed as overloaded static methods on `IORequest`, e.g. `openat` is spelled ```swift - public static func opening( + public static func open( _ path: FilePath, in directory: FileDescriptor, into slot: IORingFileSlot, @@ -48,7 +50,7 @@ IORequest operations are expressed as overloaded static methods on `IORequest`, context: UInt64 = 0 ) -> IORequest - public static func opening( + public static func open( _ path: FilePath, in directory: FileDescriptor, mode: FileDescriptor.AccessMode, @@ -84,7 +86,7 @@ Unfortunately the underlying kernel API makes it relatively difficult to determi ## Detailed design ```swift -public class IOResource { } +public struct IOResource { } public typealias IORingFileSlot = IOResource public typealias IORingBuffer = IOResource @@ -152,23 +154,27 @@ public struct IORing: ~Copyable { public func tryConsumeCompletion() -> IOCompletion? - public struct Features { + public struct Features: OptionSet { + let rawValue: UInt32 + + public init(rawValue: UInt32) + //IORING_FEAT_SINGLE_MMAP is handled internally - public var nonDroppingCompletions: Bool //IORING_FEAT_NODROP - public var stableSubmissions: Bool //IORING_FEAT_SUBMIT_STABLE - public var currentFilePosition: Bool //IORING_FEAT_RW_CUR_POS - public var assumingTaskCredentials: Bool //IORING_FEAT_CUR_PERSONALITY - public var fastPolling: Bool //IORING_FEAT_FAST_POLL - public var epoll32BitFlags: Bool //IORING_FEAT_POLL_32BITS - public var pollNonFixedFiles: Bool //IORING_FEAT_SQPOLL_NONFIXED - public var extendedArguments: Bool //IORING_FEAT_EXT_ARG - public var nativeWorkers: Bool //IORING_FEAT_NATIVE_WORKERS - public var resourceTags: Bool //IORING_FEAT_RSRC_TAGS - public var allowsSkippingSuccessfulCompletions: Bool //IORING_FEAT_CQE_SKIP - public var improvedLinkedFiles: Bool //IORING_FEAT_LINKED_FILE - public var registerRegisteredRings: Bool //IORING_FEAT_REG_REG_RING - public var minimumTimeout: Bool //IORING_FEAT_MIN_TIMEOUT - public var bundledSendReceive: Bool //IORING_FEAT_RECVSEND_BUNDLE + public static let nonDroppingCompletions: Bool //IORING_FEAT_NODROP + public static let stableSubmissions: Bool //IORING_FEAT_SUBMIT_STABLE + public static let currentFilePosition: Bool //IORING_FEAT_RW_CUR_POS + public static let assumingTaskCredentials: Bool //IORING_FEAT_CUR_PERSONALITY + public static let fastPolling: Bool //IORING_FEAT_FAST_POLL + public static let epoll32BitFlags: Bool //IORING_FEAT_POLL_32BITS + public static let pollNonFixedFiles: Bool //IORING_FEAT_SQPOLL_NONFIXED + public static let extendedArguments: Bool //IORING_FEAT_EXT_ARG + public static let nativeWorkers: Bool //IORING_FEAT_NATIVE_WORKERS + public static let resourceTags: Bool //IORING_FEAT_RSRC_TAGS + public static let allowsSkippingSuccessfulCompletions: Bool //IORING_FEAT_CQE_SKIP + public static let improvedLinkedFiles: Bool //IORING_FEAT_LINKED_FILE + public static let registerRegisteredRings: Bool //IORING_FEAT_REG_REG_RING + public static let minimumTimeout: Bool //IORING_FEAT_MIN_TIMEOUT + public static let bundledSendReceive: Bool //IORING_FEAT_RECVSEND_BUNDLE } public static var supportedFeatures: Features } @@ -178,28 +184,28 @@ public struct IORequest: ~Copyable { // overloads for each combination of registered vs unregistered buffer/descriptor // Read - public static func reading( + public static func read( _ file: IORingFileSlot, into buffer: IORingBuffer, at offset: UInt64 = 0, context: UInt64 = 0 ) -> IORequest - public static func reading( + public static func read( _ file: FileDescriptor, into buffer: IORingBuffer, at offset: UInt64 = 0, context: UInt64 = 0 ) -> IORequest - public static func reading( + public static func read( _ file: IORingFileSlot, into buffer: UnsafeMutableRawBufferPointer, at offset: UInt64 = 0, context: UInt64 = 0 ) -> IORequest - public static func reading( + public static func read( _ file: FileDescriptor, into buffer: UnsafeMutableRawBufferPointer, at offset: UInt64 = 0, @@ -207,28 +213,28 @@ public struct IORequest: ~Copyable { ) -> IORequest // Write - public static func writing( + public static func write( _ buffer: IORingBuffer, into file: IORingFileSlot, at offset: UInt64 = 0, context: UInt64 = 0 ) -> IORequest - public static func writing( + public static func write( _ buffer: IORingBuffer, into file: FileDescriptor, at offset: UInt64 = 0, context: UInt64 = 0 ) -> IORequest - public static func writing( + public static func write( _ buffer: UnsafeMutableRawBufferPointer, into file: IORingFileSlot, at offset: UInt64 = 0, context: UInt64 = 0 ) -> IORequest - public static func writing( + public static func write( _ buffer: UnsafeMutableRawBufferPointer, into file: FileDescriptor, at offset: UInt64 = 0, @@ -236,18 +242,18 @@ public struct IORequest: ~Copyable { ) -> IORequest // Close - public static func closing( + public static func close( _ file: FileDescriptor, context: UInt64 = 0 ) -> IORequest - public static func closing( + public static func close( _ file: IORingFileSlot, context: UInt64 = 0 ) -> IORequest // Open At - public static func opening( + public static func open( _ path: FilePath, in directory: FileDescriptor, into slot: IORingFileSlot, @@ -257,7 +263,7 @@ public struct IORequest: ~Copyable { context: UInt64 = 0 ) -> IORequest - public static func opening( + public static func open( _ path: FilePath, in directory: FileDescriptor, mode: FileDescriptor.AccessMode, @@ -266,12 +272,42 @@ public struct IORequest: ~Copyable { context: UInt64 = 0 ) -> IORequest - public static func unlinking( + public static func unlink( _ path: FilePath, in directory: FileDescriptor, context: UInt64 = 0 ) -> IORequest + // Cancel + + public enum CancellationMatch { + case all + case first + } + + public static func cancel( + _ matchAll: CancellationMatch, + matchingContext: UInt64, + context: UInt64 + ) -> IORequest + + public static func cancel( + _ matchAll: CancellationMatch, + matchingFileDescriptor: FileDescriptor, + context: UInt64 + ) -> IORequest + + public static func cancel( + _ matchAll: CancellationMatch, + matchingRegisteredFileDescriptorAtIndex: Int, + context: UInt64 + ) -> IORequest + + public static func cancel( + _ matchAll: CancellationMatch, + context: UInt64 + ) -> IORequest + // Other operations follow in the same pattern } @@ -322,14 +358,14 @@ let file = ring.registerFiles(count: 1)[0] var statInfo = Glibc.stat() // System doesn't have an abstraction for stat() right now // Build our requests to open the file and find out how big it is ring.prepare(linkedRequests: - .opening(path, + .open(path, in: parentDirectory, into: file, mode: mode, options: openOptions, permissions: nil ), - .readingMetadataOf(file, + .stat(file, into: &statInfo ) ) @@ -345,10 +381,10 @@ let buffer = UnsafeMutableRawBufferPointer.allocate(Int(statInfo.st_size)) // Build our requests to read the file and close it ring.prepare(linkedRequests: - .reading(file, + .read(file, into: buffer ), - .closing(file) + .close(file) ) //batch submit 2 syscalls in 1! @@ -368,7 +404,7 @@ processBuffer(buffer) //Make the read request with a context so we can get the buffer out of it in the completion handler … -.reading(file, into: buffer, context: UInt64(buffer.baseAddress!)) +.read(file, into: buffer, context: UInt64(buffer.baseAddress!)) … // Make an eventfd and register it with the ring @@ -419,7 +455,7 @@ func submitLinkedRequestsAndWait( * liburing has support for operations allocating their own buffers and returning them via the completion, we may want to support this * We may want to provide API for asynchronously waiting, rather than just exposing the eventfd to let people roll their own async waits. Doing this really well has *considerable* implications for the concurrency runtime though. * We should almost certainly expose API for more of the configuration options in `io_uring_setup` -* The API for feature probing is functional but not especially nice. Finding a better way to present that concept would be desirable. +* Stronger safety guarantees around cancellation and resource lifetimes (e.g. as described in https://without.boats/blog/io-uring/) would be very welcome, but require an API that is much more strongly opinionated about how io_uring is used. A future higher level abstraction focused on the goal of being "an async IO API for Swift" rather than "a Swifty interface to io_uring" seems like a good place for that. ## Alternatives considered @@ -428,6 +464,7 @@ func submitLinkedRequestsAndWait( * Using POSIX AIO instead of or as well as io_uring would greatly increase our ability to support older kernels and other Unix systems, but it has well-documented performance and usability issues that have prevented its adoption elsewhere, and apply just as much to Swift. * Earlier versions of this proposal had higher level "managed" abstractions over IORing. These have been removed due to lack of interest from clients, but could be added back later if needed. * I considered making any or all of `IORingError`, `IOCompletion`, and `IORequest` nested struct declarations inside `IORing`. The main reason I haven't done so is I was a little concerned about the ambiguity of having a type called `Error`. I'd be particularly interested in feedback on this choice. +* IOResource was originally a class in an attempt to manage the lifetime of the resource via language features. Changing to the current model of it being a copyable struct didn't make the lifetime management any less safe (the IORing still owns the actual resource), and reduces overhead. In the future it would be neat if we could express IOResources as being borrowed from the IORing so they can't be used after its lifetime. ## Acknowledgments From b40b09440c5ce0bca9d2633b8d374a156d95e053 Mon Sep 17 00:00:00 2001 From: David Smith Date: Wed, 16 Apr 2025 23:37:50 +0000 Subject: [PATCH 321/427] Revamp error handling --- Sources/System/IORing.swift | 170 +++++++++++++++++++++---------- Sources/System/IORingError.swift | 10 -- 2 files changed, 117 insertions(+), 63 deletions(-) delete mode 100644 Sources/System/IORingError.swift diff --git a/Sources/System/IORing.swift b/Sources/System/IORing.swift index 79840c1c..3ef92997 100644 --- a/Sources/System/IORing.swift +++ b/Sources/System/IORing.swift @@ -118,7 +118,7 @@ internal func _enter( numEvents: UInt32, minCompletions: UInt32, flags: UInt32 -) throws -> Int32 { +) throws(IORing.OperationError) -> Int32 { // Ring always needs enter right now; // TODO: support SQPOLL here while true { @@ -133,16 +133,14 @@ internal func _enter( //TODO: should we wait a bit on AGAIN? continue } else if ret < 0 { - fatalError( - "fatal error in submitting requests: " + Errno(rawValue: -ret).debugDescription - ) + throw(IORing.OperationError(result: -ret)) } else { return ret } } } -internal func _submitRequests(ring: borrowing SQRing, ringDescriptor: Int32) throws { +internal func _submitRequests(ring: borrowing SQRing, ringDescriptor: Int32) throws(IORing.OperationError) { let flushedEvents = _flushQueue(ring: ring) _ = try _enter( ringDescriptor: ringDescriptor, numEvents: flushedEvents, minCompletions: 0, flags: 0) @@ -193,7 +191,40 @@ internal func _getSubmissionEntry( return nil } +//TODO: make this not an enum. And maybe split it up? And move it into IORing + public struct IORing: ~Copyable { + + /// Errors in either submitting operations or receiving operation completions + public struct OperationError: Error, Hashable { + private var errorCode: Int + public static var operationCanceled: OperationError { .init(result: ECANCELED) } + + internal init(result: Int32) { + errorCode = Int(result) //TODO, flesh this out + } + } + + public struct SetupError: Error, Hashable { + private var errorCode: Int + + public static var missingRequiredFeatures: SetupError { + .init(setupResult: -1) /* TODO: numeric value */ + } + + internal init(setupResult: Int32) { + errorCode = Int(setupResult) //TODO, flesh this out + } + } + + public struct RegistrationError: Error, Hashable { + private var errorCode: Int + + internal init(registrationResult: Int32) { + errorCode = Int(registrationResult) //TODO, flesh this out + } + } + let ringFlags: UInt32 let ringDescriptor: Int32 @@ -212,7 +243,7 @@ public struct IORing: ~Copyable { var _registeredFiles: [UInt32]? var _registeredBuffers: [iovec]? - public init(queueDepth: UInt32) throws { + public init(queueDepth: UInt32) throws(SetupError) { var params = io_uring_params() ringDescriptor = withUnsafeMutablePointer(to: ¶ms) { @@ -224,7 +255,7 @@ public struct IORing: ~Copyable { { close(ringDescriptor) // TODO: error handling - throw IORingError.missingRequiredFeatures + throw .missingRequiredFeatures } if ringDescriptor < 0 { @@ -334,17 +365,17 @@ public struct IORing: ~Copyable { self.ringFlags = params.flags } - private func _blockingConsumeCompletionGuts( + private func _blockingConsumeCompletionGuts( minimumCount: UInt32, maximumCount: UInt32, extraArgs: UnsafeMutablePointer? = nil, - consumer: (consuming IOCompletion?, IORingError?, Bool) throws -> Void - ) rethrows { + consumer: (consuming IOCompletion?, OperationError?, Bool) throws(Err) -> Void + ) throws(Err) { var count = 0 while let completion = _tryConsumeCompletion(ring: completionRing) { count += 1 if completion.result < 0 { - try consumer(nil, IORingError(completionResult: completion.result), false) + try consumer(nil, OperationError(result: completion.result), false) } else { try consumer(completion, nil, false) } @@ -389,12 +420,12 @@ public struct IORing: ~Copyable { var count = 0 while let completion = _tryConsumeCompletion(ring: completionRing) { count += 1 - if completion.result < 0 { - try consumer(nil, IORingError(completionResult: completion.result), false) - } else { - try consumer(completion, nil, false) - } - if count == maximumCount { + if completion.result < 0 { + try consumer(nil, OperationError(result: completion.result), false) + } else { + try consumer(completion, nil, false) + } + if count == maximumCount { break } } @@ -404,14 +435,14 @@ public struct IORing: ~Copyable { internal func _blockingConsumeOneCompletion( extraArgs: UnsafeMutablePointer? = nil - ) throws -> IOCompletion { + ) throws(OperationError) -> IOCompletion { var result: IOCompletion? = nil try _blockingConsumeCompletionGuts(minimumCount: 1, maximumCount: 1, extraArgs: extraArgs) { (completion: consuming IOCompletion?, error, done) in if let error { throw error } - if let completion { + if let completion { result = consume completion } } @@ -420,46 +451,65 @@ public struct IORing: ~Copyable { public func blockingConsumeCompletion( timeout: Duration? = nil - ) throws -> IOCompletion { + ) throws(OperationError) -> IOCompletion { if let timeout { var ts = __kernel_timespec( tv_sec: timeout.components.seconds, tv_nsec: timeout.components.attoseconds / 1_000_000_000 ) - return try withUnsafePointer(to: &ts) { tsPtr in + var err: OperationError? = nil + var result: IOCompletion? = nil + result = try withUnsafePointer(to: &ts) { tsPtr in var args = io_uring_getevents_arg( sigmask: 0, sigmask_sz: 0, pad: 0, ts: UInt64(UInt(bitPattern: tsPtr)) ) - return try _blockingConsumeOneCompletion(extraArgs: &args) + do { + return try _blockingConsumeOneCompletion(extraArgs: &args) + } catch (let e) { + err = e as! OperationError + return nil + } + } + guard let result else { + throw(err!) } + return result } else { return try _blockingConsumeOneCompletion() } } - public func blockingConsumeCompletions( + public func blockingConsumeCompletions( minimumCount: UInt32 = 1, timeout: Duration? = nil, - consumer: (consuming IOCompletion?, IORingError?, Bool) throws -> Void - ) throws { + consumer: (consuming IOCompletion?, OperationError?, Bool) throws(Err) -> Void + ) throws(Err) { if let timeout { var ts = __kernel_timespec( tv_sec: timeout.components.seconds, tv_nsec: timeout.components.attoseconds / 1_000_000_000 ) - return try withUnsafePointer(to: &ts) { tsPtr in + var err: Err? = nil + withUnsafePointer(to: &ts) { tsPtr in var args = io_uring_getevents_arg( sigmask: 0, sigmask_sz: 0, pad: 0, ts: UInt64(UInt(bitPattern: tsPtr)) ) - try _blockingConsumeCompletionGuts( - minimumCount: minimumCount, maximumCount: UInt32.max, extraArgs: &args, - consumer: consumer) + do { + try _blockingConsumeCompletionGuts( + minimumCount: minimumCount, maximumCount: UInt32.max, extraArgs: &args, + consumer: consumer) + } catch (let e) { + err = e as! Err //TODO: why is `e` coming in as `any Error`? That seems wrong + } + } + if let err { + throw(err) } } else { try _blockingConsumeCompletionGuts( @@ -489,34 +539,35 @@ public struct IORing: ~Copyable { return nil } - internal func handleRegistrationResult(_ result: Int32) throws { - //TODO: error handling - } - - public mutating func registerEventFD(_ descriptor: FileDescriptor) throws { + public mutating func registerEventFD(_ descriptor: FileDescriptor) throws(RegistrationError) { var rawfd = descriptor.rawValue let result = withUnsafePointer(to: &rawfd) { fdptr in - return io_uring_register( + let result = io_uring_register( ringDescriptor, IORING_REGISTER_EVENTFD, UnsafeMutableRawPointer(mutating: fdptr), 1 ) + return result >= 0 ? nil : Errno.current + } + if let result { + throw(RegistrationError(registrationResult: result.rawValue)) } - try handleRegistrationResult(result) } - public mutating func unregisterEventFD() throws { + public mutating func unregisterEventFD() throws(RegistrationError) { let result = io_uring_register( ringDescriptor, IORING_UNREGISTER_EVENTFD, nil, 0 ) - try handleRegistrationResult(result) + if result < 0 { + throw(RegistrationError(registrationResult: result)) + } } - public mutating func registerFileSlots(count: Int) -> RegisteredResources< + public mutating func registerFileSlots(count: Int) throws(RegistrationError) -> RegisteredResources< IORingFileSlot.Resource > { precondition(_registeredFiles == nil) @@ -524,15 +575,19 @@ public struct IORing: ~Copyable { let files = [UInt32](repeating: UInt32.max, count: count) let regResult = files.withUnsafeBufferPointer { bPtr in - io_uring_register( + let result = io_uring_register( self.ringDescriptor, IORING_REGISTER_FILES, UnsafeMutableRawPointer(mutating: bPtr.baseAddress!), UInt32(truncatingIfNeeded: count) ) + return result >= 0 ? nil : Errno.current + } + + guard regResult == nil else { + throw RegistrationError(registrationResult: regResult!.rawValue) } - // TODO: error handling _registeredFiles = files return registeredFileSlots } @@ -545,7 +600,7 @@ public struct IORing: ~Copyable { RegisteredResources(resources: _registeredFiles ?? []) } - public mutating func registerBuffers(_ buffers: some Collection) + public mutating func registerBuffers(_ buffers: some Collection) throws(RegistrationError) -> RegisteredResources { precondition(buffers.count < UInt32.max) @@ -553,12 +608,17 @@ public struct IORing: ~Copyable { //TODO: check if io_uring has preconditions it needs for the buffers (e.g. alignment) let iovecs = buffers.map { $0.to_iovec() } let regResult = iovecs.withUnsafeBufferPointer { bPtr in - io_uring_register( + let result = io_uring_register( self.ringDescriptor, IORING_REGISTER_BUFFERS, UnsafeMutableRawPointer(mutating: bPtr.baseAddress!), UInt32(truncatingIfNeeded: buffers.count) ) + return result >= 0 ? nil : Errno.current + } + + guard regResult == nil else { + throw RegistrationError(registrationResult: regResult!.rawValue) } // TODO: error handling @@ -566,10 +626,10 @@ public struct IORing: ~Copyable { return registeredBuffers } - public mutating func registerBuffers(_ buffers: UnsafeMutableRawBufferPointer...) + public mutating func registerBuffers(_ buffers: UnsafeMutableRawBufferPointer...) throws(RegistrationError) -> RegisteredResources { - registerBuffers(buffers) + try registerBuffers(buffers) } public struct RegisteredResources: RandomAccessCollection { @@ -596,20 +656,24 @@ public struct IORing: ~Copyable { fatalError("failed to unregister buffers: TODO") } - public func submitPreparedRequests() throws { + public func submitPreparedRequests() throws(OperationError) { try _submitRequests(ring: submissionRing, ringDescriptor: ringDescriptor) } - public func submitPreparedRequestsAndConsumeCompletions( + public func submitPreparedRequestsAndConsumeCompletions( minimumCount: UInt32 = 1, timeout: Duration? = nil, - consumer: (consuming IOCompletion?, IORingError?, Bool) throws -> Void - ) throws { + consumer: (consuming IOCompletion?, OperationError?, Bool) throws(Err) -> Void + ) throws(Err) { //TODO: optimize this to one uring_enter - try submitPreparedRequests() + do { + try submitPreparedRequests() + } catch (let e) { + try consumer(nil, e, true) + } try blockingConsumeCompletions( - minimumCount: minimumCount, - timeout: timeout, + minimumCount: minimumCount, + timeout: timeout, consumer: consumer ) } @@ -641,7 +705,7 @@ public struct IORing: ~Copyable { prepare(linkedRequests: linkedRequests) } - public mutating func submit(linkedRequests: IORequest...) throws { + public mutating func submit(linkedRequests: IORequest...) throws(OperationError) { prepare(linkedRequests: linkedRequests) try submitPreparedRequests() } diff --git a/Sources/System/IORingError.swift b/Sources/System/IORingError.swift deleted file mode 100644 index fbd70bce..00000000 --- a/Sources/System/IORingError.swift +++ /dev/null @@ -1,10 +0,0 @@ -//TODO: make this not an enum -public enum IORingError: Error, Equatable { - case missingRequiredFeatures - case operationCanceled - case unknown(errorCode: Int) - - internal init(completionResult: Int32) { - self = .unknown(errorCode: Int(completionResult)) //TODO, flesh this out - } -} From 2f91217c90d9efc02f4d1bd28a7e2188b774bf77 Mon Sep 17 00:00:00 2001 From: David Smith Date: Wed, 16 Apr 2025 23:44:21 +0000 Subject: [PATCH 322/427] Fix a few things I missed --- Sources/System/IORing.swift | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/Sources/System/IORing.swift b/Sources/System/IORing.swift index 3ef92997..e45d48af 100644 --- a/Sources/System/IORing.swift +++ b/Sources/System/IORing.swift @@ -437,14 +437,18 @@ public struct IORing: ~Copyable { extraArgs: UnsafeMutablePointer? = nil ) throws(OperationError) -> IOCompletion { var result: IOCompletion? = nil - try _blockingConsumeCompletionGuts(minimumCount: 1, maximumCount: 1, extraArgs: extraArgs) { - (completion: consuming IOCompletion?, error, done) in - if let error { - throw error - } - if let completion { - result = consume completion + do { + try _blockingConsumeCompletionGuts(minimumCount: 1, maximumCount: 1, extraArgs: extraArgs) { + (completion: consuming IOCompletion?, error, done) in + if let error { + throw error + } + if let completion { + result = consume completion + } } + } catch (let e) { + throw e as! OperationError //TODO: why is this needed? } return result.take()! } @@ -459,7 +463,7 @@ public struct IORing: ~Copyable { ) var err: OperationError? = nil var result: IOCompletion? = nil - result = try withUnsafePointer(to: &ts) { tsPtr in + result = withUnsafePointer(to: &ts) { tsPtr in var args = io_uring_getevents_arg( sigmask: 0, sigmask_sz: 0, @@ -469,7 +473,7 @@ public struct IORing: ~Copyable { do { return try _blockingConsumeOneCompletion(extraArgs: &args) } catch (let e) { - err = e as! OperationError + err = (e as! OperationError) return nil } } @@ -505,7 +509,7 @@ public struct IORing: ~Copyable { minimumCount: minimumCount, maximumCount: UInt32.max, extraArgs: &args, consumer: consumer) } catch (let e) { - err = e as! Err //TODO: why is `e` coming in as `any Error`? That seems wrong + err = (e as! Err) //TODO: why is `e` coming in as `any Error`? That seems wrong } } if let err { @@ -692,10 +696,10 @@ public struct IORing: ~Copyable { for req in linkedRequests.dropLast() { var raw = req.makeRawRequest() raw.linkToNextRequest() - _writeRequest( + _ = _writeRequest( raw, ring: &submissionRing, submissionQueueEntries: submissionQueueEntries) } - _writeRequest( + _ = _writeRequest( last.makeRawRequest(), ring: &submissionRing, submissionQueueEntries: submissionQueueEntries) } From d4ca203c09c7e541d603436bf2f0d78d600f5309 Mon Sep 17 00:00:00 2001 From: David Smith Date: Thu, 17 Apr 2025 00:26:13 +0000 Subject: [PATCH 323/427] Most of feature querying and setup flags --- Sources/System/IORing.swift | 56 ++++++++++++++++++++++++++++++++++++- 1 file changed, 55 insertions(+), 1 deletion(-) diff --git a/Sources/System/IORing.swift b/Sources/System/IORing.swift index e45d48af..ef63eccb 100644 --- a/Sources/System/IORing.swift +++ b/Sources/System/IORing.swift @@ -243,8 +243,33 @@ public struct IORing: ~Copyable { var _registeredFiles: [UInt32]? var _registeredBuffers: [iovec]? - public init(queueDepth: UInt32) throws(SetupError) { + @frozen + public struct SetupFlags: OptionSet, RawRepresentable { + public var rawValue: UInt32 + + @inlinable public init(rawValue: UInt32) { + self.rawValue = rawValue + } + @inlinable public static var pollCompletions: SetupFlags { .init(rawValue: UInt32(1) << 0) } //IORING_SETUP_IOPOLL + @inlinable public static var pollSubmissions: SetupFlags { .init(rawValue: UInt32(1) << 1) } //IORING_SETUP_SQPOLL + //TODO: figure out how to expose IORING_SETUP_SQ_AFF, IORING_SETUP_CQSIZE, IORING_SETUP_ATTACH_WQ + @inlinable public static var clampMaxEntries: SetupFlags { .init(rawValue: UInt32(1) << 4) } //IORING_SETUP_CLAMP + @inlinable public static var startDisabled: SetupFlags { .init(rawValue: UInt32(1) << 6) } //IORING_SETUP_R_DISABLED + @inlinable public static var continueSubmittingOnError: SetupFlags { .init(rawValue: UInt32(1) << 7) } //IORING_SETUP_SUBMIT_ALL + //TODO: do we want to expose IORING_SETUP_COOP_TASKRUN and IORING_SETUP_TASKRUN_FLAG? + //public static var runTasksCooperatively: SetupFlags { .init(rawValue: UInt32(1) << 8) } //IORING_SETUP_COOP_TASKRUN + //TODO: can we even do different size sqe/cqe? It requires a kernel feature, but how do we convince swift to let the types be different sizes? + internal static var use128ByteSQEs: SetupFlags { .init(rawValue: UInt32(1) << 10) } //IORING_SETUP_SQE128 + internal static var use32ByteCQEs: SetupFlags { .init(rawValue: UInt32(1) << 11) } //IORING_SETUP_CQE32 + @inlinable public static var singleSubmissionThread: SetupFlags { .init(rawValue: UInt32(1) << 12) } //IORING_SETUP_SINGLE_ISSUER + @inlinable public static var deferRunningTasks: SetupFlags { .init(rawValue: UInt32(1) << 13) } //IORING_SETUP_DEFER_TASKRUN + //pretty sure we don't want to expose IORING_SETUP_NO_MMAP or IORING_SETUP_REGISTERED_FD_ONLY currently + //TODO: should IORING_SETUP_NO_SQARRAY be the default? do we need to adapt anything to it? + } + + public init(queueDepth: UInt32, flags: SetupFlags = []) throws(SetupError) { var params = io_uring_params() + params.flags = flags.rawValue ringDescriptor = withUnsafeMutablePointer(to: ¶ms) { return io_uring_setup(queueDepth, $0) @@ -714,6 +739,35 @@ public struct IORing: ~Copyable { try submitPreparedRequests() } + @frozen + public struct Features: OptionSet, RawRepresentable { + public let rawValue: UInt32 + + @inlinable public init(rawValue: UInt32) { + self.rawValue = rawValue + } + + //IORING_FEAT_SINGLE_MMAP is handled internally + @inlinable public static var nonDroppingCompletions: Features { .init(rawValue: UInt32(1) << 1) } //IORING_FEAT_NODROP + @inlinable public static var stableSubmissions: Features { .init(rawValue: UInt32(1) << 2) } //IORING_FEAT_SUBMIT_STABLE + @inlinable public static var currentFilePosition: Features { .init(rawValue: UInt32(1) << 3) } //IORING_FEAT_RW_CUR_POS + @inlinable public static var assumingTaskCredentials: Features { .init(rawValue: UInt32(1) << 4) } //IORING_FEAT_CUR_PERSONALITY + @inlinable public static var fastPolling: Features { .init(rawValue: UInt32(1) << 5) } //IORING_FEAT_FAST_POLL + @inlinable public static var epoll32BitFlags: Features { .init(rawValue: UInt32(1) << 6) } //IORING_FEAT_POLL_32BITS + @inlinable public static var pollNonFixedFiles: Features { .init(rawValue: UInt32(1) << 7) } //IORING_FEAT_SQPOLL_NONFIXED + @inlinable public static var extendedArguments: Features { .init(rawValue: UInt32(1) << 8) } //IORING_FEAT_EXT_ARG + @inlinable public static var nativeWorkers: Features { .init(rawValue: UInt32(1) << 9) } //IORING_FEAT_NATIVE_WORKERS + @inlinable public static var resourceTags: Features { .init(rawValue: UInt32(1) << 10) } //IORING_FEAT_RSRC_TAGS + @inlinable public static var allowsSkippingSuccessfulCompletions: Features { .init(rawValue: UInt32(1) << 11) } //IORING_FEAT_CQE_SKIP + @inlinable public static var improvedLinkedFiles: Features { .init(rawValue: UInt32(1) << 12) } //IORING_FEAT_LINKED_FILE + @inlinable public static var registerRegisteredRings: Features { .init(rawValue: UInt32(1) << 13) } //IORING_FEAT_REG_REG_RING + @inlinable public static var minimumTimeout: Features { .init(rawValue: UInt32(1) << 15) } //IORING_FEAT_MIN_TIMEOUT + @inlinable public static var bundledSendReceive: Features { .init(rawValue: UInt32(1) << 14) } //IORING_FEAT_RECVSEND_BUNDLE + } + public static var supportedFeatures: Features { + fatalError("Implement me") + } + deinit { munmap(ringPtr, ringSize) munmap( From 41f967690797dcb68220d7587215216824dac0fe Mon Sep 17 00:00:00 2001 From: David Smith Date: Thu, 17 Apr 2025 23:31:01 +0000 Subject: [PATCH 324/427] WIP, crashes the compiler --- Sources/System/IORing.swift | 237 ++++++++++++++++++++---------------- 1 file changed, 130 insertions(+), 107 deletions(-) diff --git a/Sources/System/IORing.swift b/Sources/System/IORing.swift index ef63eccb..e72c9e89 100644 --- a/Sources/System/IORing.swift +++ b/Sources/System/IORing.swift @@ -191,11 +191,113 @@ internal func _getSubmissionEntry( return nil } -//TODO: make this not an enum. And maybe split it up? And move it into IORing +private func setUpRing( + queueDepth: UInt32, flags: IORing.SetupFlags, submissionRing: inout SQRing +) throws(IORing.SetupError) -> + (params: io_uring_params, ringDescriptor: Int32, ringPtr: UnsafeMutableRawPointer, ringSize: Int, sqes: UnsafeMutableRawPointer) { + var params = io_uring_params() + params.flags = flags.rawValue + + var err: Errno? = nil + let ringDescriptor = withUnsafeMutablePointer(to: ¶ms) { + let result = io_uring_setup(queueDepth, $0) + if result < 0 { + err = Errno.current + } + return result + } + + if let err { + throw IORing.SetupError(setupResult: err.rawValue) + } + + if params.features & IORING_FEAT_SINGLE_MMAP == 0 + || params.features & IORING_FEAT_NODROP == 0 + { + close(ringDescriptor) + // TODO: error handling + throw .missingRequiredFeatures + } + + let submitRingSize = + params.sq_off.array + + params.sq_entries * UInt32(MemoryLayout.size) + + let completionRingSize = + params.cq_off.cqes + + params.cq_entries * UInt32(MemoryLayout.size) + + let ringSize = Int(max(submitRingSize, completionRingSize)) + + let ringPtr: UnsafeMutableRawPointer! = mmap( + /* addr: */ nil, + /* len: */ ringSize, + /* prot: */ PROT_READ | PROT_WRITE, + /* flags: */ MAP_SHARED | MAP_POPULATE, + /* fd: */ ringDescriptor, + /* offset: */ __off_t(IORING_OFF_SQ_RING) + ) + + if ringPtr == MAP_FAILED { + perror("mmap") + close(ringDescriptor) + throw .mapFailed + } + + let submissionRing = SQRing( + kernelHead: UnsafePointer>( + ringPtr.advanced(by: params.sq_off.head) + .assumingMemoryBound(to: Atomic.self) + ), + kernelTail: UnsafePointer>( + ringPtr.advanced(by: params.sq_off.tail) + .assumingMemoryBound(to: Atomic.self) + ), + userTail: 0, // no requests yet + ringMask: ringPtr.advanced(by: params.sq_off.ring_mask) + .assumingMemoryBound(to: UInt32.self).pointee, + flags: UnsafePointer>( + ringPtr.advanced(by: params.sq_off.flags) + .assumingMemoryBound(to: Atomic.self) + ), + array: UnsafeMutableBufferPointer( + start: ringPtr.advanced(by: params.sq_off.array) + .assumingMemoryBound(to: UInt32.self), + count: Int( + ringPtr.advanced(by: params.sq_off.ring_entries) + .assumingMemoryBound(to: UInt32.self).pointee) + ) + ) + + // fill submission ring array with 1:1 map to underlying SQEs + for i in 0...size, + /* prot: */ PROT_READ | PROT_WRITE, + /* flags: */ MAP_SHARED | MAP_POPULATE, + /* fd: */ ringDescriptor, + /* offset: */ __off_t(IORING_OFF_SQES) + ) + + if sqes == MAP_FAILED { + perror("mmap") + munmap(ringPtr, ringSize) + close(ringDescriptor) + throw .mapFailed + } + + return (params: params, ringDescriptor: ringDescriptor, ringPtr: ringPtr!, ringSize: ringSize, sqes: sqes!) +} public struct IORing: ~Copyable { /// Errors in either submitting operations or receiving operation completions + @frozen public struct OperationError: Error, Hashable { private var errorCode: Int public static var operationCanceled: OperationError { .init(result: ECANCELED) } @@ -205,6 +307,7 @@ public struct IORing: ~Copyable { } } + @frozen public struct SetupError: Error, Hashable { private var errorCode: Int @@ -212,11 +315,14 @@ public struct IORing: ~Copyable { .init(setupResult: -1) /* TODO: numeric value */ } + public static var mapFailed: SetupError { .init(setupResult: -2) } + internal init(setupResult: Int32) { errorCode = Int(setupResult) //TODO, flesh this out } } + @frozen public struct RegistrationError: Error, Hashable { private var errorCode: Int @@ -228,7 +334,7 @@ public struct IORing: ~Copyable { let ringFlags: UInt32 let ringDescriptor: Int32 - @usableFromInline var submissionRing: SQRing + @usableFromInline var submissionRing: SQRing! // FEAT: set this eventually let submissionPolling: Bool = false @@ -240,8 +346,10 @@ public struct IORing: ~Copyable { let ringSize: Int let ringPtr: UnsafeMutableRawPointer - var _registeredFiles: [UInt32]? - var _registeredBuffers: [iovec]? + var _registeredFiles: [UInt32] = [] + var _registeredBuffers: [iovec] = [] + + var features = Features(rawValue: 0) @frozen public struct SetupFlags: OptionSet, RawRepresentable { @@ -267,103 +375,22 @@ public struct IORing: ~Copyable { //TODO: should IORING_SETUP_NO_SQARRAY be the default? do we need to adapt anything to it? } - public init(queueDepth: UInt32, flags: SetupFlags = []) throws(SetupError) { - var params = io_uring_params() - params.flags = flags.rawValue - - ringDescriptor = withUnsafeMutablePointer(to: ¶ms) { - return io_uring_setup(queueDepth, $0) - } - - if params.features & IORING_FEAT_SINGLE_MMAP == 0 - || params.features & IORING_FEAT_NODROP == 0 - { - close(ringDescriptor) - // TODO: error handling - throw .missingRequiredFeatures - } - - if ringDescriptor < 0 { - // TODO: error handling - } - - let submitRingSize = - params.sq_off.array - + params.sq_entries * UInt32(MemoryLayout.size) - - let completionRingSize = - params.cq_off.cqes - + params.cq_entries * UInt32(MemoryLayout.size) - - ringSize = Int(max(submitRingSize, completionRingSize)) - - ringPtr = mmap( - /* addr: */ nil, - /* len: */ ringSize, - /* prot: */ PROT_READ | PROT_WRITE, - /* flags: */ MAP_SHARED | MAP_POPULATE, - /* fd: */ ringDescriptor, - /* offset: */ __off_t(IORING_OFF_SQ_RING) - ) - - if ringPtr == MAP_FAILED { - perror("mmap") - // TODO: error handling - fatalError("mmap failed in ring setup") - } - - let submissionRing = SQRing( - kernelHead: UnsafePointer>( - ringPtr.advanced(by: params.sq_off.head) - .assumingMemoryBound(to: Atomic.self) - ), - kernelTail: UnsafePointer>( - ringPtr.advanced(by: params.sq_off.tail) - .assumingMemoryBound(to: Atomic.self) - ), - userTail: 0, // no requests yet - ringMask: ringPtr.advanced(by: params.sq_off.ring_mask) - .assumingMemoryBound(to: UInt32.self).pointee, - flags: UnsafePointer>( - ringPtr.advanced(by: params.sq_off.flags) - .assumingMemoryBound(to: Atomic.self) - ), - array: UnsafeMutableBufferPointer( - start: ringPtr.advanced(by: params.sq_off.array) - .assumingMemoryBound(to: UInt32.self), - count: Int( - ringPtr.advanced(by: params.sq_off.ring_entries) - .assumingMemoryBound(to: UInt32.self).pointee) - ) - ) - - // fill submission ring array with 1:1 map to underlying SQEs - for i in 0...size, - /* prot: */ PROT_READ | PROT_WRITE, - /* flags: */ MAP_SHARED | MAP_POPULATE, - /* fd: */ ringDescriptor, - /* offset: */ __off_t(IORING_OFF_SQES) - ) - - if sqes == MAP_FAILED { - perror("mmap") - // TODO: error handling - fatalError("sqe mmap failed in ring setup") - } + public init(queueDepth: UInt32, flags: SetupFlags) throws(SetupError) { + let (params, tmpRingDescriptor, tmpRingPtr, tmpRingSize, sqes) = try setUpRing(queueDepth: queueDepth, flags: flags, submissionRing: &submissionRing) + // All throws need to be before initializing ivars here to avoid + // "error: conditional initialization or destruction of noncopyable types is not supported; + // this variable must be consistently in an initialized or uninitialized state through every code path" + features = Features(rawValue: params.features) + ringDescriptor = tmpRingDescriptor + ringPtr = tmpRingPtr + ringSize = tmpRingSize submissionQueueEntries = UnsafeMutableBufferPointer( - start: sqes!.assumingMemoryBound(to: io_uring_sqe.self), + start: sqes.assumingMemoryBound(to: io_uring_sqe.self), count: Int(params.sq_entries) ) - let completionRing = CQRing( + completionRing = CQRing( kernelHead: UnsafePointer>( ringPtr.advanced(by: params.cq_off.head) .assumingMemoryBound(to: Atomic.self) @@ -383,10 +410,6 @@ public struct IORing: ~Copyable { .assumingMemoryBound(to: UInt32.self).pointee) ) ) - - self.submissionRing = submissionRing - self.completionRing = completionRing - self.ringFlags = params.flags } @@ -599,7 +622,7 @@ public struct IORing: ~Copyable { public mutating func registerFileSlots(count: Int) throws(RegistrationError) -> RegisteredResources< IORingFileSlot.Resource > { - precondition(_registeredFiles == nil) + precondition(_registeredFiles.isEmpty) precondition(count < UInt32.max) let files = [UInt32](repeating: UInt32.max, count: count) @@ -626,14 +649,14 @@ public struct IORing: ~Copyable { } public var registeredFileSlots: RegisteredResources { - RegisteredResources(resources: _registeredFiles ?? []) + RegisteredResources(resources: _registeredFiles) } public mutating func registerBuffers(_ buffers: some Collection) throws(RegistrationError) -> RegisteredResources { precondition(buffers.count < UInt32.max) - precondition(_registeredBuffers == nil) + precondition(_registeredBuffers.isEmpty) //TODO: check if io_uring has preconditions it needs for the buffers (e.g. alignment) let iovecs = buffers.map { $0.to_iovec() } let regResult = iovecs.withUnsafeBufferPointer { bPtr in @@ -678,7 +701,7 @@ public struct IORing: ~Copyable { } public var registeredBuffers: RegisteredResources { - RegisteredResources(resources: _registeredBuffers ?? []) + RegisteredResources(resources: _registeredBuffers) } public func unregisterBuffers() { @@ -740,7 +763,7 @@ public struct IORing: ~Copyable { } @frozen - public struct Features: OptionSet, RawRepresentable { + public struct Features: OptionSet, RawRepresentable, Hashable { public let rawValue: UInt32 @inlinable public init(rawValue: UInt32) { @@ -764,8 +787,8 @@ public struct IORing: ~Copyable { @inlinable public static var minimumTimeout: Features { .init(rawValue: UInt32(1) << 15) } //IORING_FEAT_MIN_TIMEOUT @inlinable public static var bundledSendReceive: Features { .init(rawValue: UInt32(1) << 14) } //IORING_FEAT_RECVSEND_BUNDLE } - public static var supportedFeatures: Features { - fatalError("Implement me") + public var supportedFeatures: Features { + return features } deinit { From 014f8b78eb3b2acf07f98589661acd8fa2e42bd7 Mon Sep 17 00:00:00 2001 From: David Smith Date: Thu, 17 Apr 2025 23:36:51 +0000 Subject: [PATCH 325/427] Rename userData to context --- Sources/System/IOCompletion.swift | 2 +- Sources/System/IORequest.swift | 148 +++++++++++++++--------------- 2 files changed, 75 insertions(+), 75 deletions(-) diff --git a/Sources/System/IOCompletion.swift b/Sources/System/IOCompletion.swift index ee81797e..b15f0d45 100644 --- a/Sources/System/IOCompletion.swift +++ b/Sources/System/IOCompletion.swift @@ -20,7 +20,7 @@ extension IOCompletion { } extension IOCompletion { - public var userData: UInt64 { //TODO: naming? + public var context: UInt64 { get { rawValue.user_data } diff --git a/Sources/System/IORequest.swift b/Sources/System/IORequest.swift index 1eed6edf..c836fd32 100644 --- a/Sources/System/IORequest.swift +++ b/Sources/System/IORequest.swift @@ -9,7 +9,7 @@ internal enum IORequestCore { FileDescriptor.AccessMode, options: FileDescriptor.OpenOptions = FileDescriptor.OpenOptions(), permissions: FilePermissions? = nil, - userData: UInt64 = 0 + context: UInt64 = 0 ) case openatSlot( atDirectory: FileDescriptor, @@ -18,68 +18,68 @@ internal enum IORequestCore { options: FileDescriptor.OpenOptions = FileDescriptor.OpenOptions(), permissions: FilePermissions? = nil, intoSlot: IORingFileSlot, - userData: UInt64 = 0 + context: UInt64 = 0 ) case read( file: FileDescriptor, buffer: IORingBuffer, offset: UInt64 = 0, - userData: UInt64 = 0 + context: UInt64 = 0 ) case readUnregistered( file: FileDescriptor, buffer: UnsafeMutableRawBufferPointer, offset: UInt64 = 0, - userData: UInt64 = 0 + context: UInt64 = 0 ) case readSlot( file: IORingFileSlot, buffer: IORingBuffer, offset: UInt64 = 0, - userData: UInt64 = 0 + context: UInt64 = 0 ) case readUnregisteredSlot( file: IORingFileSlot, buffer: UnsafeMutableRawBufferPointer, offset: UInt64 = 0, - userData: UInt64 = 0 + context: UInt64 = 0 ) case write( file: FileDescriptor, buffer: IORingBuffer, offset: UInt64 = 0, - userData: UInt64 = 0 + context: UInt64 = 0 ) case writeUnregistered( file: FileDescriptor, buffer: UnsafeMutableRawBufferPointer, offset: UInt64 = 0, - userData: UInt64 = 0 + context: UInt64 = 0 ) case writeSlot( file: IORingFileSlot, buffer: IORingBuffer, offset: UInt64 = 0, - userData: UInt64 = 0 + context: UInt64 = 0 ) case writeUnregisteredSlot( file: IORingFileSlot, buffer: UnsafeMutableRawBufferPointer, offset: UInt64 = 0, - userData: UInt64 = 0 + context: UInt64 = 0 ) case close( FileDescriptor, - userData: UInt64 = 0 + context: UInt64 = 0 ) case closeSlot( IORingFileSlot, - userData: UInt64 = 0 + context: UInt64 = 0 ) case unlinkAt( atDirectory: FileDescriptor, path: FilePath, - userData: UInt64 = 0 + context: UInt64 = 0 ) } @@ -88,14 +88,14 @@ internal func makeRawRequest_readWrite_registered( file: FileDescriptor, buffer: IORingBuffer, offset: UInt64, - userData: UInt64 = 0, + context: UInt64 = 0, request: consuming RawIORequest ) -> RawIORequest { request.fileDescriptor = file request.buffer = buffer.unsafeBuffer request.rawValue.buf_index = UInt16(exactly: buffer.index)! request.offset = offset - request.rawValue.user_data = userData + request.rawValue.user_data = context return request } @@ -104,7 +104,7 @@ internal func makeRawRequest_readWrite_registered_slot( file: IORingFileSlot, buffer: IORingBuffer, offset: UInt64, - userData: UInt64 = 0, + context: UInt64 = 0, request: consuming RawIORequest ) -> RawIORequest { request.rawValue.fd = Int32(exactly: file.index)! @@ -112,7 +112,7 @@ internal func makeRawRequest_readWrite_registered_slot( request.buffer = buffer.unsafeBuffer request.rawValue.buf_index = UInt16(exactly: buffer.index)! request.offset = offset - request.rawValue.user_data = userData + request.rawValue.user_data = context return request } @@ -121,13 +121,13 @@ internal func makeRawRequest_readWrite_unregistered( file: FileDescriptor, buffer: UnsafeMutableRawBufferPointer, offset: UInt64, - userData: UInt64 = 0, + context: UInt64 = 0, request: consuming RawIORequest ) -> RawIORequest { request.fileDescriptor = file request.buffer = buffer request.offset = offset - request.rawValue.user_data = userData + request.rawValue.user_data = context return request } @@ -136,14 +136,14 @@ internal func makeRawRequest_readWrite_unregistered_slot( file: IORingFileSlot, buffer: UnsafeMutableRawBufferPointer, offset: UInt64, - userData: UInt64 = 0, + context: UInt64 = 0, request: consuming RawIORequest ) -> RawIORequest { request.rawValue.fd = Int32(exactly: file.index)! request.flags = .fixedFile request.buffer = buffer request.offset = offset - request.rawValue.user_data = userData + request.rawValue.user_data = context return request } @@ -156,7 +156,7 @@ public struct IORequest { } extension IORequest { - public static func nop(userData: UInt64 = 0) -> IORequest { + public static func nop(context: UInt64 = 0) -> IORequest { IORequest(core: .nop) } @@ -164,93 +164,93 @@ extension IORequest { _ file: IORingFileSlot, into buffer: IORingBuffer, at offset: UInt64 = 0, - userData: UInt64 = 0 + context: UInt64 = 0 ) -> IORequest { - IORequest(core: .readSlot(file: file, buffer: buffer, offset: offset, userData: userData)) + IORequest(core: .readSlot(file: file, buffer: buffer, offset: offset, context: context)) } public static func reading( _ file: FileDescriptor, into buffer: IORingBuffer, at offset: UInt64 = 0, - userData: UInt64 = 0 + context: UInt64 = 0 ) -> IORequest { - IORequest(core: .read(file: file, buffer: buffer, offset: offset, userData: userData)) + IORequest(core: .read(file: file, buffer: buffer, offset: offset, context: context)) } public static func reading( _ file: IORingFileSlot, into buffer: UnsafeMutableRawBufferPointer, at offset: UInt64 = 0, - userData: UInt64 = 0 + context: UInt64 = 0 ) -> IORequest { IORequest( core: .readUnregisteredSlot( - file: file, buffer: buffer, offset: offset, userData: userData)) + file: file, buffer: buffer, offset: offset, context: context)) } public static func reading( _ file: FileDescriptor, into buffer: UnsafeMutableRawBufferPointer, at offset: UInt64 = 0, - userData: UInt64 = 0 + context: UInt64 = 0 ) -> IORequest { IORequest( - core: .readUnregistered(file: file, buffer: buffer, offset: offset, userData: userData)) + core: .readUnregistered(file: file, buffer: buffer, offset: offset, context: context)) } public static func writing( _ buffer: IORingBuffer, into file: IORingFileSlot, at offset: UInt64 = 0, - userData: UInt64 = 0 + context: UInt64 = 0 ) -> IORequest { - IORequest(core: .writeSlot(file: file, buffer: buffer, offset: offset, userData: userData)) + IORequest(core: .writeSlot(file: file, buffer: buffer, offset: offset, context: context)) } public static func writing( _ buffer: IORingBuffer, into file: FileDescriptor, at offset: UInt64 = 0, - userData: UInt64 = 0 + context: UInt64 = 0 ) -> IORequest { - IORequest(core: .write(file: file, buffer: buffer, offset: offset, userData: userData)) + IORequest(core: .write(file: file, buffer: buffer, offset: offset, context: context)) } public static func writing( _ buffer: UnsafeMutableRawBufferPointer, into file: IORingFileSlot, at offset: UInt64 = 0, - userData: UInt64 = 0 + context: UInt64 = 0 ) -> IORequest { IORequest( core: .writeUnregisteredSlot( - file: file, buffer: buffer, offset: offset, userData: userData)) + file: file, buffer: buffer, offset: offset, context: context)) } public static func writing( _ buffer: UnsafeMutableRawBufferPointer, into file: FileDescriptor, at offset: UInt64 = 0, - userData: UInt64 = 0 + context: UInt64 = 0 ) -> IORequest { IORequest( - core: .writeUnregistered(file: file, buffer: buffer, offset: offset, userData: userData) + core: .writeUnregistered(file: file, buffer: buffer, offset: offset, context: context) ) } public static func closing( _ file: FileDescriptor, - userData: UInt64 = 0 + context: UInt64 = 0 ) -> IORequest { - IORequest(core: .close(file, userData: userData)) + IORequest(core: .close(file, context: context)) } public static func closing( _ file: IORingFileSlot, - userData: UInt64 = 0 + context: UInt64 = 0 ) -> IORequest { - IORequest(core: .closeSlot(file, userData: userData)) + IORequest(core: .closeSlot(file, context: context)) } public static func opening( @@ -260,12 +260,12 @@ extension IORequest { mode: FileDescriptor.AccessMode, options: FileDescriptor.OpenOptions = FileDescriptor.OpenOptions(), permissions: FilePermissions? = nil, - userData: UInt64 = 0 + context: UInt64 = 0 ) -> IORequest { IORequest( core: .openatSlot( atDirectory: directory, path: path, mode, options: options, - permissions: permissions, intoSlot: slot, userData: userData)) + permissions: permissions, intoSlot: slot, context: context)) } public static func opening( @@ -274,21 +274,21 @@ extension IORequest { mode: FileDescriptor.AccessMode, options: FileDescriptor.OpenOptions = FileDescriptor.OpenOptions(), permissions: FilePermissions? = nil, - userData: UInt64 = 0 + context: UInt64 = 0 ) -> IORequest { IORequest( core: .openat( atDirectory: directory, path: path, mode, options: options, - permissions: permissions, userData: userData + permissions: permissions, context: context )) } public static func unlinking( _ path: FilePath, in directory: FileDescriptor, - userData: UInt64 = 0 + context: UInt64 = 0 ) -> IORequest { - IORequest(core: .unlinkAt(atDirectory: directory, path: path, userData: userData)) + IORequest(core: .unlinkAt(atDirectory: directory, path: path, context: context)) } @inline(__always) @@ -299,7 +299,7 @@ extension IORequest { request.operation = .nop case .openatSlot( let atDirectory, let path, let mode, let options, let permissions, let fileSlot, - let userData): + let context): // TODO: use rawValue less request.operation = .openAt request.fileDescriptor = atDirectory @@ -312,9 +312,9 @@ extension IORequest { request.rawValue.len = permissions?.rawValue ?? 0 request.rawValue.file_index = UInt32(fileSlot.index + 1) request.path = path - request.rawValue.user_data = userData + request.rawValue.user_data = context case .openat( - let atDirectory, let path, let mode, let options, let permissions, let userData): + let atDirectory, let path, let mode, let options, let permissions, let context): request.operation = .openAt request.fileDescriptor = atDirectory request.rawValue.addr = UInt64( @@ -325,48 +325,48 @@ extension IORequest { request.rawValue.open_flags = UInt32(bitPattern: options.rawValue | mode.rawValue) request.rawValue.len = permissions?.rawValue ?? 0 request.path = path - request.rawValue.user_data = userData - case .write(let file, let buffer, let offset, let userData): + request.rawValue.user_data = context + case .write(let file, let buffer, let offset, let context): request.operation = .writeFixed return makeRawRequest_readWrite_registered( - file: file, buffer: buffer, offset: offset, userData: userData, request: request) - case .writeSlot(let file, let buffer, let offset, let userData): + file: file, buffer: buffer, offset: offset, context: context, request: request) + case .writeSlot(let file, let buffer, let offset, let context): request.operation = .writeFixed return makeRawRequest_readWrite_registered_slot( - file: file, buffer: buffer, offset: offset, userData: userData, request: request) - case .writeUnregistered(let file, let buffer, let offset, let userData): + file: file, buffer: buffer, offset: offset, context: context, request: request) + case .writeUnregistered(let file, let buffer, let offset, let context): request.operation = .write return makeRawRequest_readWrite_unregistered( - file: file, buffer: buffer, offset: offset, userData: userData, request: request) - case .writeUnregisteredSlot(let file, let buffer, let offset, let userData): + file: file, buffer: buffer, offset: offset, context: context, request: request) + case .writeUnregisteredSlot(let file, let buffer, let offset, let context): request.operation = .write return makeRawRequest_readWrite_unregistered_slot( - file: file, buffer: buffer, offset: offset, userData: userData, request: request) - case .read(let file, let buffer, let offset, let userData): + file: file, buffer: buffer, offset: offset, context: context, request: request) + case .read(let file, let buffer, let offset, let context): request.operation = .readFixed return makeRawRequest_readWrite_registered( - file: file, buffer: buffer, offset: offset, userData: userData, request: request) - case .readSlot(let file, let buffer, let offset, let userData): + file: file, buffer: buffer, offset: offset, context: context, request: request) + case .readSlot(let file, let buffer, let offset, let context): request.operation = .readFixed return makeRawRequest_readWrite_registered_slot( - file: file, buffer: buffer, offset: offset, userData: userData, request: request) - case .readUnregistered(let file, let buffer, let offset, let userData): + file: file, buffer: buffer, offset: offset, context: context, request: request) + case .readUnregistered(let file, let buffer, let offset, let context): request.operation = .read return makeRawRequest_readWrite_unregistered( - file: file, buffer: buffer, offset: offset, userData: userData, request: request) - case .readUnregisteredSlot(let file, let buffer, let offset, let userData): + file: file, buffer: buffer, offset: offset, context: context, request: request) + case .readUnregisteredSlot(let file, let buffer, let offset, let context): request.operation = .read return makeRawRequest_readWrite_unregistered_slot( - file: file, buffer: buffer, offset: offset, userData: userData, request: request) - case .close(let file, let userData): + file: file, buffer: buffer, offset: offset, context: context, request: request) + case .close(let file, let context): request.operation = .close request.fileDescriptor = file - request.rawValue.user_data = userData - case .closeSlot(let file, let userData): + request.rawValue.user_data = context + case .closeSlot(let file, let context): request.operation = .close request.rawValue.file_index = UInt32(file.index + 1) - request.rawValue.user_data = userData - case .unlinkAt(let atDirectory, let path, let userData): + request.rawValue.user_data = context + case .unlinkAt(let atDirectory, let path, let context): request.operation = .unlinkAt request.fileDescriptor = atDirectory request.rawValue.addr = UInt64( @@ -376,7 +376,7 @@ extension IORequest { }) ) request.path = path - request.rawValue.user_data = userData + request.rawValue.user_data = context } return request } From 261ca250fe3d097247b903e141396dc28ee76c37 Mon Sep 17 00:00:00 2001 From: David Smith Date: Fri, 18 Apr 2025 22:20:09 +0000 Subject: [PATCH 326/427] Centralize on Errno for errors, and work around lifetime compiler crash. There's a second compiler crash still to work around though. --- Sources/System/IORing.swift | 159 ++++++++++++------------------------ 1 file changed, 52 insertions(+), 107 deletions(-) diff --git a/Sources/System/IORing.swift b/Sources/System/IORing.swift index e72c9e89..6c994b94 100644 --- a/Sources/System/IORing.swift +++ b/Sources/System/IORing.swift @@ -118,7 +118,7 @@ internal func _enter( numEvents: UInt32, minCompletions: UInt32, flags: UInt32 -) throws(IORing.OperationError) -> Int32 { +) throws(Errno) -> Int32 { // Ring always needs enter right now; // TODO: support SQPOLL here while true { @@ -133,14 +133,14 @@ internal func _enter( //TODO: should we wait a bit on AGAIN? continue } else if ret < 0 { - throw(IORing.OperationError(result: -ret)) + throw(Errno(rawValue: -ret)) } else { return ret } } } -internal func _submitRequests(ring: borrowing SQRing, ringDescriptor: Int32) throws(IORing.OperationError) { +internal func _submitRequests(ring: borrowing SQRing, ringDescriptor: Int32) throws(Errno) { let flushedEvents = _flushQueue(ring: ring) _ = try _enter( ringDescriptor: ringDescriptor, numEvents: flushedEvents, minCompletions: 0, flags: 0) @@ -193,7 +193,7 @@ internal func _getSubmissionEntry( private func setUpRing( queueDepth: UInt32, flags: IORing.SetupFlags, submissionRing: inout SQRing -) throws(IORing.SetupError) -> +) throws(Errno) -> (params: io_uring_params, ringDescriptor: Int32, ringPtr: UnsafeMutableRawPointer, ringSize: Int, sqes: UnsafeMutableRawPointer) { var params = io_uring_params() params.flags = flags.rawValue @@ -208,7 +208,7 @@ private func setUpRing( } if let err { - throw IORing.SetupError(setupResult: err.rawValue) + throw err } if params.features & IORING_FEAT_SINGLE_MMAP == 0 @@ -216,7 +216,7 @@ private func setUpRing( { close(ringDescriptor) // TODO: error handling - throw .missingRequiredFeatures + throw Errno.invalidArgument } let submitRingSize = @@ -239,9 +239,10 @@ private func setUpRing( ) if ringPtr == MAP_FAILED { + let errno = Errno.current perror("mmap") close(ringDescriptor) - throw .mapFailed + throw errno } let submissionRing = SQRing( @@ -285,52 +286,17 @@ private func setUpRing( ) if sqes == MAP_FAILED { + let errno = Errno.current perror("mmap") munmap(ringPtr, ringSize) close(ringDescriptor) - throw .mapFailed + throw errno } return (params: params, ringDescriptor: ringDescriptor, ringPtr: ringPtr!, ringSize: ringSize, sqes: sqes!) } public struct IORing: ~Copyable { - - /// Errors in either submitting operations or receiving operation completions - @frozen - public struct OperationError: Error, Hashable { - private var errorCode: Int - public static var operationCanceled: OperationError { .init(result: ECANCELED) } - - internal init(result: Int32) { - errorCode = Int(result) //TODO, flesh this out - } - } - - @frozen - public struct SetupError: Error, Hashable { - private var errorCode: Int - - public static var missingRequiredFeatures: SetupError { - .init(setupResult: -1) /* TODO: numeric value */ - } - - public static var mapFailed: SetupError { .init(setupResult: -2) } - - internal init(setupResult: Int32) { - errorCode = Int(setupResult) //TODO, flesh this out - } - } - - @frozen - public struct RegistrationError: Error, Hashable { - private var errorCode: Int - - internal init(registrationResult: Int32) { - errorCode = Int(registrationResult) //TODO, flesh this out - } - } - let ringFlags: UInt32 let ringDescriptor: Int32 @@ -346,8 +312,8 @@ public struct IORing: ~Copyable { let ringSize: Int let ringPtr: UnsafeMutableRawPointer - var _registeredFiles: [UInt32] = [] - var _registeredBuffers: [iovec] = [] + var _registeredFiles: [UInt32] + var _registeredBuffers: [iovec] var features = Features(rawValue: 0) @@ -375,7 +341,7 @@ public struct IORing: ~Copyable { //TODO: should IORING_SETUP_NO_SQARRAY be the default? do we need to adapt anything to it? } - public init(queueDepth: UInt32, flags: SetupFlags) throws(SetupError) { + public init(queueDepth: UInt32, flags: SetupFlags) throws(Errno) { let (params, tmpRingDescriptor, tmpRingPtr, tmpRingSize, sqes) = try setUpRing(queueDepth: queueDepth, flags: flags, submissionRing: &submissionRing) // All throws need to be before initializing ivars here to avoid // "error: conditional initialization or destruction of noncopyable types is not supported; @@ -384,6 +350,8 @@ public struct IORing: ~Copyable { ringDescriptor = tmpRingDescriptor ringPtr = tmpRingPtr ringSize = tmpRingSize + _registeredFiles = [] + _registeredBuffers = [] submissionQueueEntries = UnsafeMutableBufferPointer( start: sqes.assumingMemoryBound(to: io_uring_sqe.self), @@ -417,13 +385,13 @@ public struct IORing: ~Copyable { minimumCount: UInt32, maximumCount: UInt32, extraArgs: UnsafeMutablePointer? = nil, - consumer: (consuming IOCompletion?, OperationError?, Bool) throws(Err) -> Void + consumer: (consuming IOCompletion?, Errno?, Bool) throws(Err) -> Void ) throws(Err) { var count = 0 while let completion = _tryConsumeCompletion(ring: completionRing) { count += 1 if completion.result < 0 { - try consumer(nil, OperationError(result: completion.result), false) + try consumer(nil, Errno(rawValue: -completion.result), false) } else { try consumer(completion, nil, false) } @@ -469,7 +437,7 @@ public struct IORing: ~Copyable { while let completion = _tryConsumeCompletion(ring: completionRing) { count += 1 if completion.result < 0 { - try consumer(nil, OperationError(result: completion.result), false) + try consumer(nil, Errno(rawValue: -completion.result), false) } else { try consumer(completion, nil, false) } @@ -483,52 +451,37 @@ public struct IORing: ~Copyable { internal func _blockingConsumeOneCompletion( extraArgs: UnsafeMutablePointer? = nil - ) throws(OperationError) -> IOCompletion { + ) throws(Errno) -> IOCompletion { var result: IOCompletion? = nil - do { - try _blockingConsumeCompletionGuts(minimumCount: 1, maximumCount: 1, extraArgs: extraArgs) { - (completion: consuming IOCompletion?, error, done) in - if let error { - throw error - } - if let completion { - result = consume completion - } + try _blockingConsumeCompletionGuts(minimumCount: 1, maximumCount: 1, extraArgs: extraArgs) { + (completion: consuming IOCompletion?, error, done) throws(Errno) in + if let error { + throw error + } + if let completion { + result = consume completion } - } catch (let e) { - throw e as! OperationError //TODO: why is this needed? } return result.take()! } public func blockingConsumeCompletion( timeout: Duration? = nil - ) throws(OperationError) -> IOCompletion { + ) throws(Errno) -> IOCompletion { if let timeout { var ts = __kernel_timespec( tv_sec: timeout.components.seconds, tv_nsec: timeout.components.attoseconds / 1_000_000_000 ) - var err: OperationError? = nil - var result: IOCompletion? = nil - result = withUnsafePointer(to: &ts) { tsPtr in + return try withUnsafePointer(to: &ts) { (tsPtr) throws(Errno) -> IOCompletion in var args = io_uring_getevents_arg( sigmask: 0, sigmask_sz: 0, pad: 0, ts: UInt64(UInt(bitPattern: tsPtr)) ) - do { - return try _blockingConsumeOneCompletion(extraArgs: &args) - } catch (let e) { - err = (e as! OperationError) - return nil - } - } - guard let result else { - throw(err!) + return try _blockingConsumeOneCompletion(extraArgs: &args) } - return result } else { return try _blockingConsumeOneCompletion() } @@ -537,31 +490,23 @@ public struct IORing: ~Copyable { public func blockingConsumeCompletions( minimumCount: UInt32 = 1, timeout: Duration? = nil, - consumer: (consuming IOCompletion?, OperationError?, Bool) throws(Err) -> Void + consumer: (consuming IOCompletion?, Errno?, Bool) throws(Err) -> Void ) throws(Err) { if let timeout { var ts = __kernel_timespec( tv_sec: timeout.components.seconds, tv_nsec: timeout.components.attoseconds / 1_000_000_000 ) - var err: Err? = nil - withUnsafePointer(to: &ts) { tsPtr in + try withUnsafePointer(to: &ts) { (tsPtr) throws(Err) in var args = io_uring_getevents_arg( sigmask: 0, sigmask_sz: 0, pad: 0, ts: UInt64(UInt(bitPattern: tsPtr)) ) - do { - try _blockingConsumeCompletionGuts( - minimumCount: minimumCount, maximumCount: UInt32.max, extraArgs: &args, - consumer: consumer) - } catch (let e) { - err = (e as! Err) //TODO: why is `e` coming in as `any Error`? That seems wrong - } - } - if let err { - throw(err) + try _blockingConsumeCompletionGuts( + minimumCount: minimumCount, maximumCount: UInt32.max, extraArgs: &args, + consumer: consumer) } } else { try _blockingConsumeCompletionGuts( @@ -591,7 +536,7 @@ public struct IORing: ~Copyable { return nil } - public mutating func registerEventFD(_ descriptor: FileDescriptor) throws(RegistrationError) { + public mutating func registerEventFD(_ descriptor: FileDescriptor) throws(Errno) { var rawfd = descriptor.rawValue let result = withUnsafePointer(to: &rawfd) { fdptr in let result = io_uring_register( @@ -600,14 +545,14 @@ public struct IORing: ~Copyable { UnsafeMutableRawPointer(mutating: fdptr), 1 ) - return result >= 0 ? nil : Errno.current + return result >= 0 ? nil : Errno(rawValue: -result) } if let result { - throw(RegistrationError(registrationResult: result.rawValue)) + throw result } } - public mutating func unregisterEventFD() throws(RegistrationError) { + public mutating func unregisterEventFD() throws(Errno) { let result = io_uring_register( ringDescriptor, IORING_UNREGISTER_EVENTFD, @@ -615,11 +560,11 @@ public struct IORing: ~Copyable { 0 ) if result < 0 { - throw(RegistrationError(registrationResult: result)) + throw Errno(rawValue: -result) } } - public mutating func registerFileSlots(count: Int) throws(RegistrationError) -> RegisteredResources< + public mutating func registerFileSlots(count: Int) throws(Errno) -> RegisteredResources< IORingFileSlot.Resource > { precondition(_registeredFiles.isEmpty) @@ -633,11 +578,11 @@ public struct IORing: ~Copyable { UnsafeMutableRawPointer(mutating: bPtr.baseAddress!), UInt32(truncatingIfNeeded: count) ) - return result >= 0 ? nil : Errno.current + return result >= 0 ? nil : Errno(rawValue: -result) } - - guard regResult == nil else { - throw RegistrationError(registrationResult: regResult!.rawValue) + + if let regResult { + throw regResult } _registeredFiles = files @@ -652,7 +597,7 @@ public struct IORing: ~Copyable { RegisteredResources(resources: _registeredFiles) } - public mutating func registerBuffers(_ buffers: some Collection) throws(RegistrationError) + public mutating func registerBuffers(_ buffers: some Collection) throws(Errno) -> RegisteredResources { precondition(buffers.count < UInt32.max) @@ -666,11 +611,11 @@ public struct IORing: ~Copyable { UnsafeMutableRawPointer(mutating: bPtr.baseAddress!), UInt32(truncatingIfNeeded: buffers.count) ) - return result >= 0 ? nil : Errno.current + return result >= 0 ? nil : Errno(rawValue: -result) } - guard regResult == nil else { - throw RegistrationError(registrationResult: regResult!.rawValue) + if let regResult { + throw regResult } // TODO: error handling @@ -678,7 +623,7 @@ public struct IORing: ~Copyable { return registeredBuffers } - public mutating func registerBuffers(_ buffers: UnsafeMutableRawBufferPointer...) throws(RegistrationError) + public mutating func registerBuffers(_ buffers: UnsafeMutableRawBufferPointer...) throws(Errno) -> RegisteredResources { try registerBuffers(buffers) @@ -708,14 +653,14 @@ public struct IORing: ~Copyable { fatalError("failed to unregister buffers: TODO") } - public func submitPreparedRequests() throws(OperationError) { + public func submitPreparedRequests() throws(Errno) { try _submitRequests(ring: submissionRing, ringDescriptor: ringDescriptor) } public func submitPreparedRequestsAndConsumeCompletions( minimumCount: UInt32 = 1, timeout: Duration? = nil, - consumer: (consuming IOCompletion?, OperationError?, Bool) throws(Err) -> Void + consumer: (consuming IOCompletion?, Errno?, Bool) throws(Err) -> Void ) throws(Err) { //TODO: optimize this to one uring_enter do { @@ -757,7 +702,7 @@ public struct IORing: ~Copyable { prepare(linkedRequests: linkedRequests) } - public mutating func submit(linkedRequests: IORequest...) throws(OperationError) { + public mutating func submit(linkedRequests: IORequest...) throws(Errno) { prepare(linkedRequests: linkedRequests) try submitPreparedRequests() } From c029c1bfbd0da1f8672229dc80f39626617c1abb Mon Sep 17 00:00:00 2001 From: David Smith Date: Fri, 18 Apr 2025 15:22:17 -0700 Subject: [PATCH 327/427] Proposal updates and a minor change to setup flags --- NNNN-swift-system-io-uring.md | 59 ++++++++++++++++++----------------- Sources/System/IORing.swift | 2 +- 2 files changed, 31 insertions(+), 30 deletions(-) diff --git a/NNNN-swift-system-io-uring.md b/NNNN-swift-system-io-uring.md index 5c08588b..0d8cf8ec 100644 --- a/NNNN-swift-system-io-uring.md +++ b/NNNN-swift-system-io-uring.md @@ -79,8 +79,6 @@ Since neither polling nor synchronously waiting is optimal in many cases, `IORin Unfortunately the underlying kernel API makes it relatively difficult to determine which `IORequest` led to a given `IOCompletion`, so it's expected that users will need to create this association themselves via the context parameter. -`IORingError` represents failure of an operation. - `IORing.Features` describes the supported features of the underlying kernel `IORing` implementation, which can be used to provide graceful reduction in functionality when running on older systems. ## Detailed design @@ -97,10 +95,22 @@ extension IORingBuffer { // IORing is intentionally not Sendable, to avoid internal locking overhead public struct IORing: ~Copyable { - public init(queueDepth: UInt32) throws(IORingError) + public init(queueDepth: UInt32, flags: IORing.SetupFlags) throws(Errno) + + public struct SetupFlags: OptionSet, RawRepresentable, Hashable { + public var rawValue: UInt32 + public init(rawValue: UInt32) + public static var pollCompletions: SetupFlags //IORING_SETUP_IOPOLL + public static var pollSubmissions: SetupFlags //IORING_SETUP_SQPOLL + public static var clampMaxEntries: SetupFlags //IORING_SETUP_CLAMP + public static var startDisabled: SetupFlags //IORING_SETUP_R_DISABLED + public static var continueSubmittingOnError: SetupFlags //IORING_SETUP_SUBMIT_ALL + public static var singleSubmissionThread: SetupFlags //IORING_SETUP_SINGLE_ISSUER + public static var deferRunningTasks: SetupFlags //IORING_SETUP_DEFER_TASKRUN + } - public mutating func registerEventFD(_ descriptor: FileDescriptor) throws(IORingError) - public mutating func unregisterEventFD(_ descriptor: FileDescriptor) throws(IORingError) + public mutating func registerEventFD(_ descriptor: FileDescriptor) throws(Errno) + public mutating func unregisterEventFD(_ descriptor: FileDescriptor) throws(Errno) // An IORing.RegisteredResources is a view into the buffers or files registered with the ring, if any public struct RegisteredResources: RandomAccessCollection { @@ -108,7 +118,7 @@ public struct IORing: ~Copyable { public subscript(position: UInt16) -> IOResource // This is useful because io_uring likes to use UInt16s as indexes } - public mutating func registerFileSlots(count: Int) throws(IORingError) -> RegisteredResources + public mutating func registerFileSlots(count: Int) throws(Errno) -> RegisteredResources public func unregisterFiles() @@ -116,11 +126,11 @@ public struct IORing: ~Copyable { public mutating func registerBuffers( _ buffers: some Collection - ) throws(IORingError) -> RegisteredResources + ) throws(Errno) -> RegisteredResources public mutating func registerBuffers( _ buffers: UnsafeMutableRawBufferPointer... - ) throws(IORingError) -> RegisteredResources + ) throws(Errno) -> RegisteredResources public func unregisterBuffers() @@ -129,32 +139,32 @@ public struct IORing: ~Copyable { public func prepare(requests: IORequest...) public func prepare(linkedRequests: IORequest...) - public func submitPreparedRequests(timeout: Duration? = nil) throws(IORingError) - public func submit(requests: IORequest..., timeout: Duration? = nil) throws(IORingError) - public func submit(linkedRequests: IORequest..., timeout: Duration? = nil) throws(IORingError) + public func submitPreparedRequests(timeout: Duration? = nil) throws(Errno) + public func submit(requests: IORequest..., timeout: Duration? = nil) throws(Errno) + public func submit(linkedRequests: IORequest..., timeout: Duration? = nil) throws(Errno) - public func submitPreparedRequests() throws(IORingError) - public func submitPreparedRequestsAndWait(timeout: Duration? = nil) throws(IORingError) + public func submitPreparedRequests() throws(Errno) + public func submitPreparedRequestsAndWait(timeout: Duration? = nil) throws(Errno) public func submitPreparedRequestsAndConsumeCompletions( minimumCount: UInt32 = 1, timeout: Duration? = nil, - consumer: (consuming IOCompletion?, IORingError?, Bool) throws(E) -> Void + consumer: (consuming IOCompletion?, Errno?, Bool) throws(E) -> Void ) throws(E) public func blockingConsumeCompletion( timeout: Duration? = nil - ) throws(IORingError) -> IOCompletion + ) throws(Errno) -> IOCompletion public func blockingConsumeCompletions( minimumCount: UInt32 = 1, timeout: Duration? = nil, - consumer: (consuming IOCompletion?, IORingError?, Bool) throws(E) -> Void + consumer: (consuming IOCompletion?, Errno?, Bool) throws(E) -> Void ) throws(E) public func tryConsumeCompletion() -> IOCompletion? - public struct Features: OptionSet { + public struct Features: OptionSet, RawRepresentable, Hashable { let rawValue: UInt32 public init(rawValue: UInt32) @@ -176,7 +186,7 @@ public struct IORing: ~Copyable { public static let minimumTimeout: Bool //IORING_FEAT_MIN_TIMEOUT public static let bundledSendReceive: Bool //IORING_FEAT_RECVSEND_BUNDLE } - public static var supportedFeatures: Features + public var supportedFeatures: Features } public struct IORequest: ~Copyable { @@ -329,19 +339,10 @@ public struct IOCompletion { public var result: Int32 - public var error: IORingError? // Convenience wrapper over `result` + public var error: Errno? // Convenience wrapper over `result` public var flags: Flags } - -public struct IORingError: Error, Equatable { - static var missingRequiredFeatures: IORingError - static var operationCanceled: IORingError - static var timedOut: IORingError - static var resourceRegistrationFailed: IORingError - // Other error values to be filled out as the set of supported operations expands in the future - static var unknown: IORingError(errorCode: Int) -} ``` @@ -463,7 +464,7 @@ func submitLinkedRequestsAndWait( * We could multiplex all IO onto a single actor as `AsyncBytes` currently does, but this has a number of downsides that make it entirely unsuitable to server usage. Most notably, it eliminates IO parallelism entirely. * Using POSIX AIO instead of or as well as io_uring would greatly increase our ability to support older kernels and other Unix systems, but it has well-documented performance and usability issues that have prevented its adoption elsewhere, and apply just as much to Swift. * Earlier versions of this proposal had higher level "managed" abstractions over IORing. These have been removed due to lack of interest from clients, but could be added back later if needed. -* I considered making any or all of `IORingError`, `IOCompletion`, and `IORequest` nested struct declarations inside `IORing`. The main reason I haven't done so is I was a little concerned about the ambiguity of having a type called `Error`. I'd be particularly interested in feedback on this choice. +* I considered having dedicated error types for IORing, but eventually decided throwing Errno was more consistent with other platform APIs * IOResource was originally a class in an attempt to manage the lifetime of the resource via language features. Changing to the current model of it being a copyable struct didn't make the lifetime management any less safe (the IORing still owns the actual resource), and reduces overhead. In the future it would be neat if we could express IOResources as being borrowed from the IORing so they can't be used after its lifetime. ## Acknowledgments diff --git a/Sources/System/IORing.swift b/Sources/System/IORing.swift index 6c994b94..81efe128 100644 --- a/Sources/System/IORing.swift +++ b/Sources/System/IORing.swift @@ -318,7 +318,7 @@ public struct IORing: ~Copyable { var features = Features(rawValue: 0) @frozen - public struct SetupFlags: OptionSet, RawRepresentable { + public struct SetupFlags: OptionSet, RawRepresentable, Hashable { public var rawValue: UInt32 @inlinable public init(rawValue: UInt32) { From 05eac87c08b651d93c644fade578fea4bf13f1cd Mon Sep 17 00:00:00 2001 From: David Smith Date: Fri, 18 Apr 2025 23:37:50 +0000 Subject: [PATCH 328/427] It compiles again! --- Sources/CSystem/include/io_uring.h | 8 ++++---- Sources/System/IORing.swift | 7 ++++++- Tests/SystemTests/IORingTests.swift | 4 ++-- 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/Sources/CSystem/include/io_uring.h b/Sources/CSystem/include/io_uring.h index 5cab757c..75a07e57 100644 --- a/Sources/CSystem/include/io_uring.h +++ b/Sources/CSystem/include/io_uring.h @@ -34,25 +34,25 @@ # endif #endif -int io_uring_register(int fd, unsigned int opcode, void *arg, +static inline int io_uring_register(int fd, unsigned int opcode, void *arg, unsigned int nr_args) { return syscall(__NR_io_uring_register, fd, opcode, arg, nr_args); } -int io_uring_setup(unsigned int entries, struct io_uring_params *p) +static inline int io_uring_setup(unsigned int entries, struct io_uring_params *p) { return syscall(__NR_io_uring_setup, entries, p); } -int io_uring_enter2(int fd, unsigned int to_submit, unsigned int min_complete, +static inline int io_uring_enter2(int fd, unsigned int to_submit, unsigned int min_complete, unsigned int flags, void *args, size_t sz) { return syscall(__NR_io_uring_enter, fd, to_submit, min_complete, flags, args, _NSIG / 8); } -int io_uring_enter(int fd, unsigned int to_submit, unsigned int min_complete, +static inline int io_uring_enter(int fd, unsigned int to_submit, unsigned int min_complete, unsigned int flags, sigset_t *sig) { return io_uring_enter2(fd, to_submit, min_complete, flags, sig, _NSIG / 8); diff --git a/Sources/System/IORing.swift b/Sources/System/IORing.swift index 81efe128..9b45f564 100644 --- a/Sources/System/IORing.swift +++ b/Sources/System/IORing.swift @@ -654,7 +654,12 @@ public struct IORing: ~Copyable { } public func submitPreparedRequests() throws(Errno) { - try _submitRequests(ring: submissionRing, ringDescriptor: ringDescriptor) + switch submissionRing { + case .some(let submissionRing): + try _submitRequests(ring: submissionRing, ringDescriptor: ringDescriptor) + case .none: + fatalError() + } } public func submitPreparedRequestsAndConsumeCompletions( diff --git a/Tests/SystemTests/IORingTests.swift b/Tests/SystemTests/IORingTests.swift index 306516be..57437e4d 100644 --- a/Tests/SystemTests/IORingTests.swift +++ b/Tests/SystemTests/IORingTests.swift @@ -8,11 +8,11 @@ import System final class IORingTests: XCTestCase { func testInit() throws { - _ = try IORing(queueDepth: 32) + _ = try IORing(queueDepth: 32, flags: []) } func testNop() throws { - var ring = try IORing(queueDepth: 32) + var ring = try IORing(queueDepth: 32, flags: []) try ring.submit(linkedRequests: IORequest.nop()) let completion = try ring.blockingConsumeCompletion() XCTAssertEqual(completion.result, 0) From 28ce8353633af7ba7b3e78f9405693bb79bdec4d Mon Sep 17 00:00:00 2001 From: Jake Petroules Date: Fri, 18 Apr 2025 21:18:59 -0700 Subject: [PATCH 329/427] Enable Windows CI FileOperationsTestWindows.testPermissions fails on Windows in a container environment with Hyper-V isolated containers; see https://learn.microsoft.com/en-us/virtualization/windowscontainers/manage-containers/persistent-storage#permissions for some potentially-relevant information. Skip it if we detect we're in a container. Closes #223 --- .github/workflows/pull_request.yml | 2 -- Tests/SystemTests/FileOperationsTestWindows.swift | 6 ++++++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index 6ab954b5..d543be29 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -10,8 +10,6 @@ jobs: uses: swiftlang/github-workflows/.github/workflows/swift_package_test.yml@main with: linux_exclude_swift_versions: '[{"swift_version": "5.8"}]' - # https://github.com/apple/swift-system/issues/223 - enable_windows_checks: false soundness: name: Soundness uses: swiftlang/github-workflows/.github/workflows/soundness.yml@main diff --git a/Tests/SystemTests/FileOperationsTestWindows.swift b/Tests/SystemTests/FileOperationsTestWindows.swift index 82030516..7b87b354 100644 --- a/Tests/SystemTests/FileOperationsTestWindows.swift +++ b/Tests/SystemTests/FileOperationsTestWindows.swift @@ -174,6 +174,9 @@ final class FileOperationsTestWindows: XCTestCase { /// Test that the umask works properly func testUmask() throws { + // See https://learn.microsoft.com/en-us/virtualization/windowscontainers/manage-containers/persistent-storage#permissions + try XCTSkipIf(NSUserName() == "ContainerAdministrator", "containers use a different permission model") + // Default mask should be 0o022 XCTAssertEqual(FilePermissions.creationMask, [.groupWrite, .otherWrite]) @@ -205,6 +208,9 @@ final class FileOperationsTestWindows: XCTestCase { /// Test that setting permissions on a file works as expected func testPermissions() throws { + // See https://learn.microsoft.com/en-us/virtualization/windowscontainers/manage-containers/persistent-storage#permissions + try XCTSkipIf(NSUserName() == "ContainerAdministrator", "containers use a different permission model") + try FilePermissions.withCreationMask([]) { try withTemporaryFilePath(basename: "testPermissions") { path in let tests = [ From 7798f0c9577319dab7fe5080c9ed4bc4e856c785 Mon Sep 17 00:00:00 2001 From: David Smith Date: Mon, 21 Apr 2025 23:18:33 +0000 Subject: [PATCH 330/427] Add cancellation support and drop gerunds --- Sources/System/IORequest.swift | 116 ++++++++++++++++++++++--- Sources/System/RawIORequest.swift | 11 +++ Tests/SystemTests/IORequestTests.swift | 2 +- 3 files changed, 115 insertions(+), 14 deletions(-) diff --git a/Sources/System/IORequest.swift b/Sources/System/IORequest.swift index c836fd32..df505210 100644 --- a/Sources/System/IORequest.swift +++ b/Sources/System/IORequest.swift @@ -81,6 +81,18 @@ internal enum IORequestCore { path: FilePath, context: UInt64 = 0 ) + case cancel( + flags: UInt32, + targetContext: UInt64 + ) + case cancelFD( + flags: UInt32, + targetFD: FileDescriptor + ) + case cancelFDSlot( + flags: UInt32, + target: IORingFileSlot + ) } @inline(__always) @@ -160,7 +172,7 @@ extension IORequest { IORequest(core: .nop) } - public static func reading( + public static func read( _ file: IORingFileSlot, into buffer: IORingBuffer, at offset: UInt64 = 0, @@ -169,7 +181,7 @@ extension IORequest { IORequest(core: .readSlot(file: file, buffer: buffer, offset: offset, context: context)) } - public static func reading( + public static func read( _ file: FileDescriptor, into buffer: IORingBuffer, at offset: UInt64 = 0, @@ -178,7 +190,7 @@ extension IORequest { IORequest(core: .read(file: file, buffer: buffer, offset: offset, context: context)) } - public static func reading( + public static func read( _ file: IORingFileSlot, into buffer: UnsafeMutableRawBufferPointer, at offset: UInt64 = 0, @@ -189,7 +201,7 @@ extension IORequest { file: file, buffer: buffer, offset: offset, context: context)) } - public static func reading( + public static func read( _ file: FileDescriptor, into buffer: UnsafeMutableRawBufferPointer, at offset: UInt64 = 0, @@ -199,7 +211,7 @@ extension IORequest { core: .readUnregistered(file: file, buffer: buffer, offset: offset, context: context)) } - public static func writing( + public static func write( _ buffer: IORingBuffer, into file: IORingFileSlot, at offset: UInt64 = 0, @@ -208,7 +220,7 @@ extension IORequest { IORequest(core: .writeSlot(file: file, buffer: buffer, offset: offset, context: context)) } - public static func writing( + public static func write( _ buffer: IORingBuffer, into file: FileDescriptor, at offset: UInt64 = 0, @@ -217,7 +229,7 @@ extension IORequest { IORequest(core: .write(file: file, buffer: buffer, offset: offset, context: context)) } - public static func writing( + public static func write( _ buffer: UnsafeMutableRawBufferPointer, into file: IORingFileSlot, at offset: UInt64 = 0, @@ -228,7 +240,7 @@ extension IORequest { file: file, buffer: buffer, offset: offset, context: context)) } - public static func writing( + public static func write( _ buffer: UnsafeMutableRawBufferPointer, into file: FileDescriptor, at offset: UInt64 = 0, @@ -239,21 +251,21 @@ extension IORequest { ) } - public static func closing( + public static func close( _ file: FileDescriptor, context: UInt64 = 0 ) -> IORequest { IORequest(core: .close(file, context: context)) } - public static func closing( + public static func close( _ file: IORingFileSlot, context: UInt64 = 0 ) -> IORequest { IORequest(core: .closeSlot(file, context: context)) } - public static func opening( + public static func open( _ path: FilePath, in directory: FileDescriptor, into slot: IORingFileSlot, @@ -268,7 +280,7 @@ extension IORequest { permissions: permissions, intoSlot: slot, context: context)) } - public static func opening( + public static func open( _ path: FilePath, in directory: FileDescriptor, mode: FileDescriptor.AccessMode, @@ -283,7 +295,7 @@ extension IORequest { )) } - public static func unlinking( + public static func unlink( _ path: FilePath, in directory: FileDescriptor, context: UInt64 = 0 @@ -291,6 +303,71 @@ extension IORequest { IORequest(core: .unlinkAt(atDirectory: directory, path: path, context: context)) } + // Cancel + + /* + * ASYNC_CANCEL flags. + * + * IORING_ASYNC_CANCEL_ALL Cancel all requests that match the given key + * IORING_ASYNC_CANCEL_FD Key off 'fd' for cancelation rather than the + * request 'user_data' + * IORING_ASYNC_CANCEL_ANY Match any request + * IORING_ASYNC_CANCEL_FD_FIXED 'fd' passed in is a fixed descriptor + * IORING_ASYNC_CANCEL_USERDATA Match on user_data, default for no other key + * IORING_ASYNC_CANCEL_OP Match request based on opcode + */ + //TODO: why aren't these showing up from the header import? + private static var IORING_ASYNC_CANCEL_ALL: UInt32 { (1 as UInt32) << 0 } + private static var IORING_ASYNC_CANCEL_FD: UInt32 { (1 as UInt32) << 1 } + private static var IORING_ASYNC_CANCEL_ANY: UInt32 { (1 as UInt32) << 2 } + private static var IORING_ASYNC_CANCEL_FD_FIXED: UInt32 { (1 as UInt32) << 3 } + private static var IORING_ASYNC_CANCEL_USERDATA: UInt32 { (1 as UInt32) << 4 } + private static var IORING_ASYNC_CANCEL_OP: UInt32 { (1 as UInt32) << 5 } + + + public enum CancellationMatch { + case all + case first + } + + public static func cancel( + _ matchAll: CancellationMatch, + matchingContext: UInt64, + ) -> IORequest { + switch matchAll { + case .all: + IORequest(core: .cancel(flags: IORING_ASYNC_CANCEL_ALL | IORING_ASYNC_CANCEL_USERDATA, targetContext: matchingContext)) + case .first: + IORequest(core: .cancel(flags: IORING_ASYNC_CANCEL_ANY | IORING_ASYNC_CANCEL_USERDATA, targetContext: matchingContext)) + } + } + + public static func cancel( + _ matchAll: CancellationMatch, + matchingFileDescriptor: FileDescriptor, + ) -> IORequest { + switch matchAll { + case .all: + IORequest(core: .cancelFD(flags: IORING_ASYNC_CANCEL_ALL | IORING_ASYNC_CANCEL_FD, targetFD: matchingFileDescriptor)) + case .first: + IORequest(core: .cancelFD(flags: IORING_ASYNC_CANCEL_ANY | IORING_ASYNC_CANCEL_FD, targetFD: matchingFileDescriptor)) + } + } + + public static func cancel( + _ matchAll: CancellationMatch, + matchingFile: IORingFileSlot, + ) -> IORequest { + switch matchAll { + case .all: + IORequest(core: .cancelFDSlot(flags: IORING_ASYNC_CANCEL_ALL | IORING_ASYNC_CANCEL_FD_FIXED, target: matchingFile)) + case .first: + IORequest(core: .cancelFDSlot(flags: IORING_ASYNC_CANCEL_ANY | IORING_ASYNC_CANCEL_FD_FIXED, target: matchingFile)) + } + } + + //TODO: add support for CANCEL_OP + @inline(__always) public consuming func makeRawRequest() -> RawIORequest { var request = RawIORequest() @@ -377,7 +454,20 @@ extension IORequest { ) request.path = path request.rawValue.user_data = context + case .cancel(let flags, let targetContext): + request.operation = .asyncCancel + request.cancel_flags = flags + request.addr = targetContext + case .cancelFD(let flags, let targetFD): + request.operation = .asyncCancel + request.cancel_flags = flags + request.fileDescriptor = targetFD + case .cancelFDSlot(let flags, let target): + request.operation = .asyncCancel + request.cancel_flags = flags + request.rawValue.fd = Int32(target.index) } + return request } } diff --git a/Sources/System/RawIORequest.swift b/Sources/System/RawIORequest.swift index 50c97c61..6190b0da 100644 --- a/Sources/System/RawIORequest.swift +++ b/Sources/System/RawIORequest.swift @@ -26,6 +26,7 @@ extension RawIORequest { case sendMessage = 9 case receiveMessage = 10 // ... + case asyncCancel = 14 case link_timeout = 15 // ... case openAt = 18 @@ -61,6 +62,16 @@ extension RawIORequest { set { rawValue.opcode = newValue.rawValue } } + var cancel_flags: UInt32 { + get { rawValue.cancel_flags } + set { rawValue.cancel_flags = newValue } + } + + var addr: UInt64 { + get { rawValue.addr } + set { rawValue.addr = newValue } + } + public var flags: Flags { get { Flags(rawValue: rawValue.flags) } set { rawValue.flags = newValue.rawValue } diff --git a/Tests/SystemTests/IORequestTests.swift b/Tests/SystemTests/IORequestTests.swift index 4aaf7543..6642293e 100644 --- a/Tests/SystemTests/IORequestTests.swift +++ b/Tests/SystemTests/IORequestTests.swift @@ -30,7 +30,7 @@ final class IORequestTests: XCTestCase { func testOpenatFixedFile() throws { let pathPtr = UnsafePointer(bitPattern: 0x414141410badf00d)! let fileSlot: IORingFileSlot = IORingFileSlot(resource: UInt32.max, index: 0) - let req = IORequest.opening(FilePath(platformString: pathPtr), + let req = IORequest.open(FilePath(platformString: pathPtr), in: FileDescriptor(rawValue: -100), into: fileSlot, mode: .readOnly, From 9d325b29ceb88760b55a5900d6cb796e5cbc1a20 Mon Sep 17 00:00:00 2001 From: David Smith Date: Mon, 21 Apr 2025 23:23:26 +0000 Subject: [PATCH 331/427] Change IORingFileSlot and IORingBuffer to be nested types in IORing --- Sources/System/IORequest.swift | 52 +++++++++++++------------- Sources/System/IORing.swift | 38 +++++++++---------- Tests/SystemTests/IORequestTests.swift | 2 +- 3 files changed, 46 insertions(+), 46 deletions(-) diff --git a/Sources/System/IORequest.swift b/Sources/System/IORequest.swift index df505210..f89facdb 100644 --- a/Sources/System/IORequest.swift +++ b/Sources/System/IORequest.swift @@ -17,12 +17,12 @@ internal enum IORequestCore { FileDescriptor.AccessMode, options: FileDescriptor.OpenOptions = FileDescriptor.OpenOptions(), permissions: FilePermissions? = nil, - intoSlot: IORingFileSlot, + intoSlot: IORing.RegisteredFile, context: UInt64 = 0 ) case read( file: FileDescriptor, - buffer: IORingBuffer, + buffer: IORing.RegisteredBuffer, offset: UInt64 = 0, context: UInt64 = 0 ) @@ -33,20 +33,20 @@ internal enum IORequestCore { context: UInt64 = 0 ) case readSlot( - file: IORingFileSlot, - buffer: IORingBuffer, + file: IORing.RegisteredFile, + buffer: IORing.RegisteredBuffer, offset: UInt64 = 0, context: UInt64 = 0 ) case readUnregisteredSlot( - file: IORingFileSlot, + file: IORing.RegisteredFile, buffer: UnsafeMutableRawBufferPointer, offset: UInt64 = 0, context: UInt64 = 0 ) case write( file: FileDescriptor, - buffer: IORingBuffer, + buffer: IORing.RegisteredBuffer, offset: UInt64 = 0, context: UInt64 = 0 ) @@ -57,13 +57,13 @@ internal enum IORequestCore { context: UInt64 = 0 ) case writeSlot( - file: IORingFileSlot, - buffer: IORingBuffer, + file: IORing.RegisteredFile, + buffer: IORing.RegisteredBuffer, offset: UInt64 = 0, context: UInt64 = 0 ) case writeUnregisteredSlot( - file: IORingFileSlot, + file: IORing.RegisteredFile, buffer: UnsafeMutableRawBufferPointer, offset: UInt64 = 0, context: UInt64 = 0 @@ -73,7 +73,7 @@ internal enum IORequestCore { context: UInt64 = 0 ) case closeSlot( - IORingFileSlot, + IORing.RegisteredFile, context: UInt64 = 0 ) case unlinkAt( @@ -91,14 +91,14 @@ internal enum IORequestCore { ) case cancelFDSlot( flags: UInt32, - target: IORingFileSlot + target: IORing.RegisteredFile ) } @inline(__always) internal func makeRawRequest_readWrite_registered( file: FileDescriptor, - buffer: IORingBuffer, + buffer: IORing.RegisteredBuffer, offset: UInt64, context: UInt64 = 0, request: consuming RawIORequest @@ -113,8 +113,8 @@ internal func makeRawRequest_readWrite_registered( @inline(__always) internal func makeRawRequest_readWrite_registered_slot( - file: IORingFileSlot, - buffer: IORingBuffer, + file: IORing.RegisteredFile, + buffer: IORing.RegisteredBuffer, offset: UInt64, context: UInt64 = 0, request: consuming RawIORequest @@ -145,7 +145,7 @@ internal func makeRawRequest_readWrite_unregistered( @inline(__always) internal func makeRawRequest_readWrite_unregistered_slot( - file: IORingFileSlot, + file: IORing.RegisteredFile, buffer: UnsafeMutableRawBufferPointer, offset: UInt64, context: UInt64 = 0, @@ -173,8 +173,8 @@ extension IORequest { } public static func read( - _ file: IORingFileSlot, - into buffer: IORingBuffer, + _ file: IORing.RegisteredFile, + into buffer: IORing.RegisteredBuffer, at offset: UInt64 = 0, context: UInt64 = 0 ) -> IORequest { @@ -183,7 +183,7 @@ extension IORequest { public static func read( _ file: FileDescriptor, - into buffer: IORingBuffer, + into buffer: IORing.RegisteredBuffer, at offset: UInt64 = 0, context: UInt64 = 0 ) -> IORequest { @@ -191,7 +191,7 @@ extension IORequest { } public static func read( - _ file: IORingFileSlot, + _ file: IORing.RegisteredFile, into buffer: UnsafeMutableRawBufferPointer, at offset: UInt64 = 0, context: UInt64 = 0 @@ -212,8 +212,8 @@ extension IORequest { } public static func write( - _ buffer: IORingBuffer, - into file: IORingFileSlot, + _ buffer: IORing.RegisteredBuffer, + into file: IORing.RegisteredFile, at offset: UInt64 = 0, context: UInt64 = 0 ) -> IORequest { @@ -221,7 +221,7 @@ extension IORequest { } public static func write( - _ buffer: IORingBuffer, + _ buffer: IORing.RegisteredBuffer, into file: FileDescriptor, at offset: UInt64 = 0, context: UInt64 = 0 @@ -231,7 +231,7 @@ extension IORequest { public static func write( _ buffer: UnsafeMutableRawBufferPointer, - into file: IORingFileSlot, + into file: IORing.RegisteredFile, at offset: UInt64 = 0, context: UInt64 = 0 ) -> IORequest { @@ -259,7 +259,7 @@ extension IORequest { } public static func close( - _ file: IORingFileSlot, + _ file: IORing.RegisteredFile, context: UInt64 = 0 ) -> IORequest { IORequest(core: .closeSlot(file, context: context)) @@ -268,7 +268,7 @@ extension IORequest { public static func open( _ path: FilePath, in directory: FileDescriptor, - into slot: IORingFileSlot, + into slot: IORing.RegisteredFile, mode: FileDescriptor.AccessMode, options: FileDescriptor.OpenOptions = FileDescriptor.OpenOptions(), permissions: FilePermissions? = nil, @@ -356,7 +356,7 @@ extension IORequest { public static func cancel( _ matchAll: CancellationMatch, - matchingFile: IORingFileSlot, + matchingFile: IORing.RegisteredFile, ) -> IORequest { switch matchAll { case .all: diff --git a/Sources/System/IORing.swift b/Sources/System/IORing.swift index 9b45f564..89391b02 100644 --- a/Sources/System/IORing.swift +++ b/Sources/System/IORing.swift @@ -66,20 +66,6 @@ public struct IOResource { } } -public typealias IORingFileSlot = IOResource -public typealias IORingBuffer = IOResource - -extension IORingFileSlot { - public var unsafeFileSlot: Int { - return index - } -} -extension IORingBuffer { - public var unsafeBuffer: UnsafeMutableRawBufferPointer { - return .init(start: resource.iov_base, count: resource.iov_len) - } -} - @inline(__always) internal func _writeRequest( _ request: __owned RawIORequest, ring: inout SQRing, @@ -317,6 +303,9 @@ public struct IORing: ~Copyable { var features = Features(rawValue: 0) + public typealias RegisteredFile = IOResource + public typealias RegisteredBuffer = IOResource + @frozen public struct SetupFlags: OptionSet, RawRepresentable, Hashable { public var rawValue: UInt32 @@ -565,7 +554,7 @@ public struct IORing: ~Copyable { } public mutating func registerFileSlots(count: Int) throws(Errno) -> RegisteredResources< - IORingFileSlot.Resource + IORing.RegisteredFile.Resource > { precondition(_registeredFiles.isEmpty) precondition(count < UInt32.max) @@ -593,12 +582,12 @@ public struct IORing: ~Copyable { fatalError("failed to unregister files") } - public var registeredFileSlots: RegisteredResources { + public var registeredFileSlots: RegisteredResources { RegisteredResources(resources: _registeredFiles) } public mutating func registerBuffers(_ buffers: some Collection) throws(Errno) - -> RegisteredResources + -> RegisteredResources { precondition(buffers.count < UInt32.max) precondition(_registeredBuffers.isEmpty) @@ -624,7 +613,7 @@ public struct IORing: ~Copyable { } public mutating func registerBuffers(_ buffers: UnsafeMutableRawBufferPointer...) throws(Errno) - -> RegisteredResources + -> RegisteredResources { try registerBuffers(buffers) } @@ -645,7 +634,7 @@ public struct IORing: ~Copyable { } } - public var registeredBuffers: RegisteredResources { + public var registeredBuffers: RegisteredResources { RegisteredResources(resources: _registeredBuffers) } @@ -750,3 +739,14 @@ public struct IORing: ~Copyable { close(ringDescriptor) } } + +extension IORing.RegisteredFile { + public var unsafeFileSlot: Int { + return index + } +} +extension IORing.RegisteredBuffer { + public var unsafeBuffer: UnsafeMutableRawBufferPointer { + return .init(start: resource.iov_base, count: resource.iov_len) + } +} \ No newline at end of file diff --git a/Tests/SystemTests/IORequestTests.swift b/Tests/SystemTests/IORequestTests.swift index 6642293e..9705026f 100644 --- a/Tests/SystemTests/IORequestTests.swift +++ b/Tests/SystemTests/IORequestTests.swift @@ -29,7 +29,7 @@ final class IORequestTests: XCTestCase { func testOpenatFixedFile() throws { let pathPtr = UnsafePointer(bitPattern: 0x414141410badf00d)! - let fileSlot: IORingFileSlot = IORingFileSlot(resource: UInt32.max, index: 0) + let fileSlot = IORing.RegisteredFile(resource: UInt32.max, index: 0) let req = IORequest.open(FilePath(platformString: pathPtr), in: FileDescriptor(rawValue: -100), into: fileSlot, From ea77ef8a21e45e28c267c68a6f318fe578acae69 Mon Sep 17 00:00:00 2001 From: David Smith Date: Mon, 21 Apr 2025 23:24:09 +0000 Subject: [PATCH 332/427] Minor cleanup --- Sources/System/IORing.swift | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Sources/System/IORing.swift b/Sources/System/IORing.swift index 89391b02..54e3b104 100644 --- a/Sources/System/IORing.swift +++ b/Sources/System/IORing.swift @@ -553,9 +553,7 @@ public struct IORing: ~Copyable { } } - public mutating func registerFileSlots(count: Int) throws(Errno) -> RegisteredResources< - IORing.RegisteredFile.Resource - > { + public mutating func registerFileSlots(count: Int) throws(Errno) -> RegisteredResources { precondition(_registeredFiles.isEmpty) precondition(count < UInt32.max) let files = [UInt32](repeating: UInt32.max, count: count) From baa3d625693df48a7882c689350431fa7794e361 Mon Sep 17 00:00:00 2001 From: David Smith Date: Mon, 21 Apr 2025 23:26:14 +0000 Subject: [PATCH 333/427] API tweak --- Sources/System/IORequest.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/System/IORequest.swift b/Sources/System/IORequest.swift index f89facdb..4be37d32 100644 --- a/Sources/System/IORequest.swift +++ b/Sources/System/IORequest.swift @@ -344,7 +344,7 @@ extension IORequest { public static func cancel( _ matchAll: CancellationMatch, - matchingFileDescriptor: FileDescriptor, + matching: FileDescriptor, ) -> IORequest { switch matchAll { case .all: @@ -356,7 +356,7 @@ extension IORequest { public static func cancel( _ matchAll: CancellationMatch, - matchingFile: IORing.RegisteredFile, + matching: IORing.RegisteredFile, ) -> IORequest { switch matchAll { case .all: From 3760dc265ac2ebce5dfa87d030148edc2e7b0d30 Mon Sep 17 00:00:00 2001 From: David Smith Date: Mon, 21 Apr 2025 16:35:08 -0700 Subject: [PATCH 334/427] Nest IOResource, and update proposal to match --- NNNN-swift-system-io-uring.md | 70 ++++++++++++++++------------------- Sources/System/IORing.swift | 34 ++++++++--------- 2 files changed, 48 insertions(+), 56 deletions(-) diff --git a/NNNN-swift-system-io-uring.md b/NNNN-swift-system-io-uring.md index 0d8cf8ec..2bd981d8 100644 --- a/NNNN-swift-system-io-uring.md +++ b/NNNN-swift-system-io-uring.md @@ -33,7 +33,7 @@ We propose a *low level, unopinionated* Swift interface for io_uring on Linux (s * Enqueueing IO requests * Dequeueing IO completions -`struct IOResource` represents, via its two typealiases `IORingFileSlot` and `IORingBuffer`, registered file descriptors and buffers. +`struct RegisteredResource` represents, via its two typealiases `IORing.RegisteredFile` and `IORing.RegisteredBuffer`, registered file descriptors and buffers. `struct IORequest: ~Copyable` represents an IO operation that can be enqueued for the kernel to execute. It supports a wide variety of operations matching traditional unix file and socket operations. @@ -43,7 +43,7 @@ IORequest operations are expressed as overloaded static methods on `IORequest`, public static func open( _ path: FilePath, in directory: FileDescriptor, - into slot: IORingFileSlot, + into slot: IORing.RegisteredFile, mode: FileDescriptor.AccessMode, options: FileDescriptor.OpenOptions = FileDescriptor.OpenOptions(), permissions: FilePermissions? = nil, @@ -84,14 +84,6 @@ Unfortunately the underlying kernel API makes it relatively difficult to determi ## Detailed design ```swift -public struct IOResource { } -public typealias IORingFileSlot = IOResource -public typealias IORingBuffer = IOResource - -extension IORingBuffer { - public var unsafeBuffer: UnsafeMutableRawBufferPointer -} - // IORing is intentionally not Sendable, to avoid internal locking overhead public struct IORing: ~Copyable { @@ -111,6 +103,10 @@ public struct IORing: ~Copyable { public mutating func registerEventFD(_ descriptor: FileDescriptor) throws(Errno) public mutating func unregisterEventFD(_ descriptor: FileDescriptor) throws(Errno) + + public struct RegisteredResource { } + public typealias RegisteredFile = RegisteredResource + public typealias RegisteredBuffer = RegisteredResource // An IORing.RegisteredResources is a view into the buffers or files registered with the ring, if any public struct RegisteredResources: RandomAccessCollection { @@ -118,23 +114,23 @@ public struct IORing: ~Copyable { public subscript(position: UInt16) -> IOResource // This is useful because io_uring likes to use UInt16s as indexes } - public mutating func registerFileSlots(count: Int) throws(Errno) -> RegisteredResources + public mutating func registerFileSlots(count: Int) throws(Errno) -> RegisteredResources public func unregisterFiles() - public var registeredFileSlots: RegisteredResources + public var registeredFileSlots: RegisteredResources public mutating func registerBuffers( _ buffers: some Collection - ) throws(Errno) -> RegisteredResources + ) throws(Errno) -> RegisteredResources public mutating func registerBuffers( _ buffers: UnsafeMutableRawBufferPointer... - ) throws(Errno) -> RegisteredResources + ) throws(Errno) -> RegisteredResources public func unregisterBuffers() - public var registeredBuffers: RegisteredResources + public var registeredBuffers: RegisteredResources public func prepare(requests: IORequest...) public func prepare(linkedRequests: IORequest...) @@ -189,27 +185,31 @@ public struct IORing: ~Copyable { public var supportedFeatures: Features } +extension IORing.RegisteredBuffer { + public var unsafeBuffer: UnsafeMutableRawBufferPointer +} + public struct IORequest: ~Copyable { public static func nop(context: UInt64 = 0) -> IORequest // overloads for each combination of registered vs unregistered buffer/descriptor // Read public static func read( - _ file: IORingFileSlot, - into buffer: IORingBuffer, + _ file: IORing.RegisteredFile, + into buffer: IORing.RegisteredBuffer, at offset: UInt64 = 0, context: UInt64 = 0 ) -> IORequest public static func read( _ file: FileDescriptor, - into buffer: IORingBuffer, + into buffer: IORing.RegisteredBuffer, at offset: UInt64 = 0, context: UInt64 = 0 ) -> IORequest public static func read( - _ file: IORingFileSlot, + _ file: IORing.RegisteredFile, into buffer: UnsafeMutableRawBufferPointer, at offset: UInt64 = 0, context: UInt64 = 0 @@ -224,14 +224,14 @@ public struct IORequest: ~Copyable { // Write public static func write( - _ buffer: IORingBuffer, - into file: IORingFileSlot, + _ buffer: IORing.RegisteredBuffer, + into file: IORing.RegisteredFile, at offset: UInt64 = 0, context: UInt64 = 0 ) -> IORequest public static func write( - _ buffer: IORingBuffer, + _ buffer: IORing.RegisteredBuffer, into file: FileDescriptor, at offset: UInt64 = 0, context: UInt64 = 0 @@ -239,7 +239,7 @@ public struct IORequest: ~Copyable { public static func write( _ buffer: UnsafeMutableRawBufferPointer, - into file: IORingFileSlot, + into file: IORing.RegisteredFile, at offset: UInt64 = 0, context: UInt64 = 0 ) -> IORequest @@ -258,7 +258,7 @@ public struct IORequest: ~Copyable { ) -> IORequest public static func close( - _ file: IORingFileSlot, + _ file: IORing.RegisteredFile, context: UInt64 = 0 ) -> IORequest @@ -266,7 +266,7 @@ public struct IORequest: ~Copyable { public static func open( _ path: FilePath, in directory: FileDescriptor, - into slot: IORingFileSlot, + into slot: IORing.RegisteredFile, mode: FileDescriptor.AccessMode, options: FileDescriptor.OpenOptions = FileDescriptor.OpenOptions(), permissions: FilePermissions? = nil, @@ -296,26 +296,18 @@ public struct IORequest: ~Copyable { } public static func cancel( - _ matchAll: CancellationMatch, - matchingContext: UInt64, - context: UInt64 - ) -> IORequest - - public static func cancel( - _ matchAll: CancellationMatch, - matchingFileDescriptor: FileDescriptor, - context: UInt64 + _ matchAll: CancellationMatch, + matchingContext: UInt64, ) -> IORequest public static func cancel( - _ matchAll: CancellationMatch, - matchingRegisteredFileDescriptorAtIndex: Int, - context: UInt64 + _ matchAll: CancellationMatch, + matching: FileDescriptor, ) -> IORequest public static func cancel( - _ matchAll: CancellationMatch, - context: UInt64 + _ matchAll: CancellationMatch, + matching: IORing.RegisteredFile, ) -> IORequest // Other operations follow in the same pattern diff --git a/Sources/System/IORing.swift b/Sources/System/IORing.swift index 54e3b104..7d69aa35 100644 --- a/Sources/System/IORing.swift +++ b/Sources/System/IORing.swift @@ -52,20 +52,6 @@ struct CQRing: ~Copyable { let cqes: UnsafeBufferPointer } -public struct IOResource { - public typealias Resource = T - @usableFromInline let resource: T - @usableFromInline let index: Int - - internal init( - resource: T, - index: Int - ) { - self.resource = resource - self.index = index - } -} - @inline(__always) internal func _writeRequest( _ request: __owned RawIORequest, ring: inout SQRing, @@ -302,9 +288,23 @@ public struct IORing: ~Copyable { var _registeredBuffers: [iovec] var features = Features(rawValue: 0) + + public struct RegisteredResource { + public typealias Resource = T + @usableFromInline let resource: T + @usableFromInline let index: Int + + internal init( + resource: T, + index: Int + ) { + self.resource = resource + self.index = index + } + } - public typealias RegisteredFile = IOResource - public typealias RegisteredBuffer = IOResource + public typealias RegisteredFile = RegisteredResource + public typealias RegisteredBuffer = RegisteredResource @frozen public struct SetupFlags: OptionSet, RawRepresentable, Hashable { @@ -747,4 +747,4 @@ extension IORing.RegisteredBuffer { public var unsafeBuffer: UnsafeMutableRawBufferPointer { return .init(start: resource.iov_base, count: resource.iov_len) } -} \ No newline at end of file +} From a98cd3f2dae5cdd397f71e4cc6d549229a570bf7 Mon Sep 17 00:00:00 2001 From: David Smith Date: Mon, 21 Apr 2025 23:47:00 +0000 Subject: [PATCH 335/427] Build fix and rename IOResource --- Sources/System/IORequest.swift | 8 ++++---- Sources/System/IORing.swift | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Sources/System/IORequest.swift b/Sources/System/IORequest.swift index 4be37d32..b5d48af3 100644 --- a/Sources/System/IORequest.swift +++ b/Sources/System/IORequest.swift @@ -348,9 +348,9 @@ extension IORequest { ) -> IORequest { switch matchAll { case .all: - IORequest(core: .cancelFD(flags: IORING_ASYNC_CANCEL_ALL | IORING_ASYNC_CANCEL_FD, targetFD: matchingFileDescriptor)) + IORequest(core: .cancelFD(flags: IORING_ASYNC_CANCEL_ALL | IORING_ASYNC_CANCEL_FD, targetFD: matching)) case .first: - IORequest(core: .cancelFD(flags: IORING_ASYNC_CANCEL_ANY | IORING_ASYNC_CANCEL_FD, targetFD: matchingFileDescriptor)) + IORequest(core: .cancelFD(flags: IORING_ASYNC_CANCEL_ANY | IORING_ASYNC_CANCEL_FD, targetFD: matching)) } } @@ -360,9 +360,9 @@ extension IORequest { ) -> IORequest { switch matchAll { case .all: - IORequest(core: .cancelFDSlot(flags: IORING_ASYNC_CANCEL_ALL | IORING_ASYNC_CANCEL_FD_FIXED, target: matchingFile)) + IORequest(core: .cancelFDSlot(flags: IORING_ASYNC_CANCEL_ALL | IORING_ASYNC_CANCEL_FD_FIXED, target: matching)) case .first: - IORequest(core: .cancelFDSlot(flags: IORING_ASYNC_CANCEL_ANY | IORING_ASYNC_CANCEL_FD_FIXED, target: matchingFile)) + IORequest(core: .cancelFDSlot(flags: IORING_ASYNC_CANCEL_ANY | IORING_ASYNC_CANCEL_FD_FIXED, target: matching)) } } diff --git a/Sources/System/IORing.swift b/Sources/System/IORing.swift index 7d69aa35..76f68182 100644 --- a/Sources/System/IORing.swift +++ b/Sources/System/IORing.swift @@ -624,11 +624,11 @@ public struct IORing: ~Copyable { init(resources: [T]) { self.resources = resources } - public subscript(position: Int) -> IOResource { - IOResource(resource: resources[position], index: position) + public subscript(position: Int) -> RegisteredResource { + RegisteredResource(resource: resources[position], index: position) } - public subscript(position: UInt16) -> IOResource { - IOResource(resource: resources[Int(position)], index: Int(position)) + public subscript(position: UInt16) -> RegisteredResource { + RegisteredResource(resource: resources[Int(position)], index: Int(position)) } } From 344b3b85f8bb0d8014efb27927e639fb2dae5d62 Mon Sep 17 00:00:00 2001 From: David Smith Date: Mon, 21 Apr 2025 16:59:39 -0700 Subject: [PATCH 336/427] Proposal updates --- NNNN-swift-system-io-uring.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/NNNN-swift-system-io-uring.md b/NNNN-swift-system-io-uring.md index 2bd981d8..978b87bd 100644 --- a/NNNN-swift-system-io-uring.md +++ b/NNNN-swift-system-io-uring.md @@ -108,7 +108,7 @@ public struct IORing: ~Copyable { public typealias RegisteredFile = RegisteredResource public typealias RegisteredBuffer = RegisteredResource - // An IORing.RegisteredResources is a view into the buffers or files registered with the ring, if any + // A `RegisteredResources` is a view into the buffers or files registered with the ring, if any public struct RegisteredResources: RandomAccessCollection { public subscript(position: Int) -> IOResource public subscript(position: UInt16) -> IOResource // This is useful because io_uring likes to use UInt16s as indexes @@ -355,7 +355,7 @@ ring.prepare(linkedRequests: in: parentDirectory, into: file, mode: mode, - options: openOptions, + options: openOptions, permissions: nil ), .stat(file, From c47779fac00f63042684364a7011aefc541a187d Mon Sep 17 00:00:00 2001 From: David Smith Date: Tue, 22 Apr 2025 23:06:48 +0000 Subject: [PATCH 337/427] Try making requests and completions nested types --- Sources/System/IOCompletion.swift | 22 +++--- Sources/System/IORequest.swift | 92 +++++++++++++------------- Sources/System/IORing.swift | 30 ++++----- Tests/SystemTests/IORequestTests.swift | 4 +- Tests/SystemTests/IORingTests.swift | 2 +- 5 files changed, 76 insertions(+), 74 deletions(-) diff --git a/Sources/System/IOCompletion.swift b/Sources/System/IOCompletion.swift index b15f0d45..5fa29325 100644 --- a/Sources/System/IOCompletion.swift +++ b/Sources/System/IOCompletion.swift @@ -1,11 +1,13 @@ @_implementationOnly import CSystem -public struct IOCompletion: ~Copyable { - let rawValue: io_uring_cqe +public extension IORing { + struct Completion: ~Copyable { + let rawValue: io_uring_cqe + } } -extension IOCompletion { - public struct Flags: OptionSet, Hashable, Codable { +public extension IORing.Completion { + struct Flags: OptionSet, Hashable, Codable { public let rawValue: UInt32 public init(rawValue: UInt32) { @@ -19,32 +21,32 @@ extension IOCompletion { } } -extension IOCompletion { - public var context: UInt64 { +public extension IORing.Completion { + var context: UInt64 { get { rawValue.user_data } } - public var userPointer: UnsafeRawPointer? { + var userPointer: UnsafeRawPointer? { get { UnsafeRawPointer(bitPattern: UInt(rawValue.user_data)) } } - public var result: Int32 { + var result: Int32 { get { rawValue.res } } - public var flags: IOCompletion.Flags { + var flags: IORing.Completion.Flags { get { Flags(rawValue: rawValue.flags & 0x0000FFFF) } } - public var bufferIndex: UInt16? { + var bufferIndex: UInt16? { get { if self.flags.contains(.allocatedBuffer) { return UInt16(rawValue.flags >> 16) diff --git a/Sources/System/IORequest.swift b/Sources/System/IORequest.swift index b5d48af3..3c4d2a27 100644 --- a/Sources/System/IORequest.swift +++ b/Sources/System/IORequest.swift @@ -159,17 +159,21 @@ internal func makeRawRequest_readWrite_unregistered_slot( return request } -public struct IORequest { - @usableFromInline var core: IORequestCore +extension IORing { + public struct Request { + @usableFromInline var core: IORequestCore - @inlinable internal consuming func extractCore() -> IORequestCore { - return core + @inlinable internal consuming func extractCore() -> IORequestCore { + return core + } } } -extension IORequest { - public static func nop(context: UInt64 = 0) -> IORequest { - IORequest(core: .nop) + + +extension IORing.Request { + public static func nop(context: UInt64 = 0) -> IORing.Request { + .init(core: .nop) } public static func read( @@ -177,8 +181,8 @@ extension IORequest { into buffer: IORing.RegisteredBuffer, at offset: UInt64 = 0, context: UInt64 = 0 - ) -> IORequest { - IORequest(core: .readSlot(file: file, buffer: buffer, offset: offset, context: context)) + ) -> IORing.Request { + .init(core: .readSlot(file: file, buffer: buffer, offset: offset, context: context)) } public static func read( @@ -186,8 +190,8 @@ extension IORequest { into buffer: IORing.RegisteredBuffer, at offset: UInt64 = 0, context: UInt64 = 0 - ) -> IORequest { - IORequest(core: .read(file: file, buffer: buffer, offset: offset, context: context)) + ) -> IORing.Request { + .init(core: .read(file: file, buffer: buffer, offset: offset, context: context)) } public static func read( @@ -195,10 +199,8 @@ extension IORequest { into buffer: UnsafeMutableRawBufferPointer, at offset: UInt64 = 0, context: UInt64 = 0 - ) -> IORequest { - IORequest( - core: .readUnregisteredSlot( - file: file, buffer: buffer, offset: offset, context: context)) + ) -> IORing.Request { + .init(core: .readUnregisteredSlot(file: file, buffer: buffer, offset: offset, context: context)) } public static func read( @@ -206,9 +208,8 @@ extension IORequest { into buffer: UnsafeMutableRawBufferPointer, at offset: UInt64 = 0, context: UInt64 = 0 - ) -> IORequest { - IORequest( - core: .readUnregistered(file: file, buffer: buffer, offset: offset, context: context)) + ) -> IORing.Request { + .init(core: .readUnregistered(file: file, buffer: buffer, offset: offset, context: context)) } public static func write( @@ -216,8 +217,8 @@ extension IORequest { into file: IORing.RegisteredFile, at offset: UInt64 = 0, context: UInt64 = 0 - ) -> IORequest { - IORequest(core: .writeSlot(file: file, buffer: buffer, offset: offset, context: context)) + ) -> IORing.Request { + .init(core: .writeSlot(file: file, buffer: buffer, offset: offset, context: context)) } public static func write( @@ -225,8 +226,8 @@ extension IORequest { into file: FileDescriptor, at offset: UInt64 = 0, context: UInt64 = 0 - ) -> IORequest { - IORequest(core: .write(file: file, buffer: buffer, offset: offset, context: context)) + ) -> IORing.Request { + .init(core: .write(file: file, buffer: buffer, offset: offset, context: context)) } public static func write( @@ -234,9 +235,8 @@ extension IORequest { into file: IORing.RegisteredFile, at offset: UInt64 = 0, context: UInt64 = 0 - ) -> IORequest { - IORequest( - core: .writeUnregisteredSlot( + ) -> IORing.Request { + .init(core: .writeUnregisteredSlot( file: file, buffer: buffer, offset: offset, context: context)) } @@ -245,8 +245,8 @@ extension IORequest { into file: FileDescriptor, at offset: UInt64 = 0, context: UInt64 = 0 - ) -> IORequest { - IORequest( + ) -> IORing.Request { + .init( core: .writeUnregistered(file: file, buffer: buffer, offset: offset, context: context) ) } @@ -254,15 +254,15 @@ extension IORequest { public static func close( _ file: FileDescriptor, context: UInt64 = 0 - ) -> IORequest { - IORequest(core: .close(file, context: context)) + ) -> IORing.Request { + .init(core: .close(file, context: context)) } public static func close( _ file: IORing.RegisteredFile, context: UInt64 = 0 - ) -> IORequest { - IORequest(core: .closeSlot(file, context: context)) + ) -> IORing.Request { + .init(core: .closeSlot(file, context: context)) } public static func open( @@ -273,8 +273,8 @@ extension IORequest { options: FileDescriptor.OpenOptions = FileDescriptor.OpenOptions(), permissions: FilePermissions? = nil, context: UInt64 = 0 - ) -> IORequest { - IORequest( + ) -> IORing.Request { + .init( core: .openatSlot( atDirectory: directory, path: path, mode, options: options, permissions: permissions, intoSlot: slot, context: context)) @@ -287,8 +287,8 @@ extension IORequest { options: FileDescriptor.OpenOptions = FileDescriptor.OpenOptions(), permissions: FilePermissions? = nil, context: UInt64 = 0 - ) -> IORequest { - IORequest( + ) -> IORing.Request { + .init( core: .openat( atDirectory: directory, path: path, mode, options: options, permissions: permissions, context: context @@ -299,8 +299,8 @@ extension IORequest { _ path: FilePath, in directory: FileDescriptor, context: UInt64 = 0 - ) -> IORequest { - IORequest(core: .unlinkAt(atDirectory: directory, path: path, context: context)) + ) -> IORing.Request { + .init(core: .unlinkAt(atDirectory: directory, path: path, context: context)) } // Cancel @@ -333,36 +333,36 @@ extension IORequest { public static func cancel( _ matchAll: CancellationMatch, matchingContext: UInt64, - ) -> IORequest { + ) -> IORing.Request { switch matchAll { case .all: - IORequest(core: .cancel(flags: IORING_ASYNC_CANCEL_ALL | IORING_ASYNC_CANCEL_USERDATA, targetContext: matchingContext)) + .init(core: .cancel(flags: IORING_ASYNC_CANCEL_ALL | IORING_ASYNC_CANCEL_USERDATA, targetContext: matchingContext)) case .first: - IORequest(core: .cancel(flags: IORING_ASYNC_CANCEL_ANY | IORING_ASYNC_CANCEL_USERDATA, targetContext: matchingContext)) + .init(core: .cancel(flags: IORING_ASYNC_CANCEL_ANY | IORING_ASYNC_CANCEL_USERDATA, targetContext: matchingContext)) } } public static func cancel( _ matchAll: CancellationMatch, matching: FileDescriptor, - ) -> IORequest { + ) -> IORing.Request { switch matchAll { case .all: - IORequest(core: .cancelFD(flags: IORING_ASYNC_CANCEL_ALL | IORING_ASYNC_CANCEL_FD, targetFD: matching)) + .init(core: .cancelFD(flags: IORING_ASYNC_CANCEL_ALL | IORING_ASYNC_CANCEL_FD, targetFD: matching)) case .first: - IORequest(core: .cancelFD(flags: IORING_ASYNC_CANCEL_ANY | IORING_ASYNC_CANCEL_FD, targetFD: matching)) + .init(core: .cancelFD(flags: IORING_ASYNC_CANCEL_ANY | IORING_ASYNC_CANCEL_FD, targetFD: matching)) } } public static func cancel( _ matchAll: CancellationMatch, matching: IORing.RegisteredFile, - ) -> IORequest { + ) -> IORing.Request { switch matchAll { case .all: - IORequest(core: .cancelFDSlot(flags: IORING_ASYNC_CANCEL_ALL | IORING_ASYNC_CANCEL_FD_FIXED, target: matching)) + .init(core: .cancelFDSlot(flags: IORING_ASYNC_CANCEL_ALL | IORING_ASYNC_CANCEL_FD_FIXED, target: matching)) case .first: - IORequest(core: .cancelFDSlot(flags: IORING_ASYNC_CANCEL_ANY | IORING_ASYNC_CANCEL_FD_FIXED, target: matching)) + .init(core: .cancelFDSlot(flags: IORING_ASYNC_CANCEL_ANY | IORING_ASYNC_CANCEL_FD_FIXED, target: matching)) } } diff --git a/Sources/System/IORing.swift b/Sources/System/IORing.swift index 76f68182..dafedce2 100644 --- a/Sources/System/IORing.swift +++ b/Sources/System/IORing.swift @@ -374,7 +374,7 @@ public struct IORing: ~Copyable { minimumCount: UInt32, maximumCount: UInt32, extraArgs: UnsafeMutablePointer? = nil, - consumer: (consuming IOCompletion?, Errno?, Bool) throws(Err) -> Void + consumer: (consuming IORing.Completion?, Errno?, Bool) throws(Err) -> Void ) throws(Err) { var count = 0 while let completion = _tryConsumeCompletion(ring: completionRing) { @@ -440,10 +440,10 @@ public struct IORing: ~Copyable { internal func _blockingConsumeOneCompletion( extraArgs: UnsafeMutablePointer? = nil - ) throws(Errno) -> IOCompletion { - var result: IOCompletion? = nil + ) throws(Errno) -> Completion { + var result: Completion? = nil try _blockingConsumeCompletionGuts(minimumCount: 1, maximumCount: 1, extraArgs: extraArgs) { - (completion: consuming IOCompletion?, error, done) throws(Errno) in + (completion: consuming Completion?, error, done) throws(Errno) in if let error { throw error } @@ -456,13 +456,13 @@ public struct IORing: ~Copyable { public func blockingConsumeCompletion( timeout: Duration? = nil - ) throws(Errno) -> IOCompletion { + ) throws(Errno) -> Completion { if let timeout { var ts = __kernel_timespec( tv_sec: timeout.components.seconds, tv_nsec: timeout.components.attoseconds / 1_000_000_000 ) - return try withUnsafePointer(to: &ts) { (tsPtr) throws(Errno) -> IOCompletion in + return try withUnsafePointer(to: &ts) { (tsPtr) throws(Errno) -> Completion in var args = io_uring_getevents_arg( sigmask: 0, sigmask_sz: 0, @@ -479,7 +479,7 @@ public struct IORing: ~Copyable { public func blockingConsumeCompletions( minimumCount: UInt32 = 1, timeout: Duration? = nil, - consumer: (consuming IOCompletion?, Errno?, Bool) throws(Err) -> Void + consumer: (consuming Completion?, Errno?, Bool) throws(Err) -> Void ) throws(Err) { if let timeout { var ts = __kernel_timespec( @@ -507,11 +507,11 @@ public struct IORing: ~Copyable { // } - public func tryConsumeCompletion() -> IOCompletion? { + public func tryConsumeCompletion() -> Completion? { return _tryConsumeCompletion(ring: completionRing) } - func _tryConsumeCompletion(ring: borrowing CQRing) -> IOCompletion? { + func _tryConsumeCompletion(ring: borrowing CQRing) -> Completion? { let tail = ring.kernelTail.pointee.load(ordering: .acquiring) let head = ring.kernelHead.pointee.load(ordering: .acquiring) @@ -519,7 +519,7 @@ public struct IORing: ~Copyable { // 32 byte copy - oh well let res = ring.cqes[Int(head & ring.ringMask)] ring.kernelHead.pointee.store(head &+ 1, ordering: .releasing) - return IOCompletion(rawValue: res) + return Completion(rawValue: res) } return nil @@ -652,7 +652,7 @@ public struct IORing: ~Copyable { public func submitPreparedRequestsAndConsumeCompletions( minimumCount: UInt32 = 1, timeout: Duration? = nil, - consumer: (consuming IOCompletion?, Errno?, Bool) throws(Err) -> Void + consumer: (consuming Completion?, Errno?, Bool) throws(Err) -> Void ) throws(Err) { //TODO: optimize this to one uring_enter do { @@ -667,13 +667,13 @@ public struct IORing: ~Copyable { ) } - public mutating func prepare(request: __owned IORequest) -> Bool { + public mutating func prepare(request: __owned Request) -> Bool { var raw: RawIORequest? = request.makeRawRequest() return _writeRequest( raw.take()!, ring: &submissionRing, submissionQueueEntries: submissionQueueEntries) } - mutating func prepare(linkedRequests: some BidirectionalCollection) { + mutating func prepare(linkedRequests: some BidirectionalCollection) { guard linkedRequests.count > 0 else { return } @@ -690,11 +690,11 @@ public struct IORing: ~Copyable { } //@inlinable //TODO: make sure the array allocation gets optimized out... - public mutating func prepare(linkedRequests: IORequest...) { + public mutating func prepare(linkedRequests: Request...) { prepare(linkedRequests: linkedRequests) } - public mutating func submit(linkedRequests: IORequest...) throws(Errno) { + public mutating func submit(linkedRequests: Request...) throws(Errno) { prepare(linkedRequests: linkedRequests) try submitPreparedRequests() } diff --git a/Tests/SystemTests/IORequestTests.swift b/Tests/SystemTests/IORequestTests.swift index 9705026f..3d1f72de 100644 --- a/Tests/SystemTests/IORequestTests.swift +++ b/Tests/SystemTests/IORequestTests.swift @@ -19,7 +19,7 @@ func requestBytes(_ request: consuming RawIORequest) -> [UInt8] { // which are known to work correctly. final class IORequestTests: XCTestCase { func testNop() { - let req = IORequest.nop().makeRawRequest() + let req = IORing.Request.nop().makeRawRequest() let sourceBytes = requestBytes(req) // convenient property of nop: it's all zeros! // for some unknown reason, liburing sets the fd field to -1. @@ -30,7 +30,7 @@ final class IORequestTests: XCTestCase { func testOpenatFixedFile() throws { let pathPtr = UnsafePointer(bitPattern: 0x414141410badf00d)! let fileSlot = IORing.RegisteredFile(resource: UInt32.max, index: 0) - let req = IORequest.open(FilePath(platformString: pathPtr), + let req = IORing.Request.open(FilePath(platformString: pathPtr), in: FileDescriptor(rawValue: -100), into: fileSlot, mode: .readOnly, diff --git a/Tests/SystemTests/IORingTests.swift b/Tests/SystemTests/IORingTests.swift index 57437e4d..6c086da6 100644 --- a/Tests/SystemTests/IORingTests.swift +++ b/Tests/SystemTests/IORingTests.swift @@ -13,7 +13,7 @@ final class IORingTests: XCTestCase { func testNop() throws { var ring = try IORing(queueDepth: 32, flags: []) - try ring.submit(linkedRequests: IORequest.nop()) + try ring.submit(linkedRequests: .nop()) let completion = try ring.blockingConsumeCompletion() XCTAssertEqual(completion.result, 0) } From a49ba8a8b3194043d839d691a3229d8cb20a8309 Mon Sep 17 00:00:00 2001 From: David Smith Date: Tue, 22 Apr 2025 23:20:37 +0000 Subject: [PATCH 338/427] Give flags a default value --- Sources/System/IORing.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/System/IORing.swift b/Sources/System/IORing.swift index dafedce2..94c9504a 100644 --- a/Sources/System/IORing.swift +++ b/Sources/System/IORing.swift @@ -330,7 +330,7 @@ public struct IORing: ~Copyable { //TODO: should IORING_SETUP_NO_SQARRAY be the default? do we need to adapt anything to it? } - public init(queueDepth: UInt32, flags: SetupFlags) throws(Errno) { + public init(queueDepth: UInt32, flags: SetupFlags = []) throws(Errno) { let (params, tmpRingDescriptor, tmpRingPtr, tmpRingSize, sqes) = try setUpRing(queueDepth: queueDepth, flags: flags, submissionRing: &submissionRing) // All throws need to be before initializing ivars here to avoid // "error: conditional initialization or destruction of noncopyable types is not supported; From f38d72f310a133490e85f1fa50a64c49bd59b8a8 Mon Sep 17 00:00:00 2001 From: David Smith Date: Tue, 22 Apr 2025 16:30:50 -0700 Subject: [PATCH 339/427] Nested types, some renames, misc other updates --- NNNN-swift-system-io-uring.md | 333 +++++++++++++++++----------------- 1 file changed, 166 insertions(+), 167 deletions(-) diff --git a/NNNN-swift-system-io-uring.md b/NNNN-swift-system-io-uring.md index 978b87bd..37f0c5c9 100644 --- a/NNNN-swift-system-io-uring.md +++ b/NNNN-swift-system-io-uring.md @@ -28,16 +28,16 @@ We propose a *low level, unopinionated* Swift interface for io_uring on Linux (s `struct IORing: ~Copyable` provides facilities for -* Registering and unregistering resources (files and buffers), an `io_uring` specific variation on Unix file descriptors that improves their efficiency +* Registering and unregistering resources (files and buffers), an `io_uring` specific variation on Unix file IOdescriptors that improves their efficiency * Registering and unregistering eventfds, which allow asynchronous waiting for completions * Enqueueing IO requests * Dequeueing IO completions -`struct RegisteredResource` represents, via its two typealiases `IORing.RegisteredFile` and `IORing.RegisteredBuffer`, registered file descriptors and buffers. +`struct IORing.RegisteredResource` represents, via its two typealiases `IORing.RegisteredFile` and `IORing.RegisteredBuffer`, registered file descriptors and buffers. -`struct IORequest: ~Copyable` represents an IO operation that can be enqueued for the kernel to execute. It supports a wide variety of operations matching traditional unix file and socket operations. +`struct IORing.Request: ~Copyable` represents an IO operation that can be enqueued for the kernel to execute. It supports a wide variety of operations matching traditional unix file and socket operations. -IORequest operations are expressed as overloaded static methods on `IORequest`, e.g. `openat` is spelled +Request operations are expressed as overloaded static methods on `Request`, e.g. `openat` is spelled ```swift public static func open( @@ -48,7 +48,7 @@ IORequest operations are expressed as overloaded static methods on `IORequest`, options: FileDescriptor.OpenOptions = FileDescriptor.OpenOptions(), permissions: FilePermissions? = nil, context: UInt64 = 0 - ) -> IORequest + ) -> Request public static func open( _ path: FilePath, @@ -57,12 +57,12 @@ IORequest operations are expressed as overloaded static methods on `IORequest`, options: FileDescriptor.OpenOptions = FileDescriptor.OpenOptions(), permissions: FilePermissions? = nil, context: UInt64 = 0 - ) -> IORequest + ) -> Request ``` which allows clients to decide whether they want to open the file into a slot on the ring, or have it return a file descriptor via a completion. Similarly, read operations have overloads for "use a buffer from the ring" or "read into this `UnsafeMutableBufferPointer`" -Multiple `IORequests` can be enqueued on a single `IORing` using the `prepare(…)` family of methods, and then submitted together using `submitPreparedRequests`, allowing for things like "open this file, read its contents, and then close it" to be a single syscall. Conveniences are provided for preparing and submitting requests in one call. +Multiple `Requests` can be enqueued on a single `IORing` using the `prepare(…)` family of methods, and then submitted together using `submitPreparedRequests`, allowing for things like "open this file, read its contents, and then close it" to be a single syscall. Conveniences are provided for preparing and submitting requests in one call. Since IO operations can execute in parallel or out of order by default, linked chains of operations can be established with `prepare(linkedRequests:…)` and related methods. Separate chains can still execute in parallel, and if an operation early in the chain fails, all subsequent operations will deliver cancellation errors as their completion. @@ -70,14 +70,14 @@ Already-completed results can be retrieved from the ring using `tryConsumeComple Since neither polling nor synchronously waiting is optimal in many cases, `IORing` also exposes the ability to register an eventfd (see `man eventfd(2)`), which will become readable when completions are available on the ring. This can then be monitored asynchronously with `epoll`, `kqueue`, or for clients who are linking libdispatch, `DispatchSource`. -`struct IOCompletion: ~Copyable` represents the result of an IO operation and provides +`struct Completion: ~Copyable` represents the result of an IO operation and provides * Flags indicating various operation-specific metadata about the now-completed syscall * The context associated with the operation when it was enqueued, as an `UnsafeRawPointer` or a `UInt64` * The result of the operation, as an `Int32` with operation-specific meaning * The error, if one occurred -Unfortunately the underlying kernel API makes it relatively difficult to determine which `IORequest` led to a given `IOCompletion`, so it's expected that users will need to create this association themselves via the context parameter. +Unfortunately the underlying kernel API makes it relatively difficult to determine which `Request` led to a given `Completion`, so it's expected that users will need to create this association themselves via the context parameter. `IORing.Features` describes the supported features of the underlying kernel `IORing` implementation, which can be used to provide graceful reduction in functionality when running on older systems. @@ -87,37 +87,35 @@ Unfortunately the underlying kernel API makes it relatively difficult to determi // IORing is intentionally not Sendable, to avoid internal locking overhead public struct IORing: ~Copyable { - public init(queueDepth: UInt32, flags: IORing.SetupFlags) throws(Errno) - - public struct SetupFlags: OptionSet, RawRepresentable, Hashable { - public var rawValue: UInt32 - public init(rawValue: UInt32) - public static var pollCompletions: SetupFlags //IORING_SETUP_IOPOLL - public static var pollSubmissions: SetupFlags //IORING_SETUP_SQPOLL - public static var clampMaxEntries: SetupFlags //IORING_SETUP_CLAMP - public static var startDisabled: SetupFlags //IORING_SETUP_R_DISABLED - public static var continueSubmittingOnError: SetupFlags //IORING_SETUP_SUBMIT_ALL - public static var singleSubmissionThread: SetupFlags //IORING_SETUP_SINGLE_ISSUER - public static var deferRunningTasks: SetupFlags //IORING_SETUP_DEFER_TASKRUN - } - - public mutating func registerEventFD(_ descriptor: FileDescriptor) throws(Errno) - public mutating func unregisterEventFD(_ descriptor: FileDescriptor) throws(Errno) + public init(queueDepth: UInt32, flags: IORing.SetupFlags = []) throws(Errno) + + public struct SetupFlags: OptionSet, RawRepresentable, Hashable { + public var rawValue: UInt32 + public init(rawValue: UInt32) + public static var pollCompletions: SetupFlags //IORING_SETUP_IOPOLL + public static var pollSubmissions: SetupFlags //IORING_SETUP_SQPOLL + public static var clampMaxEntries: SetupFlags //IORING_SETUP_CLAMP + public static var startDisabled: SetupFlags //IORING_SETUP_R_DISABLED + public static var continueSubmittingOnError: SetupFlags //IORING_SETUP_SUBMIT_ALL + public static var singleSubmissionThread: SetupFlags //IORING_SETUP_SINGLE_ISSUER + public static var deferRunningTasks: SetupFlags //IORING_SETUP_DEFER_TASKRUN + } + + public mutating func registerEventFD(_ descriptor: FileDescriptor) throws(Errno) + public mutating func unregisterEventFD() throws(Errno) public struct RegisteredResource { } public typealias RegisteredFile = RegisteredResource public typealias RegisteredBuffer = RegisteredResource - - // A `RegisteredResources` is a view into the buffers or files registered with the ring, if any - public struct RegisteredResources: RandomAccessCollection { - public subscript(position: Int) -> IOResource - public subscript(position: UInt16) -> IOResource // This is useful because io_uring likes to use UInt16s as indexes - } + + // A `RegisteredResources` is a view into the buffers or files registered with the ring, if any + public struct RegisteredResources: RandomAccessCollection { + public subscript(position: Int) -> RegisteredResource + public subscript(position: UInt16) -> RegisteredResource // This is useful because io_uring likes to use UInt16s as indexes + } public mutating func registerFileSlots(count: Int) throws(Errno) -> RegisteredResources - public func unregisterFiles() - public var registeredFileSlots: RegisteredResources public mutating func registerBuffers( @@ -132,33 +130,33 @@ public struct IORing: ~Copyable { public var registeredBuffers: RegisteredResources - public func prepare(requests: IORequest...) - public func prepare(linkedRequests: IORequest...) + public func prepare(requests: Request...) + public func prepare(linkedRequests: Request...) public func submitPreparedRequests(timeout: Duration? = nil) throws(Errno) - public func submit(requests: IORequest..., timeout: Duration? = nil) throws(Errno) - public func submit(linkedRequests: IORequest..., timeout: Duration? = nil) throws(Errno) + public func submit(requests: Request..., timeout: Duration? = nil) throws(Errno) + public func submit(linkedRequests: Request..., timeout: Duration? = nil) throws(Errno) public func submitPreparedRequests() throws(Errno) public func submitPreparedRequestsAndWait(timeout: Duration? = nil) throws(Errno) public func submitPreparedRequestsAndConsumeCompletions( - minimumCount: UInt32 = 1, - timeout: Duration? = nil, - consumer: (consuming IOCompletion?, Errno?, Bool) throws(E) -> Void - ) throws(E) + minimumCount: UInt32 = 1, + timeout: Duration? = nil, + consumer: (consuming Completion?, Errno?, Bool) throws(E) -> Void + ) throws(E) public func blockingConsumeCompletion( - timeout: Duration? = nil - ) throws(Errno) -> IOCompletion + timeout: Duration? = nil + ) throws(Errno) -> Completion public func blockingConsumeCompletions( - minimumCount: UInt32 = 1, - timeout: Duration? = nil, - consumer: (consuming IOCompletion?, Errno?, Bool) throws(E) -> Void + minimumCount: UInt32 = 1, + timeout: Duration? = nil, + consumer: (consuming Completion?, Errno?, Bool) throws(E) -> Void ) throws(E) - public func tryConsumeCompletion() -> IOCompletion? + public func tryConsumeCompletion() -> Completion? public struct Features: OptionSet, RawRepresentable, Hashable { let rawValue: UInt32 @@ -185,155 +183,156 @@ public struct IORing: ~Copyable { public var supportedFeatures: Features } -extension IORing.RegisteredBuffer { - public var unsafeBuffer: UnsafeMutableRawBufferPointer +public extension IORing.RegisteredBuffer { + var unsafeBuffer: UnsafeMutableRawBufferPointer } -public struct IORequest: ~Copyable { - public static func nop(context: UInt64 = 0) -> IORequest - - // overloads for each combination of registered vs unregistered buffer/descriptor - // Read +public extension IORing { + struct Request: ~Copyable { + public static func nop(context: UInt64 = 0) -> Request + + // overloads for each combination of registered vs unregistered buffer/descriptor + // Read public static func read( - _ file: IORing.RegisteredFile, - into buffer: IORing.RegisteredBuffer, - at offset: UInt64 = 0, - context: UInt64 = 0 - ) -> IORequest - + _ file: IORing.RegisteredFile, + into buffer: IORing.RegisteredBuffer, + at offset: UInt64 = 0, + context: UInt64 = 0 + ) -> Request + public static func read( - _ file: FileDescriptor, - into buffer: IORing.RegisteredBuffer, - at offset: UInt64 = 0, - context: UInt64 = 0 - ) -> IORequest - + _ file: FileDescriptor, + into buffer: IORing.RegisteredBuffer, + at offset: UInt64 = 0, + context: UInt64 = 0 + ) -> Request + public static func read( - _ file: IORing.RegisteredFile, - into buffer: UnsafeMutableRawBufferPointer, - at offset: UInt64 = 0, - context: UInt64 = 0 - ) -> IORequest - + _ file: IORing.RegisteredFile, + into buffer: UnsafeMutableRawBufferPointer, + at offset: UInt64 = 0, + context: UInt64 = 0 + ) -> Request + public static func read( - _ file: FileDescriptor, - into buffer: UnsafeMutableRawBufferPointer, - at offset: UInt64 = 0, - context: UInt64 = 0 - ) -> IORequest - + _ file: FileDescriptor, + into buffer: UnsafeMutableRawBufferPointer, + at offset: UInt64 = 0, + context: UInt64 = 0 + ) -> Request + // Write public static func write( - _ buffer: IORing.RegisteredBuffer, - into file: IORing.RegisteredFile, - at offset: UInt64 = 0, - context: UInt64 = 0 - ) -> IORequest - + _ buffer: IORing.RegisteredBuffer, + into file: IORing.RegisteredFile, + at offset: UInt64 = 0, + context: UInt64 = 0 + ) -> Request + public static func write( - _ buffer: IORing.RegisteredBuffer, - into file: FileDescriptor, - at offset: UInt64 = 0, - context: UInt64 = 0 - ) -> IORequest - + _ buffer: IORing.RegisteredBuffer, + into file: FileDescriptor, + at offset: UInt64 = 0, + context: UInt64 = 0 + ) -> Request + public static func write( - _ buffer: UnsafeMutableRawBufferPointer, - into file: IORing.RegisteredFile, - at offset: UInt64 = 0, - context: UInt64 = 0 - ) -> IORequest - + _ buffer: UnsafeMutableRawBufferPointer, + into file: IORing.RegisteredFile, + at offset: UInt64 = 0, + context: UInt64 = 0 + ) -> Request + public static func write( - _ buffer: UnsafeMutableRawBufferPointer, - into file: FileDescriptor, - at offset: UInt64 = 0, - context: UInt64 = 0 - ) -> IORequest - + _ buffer: UnsafeMutableRawBufferPointer, + into file: FileDescriptor, + at offset: UInt64 = 0, + context: UInt64 = 0 + ) -> Request + // Close public static func close( - _ file: FileDescriptor, - context: UInt64 = 0 - ) -> IORequest - + _ file: FileDescriptor, + context: UInt64 = 0 + ) -> Request + public static func close( - _ file: IORing.RegisteredFile, - context: UInt64 = 0 - ) -> IORequest - + _ file: IORing.RegisteredFile, + context: UInt64 = 0 + ) -> Request + // Open At public static func open( - _ path: FilePath, - in directory: FileDescriptor, - into slot: IORing.RegisteredFile, - mode: FileDescriptor.AccessMode, - options: FileDescriptor.OpenOptions = FileDescriptor.OpenOptions(), - permissions: FilePermissions? = nil, - context: UInt64 = 0 - ) -> IORequest - + _ path: FilePath, + in directory: FileDescriptor, + into slot: IORing.RegisteredFile, + mode: FileDescriptor.AccessMode, + options: FileDescriptor.OpenOptions = FileDescriptor.OpenOptions(), + permissions: FilePermissions? = nil, + context: UInt64 = 0 + ) -> Request + public static func open( - _ path: FilePath, - in directory: FileDescriptor, - mode: FileDescriptor.AccessMode, - options: FileDescriptor.OpenOptions = FileDescriptor.OpenOptions(), - permissions: FilePermissions? = nil, - context: UInt64 = 0 - ) -> IORequest - + _ path: FilePath, + in directory: FileDescriptor, + mode: FileDescriptor.AccessMode, + options: FileDescriptor.OpenOptions = FileDescriptor.OpenOptions(), + permissions: FilePermissions? = nil, + context: UInt64 = 0 + ) -> Request + public static func unlink( - _ path: FilePath, - in directory: FileDescriptor, - context: UInt64 = 0 - ) -> IORequest - + _ path: FilePath, + in directory: FileDescriptor, + context: UInt64 = 0 + ) -> Request + // Cancel - + public enum CancellationMatch { - case all - case first + case all + case first } - + public static func cancel( _ matchAll: CancellationMatch, matchingContext: UInt64, - ) -> IORequest - + ) -> Request + public static func cancel( _ matchAll: CancellationMatch, matching: FileDescriptor, - ) -> IORequest - + ) -> Request + public static func cancel( _ matchAll: CancellationMatch, matching: IORing.RegisteredFile, - ) -> IORequest - - // Other operations follow in the same pattern -} + ) -> Request -public struct IOCompletion { + // Other operations follow in the same pattern + } + + struct Completion { + public struct Flags: OptionSet, Hashable, Codable { + public let rawValue: UInt32 - public struct Flags: OptionSet, Hashable, Codable { - public let rawValue: UInt32 + public init(rawValue: UInt32) - public init(rawValue: UInt32) - - public static let moreCompletions: Flags - public static let socketNotEmpty: Flags - public static let isNotificationEvent: Flags - } + public static let moreCompletions: Flags + public static let socketNotEmpty: Flags + public static let isNotificationEvent: Flags + } - //These are both the same value, but having both eliminates some ugly casts in client code - public var context: UInt64 - public var contextPointer: UnsafeRawPointer - - public var result: Int32 - - public var error: Errno? // Convenience wrapper over `result` - - public var flags: Flags + //These are both the same value, but having both eliminates some ugly casts in client code + public var context: UInt64 + public var contextPointer: UnsafeRawPointer + + public var result: Int32 + + public var error: Errno? // Convenience wrapper over `result` + + public var flags: Flags + } } ``` @@ -363,7 +362,7 @@ ring.prepare(linkedRequests: ) ) //batch submit 2 syscalls in 1! -try ring.submitPreparedRequestsAndConsumeCompletions(minimumCount: 2) { (completion: consuming IOCompletion?, error, done) in +try ring.submitPreparedRequestsAndConsumeCompletions(minimumCount: 2) { (completion: consuming Completion?, error, done) in if let error { throw error //or other error handling as desired } @@ -381,7 +380,7 @@ ring.prepare(linkedRequests: ) //batch submit 2 syscalls in 1! -try ring.submitPreparedRequestsAndConsumeCompletions(minimumCount: 2) { (completion: consuming IOCompletion?, error, done) in +try ring.submitPreparedRequestsAndConsumeCompletions(minimumCount: 2) { (completion: consuming Completion?, error, done) in if let error { throw error //or other error handling as desired } @@ -439,8 +438,8 @@ This feature is intrinsically linked to Linux kernel support, so constrains the ``` func submitLinkedRequestsAndWait( _ requests: repeat each Request -) where Request == IORequest - -> InlineArray<(repeat each Request).count, IOCompletion> +) where Request == IORing.Request + -> InlineArray<(repeat each Request).count, IORing.Completion> ``` * Once mutable borrows are supported, we should consider replacing the closure-taking bulk completion APIs (e.g. `blockingConsumeCompletions(…)`) with ones that return a sequence of completions instead * We should consider making more types noncopyable as compiler support improves @@ -457,7 +456,7 @@ func submitLinkedRequestsAndWait( * Using POSIX AIO instead of or as well as io_uring would greatly increase our ability to support older kernels and other Unix systems, but it has well-documented performance and usability issues that have prevented its adoption elsewhere, and apply just as much to Swift. * Earlier versions of this proposal had higher level "managed" abstractions over IORing. These have been removed due to lack of interest from clients, but could be added back later if needed. * I considered having dedicated error types for IORing, but eventually decided throwing Errno was more consistent with other platform APIs -* IOResource was originally a class in an attempt to manage the lifetime of the resource via language features. Changing to the current model of it being a copyable struct didn't make the lifetime management any less safe (the IORing still owns the actual resource), and reduces overhead. In the future it would be neat if we could express IOResources as being borrowed from the IORing so they can't be used after its lifetime. +* RegisteredResource was originally a class in an attempt to manage the lifetime of the resource via language features. Changing to the current model of it being a copyable struct didn't make the lifetime management any less safe (the IORing still owns the actual resource), and reduces overhead. In the future it would be neat if we could express RegisteredResources as being borrowed from the IORing so they can't be used after its lifetime. ## Acknowledgments From 5695a8831bcee664428fae077296ec6981d1e0f5 Mon Sep 17 00:00:00 2001 From: Jake Petroules Date: Fri, 18 Apr 2025 14:30:21 -0700 Subject: [PATCH 340/427] Transparently add the \\?\ prefix to Win32 calls for extended length path handling On Windows, there is a built-in maximum path limitation of 260 characters under most conditions. This can be extended to 32767 characters under either of the following two conditions: - Adding the longPathAware attribute to the executable's manifest AND enabling the LongPathsEnabled system-wide registry key or group policy. - Ensuring fully qualified paths passed to Win32 APIs are prefixed with \\?\ Unfortunately, the former is not realistic for the Swift ecosystem, since it requires developers to have awareness of this specific Windows limitation, AND set longPathAware in their apps' manifest AND expect end users of those apps to change their system configuration. Instead, this patch transparently prefixes all eligible paths in calls to Win32 APIs with the \\?\ prefix to allow them to work with paths longer than 260 characters without requiring the caller of System to manually prefix the paths. See https://learn.microsoft.com/en-us/windows/win32/fileio/maximum-file-path-limitation for more info. --- .../System/FilePath/FilePathTempWindows.swift | 8 +- Sources/System/FilePath/FilePathWindows.swift | 75 +++++++++++++++++++ .../Internals/WindowsSyscallAdapters.swift | 48 ++++++------ 3 files changed, 103 insertions(+), 28 deletions(-) diff --git a/Sources/System/FilePath/FilePathTempWindows.swift b/Sources/System/FilePath/FilePathTempWindows.swift index d6e45f4f..259aaafe 100644 --- a/Sources/System/FilePath/FilePathTempWindows.swift +++ b/Sources/System/FilePath/FilePathTempWindows.swift @@ -40,7 +40,7 @@ fileprivate func forEachFile( try searchPath.withPlatformString { szPath in var findData = WIN32_FIND_DATAW() - let hFind = FindFirstFileW(szPath, &findData) + let hFind = try szPath.withCanonicalPathRepresentation({ szPath in FindFirstFileW(szPath, &findData) }) if hFind == INVALID_HANDLE_VALUE { throw Errno(windowsError: GetLastError()) } @@ -95,8 +95,8 @@ internal func _recursiveRemove( let subpath = path.appending(component) if (findData.dwFileAttributes & DWORD(FILE_ATTRIBUTE_DIRECTORY)) == 0 { - try subpath.withPlatformString { - if !DeleteFileW($0) { + try subpath.withPlatformString { subpath in + if try !subpath.withCanonicalPathRepresentation({ DeleteFileW($0) }) { throw Errno(windowsError: GetLastError()) } } @@ -105,7 +105,7 @@ internal func _recursiveRemove( // Finally, delete the parent try path.withPlatformString { - if !RemoveDirectoryW($0) { + if try !$0.withCanonicalPathRepresentation({ RemoveDirectoryW($0) }) { throw Errno(windowsError: GetLastError()) } } diff --git a/Sources/System/FilePath/FilePathWindows.swift b/Sources/System/FilePath/FilePathWindows.swift index b725dd17..2226816e 100644 --- a/Sources/System/FilePath/FilePathWindows.swift +++ b/Sources/System/FilePath/FilePathWindows.swift @@ -461,3 +461,78 @@ extension SystemString { return lexer.current } } + +#if os(Windows) +import WinSDK + +// FIXME: Rather than canonicalizing the path at every call site to a Win32 API, +// we should consider always storing absolute paths with the \\?\ prefix applied, +// for better performance. +extension UnsafePointer where Pointee == CInterop.PlatformChar { + /// Invokes `body` with a resolved and potentially `\\?\`-prefixed version of the pointee, + /// to ensure long paths greater than MAX_PATH (260) characters are handled correctly. + /// + /// - seealso: https://learn.microsoft.com/en-us/windows/win32/fileio/maximum-file-path-limitation + internal func withCanonicalPathRepresentation(_ body: (Self) throws -> Result) throws -> Result { + // 1. Normalize the path first. + // Contrary to the documentation, this works on long paths independently + // of the registry or process setting to enable long paths (but it will also + // not add the \\?\ prefix required by other functions under these conditions). + let dwLength: DWORD = GetFullPathNameW(self, 0, nil, nil) + return try withUnsafeTemporaryAllocation(of: WCHAR.self, capacity: Int(dwLength)) { pwszFullPath in + guard (1.. DWORD { + DWORD(hr) & 0xffff +} + +@inline(__always) +fileprivate func HRESULT_FACILITY(_ hr: HRESULT) -> DWORD { + DWORD(hr << 16) & 0x1fff +} + +@inline(__always) +fileprivate func SUCCEEDED(_ hr: HRESULT) -> Bool { + hr >= 0 +} + +// This is a non-standard extension to the Windows SDK that allows us to convert +// an HRESULT to a Win32 error code. +@inline(__always) +fileprivate func WIN32_FROM_HRESULT(_ hr: HRESULT) -> DWORD { + if SUCCEEDED(hr) { return DWORD(ERROR_SUCCESS) } + if HRESULT_FACILITY(hr) == FACILITY_WIN32 { + return HRESULT_CODE(hr) + } + return DWORD(hr) +} +#endif diff --git a/Sources/System/Internals/WindowsSyscallAdapters.swift b/Sources/System/Internals/WindowsSyscallAdapters.swift index 706881ec..6437d16a 100644 --- a/Sources/System/Internals/WindowsSyscallAdapters.swift +++ b/Sources/System/Internals/WindowsSyscallAdapters.swift @@ -35,17 +35,17 @@ internal func open( bInheritHandle: decodedFlags.bInheritHandle ) - let hFile = CreateFileW(path, - decodedFlags.dwDesiredAccess, - DWORD(FILE_SHARE_DELETE - | FILE_SHARE_READ - | FILE_SHARE_WRITE), - &saAttrs, - decodedFlags.dwCreationDisposition, - decodedFlags.dwFlagsAndAttributes, - nil) - - if hFile == INVALID_HANDLE_VALUE { + guard let hFile = try? path.withCanonicalPathRepresentation({ path in + CreateFileW(path, + decodedFlags.dwDesiredAccess, + DWORD(FILE_SHARE_DELETE + | FILE_SHARE_READ + | FILE_SHARE_WRITE), + &saAttrs, + decodedFlags.dwCreationDisposition, + decodedFlags.dwFlagsAndAttributes, + nil) + }), hFile != INVALID_HANDLE_VALUE else { ucrt._set_errno(_mapWindowsErrorToErrno(GetLastError())) return -1 } @@ -77,17 +77,17 @@ internal func open( bInheritHandle: decodedFlags.bInheritHandle ) - let hFile = CreateFileW(path, - decodedFlags.dwDesiredAccess, - DWORD(FILE_SHARE_DELETE - | FILE_SHARE_READ - | FILE_SHARE_WRITE), - &saAttrs, - decodedFlags.dwCreationDisposition, - decodedFlags.dwFlagsAndAttributes, - nil) - - if hFile == INVALID_HANDLE_VALUE { + guard let hFile = try? path.withCanonicalPathRepresentation({ path in + CreateFileW(path, + decodedFlags.dwDesiredAccess, + DWORD(FILE_SHARE_DELETE + | FILE_SHARE_READ + | FILE_SHARE_WRITE), + &saAttrs, + decodedFlags.dwCreationDisposition, + decodedFlags.dwFlagsAndAttributes, + nil) + }), hFile != INVALID_HANDLE_VALUE else { ucrt._set_errno(_mapWindowsErrorToErrno(GetLastError())) return -1 } @@ -242,7 +242,7 @@ internal func mkdir( bInheritHandle: false ) - if !CreateDirectoryW(path, &saAttrs) { + guard (try? path.withCanonicalPathRepresentation({ path in CreateDirectoryW(path, &saAttrs) })) == true else { ucrt._set_errno(_mapWindowsErrorToErrno(GetLastError())) return -1 } @@ -254,7 +254,7 @@ internal func mkdir( internal func rmdir( _ path: UnsafePointer ) -> CInt { - if !RemoveDirectoryW(path) { + guard (try? path.withCanonicalPathRepresentation({ path in RemoveDirectoryW(path) })) == true else { ucrt._set_errno(_mapWindowsErrorToErrno(GetLastError())) return -1 } From 3a649e32a67625a06e183bbe68094056b28c690b Mon Sep 17 00:00:00 2001 From: David Smith Date: Thu, 24 Apr 2025 16:39:41 -0700 Subject: [PATCH 341/427] Fix type of features, and make it clear that Completion is a nested type --- NNNN-swift-system-io-uring.md | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/NNNN-swift-system-io-uring.md b/NNNN-swift-system-io-uring.md index 37f0c5c9..d103578e 100644 --- a/NNNN-swift-system-io-uring.md +++ b/NNNN-swift-system-io-uring.md @@ -70,7 +70,7 @@ Already-completed results can be retrieved from the ring using `tryConsumeComple Since neither polling nor synchronously waiting is optimal in many cases, `IORing` also exposes the ability to register an eventfd (see `man eventfd(2)`), which will become readable when completions are available on the ring. This can then be monitored asynchronously with `epoll`, `kqueue`, or for clients who are linking libdispatch, `DispatchSource`. -`struct Completion: ~Copyable` represents the result of an IO operation and provides +`struct IORing.Completion: ~Copyable` represents the result of an IO operation and provides * Flags indicating various operation-specific metadata about the now-completed syscall * The context associated with the operation when it was enqueued, as an `UnsafeRawPointer` or a `UInt64` @@ -87,7 +87,7 @@ Unfortunately the underlying kernel API makes it relatively difficult to determi // IORing is intentionally not Sendable, to avoid internal locking overhead public struct IORing: ~Copyable { - public init(queueDepth: UInt32, flags: IORing.SetupFlags = []) throws(Errno) + public init(queueDepth: UInt32, flags: IORing.SetupFlags = []) throws(Errno) public struct SetupFlags: OptionSet, RawRepresentable, Hashable { public var rawValue: UInt32 @@ -164,21 +164,21 @@ public struct IORing: ~Copyable { public init(rawValue: UInt32) //IORING_FEAT_SINGLE_MMAP is handled internally - public static let nonDroppingCompletions: Bool //IORING_FEAT_NODROP - public static let stableSubmissions: Bool //IORING_FEAT_SUBMIT_STABLE - public static let currentFilePosition: Bool //IORING_FEAT_RW_CUR_POS - public static let assumingTaskCredentials: Bool //IORING_FEAT_CUR_PERSONALITY - public static let fastPolling: Bool //IORING_FEAT_FAST_POLL - public static let epoll32BitFlags: Bool //IORING_FEAT_POLL_32BITS - public static let pollNonFixedFiles: Bool //IORING_FEAT_SQPOLL_NONFIXED - public static let extendedArguments: Bool //IORING_FEAT_EXT_ARG - public static let nativeWorkers: Bool //IORING_FEAT_NATIVE_WORKERS - public static let resourceTags: Bool //IORING_FEAT_RSRC_TAGS - public static let allowsSkippingSuccessfulCompletions: Bool //IORING_FEAT_CQE_SKIP - public static let improvedLinkedFiles: Bool //IORING_FEAT_LINKED_FILE - public static let registerRegisteredRings: Bool //IORING_FEAT_REG_REG_RING - public static let minimumTimeout: Bool //IORING_FEAT_MIN_TIMEOUT - public static let bundledSendReceive: Bool //IORING_FEAT_RECVSEND_BUNDLE + public static let nonDroppingCompletions: Features //IORING_FEAT_NODROP + public static let stableSubmissions: Features //IORING_FEAT_SUBMIT_STABLE + public static let currentFilePosition: Features //IORING_FEAT_RW_CUR_POS + public static let assumingTaskCredentials: Features //IORING_FEAT_CUR_PERSONALITY + public static let fastPolling: Features //IORING_FEAT_FAST_POLL + public static let epoll32BitFlags: Features //IORING_FEAT_POLL_32BITS + public static let pollNonFixedFiles: Features //IORING_FEAT_SQPOLL_NONFIXED + public static let extendedArguments: Features //IORING_FEAT_EXT_ARG + public static let nativeWorkers: Features //IORING_FEAT_NATIVE_WORKERS + public static let resourceTags: Features //IORING_FEAT_RSRC_TAGS + public static let allowsSkippingSuccessfulCompletions: Features //IORING_FEAT_CQE_SKIP + public static let improvedLinkedFiles: Features //IORING_FEAT_LINKED_FILE + public static let registerRegisteredRings: Features //IORING_FEAT_REG_REG_RING + public static let minimumTimeout: Features //IORING_FEAT_MIN_TIMEOUT + public static let bundledSendReceive: Features //IORING_FEAT_RECVSEND_BUNDLE } public var supportedFeatures: Features } From 7e2f797fb497f820cbbf7a60ec4376fe20a215f5 Mon Sep 17 00:00:00 2001 From: David Smith Date: Mon, 28 Apr 2025 22:30:00 +0000 Subject: [PATCH 342/427] Fix some errors on newer linux systems --- Sources/System/IORing.swift | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Sources/System/IORing.swift b/Sources/System/IORing.swift index 94c9504a..b4e633e0 100644 --- a/Sources/System/IORing.swift +++ b/Sources/System/IORing.swift @@ -466,7 +466,7 @@ public struct IORing: ~Copyable { var args = io_uring_getevents_arg( sigmask: 0, sigmask_sz: 0, - pad: 0, + min_wait_usec: 0, ts: UInt64(UInt(bitPattern: tsPtr)) ) return try _blockingConsumeOneCompletion(extraArgs: &args) @@ -490,7 +490,7 @@ public struct IORing: ~Copyable { var args = io_uring_getevents_arg( sigmask: 0, sigmask_sz: 0, - pad: 0, + min_wait_usec: 0, ts: UInt64(UInt(bitPattern: tsPtr)) ) try _blockingConsumeCompletionGuts( @@ -530,7 +530,7 @@ public struct IORing: ~Copyable { let result = withUnsafePointer(to: &rawfd) { fdptr in let result = io_uring_register( ringDescriptor, - IORING_REGISTER_EVENTFD, + IORING_REGISTER_EVENTFD.rawValue, UnsafeMutableRawPointer(mutating: fdptr), 1 ) @@ -544,7 +544,7 @@ public struct IORing: ~Copyable { public mutating func unregisterEventFD() throws(Errno) { let result = io_uring_register( ringDescriptor, - IORING_UNREGISTER_EVENTFD, + IORING_UNREGISTER_EVENTFD.rawValue, nil, 0 ) @@ -561,7 +561,7 @@ public struct IORing: ~Copyable { let regResult = files.withUnsafeBufferPointer { bPtr in let result = io_uring_register( self.ringDescriptor, - IORING_REGISTER_FILES, + IORING_REGISTER_FILES.rawValue, UnsafeMutableRawPointer(mutating: bPtr.baseAddress!), UInt32(truncatingIfNeeded: count) ) @@ -594,7 +594,7 @@ public struct IORing: ~Copyable { let regResult = iovecs.withUnsafeBufferPointer { bPtr in let result = io_uring_register( self.ringDescriptor, - IORING_REGISTER_BUFFERS, + IORING_REGISTER_BUFFERS.rawValue, UnsafeMutableRawPointer(mutating: bPtr.baseAddress!), UInt32(truncatingIfNeeded: buffers.count) ) From 3d6cba22b69e3adac60528e69f759b2ff76dc166 Mon Sep 17 00:00:00 2001 From: David Smith Date: Mon, 28 Apr 2025 23:03:26 +0000 Subject: [PATCH 343/427] Work around compiler bugs harder --- Sources/System/IORing.swift | 72 +++++++++++++++++-------------------- 1 file changed, 33 insertions(+), 39 deletions(-) diff --git a/Sources/System/IORing.swift b/Sources/System/IORing.swift index b4e633e0..5f54af91 100644 --- a/Sources/System/IORing.swift +++ b/Sources/System/IORing.swift @@ -164,7 +164,7 @@ internal func _getSubmissionEntry( } private func setUpRing( - queueDepth: UInt32, flags: IORing.SetupFlags, submissionRing: inout SQRing + queueDepth: UInt32, flags: IORing.SetupFlags ) throws(Errno) -> (params: io_uring_params, ringDescriptor: Int32, ringPtr: UnsafeMutableRawPointer, ringSize: Int, sqes: UnsafeMutableRawPointer) { var params = io_uring_params() @@ -217,36 +217,6 @@ private func setUpRing( throw errno } - let submissionRing = SQRing( - kernelHead: UnsafePointer>( - ringPtr.advanced(by: params.sq_off.head) - .assumingMemoryBound(to: Atomic.self) - ), - kernelTail: UnsafePointer>( - ringPtr.advanced(by: params.sq_off.tail) - .assumingMemoryBound(to: Atomic.self) - ), - userTail: 0, // no requests yet - ringMask: ringPtr.advanced(by: params.sq_off.ring_mask) - .assumingMemoryBound(to: UInt32.self).pointee, - flags: UnsafePointer>( - ringPtr.advanced(by: params.sq_off.flags) - .assumingMemoryBound(to: Atomic.self) - ), - array: UnsafeMutableBufferPointer( - start: ringPtr.advanced(by: params.sq_off.array) - .assumingMemoryBound(to: UInt32.self), - count: Int( - ringPtr.advanced(by: params.sq_off.ring_entries) - .assumingMemoryBound(to: UInt32.self).pointee) - ) - ) - - // fill submission ring array with 1:1 map to underlying SQEs - for i in 0..>( + ringPtr.advanced(by: params.sq_off.head) + .assumingMemoryBound(to: Atomic.self) + ), + kernelTail: UnsafePointer>( + ringPtr.advanced(by: params.sq_off.tail) + .assumingMemoryBound(to: Atomic.self) + ), + userTail: 0, // no requests yet + ringMask: ringPtr.advanced(by: params.sq_off.ring_mask) + .assumingMemoryBound(to: UInt32.self).pointee, + flags: UnsafePointer>( + ringPtr.advanced(by: params.sq_off.flags) + .assumingMemoryBound(to: Atomic.self) + ), + array: UnsafeMutableBufferPointer( + start: ringPtr.advanced(by: params.sq_off.array) + .assumingMemoryBound(to: UInt32.self), + count: Int( + ringPtr.advanced(by: params.sq_off.ring_entries) + .assumingMemoryBound(to: UInt32.self).pointee) + ) + ) + + // fill submission ring array with 1:1 map to underlying SQEs + for i in 0 ..< submissionRing.array.count { + submissionRing.array[i] = UInt32(i) + } submissionQueueEntries = UnsafeMutableBufferPointer( start: sqes.assumingMemoryBound(to: io_uring_sqe.self), @@ -641,12 +640,7 @@ public struct IORing: ~Copyable { } public func submitPreparedRequests() throws(Errno) { - switch submissionRing { - case .some(let submissionRing): - try _submitRequests(ring: submissionRing, ringDescriptor: ringDescriptor) - case .none: - fatalError() - } + try _submitRequests(ring: submissionRing, ringDescriptor: ringDescriptor) } public func submitPreparedRequestsAndConsumeCompletions( From 595c862c19722fe1f544163c3ea9448f0e4be094 Mon Sep 17 00:00:00 2001 From: Owen Voorhees Date: Wed, 21 May 2025 21:12:58 -0700 Subject: [PATCH 344/427] Fix Windows path parsing when the package is built in release configuration forceWindowsPaths can be used to force Windows path parsing both on and off. Instead of defaulting it to false in release builds, we should not express any preference --- Sources/System/Internals/Mocking.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/System/Internals/Mocking.swift b/Sources/System/Internals/Mocking.swift index 2945651c..ffdaaa92 100644 --- a/Sources/System/Internals/Mocking.swift +++ b/Sources/System/Internals/Mocking.swift @@ -130,7 +130,7 @@ internal var mockingEnabled: Bool { @inline(__always) internal var forceWindowsPaths: Bool? { #if !ENABLE_MOCKING - return false + return nil #else return MockingDriver.forceWindowsPaths #endif From f9c2a2b8e78c458c207b7e353ec0e43606d15162 Mon Sep 17 00:00:00 2001 From: David Smith Date: Wed, 18 Jun 2025 23:08:45 +0000 Subject: [PATCH 345/427] Work around io_uring.h in ways that will work on both versions --- Sources/CSystem/include/io_uring.h | 33 ++++++++++++++++++++++++++++++ Sources/System/IORing.swift | 12 +++++------ 2 files changed, 39 insertions(+), 6 deletions(-) diff --git a/Sources/CSystem/include/io_uring.h b/Sources/CSystem/include/io_uring.h index 75a07e57..df1b36a8 100644 --- a/Sources/CSystem/include/io_uring.h +++ b/Sources/CSystem/include/io_uring.h @@ -34,6 +34,39 @@ # endif #endif +/* +struct io_uring_getevents_arg { + __u64 sigmask; + __u32 sigmask_sz; + __u32 min_wait_usec; //used to be called `pad`. This compatibility wrapper avoids dealing with that. + __u64 ts; +}; +*/ +struct swift_io_uring_getevents_arg { + __u64 sigmask; + __u32 sigmask_sz; + __u32 min_wait_usec; + __u64 ts; +}; + +//This was #defines in older headers, so we redeclare it to get a consistent import +typedef enum : __u32 { + SWIFT_IORING_REGISTER_BUFFERS = 0, + SWIFT_IORING_UNREGISTER_BUFFERS = 1, + SWIFT_IORING_REGISTER_FILES = 2, + SWIFT_IORING_UNREGISTER_FILES = 3, + SWIFT_IORING_REGISTER_EVENTFD = 4, + SWIFT_IORING_UNREGISTER_EVENTFD = 5, + SWIFT_IORING_REGISTER_FILES_UPDATE = 6, + SWIFT_IORING_REGISTER_EVENTFD_ASYNC = 7, + SWIFT_IORING_REGISTER_PROBE = 8, + SWIFT_IORING_REGISTER_PERSONALITY = 9, + SWIFT_IORING_UNREGISTER_PERSONALITY = 10, + + /* this goes last */ + SWIFT_IORING_REGISTER_LAST +} SWIFT_IORING_REGISTER_OPS; + static inline int io_uring_register(int fd, unsigned int opcode, void *arg, unsigned int nr_args) { diff --git a/Sources/System/IORing.swift b/Sources/System/IORing.swift index 5f54af91..ce72aa7c 100644 --- a/Sources/System/IORing.swift +++ b/Sources/System/IORing.swift @@ -372,7 +372,7 @@ public struct IORing: ~Copyable { private func _blockingConsumeCompletionGuts( minimumCount: UInt32, maximumCount: UInt32, - extraArgs: UnsafeMutablePointer? = nil, + extraArgs: UnsafeMutablePointer? = nil, consumer: (consuming IORing.Completion?, Errno?, Bool) throws(Err) -> Void ) throws(Err) { var count = 0 @@ -393,7 +393,7 @@ public struct IORing: ~Copyable { while count < minimumCount { var sz = 0 if extraArgs != nil { - sz = MemoryLayout.size + sz = MemoryLayout.size } let res = io_uring_enter2( ringDescriptor, @@ -438,7 +438,7 @@ public struct IORing: ~Copyable { } internal func _blockingConsumeOneCompletion( - extraArgs: UnsafeMutablePointer? = nil + extraArgs: UnsafeMutablePointer? = nil ) throws(Errno) -> Completion { var result: Completion? = nil try _blockingConsumeCompletionGuts(minimumCount: 1, maximumCount: 1, extraArgs: extraArgs) { @@ -462,7 +462,7 @@ public struct IORing: ~Copyable { tv_nsec: timeout.components.attoseconds / 1_000_000_000 ) return try withUnsafePointer(to: &ts) { (tsPtr) throws(Errno) -> Completion in - var args = io_uring_getevents_arg( + var args = swift_io_uring_getevents_arg( sigmask: 0, sigmask_sz: 0, min_wait_usec: 0, @@ -486,7 +486,7 @@ public struct IORing: ~Copyable { tv_nsec: timeout.components.attoseconds / 1_000_000_000 ) try withUnsafePointer(to: &ts) { (tsPtr) throws(Err) in - var args = io_uring_getevents_arg( + var args = swift_io_uring_getevents_arg( sigmask: 0, sigmask_sz: 0, min_wait_usec: 0, @@ -529,7 +529,7 @@ public struct IORing: ~Copyable { let result = withUnsafePointer(to: &rawfd) { fdptr in let result = io_uring_register( ringDescriptor, - IORING_REGISTER_EVENTFD.rawValue, + SWIFT_IORING_REGISTER_EVENTFD.rawValue, UnsafeMutableRawPointer(mutating: fdptr), 1 ) From f91cb6b8de316075a086504e688b81af6aa157dc Mon Sep 17 00:00:00 2001 From: David Smith Date: Fri, 27 Jun 2025 00:39:35 +0000 Subject: [PATCH 346/427] Improve tests. Not quite working but close --- Tests/SystemTests/IORequestTests.swift | 57 +++++++++++--------------- 1 file changed, 25 insertions(+), 32 deletions(-) diff --git a/Tests/SystemTests/IORequestTests.swift b/Tests/SystemTests/IORequestTests.swift index 3d1f72de..04100e01 100644 --- a/Tests/SystemTests/IORequestTests.swift +++ b/Tests/SystemTests/IORequestTests.swift @@ -1,9 +1,9 @@ import XCTest #if SYSTEM_PACKAGE -@testable import SystemPackage + @testable import SystemPackage #else -import System + import System #endif func requestBytes(_ request: consuming RawIORequest) -> [UInt8] { @@ -27,37 +27,30 @@ final class IORequestTests: XCTestCase { XCTAssertEqual(sourceBytes, .init(repeating: 0, count: 64)) } - func testOpenatFixedFile() throws { - let pathPtr = UnsafePointer(bitPattern: 0x414141410badf00d)! - let fileSlot = IORing.RegisteredFile(resource: UInt32.max, index: 0) - let req = IORing.Request.open(FilePath(platformString: pathPtr), - in: FileDescriptor(rawValue: -100), - into: fileSlot, - mode: .readOnly, - options: [], - permissions: nil + func testOpenAndReadFixedFile() throws { + mkdir("/tmp/IORingTests/", 0o777) + let path: FilePath = "/tmp/IORingTests/test.txt" + let fd = try FileDescriptor.open( + path, .readWrite, options: .create, permissions: .ownerReadWrite) + try fd.writeAll("Hello, World!".utf8) + try fd.close() + var ring = try IORing(queueDepth: 3) + let parent = try FileDescriptor.open("/tmp/IORingTests/", .readOnly) + let fileSlot = try ring.registerFileSlots(count: 1)[0] + let rawBuffer = UnsafeMutableRawBufferPointer.allocate(byteCount: 64, alignment: 16) + let buffer = try ring.registerBuffers([rawBuffer])[0] + try ring.submit(linkedRequests: + .open(path, in: parent, into: fileSlot, mode: .readOnly), + .read(fileSlot, into: buffer), + .close(fileSlot) ) + _ = try ring.blockingConsumeCompletion() //open + _ = try ring.blockingConsumeCompletion() //read + _ = try ring.blockingConsumeCompletion() //close - let expectedRequest: [UInt8] = { - var bin = [UInt8].init(repeating: 0, count: 64) - bin[0] = 0x12 // opcode for the request - // bin[1] = 0 - no request flags - // bin[2...3] = 0 - padding - bin[4...7] = [0x9c, 0xff, 0xff, 0xff] // -100 in UInt32 - dirfd - // bin[8...15] = 0 - zeroes - withUnsafeBytes(of: pathPtr) { - // path pointer - bin[16...23] = ArraySlice($0) - } - // bin[24...43] = 0 - zeroes - withUnsafeBytes(of: UInt32(fileSlot.index + 1)) { - // file index + 1 - yes, unfortunately - bin[44...47] = ArraySlice($0) - } - return bin - }() - - let actualRequest = requestBytes(req.makeRawRequest()) - XCTAssertEqual(expectedRequest, actualRequest) + let result = String(cString: rawBuffer.assumingMemoryBound(to: CChar.self).baseAddress!) + XCTAssertEqual(result, "Hello, World!") + + rmdir("/tmp/IORingTests/") } } From c0273af7e5543305327fe2d434072f35f9dcd272 Mon Sep 17 00:00:00 2001 From: Alex Azarov Date: Sat, 28 Jun 2025 17:46:44 +0200 Subject: [PATCH 347/427] Fix API documentation links in README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8fa266b5..c18ba496 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ try fd.closeAfter { } ``` -[API documentation](https://swiftpackageindex.com/apple/swift-system/main/documentation/SystemPackage) +[API documentation](https://swiftpackageindex.com/apple/swift-system/documentation/SystemPackage) ## Adding `SystemPackage` as a Dependency From 131326c8aedf6e32d9084075cc67cbed0271c538 Mon Sep 17 00:00:00 2001 From: David Smith Date: Tue, 1 Jul 2025 01:02:40 +0000 Subject: [PATCH 348/427] Fix tests --- Tests/SystemTests/IORequestTests.swift | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/Tests/SystemTests/IORequestTests.swift b/Tests/SystemTests/IORequestTests.swift index 04100e01..d22b527a 100644 --- a/Tests/SystemTests/IORequestTests.swift +++ b/Tests/SystemTests/IORequestTests.swift @@ -7,7 +7,7 @@ import XCTest #endif func requestBytes(_ request: consuming RawIORequest) -> [UInt8] { - return withUnsafePointer(to: request) { + return withUnsafePointer(to: request.rawValue) { let requestBuf = UnsafeBufferPointer(start: $0, count: 1) let rawBytes = UnsafeRawBufferPointer(requestBuf) return .init(rawBytes) @@ -31,26 +31,32 @@ final class IORequestTests: XCTestCase { mkdir("/tmp/IORingTests/", 0o777) let path: FilePath = "/tmp/IORingTests/test.txt" let fd = try FileDescriptor.open( - path, .readWrite, options: .create, permissions: .ownerReadWrite) + path, + .readWrite, + options: .create, + permissions: .ownerReadWrite + ) try fd.writeAll("Hello, World!".utf8) try fd.close() - var ring = try IORing(queueDepth: 3) + var ring = try IORing(queueDepth: 6) let parent = try FileDescriptor.open("/tmp/IORingTests/", .readOnly) let fileSlot = try ring.registerFileSlots(count: 1)[0] - let rawBuffer = UnsafeMutableRawBufferPointer.allocate(byteCount: 64, alignment: 16) + let rawBuffer = UnsafeMutableRawBufferPointer.allocate(byteCount: 13, alignment: 16) let buffer = try ring.registerBuffers([rawBuffer])[0] + try ring.submit(linkedRequests: .open(path, in: parent, into: fileSlot, mode: .readOnly), .read(fileSlot, into: buffer), - .close(fileSlot) - ) + .close(fileSlot)) _ = try ring.blockingConsumeCompletion() //open _ = try ring.blockingConsumeCompletion() //read _ = try ring.blockingConsumeCompletion() //close + let result = String(cString: rawBuffer.assumingMemoryBound(to: CChar.self).baseAddress!) XCTAssertEqual(result, "Hello, World!") + try parent.close() rmdir("/tmp/IORingTests/") } } From 321a71c2e999932b983516918254bffcfde912ea Mon Sep 17 00:00:00 2001 From: David Smith Date: Thu, 3 Jul 2025 00:13:29 +0000 Subject: [PATCH 349/427] Expand test coverage --- Tests/SystemTests/IORequestTests.swift | 33 ----------- Tests/SystemTests/IORingTests.swift | 80 ++++++++++++++++++++++++++ 2 files changed, 80 insertions(+), 33 deletions(-) diff --git a/Tests/SystemTests/IORequestTests.swift b/Tests/SystemTests/IORequestTests.swift index d22b527a..0e5c9724 100644 --- a/Tests/SystemTests/IORequestTests.swift +++ b/Tests/SystemTests/IORequestTests.swift @@ -26,37 +26,4 @@ final class IORequestTests: XCTestCase { // we're not trying to be bug-compatible with it, so 0 *should* work. XCTAssertEqual(sourceBytes, .init(repeating: 0, count: 64)) } - - func testOpenAndReadFixedFile() throws { - mkdir("/tmp/IORingTests/", 0o777) - let path: FilePath = "/tmp/IORingTests/test.txt" - let fd = try FileDescriptor.open( - path, - .readWrite, - options: .create, - permissions: .ownerReadWrite - ) - try fd.writeAll("Hello, World!".utf8) - try fd.close() - var ring = try IORing(queueDepth: 6) - let parent = try FileDescriptor.open("/tmp/IORingTests/", .readOnly) - let fileSlot = try ring.registerFileSlots(count: 1)[0] - let rawBuffer = UnsafeMutableRawBufferPointer.allocate(byteCount: 13, alignment: 16) - let buffer = try ring.registerBuffers([rawBuffer])[0] - - try ring.submit(linkedRequests: - .open(path, in: parent, into: fileSlot, mode: .readOnly), - .read(fileSlot, into: buffer), - .close(fileSlot)) - _ = try ring.blockingConsumeCompletion() //open - _ = try ring.blockingConsumeCompletion() //read - _ = try ring.blockingConsumeCompletion() //close - - - let result = String(cString: rawBuffer.assumingMemoryBound(to: CChar.self).baseAddress!) - XCTAssertEqual(result, "Hello, World!") - - try parent.close() - rmdir("/tmp/IORingTests/") - } } diff --git a/Tests/SystemTests/IORingTests.swift b/Tests/SystemTests/IORingTests.swift index 6c086da6..8b32b92d 100644 --- a/Tests/SystemTests/IORingTests.swift +++ b/Tests/SystemTests/IORingTests.swift @@ -1,4 +1,5 @@ import XCTest +import CSystem //for eventfd #if SYSTEM_PACKAGE import SystemPackage @@ -17,4 +18,83 @@ final class IORingTests: XCTestCase { let completion = try ring.blockingConsumeCompletion() XCTAssertEqual(completion.result, 0) } + + func makeHelloWorldFile() throws -> (dir: FileDescriptor, file: FilePath) { + mkdir("/tmp/IORingTests/", 0o777) + let path: FilePath = "/tmp/IORingTests/test.txt" + let fd = try FileDescriptor.open( + path, + .readWrite, + options: .create, + permissions: .ownerReadWrite + ) + try fd.writeAll("Hello, World!".utf8) + try fd.close() + let parent = try FileDescriptor.open("/tmp/IORingTests/", .readOnly) + + return (parent, path) + } + + func cleanUpHelloWorldFile(_ parent: FileDescriptor) throws { + try parent.close() + rmdir("/tmp/IORingTests/") + } + + func setupTestRing(depth: Int, fileSlots: Int, buffers: [UnsafeMutableRawBufferPointer]) throws -> IORing { + var ring: IORing = try IORing(queueDepth: 6) + _ = try ring.registerFileSlots(count: 1) + _ = try ring.registerBuffers(buffers) + return ring + } + + // Exercises opening, reading, closing, registered files, registered buffers, and eventfd + func testOpenReadAndWriteFixedFile() throws { + let (parent, path) = try makeHelloWorldFile() + let rawBuffer = UnsafeMutableRawBufferPointer.allocate(byteCount: 13, alignment: 16) + var ring = try setupTestRing(depth: 3, fileSlots: 1, buffers: [rawBuffer]) + let eventFD = FileDescriptor(rawValue: eventfd(0, Int32(EFD_SEMAPHORE))) + try ring.registerEventFD(eventFD) + + //Part 1: read the file we just created, and make sure the eventfd fires + try ring.submit(linkedRequests: + .open(path, in: parent, into: ring.registeredFileSlots[0], mode: .readOnly), + .read(ring.registeredFileSlots[0], into: ring.registeredBuffers[0]), + .close(ring.registeredFileSlots[0])) + let efdBuf = UnsafeMutableRawBufferPointer.allocate(byteCount: 8, alignment: 0) + _ = try eventFD.read(into: efdBuf) + _ = try ring.blockingConsumeCompletion() //open + _ = try eventFD.read(into: efdBuf) + _ = try ring.blockingConsumeCompletion() //read + _ = try eventFD.read(into: efdBuf) + _ = try ring.blockingConsumeCompletion() //close + let result = String(cString: rawBuffer.assumingMemoryBound(to: CChar.self).baseAddress!) + XCTAssertEqual(result, "Hello, World!") + + //Part 2: delete that file, then use the ring to write out a new one + let rmResult = path.withPlatformString { + remove($0) + } + XCTAssertEqual(rmResult, 0) + try ring.submit(linkedRequests: + .open(path, in: parent, into: ring.registeredFileSlots[0], mode: .readWrite, options: .create, permissions: .ownerReadWrite), + .write(ring.registeredBuffers[0], into: ring.registeredFileSlots[0]), + .close(ring.registeredFileSlots[0])) + _ = try eventFD.read(into: efdBuf) + _ = try ring.blockingConsumeCompletion() //open + _ = try eventFD.read(into: efdBuf) + _ = try ring.blockingConsumeCompletion() //write + _ = try eventFD.read(into: efdBuf) + _ = try ring.blockingConsumeCompletion() //close + memset(rawBuffer.baseAddress!, 0, rawBuffer.count) + //Verify using a non-ring IO method that what we wrote matches our expectations + print("about to open") + let nonRingFD = try FileDescriptor.open(path, .readOnly) + let bytesRead = try nonRingFD.read(into: rawBuffer) + XCTAssert(bytesRead == 13) + let result2 = String(cString: rawBuffer.assumingMemoryBound(to: CChar.self).baseAddress!) + XCTAssertEqual(result2, "Hello, World!") + try cleanUpHelloWorldFile(parent) + efdBuf.deallocate() + rawBuffer.deallocate() + } } From 27ccce55db6615e69056424c747b4b330391b0e0 Mon Sep 17 00:00:00 2001 From: David Smith Date: Thu, 3 Jul 2025 16:04:52 -0700 Subject: [PATCH 350/427] Conditionalize IORing stuff for Linux --- Sources/System/IOCompletion.swift | 2 ++ Sources/System/IORequest.swift | 2 ++ Sources/System/IORing.swift | 2 ++ Sources/System/RawIORequest.swift | 4 +++- 4 files changed, 9 insertions(+), 1 deletion(-) diff --git a/Sources/System/IOCompletion.swift b/Sources/System/IOCompletion.swift index 5fa29325..ed0feab5 100644 --- a/Sources/System/IOCompletion.swift +++ b/Sources/System/IOCompletion.swift @@ -1,3 +1,4 @@ +#if os(Linux) @_implementationOnly import CSystem public extension IORing { @@ -56,3 +57,4 @@ public extension IORing.Completion { } } } +#endif diff --git a/Sources/System/IORequest.swift b/Sources/System/IORequest.swift index 3c4d2a27..35741bd7 100644 --- a/Sources/System/IORequest.swift +++ b/Sources/System/IORequest.swift @@ -1,3 +1,4 @@ +#if os(Linux) @_implementationOnly import struct CSystem.io_uring_sqe @usableFromInline @@ -471,3 +472,4 @@ extension IORing.Request { return request } } +#endif diff --git a/Sources/System/IORing.swift b/Sources/System/IORing.swift index ce72aa7c..581e7950 100644 --- a/Sources/System/IORing.swift +++ b/Sources/System/IORing.swift @@ -1,3 +1,4 @@ +#if os(Linux) @_implementationOnly import CSystem import Glibc // needed for mmap import Synchronization @@ -742,3 +743,4 @@ extension IORing.RegisteredBuffer { return .init(start: resource.iov_base, count: resource.iov_len) } } +#endif diff --git a/Sources/System/RawIORequest.swift b/Sources/System/RawIORequest.swift index 6190b0da..33bea295 100644 --- a/Sources/System/RawIORequest.swift +++ b/Sources/System/RawIORequest.swift @@ -1,3 +1,4 @@ +#if os(Linux) // TODO: investigate @usableFromInline / @_implementationOnly dichotomy @_implementationOnly import CSystem @_implementationOnly import struct CSystem.io_uring_sqe @@ -196,4 +197,5 @@ extension RawIORequest { return try work() } } -} \ No newline at end of file +} +#endif From 0d4c6fc97147e14c0291d51bab68253b59260e7c Mon Sep 17 00:00:00 2001 From: David Smith Date: Thu, 3 Jul 2025 16:12:56 -0700 Subject: [PATCH 351/427] Fix Musl --- Sources/System/IORing.swift | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Sources/System/IORing.swift b/Sources/System/IORing.swift index 581e7950..0af574d5 100644 --- a/Sources/System/IORing.swift +++ b/Sources/System/IORing.swift @@ -1,6 +1,11 @@ #if os(Linux) @_implementationOnly import CSystem -import Glibc // needed for mmap +// needed for mmap +#if canImport(Glibc) +import Glibc +#elseif canImport(Musl) +import Musl +#endif import Synchronization @_implementationOnly import struct CSystem.io_uring_sqe From de2d6e2d391368ac3db87b99f1ea9dc2d6e45a3e Mon Sep 17 00:00:00 2001 From: David Smith Date: Thu, 3 Jul 2025 16:17:51 -0700 Subject: [PATCH 352/427] Conditionalize tests too --- Tests/SystemTests/IORequestTests.swift | 2 ++ Tests/SystemTests/IORingTests.swift | 2 ++ 2 files changed, 4 insertions(+) diff --git a/Tests/SystemTests/IORequestTests.swift b/Tests/SystemTests/IORequestTests.swift index 0e5c9724..4d32196a 100644 --- a/Tests/SystemTests/IORequestTests.swift +++ b/Tests/SystemTests/IORequestTests.swift @@ -1,3 +1,4 @@ +#if os(Linux) import XCTest #if SYSTEM_PACKAGE @@ -27,3 +28,4 @@ final class IORequestTests: XCTestCase { XCTAssertEqual(sourceBytes, .init(repeating: 0, count: 64)) } } +#endif diff --git a/Tests/SystemTests/IORingTests.swift b/Tests/SystemTests/IORingTests.swift index 8b32b92d..29973c63 100644 --- a/Tests/SystemTests/IORingTests.swift +++ b/Tests/SystemTests/IORingTests.swift @@ -1,3 +1,4 @@ +#if os(Linux) import XCTest import CSystem //for eventfd @@ -98,3 +99,4 @@ final class IORingTests: XCTestCase { rawBuffer.deallocate() } } +#endif From a6149c215ef7f0b0937fe2ca9091f043ce44f65a Mon Sep 17 00:00:00 2001 From: David Smith Date: Fri, 4 Jul 2025 00:09:19 +0000 Subject: [PATCH 353/427] Cleanup, implement buffer unregistering, inline almost everything because we're not ABI stable on Linux --- Sources/System/IOCompletion.swift | 19 +++--- Sources/System/IORequest.swift | 81 ++++++++++++------------- Sources/System/IORing.swift | 99 +++++++++++++++++++------------ Sources/System/RawIORequest.swift | 69 ++++++++++----------- 4 files changed, 144 insertions(+), 124 deletions(-) diff --git a/Sources/System/IOCompletion.swift b/Sources/System/IOCompletion.swift index ed0feab5..4263bd8d 100644 --- a/Sources/System/IOCompletion.swift +++ b/Sources/System/IOCompletion.swift @@ -1,9 +1,12 @@ #if os(Linux) -@_implementationOnly import CSystem +import CSystem public extension IORing { struct Completion: ~Copyable { - let rawValue: io_uring_cqe + @inlinable init(rawValue inRawValue: io_uring_cqe) { + rawValue = inRawValue + } + @usableFromInline let rawValue: io_uring_cqe } } @@ -11,7 +14,7 @@ public extension IORing.Completion { struct Flags: OptionSet, Hashable, Codable { public let rawValue: UInt32 - public init(rawValue: UInt32) { + @inlinable public init(rawValue: UInt32) { self.rawValue = rawValue } @@ -23,31 +26,31 @@ public extension IORing.Completion { } public extension IORing.Completion { - var context: UInt64 { + @inlinable var context: UInt64 { get { rawValue.user_data } } - var userPointer: UnsafeRawPointer? { + @inlinable var userPointer: UnsafeRawPointer? { get { UnsafeRawPointer(bitPattern: UInt(rawValue.user_data)) } } - var result: Int32 { + @inlinable var result: Int32 { get { rawValue.res } } - var flags: IORing.Completion.Flags { + @inlinable var flags: IORing.Completion.Flags { get { Flags(rawValue: rawValue.flags & 0x0000FFFF) } } - var bufferIndex: UInt16? { + @inlinable var bufferIndex: UInt16? { get { if self.flags.contains(.allocatedBuffer) { return UInt16(rawValue.flags >> 16) diff --git a/Sources/System/IORequest.swift b/Sources/System/IORequest.swift index 35741bd7..8af313a3 100644 --- a/Sources/System/IORequest.swift +++ b/Sources/System/IORequest.swift @@ -1,5 +1,5 @@ #if os(Linux) -@_implementationOnly import struct CSystem.io_uring_sqe +import CSystem @usableFromInline internal enum IORequestCore { @@ -96,7 +96,7 @@ internal enum IORequestCore { ) } -@inline(__always) +@inline(__always) @inlinable internal func makeRawRequest_readWrite_registered( file: FileDescriptor, buffer: IORing.RegisteredBuffer, @@ -112,7 +112,7 @@ internal func makeRawRequest_readWrite_registered( return request } -@inline(__always) +@inline(__always) @inlinable internal func makeRawRequest_readWrite_registered_slot( file: IORing.RegisteredFile, buffer: IORing.RegisteredBuffer, @@ -129,7 +129,7 @@ internal func makeRawRequest_readWrite_registered_slot( return request } -@inline(__always) +@inline(__always) @inlinable internal func makeRawRequest_readWrite_unregistered( file: FileDescriptor, buffer: UnsafeMutableRawBufferPointer, @@ -144,7 +144,7 @@ internal func makeRawRequest_readWrite_unregistered( return request } -@inline(__always) +@inline(__always) @inlinable internal func makeRawRequest_readWrite_unregistered_slot( file: IORing.RegisteredFile, buffer: UnsafeMutableRawBufferPointer, @@ -164,6 +164,10 @@ extension IORing { public struct Request { @usableFromInline var core: IORequestCore + @inlinable internal init(core inCore: IORequestCore) { + core = inCore + } + @inlinable internal consuming func extractCore() -> IORequestCore { return core } @@ -173,11 +177,11 @@ extension IORing { extension IORing.Request { - public static func nop(context: UInt64 = 0) -> IORing.Request { + @inlinable public static func nop(context: UInt64 = 0) -> IORing.Request { .init(core: .nop) } - public static func read( + @inlinable public static func read( _ file: IORing.RegisteredFile, into buffer: IORing.RegisteredBuffer, at offset: UInt64 = 0, @@ -186,7 +190,7 @@ extension IORing.Request { .init(core: .readSlot(file: file, buffer: buffer, offset: offset, context: context)) } - public static func read( + @inlinable public static func read( _ file: FileDescriptor, into buffer: IORing.RegisteredBuffer, at offset: UInt64 = 0, @@ -195,7 +199,7 @@ extension IORing.Request { .init(core: .read(file: file, buffer: buffer, offset: offset, context: context)) } - public static func read( + @inlinable public static func read( _ file: IORing.RegisteredFile, into buffer: UnsafeMutableRawBufferPointer, at offset: UInt64 = 0, @@ -204,7 +208,7 @@ extension IORing.Request { .init(core: .readUnregisteredSlot(file: file, buffer: buffer, offset: offset, context: context)) } - public static func read( + @inlinable public static func read( _ file: FileDescriptor, into buffer: UnsafeMutableRawBufferPointer, at offset: UInt64 = 0, @@ -213,7 +217,7 @@ extension IORing.Request { .init(core: .readUnregistered(file: file, buffer: buffer, offset: offset, context: context)) } - public static func write( + @inlinable public static func write( _ buffer: IORing.RegisteredBuffer, into file: IORing.RegisteredFile, at offset: UInt64 = 0, @@ -222,7 +226,7 @@ extension IORing.Request { .init(core: .writeSlot(file: file, buffer: buffer, offset: offset, context: context)) } - public static func write( + @inlinable public static func write( _ buffer: IORing.RegisteredBuffer, into file: FileDescriptor, at offset: UInt64 = 0, @@ -231,7 +235,7 @@ extension IORing.Request { .init(core: .write(file: file, buffer: buffer, offset: offset, context: context)) } - public static func write( + @inlinable public static func write( _ buffer: UnsafeMutableRawBufferPointer, into file: IORing.RegisteredFile, at offset: UInt64 = 0, @@ -241,7 +245,7 @@ extension IORing.Request { file: file, buffer: buffer, offset: offset, context: context)) } - public static func write( + @inlinable public static func write( _ buffer: UnsafeMutableRawBufferPointer, into file: FileDescriptor, at offset: UInt64 = 0, @@ -252,21 +256,21 @@ extension IORing.Request { ) } - public static func close( + @inlinable public static func close( _ file: FileDescriptor, context: UInt64 = 0 ) -> IORing.Request { .init(core: .close(file, context: context)) } - public static func close( + @inlinable public static func close( _ file: IORing.RegisteredFile, context: UInt64 = 0 ) -> IORing.Request { .init(core: .closeSlot(file, context: context)) } - public static func open( + @inlinable public static func open( _ path: FilePath, in directory: FileDescriptor, into slot: IORing.RegisteredFile, @@ -281,7 +285,7 @@ extension IORing.Request { permissions: permissions, intoSlot: slot, context: context)) } - public static func open( + @inlinable public static func open( _ path: FilePath, in directory: FileDescriptor, mode: FileDescriptor.AccessMode, @@ -296,7 +300,7 @@ extension IORing.Request { )) } - public static func unlink( + @inlinable public static func unlink( _ path: FilePath, in directory: FileDescriptor, context: UInt64 = 0 @@ -307,31 +311,22 @@ extension IORing.Request { // Cancel /* - * ASYNC_CANCEL flags. - * - * IORING_ASYNC_CANCEL_ALL Cancel all requests that match the given key - * IORING_ASYNC_CANCEL_FD Key off 'fd' for cancelation rather than the - * request 'user_data' - * IORING_ASYNC_CANCEL_ANY Match any request - * IORING_ASYNC_CANCEL_FD_FIXED 'fd' passed in is a fixed descriptor - * IORING_ASYNC_CANCEL_USERDATA Match on user_data, default for no other key - * IORING_ASYNC_CANCEL_OP Match request based on opcode - */ - //TODO: why aren't these showing up from the header import? - private static var IORING_ASYNC_CANCEL_ALL: UInt32 { (1 as UInt32) << 0 } - private static var IORING_ASYNC_CANCEL_FD: UInt32 { (1 as UInt32) << 1 } - private static var IORING_ASYNC_CANCEL_ANY: UInt32 { (1 as UInt32) << 2 } - private static var IORING_ASYNC_CANCEL_FD_FIXED: UInt32 { (1 as UInt32) << 3 } - private static var IORING_ASYNC_CANCEL_USERDATA: UInt32 { (1 as UInt32) << 4 } - private static var IORING_ASYNC_CANCEL_OP: UInt32 { (1 as UInt32) << 5 } - - + * ASYNC_CANCEL flags. + * + * IORING_ASYNC_CANCEL_ALL Cancel all requests that match the given key + * IORING_ASYNC_CANCEL_FD Key off 'fd' for cancelation rather than the + * request 'user_data' + * IORING_ASYNC_CANCEL_ANY Match any request + * IORING_ASYNC_CANCEL_FD_FIXED 'fd' passed in is a fixed descriptor + * IORING_ASYNC_CANCEL_USERDATA Match on user_data, default for no other key + * IORING_ASYNC_CANCEL_OP Match request based on opcode + */ public enum CancellationMatch { case all case first } - public static func cancel( + @inlinable public static func cancel( _ matchAll: CancellationMatch, matchingContext: UInt64, ) -> IORing.Request { @@ -343,7 +338,7 @@ extension IORing.Request { } } - public static func cancel( + @inlinable public static func cancel( _ matchAll: CancellationMatch, matching: FileDescriptor, ) -> IORing.Request { @@ -355,7 +350,7 @@ extension IORing.Request { } } - public static func cancel( + @inlinable public static func cancel( _ matchAll: CancellationMatch, matching: IORing.RegisteredFile, ) -> IORing.Request { @@ -369,8 +364,8 @@ extension IORing.Request { //TODO: add support for CANCEL_OP - @inline(__always) - public consuming func makeRawRequest() -> RawIORequest { + @inline(__always) @inlinable + internal consuming func makeRawRequest() -> RawIORequest { var request = RawIORequest() switch extractCore() { case .nop: diff --git a/Sources/System/IORing.swift b/Sources/System/IORing.swift index 581e7950..6e31a42b 100644 --- a/Sources/System/IORing.swift +++ b/Sources/System/IORing.swift @@ -1,11 +1,8 @@ #if os(Linux) -@_implementationOnly import CSystem +import CSystem import Glibc // needed for mmap import Synchronization -@_implementationOnly import struct CSystem.io_uring_sqe - -// XXX: this *really* shouldn't be here. oh well. extension UnsafeMutableRawPointer { func advanced(by offset: UInt32) -> UnsafeMutableRawPointer { return advanced(by: Int(offset)) @@ -20,40 +17,40 @@ extension UnsafeMutableRawBufferPointer { // all pointers in this struct reference kernel-visible memory @usableFromInline struct SQRing: ~Copyable { - let kernelHead: UnsafePointer> - let kernelTail: UnsafePointer> - var userTail: UInt32 + @usableFromInline let kernelHead: UnsafePointer> + @usableFromInline let kernelTail: UnsafePointer> + @usableFromInline var userTail: UInt32 // from liburing: the kernel should never change these // might change in the future with resizable rings? - let ringMask: UInt32 + @usableFromInline let ringMask: UInt32 // let ringEntries: UInt32 - absorbed into array.count // ring flags bitfield // currently used by the kernel only in SQPOLL mode to indicate // when the polling thread needs to be woken up - let flags: UnsafePointer> + @usableFromInline let flags: UnsafePointer> // ring array // maps indexes between the actual ring and the submissionQueueEntries list, // allowing the latter to be used as a kind of freelist with enough work? // currently, just 1:1 mapping (0.. + @usableFromInline let array: UnsafeMutableBufferPointer } -struct CQRing: ~Copyable { - let kernelHead: UnsafePointer> - let kernelTail: UnsafePointer> +@usableFromInline struct CQRing: ~Copyable { + @usableFromInline let kernelHead: UnsafePointer> + @usableFromInline let kernelTail: UnsafePointer> // TODO: determine if this is actually used var userHead: UInt32 - let ringMask: UInt32 + @usableFromInline let ringMask: UInt32 - let cqes: UnsafeBufferPointer + @usableFromInline let cqes: UnsafeBufferPointer } -@inline(__always) +@inline(__always) @inlinable internal func _writeRequest( _ request: __owned RawIORequest, ring: inout SQRing, submissionQueueEntries: UnsafeMutableBufferPointer @@ -66,7 +63,7 @@ internal func _writeRequest( return true } -@inline(__always) +@inline(__always) @inlinable internal func _blockingGetSubmissionEntry( ring: inout SQRing, submissionQueueEntries: UnsafeMutableBufferPointer ) -> UnsafeMutablePointer< @@ -86,6 +83,7 @@ internal func _blockingGetSubmissionEntry( //TODO: omitting signal mask for now //Tell the kernel that we've submitted requests and/or are waiting for completions +@inlinable internal func _enter( ringDescriptor: Int32, numEvents: UInt32, @@ -113,22 +111,26 @@ internal func _enter( } } +@inlinable internal func _submitRequests(ring: borrowing SQRing, ringDescriptor: Int32) throws(Errno) { let flushedEvents = _flushQueue(ring: ring) _ = try _enter( ringDescriptor: ringDescriptor, numEvents: flushedEvents, minCompletions: 0, flags: 0) } +@inlinable internal func _getUnconsumedSubmissionCount(ring: borrowing SQRing) -> UInt32 { return ring.userTail - ring.kernelHead.pointee.load(ordering: .acquiring) } +@inlinable internal func _getUnconsumedCompletionCount(ring: borrowing CQRing) -> UInt32 { return ring.kernelTail.pointee.load(ordering: .acquiring) - ring.kernelHead.pointee.load(ordering: .acquiring) } //TODO: pretty sure this is supposed to do more than it does +@inlinable internal func _flushQueue(ring: borrowing SQRing) -> UInt32 { ring.kernelTail.pointee.store( ring.userTail, ordering: .releasing @@ -136,7 +138,7 @@ internal func _flushQueue(ring: borrowing SQRing) -> UInt32 { return _getUnconsumedSubmissionCount(ring: ring) } -@inline(__always) +@inlinable internal func _getSubmissionEntry( ring: inout SQRing, submissionQueueEntries: UnsafeMutableBufferPointer ) -> UnsafeMutablePointer< @@ -188,7 +190,6 @@ private func setUpRing( || params.features & IORING_FEAT_NODROP == 0 { close(ringDescriptor) - // TODO: error handling throw Errno.invalidArgument } @@ -241,22 +242,22 @@ private func setUpRing( public struct IORing: ~Copyable { let ringFlags: UInt32 - let ringDescriptor: Int32 + @usableFromInline let ringDescriptor: Int32 @usableFromInline var submissionRing: SQRing // FEAT: set this eventually let submissionPolling: Bool = false - let completionRing: CQRing + @usableFromInline let completionRing: CQRing - let submissionQueueEntries: UnsafeMutableBufferPointer + @usableFromInline let submissionQueueEntries: UnsafeMutableBufferPointer // kept around for unmap / cleanup let ringSize: Int let ringPtr: UnsafeMutableRawPointer - var _registeredFiles: [UInt32] - var _registeredBuffers: [iovec] + @usableFromInline var _registeredFiles: [UInt32] + @usableFromInline var _registeredBuffers: [iovec] var features = Features(rawValue: 0) @@ -265,7 +266,7 @@ public struct IORing: ~Copyable { @usableFromInline let resource: T @usableFromInline let index: Int - internal init( + @inlinable internal init( resource: T, index: Int ) { @@ -277,7 +278,6 @@ public struct IORing: ~Copyable { public typealias RegisteredFile = RegisteredResource public typealias RegisteredBuffer = RegisteredResource - @frozen public struct SetupFlags: OptionSet, RawRepresentable, Hashable { public var rawValue: UInt32 @@ -370,7 +370,8 @@ public struct IORing: ~Copyable { self.ringFlags = params.flags } - private func _blockingConsumeCompletionGuts( + @inlinable + internal func _blockingConsumeCompletionGuts( minimumCount: UInt32, maximumCount: UInt32, extraArgs: UnsafeMutablePointer? = nil, @@ -438,6 +439,7 @@ public struct IORing: ~Copyable { } } + @inlinable internal func _blockingConsumeOneCompletion( extraArgs: UnsafeMutablePointer? = nil ) throws(Errno) -> Completion { @@ -454,6 +456,7 @@ public struct IORing: ~Copyable { return result.take()! } + @inlinable public func blockingConsumeCompletion( timeout: Duration? = nil ) throws(Errno) -> Completion { @@ -476,6 +479,7 @@ public struct IORing: ~Copyable { } } + @inlinable public func blockingConsumeCompletions( minimumCount: UInt32 = 1, timeout: Duration? = nil, @@ -507,10 +511,12 @@ public struct IORing: ~Copyable { // } + @inlinable public func tryConsumeCompletion() -> Completion? { return _tryConsumeCompletion(ring: completionRing) } + @inlinable func _tryConsumeCompletion(ring: borrowing CQRing) -> Completion? { let tail = ring.kernelTail.pointee.load(ordering: .acquiring) let head = ring.kernelHead.pointee.load(ordering: .acquiring) @@ -580,6 +586,7 @@ public struct IORing: ~Copyable { fatalError("failed to unregister files") } + @inlinable public var registeredFileSlots: RegisteredResources { RegisteredResources(resources: _registeredFiles) } @@ -610,6 +617,7 @@ public struct IORing: ~Copyable { return registeredBuffers } + @inlinable public mutating func registerBuffers(_ buffers: UnsafeMutableRawBufferPointer...) throws(Errno) -> RegisteredResources { @@ -617,33 +625,44 @@ public struct IORing: ~Copyable { } public struct RegisteredResources: RandomAccessCollection { - let resources: [T] + @usableFromInline let resources: [T] - public var startIndex: Int { 0 } - public var endIndex: Int { resources.endIndex } - init(resources: [T]) { + @inlinable public var startIndex: Int { 0 } + @inlinable public var endIndex: Int { resources.endIndex } + @inlinable init(resources: [T]) { self.resources = resources } - public subscript(position: Int) -> RegisteredResource { + @inlinable public subscript(position: Int) -> RegisteredResource { RegisteredResource(resource: resources[position], index: position) } - public subscript(position: UInt16) -> RegisteredResource { + @inlinable public subscript(position: UInt16) -> RegisteredResource { RegisteredResource(resource: resources[Int(position)], index: Int(position)) } } + @inlinable public var registeredBuffers: RegisteredResources { RegisteredResources(resources: _registeredBuffers) } - public func unregisterBuffers() { - fatalError("failed to unregister buffers: TODO") + public func unregisterBuffers() throws { + let result = io_uring_register( + self.ringDescriptor, + IORING_UNREGISTER_BUFFERS.rawValue, + nil, + 0 + ) + guard result >= 0 else { + throw Errno(rawValue: -result) + } } + @inlinable public func submitPreparedRequests() throws(Errno) { try _submitRequests(ring: submissionRing, ringDescriptor: ringDescriptor) } + @inlinable public func submitPreparedRequestsAndConsumeCompletions( minimumCount: UInt32 = 1, timeout: Duration? = nil, @@ -662,12 +681,14 @@ public struct IORing: ~Copyable { ) } + @inlinable public mutating func prepare(request: __owned Request) -> Bool { var raw: RawIORequest? = request.makeRawRequest() return _writeRequest( raw.take()!, ring: &submissionRing, submissionQueueEntries: submissionQueueEntries) } + @inlinable mutating func prepare(linkedRequests: some BidirectionalCollection) { guard linkedRequests.count > 0 else { return @@ -684,17 +705,17 @@ public struct IORing: ~Copyable { submissionQueueEntries: submissionQueueEntries) } - //@inlinable //TODO: make sure the array allocation gets optimized out... + @inlinable public mutating func prepare(linkedRequests: Request...) { prepare(linkedRequests: linkedRequests) } + @inlinable public mutating func submit(linkedRequests: Request...) throws(Errno) { prepare(linkedRequests: linkedRequests) try submitPreparedRequests() } - @frozen public struct Features: OptionSet, RawRepresentable, Hashable { public let rawValue: UInt32 @@ -734,12 +755,12 @@ public struct IORing: ~Copyable { } extension IORing.RegisteredFile { - public var unsafeFileSlot: Int { + @inlinable public var unsafeFileSlot: Int { return index } } extension IORing.RegisteredBuffer { - public var unsafeBuffer: UnsafeMutableRawBufferPointer { + @inlinable public var unsafeBuffer: UnsafeMutableRawBufferPointer { return .init(start: resource.iov_base, count: resource.iov_len) } } diff --git a/Sources/System/RawIORequest.swift b/Sources/System/RawIORequest.swift index 33bea295..e4d0f474 100644 --- a/Sources/System/RawIORequest.swift +++ b/Sources/System/RawIORequest.swift @@ -1,19 +1,19 @@ #if os(Linux) -// TODO: investigate @usableFromInline / @_implementationOnly dichotomy -@_implementationOnly import CSystem -@_implementationOnly import struct CSystem.io_uring_sqe +import CSystem +import struct CSystem.io_uring_sqe -//TODO: make this internal -public struct RawIORequest: ~Copyable { - var rawValue: io_uring_sqe - var path: FilePath? //buffer owner for the path pointer that the sqe may have +@usableFromInline +internal struct RawIORequest: ~Copyable { + @usableFromInline var rawValue: io_uring_sqe + @usableFromInline var path: FilePath? //buffer owner for the path pointer that the sqe may have - public init() { + @inlinable public init() { self.rawValue = io_uring_sqe() } } extension RawIORequest { + @usableFromInline enum Operation: UInt8 { case nop = 0 case readv = 1 @@ -45,49 +45,49 @@ extension RawIORequest { public struct Flags: OptionSet, Hashable, Codable { public let rawValue: UInt8 - public init(rawValue: UInt8) { + @inlinable public init(rawValue: UInt8) { self.rawValue = rawValue } - public static let fixedFile = Flags(rawValue: 1 << 0) - public static let drainQueue = Flags(rawValue: 1 << 1) - public static let linkRequest = Flags(rawValue: 1 << 2) - public static let hardlinkRequest = Flags(rawValue: 1 << 3) - public static let asynchronous = Flags(rawValue: 1 << 4) - public static let selectBuffer = Flags(rawValue: 1 << 5) - public static let skipSuccess = Flags(rawValue: 1 << 6) + @inlinable public static var fixedFile: RawIORequest.Flags { Flags(rawValue: 1 << 0) } + @inlinable public static var drainQueue: RawIORequest.Flags { Flags(rawValue: 1 << 1) } + @inlinable public static var linkRequest: RawIORequest.Flags { Flags(rawValue: 1 << 2) } + @inlinable public static var hardlinkRequest: RawIORequest.Flags { Flags(rawValue: 1 << 3) } + @inlinable public static var asynchronous: RawIORequest.Flags { Flags(rawValue: 1 << 4) } + @inlinable public static var selectBuffer: RawIORequest.Flags { Flags(rawValue: 1 << 5) } + @inlinable public static var skipSuccess: RawIORequest.Flags { Flags(rawValue: 1 << 6) } } - var operation: Operation { + @inlinable var operation: Operation { get { Operation(rawValue: rawValue.opcode)! } set { rawValue.opcode = newValue.rawValue } } - var cancel_flags: UInt32 { + @inlinable var cancel_flags: UInt32 { get { rawValue.cancel_flags } set { rawValue.cancel_flags = newValue } } - var addr: UInt64 { + @inlinable var addr: UInt64 { get { rawValue.addr } set { rawValue.addr = newValue } } - public var flags: Flags { + @inlinable public var flags: Flags { get { Flags(rawValue: rawValue.flags) } set { rawValue.flags = newValue.rawValue } } - public mutating func linkToNextRequest() { + @inlinable public mutating func linkToNextRequest() { flags = Flags(rawValue: flags.rawValue | Flags.linkRequest.rawValue) } - public var fileDescriptor: FileDescriptor { + @inlinable public var fileDescriptor: FileDescriptor { get { FileDescriptor(rawValue: rawValue.fd) } set { rawValue.fd = newValue.rawValue } } - public var offset: UInt64? { + @inlinable public var offset: UInt64? { get { if (rawValue.off == UInt64.max) { return nil @@ -104,7 +104,7 @@ extension RawIORequest { } } - public var buffer: UnsafeMutableRawBufferPointer { + @inlinable public var buffer: UnsafeMutableRawBufferPointer { get { let ptr = UnsafeMutableRawPointer(bitPattern: UInt(exactly: rawValue.addr)!) return UnsafeMutableRawBufferPointer(start: ptr, count: Int(rawValue.len)) @@ -135,45 +135,46 @@ extension RawIORequest { public struct ReadWriteFlags: OptionSet, Hashable, Codable { public var rawValue: UInt32 - public init(rawValue: UInt32) { + @inlinable public init(rawValue: UInt32) { self.rawValue = rawValue } - public static let highPriority = ReadWriteFlags(rawValue: 1 << 0) + @inlinable public static var highPriority: RawIORequest.ReadWriteFlags { ReadWriteFlags(rawValue: 1 << 0) } // sync with only data integrity - public static let dataSync = ReadWriteFlags(rawValue: 1 << 1) + @inlinable public static var dataSync: RawIORequest.ReadWriteFlags { ReadWriteFlags(rawValue: 1 << 1) } // sync with full data + file integrity - public static let fileSync = ReadWriteFlags(rawValue: 1 << 2) + @inlinable public static var fileSync: RawIORequest.ReadWriteFlags { ReadWriteFlags(rawValue: 1 << 2) } // return -EAGAIN if operation blocks - public static let noWait = ReadWriteFlags(rawValue: 1 << 3) + @inlinable public static var noWait: RawIORequest.ReadWriteFlags { ReadWriteFlags(rawValue: 1 << 3) } // append to end of the file - public static let append = ReadWriteFlags(rawValue: 1 << 4) + @inlinable public static var append: RawIORequest.ReadWriteFlags { ReadWriteFlags(rawValue: 1 << 4) } } public struct TimeOutFlags: OptionSet, Hashable, Codable { public var rawValue: UInt32 - public init(rawValue: UInt32) { + @inlinable public init(rawValue: UInt32) { self.rawValue = rawValue } - public static let relativeTime: RawIORequest.TimeOutFlags = TimeOutFlags(rawValue: 0) - public static let absoluteTime: RawIORequest.TimeOutFlags = TimeOutFlags(rawValue: 1 << 0) + @inlinable public static var relativeTime: RawIORequest.TimeOutFlags { TimeOutFlags(rawValue: 0) } + @inlinable public static var absoluteTime: RawIORequest.TimeOutFlags { TimeOutFlags(rawValue: 1 << 0) } } } extension RawIORequest { + @inlinable static func nop() -> RawIORequest { var req: RawIORequest = RawIORequest() req.operation = .nop return req } - //TODO: typed errors + @inlinable static func withTimeoutRequest( linkedTo opEntry: UnsafeMutablePointer, in timeoutEntry: UnsafeMutablePointer, From bbe2f1c192eedf9ee1019264fb27173446ac2624 Mon Sep 17 00:00:00 2001 From: David Smith Date: Tue, 8 Jul 2025 00:41:50 +0000 Subject: [PATCH 354/427] Add doc comments, Span/MutableSpan support, unregistering registered fds, variants of submission operations that don't block, address various TODOs --- Package.swift | 1 + Sources/System/IORequest.swift | 2 - Sources/System/IORing.swift | 140 ++++++++++++++++++++++++------ Sources/System/RawIORequest.swift | 2 - 4 files changed, 116 insertions(+), 29 deletions(-) diff --git a/Package.swift b/Package.swift index 14b040e7..48b348af 100644 --- a/Package.swift +++ b/Package.swift @@ -22,6 +22,7 @@ let swiftSettings: [SwiftSetting] = [ .when(platforms: [.macOS, .macCatalyst, .iOS, .watchOS, .tvOS, .visionOS])), .define("SYSTEM_PACKAGE"), .define("ENABLE_MOCKING", .when(configuration: .debug)), + .enableExperimentalFeature("Lifetimes"), ] let package = Package( diff --git a/Sources/System/IORequest.swift b/Sources/System/IORequest.swift index 8af313a3..1d0638b4 100644 --- a/Sources/System/IORequest.swift +++ b/Sources/System/IORequest.swift @@ -362,8 +362,6 @@ extension IORing.Request { } } - //TODO: add support for CANCEL_OP - @inline(__always) @inlinable internal consuming func makeRawRequest() -> RawIORequest { var request = RawIORequest() diff --git a/Sources/System/IORing.swift b/Sources/System/IORing.swift index 15d9cd79..681ca7e8 100644 --- a/Sources/System/IORing.swift +++ b/Sources/System/IORing.swift @@ -47,25 +47,35 @@ extension UnsafeMutableRawBufferPointer { @usableFromInline let kernelHead: UnsafePointer> @usableFromInline let kernelTail: UnsafePointer> - // TODO: determine if this is actually used - var userHead: UInt32 - @usableFromInline let ringMask: UInt32 @usableFromInline let cqes: UnsafeBufferPointer } @inline(__always) @inlinable -internal func _writeRequest( +internal func _tryWriteRequest( _ request: __owned RawIORequest, ring: inout SQRing, submissionQueueEntries: UnsafeMutableBufferPointer ) -> Bool +{ + if let entry = _getSubmissionEntry( + ring: &ring, submissionQueueEntries: submissionQueueEntries) { + entry.pointee = request.rawValue + return true + } + return false +} + +@inline(__always) @inlinable +internal func _writeRequest( + _ request: __owned RawIORequest, ring: inout SQRing, + submissionQueueEntries: UnsafeMutableBufferPointer +) { let entry = _blockingGetSubmissionEntry( ring: &ring, submissionQueueEntries: submissionQueueEntries) entry.pointee = request.rawValue - return true } @inline(__always) @inlinable @@ -151,8 +161,7 @@ internal func _getSubmissionEntry( >? { let next = ring.userTail &+ 1 //this is expected to wrap - // FEAT: smp load when SQPOLL in use (not in MVP) - let kernelHead = ring.kernelHead.pointee.load(ordering: .acquiring) + let kernelHead: UInt32 = ring.kernelHead.pointee.load(ordering: .acquiring) // FEAT: 128-bit event support (not in MVP) if next - kernelHead <= ring.array.count { @@ -245,6 +254,11 @@ private func setUpRing( return (params: params, ringDescriptor: ringDescriptor, ringPtr: ringPtr!, ringSize: ringSize, sqes: sqes!) } +///IORing provides facilities for +/// * Registering and unregistering resources (files and buffers), an `io_uring` specific variation on Unix file IOdescriptors that improves their efficiency +/// * Registering and unregistering eventfds, which allow asynchronous waiting for completions +/// * Enqueueing IO requests +/// * Dequeueing IO completions public struct IORing: ~Copyable { let ringFlags: UInt32 @usableFromInline let ringDescriptor: Int32 @@ -266,10 +280,11 @@ public struct IORing: ~Copyable { var features = Features(rawValue: 0) + /// RegisteredResource is used via its typealiases, RegisteredFile and RegisteredBuffer. Registering file descriptors and buffers with the IORing allows for more efficient access to them. public struct RegisteredResource { public typealias Resource = T @usableFromInline let resource: T - @usableFromInline let index: Int + public let index: Int @inlinable internal init( resource: T, @@ -283,6 +298,7 @@ public struct IORing: ~Copyable { public typealias RegisteredFile = RegisteredResource public typealias RegisteredBuffer = RegisteredResource + /// SetupFlags represents configuration options to an IORing as it's being created public struct SetupFlags: OptionSet, RawRepresentable, Hashable { public var rawValue: UInt32 @@ -298,14 +314,15 @@ public struct IORing: ~Copyable { //TODO: do we want to expose IORING_SETUP_COOP_TASKRUN and IORING_SETUP_TASKRUN_FLAG? //public static var runTasksCooperatively: SetupFlags { .init(rawValue: UInt32(1) << 8) } //IORING_SETUP_COOP_TASKRUN //TODO: can we even do different size sqe/cqe? It requires a kernel feature, but how do we convince swift to let the types be different sizes? - internal static var use128ByteSQEs: SetupFlags { .init(rawValue: UInt32(1) << 10) } //IORING_SETUP_SQE128 - internal static var use32ByteCQEs: SetupFlags { .init(rawValue: UInt32(1) << 11) } //IORING_SETUP_CQE32 + //internal static var use128ByteSQEs: SetupFlags { .init(rawValue: UInt32(1) << 10) } //IORING_SETUP_SQE128 + //internal static var use32ByteCQEs: SetupFlags { .init(rawValue: UInt32(1) << 11) } //IORING_SETUP_CQE32 @inlinable public static var singleSubmissionThread: SetupFlags { .init(rawValue: UInt32(1) << 12) } //IORING_SETUP_SINGLE_ISSUER @inlinable public static var deferRunningTasks: SetupFlags { .init(rawValue: UInt32(1) << 13) } //IORING_SETUP_DEFER_TASKRUN //pretty sure we don't want to expose IORING_SETUP_NO_MMAP or IORING_SETUP_REGISTERED_FD_ONLY currently //TODO: should IORING_SETUP_NO_SQARRAY be the default? do we need to adapt anything to it? } + /// Initializes an IORing with enough space for `queueDepth` prepared requests and completed operations public init(queueDepth: UInt32, flags: SetupFlags = []) throws(Errno) { let (params, tmpRingDescriptor, tmpRingPtr, tmpRingSize, sqes) = try setUpRing(queueDepth: queueDepth, flags: flags) // All throws need to be before initializing ivars here to avoid @@ -361,7 +378,6 @@ public struct IORing: ~Copyable { ringPtr.advanced(by: params.cq_off.tail) .assumingMemoryBound(to: Atomic.self) ), - userHead: 0, // no completions yet ringMask: ringPtr.advanced(by: params.cq_off.ring_mask) .assumingMemoryBound(to: UInt32.self).pointee, cqes: UnsafeBufferPointer( @@ -461,6 +477,7 @@ public struct IORing: ~Copyable { return result.take()! } + /// Synchronously waits for an operation to complete for up to `timeout` (or forever if not specified) @inlinable public func blockingConsumeCompletion( timeout: Duration? = nil @@ -484,6 +501,7 @@ public struct IORing: ~Copyable { } } + /// Synchronously waits for `minimumCount` or more operations to complete for up to `timeout` (or forever if not specified). For each completed operation found, `consumer` is called to handle processing it @inlinable public func blockingConsumeCompletions( minimumCount: UInt32 = 1, @@ -516,6 +534,7 @@ public struct IORing: ~Copyable { // } + /// Takes a completed operation from the ring and returns it, if one is ready. Otherwise, returns nil. @inlinable public func tryConsumeCompletion() -> Completion? { return _tryConsumeCompletion(ring: completionRing) @@ -536,6 +555,7 @@ public struct IORing: ~Copyable { return nil } + /// Registers an event monitoring file descriptor with the ring. The file descriptor becomes readable whenever completions are ready to be dequeued. See `man eventfd(2)` for additional information. public mutating func registerEventFD(_ descriptor: FileDescriptor) throws(Errno) { var rawfd = descriptor.rawValue let result = withUnsafePointer(to: &rawfd) { fdptr in @@ -552,6 +572,7 @@ public struct IORing: ~Copyable { } } + /// Removes a registered event file descriptor from the ring public mutating func unregisterEventFD() throws(Errno) { let result = io_uring_register( ringDescriptor, @@ -564,6 +585,7 @@ public struct IORing: ~Copyable { } } + /// Registers `count` files with the ring for later use in IO operations public mutating func registerFileSlots(count: Int) throws(Errno) -> RegisteredResources { precondition(_registeredFiles.isEmpty) precondition(count < UInt32.max) @@ -587,21 +609,31 @@ public struct IORing: ~Copyable { return registeredFileSlots } - public func unregisterFiles() { - fatalError("failed to unregister files") + /// Removes registered files from the ring + public func unregisterFiles() throws { + let result = io_uring_register( + ringDescriptor, + IORING_UNREGISTER_FILES.rawValue, + nil, + 0 + ) + if result < 0 { + throw Errno(rawValue: -result) + } } + /// Allows access to registered files by index @inlinable public var registeredFileSlots: RegisteredResources { RegisteredResources(resources: _registeredFiles) } + /// Registers buffers with the ring for later use in IO operations public mutating func registerBuffers(_ buffers: some Collection) throws(Errno) -> RegisteredResources { precondition(buffers.count < UInt32.max) precondition(_registeredBuffers.isEmpty) - //TODO: check if io_uring has preconditions it needs for the buffers (e.g. alignment) let iovecs = buffers.map { $0.to_iovec() } let regResult = iovecs.withUnsafeBufferPointer { bPtr in let result = io_uring_register( @@ -617,11 +649,11 @@ public struct IORing: ~Copyable { throw regResult } - // TODO: error handling _registeredBuffers = iovecs return registeredBuffers } + /// Registers buffers with the ring for later use in IO operations @inlinable public mutating func registerBuffers(_ buffers: UnsafeMutableRawBufferPointer...) throws(Errno) -> RegisteredResources @@ -629,6 +661,7 @@ public struct IORing: ~Copyable { try registerBuffers(buffers) } + /// A view of the registered files or buffers in a ring public struct RegisteredResources: RandomAccessCollection { @usableFromInline let resources: [T] @@ -645,6 +678,7 @@ public struct IORing: ~Copyable { } } + /// Allows access to registered files by index @inlinable public var registeredBuffers: RegisteredResources { RegisteredResources(resources: _registeredBuffers) @@ -662,11 +696,13 @@ public struct IORing: ~Copyable { } } + /// Sends all prepared requests to the kernel for processing. Results will be delivered as completions, which can be dequeued from the ring. @inlinable public func submitPreparedRequests() throws(Errno) { try _submitRequests(ring: submissionRing, ringDescriptor: ringDescriptor) } + /// Sends all prepared requests to the kernel for processing, and then dequeues at least `minimumCount` completions, waiting up to `timeout` for them to become available. `consumer` is called to process each completed IO operation as it becomes available. @inlinable public func submitPreparedRequestsAndConsumeCompletions( minimumCount: UInt32 = 1, @@ -686,13 +722,48 @@ public struct IORing: ~Copyable { ) } + /// Attempts to prepare an IO request for submission to the kernel. Returns false if no space is available to enqueue the request @inlinable - public mutating func prepare(request: __owned Request) -> Bool { + public mutating func tryPrepare(request: __owned Request) -> Bool { var raw: RawIORequest? = request.makeRawRequest() - return _writeRequest( + return _tryWriteRequest( raw.take()!, ring: &submissionRing, submissionQueueEntries: submissionQueueEntries) } + /// Attempts to prepare an IO request for submission to the kernel. Blocks if needed until space becomes available to enqueue the request + @inlinable + public mutating func prepare(request: __owned Request) { + var raw: RawIORequest? = request.makeRawRequest() + _writeRequest( + raw.take()!, ring: &submissionRing, submissionQueueEntries: submissionQueueEntries) + } + + /// Attempts to prepare a chain of linked IO requests for submission to the kernel. Returns false if not enough space is available to enqueue the request. If any linked operation fails, subsequent operations will be canceled. Linked operations always execute in order. + @inlinable + mutating func tryPrepare(linkedRequests: some BidirectionalCollection) -> Bool { + guard linkedRequests.count > 0 else { + return true + } + let freeSQECount = _getUnconsumedSubmissionCount(ring: submissionRing) + guard freeSQECount >= linkedRequests.count else { + return false + } + let last = linkedRequests.last! + for req in linkedRequests.dropLast() { + var raw = req.makeRawRequest() + raw.linkToNextRequest() + let successfullyAdded = _tryWriteRequest( + raw, ring: &submissionRing, submissionQueueEntries: submissionQueueEntries) + assert(successfullyAdded) + } + let successfullyAdded = _tryWriteRequest( + last.makeRawRequest(), ring: &submissionRing, + submissionQueueEntries: submissionQueueEntries) + assert(successfullyAdded) + return true + } + + /// Prepares a chain of linked IO requests for submission to the kernel. Blocks if needed until space becomes available to enqueue the requests. If any linked operation fails, subsequent operations will be canceled. Linked operations always execute in order. @inlinable mutating func prepare(linkedRequests: some BidirectionalCollection) { guard linkedRequests.count > 0 else { @@ -702,25 +773,34 @@ public struct IORing: ~Copyable { for req in linkedRequests.dropLast() { var raw = req.makeRawRequest() raw.linkToNextRequest() - _ = _writeRequest( + _writeRequest( raw, ring: &submissionRing, submissionQueueEntries: submissionQueueEntries) } - _ = _writeRequest( + _writeRequest( last.makeRawRequest(), ring: &submissionRing, submissionQueueEntries: submissionQueueEntries) } + /// Prepares a sequence of requests for submission to the ring. Returns false if the submission queue doesn't have enough available space. + @inlinable + public mutating func tryPrepare(linkedRequests: Request...) -> Bool { + tryPrepare(linkedRequests: linkedRequests) + } + + /// Prepares a sequence of requests for submission to the ring. Blocks if the submission queue doesn't have enough available space. @inlinable public mutating func prepare(linkedRequests: Request...) { prepare(linkedRequests: linkedRequests) } + /// Prepares and submits a sequence of requests to the ring. Blocks if the submission queue doesn't have enough available space. @inlinable public mutating func submit(linkedRequests: Request...) throws(Errno) { prepare(linkedRequests: linkedRequests) try submitPreparedRequests() } + /// Describes which io_uring features are supported by the kernel this program is running on public struct Features: OptionSet, RawRepresentable, Hashable { public let rawValue: UInt32 @@ -745,6 +825,8 @@ public struct IORing: ~Copyable { @inlinable public static var minimumTimeout: Features { .init(rawValue: UInt32(1) << 15) } //IORING_FEAT_MIN_TIMEOUT @inlinable public static var bundledSendReceive: Features { .init(rawValue: UInt32(1) << 14) } //IORING_FEAT_RECVSEND_BUNDLE } + + /// Describes which io_uring features are supported by the kernel this program is running on public var supportedFeatures: Features { return features } @@ -759,14 +841,22 @@ public struct IORing: ~Copyable { } } -extension IORing.RegisteredFile { - @inlinable public var unsafeFileSlot: Int { - return index - } -} extension IORing.RegisteredBuffer { - @inlinable public var unsafeBuffer: UnsafeMutableRawBufferPointer { + @unsafe @inlinable public var unsafeBuffer: UnsafeMutableRawBufferPointer { return .init(start: resource.iov_base, count: resource.iov_len) } + + @inlinable public var mutableBytes: MutableRawSpan { + @_lifetime(&self) + mutating get { + let span = MutableRawSpan(_unsafeBytes: unsafeBuffer) + return unsafe _overrideLifetime(span, mutating: &self) + } + } + + @inlinable public var bytes: RawSpan { + let span = RawSpan(_unsafeBytes: UnsafeRawBufferPointer(unsafeBuffer)) + return unsafe _overrideLifetime(span, borrowing: self) + } } #endif diff --git a/Sources/System/RawIORequest.swift b/Sources/System/RawIORequest.swift index e4d0f474..3734760f 100644 --- a/Sources/System/RawIORequest.swift +++ b/Sources/System/RawIORequest.swift @@ -1,6 +1,5 @@ #if os(Linux) import CSystem -import struct CSystem.io_uring_sqe @usableFromInline internal struct RawIORequest: ~Copyable { @@ -111,7 +110,6 @@ extension RawIORequest { } set { - // TODO: cleanup? rawValue.addr = UInt64(Int(bitPattern: newValue.baseAddress!)) rawValue.len = UInt32(exactly: newValue.count)! } From 227a38f230f5ad94df7fa1fc72e6b24f1e8b9056 Mon Sep 17 00:00:00 2001 From: David Smith Date: Tue, 8 Jul 2025 22:41:06 +0000 Subject: [PATCH 355/427] Remove blocking enqueue --- Sources/System/IORing.swift | 47 ++++++----------------------- Tests/SystemTests/IORingTests.swift | 8 +++-- 2 files changed, 14 insertions(+), 41 deletions(-) diff --git a/Sources/System/IORing.swift b/Sources/System/IORing.swift index 681ca7e8..64515c3f 100644 --- a/Sources/System/IORing.swift +++ b/Sources/System/IORing.swift @@ -724,23 +724,15 @@ public struct IORing: ~Copyable { /// Attempts to prepare an IO request for submission to the kernel. Returns false if no space is available to enqueue the request @inlinable - public mutating func tryPrepare(request: __owned Request) -> Bool { + public mutating func prepare(request: __owned Request) -> Bool { var raw: RawIORequest? = request.makeRawRequest() return _tryWriteRequest( raw.take()!, ring: &submissionRing, submissionQueueEntries: submissionQueueEntries) } - /// Attempts to prepare an IO request for submission to the kernel. Blocks if needed until space becomes available to enqueue the request - @inlinable - public mutating func prepare(request: __owned Request) { - var raw: RawIORequest? = request.makeRawRequest() - _writeRequest( - raw.take()!, ring: &submissionRing, submissionQueueEntries: submissionQueueEntries) - } - /// Attempts to prepare a chain of linked IO requests for submission to the kernel. Returns false if not enough space is available to enqueue the request. If any linked operation fails, subsequent operations will be canceled. Linked operations always execute in order. @inlinable - mutating func tryPrepare(linkedRequests: some BidirectionalCollection) -> Bool { + mutating func prepare(linkedRequests: some BidirectionalCollection) -> Bool { guard linkedRequests.count > 0 else { return true } @@ -763,41 +755,20 @@ public struct IORing: ~Copyable { return true } - /// Prepares a chain of linked IO requests for submission to the kernel. Blocks if needed until space becomes available to enqueue the requests. If any linked operation fails, subsequent operations will be canceled. Linked operations always execute in order. - @inlinable - mutating func prepare(linkedRequests: some BidirectionalCollection) { - guard linkedRequests.count > 0 else { - return - } - let last = linkedRequests.last! - for req in linkedRequests.dropLast() { - var raw = req.makeRawRequest() - raw.linkToNextRequest() - _writeRequest( - raw, ring: &submissionRing, submissionQueueEntries: submissionQueueEntries) - } - _writeRequest( - last.makeRawRequest(), ring: &submissionRing, - submissionQueueEntries: submissionQueueEntries) - } - /// Prepares a sequence of requests for submission to the ring. Returns false if the submission queue doesn't have enough available space. @inlinable - public mutating func tryPrepare(linkedRequests: Request...) -> Bool { - tryPrepare(linkedRequests: linkedRequests) - } - - /// Prepares a sequence of requests for submission to the ring. Blocks if the submission queue doesn't have enough available space. - @inlinable - public mutating func prepare(linkedRequests: Request...) { + public mutating func prepare(linkedRequests: Request...) -> Bool { prepare(linkedRequests: linkedRequests) } - /// Prepares and submits a sequence of requests to the ring. Blocks if the submission queue doesn't have enough available space. + /// Prepares and submits a sequence of requests to the ring. Returns false if the submission queue doesn't have enough available space. @inlinable - public mutating func submit(linkedRequests: Request...) throws(Errno) { - prepare(linkedRequests: linkedRequests) + public mutating func submit(linkedRequests: Request...) throws(Errno) -> Bool { + if !prepare(linkedRequests: linkedRequests) { + return false + } try submitPreparedRequests() + return true } /// Describes which io_uring features are supported by the kernel this program is running on diff --git a/Tests/SystemTests/IORingTests.swift b/Tests/SystemTests/IORingTests.swift index 29973c63..1297bf2d 100644 --- a/Tests/SystemTests/IORingTests.swift +++ b/Tests/SystemTests/IORingTests.swift @@ -15,7 +15,7 @@ final class IORingTests: XCTestCase { func testNop() throws { var ring = try IORing(queueDepth: 32, flags: []) - try ring.submit(linkedRequests: .nop()) + _ = try ring.submit(linkedRequests: .nop()) let completion = try ring.blockingConsumeCompletion() XCTAssertEqual(completion.result, 0) } @@ -57,10 +57,11 @@ final class IORingTests: XCTestCase { try ring.registerEventFD(eventFD) //Part 1: read the file we just created, and make sure the eventfd fires - try ring.submit(linkedRequests: + let enqueued = try ring.submit(linkedRequests: .open(path, in: parent, into: ring.registeredFileSlots[0], mode: .readOnly), .read(ring.registeredFileSlots[0], into: ring.registeredBuffers[0]), .close(ring.registeredFileSlots[0])) + XCTAssert(enqueued) let efdBuf = UnsafeMutableRawBufferPointer.allocate(byteCount: 8, alignment: 0) _ = try eventFD.read(into: efdBuf) _ = try ring.blockingConsumeCompletion() //open @@ -76,10 +77,11 @@ final class IORingTests: XCTestCase { remove($0) } XCTAssertEqual(rmResult, 0) - try ring.submit(linkedRequests: + let enqueued2 = try ring.submit(linkedRequests: .open(path, in: parent, into: ring.registeredFileSlots[0], mode: .readWrite, options: .create, permissions: .ownerReadWrite), .write(ring.registeredBuffers[0], into: ring.registeredFileSlots[0]), .close(ring.registeredFileSlots[0])) + XCTAssert(enqueued2) _ = try eventFD.read(into: efdBuf) _ = try ring.blockingConsumeCompletion() //open _ = try eventFD.read(into: efdBuf) From bc097e91dc7efe4c79324ec970aace5ed7b63ddd Mon Sep 17 00:00:00 2001 From: David Smith Date: Tue, 8 Jul 2025 22:42:15 +0000 Subject: [PATCH 356/427] Remove more blocking enqueue infrastructure --- Sources/System/IORing.swift | 29 ----------------------------- 1 file changed, 29 deletions(-) diff --git a/Sources/System/IORing.swift b/Sources/System/IORing.swift index 64515c3f..5c483734 100644 --- a/Sources/System/IORing.swift +++ b/Sources/System/IORing.swift @@ -67,35 +67,6 @@ internal func _tryWriteRequest( return false } -@inline(__always) @inlinable -internal func _writeRequest( - _ request: __owned RawIORequest, ring: inout SQRing, - submissionQueueEntries: UnsafeMutableBufferPointer -) -{ - let entry = _blockingGetSubmissionEntry( - ring: &ring, submissionQueueEntries: submissionQueueEntries) - entry.pointee = request.rawValue -} - -@inline(__always) @inlinable -internal func _blockingGetSubmissionEntry( - ring: inout SQRing, submissionQueueEntries: UnsafeMutableBufferPointer -) -> UnsafeMutablePointer< - io_uring_sqe -> { - while true { - if let entry = _getSubmissionEntry( - ring: &ring, - submissionQueueEntries: submissionQueueEntries - ) { - return entry - } - // TODO: actually block here instead of spinning - } - -} - //TODO: omitting signal mask for now //Tell the kernel that we've submitted requests and/or are waiting for completions @inlinable From 55e55c197880d5f4422937d59d537e8266560f4a Mon Sep 17 00:00:00 2001 From: David Smith Date: Tue, 8 Jul 2025 23:15:17 +0000 Subject: [PATCH 357/427] Add test and fix submission queue capacity check --- Sources/System/IORing.swift | 12 ++++++++---- Tests/SystemTests/IORingTests.swift | 10 ++++++++-- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/Sources/System/IORing.swift b/Sources/System/IORing.swift index 5c483734..4a28f7c8 100644 --- a/Sources/System/IORing.swift +++ b/Sources/System/IORing.swift @@ -105,23 +105,27 @@ internal func _submitRequests(ring: borrowing SQRing, ringDescriptor: Int32) thr } @inlinable -internal func _getUnconsumedSubmissionCount(ring: borrowing SQRing) -> UInt32 { +internal func _getSubmissionQueueCount(ring: borrowing SQRing) -> UInt32 { return ring.userTail - ring.kernelHead.pointee.load(ordering: .acquiring) } +@inlinable +internal func _getRemainingSubmissionQueueCapacity(ring: borrowing SQRing) -> UInt32 { + return UInt32(truncatingIfNeeded: ring.array.count) - _getSubmissionQueueCount(ring: ring) +} + @inlinable internal func _getUnconsumedCompletionCount(ring: borrowing CQRing) -> UInt32 { return ring.kernelTail.pointee.load(ordering: .acquiring) - ring.kernelHead.pointee.load(ordering: .acquiring) } -//TODO: pretty sure this is supposed to do more than it does @inlinable internal func _flushQueue(ring: borrowing SQRing) -> UInt32 { ring.kernelTail.pointee.store( ring.userTail, ordering: .releasing ) - return _getUnconsumedSubmissionCount(ring: ring) + return _getSubmissionQueueCount(ring: ring) } @inlinable @@ -707,7 +711,7 @@ public struct IORing: ~Copyable { guard linkedRequests.count > 0 else { return true } - let freeSQECount = _getUnconsumedSubmissionCount(ring: submissionRing) + let freeSQECount = _getRemainingSubmissionQueueCapacity(ring: submissionRing) guard freeSQECount >= linkedRequests.count else { return false } diff --git a/Tests/SystemTests/IORingTests.swift b/Tests/SystemTests/IORingTests.swift index 1297bf2d..757866ad 100644 --- a/Tests/SystemTests/IORingTests.swift +++ b/Tests/SystemTests/IORingTests.swift @@ -42,17 +42,23 @@ final class IORingTests: XCTestCase { } func setupTestRing(depth: Int, fileSlots: Int, buffers: [UnsafeMutableRawBufferPointer]) throws -> IORing { - var ring: IORing = try IORing(queueDepth: 6) + var ring: IORing = try IORing(queueDepth: UInt32(depth)) _ = try ring.registerFileSlots(count: 1) _ = try ring.registerBuffers(buffers) return ring } + func testUndersizedSubmissionQueue() throws { + var ring: IORing = try IORing(queueDepth: 1) + let enqueued = ring.prepare(linkedRequests: .nop(), .nop()) + XCTAssertFalse(enqueued) + } + // Exercises opening, reading, closing, registered files, registered buffers, and eventfd func testOpenReadAndWriteFixedFile() throws { let (parent, path) = try makeHelloWorldFile() let rawBuffer = UnsafeMutableRawBufferPointer.allocate(byteCount: 13, alignment: 16) - var ring = try setupTestRing(depth: 3, fileSlots: 1, buffers: [rawBuffer]) + var ring = try setupTestRing(depth: 6, fileSlots: 1, buffers: [rawBuffer]) let eventFD = FileDescriptor(rawValue: eventfd(0, Int32(EFD_SEMAPHORE))) try ring.registerEventFD(eventFD) From a6c5a39ca53ec58d4d7cf5ebbd98e32cfcdd1185 Mon Sep 17 00:00:00 2001 From: David Smith Date: Tue, 8 Jul 2025 16:24:20 -0700 Subject: [PATCH 358/427] Remove the proposal doc --- NNNN-swift-system-io-uring.md | 463 ---------------------------------- 1 file changed, 463 deletions(-) delete mode 100644 NNNN-swift-system-io-uring.md diff --git a/NNNN-swift-system-io-uring.md b/NNNN-swift-system-io-uring.md deleted file mode 100644 index d103578e..00000000 --- a/NNNN-swift-system-io-uring.md +++ /dev/null @@ -1,463 +0,0 @@ -# IORing, a Swift System API for io_uring - -* Proposal: [SE-NNNN](NNNN-filename.md) -* Authors: [Lucy Satheesan](https://github.com/oxy), [David Smith](https://github.com/Catfish-Man/) -* Review Manager: TBD -* Status: **Awaiting implementation** -* Implementation: [apple/swift-system#208](https://github.com/apple/swift-system/pull/208) - -## Introduction - -`io_uring` is Linux's solution to asynchronous and batched syscalls, with a particular focus on IO. We propose a low-level Swift API for it in Swift System that could either be used directly by projects with unusual needs, or via intermediaries like Swift NIO, to address scalability and thread pool starvation issues. - -## Motivation - -Up until recently, the overwhelmingly dominant file IO syscalls on major Unix platforms have been synchronous, e.g. `read(2)`. This design is very simple and proved sufficient for many uses for decades, but is less than ideal for Swift's needs in a few major ways: - -1. Requiring an entire OS thread for each concurrent operation imposes significant memory overhead -2. Requiring a separate syscall for each operation imposes significant CPU/time overhead to switch into and out of kernel mode repeatedly. This has been exacerbated in recent years by mitigations for the Meltdown family of security exploits increasing the cost of syscalls. -3. Swift's N:M coroutine-on-thread-pool concurrency model assumes that threads will not be blocked. Each thread waiting for a syscall means a CPU core being left idle. In practice systems like NIO that deal in highly concurrent IO have had to work around this by providing their own thread pools. - -Non-file IO (network, pipes, etc…) has been in a somewhat better place with `epoll` and `kqueue` for asynchronously waiting for readability, but syscall overhead remains a significant issue for highly scalable systems. - -With the introduction of `io_uring` in 2019, Linux now has the kernel level tools to address these three problems directly. However, `io_uring` is quite complex and maps poorly into Swift. We expect that by providing a Swift interface to it, we can enable Swift on Linux servers to scale better and be more efficient than it has been in the past. - -## Proposed solution - -We propose a *low level, unopinionated* Swift interface for io_uring on Linux (see Future Directions for discussion of possible more abstract interfaces). - -`struct IORing: ~Copyable` provides facilities for - -* Registering and unregistering resources (files and buffers), an `io_uring` specific variation on Unix file IOdescriptors that improves their efficiency -* Registering and unregistering eventfds, which allow asynchronous waiting for completions -* Enqueueing IO requests -* Dequeueing IO completions - -`struct IORing.RegisteredResource` represents, via its two typealiases `IORing.RegisteredFile` and `IORing.RegisteredBuffer`, registered file descriptors and buffers. - -`struct IORing.Request: ~Copyable` represents an IO operation that can be enqueued for the kernel to execute. It supports a wide variety of operations matching traditional unix file and socket operations. - -Request operations are expressed as overloaded static methods on `Request`, e.g. `openat` is spelled - -```swift - public static func open( - _ path: FilePath, - in directory: FileDescriptor, - into slot: IORing.RegisteredFile, - mode: FileDescriptor.AccessMode, - options: FileDescriptor.OpenOptions = FileDescriptor.OpenOptions(), - permissions: FilePermissions? = nil, - context: UInt64 = 0 - ) -> Request - - public static func open( - _ path: FilePath, - in directory: FileDescriptor, - mode: FileDescriptor.AccessMode, - options: FileDescriptor.OpenOptions = FileDescriptor.OpenOptions(), - permissions: FilePermissions? = nil, - context: UInt64 = 0 - ) -> Request -``` - -which allows clients to decide whether they want to open the file into a slot on the ring, or have it return a file descriptor via a completion. Similarly, read operations have overloads for "use a buffer from the ring" or "read into this `UnsafeMutableBufferPointer`" - -Multiple `Requests` can be enqueued on a single `IORing` using the `prepare(…)` family of methods, and then submitted together using `submitPreparedRequests`, allowing for things like "open this file, read its contents, and then close it" to be a single syscall. Conveniences are provided for preparing and submitting requests in one call. - -Since IO operations can execute in parallel or out of order by default, linked chains of operations can be established with `prepare(linkedRequests:…)` and related methods. Separate chains can still execute in parallel, and if an operation early in the chain fails, all subsequent operations will deliver cancellation errors as their completion. - -Already-completed results can be retrieved from the ring using `tryConsumeCompletion`, which never waits but may return nil, or `blockingConsumeCompletion(timeout:)`, which synchronously waits (up to an optional timeout) until an operation completes. There's also a bulk version of `blockingConsumeCompletion`, which may reduce the number of syscalls issued. It takes a closure which will be called repeatedly as completions are available (see Future Directions for potential improvements to this API). - -Since neither polling nor synchronously waiting is optimal in many cases, `IORing` also exposes the ability to register an eventfd (see `man eventfd(2)`), which will become readable when completions are available on the ring. This can then be monitored asynchronously with `epoll`, `kqueue`, or for clients who are linking libdispatch, `DispatchSource`. - -`struct IORing.Completion: ~Copyable` represents the result of an IO operation and provides - -* Flags indicating various operation-specific metadata about the now-completed syscall -* The context associated with the operation when it was enqueued, as an `UnsafeRawPointer` or a `UInt64` -* The result of the operation, as an `Int32` with operation-specific meaning -* The error, if one occurred - -Unfortunately the underlying kernel API makes it relatively difficult to determine which `Request` led to a given `Completion`, so it's expected that users will need to create this association themselves via the context parameter. - -`IORing.Features` describes the supported features of the underlying kernel `IORing` implementation, which can be used to provide graceful reduction in functionality when running on older systems. - -## Detailed design - -```swift -// IORing is intentionally not Sendable, to avoid internal locking overhead -public struct IORing: ~Copyable { - - public init(queueDepth: UInt32, flags: IORing.SetupFlags = []) throws(Errno) - - public struct SetupFlags: OptionSet, RawRepresentable, Hashable { - public var rawValue: UInt32 - public init(rawValue: UInt32) - public static var pollCompletions: SetupFlags //IORING_SETUP_IOPOLL - public static var pollSubmissions: SetupFlags //IORING_SETUP_SQPOLL - public static var clampMaxEntries: SetupFlags //IORING_SETUP_CLAMP - public static var startDisabled: SetupFlags //IORING_SETUP_R_DISABLED - public static var continueSubmittingOnError: SetupFlags //IORING_SETUP_SUBMIT_ALL - public static var singleSubmissionThread: SetupFlags //IORING_SETUP_SINGLE_ISSUER - public static var deferRunningTasks: SetupFlags //IORING_SETUP_DEFER_TASKRUN - } - - public mutating func registerEventFD(_ descriptor: FileDescriptor) throws(Errno) - public mutating func unregisterEventFD() throws(Errno) - - public struct RegisteredResource { } - public typealias RegisteredFile = RegisteredResource - public typealias RegisteredBuffer = RegisteredResource - - // A `RegisteredResources` is a view into the buffers or files registered with the ring, if any - public struct RegisteredResources: RandomAccessCollection { - public subscript(position: Int) -> RegisteredResource - public subscript(position: UInt16) -> RegisteredResource // This is useful because io_uring likes to use UInt16s as indexes - } - - public mutating func registerFileSlots(count: Int) throws(Errno) -> RegisteredResources - public func unregisterFiles() - public var registeredFileSlots: RegisteredResources - - public mutating func registerBuffers( - _ buffers: some Collection - ) throws(Errno) -> RegisteredResources - - public mutating func registerBuffers( - _ buffers: UnsafeMutableRawBufferPointer... - ) throws(Errno) -> RegisteredResources - - public func unregisterBuffers() - - public var registeredBuffers: RegisteredResources - - public func prepare(requests: Request...) - public func prepare(linkedRequests: Request...) - - public func submitPreparedRequests(timeout: Duration? = nil) throws(Errno) - public func submit(requests: Request..., timeout: Duration? = nil) throws(Errno) - public func submit(linkedRequests: Request..., timeout: Duration? = nil) throws(Errno) - - public func submitPreparedRequests() throws(Errno) - public func submitPreparedRequestsAndWait(timeout: Duration? = nil) throws(Errno) - - public func submitPreparedRequestsAndConsumeCompletions( - minimumCount: UInt32 = 1, - timeout: Duration? = nil, - consumer: (consuming Completion?, Errno?, Bool) throws(E) -> Void - ) throws(E) - - public func blockingConsumeCompletion( - timeout: Duration? = nil - ) throws(Errno) -> Completion - - public func blockingConsumeCompletions( - minimumCount: UInt32 = 1, - timeout: Duration? = nil, - consumer: (consuming Completion?, Errno?, Bool) throws(E) -> Void - ) throws(E) - - public func tryConsumeCompletion() -> Completion? - - public struct Features: OptionSet, RawRepresentable, Hashable { - let rawValue: UInt32 - - public init(rawValue: UInt32) - - //IORING_FEAT_SINGLE_MMAP is handled internally - public static let nonDroppingCompletions: Features //IORING_FEAT_NODROP - public static let stableSubmissions: Features //IORING_FEAT_SUBMIT_STABLE - public static let currentFilePosition: Features //IORING_FEAT_RW_CUR_POS - public static let assumingTaskCredentials: Features //IORING_FEAT_CUR_PERSONALITY - public static let fastPolling: Features //IORING_FEAT_FAST_POLL - public static let epoll32BitFlags: Features //IORING_FEAT_POLL_32BITS - public static let pollNonFixedFiles: Features //IORING_FEAT_SQPOLL_NONFIXED - public static let extendedArguments: Features //IORING_FEAT_EXT_ARG - public static let nativeWorkers: Features //IORING_FEAT_NATIVE_WORKERS - public static let resourceTags: Features //IORING_FEAT_RSRC_TAGS - public static let allowsSkippingSuccessfulCompletions: Features //IORING_FEAT_CQE_SKIP - public static let improvedLinkedFiles: Features //IORING_FEAT_LINKED_FILE - public static let registerRegisteredRings: Features //IORING_FEAT_REG_REG_RING - public static let minimumTimeout: Features //IORING_FEAT_MIN_TIMEOUT - public static let bundledSendReceive: Features //IORING_FEAT_RECVSEND_BUNDLE - } - public var supportedFeatures: Features -} - -public extension IORing.RegisteredBuffer { - var unsafeBuffer: UnsafeMutableRawBufferPointer -} - -public extension IORing { - struct Request: ~Copyable { - public static func nop(context: UInt64 = 0) -> Request - - // overloads for each combination of registered vs unregistered buffer/descriptor - // Read - public static func read( - _ file: IORing.RegisteredFile, - into buffer: IORing.RegisteredBuffer, - at offset: UInt64 = 0, - context: UInt64 = 0 - ) -> Request - - public static func read( - _ file: FileDescriptor, - into buffer: IORing.RegisteredBuffer, - at offset: UInt64 = 0, - context: UInt64 = 0 - ) -> Request - - public static func read( - _ file: IORing.RegisteredFile, - into buffer: UnsafeMutableRawBufferPointer, - at offset: UInt64 = 0, - context: UInt64 = 0 - ) -> Request - - public static func read( - _ file: FileDescriptor, - into buffer: UnsafeMutableRawBufferPointer, - at offset: UInt64 = 0, - context: UInt64 = 0 - ) -> Request - - // Write - public static func write( - _ buffer: IORing.RegisteredBuffer, - into file: IORing.RegisteredFile, - at offset: UInt64 = 0, - context: UInt64 = 0 - ) -> Request - - public static func write( - _ buffer: IORing.RegisteredBuffer, - into file: FileDescriptor, - at offset: UInt64 = 0, - context: UInt64 = 0 - ) -> Request - - public static func write( - _ buffer: UnsafeMutableRawBufferPointer, - into file: IORing.RegisteredFile, - at offset: UInt64 = 0, - context: UInt64 = 0 - ) -> Request - - public static func write( - _ buffer: UnsafeMutableRawBufferPointer, - into file: FileDescriptor, - at offset: UInt64 = 0, - context: UInt64 = 0 - ) -> Request - - // Close - public static func close( - _ file: FileDescriptor, - context: UInt64 = 0 - ) -> Request - - public static func close( - _ file: IORing.RegisteredFile, - context: UInt64 = 0 - ) -> Request - - // Open At - public static func open( - _ path: FilePath, - in directory: FileDescriptor, - into slot: IORing.RegisteredFile, - mode: FileDescriptor.AccessMode, - options: FileDescriptor.OpenOptions = FileDescriptor.OpenOptions(), - permissions: FilePermissions? = nil, - context: UInt64 = 0 - ) -> Request - - public static func open( - _ path: FilePath, - in directory: FileDescriptor, - mode: FileDescriptor.AccessMode, - options: FileDescriptor.OpenOptions = FileDescriptor.OpenOptions(), - permissions: FilePermissions? = nil, - context: UInt64 = 0 - ) -> Request - - public static func unlink( - _ path: FilePath, - in directory: FileDescriptor, - context: UInt64 = 0 - ) -> Request - - // Cancel - - public enum CancellationMatch { - case all - case first - } - - public static func cancel( - _ matchAll: CancellationMatch, - matchingContext: UInt64, - ) -> Request - - public static func cancel( - _ matchAll: CancellationMatch, - matching: FileDescriptor, - ) -> Request - - public static func cancel( - _ matchAll: CancellationMatch, - matching: IORing.RegisteredFile, - ) -> Request - - // Other operations follow in the same pattern - } - - struct Completion { - public struct Flags: OptionSet, Hashable, Codable { - public let rawValue: UInt32 - - public init(rawValue: UInt32) - - public static let moreCompletions: Flags - public static let socketNotEmpty: Flags - public static let isNotificationEvent: Flags - } - - //These are both the same value, but having both eliminates some ugly casts in client code - public var context: UInt64 - public var contextPointer: UnsafeRawPointer - - public var result: Int32 - - public var error: Errno? // Convenience wrapper over `result` - - public var flags: Flags - } -} - -``` - -## Usage Examples - -### Blocking - -```swift -let ring = try IORing(queueDepth: 2) - -//Make space on the ring for our file (this is optional, but improves performance with repeated use) -let file = ring.registerFiles(count: 1)[0] - -var statInfo = Glibc.stat() // System doesn't have an abstraction for stat() right now -// Build our requests to open the file and find out how big it is -ring.prepare(linkedRequests: - .open(path, - in: parentDirectory, - into: file, - mode: mode, - options: openOptions, - permissions: nil - ), - .stat(file, - into: &statInfo - ) -) -//batch submit 2 syscalls in 1! -try ring.submitPreparedRequestsAndConsumeCompletions(minimumCount: 2) { (completion: consuming Completion?, error, done) in - if let error { - throw error //or other error handling as desired - } -} - -// We could register our buffer with the ring too, but we're only using it once -let buffer = UnsafeMutableRawBufferPointer.allocate(Int(statInfo.st_size)) - -// Build our requests to read the file and close it -ring.prepare(linkedRequests: - .read(file, - into: buffer - ), - .close(file) -) - -//batch submit 2 syscalls in 1! -try ring.submitPreparedRequestsAndConsumeCompletions(minimumCount: 2) { (completion: consuming Completion?, error, done) in - if let error { - throw error //or other error handling as desired - } -} - -processBuffer(buffer) -``` - -### Using libdispatch to wait for the read asynchronously - -```swift -//Initial setup as above up through creating buffer, omitted for brevity - -//Make the read request with a context so we can get the buffer out of it in the completion handler -… -.read(file, into: buffer, context: UInt64(buffer.baseAddress!)) -… - -// Make an eventfd and register it with the ring -let eventfd = eventfd(0, 0) -ring.registerEventFD(eventfd) - -// Make a read source to monitor the eventfd for readability -let readabilityMonitor = DispatchSource.makeReadSource(fileDescriptor: eventfd) -readabilityMonitor.setEventHandler { - let completion = ring.blockingConsumeCompletion() - if let error = completion.error { - //handle failure to read the file - } - processBuffer(completion.contextPointer) -} -readabilityMonitor.activate() - -ring.submitPreparedRequests //note, not "AndConsumeCompletions" this time -``` - -## Source compatibility - -This is an all-new API in Swift System, so has no backwards compatibility implications. Of note, though, this API is only available on Linux. - -## ABI compatibility - -Swift on Linux does not have a stable ABI, and we will likely take advantage of this to evolve IORing as compiler support improves, as described in Future Directions. - -## Implications on adoption - -This feature is intrinsically linked to Linux kernel support, so constrains the deployment target of anything that adopts it to newer kernels. Exactly which features of the evolving io_uring syscall surface area we need is under consideration. - -## Future directions - -* While most Swift users on Darwin are not limited by IO scalability issues, the thread pool considerations still make introducing something similar to this appealing if and when the relevant OS support is available. We should attempt to the best of our ability to not design this in a way that's gratuitously incompatible with non-Linux OSs, although Swift System does not attempt to have an API that's identical on all platforms. -* The set of syscalls covered by `io_uring` has grown significantly and is still growing. We should leave room for supporting additional operations in the future. -* Once same-element requirements and pack counts as integer generic arguments are supported by the compiler, we should consider adding something along the lines of the following to allow preparing, submitting, and waiting for an entire set of operations at once: - -``` -func submitLinkedRequestsAndWait( - _ requests: repeat each Request -) where Request == IORing.Request - -> InlineArray<(repeat each Request).count, IORing.Completion> -``` -* Once mutable borrows are supported, we should consider replacing the closure-taking bulk completion APIs (e.g. `blockingConsumeCompletions(…)`) with ones that return a sequence of completions instead -* We should consider making more types noncopyable as compiler support improves -* liburing has a "peek next completion" operation that doesn't consume it, and then a "mark consumed" operation. We may want to add something similar -* liburing has support for operations allocating their own buffers and returning them via the completion, we may want to support this -* We may want to provide API for asynchronously waiting, rather than just exposing the eventfd to let people roll their own async waits. Doing this really well has *considerable* implications for the concurrency runtime though. -* We should almost certainly expose API for more of the configuration options in `io_uring_setup` -* Stronger safety guarantees around cancellation and resource lifetimes (e.g. as described in https://without.boats/blog/io-uring/) would be very welcome, but require an API that is much more strongly opinionated about how io_uring is used. A future higher level abstraction focused on the goal of being "an async IO API for Swift" rather than "a Swifty interface to io_uring" seems like a good place for that. - -## Alternatives considered - -* We could use a NIO-style separate thread pool, but we believe `io_uring` is likely a better option for scalability. We may still want to provide a thread-pool backed version as an option, because many Linux systems currently disable `io_uring` due to security concerns. -* We could multiplex all IO onto a single actor as `AsyncBytes` currently does, but this has a number of downsides that make it entirely unsuitable to server usage. Most notably, it eliminates IO parallelism entirely. -* Using POSIX AIO instead of or as well as io_uring would greatly increase our ability to support older kernels and other Unix systems, but it has well-documented performance and usability issues that have prevented its adoption elsewhere, and apply just as much to Swift. -* Earlier versions of this proposal had higher level "managed" abstractions over IORing. These have been removed due to lack of interest from clients, but could be added back later if needed. -* I considered having dedicated error types for IORing, but eventually decided throwing Errno was more consistent with other platform APIs -* RegisteredResource was originally a class in an attempt to manage the lifetime of the resource via language features. Changing to the current model of it being a copyable struct didn't make the lifetime management any less safe (the IORing still owns the actual resource), and reduces overhead. In the future it would be neat if we could express RegisteredResources as being borrowed from the IORing so they can't be used after its lifetime. - -## Acknowledgments - -The NIO team, in particular Cory Benfield and Franz Busch, have provided invaluable feedback and direction on this project. From 528e7485ba5b3293114f93a8be33fe2b11fee9d2 Mon Sep 17 00:00:00 2001 From: David Smith Date: Tue, 8 Jul 2025 16:38:21 -0700 Subject: [PATCH 359/427] Document completion flags and disable ones we don't currently support --- Sources/System/IOCompletion.swift | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/Sources/System/IOCompletion.swift b/Sources/System/IOCompletion.swift index 4263bd8d..395e8927 100644 --- a/Sources/System/IOCompletion.swift +++ b/Sources/System/IOCompletion.swift @@ -18,10 +18,15 @@ public extension IORing.Completion { self.rawValue = rawValue } - public static let allocatedBuffer = Flags(rawValue: 1 << 0) - public static let moreCompletions = Flags(rawValue: 1 << 1) - public static let socketNotEmpty = Flags(rawValue: 1 << 2) - public static let isNotificationEvent = Flags(rawValue: 1 << 3) + ///`IORING_CQE_F_BUFFER` Indicates the buffer ID is stored in the upper 16 bits + @inlinable public static var allocatedBuffer: Flags { Flags(rawValue: 1 << 0) } + ///`IORING_CQE_F_MORE` Indicates more completions will be generated from the request that generated this + @inlinable public static var moreCompletions: Flags { Flags(rawValue: 1 << 1) } + //`IORING_CQE_F_SOCK_NONEMPTY`, but currently unused + //@inlinable public static var socketNotEmpty: Flags { Flags(rawValue: 1 << 2) } + //`IORING_CQE_F_NOTIF`, but currently unused + //@inlinable public static var isNotificationEvent: Flags { Flags(rawValue: 1 << 3) } + //IORING_CQE_F_BUF_MORE will eventually be (1U << 4) if we add IOU_PBUF_RING_INC support } } From d6687d2bbb73e8f93980be1f67ec5cafdc185775 Mon Sep 17 00:00:00 2001 From: David Smith Date: Tue, 8 Jul 2025 16:51:24 -0700 Subject: [PATCH 360/427] Add deleted file back --- Sources/CSystem/shims.c | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 Sources/CSystem/shims.c diff --git a/Sources/CSystem/shims.c b/Sources/CSystem/shims.c new file mode 100644 index 00000000..f492a2ae --- /dev/null +++ b/Sources/CSystem/shims.c @@ -0,0 +1,18 @@ +/* + This source file is part of the Swift System open source project + + Copyright (c) 2020 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 +*/ + +#ifdef __linux__ + +#include + +#endif + +#if defined(_WIN32) +#include +#endif From d2b78f801bf5dba3f72167e1f5e05e6a5c5edd10 Mon Sep 17 00:00:00 2001 From: David Smith Date: Tue, 8 Jul 2025 23:53:50 +0000 Subject: [PATCH 361/427] Remove unneeded defines --- Sources/CSystem/include/io_uring.h | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/Sources/CSystem/include/io_uring.h b/Sources/CSystem/include/io_uring.h index df1b36a8..2d081af0 100644 --- a/Sources/CSystem/include/io_uring.h +++ b/Sources/CSystem/include/io_uring.h @@ -8,21 +8,6 @@ #ifndef SWIFT_IORING_C_WRAPPER #define SWIFT_IORING_C_WRAPPER -#ifdef __alpha__ -/* - * alpha is the only exception, all other architectures - * have common numbers for new system calls. - */ -# ifndef __NR_io_uring_setup -# define __NR_io_uring_setup 535 -# endif -# ifndef __NR_io_uring_enter -# define __NR_io_uring_enter 536 -# endif -# ifndef __NR_io_uring_register -# define __NR_io_uring_register 537 -# endif -#else /* !__alpha__ */ # ifndef __NR_io_uring_setup # define __NR_io_uring_setup 425 # endif @@ -32,7 +17,6 @@ # ifndef __NR_io_uring_register # define __NR_io_uring_register 427 # endif -#endif /* struct io_uring_getevents_arg { From 8757fae97c67a4a74c49610d8748e691cdac26f2 Mon Sep 17 00:00:00 2001 From: David Smith Date: Tue, 8 Jul 2025 16:57:30 -0700 Subject: [PATCH 362/427] Remove some stray commas the API breakage test complains about --- Sources/System/IORequest.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Sources/System/IORequest.swift b/Sources/System/IORequest.swift index 1d0638b4..f6c1f749 100644 --- a/Sources/System/IORequest.swift +++ b/Sources/System/IORequest.swift @@ -328,7 +328,7 @@ extension IORing.Request { @inlinable public static func cancel( _ matchAll: CancellationMatch, - matchingContext: UInt64, + matchingContext: UInt64 ) -> IORing.Request { switch matchAll { case .all: @@ -340,7 +340,7 @@ extension IORing.Request { @inlinable public static func cancel( _ matchAll: CancellationMatch, - matching: FileDescriptor, + matching: FileDescriptor ) -> IORing.Request { switch matchAll { case .all: @@ -352,7 +352,7 @@ extension IORing.Request { @inlinable public static func cancel( _ matchAll: CancellationMatch, - matching: IORing.RegisteredFile, + matching: IORing.RegisteredFile ) -> IORing.Request { switch matchAll { case .all: From 4b70ffe375f6a8891e81081b97ff055b754a610b Mon Sep 17 00:00:00 2001 From: David Smith Date: Wed, 9 Jul 2025 20:53:23 +0000 Subject: [PATCH 363/427] Fix short submits and also fix the registration ops enum, which somehow stopped compiling overnight --- Sources/CSystem/include/io_uring.h | 18 ---------------- Sources/System/IORing.swift | 33 +++++++++++++++++++++++------- 2 files changed, 26 insertions(+), 25 deletions(-) diff --git a/Sources/CSystem/include/io_uring.h b/Sources/CSystem/include/io_uring.h index 2d081af0..0068f503 100644 --- a/Sources/CSystem/include/io_uring.h +++ b/Sources/CSystem/include/io_uring.h @@ -33,24 +33,6 @@ struct swift_io_uring_getevents_arg { __u64 ts; }; -//This was #defines in older headers, so we redeclare it to get a consistent import -typedef enum : __u32 { - SWIFT_IORING_REGISTER_BUFFERS = 0, - SWIFT_IORING_UNREGISTER_BUFFERS = 1, - SWIFT_IORING_REGISTER_FILES = 2, - SWIFT_IORING_UNREGISTER_FILES = 3, - SWIFT_IORING_REGISTER_EVENTFD = 4, - SWIFT_IORING_UNREGISTER_EVENTFD = 5, - SWIFT_IORING_REGISTER_FILES_UPDATE = 6, - SWIFT_IORING_REGISTER_EVENTFD_ASYNC = 7, - SWIFT_IORING_REGISTER_PROBE = 8, - SWIFT_IORING_REGISTER_PERSONALITY = 9, - SWIFT_IORING_UNREGISTER_PERSONALITY = 10, - - /* this goes last */ - SWIFT_IORING_REGISTER_LAST -} SWIFT_IORING_REGISTER_OPS; - static inline int io_uring_register(int fd, unsigned int opcode, void *arg, unsigned int nr_args) { diff --git a/Sources/System/IORing.swift b/Sources/System/IORing.swift index 4a28f7c8..5546540f 100644 --- a/Sources/System/IORing.swift +++ b/Sources/System/IORing.swift @@ -8,6 +8,21 @@ import Musl #endif import Synchronization +//This was #defines in older headers, so we redeclare it to get a consistent import +internal enum RegistrationOps: UInt32 { + case registerBuffers = 0 + case unregisterBuffers = 1 + case registerFiles = 2 + case unregisterFiles = 3 + case registerEventFD = 4 + case unregisterEventFD = 5 + case registerFilesUpdate = 6 + case registerEventFDAsync = 7 + case registerProbe = 8 + case registerPersonality = 9 + case unregisterPersonality = 10 +} + extension UnsafeMutableRawPointer { func advanced(by offset: UInt32) -> UnsafeMutableRawPointer { return advanced(by: Int(offset)) @@ -71,6 +86,7 @@ internal func _tryWriteRequest( //Tell the kernel that we've submitted requests and/or are waiting for completions @inlinable internal func _enter( + ring: borrowing SQRing, ringDescriptor: Int32, numEvents: UInt32, minCompletions: UInt32, @@ -91,6 +107,9 @@ internal func _enter( continue } else if ret < 0 { throw(Errno(rawValue: -ret)) + } else if _getSubmissionQueueCount(ring: ring) > 0 { + // See https://github.com/axboe/liburing/issues/309, in some cases not all pending requests are submitted + continue } else { return ret } @@ -101,7 +120,7 @@ internal func _enter( internal func _submitRequests(ring: borrowing SQRing, ringDescriptor: Int32) throws(Errno) { let flushedEvents = _flushQueue(ring: ring) _ = try _enter( - ringDescriptor: ringDescriptor, numEvents: flushedEvents, minCompletions: 0, flags: 0) + ring: ring, ringDescriptor: ringDescriptor, numEvents: flushedEvents, minCompletions: 0, flags: 0) } @inlinable @@ -536,7 +555,7 @@ public struct IORing: ~Copyable { let result = withUnsafePointer(to: &rawfd) { fdptr in let result = io_uring_register( ringDescriptor, - SWIFT_IORING_REGISTER_EVENTFD.rawValue, + RegistrationOps.registerEventFD.rawValue, UnsafeMutableRawPointer(mutating: fdptr), 1 ) @@ -551,7 +570,7 @@ public struct IORing: ~Copyable { public mutating func unregisterEventFD() throws(Errno) { let result = io_uring_register( ringDescriptor, - IORING_UNREGISTER_EVENTFD.rawValue, + RegistrationOps.unregisterEventFD.rawValue, nil, 0 ) @@ -569,7 +588,7 @@ public struct IORing: ~Copyable { let regResult = files.withUnsafeBufferPointer { bPtr in let result = io_uring_register( self.ringDescriptor, - IORING_REGISTER_FILES.rawValue, + RegistrationOps.registerFiles.rawValue, UnsafeMutableRawPointer(mutating: bPtr.baseAddress!), UInt32(truncatingIfNeeded: count) ) @@ -588,7 +607,7 @@ public struct IORing: ~Copyable { public func unregisterFiles() throws { let result = io_uring_register( ringDescriptor, - IORING_UNREGISTER_FILES.rawValue, + RegistrationOps.unregisterFiles.rawValue, nil, 0 ) @@ -613,7 +632,7 @@ public struct IORing: ~Copyable { let regResult = iovecs.withUnsafeBufferPointer { bPtr in let result = io_uring_register( self.ringDescriptor, - IORING_REGISTER_BUFFERS.rawValue, + RegistrationOps.registerBuffers.rawValue, UnsafeMutableRawPointer(mutating: bPtr.baseAddress!), UInt32(truncatingIfNeeded: buffers.count) ) @@ -662,7 +681,7 @@ public struct IORing: ~Copyable { public func unregisterBuffers() throws { let result = io_uring_register( self.ringDescriptor, - IORING_UNREGISTER_BUFFERS.rawValue, + RegistrationOps.unregisterBuffers.rawValue, nil, 0 ) From 329c5f59a25efc5a96468ead91d5a1a073b396e1 Mon Sep 17 00:00:00 2001 From: David Smith Date: Wed, 9 Jul 2025 21:17:42 +0000 Subject: [PATCH 364/427] Conditionalize IORing on compiler >= 6.2 --- Sources/System/IOCompletion.swift | 2 ++ Sources/System/IORequest.swift | 2 ++ Sources/System/IORing.swift | 3 +++ Sources/System/RawIORequest.swift | 3 +++ Tests/SystemTests/IORequestTests.swift | 3 +++ Tests/SystemTests/IORingTests.swift | 3 +++ 6 files changed, 16 insertions(+) diff --git a/Sources/System/IOCompletion.swift b/Sources/System/IOCompletion.swift index 395e8927..65616e27 100644 --- a/Sources/System/IOCompletion.swift +++ b/Sources/System/IOCompletion.swift @@ -1,4 +1,5 @@ #if os(Linux) +#if compiler(>=6.2) import CSystem public extension IORing { @@ -66,3 +67,4 @@ public extension IORing.Completion { } } #endif +#endif diff --git a/Sources/System/IORequest.swift b/Sources/System/IORequest.swift index f6c1f749..5d4143de 100644 --- a/Sources/System/IORequest.swift +++ b/Sources/System/IORequest.swift @@ -1,4 +1,5 @@ #if os(Linux) +#if compiler(>=6.2) import CSystem @usableFromInline @@ -466,3 +467,4 @@ extension IORing.Request { } } #endif +#endif diff --git a/Sources/System/IORing.swift b/Sources/System/IORing.swift index 5546540f..cb47e341 100644 --- a/Sources/System/IORing.swift +++ b/Sources/System/IORing.swift @@ -1,4 +1,6 @@ #if os(Linux) +#if compiler(>=6.2) + import CSystem // needed for mmap #if canImport(Glibc) @@ -825,3 +827,4 @@ extension IORing.RegisteredBuffer { } } #endif +#endif \ No newline at end of file diff --git a/Sources/System/RawIORequest.swift b/Sources/System/RawIORequest.swift index 3734760f..461bae09 100644 --- a/Sources/System/RawIORequest.swift +++ b/Sources/System/RawIORequest.swift @@ -1,4 +1,6 @@ #if os(Linux) +#if compiler(>=6.2) + import CSystem @usableFromInline @@ -198,3 +200,4 @@ extension RawIORequest { } } #endif +#endif \ No newline at end of file diff --git a/Tests/SystemTests/IORequestTests.swift b/Tests/SystemTests/IORequestTests.swift index 4d32196a..53a0a82e 100644 --- a/Tests/SystemTests/IORequestTests.swift +++ b/Tests/SystemTests/IORequestTests.swift @@ -1,4 +1,6 @@ #if os(Linux) +#if compiler(>=6.2) + import XCTest #if SYSTEM_PACKAGE @@ -29,3 +31,4 @@ final class IORequestTests: XCTestCase { } } #endif +#endif \ No newline at end of file diff --git a/Tests/SystemTests/IORingTests.swift b/Tests/SystemTests/IORingTests.swift index 757866ad..7fb899f1 100644 --- a/Tests/SystemTests/IORingTests.swift +++ b/Tests/SystemTests/IORingTests.swift @@ -1,4 +1,6 @@ #if os(Linux) +#if compiler(>=6.2) + import XCTest import CSystem //for eventfd @@ -108,3 +110,4 @@ final class IORingTests: XCTestCase { } } #endif +#endif \ No newline at end of file From a6675d7e5c54d7a9dfdae14451aa48b6d86f819c Mon Sep 17 00:00:00 2001 From: David Smith Date: Thu, 10 Jul 2025 23:22:11 +0000 Subject: [PATCH 365/427] timespec should be compatible here, let's try it to fix the Ubuntu 22 build --- Sources/System/IORing.swift | 12 ++++++------ Sources/System/RawIORequest.swift | 6 +++--- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Sources/System/IORing.swift b/Sources/System/IORing.swift index cb47e341..6a281a0e 100644 --- a/Sources/System/IORing.swift +++ b/Sources/System/IORing.swift @@ -479,9 +479,9 @@ public struct IORing: ~Copyable { timeout: Duration? = nil ) throws(Errno) -> Completion { if let timeout { - var ts = __kernel_timespec( - tv_sec: timeout.components.seconds, - tv_nsec: timeout.components.attoseconds / 1_000_000_000 + var ts = timespec( + tv_sec: Int(timeout.components.seconds), + tv_nsec: Int(timeout.components.attoseconds / 1_000_000_000) ) return try withUnsafePointer(to: &ts) { (tsPtr) throws(Errno) -> Completion in var args = swift_io_uring_getevents_arg( @@ -505,9 +505,9 @@ public struct IORing: ~Copyable { consumer: (consuming Completion?, Errno?, Bool) throws(Err) -> Void ) throws(Err) { if let timeout { - var ts = __kernel_timespec( - tv_sec: timeout.components.seconds, - tv_nsec: timeout.components.attoseconds / 1_000_000_000 + var ts = timespec( + tv_sec: Int(timeout.components.seconds), + tv_nsec: Int(timeout.components.attoseconds / 1_000_000_000) ) try withUnsafePointer(to: &ts) { (tsPtr) throws(Err) in var args = swift_io_uring_getevents_arg( diff --git a/Sources/System/RawIORequest.swift b/Sources/System/RawIORequest.swift index 461bae09..b22c1c73 100644 --- a/Sources/System/RawIORequest.swift +++ b/Sources/System/RawIORequest.swift @@ -184,9 +184,9 @@ extension RawIORequest { opEntry.pointee.flags |= Flags.linkRequest.rawValue opEntry.pointee.off = 1 - var ts = __kernel_timespec( - tv_sec: duration.components.seconds, - tv_nsec: duration.components.attoseconds / 1_000_000_000 + var ts = timespec( + tv_sec: Int(duration.components.seconds), + tv_nsec: Int(duration.components.attoseconds / 1_000_000_000) ) return try withUnsafePointer(to: &ts) { tsPtr in var req: RawIORequest = RawIORequest() From d121081c01de1970730f3623e4d42b648e7bf013 Mon Sep 17 00:00:00 2001 From: David Smith Date: Thu, 10 Jul 2025 23:30:42 +0000 Subject: [PATCH 366/427] More Ubuntu 22 workarounds. This should error at runtime rather than not compiling, since we don't have Linux version checks at compile time --- Sources/System/IORequest.swift | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/Sources/System/IORequest.swift b/Sources/System/IORequest.swift index 5d4143de..b7a0e1a4 100644 --- a/Sources/System/IORequest.swift +++ b/Sources/System/IORequest.swift @@ -321,7 +321,15 @@ extension IORing.Request { * IORING_ASYNC_CANCEL_FD_FIXED 'fd' passed in is a fixed descriptor * IORING_ASYNC_CANCEL_USERDATA Match on user_data, default for no other key * IORING_ASYNC_CANCEL_OP Match request based on opcode - */ + */ + +@inlinable internal static var SWIFT_IORING_ASYNC_CANCEL_ALL: UInt32 { 1 << 0 } +@inlinable internal static var SWIFT_IORING_ASYNC_CANCEL_FD: UInt32 { 1 << 1 } +@inlinable internal static var SWIFT_IORING_ASYNC_CANCEL_ANY: UInt32 { 1 << 2 } +@inlinable internal static var SWIFT_IORING_ASYNC_CANCEL_FD_FIXED: UInt32 { 1 << 3 } +@inlinable internal static var SWIFT_IORING_ASYNC_CANCEL_USERDATA: UInt32 { 1 << 4 } +@inlinable internal static var SWIFT_IORING_ASYNC_CANCEL_OP: UInt32 { 1 << 5 } + public enum CancellationMatch { case all case first @@ -333,9 +341,9 @@ extension IORing.Request { ) -> IORing.Request { switch matchAll { case .all: - .init(core: .cancel(flags: IORING_ASYNC_CANCEL_ALL | IORING_ASYNC_CANCEL_USERDATA, targetContext: matchingContext)) + .init(core: .cancel(flags: SWIFT_IORING_ASYNC_CANCEL_ALL | SWIFT_IORING_ASYNC_CANCEL_USERDATA, targetContext: matchingContext)) case .first: - .init(core: .cancel(flags: IORING_ASYNC_CANCEL_ANY | IORING_ASYNC_CANCEL_USERDATA, targetContext: matchingContext)) + .init(core: .cancel(flags: SWIFT_IORING_ASYNC_CANCEL_ANY | SWIFT_IORING_ASYNC_CANCEL_USERDATA, targetContext: matchingContext)) } } @@ -345,9 +353,9 @@ extension IORing.Request { ) -> IORing.Request { switch matchAll { case .all: - .init(core: .cancelFD(flags: IORING_ASYNC_CANCEL_ALL | IORING_ASYNC_CANCEL_FD, targetFD: matching)) + .init(core: .cancelFD(flags: SWIFT_IORING_ASYNC_CANCEL_ALL | SWIFT_IORING_ASYNC_CANCEL_FD, targetFD: matching)) case .first: - .init(core: .cancelFD(flags: IORING_ASYNC_CANCEL_ANY | IORING_ASYNC_CANCEL_FD, targetFD: matching)) + .init(core: .cancelFD(flags: SWIFT_IORING_ASYNC_CANCEL_ANY | SWIFT_IORING_ASYNC_CANCEL_FD, targetFD: matching)) } } @@ -357,9 +365,9 @@ extension IORing.Request { ) -> IORing.Request { switch matchAll { case .all: - .init(core: .cancelFDSlot(flags: IORING_ASYNC_CANCEL_ALL | IORING_ASYNC_CANCEL_FD_FIXED, target: matching)) + .init(core: .cancelFDSlot(flags: SWIFT_IORING_ASYNC_CANCEL_ALL | SWIFT_IORING_ASYNC_CANCEL_FD_FIXED, target: matching)) case .first: - .init(core: .cancelFDSlot(flags: IORING_ASYNC_CANCEL_ANY | IORING_ASYNC_CANCEL_FD_FIXED, target: matching)) + .init(core: .cancelFDSlot(flags: SWIFT_IORING_ASYNC_CANCEL_ANY | SWIFT_IORING_ASYNC_CANCEL_FD_FIXED, target: matching)) } } From 2f6c4e0f03638880fe92081a52b0bca07f295bf4 Mon Sep 17 00:00:00 2001 From: David Smith Date: Fri, 11 Jul 2025 16:14:36 +0000 Subject: [PATCH 367/427] Add support for non-IORING_FEAT_SINGLE_MMAP kernels --- Sources/System/IORing.swift | 123 ++++++++++++++++++++++++++---------- 1 file changed, 89 insertions(+), 34 deletions(-) diff --git a/Sources/System/IORing.swift b/Sources/System/IORing.swift index 6a281a0e..ed451c39 100644 --- a/Sources/System/IORing.swift +++ b/Sources/System/IORing.swift @@ -179,7 +179,7 @@ internal func _getSubmissionEntry( private func setUpRing( queueDepth: UInt32, flags: IORing.SetupFlags ) throws(Errno) -> - (params: io_uring_params, ringDescriptor: Int32, ringPtr: UnsafeMutableRawPointer, ringSize: Int, sqes: UnsafeMutableRawPointer) { + (params: io_uring_params, ringDescriptor: Int32, ringPtr: UnsafeMutableRawPointer?, ringSize: Int, submissionRingPtr: UnsafeMutableRawPointer?, submissionRingSize: Int, completionRingPtr: UnsafeMutableRawPointer?, completionRingSize: Int, sqes: UnsafeMutableRawPointer) { var params = io_uring_params() params.flags = flags.rawValue @@ -196,8 +196,7 @@ private func setUpRing( throw err } - if params.features & IORING_FEAT_SINGLE_MMAP == 0 - || params.features & IORING_FEAT_NODROP == 0 + if params.features & IORING_FEAT_NODROP == 0 { close(ringDescriptor) throw Errno.invalidArgument @@ -213,20 +212,55 @@ private func setUpRing( let ringSize = Int(max(submitRingSize, completionRingSize)) - let ringPtr: UnsafeMutableRawPointer! = mmap( - /* addr: */ nil, - /* len: */ ringSize, - /* prot: */ PROT_READ | PROT_WRITE, - /* flags: */ MAP_SHARED | MAP_POPULATE, - /* fd: */ ringDescriptor, - /* offset: */ __off_t(IORING_OFF_SQ_RING) - ) + var ringPtr: UnsafeMutableRawPointer! + var sqPtr: UnsafeMutableRawPointer! + var cqPtr: UnsafeMutableRawPointer! + + if params.features & IORING_FEAT_SINGLE_MMAP != 0{ + ringPtr = mmap( + /* addr: */ nil, + /* len: */ ringSize, + /* prot: */ PROT_READ | PROT_WRITE, + /* flags: */ MAP_SHARED | MAP_POPULATE, + /* fd: */ ringDescriptor, + /* offset: */ __off_t(IORING_OFF_SQ_RING) + ) - if ringPtr == MAP_FAILED { - let errno = Errno.current - perror("mmap") - close(ringDescriptor) - throw errno + if ringPtr == MAP_FAILED { + let errno = Errno.current + close(ringDescriptor) + throw errno + } + } else { + sqPtr = mmap( + /* addr: */ nil, + /* len: */ Int(submitRingSize), + /* prot: */ PROT_READ | PROT_WRITE, + /* flags: */ MAP_SHARED | MAP_POPULATE, + /* fd: */ ringDescriptor, + /* offset: */ __off_t(IORING_OFF_SQ_RING) + ) + + if sqPtr == MAP_FAILED { + let errno = Errno.current + close(ringDescriptor) + throw errno + } + + cqPtr = mmap( + /* addr: */ nil, + /* len: */ Int(completionRingSize), + /* prot: */ PROT_READ | PROT_WRITE, + /* flags: */ MAP_SHARED | MAP_POPULATE, + /* fd: */ ringDescriptor, + /* offset: */ __off_t(IORING_OFF_CQ_RING) + ) + + if cqPtr == MAP_FAILED { + let errno: Errno = Errno.current + close(ringDescriptor) + throw errno + } } // map the submission queue @@ -241,13 +275,21 @@ private func setUpRing( if sqes == MAP_FAILED { let errno = Errno.current - perror("mmap") - munmap(ringPtr, ringSize) + if ringPtr != nil { + munmap(ringPtr, ringSize) + } else { + if sqPtr != nil { + munmap(sqPtr, Int(submitRingSize)) + } + if cqPtr != nil { + munmap(cqPtr, Int(completionRingSize)) + } + } close(ringDescriptor) throw errno } - return (params: params, ringDescriptor: ringDescriptor, ringPtr: ringPtr!, ringSize: ringSize, sqes: sqes!) + return (params: params, ringDescriptor: ringDescriptor, ringPtr: ringPtr, ringSize: ringSize, submissionRingPtr: sqPtr, submissionRingSize: Int(submitRingSize), completionRingPtr: cqPtr, completionRingSize: Int(completionRingSize), sqes: sqes!) } ///IORing provides facilities for @@ -267,9 +309,13 @@ public struct IORing: ~Copyable { @usableFromInline let submissionQueueEntries: UnsafeMutableBufferPointer - // kept around for unmap / cleanup + // kept around for unmap / cleanup. TODO: we can save a few words of memory by figuring out how to handle cleanup for non-IORING_FEAT_SINGLE_MMAP better let ringSize: Int - let ringPtr: UnsafeMutableRawPointer + let ringPtr: UnsafeMutableRawPointer? + let submissionRingSize: Int + let submissionRingPtr: UnsafeMutableRawPointer? + let completionRingSize: Int + let completionRingPtr: UnsafeMutableRawPointer? @usableFromInline var _registeredFiles: [UInt32] @usableFromInline var _registeredBuffers: [iovec] @@ -320,7 +366,7 @@ public struct IORing: ~Copyable { /// Initializes an IORing with enough space for `queueDepth` prepared requests and completed operations public init(queueDepth: UInt32, flags: SetupFlags = []) throws(Errno) { - let (params, tmpRingDescriptor, tmpRingPtr, tmpRingSize, sqes) = try setUpRing(queueDepth: queueDepth, flags: flags) + let (params, tmpRingDescriptor, tmpRingPtr, tmpRingSize, tmpSQPtr, tmpSQSize, tmpCQPtr, tmpCQSize, sqes) = try setUpRing(queueDepth: queueDepth, flags: flags) // All throws need to be before initializing ivars here to avoid // "error: conditional initialization or destruction of noncopyable types is not supported; // this variable must be consistently in an initialized or uninitialized state through every code path" @@ -328,29 +374,33 @@ public struct IORing: ~Copyable { ringDescriptor = tmpRingDescriptor ringPtr = tmpRingPtr ringSize = tmpRingSize + submissionRingPtr = tmpSQPtr + submissionRingSize = tmpSQSize + completionRingPtr = tmpCQPtr + completionRingSize = tmpCQSize _registeredFiles = [] _registeredBuffers = [] submissionRing = SQRing( kernelHead: UnsafePointer>( - ringPtr.advanced(by: params.sq_off.head) + (ringPtr ?? submissionRingPtr!).advanced(by: params.sq_off.head) .assumingMemoryBound(to: Atomic.self) ), kernelTail: UnsafePointer>( - ringPtr.advanced(by: params.sq_off.tail) + (ringPtr ?? submissionRingPtr!).advanced(by: params.sq_off.tail) .assumingMemoryBound(to: Atomic.self) ), userTail: 0, // no requests yet - ringMask: ringPtr.advanced(by: params.sq_off.ring_mask) + ringMask: (ringPtr ?? submissionRingPtr!).advanced(by: params.sq_off.ring_mask) .assumingMemoryBound(to: UInt32.self).pointee, flags: UnsafePointer>( - ringPtr.advanced(by: params.sq_off.flags) + (ringPtr ?? submissionRingPtr!).advanced(by: params.sq_off.flags) .assumingMemoryBound(to: Atomic.self) ), array: UnsafeMutableBufferPointer( - start: ringPtr.advanced(by: params.sq_off.array) + start: (ringPtr ?? submissionRingPtr!).advanced(by: params.sq_off.array) .assumingMemoryBound(to: UInt32.self), count: Int( - ringPtr.advanced(by: params.sq_off.ring_entries) + (ringPtr ?? submissionRingPtr!).advanced(by: params.sq_off.ring_entries) .assumingMemoryBound(to: UInt32.self).pointee) ) ) @@ -367,20 +417,20 @@ public struct IORing: ~Copyable { completionRing = CQRing( kernelHead: UnsafePointer>( - ringPtr.advanced(by: params.cq_off.head) + (ringPtr ?? completionRingPtr!).advanced(by: params.cq_off.head) .assumingMemoryBound(to: Atomic.self) ), kernelTail: UnsafePointer>( - ringPtr.advanced(by: params.cq_off.tail) + (ringPtr ?? completionRingPtr!).advanced(by: params.cq_off.tail) .assumingMemoryBound(to: Atomic.self) ), - ringMask: ringPtr.advanced(by: params.cq_off.ring_mask) + ringMask: (ringPtr ?? completionRingPtr!).advanced(by: params.cq_off.ring_mask) .assumingMemoryBound(to: UInt32.self).pointee, cqes: UnsafeBufferPointer( - start: ringPtr.advanced(by: params.cq_off.cqes) + start: (ringPtr ?? completionRingPtr!).advanced(by: params.cq_off.cqes) .assumingMemoryBound(to: io_uring_cqe.self), count: Int( - ringPtr.advanced(by: params.cq_off.ring_entries) + (ringPtr ?? completionRingPtr!).advanced(by: params.cq_off.ring_entries) .assumingMemoryBound(to: UInt32.self).pointee) ) ) @@ -799,7 +849,12 @@ public struct IORing: ~Copyable { } deinit { - munmap(ringPtr, ringSize) + if let ringPtr { + munmap(ringPtr, ringSize) + } else if let submissionRingPtr, let completionRingPtr { + munmap(submissionRingPtr, submissionRingSize) + munmap(completionRingPtr, completionRingSize) + } munmap( UnsafeMutableRawPointer(submissionQueueEntries.baseAddress!), submissionQueueEntries.count * MemoryLayout.size From 779c9ac6285b8d8fd389e6496decf5ee3a480733 Mon Sep 17 00:00:00 2001 From: David Smith Date: Fri, 11 Jul 2025 16:50:58 +0000 Subject: [PATCH 368/427] Fix misuse of _CANCEL_ANY, and add support for non-redicated cancellation --- Sources/System/IORequest.swift | 28 +++++++++++++++++++++++----- Sources/System/IORing.swift | 1 + 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/Sources/System/IORequest.swift b/Sources/System/IORequest.swift index b7a0e1a4..275d27bb 100644 --- a/Sources/System/IORequest.swift +++ b/Sources/System/IORequest.swift @@ -84,6 +84,9 @@ internal enum IORequestCore { context: UInt64 = 0 ) case cancel( + flags:UInt32 + ) + case cancelContext( flags: UInt32, targetContext: UInt64 ) @@ -95,6 +98,7 @@ internal enum IORequestCore { flags: UInt32, target: IORing.RegisteredFile ) + } @inline(__always) @inlinable @@ -341,9 +345,9 @@ extension IORing.Request { ) -> IORing.Request { switch matchAll { case .all: - .init(core: .cancel(flags: SWIFT_IORING_ASYNC_CANCEL_ALL | SWIFT_IORING_ASYNC_CANCEL_USERDATA, targetContext: matchingContext)) + .init(core: .cancelContext(flags: SWIFT_IORING_ASYNC_CANCEL_ALL | SWIFT_IORING_ASYNC_CANCEL_USERDATA, targetContext: matchingContext)) case .first: - .init(core: .cancel(flags: SWIFT_IORING_ASYNC_CANCEL_ANY | SWIFT_IORING_ASYNC_CANCEL_USERDATA, targetContext: matchingContext)) + .init(core: .cancelContext(flags: SWIFT_IORING_ASYNC_CANCEL_USERDATA, targetContext: matchingContext)) } } @@ -355,7 +359,7 @@ extension IORing.Request { case .all: .init(core: .cancelFD(flags: SWIFT_IORING_ASYNC_CANCEL_ALL | SWIFT_IORING_ASYNC_CANCEL_FD, targetFD: matching)) case .first: - .init(core: .cancelFD(flags: SWIFT_IORING_ASYNC_CANCEL_ANY | SWIFT_IORING_ASYNC_CANCEL_FD, targetFD: matching)) + .init(core: .cancelFD(flags: SWIFT_IORING_ASYNC_CANCEL_FD, targetFD: matching)) } } @@ -367,7 +371,18 @@ extension IORing.Request { case .all: .init(core: .cancelFDSlot(flags: SWIFT_IORING_ASYNC_CANCEL_ALL | SWIFT_IORING_ASYNC_CANCEL_FD_FIXED, target: matching)) case .first: - .init(core: .cancelFDSlot(flags: SWIFT_IORING_ASYNC_CANCEL_ANY | SWIFT_IORING_ASYNC_CANCEL_FD_FIXED, target: matching)) + .init(core: .cancelFDSlot(flags: SWIFT_IORING_ASYNC_CANCEL_FD_FIXED, target: matching)) + } + } + + @inlinable public static func cancel( + _ matchAll: CancellationMatch, + ) -> IORing.Request { + switch matchAll { + case .all: + .init(core: .cancel(flags: SWIFT_IORING_ASYNC_CANCEL_ALL)) + case .first: + .init(core: .cancel(flags: SWIFT_IORING_ASYNC_CANCEL_ANY)) } } @@ -457,7 +472,7 @@ extension IORing.Request { ) request.path = path request.rawValue.user_data = context - case .cancel(let flags, let targetContext): + case .cancelContext(let flags, let targetContext): request.operation = .asyncCancel request.cancel_flags = flags request.addr = targetContext @@ -469,6 +484,9 @@ extension IORing.Request { request.operation = .asyncCancel request.cancel_flags = flags request.rawValue.fd = Int32(target.index) + case .cancel(let flags): + request.operation = .asyncCancel + request.cancel_flags = flags } return request diff --git a/Sources/System/IORing.swift b/Sources/System/IORing.swift index ed451c39..604f3fde 100644 --- a/Sources/System/IORing.swift +++ b/Sources/System/IORing.swift @@ -378,6 +378,7 @@ public struct IORing: ~Copyable { submissionRingSize = tmpSQSize completionRingPtr = tmpCQPtr completionRingSize = tmpCQSize + _registeredFiles = [] _registeredBuffers = [] submissionRing = SQRing( From 7fc787e6ba4ba3a68b43562dcde165560e7b9855 Mon Sep 17 00:00:00 2001 From: David Smith Date: Fri, 11 Jul 2025 17:00:32 +0000 Subject: [PATCH 369/427] Move IORing files to a subdirectory --- Sources/System/{ => IORing}/IOCompletion.swift | 0 Sources/System/{ => IORing}/IORequest.swift | 0 Sources/System/{ => IORing}/IORing.swift | 0 Sources/System/{ => IORing}/RawIORequest.swift | 0 4 files changed, 0 insertions(+), 0 deletions(-) rename Sources/System/{ => IORing}/IOCompletion.swift (100%) rename Sources/System/{ => IORing}/IORequest.swift (100%) rename Sources/System/{ => IORing}/IORing.swift (100%) rename Sources/System/{ => IORing}/RawIORequest.swift (100%) diff --git a/Sources/System/IOCompletion.swift b/Sources/System/IORing/IOCompletion.swift similarity index 100% rename from Sources/System/IOCompletion.swift rename to Sources/System/IORing/IOCompletion.swift diff --git a/Sources/System/IORequest.swift b/Sources/System/IORing/IORequest.swift similarity index 100% rename from Sources/System/IORequest.swift rename to Sources/System/IORing/IORequest.swift diff --git a/Sources/System/IORing.swift b/Sources/System/IORing/IORing.swift similarity index 100% rename from Sources/System/IORing.swift rename to Sources/System/IORing/IORing.swift diff --git a/Sources/System/RawIORequest.swift b/Sources/System/IORing/RawIORequest.swift similarity index 100% rename from Sources/System/RawIORequest.swift rename to Sources/System/IORing/RawIORequest.swift From f7c05e1fb846fb4748f53d015256f85c8819a7ff Mon Sep 17 00:00:00 2001 From: David Smith Date: Fri, 11 Jul 2025 18:53:55 +0000 Subject: [PATCH 370/427] Combine platform conditionals --- Sources/System/IORing/IOCompletion.swift | 4 +--- Sources/System/IORing/IORequest.swift | 4 +--- Sources/System/IORing/IORing.swift | 4 +--- Sources/System/IORing/RawIORequest.swift | 4 +--- 4 files changed, 4 insertions(+), 12 deletions(-) diff --git a/Sources/System/IORing/IOCompletion.swift b/Sources/System/IORing/IOCompletion.swift index 65616e27..375abae8 100644 --- a/Sources/System/IORing/IOCompletion.swift +++ b/Sources/System/IORing/IOCompletion.swift @@ -1,5 +1,4 @@ -#if os(Linux) -#if compiler(>=6.2) +#if os(Linux) && compiler(>=6.2) import CSystem public extension IORing { @@ -67,4 +66,3 @@ public extension IORing.Completion { } } #endif -#endif diff --git a/Sources/System/IORing/IORequest.swift b/Sources/System/IORing/IORequest.swift index 275d27bb..f7130f2f 100644 --- a/Sources/System/IORing/IORequest.swift +++ b/Sources/System/IORing/IORequest.swift @@ -1,5 +1,4 @@ -#if os(Linux) -#if compiler(>=6.2) +#if os(Linux) && compiler(>=6.2) import CSystem @usableFromInline @@ -493,4 +492,3 @@ extension IORing.Request { } } #endif -#endif diff --git a/Sources/System/IORing/IORing.swift b/Sources/System/IORing/IORing.swift index 604f3fde..3b49d5ce 100644 --- a/Sources/System/IORing/IORing.swift +++ b/Sources/System/IORing/IORing.swift @@ -1,5 +1,4 @@ -#if os(Linux) -#if compiler(>=6.2) +#if os(Linux) && compiler(>=6.2) import CSystem // needed for mmap @@ -883,4 +882,3 @@ extension IORing.RegisteredBuffer { } } #endif -#endif \ No newline at end of file diff --git a/Sources/System/IORing/RawIORequest.swift b/Sources/System/IORing/RawIORequest.swift index b22c1c73..2fc8a999 100644 --- a/Sources/System/IORing/RawIORequest.swift +++ b/Sources/System/IORing/RawIORequest.swift @@ -1,5 +1,4 @@ -#if os(Linux) -#if compiler(>=6.2) +#if os(Linux) && compiler(>=6.2) import CSystem @@ -200,4 +199,3 @@ extension RawIORequest { } } #endif -#endif \ No newline at end of file From a56bcda3c1bd85131f1e36d90ff11ffcdae900e8 Mon Sep 17 00:00:00 2001 From: David Smith Date: Fri, 11 Jul 2025 19:07:04 +0000 Subject: [PATCH 371/427] Un-combine conditionals but flip their order. This should fix the Windows build with older compilers --- Sources/System/IORing/IOCompletion.swift | 5 ++++- Sources/System/IORing/IORequest.swift | 5 ++++- Sources/System/IORing/IORing.swift | 4 +++- Sources/System/IORing/RawIORequest.swift | 4 +++- Tests/SystemTests/IORequestTests.swift | 2 +- Tests/SystemTests/IORingTests.swift | 2 +- 6 files changed, 16 insertions(+), 6 deletions(-) diff --git a/Sources/System/IORing/IOCompletion.swift b/Sources/System/IORing/IOCompletion.swift index 375abae8..ce23444d 100644 --- a/Sources/System/IORing/IOCompletion.swift +++ b/Sources/System/IORing/IOCompletion.swift @@ -1,4 +1,6 @@ -#if os(Linux) && compiler(>=6.2) +#if compiler(>=6.2) +#if os(Linux) + import CSystem public extension IORing { @@ -66,3 +68,4 @@ public extension IORing.Completion { } } #endif +#endif \ No newline at end of file diff --git a/Sources/System/IORing/IORequest.swift b/Sources/System/IORing/IORequest.swift index f7130f2f..5ab5ac94 100644 --- a/Sources/System/IORing/IORequest.swift +++ b/Sources/System/IORing/IORequest.swift @@ -1,4 +1,6 @@ -#if os(Linux) && compiler(>=6.2) +#if compiler(>=6.2) +#if os(Linux) + import CSystem @usableFromInline @@ -492,3 +494,4 @@ extension IORing.Request { } } #endif +#endif \ No newline at end of file diff --git a/Sources/System/IORing/IORing.swift b/Sources/System/IORing/IORing.swift index 3b49d5ce..c0fe005a 100644 --- a/Sources/System/IORing/IORing.swift +++ b/Sources/System/IORing/IORing.swift @@ -1,4 +1,5 @@ -#if os(Linux) && compiler(>=6.2) +#if compiler(>=6.2) +#if os(Linux) import CSystem // needed for mmap @@ -882,3 +883,4 @@ extension IORing.RegisteredBuffer { } } #endif +#endif \ No newline at end of file diff --git a/Sources/System/IORing/RawIORequest.swift b/Sources/System/IORing/RawIORequest.swift index 2fc8a999..332cd917 100644 --- a/Sources/System/IORing/RawIORequest.swift +++ b/Sources/System/IORing/RawIORequest.swift @@ -1,4 +1,5 @@ -#if os(Linux) && compiler(>=6.2) +#if compiler(>=6.2) +#if os(Linux) import CSystem @@ -199,3 +200,4 @@ extension RawIORequest { } } #endif +#endif \ No newline at end of file diff --git a/Tests/SystemTests/IORequestTests.swift b/Tests/SystemTests/IORequestTests.swift index 53a0a82e..9a809689 100644 --- a/Tests/SystemTests/IORequestTests.swift +++ b/Tests/SystemTests/IORequestTests.swift @@ -1,5 +1,5 @@ -#if os(Linux) #if compiler(>=6.2) +#if os(Linux) import XCTest diff --git a/Tests/SystemTests/IORingTests.swift b/Tests/SystemTests/IORingTests.swift index 7fb899f1..3a2473c3 100644 --- a/Tests/SystemTests/IORingTests.swift +++ b/Tests/SystemTests/IORingTests.swift @@ -1,5 +1,5 @@ -#if os(Linux) #if compiler(>=6.2) +#if os(Linux) import XCTest import CSystem //for eventfd From 9a046498d07bf9e3c1f087b51373e603a51d2024 Mon Sep 17 00:00:00 2001 From: David Smith Date: Fri, 11 Jul 2025 19:23:24 +0000 Subject: [PATCH 372/427] Try adding newlines to the ends of files to work around a compiler bug --- Sources/System/IORing/IOCompletion.swift | 2 +- Sources/System/IORing/IORequest.swift | 2 +- Sources/System/IORing/IORing.swift | 2 +- Sources/System/IORing/RawIORequest.swift | 2 +- Tests/SystemTests/IORequestTests.swift | 2 +- Tests/SystemTests/IORingTests.swift | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Sources/System/IORing/IOCompletion.swift b/Sources/System/IORing/IOCompletion.swift index ce23444d..ea99cc70 100644 --- a/Sources/System/IORing/IOCompletion.swift +++ b/Sources/System/IORing/IOCompletion.swift @@ -68,4 +68,4 @@ public extension IORing.Completion { } } #endif -#endif \ No newline at end of file +#endif diff --git a/Sources/System/IORing/IORequest.swift b/Sources/System/IORing/IORequest.swift index 5ab5ac94..cc91e476 100644 --- a/Sources/System/IORing/IORequest.swift +++ b/Sources/System/IORing/IORequest.swift @@ -494,4 +494,4 @@ extension IORing.Request { } } #endif -#endif \ No newline at end of file +#endif diff --git a/Sources/System/IORing/IORing.swift b/Sources/System/IORing/IORing.swift index c0fe005a..c676f78e 100644 --- a/Sources/System/IORing/IORing.swift +++ b/Sources/System/IORing/IORing.swift @@ -883,4 +883,4 @@ extension IORing.RegisteredBuffer { } } #endif -#endif \ No newline at end of file +#endif diff --git a/Sources/System/IORing/RawIORequest.swift b/Sources/System/IORing/RawIORequest.swift index 332cd917..dea378fb 100644 --- a/Sources/System/IORing/RawIORequest.swift +++ b/Sources/System/IORing/RawIORequest.swift @@ -200,4 +200,4 @@ extension RawIORequest { } } #endif -#endif \ No newline at end of file +#endif diff --git a/Tests/SystemTests/IORequestTests.swift b/Tests/SystemTests/IORequestTests.swift index 9a809689..50728234 100644 --- a/Tests/SystemTests/IORequestTests.swift +++ b/Tests/SystemTests/IORequestTests.swift @@ -31,4 +31,4 @@ final class IORequestTests: XCTestCase { } } #endif -#endif \ No newline at end of file +#endif diff --git a/Tests/SystemTests/IORingTests.swift b/Tests/SystemTests/IORingTests.swift index 3a2473c3..63e4e78b 100644 --- a/Tests/SystemTests/IORingTests.swift +++ b/Tests/SystemTests/IORingTests.swift @@ -110,4 +110,4 @@ final class IORingTests: XCTestCase { } } #endif -#endif \ No newline at end of file +#endif From b93169b8e28b44664dc7ff93ec79fbca4f7e9e35 Mon Sep 17 00:00:00 2001 From: David Smith Date: Fri, 11 Jul 2025 20:37:23 +0000 Subject: [PATCH 373/427] Work around windows bugs harder --- Package.swift | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Package.swift b/Package.swift index 48b348af..cb19f158 100644 --- a/Package.swift +++ b/Package.swift @@ -25,6 +25,12 @@ let swiftSettings: [SwiftSetting] = [ .enableExperimentalFeature("Lifetimes"), ] +#if os(Linux) +let filesToExclude = ["CMakeLists.txt"] +#else +let filesToExclude = ["CMakeLists.txt", "Sources/IORing/", "Tests/SystemTests/IORequestTests.swift", "Tests/SystemTests/IORingTests.swift"] +#endif + let package = Package( name: "swift-system", products: [ @@ -41,7 +47,7 @@ let package = Package( name: "SystemPackage", dependencies: ["CSystem"], path: "Sources/System", - exclude: ["CMakeLists.txt"], + exclude: filesToExclude, cSettings: cSettings, swiftSettings: swiftSettings), .testTarget( From 52d9c99a9476ad19e57b55b94803a8a3534aa8da Mon Sep 17 00:00:00 2001 From: David Smith Date: Fri, 11 Jul 2025 13:42:49 -0700 Subject: [PATCH 374/427] Fix excludes --- Package.swift | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/Package.swift b/Package.swift index cb19f158..a5013fed 100644 --- a/Package.swift +++ b/Package.swift @@ -28,7 +28,13 @@ let swiftSettings: [SwiftSetting] = [ #if os(Linux) let filesToExclude = ["CMakeLists.txt"] #else -let filesToExclude = ["CMakeLists.txt", "Sources/IORing/", "Tests/SystemTests/IORequestTests.swift", "Tests/SystemTests/IORingTests.swift"] +let filesToExclude = ["CMakeLists.txt", "IORing"] +#endif + +#if os(Linux) +let testsToExclude = [] +#else +let testsToExclude = ["IORequestTests.swift", "IORingTests.swift"] #endif let package = Package( @@ -53,6 +59,7 @@ let package = Package( .testTarget( name: "SystemTests", dependencies: ["SystemPackage"], + exclude: testsToExclude, cSettings: cSettings, swiftSettings: swiftSettings), ]) \ No newline at end of file From 33b58a81106df8b6c9fa61caf2313aec2a936f1a Mon Sep 17 00:00:00 2001 From: David Smith Date: Fri, 11 Jul 2025 13:44:46 -0700 Subject: [PATCH 375/427] Fix excludes harder --- Package.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Package.swift b/Package.swift index a5013fed..9cbfc6e9 100644 --- a/Package.swift +++ b/Package.swift @@ -32,7 +32,7 @@ let filesToExclude = ["CMakeLists.txt", "IORing"] #endif #if os(Linux) -let testsToExclude = [] +let testsToExclude:[String] = [] #else let testsToExclude = ["IORequestTests.swift", "IORingTests.swift"] #endif @@ -62,4 +62,4 @@ let package = Package( exclude: testsToExclude, cSettings: cSettings, swiftSettings: swiftSettings), - ]) \ No newline at end of file + ]) From a66176f51829683d280decf7044683364ce5ceb4 Mon Sep 17 00:00:00 2001 From: David Smith Date: Fri, 11 Jul 2025 22:42:35 +0000 Subject: [PATCH 376/427] Only run IORing tests if io_uring support is enabled --- Tests/SystemTests/IORingTests.swift | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/Tests/SystemTests/IORingTests.swift b/Tests/SystemTests/IORingTests.swift index 63e4e78b..5095f079 100644 --- a/Tests/SystemTests/IORingTests.swift +++ b/Tests/SystemTests/IORingTests.swift @@ -10,12 +10,25 @@ import SystemPackage import System #endif +func uringEnabled() throws -> Bool { + let procPath = FilePath("/proc/sys/kernel/io_uring_disabled") + let fd = try FileDescriptor.open(procPath, .readOnly) + let buffer = UnsafeMutableRawBufferPointer.allocate(byteCount: 1024, alignment: 0) + _ = try fd.read(into: buffer) + if buffer.load(fromByteOffset: 0, as: Int.self) == 0 { + return true + } + return false +} + final class IORingTests: XCTestCase { func testInit() throws { + guard try uringEnabled() else { return } _ = try IORing(queueDepth: 32, flags: []) } func testNop() throws { + guard try uringEnabled() else { return } var ring = try IORing(queueDepth: 32, flags: []) _ = try ring.submit(linkedRequests: .nop()) let completion = try ring.blockingConsumeCompletion() @@ -51,6 +64,7 @@ final class IORingTests: XCTestCase { } func testUndersizedSubmissionQueue() throws { + guard try uringEnabled() else { return } var ring: IORing = try IORing(queueDepth: 1) let enqueued = ring.prepare(linkedRequests: .nop(), .nop()) XCTAssertFalse(enqueued) @@ -58,6 +72,7 @@ final class IORingTests: XCTestCase { // Exercises opening, reading, closing, registered files, registered buffers, and eventfd func testOpenReadAndWriteFixedFile() throws { + guard try uringEnabled() else { return } let (parent, path) = try makeHelloWorldFile() let rawBuffer = UnsafeMutableRawBufferPointer.allocate(byteCount: 13, alignment: 16) var ring = try setupTestRing(depth: 6, fileSlots: 1, buffers: [rawBuffer]) From 08ed962064a04dd002c2175416e0098c180a33e0 Mon Sep 17 00:00:00 2001 From: David Smith Date: Fri, 11 Jul 2025 23:05:26 +0000 Subject: [PATCH 377/427] Don't abort the test if we can't open /proc/sys/kernel/io_uring_disabled --- Tests/SystemTests/IORingTests.swift | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/Tests/SystemTests/IORingTests.swift b/Tests/SystemTests/IORingTests.swift index 5095f079..8e0e8fa6 100644 --- a/Tests/SystemTests/IORingTests.swift +++ b/Tests/SystemTests/IORingTests.swift @@ -11,12 +11,16 @@ import System #endif func uringEnabled() throws -> Bool { - let procPath = FilePath("/proc/sys/kernel/io_uring_disabled") - let fd = try FileDescriptor.open(procPath, .readOnly) - let buffer = UnsafeMutableRawBufferPointer.allocate(byteCount: 1024, alignment: 0) - _ = try fd.read(into: buffer) - if buffer.load(fromByteOffset: 0, as: Int.self) == 0 { - return true + do { + let procPath = FilePath("/proc/sys/kernel/io_uring_disabled") + let fd = try FileDescriptor.open(procPath, .readOnly) + let buffer = UnsafeMutableRawBufferPointer.allocate(byteCount: 1024, alignment: 0) + _ = try fd.read(into: buffer) + if buffer.load(fromByteOffset: 0, as: Int.self) == 0 { + return true + } + } catch (_) { + return false } return false } From 8de80270cb7898dabf7394ef4d6f64de93acc314 Mon Sep 17 00:00:00 2001 From: Jonathan Flat Date: Fri, 25 Jul 2025 13:57:20 -0600 Subject: [PATCH 378/427] Fix WASI build (dirent, umask, errno and open flag tests) --- Sources/CSystem/include/CSystemWASI.h | 37 +++++++++++++++++++++- Sources/System/FileOperations.swift | 4 ++- Sources/System/Internals/Syscalls.swift | 18 +++++++++-- Tests/SystemTests/ErrnoTest.swift | 20 ++++++++---- Tests/SystemTests/FileOperationsTest.swift | 16 ++++++++-- Tests/SystemTests/FileTypesTest.swift | 4 ++- 6 files changed, 83 insertions(+), 16 deletions(-) diff --git a/Sources/CSystem/include/CSystemWASI.h b/Sources/CSystem/include/CSystemWASI.h index 9877853e..1c8cd0f2 100644 --- a/Sources/CSystem/include/CSystemWASI.h +++ b/Sources/CSystem/include/CSystemWASI.h @@ -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 @@ -11,8 +11,10 @@ #if __wasi__ +#include #include #include +#include // 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. @@ -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 diff --git a/Sources/System/FileOperations.swift b/Sources/System/FileOperations.swift index fc9fc932..2a8509bd 100644 --- a/Sources/System/FileOperations.swift +++ b/Sources/System/FileOperations.swift @@ -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 @@ -509,6 +509,7 @@ extension FileDescriptor { } } +#if !os(WASI) // WASI has no umask extension FilePermissions { /// The file creation permission mask (aka "umask"). /// @@ -549,3 +550,4 @@ extension FilePermissions { return system_umask(mode) } } +#endif diff --git a/Sources/System/Internals/Syscalls.swift b/Sources/System/Internals/Syscalls.swift index 1627273c..f6eb5339 100644 --- a/Sources/System/Internals/Syscalls.swift +++ b/Sources/System/Internals/Syscalls.swift @@ -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 @@ -14,6 +14,7 @@ import Glibc #elseif canImport(Musl) import Musl #elseif canImport(WASILibc) +import CSystem import WASILibc #elseif os(Windows) import ucrt @@ -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 @@ -216,8 +222,12 @@ internal func system_fdopendir( internal func system_readdir( _ dir: system_DIRPtr -) -> UnsafeMutablePointer? { +) -> UnsafeMutablePointer? { + #if os(WASI) + return _system_dirent_from_wasi_dirent(readdir(dir)) + #else return readdir(dir) + #endif } internal func system_rewinddir( @@ -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 diff --git a/Tests/SystemTests/ErrnoTest.swift b/Tests/SystemTests/ErrnoTest.swift index da09657e..5f4551ba 100644 --- a/Tests/SystemTests/ErrnoTest.swift +++ b/Tests/SystemTests/ErrnoTest.swift @@ -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 @@ -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) @@ -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) @@ -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) @@ -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) @@ -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 @@ -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) @@ -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 diff --git a/Tests/SystemTests/FileOperationsTest.swift b/Tests/SystemTests/FileOperationsTest.swift index 18adde37..479e503f 100644 --- a/Tests/SystemTests/FileOperationsTest.swift +++ b/Tests/SystemTests/FileOperationsTest.swift @@ -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 @@ -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) @@ -88,6 +91,7 @@ final class FileOperationsTest: XCTestCase { for test in syscallTestCases { test.runAllTests() } } + #endif // !os(WASI) func testWriteFromEmptyBuffer() throws { #if os(Windows) @@ -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`. @@ -171,6 +176,7 @@ final class FileOperationsTest: XCTestCase { } } } + #endif func testAdHocOpen() { // Ad-hoc test touching a file system. @@ -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( @@ -221,7 +232,6 @@ final class FileOperationsTest: XCTestCase { retryOnInterrupt: retryOnInterrupt) } issue26.runAllTests() - } func testResizeFile() throws { diff --git a/Tests/SystemTests/FileTypesTest.swift b/Tests/SystemTests/FileTypesTest.swift index 5258709a..a818dfba 100644 --- a/Tests/SystemTests/FileTypesTest.swift +++ b/Tests/SystemTests/FileTypesTest.swift @@ -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 @@ -32,6 +32,7 @@ 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 @@ -39,6 +40,7 @@ final class FileDescriptorTest: XCTestCase { 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) From 5059730d24a515d9fa7c3892663f4b0f24b9b332 Mon Sep 17 00:00:00 2001 From: Jonathan Flat Date: Mon, 28 Jul 2025 10:52:09 -0600 Subject: [PATCH 379/427] Fix IORing release build --- Sources/System/IORing/IORing.swift | 80 +++++++++++++++++------------- 1 file changed, 45 insertions(+), 35 deletions(-) diff --git a/Sources/System/IORing/IORing.swift b/Sources/System/IORing/IORing.swift index c676f78e..7dcdcde7 100644 --- a/Sources/System/IORing/IORing.swift +++ b/Sources/System/IORing/IORing.swift @@ -370,72 +370,82 @@ public struct IORing: ~Copyable { // All throws need to be before initializing ivars here to avoid // "error: conditional initialization or destruction of noncopyable types is not supported; // this variable must be consistently in an initialized or uninitialized state through every code path" - features = Features(rawValue: params.features) - ringDescriptor = tmpRingDescriptor - ringPtr = tmpRingPtr - ringSize = tmpRingSize - submissionRingPtr = tmpSQPtr - submissionRingSize = tmpSQSize - completionRingPtr = tmpCQPtr - completionRingSize = tmpCQSize - - _registeredFiles = [] - _registeredBuffers = [] - submissionRing = SQRing( + + // Pre-compute values to avoid accessing partially initialized state + let ringBasePtr = tmpRingPtr ?? tmpSQPtr! + let completionBasePtr = tmpRingPtr ?? tmpCQPtr! + + let submissionRing = SQRing( kernelHead: UnsafePointer>( - (ringPtr ?? submissionRingPtr!).advanced(by: params.sq_off.head) + ringBasePtr.advanced(by: params.sq_off.head) .assumingMemoryBound(to: Atomic.self) ), kernelTail: UnsafePointer>( - (ringPtr ?? submissionRingPtr!).advanced(by: params.sq_off.tail) + ringBasePtr.advanced(by: params.sq_off.tail) .assumingMemoryBound(to: Atomic.self) ), userTail: 0, // no requests yet - ringMask: (ringPtr ?? submissionRingPtr!).advanced(by: params.sq_off.ring_mask) + ringMask: ringBasePtr.advanced(by: params.sq_off.ring_mask) .assumingMemoryBound(to: UInt32.self).pointee, flags: UnsafePointer>( - (ringPtr ?? submissionRingPtr!).advanced(by: params.sq_off.flags) + ringBasePtr.advanced(by: params.sq_off.flags) .assumingMemoryBound(to: Atomic.self) ), array: UnsafeMutableBufferPointer( - start: (ringPtr ?? submissionRingPtr!).advanced(by: params.sq_off.array) + start: ringBasePtr.advanced(by: params.sq_off.array) .assumingMemoryBound(to: UInt32.self), count: Int( - (ringPtr ?? submissionRingPtr!).advanced(by: params.sq_off.ring_entries) + ringBasePtr.advanced(by: params.sq_off.ring_entries) .assumingMemoryBound(to: UInt32.self).pointee) ) ) - - // fill submission ring array with 1:1 map to underlying SQEs - for i in 0 ..< submissionRing.array.count { - submissionRing.array[i] = UInt32(i) - } - - submissionQueueEntries = UnsafeMutableBufferPointer( - start: sqes.assumingMemoryBound(to: io_uring_sqe.self), - count: Int(params.sq_entries) - ) - - completionRing = CQRing( + + let completionRing = CQRing( kernelHead: UnsafePointer>( - (ringPtr ?? completionRingPtr!).advanced(by: params.cq_off.head) + completionBasePtr.advanced(by: params.cq_off.head) .assumingMemoryBound(to: Atomic.self) ), kernelTail: UnsafePointer>( - (ringPtr ?? completionRingPtr!).advanced(by: params.cq_off.tail) + completionBasePtr.advanced(by: params.cq_off.tail) .assumingMemoryBound(to: Atomic.self) ), - ringMask: (ringPtr ?? completionRingPtr!).advanced(by: params.cq_off.ring_mask) + ringMask: completionBasePtr.advanced(by: params.cq_off.ring_mask) .assumingMemoryBound(to: UInt32.self).pointee, cqes: UnsafeBufferPointer( - start: (ringPtr ?? completionRingPtr!).advanced(by: params.cq_off.cqes) + start: completionBasePtr.advanced(by: params.cq_off.cqes) .assumingMemoryBound(to: io_uring_cqe.self), count: Int( - (ringPtr ?? completionRingPtr!).advanced(by: params.cq_off.ring_entries) + completionBasePtr.advanced(by: params.cq_off.ring_entries) .assumingMemoryBound(to: UInt32.self).pointee) ) ) + + let submissionQueueEntries = UnsafeMutableBufferPointer( + start: sqes.assumingMemoryBound(to: io_uring_sqe.self), + count: Int(params.sq_entries) + ) + + // Now initialize all stored properties + self.features = Features(rawValue: params.features) + self.ringDescriptor = tmpRingDescriptor + self.ringPtr = tmpRingPtr + self.ringSize = tmpRingSize + self.submissionRingPtr = tmpSQPtr + self.submissionRingSize = tmpSQSize + self.completionRingPtr = tmpCQPtr + self.completionRingSize = tmpCQSize + self._registeredFiles = [] + self._registeredBuffers = [] + self.submissionRing = submissionRing + self.completionRing = completionRing + self.submissionQueueEntries = submissionQueueEntries self.ringFlags = params.flags + + // fill submission ring array with 1:1 map to underlying SQEs + // (happens after all properties are initialized) + for i in 0 ..< self.submissionRing.array.count { + self.submissionRing.array[i] = UInt32(i) + } } @inlinable From 4f5b5fb40d597d3b3db8bf37bef0674b4f195f95 Mon Sep 17 00:00:00 2001 From: Jonathan Flat Date: Mon, 28 Jul 2025 12:07:34 -0600 Subject: [PATCH 380/427] Fix IORing static Linux SDK --- Sources/CSystem/include/io_uring.h | 157 ++++++++++++++++++++++++++++- Sources/System/IORing/IORing.swift | 8 +- 2 files changed, 160 insertions(+), 5 deletions(-) diff --git a/Sources/CSystem/include/io_uring.h b/Sources/CSystem/include/io_uring.h index 0068f503..08bce8bd 100644 --- a/Sources/CSystem/include/io_uring.h +++ b/Sources/CSystem/include/io_uring.h @@ -1,9 +1,164 @@ #include #include #include - #include + +#if __has_include() #include +#else +// Minimal fallback definitions when linux/io_uring.h is not available (e.g. static SDK) +#include + +#define IORING_OFF_SQ_RING 0ULL +#define IORING_OFF_CQ_RING 0x8000000ULL +#define IORING_OFF_SQES 0x10000000ULL + +#define IORING_ENTER_GETEVENTS (1U << 0) + +#define IORING_FEAT_SINGLE_MMAP (1U << 0) +#define IORING_FEAT_NODROP (1U << 1) +#define IORING_FEAT_SUBMIT_STABLE (1U << 2) +#define IORING_FEAT_RW_CUR_POS (1U << 3) +#define IORING_FEAT_CUR_PERSONALITY (1U << 4) +#define IORING_FEAT_FAST_POLL (1U << 5) +#define IORING_FEAT_POLL_32BITS (1U << 6) +#define IORING_FEAT_SQPOLL_NONFIXED (1U << 7) +#define IORING_FEAT_EXT_ARG (1U << 8) +#define IORING_FEAT_NATIVE_WORKERS (1U << 9) +#define IORING_FEAT_RSRC_TAGS (1U << 10) +#define IORING_FEAT_CQE_SKIP (1U << 11) +#define IORING_FEAT_LINKED_FILE (1U << 12) +#define IORING_FEAT_REG_REG_RING (1U << 13) +#define IORING_FEAT_RECVSEND_BUNDLE (1U << 14) +#define IORING_FEAT_MIN_TIMEOUT (1U << 15) +#define IORING_FEAT_RW_ATTR (1U << 16) +#define IORING_FEAT_NO_IOWAIT (1U << 17) + +typedef uint8_t __u8; +typedef uint16_t __u16; +typedef uint32_t __u32; +typedef uint64_t __u64; +typedef int32_t __s32; + +#ifndef __kernel_rwf_t +typedef int __kernel_rwf_t; +#endif + +struct io_uring_sqe { + __u8 opcode; + __u8 flags; + __u16 ioprio; + __s32 fd; + union { + __u64 off; + __u64 addr2; + struct { + __u32 cmd_op; + __u32 __pad1; + }; + }; + union { + __u64 addr; + __u64 splice_off_in; + struct { + __u32 level; + __u32 optname; + }; + }; + __u32 len; + union { + __kernel_rwf_t rw_flags; + __u32 fsync_flags; + __u16 poll_events; + __u32 poll32_events; + __u32 sync_range_flags; + __u32 msg_flags; + __u32 timeout_flags; + __u32 accept_flags; + __u32 cancel_flags; + __u32 open_flags; + __u32 statx_flags; + __u32 fadvise_advice; + __u32 splice_flags; + __u32 rename_flags; + __u32 unlink_flags; + __u32 hardlink_flags; + __u32 xattr_flags; + __u32 msg_ring_flags; + __u32 uring_cmd_flags; + __u32 waitid_flags; + __u32 futex_flags; + __u32 install_fd_flags; + __u32 nop_flags; + }; + __u64 user_data; + union { + __u16 buf_index; + __u16 buf_group; + } __attribute__((packed)); + __u16 personality; + union { + __s32 splice_fd_in; + __u32 file_index; + __u32 optlen; + struct { + __u16 addr_len; + __u16 __pad3[1]; + }; + }; + union { + struct { + __u64 addr3; + __u64 __pad2[1]; + }; + __u64 optval; + __u8 cmd[0]; + }; +}; + +struct io_uring_cqe { + __u64 user_data; + __s32 res; + __u32 flags; +}; + +struct io_sqring_offsets { + __u32 head; + __u32 tail; + __u32 ring_mask; + __u32 ring_entries; + __u32 flags; + __u32 dropped; + __u32 array; + __u32 resv1; + __u64 user_addr; +}; + +struct io_cqring_offsets { + __u32 head; + __u32 tail; + __u32 ring_mask; + __u32 ring_entries; + __u32 overflow; + __u32 cqes; + __u32 flags; + __u32 resv1; + __u64 user_addr; +}; + +struct io_uring_params { + __u32 sq_entries; + __u32 cq_entries; + __u32 flags; + __u32 sq_thread_cpu; + __u32 sq_thread_idle; + __u32 features; + __u32 wq_fd; + __u32 resv[3]; + struct io_sqring_offsets sq_off; + struct io_cqring_offsets cq_off; +}; +#endif // __has_include() #ifndef SWIFT_IORING_C_WRAPPER #define SWIFT_IORING_C_WRAPPER diff --git a/Sources/System/IORing/IORing.swift b/Sources/System/IORing/IORing.swift index c676f78e..66f191de 100644 --- a/Sources/System/IORing/IORing.swift +++ b/Sources/System/IORing/IORing.swift @@ -223,7 +223,7 @@ private func setUpRing( /* prot: */ PROT_READ | PROT_WRITE, /* flags: */ MAP_SHARED | MAP_POPULATE, /* fd: */ ringDescriptor, - /* offset: */ __off_t(IORING_OFF_SQ_RING) + /* offset: */ off_t(IORING_OFF_SQ_RING) ) if ringPtr == MAP_FAILED { @@ -238,7 +238,7 @@ private func setUpRing( /* prot: */ PROT_READ | PROT_WRITE, /* flags: */ MAP_SHARED | MAP_POPULATE, /* fd: */ ringDescriptor, - /* offset: */ __off_t(IORING_OFF_SQ_RING) + /* offset: */ off_t(IORING_OFF_SQ_RING) ) if sqPtr == MAP_FAILED { @@ -253,7 +253,7 @@ private func setUpRing( /* prot: */ PROT_READ | PROT_WRITE, /* flags: */ MAP_SHARED | MAP_POPULATE, /* fd: */ ringDescriptor, - /* offset: */ __off_t(IORING_OFF_CQ_RING) + /* offset: */ off_t(IORING_OFF_CQ_RING) ) if cqPtr == MAP_FAILED { @@ -270,7 +270,7 @@ private func setUpRing( /* prot: */ PROT_READ | PROT_WRITE, /* flags: */ MAP_SHARED | MAP_POPULATE, /* fd: */ ringDescriptor, - /* offset: */ __off_t(IORING_OFF_SQES) + /* offset: */ off_t(IORING_OFF_SQES) ) if sqes == MAP_FAILED { From c72c7589532b1d969f32c6457a524166a28ff109 Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Mon, 28 Jul 2025 17:40:24 -0700 Subject: [PATCH 381/427] Update Readme --- README.md | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index c18ba496..0089f681 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ To use the `SystemPackage` library in a SwiftPM project, add the following line to the dependencies in your `Package.swift` file: ```swift -.package(url: "https://github.com/apple/swift-system", from: "1.4.0"), +.package(url: "https://github.com/apple/swift-system", from: "1.6.0"), ``` Finally, include `"SystemPackage"` as a dependency for your executable target: @@ -41,7 +41,7 @@ Finally, include `"SystemPackage"` as a dependency for your executable target: let package = Package( // name, platforms, products, etc. dependencies: [ - .package(url: "https://github.com/apple/swift-system", from: "1.4.0"), + .package(url: "https://github.com/apple/swift-system", from: "1.6.0"), // other dependencies ], targets: [ @@ -87,7 +87,7 @@ The following table maps existing package releases to their minimum required Swi | Package version | Swift version | Xcode release | | ----------------------- | --------------- | ------------- | | swift-system 1.3.x | >= Swift 5.8 | >= Xcode 14.3 | -| swift-system 1.4.x | >= Swift 5.9 | >= Xcode 15.0 | +| swift-system 1.4.x through 1.6.x | >= Swift 5.9 | >= Xcode 15.0 | We'd like this package to quickly embrace Swift language and toolchain improvements that are relevant to its mandate. Accordingly, from time to time, new versions of this package require clients to upgrade to a more recent Swift toolchain release. (This allows the package to make use of new language/stdlib features, build on compiler bug fixes, and adopt new package manager functionality as soon as they are available.) Patch (i.e., bugfix) releases will not increase the required toolchain version, but any minor (i.e., new feature) release may do so. @@ -105,11 +105,13 @@ Before contributing, please read [CONTRIBUTING.md](CONTRIBUTING.md). We maintain separate branches for each active minor version of the package: -| Package version | Branch | +| Package version | Branch | | ----------------------- | ----------- | | swift-system 1.3.x | release/1.3 | -| swift-system 1.4.x (unreleased) | release/1.4 | -| swift-system 1.5.x (unreleased) | main | +| swift-system 1.4.x | release/1.4 | +| swift-system 1.5.x | release/1.5 | +| swift-system 1.6.x | release/1.6 | +| swift-system 1.7.x (unreleased) | main | Changes must land on the branch corresponding to the earliest release that they will need to ship on. They are periodically propagated to subsequent branches, in the following direction: From 3f68d422462feb65962db3d14344e0eb12cb4132 Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Mon, 28 Jul 2025 17:40:24 -0700 Subject: [PATCH 382/427] Update Readme --- README.md | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index c18ba496..0089f681 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ To use the `SystemPackage` library in a SwiftPM project, add the following line to the dependencies in your `Package.swift` file: ```swift -.package(url: "https://github.com/apple/swift-system", from: "1.4.0"), +.package(url: "https://github.com/apple/swift-system", from: "1.6.0"), ``` Finally, include `"SystemPackage"` as a dependency for your executable target: @@ -41,7 +41,7 @@ Finally, include `"SystemPackage"` as a dependency for your executable target: let package = Package( // name, platforms, products, etc. dependencies: [ - .package(url: "https://github.com/apple/swift-system", from: "1.4.0"), + .package(url: "https://github.com/apple/swift-system", from: "1.6.0"), // other dependencies ], targets: [ @@ -87,7 +87,7 @@ The following table maps existing package releases to their minimum required Swi | Package version | Swift version | Xcode release | | ----------------------- | --------------- | ------------- | | swift-system 1.3.x | >= Swift 5.8 | >= Xcode 14.3 | -| swift-system 1.4.x | >= Swift 5.9 | >= Xcode 15.0 | +| swift-system 1.4.x through 1.6.x | >= Swift 5.9 | >= Xcode 15.0 | We'd like this package to quickly embrace Swift language and toolchain improvements that are relevant to its mandate. Accordingly, from time to time, new versions of this package require clients to upgrade to a more recent Swift toolchain release. (This allows the package to make use of new language/stdlib features, build on compiler bug fixes, and adopt new package manager functionality as soon as they are available.) Patch (i.e., bugfix) releases will not increase the required toolchain version, but any minor (i.e., new feature) release may do so. @@ -105,11 +105,13 @@ Before contributing, please read [CONTRIBUTING.md](CONTRIBUTING.md). We maintain separate branches for each active minor version of the package: -| Package version | Branch | +| Package version | Branch | | ----------------------- | ----------- | | swift-system 1.3.x | release/1.3 | -| swift-system 1.4.x (unreleased) | release/1.4 | -| swift-system 1.5.x (unreleased) | main | +| swift-system 1.4.x | release/1.4 | +| swift-system 1.5.x | release/1.5 | +| swift-system 1.6.x | release/1.6 | +| swift-system 1.7.x (unreleased) | main | Changes must land on the branch corresponding to the earliest release that they will need to ship on. They are periodically propagated to subsequent branches, in the following direction: From 5fedb102b5da7ea93a4d9dc7e3eaa74287490dc5 Mon Sep 17 00:00:00 2001 From: Jonathan Flat Date: Tue, 29 Jul 2025 14:24:17 -0600 Subject: [PATCH 383/427] Fix IORing build for older Linux kernel versions --- Sources/CSystem/include/io_uring.h | 172 +++++++++++++---------- Sources/System/IORing/IORing.swift | 20 +-- Sources/System/IORing/RawIORequest.swift | 10 +- 3 files changed, 119 insertions(+), 83 deletions(-) diff --git a/Sources/CSystem/include/io_uring.h b/Sources/CSystem/include/io_uring.h index 08bce8bd..d8287b1a 100644 --- a/Sources/CSystem/include/io_uring.h +++ b/Sources/CSystem/include/io_uring.h @@ -3,12 +3,112 @@ #include #include +#define __SWIFT_IORING_SQE_FALLBACK_STRUCT { \ + __u8 opcode; \ + __u8 flags; \ + __u16 ioprio; \ + __s32 fd; \ + union { \ + __u64 off; \ + __u64 addr2; \ + struct { \ + __u32 cmd_op; \ + __u32 __pad1; \ + }; \ + }; \ + union { \ + __u64 addr; \ + __u64 splice_off_in; \ + struct { \ + __u32 level; \ + __u32 optname; \ + }; \ + }; \ + __u32 len; \ + union { \ + __kernel_rwf_t rw_flags; \ + __u32 fsync_flags; \ + __u16 poll_events; \ + __u32 poll32_events; \ + __u32 sync_range_flags; \ + __u32 msg_flags; \ + __u32 timeout_flags; \ + __u32 accept_flags; \ + __u32 cancel_flags; \ + __u32 open_flags; \ + __u32 statx_flags; \ + __u32 fadvise_advice; \ + __u32 splice_flags; \ + __u32 rename_flags; \ + __u32 unlink_flags; \ + __u32 hardlink_flags; \ + __u32 xattr_flags; \ + __u32 msg_ring_flags; \ + __u32 uring_cmd_flags; \ + __u32 waitid_flags; \ + __u32 futex_flags; \ + __u32 install_fd_flags; \ + __u32 nop_flags; \ + }; \ + __u64 user_data; \ + union { \ + __u16 buf_index; \ + __u16 buf_group; \ + } __attribute__((packed)); \ + __u16 personality; \ + union { \ + __s32 splice_fd_in; \ + __u32 file_index; \ + __u32 optlen; \ + struct { \ + __u16 addr_len; \ + __u16 __pad3[1]; \ + }; \ + }; \ + union { \ + struct { \ + __u64 addr3; \ + __u64 __pad2[1]; \ + }; \ + __u64 optval; \ + __u8 cmd[0]; \ + }; \ +} + #if __has_include() #include + +#ifdef IORING_TIMEOUT_BOOTTIME +// Kernel version >= 5.15, io_uring_sqe has file_index +// and all current Swift operations are supported. +#define __SWIFT_IORING_SUPPORTED true +typedef struct io_uring_sqe swift_io_uring_sqe; +#else +// io_uring_sqe is missing properties that IORequest expects. +// This configuration is not supported for now. +// +// Define a fallback struct to avoid build errors, but IORing +// will throw ENOTSUP on initialization. +#define __SWIFT_IORING_SUPPORTED false +typedef struct __SWIFT_IORING_SQE_FALLBACK_STRUCT swift_io_uring_sqe; +#endif + +// We can define more specific availability later + +#ifdef IORING_FEAT_RW_CUR_POS +// Kernel version >= 5.6, io_uring_sqe has open_flags +#endif + +#ifdef IORING_FEAT_NODROP +// Kernel version >= 5.5, io_uring_sqe has cancel_flags +#endif + #else // Minimal fallback definitions when linux/io_uring.h is not available (e.g. static SDK) #include +#define __SWIFT_IORING_SUPPORTED false + #define IORING_OFF_SQ_RING 0ULL #define IORING_OFF_CQ_RING 0x8000000ULL #define IORING_OFF_SQES 0x10000000ULL @@ -44,77 +144,7 @@ typedef int32_t __s32; typedef int __kernel_rwf_t; #endif -struct io_uring_sqe { - __u8 opcode; - __u8 flags; - __u16 ioprio; - __s32 fd; - union { - __u64 off; - __u64 addr2; - struct { - __u32 cmd_op; - __u32 __pad1; - }; - }; - union { - __u64 addr; - __u64 splice_off_in; - struct { - __u32 level; - __u32 optname; - }; - }; - __u32 len; - union { - __kernel_rwf_t rw_flags; - __u32 fsync_flags; - __u16 poll_events; - __u32 poll32_events; - __u32 sync_range_flags; - __u32 msg_flags; - __u32 timeout_flags; - __u32 accept_flags; - __u32 cancel_flags; - __u32 open_flags; - __u32 statx_flags; - __u32 fadvise_advice; - __u32 splice_flags; - __u32 rename_flags; - __u32 unlink_flags; - __u32 hardlink_flags; - __u32 xattr_flags; - __u32 msg_ring_flags; - __u32 uring_cmd_flags; - __u32 waitid_flags; - __u32 futex_flags; - __u32 install_fd_flags; - __u32 nop_flags; - }; - __u64 user_data; - union { - __u16 buf_index; - __u16 buf_group; - } __attribute__((packed)); - __u16 personality; - union { - __s32 splice_fd_in; - __u32 file_index; - __u32 optlen; - struct { - __u16 addr_len; - __u16 __pad3[1]; - }; - }; - union { - struct { - __u64 addr3; - __u64 __pad2[1]; - }; - __u64 optval; - __u8 cmd[0]; - }; -}; +typedef struct __SWIFT_IORING_SQE_FALLBACK_STRUCT swift_io_uring_sqe; struct io_uring_cqe { __u64 user_data; diff --git a/Sources/System/IORing/IORing.swift b/Sources/System/IORing/IORing.swift index f37d925a..a751a049 100644 --- a/Sources/System/IORing/IORing.swift +++ b/Sources/System/IORing/IORing.swift @@ -72,7 +72,7 @@ extension UnsafeMutableRawBufferPointer { @inline(__always) @inlinable internal func _tryWriteRequest( _ request: __owned RawIORequest, ring: inout SQRing, - submissionQueueEntries: UnsafeMutableBufferPointer + submissionQueueEntries: UnsafeMutableBufferPointer ) -> Bool { @@ -151,9 +151,9 @@ internal func _flushQueue(ring: borrowing SQRing) -> UInt32 { @inlinable internal func _getSubmissionEntry( - ring: inout SQRing, submissionQueueEntries: UnsafeMutableBufferPointer + ring: inout SQRing, submissionQueueEntries: UnsafeMutableBufferPointer ) -> UnsafeMutablePointer< - io_uring_sqe + swift_io_uring_sqe >? { let next = ring.userTail &+ 1 //this is expected to wrap @@ -196,7 +196,7 @@ private func setUpRing( throw err } - if params.features & IORING_FEAT_NODROP == 0 + if params.features & IORing.Features.nonDroppingCompletions.rawValue == 0 { close(ringDescriptor) throw Errno.invalidArgument @@ -266,7 +266,7 @@ private func setUpRing( // map the submission queue let sqes = mmap( /* addr: */ nil, - /* len: */ Int(params.sq_entries) * MemoryLayout.size, + /* len: */ Int(params.sq_entries) * MemoryLayout.size, /* prot: */ PROT_READ | PROT_WRITE, /* flags: */ MAP_SHARED | MAP_POPULATE, /* fd: */ ringDescriptor, @@ -307,7 +307,7 @@ public struct IORing: ~Copyable { @usableFromInline let completionRing: CQRing - @usableFromInline let submissionQueueEntries: UnsafeMutableBufferPointer + @usableFromInline let submissionQueueEntries: UnsafeMutableBufferPointer // kept around for unmap / cleanup. TODO: we can save a few words of memory by figuring out how to handle cleanup for non-IORING_FEAT_SINGLE_MMAP better let ringSize: Int @@ -366,6 +366,10 @@ public struct IORing: ~Copyable { /// Initializes an IORing with enough space for `queueDepth` prepared requests and completed operations public init(queueDepth: UInt32, flags: SetupFlags = []) throws(Errno) { + guard __SWIFT_IORING_SUPPORTED != 0 else { + throw Errno.notSupported + } + let (params, tmpRingDescriptor, tmpRingPtr, tmpRingSize, tmpSQPtr, tmpSQSize, tmpCQPtr, tmpCQSize, sqes) = try setUpRing(queueDepth: queueDepth, flags: flags) // All throws need to be before initializing ivars here to avoid // "error: conditional initialization or destruction of noncopyable types is not supported; @@ -421,7 +425,7 @@ public struct IORing: ~Copyable { ) let submissionQueueEntries = UnsafeMutableBufferPointer( - start: sqes.assumingMemoryBound(to: io_uring_sqe.self), + start: sqes.assumingMemoryBound(to: swift_io_uring_sqe.self), count: Int(params.sq_entries) ) @@ -868,7 +872,7 @@ public struct IORing: ~Copyable { } munmap( UnsafeMutableRawPointer(submissionQueueEntries.baseAddress!), - submissionQueueEntries.count * MemoryLayout.size + submissionQueueEntries.count * MemoryLayout.size ) close(ringDescriptor) } diff --git a/Sources/System/IORing/RawIORequest.swift b/Sources/System/IORing/RawIORequest.swift index dea378fb..4958a480 100644 --- a/Sources/System/IORing/RawIORequest.swift +++ b/Sources/System/IORing/RawIORequest.swift @@ -5,11 +5,13 @@ import CSystem @usableFromInline internal struct RawIORequest: ~Copyable { - @usableFromInline var rawValue: io_uring_sqe + // swift_io_uring_sqe is a typedef of io_uring_sqe on platforms where + // IORing is supported (currently requires kernel version >= 5.15). + @usableFromInline var rawValue: swift_io_uring_sqe @usableFromInline var path: FilePath? //buffer owner for the path pointer that the sqe may have @inlinable public init() { - self.rawValue = io_uring_sqe() + self.rawValue = swift_io_uring_sqe() } } @@ -176,8 +178,8 @@ extension RawIORequest { @inlinable static func withTimeoutRequest( - linkedTo opEntry: UnsafeMutablePointer, - in timeoutEntry: UnsafeMutablePointer, + linkedTo opEntry: UnsafeMutablePointer, + in timeoutEntry: UnsafeMutablePointer, duration: Duration, flags: TimeOutFlags, work: () throws -> R) rethrows -> R { From d75acaac3a832cc4ce55ec7728deaf44082e07f6 Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Mon, 28 Jul 2025 15:45:23 -0700 Subject: [PATCH 384/427] [ci] expand github actions --- .github/workflows/pull_request.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index d543be29..ec43b5b5 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -9,7 +9,10 @@ jobs: name: Test uses: swiftlang/github-workflows/.github/workflows/swift_package_test.yml@main with: - linux_exclude_swift_versions: '[{"swift_version": "5.8"}]' + linux_os_versions: '["jammy", "focal"]' + enable_macos_checks: false + macos_xcode_versions: '["16.3"]' + soundness: name: Soundness uses: swiftlang/github-workflows/.github/workflows/soundness.yml@main From d32abd8c6ea5482fad9e15f85e532d01cc5a777d Mon Sep 17 00:00:00 2001 From: Jonathan Flat Date: Tue, 29 Jul 2025 15:35:30 -0600 Subject: [PATCH 385/427] Fix warning: will never be executed --- Sources/System/IORing/IORing.swift | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Sources/System/IORing/IORing.swift b/Sources/System/IORing/IORing.swift index a751a049..3e54c789 100644 --- a/Sources/System/IORing/IORing.swift +++ b/Sources/System/IORing/IORing.swift @@ -10,6 +10,10 @@ import Musl #endif import Synchronization +private var ioringSupported: Bool { + __SWIFT_IORING_SUPPORTED != 0 +} + //This was #defines in older headers, so we redeclare it to get a consistent import internal enum RegistrationOps: UInt32 { case registerBuffers = 0 @@ -366,7 +370,7 @@ public struct IORing: ~Copyable { /// Initializes an IORing with enough space for `queueDepth` prepared requests and completed operations public init(queueDepth: UInt32, flags: SetupFlags = []) throws(Errno) { - guard __SWIFT_IORING_SUPPORTED != 0 else { + guard ioringSupported else { throw Errno.notSupported } From 9a1530813e138699aca84b0408e4adf2cfa78ed8 Mon Sep 17 00:00:00 2001 From: Rauhul Varma Date: Sat, 7 Jun 2025 10:49:36 -0700 Subject: [PATCH 386/427] Replace bespoke availability system Removes the script based availability system with the experimental availability macro feature. --- .github/workflows/pull_request.yml | 23 +++- Package.swift | 83 ++++++++++++- Sources/System/Errno.swift | 12 +- Sources/System/FileDescriptor.swift | 18 +-- Sources/System/FileHelpers.swift | 2 +- Sources/System/FileOperations.swift | 20 +-- Sources/System/FilePath/FilePath.swift | 8 +- .../FilePath/FilePathComponentView.swift | 16 +-- .../System/FilePath/FilePathComponents.swift | 28 ++--- Sources/System/FilePath/FilePathParsing.swift | 16 +-- Sources/System/FilePath/FilePathString.swift | 34 ++--- Sources/System/FilePath/FilePathSyntax.swift | 16 +-- Sources/System/FilePermissions.swift | 4 +- Sources/System/Internals/CInterop.swift | 4 +- Sources/System/MachPort.swift | 32 ++--- Sources/System/PlatformString.swift | 6 +- Sources/System/Util.swift | 8 +- Tests/SystemTests/ErrnoTest.swift | 2 +- Tests/SystemTests/FileOperationsTest.swift | 2 +- .../FilePathComponentsTest.swift | 4 +- .../FilePathTests/FilePathDecodable.swift | 2 +- .../FilePathTests/FilePathExtras.swift | 4 +- .../FilePathTests/FilePathParsingTest.swift | 2 +- .../FilePathTests/FilePathSyntaxTest.swift | 6 +- .../FilePathTests/FilePathTest.swift | 4 +- Tests/SystemTests/FileTypesTest.swift | 4 +- Tests/SystemTests/MachPortTests.swift | 2 +- Tests/SystemTests/MockingTest.swift | 2 +- Utilities/expand-availability.py | 117 ------------------ 29 files changed, 230 insertions(+), 251 deletions(-) delete mode 100755 Utilities/expand-availability.py diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index ec43b5b5..d4949eb1 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -10,8 +10,27 @@ jobs: uses: swiftlang/github-workflows/.github/workflows/swift_package_test.yml@main with: linux_os_versions: '["jammy", "focal"]' - enable_macos_checks: false - macos_xcode_versions: '["16.3"]' + enable_macos_checks: true + swift_flags: "-Xbuild-tools-swiftc -DSYSTEM_CI" + + build-abi-stable: + name: Build ABI Stable + uses: swiftlang/github-workflows/.github/workflows/swift_package_test.yml@main + with: + enable_linux_checks: false + enable_macos_checks: true + enable_windows_checks: false + # Only build + macos_build_command: "xcrun swift build --build-tests" + # Only test against latest Xcode + macos_exclude_xcode_versions: | + [ + {"xcode_version": "16.0"}, + {"xcode_version": "16.1"}, + {"xcode_version": "16.2"}, + ] + # Enable availability to match ABI stable verion of system. + swift_flags: "-Xbuild-tools-swiftc -DSYSTEM_CI -Xbuild-tools-swiftc -DSYSTEM_ABI_STABLE" soundness: name: Soundness diff --git a/Package.swift b/Package.swift index 9cbfc6e9..74a70160 100644 --- a/Package.swift +++ b/Package.swift @@ -12,11 +12,70 @@ import PackageDescription -let cSettings: [CSetting] = [ - .define("_CRT_SECURE_NO_WARNINGS", .when(platforms: [.windows])), +struct Available { + var name: String + var version: String + var osAvailability: String + var sourceAvailability: String + + init( + _ version: String, + _ osAvailability: String + ) { + self.name = "System" + self.version = version + self.osAvailability = osAvailability + self.sourceAvailability = "macOS 10.10, iOS 8.0, watchOS 2.0, tvOS 9.0, visionOS 1.0" + } + + var swiftSetting: SwiftSetting { + #if SYSTEM_ABI_STABLE + // Use availability matching Darwin API. + let availability = self.osAvailability + #else + // Use availability matching SwiftPM default. + let availability = self.sourceAvailability + #endif + return .enableExperimentalFeature( + "AvailabilityMacro=\(self.name) \(version):\(availability)") + } +} + +let availability: [Available] = [ + Available("0.0.1", "macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0"), + + Available("0.0.2", "macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0"), + + Available("0.0.3", "macOS 12.3, iOS 15.4, watchOS 8.5, tvOS 15.4"), + Available("1.1.0", "macOS 12.3, iOS 15.4, watchOS 8.5, tvOS 15.4"), + + Available("1.1.1", "macOS 14.4, iOS 17.4, watchOS 10.4, tvOS 17.4"), + Available("1.2.0", "macOS 14.4, iOS 17.4, watchOS 10.4, tvOS 17.4"), + + Available("1.2.1", "macOS 14.4, iOS 17.4, watchOS 10.4, tvOS 17.4"), + Available("1.3.0", "macOS 14.4, iOS 17.4, watchOS 10.4, tvOS 17.4"), + + Available("1.3.1", "macOS 14.4, iOS 17.4, watchOS 10.4, tvOS 17.4, visionOS 1.0"), + Available("1.3.2", "macOS 14.4, iOS 17.4, watchOS 10.4, tvOS 17.4, visionOS 1.0"), + Available("1.4.0", "macOS 14.4, iOS 17.4, watchOS 10.4, tvOS 17.4, visionOS 1.0"), + + Available("1.4.1", "macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, visionOS 9999"), + Available("1.4.2", "macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, visionOS 9999"), + Available("1.5.0", "macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, visionOS 9999"), + Available("1.6.0", "macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, visionOS 9999"), +] + +let swiftSettingsAvailability = availability.map(\.swiftSetting) + +#if SYSTEM_CI +let swiftSettingsCI: [SwiftSetting] = [ + .unsafeFlags(["-require-explicit-availability=error"]), ] +#else +let swiftSettingsCI: [SwiftSetting] = [] +#endif -let swiftSettings: [SwiftSetting] = [ +let swiftSettings = swiftSettingsAvailability + swiftSettingsCI + [ .define( "SYSTEM_PACKAGE_DARWIN", .when(platforms: [.macOS, .macCatalyst, .iOS, .watchOS, .tvOS, .visionOS])), @@ -25,6 +84,22 @@ let swiftSettings: [SwiftSetting] = [ .enableExperimentalFeature("Lifetimes"), ] +let cSettings: [CSetting] = [ + .define("_CRT_SECURE_NO_WARNINGS", .when(platforms: [.windows])), +] + +#if SYSTEM_ABI_STABLE +let platforms: [SupportedPlatform] = [ + .macOS("26"), + .iOS("26"), + .watchOS("26"), + .tvOS("26"), + .visionOS("26"), +] +#else +let platforms: [SupportedPlatform]? = nil +#endif + #if os(Linux) let filesToExclude = ["CMakeLists.txt"] #else @@ -39,6 +114,7 @@ let testsToExclude = ["IORequestTests.swift", "IORingTests.swift"] let package = Package( name: "swift-system", + platforms: platforms, products: [ .library(name: "SystemPackage", targets: ["SystemPackage"]), ], @@ -63,3 +139,4 @@ let package = Package( cSettings: cSettings, swiftSettings: swiftSettings), ]) + diff --git a/Sources/System/Errno.swift b/Sources/System/Errno.swift index e88b96d9..43b46af5 100644 --- a/Sources/System/Errno.swift +++ b/Sources/System/Errno.swift @@ -10,7 +10,7 @@ /// An error number used by system calls to communicate what kind of error /// occurred. @frozen -@available(/*System 0.0.1: macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0*/iOS 8, *) +@available(System 0.0.1, *) public struct Errno: RawRepresentable, Error, Hashable, Codable { /// The raw C error number. @_alwaysEmitIntoClient @@ -1391,7 +1391,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { } // Constants defined in header but not man page -@available(/*System 0.0.1: macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0*/iOS 8, *) +@available(System 0.0.1, *) extension Errno { /// Operation would block. /// @@ -1520,7 +1520,7 @@ extension Errno { #endif } -@available(/*System 0.0.1: macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0*/iOS 8, *) +@available(System 0.0.1, *) extension Errno { // TODO: We want to provide safe access to `errno`, but we need a // release-barrier to do so. @@ -1535,14 +1535,14 @@ extension Errno { } // Use "hidden" entry points for `NSError` bridging -@available(/*System 0.0.1: macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0*/iOS 8, *) +@available(System 0.0.1, *) extension Errno { public var _code: Int { Int(rawValue) } public var _domain: String { "NSPOSIXErrorDomain" } } -@available(/*System 0.0.1: macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0*/iOS 8, *) +@available(System 0.0.1, *) extension Errno: CustomStringConvertible, CustomDebugStringConvertible { /// A textual representation of the most recent error /// returned by a system call. @@ -1562,7 +1562,7 @@ extension Errno: CustomStringConvertible, CustomDebugStringConvertible { public var debugDescription: String { self.description } } -@available(/*System 0.0.1: macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0*/iOS 8, *) +@available(System 0.0.1, *) extension Errno { @_alwaysEmitIntoClient public static func ~=(_ lhs: Errno, _ rhs: Error) -> Bool { diff --git a/Sources/System/FileDescriptor.swift b/Sources/System/FileDescriptor.swift index c9953e8b..d5f5883b 100644 --- a/Sources/System/FileDescriptor.swift +++ b/Sources/System/FileDescriptor.swift @@ -14,7 +14,7 @@ /// of `FileDescriptor` values, /// in the same way as you manage a raw C file handle. @frozen -@available(/*System 0.0.1: macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0*/iOS 8, *) +@available(System 0.0.1, *) public struct FileDescriptor: RawRepresentable, Hashable, Codable { /// The raw C file handle. @_alwaysEmitIntoClient @@ -26,7 +26,7 @@ public struct FileDescriptor: RawRepresentable, Hashable, Codable { } // Standard file descriptors. -@available(/*System 0.0.1: macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0*/iOS 8, *) +@available(System 0.0.1, *) extension FileDescriptor { /// The standard input file descriptor, with a numeric value of 0. @_alwaysEmitIntoClient @@ -41,11 +41,11 @@ extension FileDescriptor { public static var standardError: FileDescriptor { .init(rawValue: 2) } } -@available(/*System 0.0.1: macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0*/iOS 8, *) +@available(System 0.0.1, *) extension FileDescriptor { /// The desired read and write access for a newly opened file. @frozen - @available(/*System 0.0.1: macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0*/iOS 8, *) + @available(System 0.0.1, *) public struct AccessMode: RawRepresentable, Sendable, Hashable, Codable { /// The raw C access mode. @_alwaysEmitIntoClient @@ -88,7 +88,7 @@ extension FileDescriptor { /// Options that specify behavior for a newly-opened file. @frozen - @available(/*System 0.0.1: macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0*/iOS 8, *) + @available(System 0.0.1, *) public struct OpenOptions: OptionSet, Sendable, Hashable, Codable { /// The raw C options. @_alwaysEmitIntoClient @@ -326,7 +326,7 @@ extension FileDescriptor { /// Options for specifying what a file descriptor's offset is relative to. @frozen - @available(/*System 0.0.1: macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0*/iOS 8, *) + @available(System 0.0.1, *) public struct SeekOrigin: RawRepresentable, Sendable, Hashable, Codable { /// The raw C value. @_alwaysEmitIntoClient @@ -402,7 +402,7 @@ extension FileDescriptor { } } -@available(/*System 0.0.1: macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0*/iOS 8, *) +@available(System 0.0.1, *) extension FileDescriptor.AccessMode : CustomStringConvertible, CustomDebugStringConvertible { @@ -421,7 +421,7 @@ extension FileDescriptor.AccessMode public var debugDescription: String { self.description } } -@available(/*System 0.0.1: macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0*/iOS 8, *) +@available(System 0.0.1, *) extension FileDescriptor.SeekOrigin : CustomStringConvertible, CustomDebugStringConvertible { @@ -444,7 +444,7 @@ extension FileDescriptor.SeekOrigin public var debugDescription: String { self.description } } -@available(/*System 0.0.1: macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0*/iOS 8, *) +@available(System 0.0.1, *) extension FileDescriptor.OpenOptions : CustomStringConvertible, CustomDebugStringConvertible { diff --git a/Sources/System/FileHelpers.swift b/Sources/System/FileHelpers.swift index 2ddb0729..5b082766 100644 --- a/Sources/System/FileHelpers.swift +++ b/Sources/System/FileHelpers.swift @@ -7,7 +7,7 @@ See https://swift.org/LICENSE.txt for license information */ -@available(/*System 0.0.1: macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0*/iOS 8, *) +@available(System 0.0.1, *) extension FileDescriptor { /// Runs a closure and then closes the file descriptor, even if an error occurs. /// diff --git a/Sources/System/FileOperations.swift b/Sources/System/FileOperations.swift index 2a8509bd..9ddc16c3 100644 --- a/Sources/System/FileOperations.swift +++ b/Sources/System/FileOperations.swift @@ -7,7 +7,7 @@ See https://swift.org/LICENSE.txt for license information */ -@available(/*System 0.0.1: macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0*/iOS 8, *) +@available(System 0.0.1, *) extension FileDescriptor { /// Opens or creates a file for reading or writing. /// @@ -368,7 +368,7 @@ extension FileDescriptor { } #if !os(WASI) -@available(/*System 0.0.2: macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0*/iOS 8, *) +@available(System 0.0.2, *) extension FileDescriptor { /// Duplicates this file descriptor and return the newly created copy. /// @@ -398,7 +398,7 @@ extension FileDescriptor { /// /// The corresponding C functions are `dup` and `dup2`. @_alwaysEmitIntoClient - @available(/*System 0.0.2: macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0*/iOS 8, *) + @available(System 0.0.2, *) public func duplicate( as target: FileDescriptor? = nil, retryOnInterrupt: Bool = true @@ -406,7 +406,7 @@ extension FileDescriptor { try _duplicate(as: target, retryOnInterrupt: retryOnInterrupt).get() } - @available(/*System 0.0.2: macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0*/iOS 8, *) + @available(System 0.0.2, *) @usableFromInline internal func _duplicate( as target: FileDescriptor?, @@ -435,7 +435,7 @@ extension FileDescriptor { #endif #if !os(WASI) -@available(/*System 1.1.0: macOS 12.3, iOS 15.4, watchOS 8.5, tvOS 15.4*/iOS 8, *) +@available(System 1.1.0, *) extension FileDescriptor { /// Creates a unidirectional data channel, which can be used for interprocess communication. /// @@ -443,12 +443,12 @@ extension FileDescriptor { /// /// The corresponding C function is `pipe`. @_alwaysEmitIntoClient - @available(/*System 1.1.0: macOS 12.3, iOS 15.4, watchOS 8.5, tvOS 15.4*/iOS 8, *) + @available(System 1.1.0, *) public static func pipe() throws -> (readEnd: FileDescriptor, writeEnd: FileDescriptor) { try _pipe().get() } - @available(/*System 1.1.0: macOS 12.3, iOS 15.4, watchOS 8.5, tvOS 15.4*/iOS 8, *) + @available(System 1.1.0, *) @usableFromInline internal static func _pipe() -> Result<(readEnd: FileDescriptor, writeEnd: FileDescriptor), Errno> { var fds: (Int32, Int32) = (-1, -1) @@ -463,7 +463,7 @@ extension FileDescriptor { } #endif -@available(/*System 1.2.0: macOS 9999, iOS 9999, watchOS 9999, tvOS 9999*/iOS 8, *) +@available(System 1.2.0, *) extension FileDescriptor { /// Truncates or extends the file referenced by this file descriptor. /// @@ -485,7 +485,7 @@ extension FileDescriptor { /// associated with the file. /// /// The corresponding C function is `ftruncate`. - @available(/*System 1.2.0: macOS 9999, iOS 9999, watchOS 9999, tvOS 9999*/iOS 8, *) + @available(System 1.2.0, *) @_alwaysEmitIntoClient public func resize( to newSize: Int64, @@ -497,7 +497,7 @@ extension FileDescriptor { ).get() } - @available(/*System 1.2.0: macOS 9999, iOS 9999, watchOS 9999, tvOS 9999*/iOS 8, *) + @available(System 1.2.0, *) @usableFromInline internal func _resize( to newSize: Int64, diff --git a/Sources/System/FilePath/FilePath.swift b/Sources/System/FilePath/FilePath.swift index f056759d..b27d053a 100644 --- a/Sources/System/FilePath/FilePath.swift +++ b/Sources/System/FilePath/FilePath.swift @@ -37,7 +37,7 @@ /// However, the rules for path equivalence /// are file-system–specific and have additional considerations /// like case insensitivity, Unicode normalization, and symbolic links. -@available(/*System 0.0.1: macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0*/iOS 8, *) +@available(System 0.0.1, *) public struct FilePath: Sendable { // TODO(docs): Section on all the new syntactic operations, lexical normalization, decomposition, // components, etc. @@ -59,16 +59,16 @@ public struct FilePath: Sendable { } } -@available(/*System 0.0.1: macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0*/iOS 8, *) +@available(System 0.0.1, *) extension FilePath { /// The length of the file path, excluding the null terminator. public var length: Int { _storage.length } } -@available(/*System 0.0.1: macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0*/iOS 8, *) +@available(System 0.0.1, *) extension FilePath: Hashable {} -@available(/*System 0.0.1: macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0*/iOS 8, *) +@available(System 0.0.1, *) extension FilePath: Codable { // Encoder is synthesized; it probably should have been explicit and used // a single-value container, but making that change now is somewhat risky. diff --git a/Sources/System/FilePath/FilePathComponentView.swift b/Sources/System/FilePath/FilePathComponentView.swift index 39381b4b..be176305 100644 --- a/Sources/System/FilePath/FilePathComponentView.swift +++ b/Sources/System/FilePath/FilePathComponentView.swift @@ -9,7 +9,7 @@ // MARK: - API -@available(/*System 0.0.2: macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0*/iOS 8, *) +@available(System 0.0.2, *) extension FilePath { /// A bidirectional, range replaceable collection of the non-root components /// that make up a file path. @@ -28,7 +28,7 @@ extension FilePath { /// /// path.components.removeAll { $0.kind == .currentDirectory } /// // path is "/home/username/bin/scripts/tree" - @available(/*System 0.0.2: macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0*/iOS 8, *) + @available(System 0.0.2, *) public struct ComponentView: Sendable { internal var _path: FilePath internal var _start: SystemString.Index @@ -64,11 +64,11 @@ extension FilePath { } } -@available(/*System 0.0.2: macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0*/iOS 8, *) +@available(System 0.0.2, *) extension FilePath.ComponentView: BidirectionalCollection { public typealias Element = FilePath.Component - @available(/*System 0.0.2: macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0*/iOS 8, *) + @available(System 0.0.2, *) public struct Index: Sendable, Comparable, Hashable { internal typealias Storage = SystemString.Index @@ -100,7 +100,7 @@ extension FilePath.ComponentView: BidirectionalCollection { } } -@available(/*System 0.0.2: macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0*/iOS 8, *) +@available(System 0.0.2, *) extension FilePath.ComponentView: RangeReplaceableCollection { public init() { self.init(FilePath()) @@ -150,7 +150,7 @@ extension FilePath.ComponentView: RangeReplaceableCollection { } } -@available(/*System 0.0.2: macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0*/iOS 8, *) +@available(System 0.0.2, *) extension FilePath { /// Create a file path from a root and a collection of components. public init( @@ -179,7 +179,7 @@ extension FilePath { // MARK: - Internals -@available(/*System 0.0.2: macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0*/iOS 8, *) +@available(System 0.0.2, *) extension FilePath.ComponentView: _PathSlice { internal var _range: Range { _start ..< _path._storage.endIndex @@ -192,7 +192,7 @@ extension FilePath.ComponentView: _PathSlice { // MARK: - Invariants -@available(/*System 0.0.2: macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0*/iOS 8, *) +@available(System 0.0.2, *) extension FilePath.ComponentView { internal func _invariantCheck() { #if DEBUG diff --git a/Sources/System/FilePath/FilePathComponents.swift b/Sources/System/FilePath/FilePathComponents.swift index b304a0a7..f2352617 100644 --- a/Sources/System/FilePath/FilePathComponents.swift +++ b/Sources/System/FilePath/FilePathComponents.swift @@ -9,7 +9,7 @@ // MARK: - API -@available(/*System 0.0.2: macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0*/iOS 8, *) +@available(System 0.0.2, *) extension FilePath { /// Represents a root of a file path. /// @@ -28,7 +28,7 @@ extension FilePath { /// * `\\server\share\` /// * `\\?\UNC\server\share\` /// * `\\?\Volume{12345678-abcd-1111-2222-123445789abc}\` - @available(/*System 0.0.2: macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0*/iOS 8, *) + @available(System 0.0.2, *) public struct Root: Sendable { internal var _path: FilePath internal var _rootEnd: SystemString.Index @@ -55,7 +55,7 @@ extension FilePath { /// file.kind == .regular // true /// file.extension // "txt" /// path.append(file) // path is "/tmp/foo.txt" - @available(/*System 0.0.2: macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0*/iOS 8, *) + @available(System 0.0.2, *) public struct Component: Sendable { internal var _path: FilePath internal var _range: Range @@ -74,13 +74,13 @@ extension FilePath { } } -@available(/*System 0.0.2: macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0*/iOS 8, *) +@available(System 0.0.2, *) extension FilePath.Component { /// Whether a component is a regular file or directory name, or a special /// directory `.` or `..` @frozen - @available(/*System 0.0.2: macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0*/iOS 8, *) + @available(System 0.0.2, *) public enum Kind: Sendable { /// The special directory `.`, representing the current directory. case currentDirectory @@ -100,7 +100,7 @@ extension FilePath.Component { } } -@available(/*System 0.0.2: macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0*/iOS 8, *) +@available(System 0.0.2, *) extension FilePath.Root { // TODO: Windows analysis APIs } @@ -186,17 +186,17 @@ extension _PathSlice { internal var _storage: SystemString { _path._storage } } -@available(/*System 0.0.2: macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0*/iOS 8, *) +@available(System 0.0.2, *) extension FilePath.Component: _PathSlice { } -@available(/*System 0.0.2: macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0*/iOS 8, *) +@available(System 0.0.2, *) extension FilePath.Root: _PathSlice { internal var _range: Range { (..<_rootEnd).relative(to: _path._storage) } } -@available(/*System 0.0.1: macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0*/iOS 8, *) +@available(System 0.0.1, *) extension FilePath: _PlatformStringable { func _withPlatformString(_ body: (UnsafePointer) throws -> Result) rethrows -> Result { try _storage.withPlatformString(body) @@ -208,7 +208,7 @@ extension FilePath: _PlatformStringable { } -@available(/*System 0.0.2: macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0*/iOS 8, *) +@available(System 0.0.2, *) extension FilePath.Component { // The index of the `.` denoting an extension internal func _extensionIndex() -> SystemString.Index? { @@ -237,7 +237,7 @@ internal func _makeExtension(_ ext: String) -> SystemString { return result } -@available(/*System 0.0.2: macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0*/iOS 8, *) +@available(System 0.0.2, *) extension FilePath.Component { internal init?(_ str: SystemString) { // FIXME: explicit null root? Or something else? @@ -250,7 +250,7 @@ extension FilePath.Component { } } -@available(/*System 0.0.2: macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0*/iOS 8, *) +@available(System 0.0.2, *) extension FilePath.Root { internal init?(_ str: SystemString) { // FIXME: explicit null root? Or something else? @@ -265,7 +265,7 @@ extension FilePath.Root { // MARK: - Invariants -@available(/*System 0.0.2: macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0*/iOS 8, *) +@available(System 0.0.2, *) extension FilePath.Component { // TODO: ensure this all gets easily optimized away in release... internal func _invariantCheck() { @@ -278,7 +278,7 @@ extension FilePath.Component { } } -@available(/*System 0.0.2: macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0*/iOS 8, *) +@available(System 0.0.2, *) extension FilePath.Root { internal func _invariantCheck() { #if DEBUG diff --git a/Sources/System/FilePath/FilePathParsing.swift b/Sources/System/FilePath/FilePathParsing.swift index c31e6a5b..f372dd67 100644 --- a/Sources/System/FilePath/FilePathParsing.swift +++ b/Sources/System/FilePath/FilePathParsing.swift @@ -116,7 +116,7 @@ extension SystemString { } } -@available(/*System 0.0.1: macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0*/iOS 8, *) +@available(System 0.0.1, *) extension FilePath { internal mutating func _removeTrailingSeparator() { _storage._removeTrailingSeparator() @@ -197,7 +197,7 @@ extension SystemString { } } -@available(/*System 0.0.1: macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0*/iOS 8, *) +@available(System 0.0.1, *) extension FilePath { internal var _relativeStart: SystemString.Index { _storage._relativePathStart @@ -209,7 +209,7 @@ extension FilePath { // Parse separators -@available(/*System 0.0.1: macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0*/iOS 8, *) +@available(System 0.0.1, *) extension FilePath { internal typealias _Index = SystemString.Index @@ -273,7 +273,7 @@ extension FilePath { } } -@available(/*System 0.0.2: macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0*/iOS 8, *) +@available(System 0.0.2, *) extension FilePath.ComponentView { // TODO: Store this... internal var _relativeStart: SystemString.Index { @@ -298,7 +298,7 @@ extension SystemString { } } -@available(/*System 0.0.2: macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0*/iOS 8, *) +@available(System 0.0.2, *) extension FilePath.Root { // Asserting self is a root, returns whether this is an // absolute root. @@ -323,7 +323,7 @@ extension FilePath.Root { } } -@available(/*System 0.0.1: macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0*/iOS 8, *) +@available(System 0.0.1, *) extension FilePath { internal var _portableDescription: String { guard _windowsPaths else { return description } @@ -345,7 +345,7 @@ internal var _windowsPaths: Bool { #endif } -@available(/*System 0.0.1: macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0*/iOS 8, *) +@available(System 0.0.1, *) extension FilePath { // Whether we should add a separator when doing an append internal var _needsSeparatorForAppend: Bool { @@ -373,7 +373,7 @@ extension FilePath { } // MARK: - Invariants -@available(/*System 0.0.1: macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0*/iOS 8, *) +@available(System 0.0.1, *) extension FilePath { internal func _invariantsSatisfied() -> Bool { var normal = self diff --git a/Sources/System/FilePath/FilePathString.swift b/Sources/System/FilePath/FilePathString.swift index 4fe9c1fd..45f79c8a 100644 --- a/Sources/System/FilePath/FilePathString.swift +++ b/Sources/System/FilePath/FilePathString.swift @@ -9,7 +9,7 @@ // MARK: - Platform string -@available(/*System 0.0.2: macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0*/iOS 8, *) +@available(System 0.0.2, *) extension FilePath { /// Creates a file path by copying bytes from a null-terminated platform /// string. @@ -109,7 +109,7 @@ extension FilePath { #endif } -@available(/*System 0.0.2: macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0*/iOS 8, *) +@available(System 0.0.2, *) extension FilePath.Component { /// Creates a file path component by copying bytes from a null-terminated /// platform string. @@ -198,7 +198,7 @@ extension FilePath.Component { } } -@available(/*System 0.0.2: macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0*/iOS 8, *) +@available(System 0.0.2, *) extension FilePath.Root { /// Creates a file path root by copying bytes from a null-terminated platform /// string. @@ -287,7 +287,7 @@ extension FilePath.Root { // MARK: - String literals -@available(/*System 0.0.1: macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0*/iOS 8, *) +@available(System 0.0.1, *) extension FilePath: ExpressibleByStringLiteral { /// Creates a file path from a string literal. /// @@ -306,7 +306,7 @@ extension FilePath: ExpressibleByStringLiteral { } } -@available(/*System 0.0.2: macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0*/iOS 8, *) +@available(System 0.0.2, *) extension FilePath.Component: ExpressibleByStringLiteral { /// Create a file path component from a string literal. /// @@ -331,7 +331,7 @@ extension FilePath.Component: ExpressibleByStringLiteral { } } -@available(/*System 0.0.2: macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0*/iOS 8, *) +@available(System 0.0.2, *) extension FilePath.Root: ExpressibleByStringLiteral { /// Create a file path root from a string literal. /// @@ -356,7 +356,7 @@ extension FilePath.Root: ExpressibleByStringLiteral { // MARK: - Printing and dumping -@available(/*System 0.0.1: macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0*/iOS 8, *) +@available(System 0.0.1, *) extension FilePath: CustomStringConvertible, CustomDebugStringConvertible { /// A textual representation of the file path. /// @@ -372,7 +372,7 @@ extension FilePath: CustomStringConvertible, CustomDebugStringConvertible { public var debugDescription: String { description.debugDescription } } -@available(/*System 0.0.2: macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0*/iOS 8, *) +@available(System 0.0.2, *) extension FilePath.Component: CustomStringConvertible, CustomDebugStringConvertible { /// A textual representation of the path component. @@ -389,7 +389,7 @@ extension FilePath.Component: CustomStringConvertible, CustomDebugStringConverti public var debugDescription: String { description.debugDescription } } -@available(/*System 0.0.2: macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0*/iOS 8, *) +@available(System 0.0.2, *) extension FilePath.Root: CustomStringConvertible, CustomDebugStringConvertible { /// A textual representation of the path root. @@ -409,7 +409,7 @@ extension FilePath.Root: CustomStringConvertible, CustomDebugStringConvertible { // MARK: - Convenience helpers // Convenience helpers -@available(/*System 0.0.2: macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0*/iOS 8, *) +@available(System 0.0.2, *) extension FilePath { /// Creates a string by interpreting the path’s content as UTF-8 on Unix /// and UTF-16 on Windows. @@ -420,7 +420,7 @@ extension FilePath { } } -@available(/*System 0.0.2: macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0*/iOS 8, *) +@available(System 0.0.2, *) extension FilePath.Component { /// Creates a string by interpreting the component’s content as UTF-8 on Unix /// and UTF-16 on Windows. @@ -431,7 +431,7 @@ extension FilePath.Component { } } -@available(/*System 0.0.2: macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0*/iOS 8, *) +@available(System 0.0.2, *) extension FilePath.Root { /// On Unix, this returns `"/"`. /// @@ -445,7 +445,7 @@ extension FilePath.Root { // MARK: - Decoding and validating -@available(/*System 0.0.1: macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0*/iOS 8, *) +@available(System 0.0.1, *) extension String { /// Creates a string by interpreting the file path's content as UTF-8 on Unix /// and UTF-16 on Windows. @@ -475,7 +475,7 @@ extension String { } } -@available(/*System 0.0.2: macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0*/iOS 8, *) +@available(System 0.0.2, *) extension String { /// Creates a string by interpreting the path component's content as UTF-8 on /// Unix and UTF-16 on Windows. @@ -505,7 +505,7 @@ extension String { } } -@available(/*System 0.0.2: macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0*/iOS 8, *) +@available(System 0.0.2, *) extension String { /// On Unix, creates the string `"/"` /// @@ -558,7 +558,7 @@ extension String { // MARK: - Deprecations -@available(/*System 0.0.1: macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0*/iOS 8, *) +@available(System 0.0.1, *) extension String { @available(*, deprecated, renamed: "init(decoding:)") public init(_ path: FilePath) { self.init(decoding: path) } @@ -568,7 +568,7 @@ extension String { } #if !os(Windows) -@available(/*System 0.0.1: macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0*/iOS 8, *) +@available(System 0.0.1, *) extension FilePath { /// For backwards compatibility only. This initializer is equivalent to /// the preferred `FilePath(platformString:)`. diff --git a/Sources/System/FilePath/FilePathSyntax.swift b/Sources/System/FilePath/FilePathSyntax.swift index 1c3fc097..eb1f25df 100644 --- a/Sources/System/FilePath/FilePathSyntax.swift +++ b/Sources/System/FilePath/FilePathSyntax.swift @@ -9,7 +9,7 @@ // MARK: - Query API -@available(/*System 0.0.2: macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0*/iOS 8, *) +@available(System 0.0.2, *) extension FilePath { /// Returns true if this path uniquely identifies the location of /// a file without reference to an additional starting location. @@ -99,7 +99,7 @@ extension FilePath { } // MARK: - Decompose a path -@available(/*System 0.0.2: macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0*/iOS 8, *) +@available(System 0.0.2, *) extension FilePath { /// Returns the root of a path if there is one, otherwise `nil`. /// @@ -182,7 +182,7 @@ extension FilePath { } } -@available(/*System 0.0.2: macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0*/iOS 8, *) +@available(System 0.0.2, *) extension FilePath { /// Returns the final component of the path. /// Returns `nil` if the path is empty or only contains a root. @@ -252,7 +252,7 @@ extension FilePath { } } -@available(/*System 0.0.2: macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0*/iOS 8, *) +@available(System 0.0.2, *) extension FilePath.Component { /// The extension of this file or directory component. /// @@ -283,7 +283,7 @@ extension FilePath.Component { } } -@available(/*System 0.0.2: macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0*/iOS 8, *) +@available(System 0.0.2, *) extension FilePath { /// The extension of the file or directory last component. @@ -349,7 +349,7 @@ extension FilePath { } -@available(/*System 0.0.2: macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0*/iOS 8, *) +@available(System 0.0.2, *) extension FilePath { /// Whether the path is in lexical-normal form, that is `.` and `..` /// components have been collapsed lexically (i.e. without following @@ -430,7 +430,7 @@ extension FilePath { } // Modification and concatenation API -@available(/*System 0.0.2: macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0*/iOS 8, *) +@available(System 0.0.2, *) extension FilePath { // TODO(Windows docs): example with roots /// If `prefix` is a prefix of `self`, removes it and returns `true`. @@ -583,7 +583,7 @@ extension FilePath { } // MARK - Renamed -@available(/*System 0.0.2: macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0*/iOS 8, *) +@available(System 0.0.2, *) extension FilePath { @available(*, unavailable, renamed: "removingLastComponent()") public var dirname: FilePath { removingLastComponent() } diff --git a/Sources/System/FilePermissions.swift b/Sources/System/FilePermissions.swift index 918246ef..f849a997 100644 --- a/Sources/System/FilePermissions.swift +++ b/Sources/System/FilePermissions.swift @@ -17,7 +17,7 @@ /// let perms = FilePermissions(rawValue: 0o644) /// perms == [.ownerReadWrite, .groupRead, .otherRead] // true @frozen -@available(/*System 0.0.1: macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0*/iOS 8, *) +@available(System 0.0.1, *) public struct FilePermissions: OptionSet, Sendable, Hashable, Codable { /// The raw C file permissions. @_alwaysEmitIntoClient @@ -132,7 +132,7 @@ public struct FilePermissions: OptionSet, Sendable, Hashable, Codable { public static var saveText: FilePermissions { .init(rawValue: 0o1000) } } -@available(/*System 0.0.1: macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0*/iOS 8, *) +@available(System 0.0.1, *) extension FilePermissions : CustomStringConvertible, CustomDebugStringConvertible { diff --git a/Sources/System/Internals/CInterop.swift b/Sources/System/Internals/CInterop.swift index 80d37d05..b6de1233 100644 --- a/Sources/System/Internals/CInterop.swift +++ b/Sources/System/Internals/CInterop.swift @@ -37,12 +37,12 @@ import Bionic public typealias CModeT = CInt #else /// The C `mode_t` type. -@available(/*System 0.0.1: macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0*/iOS 8, *) +@available(System 0.0.1, *) public typealias CModeT = mode_t #endif /// A namespace for C and platform types -@available(/*System 0.0.2: macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0*/iOS 8, *) +@available(System 0.0.2, *) public enum CInterop { #if os(Windows) public typealias Mode = CInt diff --git a/Sources/System/MachPort.swift b/Sources/System/MachPort.swift index 8cc05096..313f6c28 100644 --- a/Sources/System/MachPort.swift +++ b/Sources/System/MachPort.swift @@ -11,10 +11,10 @@ import Darwin.Mach -@available(/*System 1.4.0: macOS 9999, iOS 9999, watchOS 9999, tvOS 9999*/iOS 8, *) +@available(System 1.4.0, *) public protocol MachPortRight {} -@available(/*System 1.4.0: macOS 9999, iOS 9999, watchOS 9999, tvOS 9999*/iOS 8, *) +@available(System 1.4.0, *) @inlinable internal func _machPrecondition( file: StaticString = #file, @@ -26,10 +26,10 @@ internal func _machPrecondition( precondition(kr == expected, file: file, line: line) } -@available(/*System 1.4.0: macOS 9999, iOS 9999, watchOS 9999, tvOS 9999*/iOS 8, *) +@available(System 1.4.0, *) @frozen public enum Mach { - @available(/*System 1.4.0: macOS 9999, iOS 9999, watchOS 9999, tvOS 9999*/iOS 8, *) + @available(System 1.4.0, *) public struct Port: ~Copyable { @usableFromInline internal var _name: mach_port_name_t @@ -133,7 +133,7 @@ public enum Mach { public struct SendOnceRight: MachPortRight {} } -@available(/*System 1.4.0: macOS 9999, iOS 9999, watchOS 9999, tvOS 9999*/iOS 8, *) +@available(System 1.4.0, *) extension Mach.Port where RightType == Mach.ReceiveRight { /// Transfer ownership of an existing, unmanaged, but already guarded, /// Mach port right into a Mach.Port by name. @@ -158,7 +158,7 @@ extension Mach.Port where RightType == Mach.ReceiveRight { /// This initializer will abort if the right could not be created. /// Callers may assert that a valid right is always returned. @inlinable - @available(/*System 1.4.0: macOS 9999, iOS 9999, watchOS 9999, tvOS 9999*/iOS 8, *) + @available(System 1.4.0, *) public init() { var storage: mach_port_name_t = mach_port_name_t(MACH_PORT_NULL) _machPrecondition( @@ -181,7 +181,7 @@ extension Mach.Port where RightType == Mach.ReceiveRight { /// After this function completes, the Mach.Port is destroyed and no longer /// usable. @inlinable - @available(/*System 1.4.0: macOS 9999, iOS 9999, watchOS 9999, tvOS 9999*/iOS 8, *) + @available(System 1.4.0, *) public consuming func relinquish( ) -> (name: mach_port_name_t, context: mach_port_context_t) { let destructured = (name: _name, context: _context) @@ -204,7 +204,7 @@ extension Mach.Port where RightType == Mach.ReceiveRight { /// Mach.ReceiveRights. Use relinquish() to avoid the syscall and extract /// the context value along with the port name. @inlinable - @available(/*System 1.4.0: macOS 9999, iOS 9999, watchOS 9999, tvOS 9999*/iOS 8, *) + @available(System 1.4.0, *) public consuming func unguardAndRelinquish() -> mach_port_name_t { let (name, context) = self.relinquish() _machPrecondition(mach_port_unguard(mach_task_self_, name, context)) @@ -221,7 +221,7 @@ extension Mach.Port where RightType == Mach.ReceiveRight { /// The body block may optionally return something, which will then be /// returned to the caller of withBorrowedName. @inlinable - @available(/*System 1.4.0: macOS 9999, iOS 9999, watchOS 9999, tvOS 9999*/iOS 8, *) + @available(System 1.4.0, *) public func withBorrowedName( body: (mach_port_name_t, mach_port_context_t) -> ReturnType ) -> ReturnType { @@ -235,7 +235,7 @@ extension Mach.Port where RightType == Mach.ReceiveRight { /// This function will abort if the right could not be created. /// Callers may assert that a valid right is always returned. @inlinable - @available(/*System 1.4.0: macOS 9999, iOS 9999, watchOS 9999, tvOS 9999*/iOS 8, *) + @available(System 1.4.0, *) public func makeSendOnceRight() -> Mach.Port { // send once rights do not coalesce var newRight: mach_port_name_t = mach_port_name_t(MACH_PORT_NULL) @@ -264,7 +264,7 @@ extension Mach.Port where RightType == Mach.ReceiveRight { /// This function will abort if the right could not be created. /// Callers may assert that a valid right is always returned. @inlinable - @available(/*System 1.4.0: macOS 9999, iOS 9999, watchOS 9999, tvOS 9999*/iOS 8, *) + @available(System 1.4.0, *) public func makeSendRight() -> Mach.Port { let how = MACH_MSG_TYPE_MAKE_SEND @@ -282,7 +282,7 @@ extension Mach.Port where RightType == Mach.ReceiveRight { /// /// Each get/set of this property makes a syscall. @inlinable - @available(/*System 1.4.0: macOS 9999, iOS 9999, watchOS 9999, tvOS 9999*/iOS 8, *) + @available(System 1.4.0, *) public var makeSendCount: mach_port_mscount_t { get { var status: mach_port_status = mach_port_status() @@ -311,7 +311,7 @@ extension Mach.Port where RightType == Mach.ReceiveRight { } } -@available(/*System 1.4.0: macOS 9999, iOS 9999, watchOS 9999, tvOS 9999*/iOS 8, *) +@available(System 1.4.0, *) extension Mach.Port where RightType == Mach.SendRight { /// Transfer ownership of the underlying port right to the caller. /// @@ -337,7 +337,7 @@ extension Mach.Port where RightType == Mach.SendRight { /// receiving side has been deallocated, then copySendRight() will throw /// a Mach.PortRightError.deadName error. @inlinable - @available(/*System 1.4.0: macOS 9999, iOS 9999, watchOS 9999, tvOS 9999*/iOS 8, *) + @available(System 1.4.0, *) public func copySendRight() throws -> Mach.Port { let how = MACH_MSG_TYPE_COPY_SEND @@ -354,7 +354,7 @@ extension Mach.Port where RightType == Mach.SendRight { } } -@available(/*System 1.4.0: macOS 9999, iOS 9999, watchOS 9999, tvOS 9999*/iOS 8, *) +@available(System 1.4.0, *) extension Mach.Port where RightType == Mach.SendOnceRight { /// Transfer ownership of the underlying port right to the caller. /// @@ -366,7 +366,7 @@ extension Mach.Port where RightType == Mach.SendOnceRight { /// After this function completes, the Mach.Port is destroyed and no longer /// usable. @inlinable - @available(/*System 1.4.0: macOS 9999, iOS 9999, watchOS 9999, tvOS 9999*/iOS 8, *) + @available(System 1.4.0, *) public consuming func relinquish() -> mach_port_name_t { let name = _name discard self diff --git a/Sources/System/PlatformString.swift b/Sources/System/PlatformString.swift index 664a6089..7b8fd90c 100644 --- a/Sources/System/PlatformString.swift +++ b/Sources/System/PlatformString.swift @@ -7,7 +7,7 @@ See https://swift.org/LICENSE.txt for license information */ -@available(/*System 0.0.2: macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0*/iOS 8, *) +@available(System 0.0.2, *) extension String { /// Creates a string by interpreting the null-terminated platform string as /// UTF-8 on Unix and UTF-16 on Windows. @@ -165,7 +165,7 @@ extension String { } -@available(/*System 0.0.2: macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0*/iOS 8, *) +@available(System 0.0.2, *) extension CInterop.PlatformChar { internal var _platformCodeUnit: CInterop.PlatformUnicodeEncoding.CodeUnit { #if os(Windows) @@ -176,7 +176,7 @@ extension CInterop.PlatformChar { } } -@available(/*System 0.0.2: macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0*/iOS 8, *) +@available(System 0.0.2, *) extension CInterop.PlatformUnicodeEncoding.CodeUnit { internal var _platformChar: CInterop.PlatformChar { #if os(Windows) diff --git a/Sources/System/Util.swift b/Sources/System/Util.swift index 3a8df9ac..e4832ac5 100644 --- a/Sources/System/Util.swift +++ b/Sources/System/Util.swift @@ -8,21 +8,21 @@ */ // Results in errno if i == -1 -@available(/*System 0.0.1: macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0*/iOS 8, *) +@available(System 0.0.1, *) private func valueOrErrno( _ i: I ) -> Result { i == -1 ? .failure(Errno.current) : .success(i) } -@available(/*System 0.0.1: macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0*/iOS 8, *) +@available(System 0.0.1, *) private func nothingOrErrno( _ i: I ) -> Result<(), Errno> { valueOrErrno(i).map { _ in () } } -@available(/*System 0.0.1: macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0*/iOS 8, *) +@available(System 0.0.1, *) internal func valueOrErrno( retryOnInterrupt: Bool, _ f: () -> I ) -> Result { @@ -36,7 +36,7 @@ internal func valueOrErrno( } while true } -@available(/*System 0.0.1: macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0*/iOS 8, *) +@available(System 0.0.1, *) internal func nothingOrErrno( retryOnInterrupt: Bool, _ f: () -> I ) -> Result<(), Errno> { diff --git a/Tests/SystemTests/ErrnoTest.swift b/Tests/SystemTests/ErrnoTest.swift index 5f4551ba..767190ee 100644 --- a/Tests/SystemTests/ErrnoTest.swift +++ b/Tests/SystemTests/ErrnoTest.swift @@ -19,7 +19,7 @@ import System import WinSDK #endif -@available(/*System 0.0.1: macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0*/iOS 8, *) +@available(System 0.0.1, *) final class ErrnoTest: XCTestCase { func testConstants() { XCTAssert(EPERM == Errno.notPermitted.rawValue) diff --git a/Tests/SystemTests/FileOperationsTest.swift b/Tests/SystemTests/FileOperationsTest.swift index 479e503f..e4609bb3 100644 --- a/Tests/SystemTests/FileOperationsTest.swift +++ b/Tests/SystemTests/FileOperationsTest.swift @@ -20,7 +20,7 @@ import Android import CSystem #endif -@available(/*System 0.0.1: macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0*/iOS 8, *) +@available(System 0.0.1, *) final class FileOperationsTest: XCTestCase { #if !os(WASI) // Would need to use _getConst funcs from CSystem func testSyscalls() { diff --git a/Tests/SystemTests/FilePathTests/FilePathComponentsTest.swift b/Tests/SystemTests/FilePathTests/FilePathComponentsTest.swift index e816bb5a..d72d59a2 100644 --- a/Tests/SystemTests/FilePathTests/FilePathComponentsTest.swift +++ b/Tests/SystemTests/FilePathTests/FilePathComponentsTest.swift @@ -15,7 +15,7 @@ import XCTest @testable import System #endif -@available(/*System 0.0.2: macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0*/iOS 8, *) +@available(System 0.0.2, *) struct TestPathComponents: TestCase { var path: FilePath var expectedRoot: FilePath.Root? @@ -100,7 +100,7 @@ struct TestPathComponents: TestCase { } } -@available(/*System 0.0.2: macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0*/iOS 8, *) +@available(System 0.0.2, *) final class FilePathComponentsTest: XCTestCase { func testAdHocRRC() { var path: FilePath = "/usr/local/bin" diff --git a/Tests/SystemTests/FilePathTests/FilePathDecodable.swift b/Tests/SystemTests/FilePathTests/FilePathDecodable.swift index 290bef2f..b88b0299 100644 --- a/Tests/SystemTests/FilePathTests/FilePathDecodable.swift +++ b/Tests/SystemTests/FilePathTests/FilePathDecodable.swift @@ -15,7 +15,7 @@ import XCTest @testable import System #endif -@available(/*System 0.0.1: macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0*/iOS 8, *) +@available(System 0.0.1, *) final class FilePathDecodableTest: XCTestCase { func testInvalidFilePath() { // _storage is a valid SystemString, but the invariants of FilePath are diff --git a/Tests/SystemTests/FilePathTests/FilePathExtras.swift b/Tests/SystemTests/FilePathTests/FilePathExtras.swift index 82f11373..ff4b1b37 100644 --- a/Tests/SystemTests/FilePathTests/FilePathExtras.swift +++ b/Tests/SystemTests/FilePathTests/FilePathExtras.swift @@ -6,7 +6,7 @@ #endif // Why can't I write this extension on `FilePath.ComponentView.SubSequence`? -@available(/*System 0.0.2: macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0*/iOS 8, *) +@available(System 0.0.2, *) extension Slice where Base == FilePath.ComponentView { internal var _storageSlice: SystemString.SubSequence { base._path._storage[self.startIndex._storage ..< self.endIndex._storage] @@ -15,7 +15,7 @@ extension Slice where Base == FilePath.ComponentView { // Proposed API that didn't make the cut, but we stil want to keep our testing for -@available(/*System 0.0.2: macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0*/iOS 8, *) +@available(System 0.0.2, *) extension FilePath { /// Returns `self` relative to `base`. /// This does not cosult the file system or resolve symlinks. diff --git a/Tests/SystemTests/FilePathTests/FilePathParsingTest.swift b/Tests/SystemTests/FilePathTests/FilePathParsingTest.swift index 766bdf8d..66e9105e 100644 --- a/Tests/SystemTests/FilePathTests/FilePathParsingTest.swift +++ b/Tests/SystemTests/FilePathTests/FilePathParsingTest.swift @@ -68,7 +68,7 @@ extension ParsingTestCase { @testable import System #endif -@available(/*System 0.0.2: macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0*/iOS 8, *) +@available(System 0.0.2, *) final class FilePathParsingTest: XCTestCase { func testNormalization() { let unixPaths: Array = [ diff --git a/Tests/SystemTests/FilePathTests/FilePathSyntaxTest.swift b/Tests/SystemTests/FilePathTests/FilePathSyntaxTest.swift index d23999a2..4e182556 100644 --- a/Tests/SystemTests/FilePathTests/FilePathSyntaxTest.swift +++ b/Tests/SystemTests/FilePathTests/FilePathSyntaxTest.swift @@ -146,7 +146,7 @@ extension SyntaxTestCase { } } -@available(/*System 0.0.2: macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0*/iOS 8, *) +@available(System 0.0.2, *) extension SyntaxTestCase { func testComponents(_ path: FilePath, expected: [String]) { let expectedComponents = expected.map { FilePath.Component($0)! } @@ -342,7 +342,7 @@ private struct WindowsRootTestCase: TestCase { var line: UInt } -@available(/*System 0.0.2: macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0*/iOS 8, *) +@available(System 0.0.2, *) extension WindowsRootTestCase { func runAllTests() { withWindowsPaths(enabled: true) { @@ -357,7 +357,7 @@ extension WindowsRootTestCase { } } -@available(/*System 0.0.2: macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0*/iOS 8, *) +@available(System 0.0.2, *) final class FilePathSyntaxTest: XCTestCase { func testPathSyntax() { let unixPaths: Array = [ diff --git a/Tests/SystemTests/FilePathTests/FilePathTest.swift b/Tests/SystemTests/FilePathTests/FilePathTest.swift index b50cc17e..ef71c850 100644 --- a/Tests/SystemTests/FilePathTests/FilePathTest.swift +++ b/Tests/SystemTests/FilePathTests/FilePathTest.swift @@ -15,7 +15,7 @@ import SystemPackage import System #endif -@available(/*System 0.0.1: macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0*/iOS 8, *) +@available(System 0.0.1, *) func filePathFromInvalidCodePointSequence(_ bytes: S) -> FilePath where S.Element == CInterop.PlatformUnicodeEncoding.CodeUnit { var array = Array(bytes) assert(array.last != 0, "already null terminated") @@ -28,7 +28,7 @@ func filePathFromInvalidCodePointSequence(_ bytes: S) -> FilePath w } } -@available(/*System 0.0.2: macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0*/iOS 8, *) +@available(System 0.0.2, *) final class FilePathTest: XCTestCase { struct TestPath { let filePath: FilePath diff --git a/Tests/SystemTests/FileTypesTest.swift b/Tests/SystemTests/FileTypesTest.swift index a818dfba..0ddcb0de 100644 --- a/Tests/SystemTests/FileTypesTest.swift +++ b/Tests/SystemTests/FileTypesTest.swift @@ -18,7 +18,7 @@ import System import Android #endif -@available(/*System 0.0.1: macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0*/iOS 8, *) +@available(System 0.0.1, *) final class FileDescriptorTest: XCTestCase { func testStandardDescriptors() { XCTAssertEqual(FileDescriptor.standardInput.rawValue, 0) @@ -76,7 +76,7 @@ final class FileDescriptorTest: XCTestCase { } -@available(/*System 0.0.1: macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0*/iOS 8, *) +@available(System 0.0.1, *) final class FilePermissionsTest: XCTestCase { func testPermissions() { diff --git a/Tests/SystemTests/MachPortTests.swift b/Tests/SystemTests/MachPortTests.swift index 073da9a5..fde04d8c 100644 --- a/Tests/SystemTests/MachPortTests.swift +++ b/Tests/SystemTests/MachPortTests.swift @@ -18,7 +18,7 @@ import SystemPackage import System #endif -@available(/*System 1.4.0: macOS 9999, iOS 9999, watchOS 9999, tvOS 9999*/iOS 8, *) +@available(System 1.4.0, *) final class MachPortTests: XCTestCase { func refCountForMachPortName(name:mach_port_name_t, kind:mach_port_right_t) -> mach_port_urefs_t { var refCount:mach_port_urefs_t = .max diff --git a/Tests/SystemTests/MockingTest.swift b/Tests/SystemTests/MockingTest.swift index 1f2c96da..38985c6b 100644 --- a/Tests/SystemTests/MockingTest.swift +++ b/Tests/SystemTests/MockingTest.swift @@ -15,7 +15,7 @@ import XCTest @testable import System #endif -@available(/*System 0.0.2: macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0*/iOS 8, *) +@available(System 0.0.2, *) final class MockingTest: XCTestCase { func testMocking() { XCTAssertFalse(mockingEnabled) diff --git a/Utilities/expand-availability.py b/Utilities/expand-availability.py deleted file mode 100755 index 4cb1a1be..00000000 --- a/Utilities/expand-availability.py +++ /dev/null @@ -1,117 +0,0 @@ -#!/usr/bin/env python3 - -# This script can be used to automatically add/remove `@available` attributes to -# declarations in Swift sources in this package. -# -# In order for this to work, ABI-impacting declarations need to be annotated -# with special comments in the following format: -# -# @available(/*System 0.0.2*/iOS 8, *) -# public func greeting() -> String { -# "Hello" -# } -# -# (The iOS 8 availability is a dummy no-op declaration -- it only has to be -# there because `@available(*)` isn't valid syntax, and commenting out the -# entire `@available` attribute would interfere with parser tools for doc -# comments. `iOS 8` is the shortest version string that matches the minimum -# possible deployment target for Swift code, so we use that as our dummy -# availability version. `@available(iOS 8, *)` is functionally equivalent to not -# having an `@available` attribute at all.) -# -# The script adds full availability incantations to these comments. It can run -# in one of two modes: -# -# By default, `expand-availability.py` expands availability macros within the -# comments. This is useful during package development to cross-reference -# availability across `SystemPackage` and the ABI-stable `System` module that -# ships in Apple's OS releases: -# -# @available(/*System 0.0.2: macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0*/iOS 8, *) -# public func greeting() -> String { -# "Hello" -# } -# -# `expand-availability.py --attributes` adds actual availability declarations. -# This is used by maintainers to build ABI stable releases of System on Apple's -# platforms: -# -# @available(/*System 0.0.2: */macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *) -# public func greeting() -> String { -# "Hello" -# } -# -# The script recognizes all three forms of these annotations and updates them on -# every run, so we can run the script to enable/disable attributes as needed. - -import os -import os.path -import fileinput -import re -import sys -import argparse - -versions = { - "System 0.0.1": "macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0", - "System 0.0.2": "macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0", - "System 1.1.0": "macOS 12.3, iOS 15.4, watchOS 8.5, tvOS 15.4", - "System 1.2.0": "macOS 9999, iOS 9999, watchOS 9999, tvOS 9999", - "System 1.3.0": "macOS 9999, iOS 9999, watchOS 9999, tvOS 9999", - "System 1.4.0": "macOS 9999, iOS 9999, watchOS 9999, tvOS 9999", -} - -parser = argparse.ArgumentParser(description="Expand availability macros.") -parser.add_argument("--attributes", help="Add @available attributes", - action="store_true") -args = parser.parse_args() - -def swift_sources_in(path): - result = [] - for (dir, _, files) in os.walk(path): - for file in files: - extension = os.path.splitext(file)[1] - if extension == ".swift": - result.append(os.path.join(dir, file)) - return result - -# Old-style syntax: -# /*System 0.0.2*/ -# /*System 0.0.2, @available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *)*/ -# /*System 0.0.2*/@available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *) -old_macro_pattern = re.compile( - r"/\*(System [^ *]+)(, @available\([^)]*\))?\*/(@available\([^)]*\))?") - -# New-style comments: -# @available(/*SwiftSystem 0.0.2*/macOS 10, *) -# @available(/*SwiftSystem 0.0.2: macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0*/iOS 8, *) -# @available(/*SwiftSystem 0.0.2*/macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *) -# -# These do not interfere with our tools' ability to find doc comments. -macro_pattern = re.compile( - r"@available\(/\*(System [^ *:]+)[^*/)]*\*/([^)]*)\*\)") - -def available_attribute(filename, lineno, symbolic_version): - expansion = versions[symbolic_version] - if expansion is None: - raise ValueError("{0}:{1}: error: Unknown System version '{0}'" - .format(fileinput.filename(), fileinput.lineno(), symbolic_version)) - if args.attributes: - attribute = "@available(/*{0}*/{1}, *)".format(symbolic_version, expansion) - else: - # Sadly `@available(*)` is not valid syntax, so we have to mention at - # least one actual platform here. - attribute = "@available(/*{0}: {1}*/iOS 8, *)".format(symbolic_version, expansion) - return attribute - - -sources = swift_sources_in("Sources") + swift_sources_in("Tests") -for line in fileinput.input(files=sources, inplace=True): - match = re.search(macro_pattern, line) - if match is None: - match = re.search(old_macro_pattern, line) - if match: - symbolic_version = match.group(1) - replacement = available_attribute( - fileinput.filename(), fileinput.lineno(), symbolic_version) - line = line[:match.start()] + replacement + line[match.end():] - print(line, end="") From e5198c2b2d062741dc6653b993ce31566049364d Mon Sep 17 00:00:00 2001 From: Rauhul Varma Date: Wed, 30 Jul 2025 08:50:14 -0700 Subject: [PATCH 387/427] disable 16.0 and 16.1 --- .github/workflows/pull_request.yml | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index d4949eb1..35d50227 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -11,6 +11,13 @@ jobs: with: linux_os_versions: '["jammy", "focal"]' enable_macos_checks: true + # FIXME: https://github.com/swiftlang/github-workflows/pull/140 + # Xcode 16.0 and 16.1 are not actually available + macos_exclude_xcode_versions: | + [ + {"xcode_version": "16.0"}, + {"xcode_version": "16.1"}, + ] swift_flags: "-Xbuild-tools-swiftc -DSYSTEM_CI" build-abi-stable: @@ -22,12 +29,12 @@ jobs: enable_windows_checks: false # Only build macos_build_command: "xcrun swift build --build-tests" - # Only test against latest Xcode + # FIXME: https://github.com/swiftlang/github-workflows/pull/140 + # Xcode 16.0 and 16.1 are not actually available macos_exclude_xcode_versions: | [ {"xcode_version": "16.0"}, {"xcode_version": "16.1"}, - {"xcode_version": "16.2"}, ] # Enable availability to match ABI stable verion of system. swift_flags: "-Xbuild-tools-swiftc -DSYSTEM_CI -Xbuild-tools-swiftc -DSYSTEM_ABI_STABLE" From f97a80426c77504e51cffc6189d2f23e0965c83e Mon Sep 17 00:00:00 2001 From: Rauhul Varma Date: Wed, 30 Jul 2025 08:53:33 -0700 Subject: [PATCH 388/427] update readme --- Package.swift | 1 + README.md | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Package.swift b/Package.swift index 74a70160..35059250 100644 --- a/Package.swift +++ b/Package.swift @@ -63,6 +63,7 @@ let availability: [Available] = [ Available("1.4.2", "macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, visionOS 9999"), Available("1.5.0", "macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, visionOS 9999"), Available("1.6.0", "macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, visionOS 9999"), + Available("1.6.1", "macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, visionOS 9999"), ] let swiftSettingsAvailability = availability.map(\.swiftSetting) diff --git a/README.md b/README.md index 0089f681..0b75fd04 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ To use the `SystemPackage` library in a SwiftPM project, add the following line to the dependencies in your `Package.swift` file: ```swift -.package(url: "https://github.com/apple/swift-system", from: "1.6.0"), +.package(url: "https://github.com/apple/swift-system", from: "1.6.1"), ``` Finally, include `"SystemPackage"` as a dependency for your executable target: @@ -41,7 +41,7 @@ Finally, include `"SystemPackage"` as a dependency for your executable target: let package = Package( // name, platforms, products, etc. dependencies: [ - .package(url: "https://github.com/apple/swift-system", from: "1.6.0"), + .package(url: "https://github.com/apple/swift-system", from: "1.6.1"), // other dependencies ], targets: [ From c4fef8b59a68d5b773ddc1e8697815fe553280eb Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Wed, 30 Jul 2025 09:11:26 -0700 Subject: [PATCH 389/427] Disable non-escapable API by feature flag --- Sources/System/IORing/IOCompletion.swift | 6 +++--- Sources/System/IORing/IORequest.swift | 6 +++--- Sources/System/IORing/IORing.swift | 6 +++--- Sources/System/IORing/RawIORequest.swift | 6 +++--- Tests/SystemTests/IORequestTests.swift | 6 +++--- Tests/SystemTests/IORingTests.swift | 6 +++--- 6 files changed, 18 insertions(+), 18 deletions(-) diff --git a/Sources/System/IORing/IOCompletion.swift b/Sources/System/IORing/IOCompletion.swift index ea99cc70..d9e69050 100644 --- a/Sources/System/IORing/IOCompletion.swift +++ b/Sources/System/IORing/IOCompletion.swift @@ -1,4 +1,4 @@ -#if compiler(>=6.2) +#if compiler(>=6.2) && $Lifetimes #if os(Linux) import CSystem @@ -67,5 +67,5 @@ public extension IORing.Completion { } } } -#endif -#endif +#endif // os(Linux) +#endif // compiler(>=6.2) && $Lifetimes diff --git a/Sources/System/IORing/IORequest.swift b/Sources/System/IORing/IORequest.swift index cc91e476..7e433f9f 100644 --- a/Sources/System/IORing/IORequest.swift +++ b/Sources/System/IORing/IORequest.swift @@ -1,4 +1,4 @@ -#if compiler(>=6.2) +#if compiler(>=6.2) && $Lifetimes #if os(Linux) import CSystem @@ -493,5 +493,5 @@ extension IORing.Request { return request } } -#endif -#endif +#endif // os(Linux) +#endif // compiler(>=6.2) && $Lifetimes diff --git a/Sources/System/IORing/IORing.swift b/Sources/System/IORing/IORing.swift index 3e54c789..16c85a0f 100644 --- a/Sources/System/IORing/IORing.swift +++ b/Sources/System/IORing/IORing.swift @@ -1,4 +1,4 @@ -#if compiler(>=6.2) +#if compiler(>=6.2) && $Lifetimes #if os(Linux) import CSystem @@ -900,5 +900,5 @@ extension IORing.RegisteredBuffer { return unsafe _overrideLifetime(span, borrowing: self) } } -#endif -#endif +#endif // os(Linux) +#endif // compiler(>=6.2) && $Lifetimes diff --git a/Sources/System/IORing/RawIORequest.swift b/Sources/System/IORing/RawIORequest.swift index 4958a480..0bc40294 100644 --- a/Sources/System/IORing/RawIORequest.swift +++ b/Sources/System/IORing/RawIORequest.swift @@ -1,4 +1,4 @@ -#if compiler(>=6.2) +#if compiler(>=6.2) && $Lifetimes #if os(Linux) import CSystem @@ -201,5 +201,5 @@ extension RawIORequest { } } } -#endif -#endif +#endif // os(Linux) +#endif // compiler(>=6.2) && $Lifetimes diff --git a/Tests/SystemTests/IORequestTests.swift b/Tests/SystemTests/IORequestTests.swift index 50728234..9246d394 100644 --- a/Tests/SystemTests/IORequestTests.swift +++ b/Tests/SystemTests/IORequestTests.swift @@ -1,4 +1,4 @@ -#if compiler(>=6.2) +#if compiler(>=6.2) && $Lifetimes #if os(Linux) import XCTest @@ -30,5 +30,5 @@ final class IORequestTests: XCTestCase { XCTAssertEqual(sourceBytes, .init(repeating: 0, count: 64)) } } -#endif -#endif +#endif // os(Linux) +#endif // compiler(>=6.2) && $Lifetimes diff --git a/Tests/SystemTests/IORingTests.swift b/Tests/SystemTests/IORingTests.swift index 8e0e8fa6..c5d54d41 100644 --- a/Tests/SystemTests/IORingTests.swift +++ b/Tests/SystemTests/IORingTests.swift @@ -1,4 +1,4 @@ -#if compiler(>=6.2) +#if compiler(>=6.2) && $Lifetimes #if os(Linux) import XCTest @@ -128,5 +128,5 @@ final class IORingTests: XCTestCase { rawBuffer.deallocate() } } -#endif -#endif +#endif // os(Linux) +#endif // compiler(>=6.2) && $Lifetimes From 2610d66a5274c40b42374f66ad63340f11403320 Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Wed, 30 Jul 2025 14:28:58 -0700 Subject: [PATCH 390/427] This package requires Swift 5.9 Accordingly, these conditions would always be true --- Sources/System/MachPort.swift | 2 +- Tests/SystemTests/MachPortTests.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/System/MachPort.swift b/Sources/System/MachPort.swift index 8cc05096..b566895b 100644 --- a/Sources/System/MachPort.swift +++ b/Sources/System/MachPort.swift @@ -7,7 +7,7 @@ See https://swift.org/LICENSE.txt for license information */ -#if swift(>=5.9) && SYSTEM_PACKAGE_DARWIN +#if SYSTEM_PACKAGE_DARWIN import Darwin.Mach diff --git a/Tests/SystemTests/MachPortTests.swift b/Tests/SystemTests/MachPortTests.swift index 073da9a5..6baf50c6 100644 --- a/Tests/SystemTests/MachPortTests.swift +++ b/Tests/SystemTests/MachPortTests.swift @@ -7,7 +7,7 @@ See https://swift.org/LICENSE.txt for license information */ -#if swift(>=5.9) && SYSTEM_PACKAGE_DARWIN +#if SYSTEM_PACKAGE_DARWIN import XCTest import Darwin.Mach From 13ad3fe4aca421b8a18ff872724f916e4ff4d481 Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Wed, 30 Jul 2025 14:29:12 -0700 Subject: [PATCH 391/427] Update copyright dates --- Sources/System/MachPort.swift | 2 +- Tests/SystemTests/MachPortTests.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/System/MachPort.swift b/Sources/System/MachPort.swift index b566895b..452fd5ae 100644 --- a/Sources/System/MachPort.swift +++ b/Sources/System/MachPort.swift @@ -1,7 +1,7 @@ /* This source file is part of the Swift System open source project - Copyright (c) 2022 Apple Inc. and the Swift System project authors + Copyright (c) 2022 - 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 diff --git a/Tests/SystemTests/MachPortTests.swift b/Tests/SystemTests/MachPortTests.swift index 6baf50c6..a1c85a4a 100644 --- a/Tests/SystemTests/MachPortTests.swift +++ b/Tests/SystemTests/MachPortTests.swift @@ -1,7 +1,7 @@ /* This source file is part of the Swift System open source project - Copyright (c) 2022 Apple Inc. and the Swift System project authors + Copyright (c) 2022 - 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 From 9ec41a667cf84d553edb40b44633b94e507ab532 Mon Sep 17 00:00:00 2001 From: Jonathan Flat Date: Wed, 30 Jul 2025 18:47:26 -0600 Subject: [PATCH 392/427] Support testing in release mode --- Tests/SystemTests/FileOperationsTest.swift | 6 ++++-- Tests/SystemTests/FilePathTests/FilePathParsingTest.swift | 2 ++ Tests/SystemTests/FilePathTests/FilePathSyntaxTest.swift | 2 ++ Tests/SystemTests/MockingTest.swift | 2 ++ Tests/SystemTests/TestingInfrastructure.swift | 6 ++++++ 5 files changed, 16 insertions(+), 2 deletions(-) diff --git a/Tests/SystemTests/FileOperationsTest.swift b/Tests/SystemTests/FileOperationsTest.swift index 479e503f..dcffc132 100644 --- a/Tests/SystemTests/FileOperationsTest.swift +++ b/Tests/SystemTests/FileOperationsTest.swift @@ -22,7 +22,7 @@ import CSystem @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 + #if ENABLE_MOCKING && !os(WASI) // Would need to use _getConst funcs from CSystem func testSyscalls() { let fd = FileDescriptor(rawValue: 1) @@ -91,7 +91,7 @@ final class FileOperationsTest: XCTestCase { for test in syscallTestCases { test.runAllTests() } } - #endif // !os(WASI) + #endif // ENABLE_MOCKING && !os(WASI) func testWriteFromEmptyBuffer() throws { #if os(Windows) @@ -215,6 +215,7 @@ final class FileOperationsTest: XCTestCase { } } + #if ENABLE_MOCKING func testGithubIssues() { // https://github.com/apple/swift-system/issues/26 #if os(WASI) @@ -233,6 +234,7 @@ final class FileOperationsTest: XCTestCase { } issue26.runAllTests() } + #endif // ENABLE_MOCKING func testResizeFile() throws { try withTemporaryFilePath(basename: "testResizeFile") { path in diff --git a/Tests/SystemTests/FilePathTests/FilePathParsingTest.swift b/Tests/SystemTests/FilePathTests/FilePathParsingTest.swift index 766bdf8d..b592c08b 100644 --- a/Tests/SystemTests/FilePathTests/FilePathParsingTest.swift +++ b/Tests/SystemTests/FilePathTests/FilePathParsingTest.swift @@ -7,6 +7,7 @@ See https://swift.org/LICENSE.txt for license information */ +#if ENABLE_MOCKING import XCTest #if SYSTEM_PACKAGE @@ -105,3 +106,4 @@ final class FilePathParsingTest: XCTestCase { } } } +#endif // ENABLE_MOCKING diff --git a/Tests/SystemTests/FilePathTests/FilePathSyntaxTest.swift b/Tests/SystemTests/FilePathTests/FilePathSyntaxTest.swift index d23999a2..558a003d 100644 --- a/Tests/SystemTests/FilePathTests/FilePathSyntaxTest.swift +++ b/Tests/SystemTests/FilePathTests/FilePathSyntaxTest.swift @@ -7,6 +7,7 @@ See https://swift.org/LICENSE.txt for license information */ +#if ENABLE_MOCKING import XCTest #if SYSTEM_PACKAGE @@ -1238,3 +1239,4 @@ final class FilePathSyntaxTest: XCTestCase { } } +#endif // ENABLE_MOCKING diff --git a/Tests/SystemTests/MockingTest.swift b/Tests/SystemTests/MockingTest.swift index 1f2c96da..a90125a9 100644 --- a/Tests/SystemTests/MockingTest.swift +++ b/Tests/SystemTests/MockingTest.swift @@ -7,6 +7,7 @@ See https://swift.org/LICENSE.txt for license information */ +#if ENABLE_MOCKING import XCTest #if SYSTEM_PACKAGE @@ -48,3 +49,4 @@ final class MockingTest: XCTestCase { XCTAssertFalse(mockingEnabled) } } +#endif // ENABLE_MOCKING diff --git a/Tests/SystemTests/TestingInfrastructure.swift b/Tests/SystemTests/TestingInfrastructure.swift index b8905fc4..c169a364 100644 --- a/Tests/SystemTests/TestingInfrastructure.swift +++ b/Tests/SystemTests/TestingInfrastructure.swift @@ -17,6 +17,7 @@ import XCTest internal struct Wildcard: Hashable {} +#if ENABLE_MOCKING extension Trace.Entry { /// This implements `==` with wildcard matching. /// (`Entry` cannot conform to `Equatable`/`Hashable` this way because @@ -33,6 +34,7 @@ extension Trace.Entry { return true } } +#endif // ENABLE_MOCKING // To aid debugging, force failures to fatal error internal var forceFatalFailures = false @@ -81,6 +83,7 @@ extension TestCase { fail(message) } } + #if ENABLE_MOCKING func expectMatch( _ expected: Trace.Entry?, _ actual: Trace.Entry?, _ message: String? = nil @@ -102,6 +105,7 @@ extension TestCase { fail(message) } } + #endif // ENABLE_MOCKING func expectNil( _ actual: T?, _ message: String? = nil @@ -142,6 +146,7 @@ extension TestCase { } +#if ENABLE_MOCKING internal struct MockTestCase: TestCase { var file: StaticString var line: UInt @@ -241,6 +246,7 @@ internal struct MockTestCase: TestCase { } } } +#endif // ENABLE_MOCKING internal func withWindowsPaths(enabled: Bool, _ body: () -> ()) { _withWindowsPaths(enabled: enabled, body) From 11d9d4a78e87c2de2af9dbb3fbd8fe3f460a1623 Mon Sep 17 00:00:00 2001 From: Jonathan Flat Date: Fri, 1 Aug 2025 14:51:27 -0600 Subject: [PATCH 393/427] [CI] Enable Linux static SDK and Wasm SDK builds --- .github/workflows/pull_request.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index 35d50227..b72c36a4 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -19,6 +19,8 @@ jobs: {"xcode_version": "16.1"}, ] swift_flags: "-Xbuild-tools-swiftc -DSYSTEM_CI" + enable_linux_static_sdk_build: true + enable_wasm_sdk_build: true build-abi-stable: name: Build ABI Stable From a5f13c77b809bd42844d7cf37fa39aa6e30e66bc Mon Sep 17 00:00:00 2001 From: Jonathan Flat Date: Sun, 3 Aug 2025 10:59:47 -0600 Subject: [PATCH 394/427] Fix typedef errors on platforms with _ASM_GENERIC_INT headers --- Sources/CSystem/include/io_uring.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Sources/CSystem/include/io_uring.h b/Sources/CSystem/include/io_uring.h index d8287b1a..ce618f3a 100644 --- a/Sources/CSystem/include/io_uring.h +++ b/Sources/CSystem/include/io_uring.h @@ -134,11 +134,13 @@ typedef struct __SWIFT_IORING_SQE_FALLBACK_STRUCT swift_io_uring_sqe; #define IORING_FEAT_RW_ATTR (1U << 16) #define IORING_FEAT_NO_IOWAIT (1U << 17) +#if !defined(_ASM_GENERIC_INT_LL64_H) && !defined(_ASM_GENERIC_INT_L64_H) && !defined(_UAPI_ASM_GENERIC_INT_LL64_H) && !defined(_UAPI_ASM_GENERIC_INT_L64_H) typedef uint8_t __u8; typedef uint16_t __u16; typedef uint32_t __u32; typedef uint64_t __u64; typedef int32_t __s32; +#endif #ifndef __kernel_rwf_t typedef int __kernel_rwf_t; From 0ea6743c17f1e6d0f1cbb1c62753262ba64a3217 Mon Sep 17 00:00:00 2001 From: "LamTrinh.Dev" Date: Mon, 11 Aug 2025 00:56:26 +0700 Subject: [PATCH 395/427] Fix spelling errors in some comments. --- Sources/System/IORing/IORequest.swift | 2 +- Sources/System/UtilConsumers.swift | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Sources/System/IORing/IORequest.swift b/Sources/System/IORing/IORequest.swift index 7e433f9f..17be7927 100644 --- a/Sources/System/IORing/IORequest.swift +++ b/Sources/System/IORing/IORequest.swift @@ -320,7 +320,7 @@ extension IORing.Request { * ASYNC_CANCEL flags. * * IORING_ASYNC_CANCEL_ALL Cancel all requests that match the given key - * IORING_ASYNC_CANCEL_FD Key off 'fd' for cancelation rather than the + * IORING_ASYNC_CANCEL_FD Key off 'fd' for cancellation rather than the * request 'user_data' * IORING_ASYNC_CANCEL_ANY Match any request * IORING_ASYNC_CANCEL_FD_FIXED 'fd' passed in is a fixed descriptor diff --git a/Sources/System/UtilConsumers.swift b/Sources/System/UtilConsumers.swift index 183f5f7d..6977543f 100644 --- a/Sources/System/UtilConsumers.swift +++ b/Sources/System/UtilConsumers.swift @@ -52,13 +52,13 @@ extension Slice where Element: Equatable { return self[...idx] } - // If `e` is present, eat up to first occurence of `e` + // If `e` is present, eat up to first occurrence of `e` internal mutating func _eatUntil(_ e: Element) -> Slice? { guard let idx = self.firstIndex(of: e) else { return nil } return _eatUntil(idx) } - // If `e` is present, eat up to and through first occurence of `e` + // If `e` is present, eat up to and through first occurrence of `e` internal mutating func _eatThrough(_ e: Element) -> Slice? { guard let idx = self.firstIndex(of: e) else { return nil } return _eatThrough(idx) From 0610d8f893d5eec935883802bb6f9eb31c2b845b Mon Sep 17 00:00:00 2001 From: Jonathan Flat Date: Tue, 12 Aug 2025 11:44:05 -0600 Subject: [PATCH 396/427] Stat proposal --- NNNN-system-stat.md | 890 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 890 insertions(+) create mode 100644 NNNN-system-stat.md diff --git a/NNNN-system-stat.md b/NNNN-system-stat.md new file mode 100644 index 00000000..a44aa422 --- /dev/null +++ b/NNNN-system-stat.md @@ -0,0 +1,890 @@ +# Stat for Swift System + +* Proposal: [SE-NNNN](NNNN-system-stat.md) +* Authors: [Jonathan Flat](https://github.com/jrflat), [Michael Ilseman](https://github.com/milseman), [Rauhul Varma](https://github.com/rauhul) +* Review Manager: TBD +* Status: **Awaiting review** +* Implementation: [apple/swift-system#256](https://github.com/apple/swift-system/pull/256) +* Review: ([pitch](https://forums.swift.org/)) + +## Introduction + +This proposal introduces a Swift-native `Stat` type to the System library, providing comprehensive access to file metadata on Unix-like platforms through type-safe, platform-aware APIs that wrap the C `stat` types and system calls. + +## Motivation + +Currently, Swift developers who want to work with the file system's lowest level API can only do so through bridged C interfaces. These interfaces lack type safety and require writing non-idiomatic Swift, leading to errors and confusion. + +The goal of the `Stat` type is to provide a faithful and performant Swift wrapper around the underlying C system calls while adding type safety, platform abstraction, and improved discoverability/usability with clear naming. For more on the motivation behind System, see [https://www.swift.org/blog/swift-system](https://www.swift.org/blog/swift-system) + +## Proposed solution + +This proposal adds a `struct Stat` that is available on Unix-like platforms. See discussion on Windows-specific API in **Future Directions**. + +### `Stat` - File Metadata +A Swift wrapper around the C `stat` struct that provides type-safe access to file metadata: + +```swift +// Get file status from path String +let stat = try Stat("/path/to/file") + +// From FileDescriptor +let stat = try fd.stat() + +// From FilePath +let stat = try filePath.stat() + +// `followTargetSymlink: true` (default) behaves like `stat()` +// `followTargetSymlink: false` behaves like `lstat()` +let stat = try symlinkPath.stat(followTargetSymlink: false) + +// Supply flags and optional file descriptor to use the `fstatat()` variant +let stat = try Stat("path/to/file", relativeTo: fd, flags: .symlinkNoFollow) + +print("Size: \(stat.size) bytes") +print("Type: \(stat.type)") // .regular, .directory, .symbolicLink, etc. +print("Permissions: \(stat.permissions)") +print("Modified: \(stat.modificationTime)") + +// Platform-specific information when available +#if canImport(Darwin) || os(FreeBSD) +print("Creation time: \(stat.creationTime)") +#endif +``` + +### Error Handling + +All initializers throw the existing `Errno` type: + +```swift +do { + let stat = try Stat("/nonexistent/file") +} catch Errno.noSuchFileOrDirectory { + print("File not found") +} catch { + print("Other error: \(error)") +} +``` + +These initializers use a typed `throws(Errno)` and require Swift 6.0 or later. + +## Detailed design + +See the **Appendix** section at the end of this proposal for a table view of Swift API to C mappings. + +All API are marked `@_alwaysEmitIntoClient` for performance and back-dating of availability. + +### FileType + +This proposal introduces `FileType` and `FileMode` types to represent `mode_t` values from the C `stat` struct. The type and permissions of a `FileMode` can be modified for convenience, and `FileMode` handles the respective bit masking. + +```swift +/// A file type matching those contained in a C `mode_t`. +/// +/// - Note: Only available on Unix-like platforms. +@frozen +public struct FileType: RawRepresentable, Sendable, Hashable, Codable { + + /// The raw file-type bits from the C mode. + public var rawValue: CInterop.Mode + + /// Creates a strongly-typed file type from the raw C value. + /// + /// - Note: `rawValue` should only contain the mode's file-type bits. Otherwise, + /// use `FileMode(rawValue:)` to get a strongly-typed `FileMode`, then + /// call `.type` to get the properly masked `FileType`. + public init(rawValue: CInterop.Mode) + + /// Directory + /// + /// The corresponding C constant is `S_IFDIR`. + public static var directory: FileType { get } + + /// Character special device + /// + /// The corresponding C constant is `S_IFCHR`. + public static var characterSpecial: FileType { get } + + /// Block special device + /// + /// The corresponding C constant is `S_IFBLK`. + public static var blockSpecial: FileType { get } + + /// Regular file + /// + /// The corresponding C constant is `S_IFREG`. + public static var regular: FileType { get } + + /// FIFO (or pipe) + /// + /// The corresponding C constant is `S_IFIFO`. + public static var pipe: FileType { get } + + /// Symbolic link + /// + /// The corresponding C constant is `S_IFLNK`. + public static var symbolicLink: FileType { get } + + /// Socket + /// + /// The corresponding C constant is `S_IFSOCK`. + public static var socket: FileType { get } + + #if SYSTEM_PACKAGE_DARWIN || os(FreeBSD) + /// Whiteout file + /// + /// The corresponding C constant is `S_IFWHT`. + public static var whiteout: FileType { get } + #endif +} +``` + +### FileMode +```swift +/// A strongly-typed file mode representing a C `mode_t`. +/// +/// - Note: Only available on Unix-like platforms. +@frozen +public struct FileMode: RawRepresentable, Sendable, Hashable, Codable { + + /// The raw C mode. + public var rawValue: CInterop.Mode + + /// Creates a strongly-typed `FileMode` from the raw C value. + public init(rawValue: CInterop.Mode) + + /// Creates a `FileMode` from the given file type and permissions. + /// + /// - Note: This initializer masks the inputs with their respective bit masks. + public init(type: FileType, permissions: FilePermissions) + + /// The file's type, from the mode's file-type bits. + /// + /// Setting this property will mask the `newValue` with the file-type bit mask `S_IFMT`. + public var type: FileType { get set } + + /// The file's permissions, from the mode's permission bits. + /// + /// Setting this property will mask the `newValue` with the permissions bit mask `0o7777`. + public var permissions: FilePermissions { get set } +} +``` + +### Supporting ID Types + +This proposal also uses new `DeviceID`, `UserID`, `GroupID`, and `Inode` types to represent the respective C data types found in `stat`. These are strongly-typed structs instead of `CInterop` typealiases to prevent ambiguity in future System implementations and to allow for added functionality. + +For example, with an implementation of `chown`, a developer might accidentally misplace user and group parameters with no warning if both were a typealias of the underlying `unsigned int`. Furthermore, a strongly-typed `DeviceID` would allow us to add functionality such as a `makedev` function, or `major` and `minor` getters. + +For now, we define the following for use in `Stat`. + +```swift +@frozen +public struct UserID: RawRepresentable, Sendable, Hashable, Codable { + public var rawValue: CInterop.UserID + public init(rawValue: CInterop.UserID) +} + +@frozen +public struct GroupID: RawRepresentable, Sendable, Hashable, Codable { + public var rawValue: CInterop.GroupID + public init(rawValue: CInterop.GroupID) +} + +@frozen +public struct DeviceID: RawRepresentable, Sendable, Hashable, Codable { + public var rawValue: CInterop.DeviceID + public init(rawValue: CInterop.DeviceID) +} + +@frozen +public struct Inode: RawRepresentable, Sendable, Hashable, Codable { + public var rawValue: CInterop.Inode + public init(rawValue: CInterop.Inode) +} +``` + +Each type stores a `CInterop` typealias to ensure an appropriate `rawValue` for the current platform. Added functionality is outside the scope of this proposal and will be included in a future proposal. + +### FileFlags + +A new `FileFlags` type represents file-specific flags found in a `stat` struct on Darwin, FreeBSD, and OpenBSD. This type would also be useful for an implementation of `chflags()`. + +```swift +/// File-specific flags found in the `st_flags` property of a `stat` struct +/// or used as input to `chflags()`. +/// +/// - Note: Only available on Darwin, FreeBSD, and OpenBSD. +@frozen +public struct FileFlags: OptionSet, Sendable, Hashable, Codable { + + /// The raw C flags. + public let rawValue: CInterop.FileFlags + + /// Creates a strongly-typed `FileFlags` from the raw C value. + public init(rawValue: CInterop.FileFlags) + + // MARK: Flags Available on Darwin, FreeBSD, and OpenBSD + + /// Do not dump the file during backups. + /// + /// The corresponding C constant is `UF_NODUMP`. + /// - Note: This flag may be changed by the file owner or superuser. + public static var noDump: FileFlags { get } + + /// File may not be changed. + /// + /// The corresponding C constant is `UF_IMMUTABLE`. + /// - Note: This flag may be changed by the file owner or superuser. + public static var userImmutable: FileFlags { get } + + /// Writes to the file may only append. + /// + /// The corresponding C constant is `UF_APPEND`. + /// - Note: This flag may be changed by the file owner or superuser. + public static var userAppend: FileFlags { get } + + /// File has been archived. + /// + /// The corresponding C constant is `SF_ARCHIVED`. + /// - Note: This flag may only be changed by the superuser. + public static var archived: FileFlags { get } + + /// File may not be changed. + /// + /// The corresponding C constant is `SF_IMMUTABLE`. + /// - Note: This flag may only be changed by the superuser. + public static var systemImmutable: FileFlags { get } + + /// Writes to the file may only append. + /// + /// The corresponding C constant is `SF_APPEND`. + /// - Note: This flag may only be changed by the superuser. + public static var systemAppend: FileFlags { get } + + // MARK: Flags Available on Darwin and FreeBSD + + #if SYSTEM_PACKAGE_DARWIN || os(FreeBSD) + /// Directory is opaque when viewed through a union mount. + /// + /// The corresponding C constant is `UF_OPAQUE`. + /// - Note: This flag may be changed by the file owner or superuser. + public static var opaque: FileFlags { get } + + /// File is compressed at the file system level. + /// + /// The corresponding C constant is `UF_COMPRESSED`. + /// - Note: This flag is read-only. Attempting to change it will result in undefined behavior. + public static var compressed: FileFlags { get } + + /// File is tracked for the purpose of document IDs. + /// + /// The corresponding C constant is `UF_TRACKED`. + /// - Note: This flag may be changed by the file owner or superuser. + public static var tracked: FileFlags { get } + + /// File should not be displayed in a GUI. + /// + /// The corresponding C constant is `UF_HIDDEN`. + /// - Note: This flag may be changed by the file owner or superuser. + public static var hidden: FileFlags { get } + + /// File requires an entitlement for writing. + /// + /// The corresponding C constant is `SF_RESTRICTED`. + /// - Note: This flag may only be changed by the superuser. + public static var restricted: FileFlags { get } + + /// File may not be removed or renamed. + /// + /// The corresponding C constant is `SF_NOUNLINK`. + /// - Note: This flag may only be changed by the superuser. + public static var systemNoUnlink: FileFlags { get } + #endif + + // MARK: Flags Available on Darwin only + + #if SYSTEM_PACKAGE_DARWIN + /// File requires an entitlement for reading and writing. + /// + /// The corresponding C constant is `UF_DATAVAULT`. + /// - Note: This flag may be changed by the file owner or superuser. + public static var dataVault: FileFlags { get } + + /// File is a firmlink. + /// + /// Firmlinks are used by macOS to create transparent links between + /// the read-only system volume and writable data volume. For example, + /// the `/Applications` folder on the system volume is a firmlink to + /// the `/Applications` folder on the data volume, allowing the user + /// to see both system- and user-installed applications in a single folder. + /// + /// The corresponding C constant is `SF_FIRMLINK`. + /// - Note: This flag may only be changed by the superuser. + public static var firmlink: FileFlags { get } + + /// File is a dataless placeholder (content is stored remotely). + /// + /// The system will attempt to materialize the file when accessed according to + /// the dataless file materialization policy of the accessing thread or process. + /// See `getiopolicy_np(3)`. + /// + /// The corresponding C constant is `SF_DATALESS`. + /// - Note: This flag is read-only. Attempting to change it will result in undefined behavior. + public static var dataless: FileFlags { get } + #endif + + // MARK: Flags Available on FreeBSD Only + + #if os(FreeBSD) + /// File may not be removed or renamed. + /// + /// The corresponding C constant is `UF_NOUNLINK`. + /// - Note: This flag may be changed by the file owner or superuser. + public static var userNoUnlink: FileFlags { get } + + /// File has the Windows offline attribute. + /// + /// File systems may use this flag for compatibility with the Windows `FILE_ATTRIBUTE_OFFLINE` attribute, + /// but otherwise provide no special handling when it's set. + /// + /// The corresponding C constant is `UF_OFFLINE`. + /// - Note: This flag may be changed by the file owner or superuser. + public static var offline: FileFlags { get } + + /// File is read-only. + /// + /// File systems may use this flag for compatibility with the Windows `FILE_ATTRIBUTE_READONLY` attribute. + /// + /// The corresponding C constant is `UF_READONLY`. + /// - Note: This flag may be changed by the file owner or superuser. + public static var readOnly: FileFlags { get } + + /// File contains a Windows reparse point. + /// + /// File systems may use this flag for compatibility with the Windows `FILE_ATTRIBUTE_REPARSE_POINT` attribute. + /// + /// The corresponding C constant is `UF_REPARSE`. + /// - Note: This flag may be changed by the file owner or superuser. + public static var reparse: FileFlags { get } + + /// File is sparse. + /// + /// File systems may use this flag for compatibility with the Windows `FILE_ATTRIBUTE_SPARSE_FILE` attribute, + /// or to indicate a sparse file. + /// + /// The corresponding C constant is `UF_SPARSE`. + /// - Note: This flag may be changed by the file owner or superuser. + public static var sparse: FileFlags { get } + + /// File has the Windows system attribute. + /// + /// File systems may use this flag for compatibility with the Windows `FILE_ATTRIBUTE_SYSTEM` attribute, + /// but otherwise provide no special handling when it's set. + /// + /// The corresponding C constant is `UF_SYSTEM`. + /// - Note: This flag may be changed by the file owner or superuser. + public static var system: FileFlags { get } + + /// File is a snapshot. + /// + /// The corresponding C constant is `SF_SNAPSHOT`. + /// - Note: This flag may only be changed by the superuser. + public static var snapshot: FileFlags { get } + #endif +} +``` + +### Stat + +`Stat` can be initialized from a `FilePath`, `UnsafePointer`, or `FileDescriptor`. This proposal also includes functions on `FileDescriptor` and `FilePath` for creating a `Stat` object, seen in the section below. + +The initializer accepting a `FileDescriptor` corresponds to `fstat()`. If the file descriptor points to a symlink, this will return information about the symlink itself. + +In the non-`FileDescriptor` case, one form of the initializer takes a `followTargetSymlink: Bool = true` parameter. The default `true` corresponds to `stat()` and will follow a symlink at the end of the path. Setting `followTargetSymlink: false` corresponds to `lstat()` and will return information about the symlink itself. + +The other form of the initializer receives a path, which can be optionally resolved against a given file descriptor, and a set of `Stat.Flags`. These APIs correspond to the `fstatat()` system call and use a default file descriptor of `AT_FDCWD` if one isn't supplied. + +```swift +/// A Swift wrapper of the C `stat` struct. +/// +/// - Note: Only available on Unix-like platforms. +@frozen +public struct Stat: RawRepresentable, Sendable { + + /// The raw C `stat` struct. + public var rawValue: CInterop.Stat + + /// Creates a Swift `Stat` from the raw C struct. + public init(rawValue: CInterop.Stat) + + // MARK: Stat.Flags + + /// Flags representing those passed to `fstatat()`. + @frozen + public struct Flags: OptionSet, Sendable, Hashable, Codable { + + /// The raw C flags. + public let rawValue: CInt + + /// Creates a strongly-typed `Stat.Flags` from raw C flags. + public init(rawValue: CInt) + + /// If the path ends with a symbolic link, return information about the link itself. + /// + /// The corresponding C constant is `AT_SYMLINK_NOFOLLOW`. + public static var symlinkNoFollow: Flags { get } + + #if SYSTEM_PACKAGE_DARWIN + /// If the path ends with a symbolic link, return information about the link itself. + /// If _any_ symbolic link is encountered during path resolution, return an error. + /// + /// The corresponding C constant is `AT_SYMLINK_NOFOLLOW_ANY`. + /// - Note: Only available on Darwin. + public static var symlinkNoFollowAny: Flags { get } + #endif + + #if SYSTEM_PACKAGE_DARWIN || os(FreeBSD) + /// If the path does not reside in the hierarchy beneath the starting directory, return an error. + /// + /// The corresponding C constant is `AT_RESOLVE_BENEATH`. + /// - Note: Only available on Darwin and FreeBSD. + public static var resolveBeneath: Flags { get } + #endif + + #if os(FreeBSD) || os(Linux) || os(Android) + /// If the path is an empty string (or `NULL` since Linux 6.11), + /// return information about the given file descriptor. + /// + /// The corresponding C constant is `AT_EMPTY_PATH`. + /// - Note: Only available on FreeBSD, Linux, and Android. + public static var emptyPath: Flags { get } + #endif + } + + // MARK: Initializers + + /// Creates a `Stat` struct from a `FilePath`. + /// + /// `followTargetSymlink` determines the behavior if `path` ends with a symbolic link. + /// By default, `followTargetSymlink` is `true` and this initializer behaves like `stat()`. + /// If `followTargetSymlink` is set to `false`, this initializer behaves like `lstat()` and + /// returns information about the symlink itself. + /// + /// The corresponding C function is `stat()` or `lstat()` as described above. + public init( + _ path: FilePath, + followTargetSymlink: Bool = true, + retryOnInterrupt: Bool = true + ) throws(Errno) + + /// Creates a `Stat` struct from an`UnsafePointer` path. + /// + /// `followTargetSymlink` determines the behavior if `path` ends with a symbolic link. + /// By default, `followTargetSymlink` is `true` and this initializer behaves like `stat()`. + /// If `followTargetSymlink` is set to `false`, this initializer behaves like `lstat()` and + /// returns information about the symlink itself. + /// + /// The corresponding C function is `stat()` or `lstat()` as described above. + public init( + _ path: UnsafePointer, + followTargetSymlink: Bool = true, + retryOnInterrupt: Bool = true + ) throws(Errno) + + /// Creates a `Stat` struct from a `FileDescriptor`. + /// + /// The corresponding C function is `fstat()`. + public init( + _ fd: FileDescriptor, + retryOnInterrupt: Bool = true + ) throws(Errno) + + /// Creates a `Stat` struct from a `FilePath` and `Flags`. + /// + /// If `path` is relative, it is resolved against the current working directory. + /// + /// The corresponding C function is `fstatat()`. + public init( + _ path: FilePath, + flags: Stat.Flags, + retryOnInterrupt: Bool = true + ) throws(Errno) + + /// Creates a `Stat` struct from a `FilePath` and `Flags`, + /// including a `FileDescriptor` to resolve a relative path. + /// + /// If `path` is absolute (starts with a forward slash), then `fd` is ignored. + /// If `path` is relative, it is resolved against the directory given by `fd`. + /// + /// The corresponding C function is `fstatat()`. + public init( + _ path: FilePath, + relativeTo fd: FileDescriptor, + flags: Stat.Flags, + retryOnInterrupt: Bool = true + ) throws(Errno) + + /// Creates a `Stat` struct from an `UnsafePointer` path and `Flags`. + /// + /// If `path` is relative, it is resolved against the current working directory. + /// + /// The corresponding C function is `fstatat()`. + public init( + _ path: UnsafePointer, + flags: Stat.Flags, + retryOnInterrupt: Bool = true + ) throws(Errno) + + /// Creates a `Stat` struct from an `UnsafePointer` path and `Flags`, + /// including a `FileDescriptor` to resolve a relative path. + /// + /// If `path` is absolute (starts with a forward slash), then `fd` is ignored. + /// If `path` is relative, it is resolved against the directory given by `fd`. + /// + /// The corresponding C function is `fstatat()`. + public init( + _ path: UnsafePointer, + relativeTo fd: FileDescriptor, + flags: Stat.Flags, + retryOnInterrupt: Bool = true + ) throws(Errno) + + // MARK: Properties + + /// ID of device containing file + /// + /// The corresponding C property is `st_dev`. + public var deviceID: DeviceID { get set } + + /// Inode number + /// + /// The corresponding C property is `st_ino`. + public var inode: Inode { get set } + + /// File mode + /// + /// The corresponding C property is `st_mode`. + public var mode: FileMode { get set } + + /// File type for the given mode + public var type: FileType { get set } + + /// File permissions for the given mode + public var permissions: FilePermissions { get set } + + /// Number of hard links + /// + /// The corresponding C property is `st_nlink`. + public var linkCount: Int { get set } + + /// User ID of owner + /// + /// The corresponding C property is `st_uid`. + public var userID: UserID { get set } + + /// Group ID of owner + /// + /// The corresponding C property is `st_gid`. + public var groupID: GroupID { get set } + + /// Device ID (if special file) + /// + /// For character or block special files, the returned `DeviceID` may have + /// meaningful `.major` and `.minor` values. For non-special files, this + /// property is usually meaningless and often set to 0. + /// + /// The corresponding C property is `st_rdev`. + public var specialDeviceID: DeviceID { get set } + + /// Total size, in bytes + /// + /// The corresponding C property is `st_size`. + public var size: Int64 { get set } + + /// Block size for filesystem I/O, in bytes + /// + /// The corresponding C property is `st_blksize`. + public var preferredIOBlockSize: Int { get set } + + /// Number of 512-byte blocks allocated + /// + /// The corresponding C property is `st_blocks`. + public var blocksAllocated: Int64 { get set } + + /// Total size allocated, in bytes + /// + /// - Note: Calculated as `512 * blocksAllocated`. + public var sizeAllocated: Int64 { get } + + /// Time of last access, given as a `UTCClock.Instant` + /// + /// The corresponding C property is `st_atim` (or `st_atimespec` on Darwin). + @available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *) + public var accessTime: UTCClock.Instant { get set } + + /// Time of last modification, given as a `UTCClock.Instant` + /// + /// The corresponding C property is `st_mtim` (or `st_mtimespec` on Darwin). + @available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *) + public var modificationTime: UTCClock.Instant { get set } + + /// Time of last status (inode) change, given as a `UTCClock.Instant` + /// + /// The corresponding C property is `st_ctim` (or `st_ctimespec` on Darwin). + @available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *) + public var changeTime: UTCClock.Instant { get set } + + #if SYSTEM_PACKAGE_DARWIN || os(FreeBSD) + /// Time of file creation, given as a `UTCClock.Instant` + /// + /// The corresponding C property is `st_birthtim` (or `st_birthtimespec` on Darwin). + /// - Note: Only available on Darwin and FreeBSD. + @available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *) + public var creationTime: UTCClock.Instant { get set } + #endif + + #if SYSTEM_PACKAGE_DARWIN || os(FreeBSD) || os(OpenBSD) + /// File flags + /// + /// The corresponding C property is `st_flags`. + /// - Note: Only available on Darwin, FreeBSD, and OpenBSD. + public var flags: FileFlags { get set } + + /// File generation number + /// + /// The file generation number is used to distinguish between different files + /// that have used the same inode over time. + /// + /// The corresponding C property is `st_gen`. + /// - Note: Only available on Darwin, FreeBSD, and OpenBSD. + public var generationNumber: Int { get set } + #endif +} + +// MARK: - Equatable and Hashable + +extension Stat: Equatable { + /// Compares the raw bytes of two `Stat` structs for equality. + public static func == (lhs: Self, rhs: Self) -> Bool +} + +extension Stat: Hashable { + /// Hashes the raw bytes of this `Stat` struct. + public func hash(into hasher: inout Hasher) +} +``` + +### FileDescriptor and FilePath Extensions + +```swift +extension FileDescriptor { + + /// Creates a `Stat` struct for the file referenced by this `FileDescriptor`. + /// + /// The corresponding C function is `fstat()`. + public func stat( + retryOnInterrupt: Bool = true + ) throws(Errno) -> Stat +} + +extension FilePath { + + /// Creates a `Stat` struct for the file referenced by this `FilePath`. + /// + /// `followTargetSymlink` determines the behavior if `path` ends with a symbolic link. + /// By default, `followTargetSymlink` is `true` and this initializer behaves like `stat()`. + /// If `followTargetSymlink` is set to `false`, this initializer behaves like `lstat()` and + /// returns information about the symlink itself. + /// + /// The corresponding C function is `stat()` or `lstat()` as described above. + public func stat( + followTargetSymlink: Bool = true, + retryOnInterrupt: Bool = true + ) throws(Errno) -> Stat + + /// Creates a `Stat` struct for the file referenced by this`FilePath` using the given `Flags`. + /// + /// If `path` is relative, it is resolved against the current working directory. + /// + /// The corresponding C function is `fstatat()`. + public func stat( + flags: Stat.Flags, + retryOnInterrupt: Bool = true + ) throws(Errno) -> Stat + + /// Creates a `Stat` struct for the file referenced by this`FilePath` using the given `Flags`, + /// including a `FileDescriptor` to resolve a relative path. + /// + /// If `path` is absolute (starts with a forward slash), then `fd` is ignored. + /// If `path` is relative, it is resolved against the directory given by `fd`. + /// + /// The corresponding C function is `fstatat()`. + public func stat( + relativeTo fd: FileDescriptor, + flags: Stat.Flags, + retryOnInterrupt: Bool = true + ) throws(Errno) -> Stat +} +``` + +### CInterop Extensions + +This proposal extends the existing `CInterop` namespace with platform-appropriate typealiases for the underlying C types. These typealiases are used as the `rawValue` for their strongly-typed representations. + +```swift +extension CInterop { + public typealias Stat + public typealias Inode + public typealias UserID + public typealias GroupID + public typealias DeviceID + public typealias FileFlags +} +``` + +## Source compatibility + +This proposal is additive and source-compatible with existing code. + +## ABI compatibility + +This proposal is additive and ABI-compatible with existing code. + +## Implications on adoption + +This feature can be freely adopted and un-adopted in source code with no deployment constraints and without affecting source or ABI compatibility. + +## Future directions + +To remain faithful to the underlying system calls, we don't anticipate extending `Stat`. However, the types introduced in this proposal could serve as the foundation of broader file system APIs in Swift. + +While this proposal does not include `Stat` on Windows, a separate proposal should provide Swift-native wrappers of idiomatic `GetFileInformation` functions with their associated types. + +A more general `FileInfo` API could then build on these OS-specific types to provide an ergonomic, cross-platform abstraction for file metadata. These future cross-platform APIs might be better implemented outside of System, such as in Foundation, the standard library, or somewhere in between. They could provide additional information or conveniences, such as reading and modifying extended attributes or setting file timestamps. + +In the future, more functionality could be added to types such as `DeviceID`. + +## Alternatives considered + +### `FileInfo` as the lowest-level type +An alternative approach could be to have a more general `FileInfo` type be the lowest level of abstraction provided by the System library. This type would then handle all the `stat` or Windows-specific struct storage and accessors. However, this alternative: + +- Is inconsistent with System's philosophy of providing low-level system abstractions. +- Introduces an even larger number of system-specific APIs on each type. +- Misses out on the familiarity of the `stat` name. Developers know what to look for and what to expect from this type. + +### Single combined type for both file and file system metadata +Combining `Stat` and `StatFS` (separate proposal) into a single type was considered but rejected because file and file system information serve different purposes and are typically needed in different contexts. Storing and/or initializing both `stat` and `statfs` structs unnecessarily reduces performance when one isn't needed. + +### Making `Stat` available on Windows +It's possible to make `Stat` available on Windows and use either the non-native `_stat` functions from CRT or populate the information via a separate `GetFileInformation` call. However, many of the `stat` fields are not- or less-applicable on Windows and are treated as such by `_stat`. For instance, `st_uid` is always zero on Windows, `st_ino` has no meaning in FAT, HPFS, or NTFS file systems, and `st_mode` can only specify a regular file, directory, or character special, with the executable bit depending entirely on the file's path extension. + +Rather than forcing Windows file metadata semantics into a cross-platform `Stat` type, we should instead create Windows-specific types that give developers full access to platform-native file metadata. Combined with a higher-level `FileInfo` type that _is_ cross-platform, this gives the best of both low-level and platform-agnostic APIs. + +### Only have `FilePath` and `FileDescriptor` extensions rather than initializers that accept these types +While having `.stat()` functions on `FilePath` and `FileDescriptor` is preferred for ergonomics and function chaining, this technique might lack the discoverability of having an initializer on `Stat` directly. This proposal therefore includes both the initializers and extensions. + +### Types for time properties + +`UTCClock.Instant` was chosen over alternatives such as `Duration` or a new `Timespec` type to provide a comparable instant in time rather than a duration since the Epoch. This would depend on lowering `UTCClock` to System or the standard library, which could be discussed in a separate pitch or proposal. + +### Type names + +`Stat` was chosen over alternatives like `FileStat` or `FileStatus` for its brevity and likeness to the "stat" system call. Unlike generic names such as `FileInfo` or `FileMetadata`, `Stat` emphasizes the platform-specific nature of this type. + +`Inode` was similarly chosen over alternatives like `FileIndex` or `FileID` to emphasize the platform-specific nature. `IndexNode` is a bit verbose, and despite its etymology, "inode" is now ubiquitous and understood as a single word, making the capitalization `Inode` preferable to `INode`. + + +## Acknowledgments + +These new APIs build on excellent types currently available in the System library. + +## Appendix + +### Swift API to C Mappings + +The following tables show the mapping between Swift APIs and their underlying C system calls across different operating systems: + +#### `Stat` Initializer Mappings + +The `retryOnInterrupt: Bool = true` parameter is omitted for clarity. + +| Swift API | Unix-like Platforms | +|-----------|---------------------| +| `Stat(_ path: FilePath, followTargetSymlink: true)` | `stat()` | +| `Stat(_ path: UnsafePointer, followTargetSymlink: true)` | `stat()` | +|| +| `Stat(_ path: FilePath, followTargetSymlink: false)` | `lstat()` | +| `Stat(_ path: UnsafePointer, followTargetSymlink: false)` | `lstat()` | +|| +| `Stat(_ path: FilePath, relativeTo: FileDescriptor, flags: Stat.Flags)` | `fstatat()` | +| `Stat(_ path: UnsafePointer, relativeTo: FileDescriptor, flags: Stat.Flags)` | `fstatat()` | +|| +| `Stat(_ fd: FileDescriptor)` | `fstat()` | +| `FileDescriptor.stat()` | `fstat()` | +|| +| `FilePath.stat(followTargetSymlink: true)` | `stat()` | +| `FilePath.stat(followTargetSymlink: false)` | `lstat()` | +| `FilePath.stat(relativeTo: FileDescriptor, flags: Stat.Flags)` | `fstatat()` | + +#### `Stat` Property Mappings + +`"` denotes the same property name across all operating systems. + +| Swift Property | Darwin | FreeBSD | OpenBSD | Linux | Android | WASI | +|----------------|--------|---------|---------|-------|---------|------| +| `deviceID` | `st_dev` | " | " | " | " | " | +| `inode` | `st_ino` | " | " | " | " | " | +| `mode` | `st_mode` | " | " | " | " | " | +| `linkCount` | `st_nlink` | " | " | " | " | " | +| `userID` | `st_uid` | " | " | " | " | " | +| `groupID` | `st_gid` | " | " | " | " | " | +| `specialDeviceID` | `st_rdev` | " | " | " | " | " | +| `size` | `st_size` | " | " | " | " | " | +| `preferredIOBlockSize` | `st_blksize` | " | " | " | " | " | +| `blocksAllocated` | `st_blocks` | " | " | " | " | " | +| `accessTime` | `st_atimespec` | `st_atim` | `st_atim` | `st_atim` | `st_atim` | `st_atim` | +| `modificationTime` | `st_mtimespec` | `st_mtim` | `st_mtim` | `st_mtim` | `st_mtim` | `st_mtim` | +| `changeTime` | `st_ctimespec` | `st_ctim` | `st_ctim` | `st_ctim` | `st_ctim` | `st_ctim` | +| `creationTime` | `st_birthtimespec` | `st_birthtim` | N/A | N/A | N/A | N/A | +| `flags` | `st_flags` | `st_flags` | `st_flags` | N/A | N/A | N/A | +| `generationNumber` | `st_gen` | `st_gen` | `st_gen` | N/A | N/A | N/A | + +#### `Stat.Flags` Mappings + +| Swift Flag | Darwin | FreeBSD | OpenBSD | Linux | Android | WASI | +|------------|--------|---------|---------|-------|---------|------| +| `symlinkNoFollow` | `AT_SYMLINK_NOFOLLOW` | `AT_SYMLINK_NOFOLLOW` | `AT_SYMLINK_NOFOLLOW` | `AT_SYMLINK_NOFOLLOW` | `AT_SYMLINK_NOFOLLOW` | `AT_SYMLINK_NOFOLLOW` | +| `symlinkNoFollowAny` | `AT_SYMLINK_NOFOLLOW_ANY` | N/A | N/A | N/A | N/A | N/A | +| `resolveBeneath` | `AT_RESOLVE_BENEATH` | `AT_RESOLVE_BENEATH` | N/A | N/A | N/A | N/A | +| `emptyPath` | N/A | `AT_EMPTY_PATH` | N/A | `AT_EMPTY_PATH` | `AT_EMPTY_PATH` | N/A | + +#### `FileFlags` Mappings + +**Note:** `FileFlags` is only available on Darwin, FreeBSD, and OpenBSD. + +| Swift Flag | Darwin | FreeBSD | OpenBSD | +|------------|--------|---------|---------| +| `noDump` | `UF_NODUMP` | `UF_NODUMP` | `UF_NODUMP` | +| `userImmutable` | `UF_IMMUTABLE` | `UF_IMMUTABLE` | `UF_IMMUTABLE` | +| `userAppend` | `UF_APPEND` | `UF_APPEND` | `UF_APPEND` | +| `archived` | `SF_ARCHIVED` | `SF_ARCHIVED` | `SF_ARCHIVED` | +| `systemImmutable` | `SF_IMMUTABLE` | `SF_IMMUTABLE` | `SF_IMMUTABLE` | +| `systemAppend` | `SF_APPEND` | `SF_APPEND` | `SF_APPEND` | +| `opaque` | `UF_OPAQUE` | `UF_OPAQUE` | N/A | +| `compressed` | `UF_COMPRESSED` | `UF_COMPRESSED` | N/A | +| `tracked` | `UF_TRACKED` | `UF_TRACKED` | N/A | +| `hidden` | `UF_HIDDEN` | `UF_HIDDEN` | N/A | +| `restricted` | `SF_RESTRICTED` | `SF_RESTRICTED` | N/A | +| `systemNoUnlink` | `SF_NOUNLINK` | `SF_NOUNLINK` | N/A | +| `dataVault` | `UF_DATAVAULT` | N/A | N/A | +| `firmlink` | `SF_FIRMLINK` | N/A | N/A | +| `dataless` | `SF_DATALESS` | N/A | N/A | +| `userNoUnlink` | N/A | `UF_NOUNLINK` | N/A | +| `offline` | N/A | `UF_OFFLINE` | N/A | +| `readOnly` | N/A | `UF_READONLY` | N/A | +| `reparse` | N/A | `UF_REPARSE` | N/A | +| `sparse` | N/A | `UF_SPARSE` | N/A | +| `system` | N/A | `UF_SYSTEM` | N/A | +| `snapshot` | N/A | `SF_SNAPSHOT` | N/A | From 0b3b691d0b292bc0037f79db0a2bb858b7d5d942 Mon Sep 17 00:00:00 2001 From: Jonathan Flat Date: Tue, 12 Aug 2025 12:36:17 -0600 Subject: [PATCH 397/427] Update pitch link --- NNNN-system-stat.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NNNN-system-stat.md b/NNNN-system-stat.md index a44aa422..adee11f1 100644 --- a/NNNN-system-stat.md +++ b/NNNN-system-stat.md @@ -5,7 +5,7 @@ * Review Manager: TBD * Status: **Awaiting review** * Implementation: [apple/swift-system#256](https://github.com/apple/swift-system/pull/256) -* Review: ([pitch](https://forums.swift.org/)) +* Review: ([pitch](https://forums.swift.org/t/pitch-stat-types-for-swift-system/81616)) ## Introduction From b2711a872a8f5ae979cc326f75d7548175376663 Mon Sep 17 00:00:00 2001 From: Jake Petroules Date: Fri, 12 Sep 2025 18:57:46 -0700 Subject: [PATCH 398/427] Fix: FileDescriptor.duplicate(as:) returns an invalid file descriptor on Windows FileDescriptor.duplicate(as:) is backed by dup2 on Unix, and _dup2 on Windows. On Unix, dup2 returns its second argument. On Windows, _dup2 instead returns 0 on success and -1 on error. This results in the newly returned FileDescriptor object always containing a '0' file descriptor rather than the newly created file descriptor, in violation of the documented behavior. Account for the platform difference in the syscall wrapper to fix this. Closes #192 --- Sources/System/Internals/WindowsSyscallAdapters.swift | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Sources/System/Internals/WindowsSyscallAdapters.swift b/Sources/System/Internals/WindowsSyscallAdapters.swift index 6437d16a..da5dfb8d 100644 --- a/Sources/System/Internals/WindowsSyscallAdapters.swift +++ b/Sources/System/Internals/WindowsSyscallAdapters.swift @@ -135,7 +135,11 @@ internal func dup(_ fd: Int32) -> Int32 { @inline(__always) internal func dup2(_ fd: Int32, _ fd2: Int32) -> Int32 { - _dup2(fd, fd2) + // _dup2 returns 0 to indicate success. + if _dup2(fd, fd2) == 0 { + return fd2 + } + return -1 } @inline(__always) From b1618475ccf5d901bc91b9253a707630967b5173 Mon Sep 17 00:00:00 2001 From: Jake Petroules Date: Sun, 3 Aug 2025 17:11:41 -0700 Subject: [PATCH 399/427] [1.6] Fix: FileDescriptor.duplicate(as:) returns an invalid file descriptor on Windows FileDescriptor.duplicate(as:) is backed by dup2 on Unix, and _dup2 on Windows. On Unix, dup2 returns its second argument. On Windows, _dup2 instead returns 0 on success and -1 on error. This results in the newly returned FileDescriptor object always containing a '0' file descriptor rather than the newly created file descriptor, in violation of the documented behavior. Account for the platform difference in the syscall wrapper to fix this. Closes #192 (cherry picked from commit b2711a872a8f5ae979cc326f75d7548175376663) --- Sources/System/Internals/WindowsSyscallAdapters.swift | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Sources/System/Internals/WindowsSyscallAdapters.swift b/Sources/System/Internals/WindowsSyscallAdapters.swift index 6437d16a..da5dfb8d 100644 --- a/Sources/System/Internals/WindowsSyscallAdapters.swift +++ b/Sources/System/Internals/WindowsSyscallAdapters.swift @@ -135,7 +135,11 @@ internal func dup(_ fd: Int32) -> Int32 { @inline(__always) internal func dup2(_ fd: Int32, _ fd2: Int32) -> Int32 { - _dup2(fd, fd2) + // _dup2 returns 0 to indicate success. + if _dup2(fd, fd2) == 0 { + return fd2 + } + return -1 } @inline(__always) From b6bd66889b16f64cd94d3270a6da5135476a599c Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Wed, 17 Sep 2025 12:33:32 -0700 Subject: [PATCH 400/427] [workflows] change ubuntus to test against --- .github/workflows/pull_request.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index b72c36a4..ec6f66bc 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -9,7 +9,7 @@ jobs: name: Test uses: swiftlang/github-workflows/.github/workflows/swift_package_test.yml@main with: - linux_os_versions: '["jammy", "focal"]' + linux_os_versions: '["noble", "jammy", "focal"]' enable_macos_checks: true # FIXME: https://github.com/swiftlang/github-workflows/pull/140 # Xcode 16.0 and 16.1 are not actually available From 227ebbbe2d082de79ae08217364ff9a3ff9bc89d Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Wed, 17 Sep 2025 15:40:27 -0700 Subject: [PATCH 401/427] [workflows] declare 2D exclusions --- .github/workflows/pull_request.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index ec6f66bc..4c3a66c6 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -10,6 +10,7 @@ jobs: uses: swiftlang/github-workflows/.github/workflows/swift_package_test.yml@main with: linux_os_versions: '["noble", "jammy", "focal"]' + linux_exclude_swift_versions: '[{"os_version": "focal", "swift_version": "nightly-main"}, {"os_version": "focal", "swift_version": "nightly-6.2"}, {"os_version": "focal", "swift_version": "6.2"}, {"os_version": "noble", "swift_version": "5.9"}, {"os_version": "noble", "swift_version": "5.10"}]' enable_macos_checks: true # FIXME: https://github.com/swiftlang/github-workflows/pull/140 # Xcode 16.0 and 16.1 are not actually available @@ -20,7 +21,9 @@ jobs: ] swift_flags: "-Xbuild-tools-swiftc -DSYSTEM_CI" enable_linux_static_sdk_build: true + linux_static_sdk_exclude_swift_versions: '[{"os_version": "focal", "swift_version": "nightly-main"}, {"os_version": "focal", "swift_version": "nightly-6.2"}, {"os_version": "focal", "swift_version": "6.2"}]' enable_wasm_sdk_build: true + wasm_exclude_swift_versions: '[{"os_version": "focal", "swift_version": "nightly-main"}, {"os_version": "focal", "swift_version": "nightly-6.2"}, {"os_version": "focal", "swift_version": "6.2"}]' build-abi-stable: name: Build ABI Stable From 1c1ad1d3f486667658b28a8f3bd6ae334e6f11ec Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Wed, 17 Sep 2025 12:33:32 -0700 Subject: [PATCH 402/427] [workflows] change ubuntus to test against --- .github/workflows/pull_request.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index 35d50227..a5b2889c 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -9,7 +9,7 @@ jobs: name: Test uses: swiftlang/github-workflows/.github/workflows/swift_package_test.yml@main with: - linux_os_versions: '["jammy", "focal"]' + linux_os_versions: '["noble", "jammy", "focal"]' enable_macos_checks: true # FIXME: https://github.com/swiftlang/github-workflows/pull/140 # Xcode 16.0 and 16.1 are not actually available From 18a52c47292e4c0dcced61dc47ef13b8fe857b81 Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Thu, 18 Sep 2025 10:47:41 -0700 Subject: [PATCH 403/427] [workflows] declare 2D exclusions --- .github/workflows/pull_request.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index a5b2889c..4c3a66c6 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -10,6 +10,7 @@ jobs: uses: swiftlang/github-workflows/.github/workflows/swift_package_test.yml@main with: linux_os_versions: '["noble", "jammy", "focal"]' + linux_exclude_swift_versions: '[{"os_version": "focal", "swift_version": "nightly-main"}, {"os_version": "focal", "swift_version": "nightly-6.2"}, {"os_version": "focal", "swift_version": "6.2"}, {"os_version": "noble", "swift_version": "5.9"}, {"os_version": "noble", "swift_version": "5.10"}]' enable_macos_checks: true # FIXME: https://github.com/swiftlang/github-workflows/pull/140 # Xcode 16.0 and 16.1 are not actually available @@ -19,6 +20,10 @@ jobs: {"xcode_version": "16.1"}, ] swift_flags: "-Xbuild-tools-swiftc -DSYSTEM_CI" + enable_linux_static_sdk_build: true + linux_static_sdk_exclude_swift_versions: '[{"os_version": "focal", "swift_version": "nightly-main"}, {"os_version": "focal", "swift_version": "nightly-6.2"}, {"os_version": "focal", "swift_version": "6.2"}]' + enable_wasm_sdk_build: true + wasm_exclude_swift_versions: '[{"os_version": "focal", "swift_version": "nightly-main"}, {"os_version": "focal", "swift_version": "nightly-6.2"}, {"os_version": "focal", "swift_version": "6.2"}]' build-abi-stable: name: Build ABI Stable From 80587df07f614905e5406123bb592174c0ee5bd2 Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Thu, 18 Sep 2025 16:58:21 -0700 Subject: [PATCH 404/427] Require Swift 6.0 --- Package.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Package.swift b/Package.swift index 35059250..37e259b9 100644 --- a/Package.swift +++ b/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version:5.9 +// swift-tools-version:6.0 //===----------------------------------------------------------------------===// // // This source file is part of the Swift System open source project From 478da393fa0f8b2de63612f4bd076ab97e03daa6 Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Thu, 18 Sep 2025 16:59:28 -0700 Subject: [PATCH 405/427] Use new names for Swift 6 mode --- Sources/System/Internals/Exports.swift | 2 +- Tests/SystemTests/FilePathTests/FilePathComponentsTest.swift | 2 +- Tests/SystemTests/FilePathTests/FilePathSyntaxTest.swift | 2 +- Tests/SystemTests/UtilTests.swift | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Sources/System/Internals/Exports.swift b/Sources/System/Internals/Exports.swift index d18102a2..c7d9944f 100644 --- a/Sources/System/Internals/Exports.swift +++ b/Sources/System/Internals/Exports.swift @@ -140,7 +140,7 @@ extension String { return #else - self.init(validatingUTF8: platformString) + self.init(validatingCString: platformString) #endif } diff --git a/Tests/SystemTests/FilePathTests/FilePathComponentsTest.swift b/Tests/SystemTests/FilePathTests/FilePathComponentsTest.swift index d72d59a2..675b5330 100644 --- a/Tests/SystemTests/FilePathTests/FilePathComponentsTest.swift +++ b/Tests/SystemTests/FilePathTests/FilePathComponentsTest.swift @@ -107,7 +107,7 @@ final class FilePathComponentsTest: XCTestCase { func expect( _ s: String, - _ file: StaticString = #file, + _ file: StaticString = #filePath, _ line: UInt = #line ) { if path == FilePath(s) { return } diff --git a/Tests/SystemTests/FilePathTests/FilePathSyntaxTest.swift b/Tests/SystemTests/FilePathTests/FilePathSyntaxTest.swift index 6e031895..0fdfbc27 100644 --- a/Tests/SystemTests/FilePathTests/FilePathSyntaxTest.swift +++ b/Tests/SystemTests/FilePathTests/FilePathSyntaxTest.swift @@ -850,7 +850,7 @@ final class FilePathSyntaxTest: XCTestCase { func expect( _ s: String, - _ file: StaticString = #file, + _ file: StaticString = #filePath, _ line: UInt = #line ) { if path == FilePath(s) { return } diff --git a/Tests/SystemTests/UtilTests.swift b/Tests/SystemTests/UtilTests.swift index bfd21341..e9523207 100644 --- a/Tests/SystemTests/UtilTests.swift +++ b/Tests/SystemTests/UtilTests.swift @@ -34,7 +34,7 @@ class UtilTests: XCTestCase { func testCStringArray() { func check( _ array: [String], - file: StaticString = #file, + file: StaticString = #filePath, line: UInt = #line ) { array._withCStringArray { carray in From 2ae0c650beceb41f8367ab6eddd9d9b1deb7dfe5 Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Thu, 18 Sep 2025 17:01:26 -0700 Subject: [PATCH 406/427] Make shared state constant as intended. --- Tests/SystemTests/TestingInfrastructure.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/SystemTests/TestingInfrastructure.swift b/Tests/SystemTests/TestingInfrastructure.swift index c169a364..12610ff9 100644 --- a/Tests/SystemTests/TestingInfrastructure.swift +++ b/Tests/SystemTests/TestingInfrastructure.swift @@ -37,7 +37,7 @@ extension Trace.Entry { #endif // ENABLE_MOCKING // To aid debugging, force failures to fatal error -internal var forceFatalFailures = false +internal let forceFatalFailures = false internal protocol TestCase { // TODO: want a source location stack, more fidelity, kinds of stack entries, etc From d8dd95176baf68c43edb3d2c8cbaa0c5d57db0d0 Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Thu, 18 Sep 2025 17:26:43 -0700 Subject: [PATCH 407/427] adjust pull-request workflow --- .github/workflows/pull_request.yml | 44 +++++++++++++++++++----------- 1 file changed, 28 insertions(+), 16 deletions(-) diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index 4c3a66c6..cd2bef9e 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -9,21 +9,39 @@ jobs: name: Test uses: swiftlang/github-workflows/.github/workflows/swift_package_test.yml@main with: + swift_flags: "-Xbuild-tools-swiftc -DSYSTEM_CI" + enable_linux_checks: true linux_os_versions: '["noble", "jammy", "focal"]' - linux_exclude_swift_versions: '[{"os_version": "focal", "swift_version": "nightly-main"}, {"os_version": "focal", "swift_version": "nightly-6.2"}, {"os_version": "focal", "swift_version": "6.2"}, {"os_version": "noble", "swift_version": "5.9"}, {"os_version": "noble", "swift_version": "5.10"}]' + linux_exclude_swift_versions: | + [ + {"swift_version": "5.9"}, + {"swift_version": "5.10"}, + {"os_version": "focal", "swift_version": "nightly-6.2"}, + {"os_version": "focal", "swift_version": "6.2"}, + {"os_version": "focal", "swift_version": "nightly-main"}, + ] enable_macos_checks: true - # FIXME: https://github.com/swiftlang/github-workflows/pull/140 - # Xcode 16.0 and 16.1 are not actually available - macos_exclude_xcode_versions: | + macos_exclude_xcode_versions: '[]' + enable_windows_checks: true + windows_exclude_swift_versions: | [ - {"xcode_version": "16.0"}, - {"xcode_version": "16.1"}, + {"swift_version": "5.9"}, + {"swift_version": "5.10"} ] - swift_flags: "-Xbuild-tools-swiftc -DSYSTEM_CI" enable_linux_static_sdk_build: true - linux_static_sdk_exclude_swift_versions: '[{"os_version": "focal", "swift_version": "nightly-main"}, {"os_version": "focal", "swift_version": "nightly-6.2"}, {"os_version": "focal", "swift_version": "6.2"}]' + linux_static_sdk_exclude_swift_versions: | + [ + {"os_version": "focal", "swift_version": "nightly-6.2"}, + {"os_version": "focal", "swift_version": "6.2"}, + {"os_version": "focal", "swift_version": "nightly-main"}, + ] enable_wasm_sdk_build: true - wasm_exclude_swift_versions: '[{"os_version": "focal", "swift_version": "nightly-main"}, {"os_version": "focal", "swift_version": "nightly-6.2"}, {"os_version": "focal", "swift_version": "6.2"}]' + wasm_exclude_swift_versions: | + [ + {"os_version": "focal", "swift_version": "nightly-6.2"}, + {"os_version": "focal", "swift_version": "6.2"}, + {"os_version": "focal", "swift_version": "nightly-main"}, + ] build-abi-stable: name: Build ABI Stable @@ -34,13 +52,7 @@ jobs: enable_windows_checks: false # Only build macos_build_command: "xcrun swift build --build-tests" - # FIXME: https://github.com/swiftlang/github-workflows/pull/140 - # Xcode 16.0 and 16.1 are not actually available - macos_exclude_xcode_versions: | - [ - {"xcode_version": "16.0"}, - {"xcode_version": "16.1"}, - ] + macos_exclude_xcode_versions: '[]' # Enable availability to match ABI stable verion of system. swift_flags: "-Xbuild-tools-swiftc -DSYSTEM_CI -Xbuild-tools-swiftc -DSYSTEM_ABI_STABLE" From bc4e6e3bea6dc420e9ee071b5309a0765d5fe117 Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Thu, 18 Sep 2025 17:37:49 -0700 Subject: [PATCH 408/427] Use Swift 5 language mode MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `MachPort` is fine with Swift 6.1 and up, but does not compile with Swift 6.0’s Swift 6 language mode. `import CSystem` is a problem in the Linux version (inconsistently imported as implementation-only.) The Wasm implementation seems not concurrency-safe. --- Package.swift | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Package.swift b/Package.swift index 37e259b9..11a43f61 100644 --- a/Package.swift +++ b/Package.swift @@ -139,5 +139,6 @@ let package = Package( exclude: testsToExclude, cSettings: cSettings, swiftSettings: swiftSettings), - ]) - + ], + swiftLanguageVersions: [.v5] +) From 70f197f03691659bf3e4086056a3ac034a43401f Mon Sep 17 00:00:00 2001 From: Jonathan Flat Date: Mon, 29 Sep 2025 11:19:36 -0600 Subject: [PATCH 409/427] Address feedback (v3) --- NNNN-system-stat.md | 202 ++++++++++++++++++++++++++++++++------------ 1 file changed, 147 insertions(+), 55 deletions(-) diff --git a/NNNN-system-stat.md b/NNNN-system-stat.md index adee11f1..f7d8f2cc 100644 --- a/NNNN-system-stat.md +++ b/NNNN-system-stat.md @@ -1,12 +1,18 @@ # Stat for Swift System -* Proposal: [SE-NNNN](NNNN-system-stat.md) +* Proposal: [SYS-0006](NNNN-system-stat.md) * Authors: [Jonathan Flat](https://github.com/jrflat), [Michael Ilseman](https://github.com/milseman), [Rauhul Varma](https://github.com/rauhul) * Review Manager: TBD * Status: **Awaiting review** * Implementation: [apple/swift-system#256](https://github.com/apple/swift-system/pull/256) * Review: ([pitch](https://forums.swift.org/t/pitch-stat-types-for-swift-system/81616)) +#### Revision history + +* **v1** Initial version +* **v2** Moved `UTCClock.Instant` properties to **Future Directions** and exposed C `timespec` properties. Expanded **Alternatives Considered** for `Stat` name and time properties. +* **v3** Add `init(_:)` to wrapper types, clarify `FileType(rawValue:)` behavior with `S_IFMT`, rename `.pipe` to `.fifo`, mention `ALLPERMS` instead of `0o7777`, explain "is"-less flag names in **Alternatives Considered**, fix conditionals for FreeBSD flags, clarify that `.type` and `.permissions` depend on `.mode`, clarify that size property behaviors are file system-dependent. + ## Introduction This proposal introduces a Swift-native `Stat` type to the System library, providing comprehensive access to file metadata on Unix-like platforms through type-safe, platform-aware APIs that wrap the C `stat` types and system calls. @@ -42,6 +48,7 @@ let stat = try symlinkPath.stat(followTargetSymlink: false) let stat = try Stat("path/to/file", relativeTo: fd, flags: .symlinkNoFollow) print("Size: \(stat.size) bytes") +print("Size allocated: \(stat.sizeAllocated) bytes") print("Type: \(stat.type)") // .regular, .directory, .symbolicLink, etc. print("Permissions: \(stat.permissions)") print("Modified: \(stat.modificationTime)") @@ -88,11 +95,17 @@ public struct FileType: RawRepresentable, Sendable, Hashable, Codable { /// The raw file-type bits from the C mode. public var rawValue: CInterop.Mode - /// Creates a strongly-typed file type from the raw C value. - /// - /// - Note: `rawValue` should only contain the mode's file-type bits. Otherwise, - /// use `FileMode(rawValue:)` to get a strongly-typed `FileMode`, then - /// call `.type` to get the properly masked `FileType`. + /// Creates a strongly-typed file type from the raw C `mode_t`. + /// + /// - Note: This initializer stores the `rawValue` directly and **does not** + /// mask the value with `S_IFMT`. If the supplied `rawValue` contains bits + /// outside of the `S_IFMT` mask, the resulting `FileType` will not compare + /// equal to constants like `.directory` and `.symbolicLink`, which may + /// be unexpected. + /// + /// If you're unsure whether the `mode_t` contains bits outside of `S_IFMT`, + /// you can use `FileMode(rawValue:)` instead to get a strongly-typed + /// `FileMode`, then call `.type` to get the properly masked `FileType`. public init(rawValue: CInterop.Mode) /// Directory @@ -115,10 +128,10 @@ public struct FileType: RawRepresentable, Sendable, Hashable, Codable { /// The corresponding C constant is `S_IFREG`. public static var regular: FileType { get } - /// FIFO (or pipe) + /// FIFO (or named pipe) /// /// The corresponding C constant is `S_IFIFO`. - public static var pipe: FileType { get } + public static var fifo: FileType { get } /// Symbolic link /// @@ -152,6 +165,9 @@ public struct FileMode: RawRepresentable, Sendable, Hashable, Codable { /// Creates a strongly-typed `FileMode` from the raw C value. public init(rawValue: CInterop.Mode) + + /// Creates a strongly-typed `FileMode` from the raw C value. + public init(_ rawValue: CInterop.Mode) /// Creates a `FileMode` from the given file type and permissions. /// @@ -165,7 +181,7 @@ public struct FileMode: RawRepresentable, Sendable, Hashable, Codable { /// The file's permissions, from the mode's permission bits. /// - /// Setting this property will mask the `newValue` with the permissions bit mask `0o7777`. + /// Setting this property will mask the `newValue` with the permissions bit mask `ALLPERMS`. public var permissions: FilePermissions { get set } } ``` @@ -183,24 +199,28 @@ For now, we define the following for use in `Stat`. public struct UserID: RawRepresentable, Sendable, Hashable, Codable { public var rawValue: CInterop.UserID public init(rawValue: CInterop.UserID) + public init(_ rawValue: CInterop.UserID) } @frozen public struct GroupID: RawRepresentable, Sendable, Hashable, Codable { public var rawValue: CInterop.GroupID public init(rawValue: CInterop.GroupID) + public init(_ rawValue: CInterop.GroupID) } @frozen public struct DeviceID: RawRepresentable, Sendable, Hashable, Codable { public var rawValue: CInterop.DeviceID public init(rawValue: CInterop.DeviceID) + public init(_ rawValue: CInterop.DeviceID) } @frozen public struct Inode: RawRepresentable, Sendable, Hashable, Codable { public var rawValue: CInterop.Inode public init(rawValue: CInterop.Inode) + public init(_ rawValue: CInterop.Inode) } ``` @@ -271,30 +291,12 @@ public struct FileFlags: OptionSet, Sendable, Hashable, Codable { /// - Note: This flag may be changed by the file owner or superuser. public static var opaque: FileFlags { get } - /// File is compressed at the file system level. - /// - /// The corresponding C constant is `UF_COMPRESSED`. - /// - Note: This flag is read-only. Attempting to change it will result in undefined behavior. - public static var compressed: FileFlags { get } - - /// File is tracked for the purpose of document IDs. - /// - /// The corresponding C constant is `UF_TRACKED`. - /// - Note: This flag may be changed by the file owner or superuser. - public static var tracked: FileFlags { get } - /// File should not be displayed in a GUI. /// /// The corresponding C constant is `UF_HIDDEN`. /// - Note: This flag may be changed by the file owner or superuser. public static var hidden: FileFlags { get } - /// File requires an entitlement for writing. - /// - /// The corresponding C constant is `SF_RESTRICTED`. - /// - Note: This flag may only be changed by the superuser. - public static var restricted: FileFlags { get } - /// File may not be removed or renamed. /// /// The corresponding C constant is `SF_NOUNLINK`. @@ -305,11 +307,29 @@ public struct FileFlags: OptionSet, Sendable, Hashable, Codable { // MARK: Flags Available on Darwin only #if SYSTEM_PACKAGE_DARWIN + /// File is compressed at the file system level. + /// + /// The corresponding C constant is `UF_COMPRESSED`. + /// - Note: This flag is read-only. Attempting to change it will result in undefined behavior. + public static var compressed: FileFlags { get } + + /// File is tracked for the purpose of document IDs. + /// + /// The corresponding C constant is `UF_TRACKED`. + /// - Note: This flag may be changed by the file owner or superuser. + public static var tracked: FileFlags { get } + /// File requires an entitlement for reading and writing. /// /// The corresponding C constant is `UF_DATAVAULT`. /// - Note: This flag may be changed by the file owner or superuser. public static var dataVault: FileFlags { get } + + /// File requires an entitlement for writing. + /// + /// The corresponding C constant is `SF_RESTRICTED`. + /// - Note: This flag may only be changed by the superuser. + public static var restricted: FileFlags { get } /// File is a firmlink. /// @@ -449,6 +469,7 @@ public struct Stat: RawRepresentable, Sendable { /// /// The corresponding C constant is `AT_RESOLVE_BENEATH`. /// - Note: Only available on Darwin and FreeBSD. + @available(macOS 26.0, iOS 26.0, tvOS 26.0, watchOS 26.0, visionOS 26.0, *) public static var resolveBeneath: Flags { get } #endif @@ -478,7 +499,7 @@ public struct Stat: RawRepresentable, Sendable { retryOnInterrupt: Bool = true ) throws(Errno) - /// Creates a `Stat` struct from an`UnsafePointer` path. + /// Creates a `Stat` struct from an `UnsafePointer` path. /// /// `followTargetSymlink` determines the behavior if `path` ends with a symbolic link. /// By default, `followTargetSymlink` is `true` and this initializer behaves like `stat()`. @@ -568,9 +589,15 @@ public struct Stat: RawRepresentable, Sendable { public var mode: FileMode { get set } /// File type for the given mode + /// + /// - Note: This property is equivalent to `mode.type`. Modifying this + /// property will update the underlying `st_mode` accordingly. public var type: FileType { get set } /// File permissions for the given mode + /// + /// - Note: This property is equivalent to `mode.permissions`. Modifying + /// this property will update the underlying `st_mode` accordingly. public var permissions: FilePermissions { get set } /// Number of hard links @@ -599,6 +626,12 @@ public struct Stat: RawRepresentable, Sendable { /// Total size, in bytes /// + /// The semantics of this property are tied to the underlying C `st_size` field, + /// which can have file system-dependent behavior. For example, this property + /// can return different values for a file's data fork and resource fork, and some + /// file systems report logical size rather than actual disk usage for compressed + /// or cloned files. + /// /// The corresponding C property is `st_size`. public var size: Int64 { get set } @@ -609,39 +642,46 @@ public struct Stat: RawRepresentable, Sendable { /// Number of 512-byte blocks allocated /// + /// The semantics of this property are tied to the underlying C `st_blocks` field, + /// which can have file system-dependent behavior. + /// /// The corresponding C property is `st_blocks`. public var blocksAllocated: Int64 { get set } /// Total size allocated, in bytes /// + /// The semantics of this property are tied to the underlying C `st_blocks` field, + /// which can have file system-dependent behavior. + /// /// - Note: Calculated as `512 * blocksAllocated`. public var sizeAllocated: Int64 { get } - - /// Time of last access, given as a `UTCClock.Instant` + + // NOTE: "st_" property names are used for the `timespec` properties so + // we can reserve `accessTime`, `modificationTime`, etc. for potential + // `UTCClock.Instant` properties in the future. + // See Future Directions for more info. + + /// Time of last access, given as a C `timespec` since the Epoch. /// /// The corresponding C property is `st_atim` (or `st_atimespec` on Darwin). - @available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *) - public var accessTime: UTCClock.Instant { get set } - - /// Time of last modification, given as a `UTCClock.Instant` + public var st_atim: timespec { get set } + + /// Time of last modification, given as a C `timespec` since the Epoch. /// /// The corresponding C property is `st_mtim` (or `st_mtimespec` on Darwin). - @available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *) - public var modificationTime: UTCClock.Instant { get set } - - /// Time of last status (inode) change, given as a `UTCClock.Instant` + public var st_mtim: timespec { get set } + + /// Time of last status (inode) change, given as a C `timespec` since the Epoch. /// /// The corresponding C property is `st_ctim` (or `st_ctimespec` on Darwin). - @available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *) - public var changeTime: UTCClock.Instant { get set } + public var st_ctim: timespec { get set } #if SYSTEM_PACKAGE_DARWIN || os(FreeBSD) - /// Time of file creation, given as a `UTCClock.Instant` + /// Time of file creation, given as a C `timespec` since the Epoch. /// /// The corresponding C property is `st_birthtim` (or `st_birthtimespec` on Darwin). /// - Note: Only available on Darwin and FreeBSD. - @available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *) - public var creationTime: UTCClock.Instant { get set } + public var st_birthtim: timespec { get set } #endif #if SYSTEM_PACKAGE_DARWIN || os(FreeBSD) || os(OpenBSD) @@ -703,7 +743,7 @@ extension FilePath { retryOnInterrupt: Bool = true ) throws(Errno) -> Stat - /// Creates a `Stat` struct for the file referenced by this`FilePath` using the given `Flags`. + /// Creates a `Stat` struct for the file referenced by this `FilePath` using the given `Flags`. /// /// If `path` is relative, it is resolved against the current working directory. /// @@ -713,7 +753,7 @@ extension FilePath { retryOnInterrupt: Bool = true ) throws(Errno) -> Stat - /// Creates a `Stat` struct for the file referenced by this`FilePath` using the given `Flags`, + /// Creates a `Stat` struct for the file referenced by this `FilePath` using the given `Flags`, /// including a `FileDescriptor` to resolve a relative path. /// /// If `path` is absolute (starts with a forward slash), then `fd` is ignored. @@ -761,10 +801,43 @@ To remain faithful to the underlying system calls, we don't anticipate extending While this proposal does not include `Stat` on Windows, a separate proposal should provide Swift-native wrappers of idiomatic `GetFileInformation` functions with their associated types. -A more general `FileInfo` API could then build on these OS-specific types to provide an ergonomic, cross-platform abstraction for file metadata. These future cross-platform APIs might be better implemented outside of System, such as in Foundation, the standard library, or somewhere in between. They could provide additional information or conveniences, such as reading and modifying extended attributes or setting file timestamps. +A more general `FileInfo` API could then build on these OS-specific types to provide an ergonomic, cross-platform abstraction for file metadata. These future cross-platform APIs might be better implemented outside of System, such as in Foundation, the standard library, or somewhere in between. They could provide additional information or convenience features, such as reading and modifying extended attributes or setting file timestamps. In the future, more functionality could be added to types such as `DeviceID`. +### Using `UTCClock.Instant` for time properties + +When the `UTCClock` proposal and code destination is finalized, we could use the `UTCClock.Instant` type for `Stat` time properties: + +```swift +extension Stat { + /// Time of last access, given as a `UTCClock.Instant` + /// + /// The corresponding C property is `st_atim` (or `st_atimespec` on Darwin). + public var accessTime: UTCClock.Instant { get set } + + /// Time of last modification, given as a `UTCClock.Instant` + /// + /// The corresponding C property is `st_mtim` (or `st_mtimespec` on Darwin). + public var modificationTime: UTCClock.Instant { get set } + + /// Time of last status (inode) change, given as a `UTCClock.Instant` + /// + /// The corresponding C property is `st_ctim` (or `st_ctimespec` on Darwin). + public var changeTime: UTCClock.Instant { get set } + + #if SYSTEM_PACKAGE_DARWIN || os(FreeBSD) + /// Time of file creation, given as a `UTCClock.Instant` + /// + /// The corresponding C property is `st_birthtim` (or `st_birthtimespec` on Darwin). + /// - Note: Only available on Darwin and FreeBSD. + public var creationTime: UTCClock.Instant { get set } + #endif +} +``` + +We would reserve the more ergonomic `accessTime`, `modificationTime`, etc. names for these future extensions. + ## Alternatives considered ### `FileInfo` as the lowest-level type @@ -787,14 +860,33 @@ While having `.stat()` functions on `FilePath` and `FileDescriptor` is preferred ### Types for time properties -`UTCClock.Instant` was chosen over alternatives such as `Duration` or a new `Timespec` type to provide a comparable instant in time rather than a duration since the Epoch. This would depend on lowering `UTCClock` to System or the standard library, which could be discussed in a separate pitch or proposal. +`UTCClock.Instant` would ideally be chosen over alternatives such as `Duration` or a new `Timespec` type to provide a comparable instant in time rather than a duration since the Epoch. However, this would depend on lowering `UTCClock` to System or the standard library and depends on that separate proposal. + +Exposing a `timespec` property directly also has benefits; it's faithful to the underlying system's type and already has conversion support to/from `Duration` in the standard library. + +Given that `timespec` is not particularly crufty and already has public API supporting its conversions, we decided to expose the raw `timespec` for now under the original C property names (`st_atim`, `st_mtim`, etc.) and reserve more ergonomic names for future extensions. ### Type names `Stat` was chosen over alternatives like `FileStat` or `FileStatus` for its brevity and likeness to the "stat" system call. Unlike generic names such as `FileInfo` or `FileMetadata`, `Stat` emphasizes the platform-specific nature of this type. -`Inode` was similarly chosen over alternatives like `FileIndex` or `FileID` to emphasize the platform-specific nature. `IndexNode` is a bit verbose, and despite its etymology, "inode" is now ubiquitous and understood as a single word, making the capitalization `Inode` preferable to `INode`. +`Stat` and (possible future) `StatFS` names were chosen over `FileStat` and `FileSystemStat`, or a namespaced `File.Stat` and `FileSystem.Stat`, because `Stat` is recognized more as its own concept rather than shorthand for "status." Thus, using `FileSystem.Stat` or `FileSystemStat` for `statfs` in the future might lead to confusion. Also, precedence from other languages' `stat` APIs that use a "file system" namespace might add to this confusion: + +``` +Rust: fs::metadata() -> fs::Metadata +Python: os.stat() -> os.stat_result +Go: os.Stat() -> fs.FileInfo +``` + +`Inode` was chosen over alternatives like `FileIndex` or `FileID` to emphasize the platform-specific nature. `IndexNode` is a bit verbose, and despite its etymology, "inode" is now ubiquitous and understood as a single word, making the capitalization `Inode` preferable to `INode`. + +### `FileFlags` naming conventions + +`FileFlags` property names such as `hidden` and `compressed` could alternatively use an "is" prefix commonly seen in boolean properties to form `.isHidden` and `.isCompressed`. However, we chose to omit the "is" prefix for the following reasons: +- The "is"-less flag names are succinct and closely aligned with the underlying C constants they represent. +- `OptionSet` property names often use an adjective ("hidden") rather than a predicate ("is hidden") when describing a single subject, such as a file. This is likely because "is" does not add to the flow of `flags.contains(.isHidden)` like it does for a direct boolean property, such as `file.isHidden`. +- For both `OptionSet` APIs that describe a single subject and those that describe a collection of elements, there's precedence to omit the "is". Examples of single-subject `OptionSet` APIs include `UIControl.State`, which uses `.highlighted` and `.disabled` rather than `.isHighlighted` and `.isDisabled`, and `FilePermissions`, which uses `.ownerRead` rather than `.isOwnerReadable`. Examples of multi-subject `OptionSet` APIs include `Edge.Set`, which uses `.top` and `.bottom`, and `ShippingOptions` from the `OptionSet` documentation, which uses `.nextDay`, `.priority`, etc. ## Acknowledgments @@ -844,10 +936,10 @@ The `retryOnInterrupt: Bool = true` parameter is omitted for clarity. | `size` | `st_size` | " | " | " | " | " | | `preferredIOBlockSize` | `st_blksize` | " | " | " | " | " | | `blocksAllocated` | `st_blocks` | " | " | " | " | " | -| `accessTime` | `st_atimespec` | `st_atim` | `st_atim` | `st_atim` | `st_atim` | `st_atim` | -| `modificationTime` | `st_mtimespec` | `st_mtim` | `st_mtim` | `st_mtim` | `st_mtim` | `st_mtim` | -| `changeTime` | `st_ctimespec` | `st_ctim` | `st_ctim` | `st_ctim` | `st_ctim` | `st_ctim` | -| `creationTime` | `st_birthtimespec` | `st_birthtim` | N/A | N/A | N/A | N/A | +| `st_atim` | `st_atimespec` | `st_atim` | `st_atim` | `st_atim` | `st_atim` | `st_atim` | +| `st_mtim` | `st_mtimespec` | `st_mtim` | `st_mtim` | `st_mtim` | `st_mtim` | `st_mtim` | +| `st_ctim` | `st_ctimespec` | `st_ctim` | `st_ctim` | `st_ctim` | `st_ctim` | `st_ctim` | +| `st_birthtim` | `st_birthtimespec` | `st_birthtim` | N/A | N/A | N/A | N/A | | `flags` | `st_flags` | `st_flags` | `st_flags` | N/A | N/A | N/A | | `generationNumber` | `st_gen` | `st_gen` | `st_gen` | N/A | N/A | N/A | @@ -873,12 +965,12 @@ The `retryOnInterrupt: Bool = true` parameter is omitted for clarity. | `systemImmutable` | `SF_IMMUTABLE` | `SF_IMMUTABLE` | `SF_IMMUTABLE` | | `systemAppend` | `SF_APPEND` | `SF_APPEND` | `SF_APPEND` | | `opaque` | `UF_OPAQUE` | `UF_OPAQUE` | N/A | -| `compressed` | `UF_COMPRESSED` | `UF_COMPRESSED` | N/A | -| `tracked` | `UF_TRACKED` | `UF_TRACKED` | N/A | | `hidden` | `UF_HIDDEN` | `UF_HIDDEN` | N/A | -| `restricted` | `SF_RESTRICTED` | `SF_RESTRICTED` | N/A | | `systemNoUnlink` | `SF_NOUNLINK` | `SF_NOUNLINK` | N/A | +| `compressed` | `UF_COMPRESSED` | N/A | N/A | +| `tracked` | `UF_TRACKED` | N/A | N/A | | `dataVault` | `UF_DATAVAULT` | N/A | N/A | +| `restricted` | `SF_RESTRICTED` | N/A | N/A | | `firmlink` | `SF_FIRMLINK` | N/A | N/A | | `dataless` | `SF_DATALESS` | N/A | N/A | | `userNoUnlink` | N/A | `UF_NOUNLINK` | N/A | From 2f4c8c216c59c6a1932f7465e81321525cd45e0e Mon Sep 17 00:00:00 2001 From: Jonathan Flat Date: Mon, 29 Sep 2025 11:26:15 -0600 Subject: [PATCH 410/427] Create Proposals directory --- NNNN-system-stat.md => Proposals/0006-system-stat.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename NNNN-system-stat.md => Proposals/0006-system-stat.md (99%) diff --git a/NNNN-system-stat.md b/Proposals/0006-system-stat.md similarity index 99% rename from NNNN-system-stat.md rename to Proposals/0006-system-stat.md index f7d8f2cc..39c1bd3a 100644 --- a/NNNN-system-stat.md +++ b/Proposals/0006-system-stat.md @@ -1,6 +1,6 @@ # Stat for Swift System -* Proposal: [SYS-0006](NNNN-system-stat.md) +* Proposal: [SYS-0006](0006-system-stat.md) * Authors: [Jonathan Flat](https://github.com/jrflat), [Michael Ilseman](https://github.com/milseman), [Rauhul Varma](https://github.com/rauhul) * Review Manager: TBD * Status: **Awaiting review** From 57d515e1d0671a252415e3631f6ac24a55093345 Mon Sep 17 00:00:00 2001 From: Jonathan Flat Date: Tue, 12 Aug 2025 12:09:37 -0600 Subject: [PATCH 411/427] Stat implementation --- Package.swift | 1 + Sources/System/FileSystem/FileFlags.swift | 254 +++++++ Sources/System/FileSystem/FileMode.swift | 54 ++ Sources/System/FileSystem/FileType.swift | 102 +++ Sources/System/FileSystem/Identifiers.swift | 89 +++ Sources/System/FileSystem/Stat.swift | 700 ++++++++++++++++++++ Sources/System/Internals/CInterop.swift | 10 +- Sources/System/Internals/Constants.swift | 152 ++++- Sources/System/Internals/Exports.swift | 40 +- Tests/SystemTests/FileModeTests.swift | 134 ++++ Tests/SystemTests/StatTests.swift | 408 ++++++++++++ 11 files changed, 1935 insertions(+), 9 deletions(-) create mode 100644 Sources/System/FileSystem/FileFlags.swift create mode 100644 Sources/System/FileSystem/FileMode.swift create mode 100644 Sources/System/FileSystem/FileType.swift create mode 100644 Sources/System/FileSystem/Identifiers.swift create mode 100644 Sources/System/FileSystem/Stat.swift create mode 100644 Tests/SystemTests/FileModeTests.swift create mode 100644 Tests/SystemTests/StatTests.swift diff --git a/Package.swift b/Package.swift index 11a43f61..8aba3315 100644 --- a/Package.swift +++ b/Package.swift @@ -87,6 +87,7 @@ let swiftSettings = swiftSettingsAvailability + swiftSettingsCI + [ let cSettings: [CSetting] = [ .define("_CRT_SECURE_NO_WARNINGS", .when(platforms: [.windows])), + .define("_GNU_SOURCE", .when(platforms: [.linux])), ] #if SYSTEM_ABI_STABLE diff --git a/Sources/System/FileSystem/FileFlags.swift b/Sources/System/FileSystem/FileFlags.swift new file mode 100644 index 00000000..f89cbc15 --- /dev/null +++ b/Sources/System/FileSystem/FileFlags.swift @@ -0,0 +1,254 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift System open source project +// +// Copyright (c) 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 +// +//===----------------------------------------------------------------------===// + +// |------------------------| +// | Swift API to C Mapping | +// |------------------------------------------------------------------| +// | FileFlags | Darwin | FreeBSD | OpenBSD | +// |------------------|---------------|---------------|---------------| +// | noDump | UF_NODUMP | UF_NODUMP | UF_NODUMP | +// | userImmutable | UF_IMMUTABLE | UF_IMMUTABLE | UF_IMMUTABLE | +// | userAppend | UF_APPEND | UF_APPEND | UF_APPEND | +// | archived | SF_ARCHIVED | SF_ARCHIVED | SF_ARCHIVED | +// | systemImmutable | SF_IMMUTABLE | SF_IMMUTABLE | SF_IMMUTABLE | +// | systemAppend | SF_APPEND | SF_APPEND | SF_APPEND | +// | opaque | UF_OPAQUE | UF_OPAQUE | N/A | +// | compressed | UF_COMPRESSED | UF_COMPRESSED | N/A | +// | tracked | UF_TRACKED | UF_TRACKED | N/A | +// | hidden | UF_HIDDEN | UF_HIDDEN | N/A | +// | restricted | SF_RESTRICTED | SF_RESTRICTED | N/A | +// | systemNoUnlink | SF_NOUNLINK | SF_NOUNLINK | N/A | +// | dataVault | UF_DATAVAULT | N/A | N/A | +// | firmlink | SF_FIRMLINK | N/A | N/A | +// | dataless | SF_DATALESS | N/A | N/A | +// | userNoUnlink | N/A | UF_NOUNLINK | N/A | +// | offline | N/A | UF_OFFLINE | N/A | +// | readOnly | N/A | UF_READONLY | N/A | +// | reparse | N/A | UF_REPARSE | N/A | +// | sparse | N/A | UF_SPARSE | N/A | +// | system | N/A | UF_SYSTEM | N/A | +// | snapshot | N/A | SF_SNAPSHOT | N/A | +// |------------------|---------------|---------------|---------------| + +#if SYSTEM_PACKAGE_DARWIN || os(FreeBSD) || os(OpenBSD) +// @available(System X.Y.Z, *) +extension CInterop { + public typealias FileFlags = UInt32 +} + +/// File-specific flags found in the `st_flags` property of a `stat` struct +/// or used as input to `chflags()`. +/// +/// - Note: Only available on Darwin, FreeBSD, and OpenBSD. +@frozen +// @available(System X.Y.Z, *) +public struct FileFlags: OptionSet, Sendable, Hashable, Codable { + + /// The raw C flags. + @_alwaysEmitIntoClient + public let rawValue: CInterop.FileFlags + + /// Creates a strongly-typed `FileFlags` from the raw C value. + @_alwaysEmitIntoClient + public init(rawValue: CInterop.FileFlags) { self.rawValue = rawValue } + + // MARK: Flags Available on Darwin, FreeBSD, and OpenBSD + + /// Do not dump the file during backups. + /// + /// The corresponding C constant is `UF_NODUMP`. + /// - Note: This flag may be changed by the file owner or superuser. + @_alwaysEmitIntoClient + public static var noDump: FileFlags { FileFlags(rawValue: _UF_NODUMP) } + + /// File may not be changed. + /// + /// The corresponding C constant is `UF_IMMUTABLE`. + /// - Note: This flag may be changed by the file owner or superuser. + @_alwaysEmitIntoClient + public static var userImmutable: FileFlags { FileFlags(rawValue: _UF_IMMUTABLE) } + + /// Writes to the file may only append. + /// + /// The corresponding C constant is `UF_APPEND`. + /// - Note: This flag may be changed by the file owner or superuser. + @_alwaysEmitIntoClient + public static var userAppend: FileFlags { FileFlags(rawValue: _UF_APPEND) } + + /// File has been archived. + /// + /// The corresponding C constant is `SF_ARCHIVED`. + /// - Note: This flag may only be changed by the superuser. + @_alwaysEmitIntoClient + public static var archived: FileFlags { FileFlags(rawValue: _SF_ARCHIVED) } + + /// File may not be changed. + /// + /// The corresponding C constant is `SF_IMMUTABLE`. + /// - Note: This flag may only be changed by the superuser. + @_alwaysEmitIntoClient + public static var systemImmutable: FileFlags { FileFlags(rawValue: _SF_IMMUTABLE) } + + /// Writes to the file may only append. + /// + /// The corresponding C constant is `SF_APPEND`. + /// - Note: This flag may only be changed by the superuser. + @_alwaysEmitIntoClient + public static var systemAppend: FileFlags { FileFlags(rawValue: _SF_APPEND) } + + // MARK: Flags Available on Darwin and FreeBSD + + #if SYSTEM_PACKAGE_DARWIN || os(FreeBSD) + /// Directory is opaque when viewed through a union mount. + /// + /// The corresponding C constant is `UF_OPAQUE`. + /// - Note: This flag may be changed by the file owner or superuser. + @_alwaysEmitIntoClient + public static var opaque: FileFlags { FileFlags(rawValue: _UF_OPAQUE) } + + /// File is compressed at the file system level. + /// + /// The corresponding C constant is `UF_COMPRESSED`. + /// - Note: This flag is read-only. Attempting to change it will result in undefined behavior. + @_alwaysEmitIntoClient + public static var compressed: FileFlags { FileFlags(rawValue: _UF_COMPRESSED) } + + /// File is tracked for the purpose of document IDs. + /// + /// The corresponding C constant is `UF_TRACKED`. + /// - Note: This flag may be changed by the file owner or superuser. + @_alwaysEmitIntoClient + public static var tracked: FileFlags { FileFlags(rawValue: _UF_TRACKED) } + + /// File should not be displayed in a GUI. + /// + /// The corresponding C constant is `UF_HIDDEN`. + /// - Note: This flag may be changed by the file owner or superuser. + @_alwaysEmitIntoClient + public static var hidden: FileFlags { FileFlags(rawValue: _UF_HIDDEN) } + + /// File requires an entitlement for writing. + /// + /// The corresponding C constant is `SF_RESTRICTED`. + /// - Note: This flag may only be changed by the superuser. + @_alwaysEmitIntoClient + public static var restricted: FileFlags { FileFlags(rawValue: _SF_RESTRICTED) } + + /// File may not be removed or renamed. + /// + /// The corresponding C constant is `SF_NOUNLINK`. + /// - Note: This flag may only be changed by the superuser. + @_alwaysEmitIntoClient + public static var systemNoUnlink: FileFlags { FileFlags(rawValue: _SF_NOUNLINK) } + #endif + + // MARK: Flags Available on Darwin only + + #if SYSTEM_PACKAGE_DARWIN + /// File requires an entitlement for reading and writing. + /// + /// The corresponding C constant is `UF_DATAVAULT`. + /// - Note: This flag may be changed by the file owner or superuser. + @_alwaysEmitIntoClient + public static var dataVault: FileFlags { FileFlags(rawValue: _UF_DATAVAULT) } + + /// File is a firmlink. + /// + /// Firmlinks are used by macOS to create transparent links between + /// the read-only system volume and writable data volume. For example, + /// the `/Applications` folder on the system volume is a firmlink to + /// the `/Applications` folder on the data volume, allowing the user + /// to see both system- and user-installed applications in a single folder. + /// + /// The corresponding C constant is `SF_FIRMLINK`. + /// - Note: This flag may only be changed by the superuser. + @_alwaysEmitIntoClient + public static var firmlink: FileFlags { FileFlags(rawValue: _SF_FIRMLINK) } + + /// File is a dataless placeholder (content is stored remotely). + /// + /// The system will attempt to materialize the file when accessed according to + /// the dataless file materialization policy of the accessing thread or process. + /// See `getiopolicy_np(3)`. + /// + /// The corresponding C constant is `SF_DATALESS`. + /// - Note: This flag is read-only. Attempting to change it will result in undefined behavior. + @_alwaysEmitIntoClient + public static var dataless: FileFlags { FileFlags(rawValue: _SF_DATALESS) } + #endif + + // MARK: Flags Available on FreeBSD Only + + #if os(FreeBSD) + /// File may not be removed or renamed. + /// + /// The corresponding C constant is `UF_NOUNLINK`. + /// - Note: This flag may be changed by the file owner or superuser. + @_alwaysEmitIntoClient + public static var userNoUnlink: FileFlags { FileFlags(rawValue: _UF_NOUNLINK) } + + /// File has the Windows offline attribute. + /// + /// File systems may use this flag for compatibility with the Windows `FILE_ATTRIBUTE_OFFLINE` attribute, + /// but otherwise provide no special handling when it's set. + /// + /// The corresponding C constant is `UF_OFFLINE`. + /// - Note: This flag may be changed by the file owner or superuser. + @_alwaysEmitIntoClient + public static var offline: FileFlags { FileFlags(rawValue: _UF_OFFLINE) } + + /// File is read-only. + /// + /// File systems may use this flag for compatibility with the Windows `FILE_ATTRIBUTE_READONLY` attribute. + /// + /// The corresponding C constant is `UF_READONLY`. + /// - Note: This flag may be changed by the file owner or superuser. + @_alwaysEmitIntoClient + public static var readOnly: FileFlags { FileFlags(rawValue: _UF_READONLY) } + + /// File contains a Windows reparse point. + /// + /// File systems may use this flag for compatibility with the Windows `FILE_ATTRIBUTE_REPARSE_POINT` attribute. + /// + /// The corresponding C constant is `UF_REPARSE`. + /// - Note: This flag may be changed by the file owner or superuser. + @_alwaysEmitIntoClient + public static var reparse: FileFlags { FileFlags(rawValue: _UF_REPARSE) } + + /// File is sparse. + /// + /// File systems may use this flag for compatibility with the Windows `FILE_ATTRIBUTE_SPARSE_FILE` attribute, + /// or to indicate a sparse file. + /// + /// The corresponding C constant is `UF_SPARSE`. + /// - Note: This flag may be changed by the file owner or superuser. + @_alwaysEmitIntoClient + public static var sparse: FileFlags { FileFlags(rawValue: _UF_SPARSE) } + + /// File has the Windows system attribute. + /// + /// File systems may use this flag for compatibility with the Windows `FILE_ATTRIBUTE_SYSTEM` attribute, + /// but otherwise provide no special handling when it's set. + /// + /// The corresponding C constant is `UF_SYSTEM`. + /// - Note: This flag may be changed by the file owner or superuser. + @_alwaysEmitIntoClient + public static var system: FileFlags { FileFlags(rawValue: _UF_SYSTEM) } + + /// File is a snapshot. + /// + /// The corresponding C constant is `SF_SNAPSHOT`. + /// - Note: This flag may only be changed by the superuser. + @_alwaysEmitIntoClient + public static var snapshot: FileFlags { FileFlags(rawValue: _SF_SNAPSHOT) } + #endif +} +#endif diff --git a/Sources/System/FileSystem/FileMode.swift b/Sources/System/FileSystem/FileMode.swift new file mode 100644 index 00000000..14ae30ea --- /dev/null +++ b/Sources/System/FileSystem/FileMode.swift @@ -0,0 +1,54 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift System open source project +// +// Copyright (c) 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 +// +//===----------------------------------------------------------------------===// + +#if !os(Windows) +/// A strongly-typed file mode representing a C `mode_t`. +/// +/// - Note: Only available on Unix-like platforms. +@frozen +// @available(System X.Y.Z, *) +public struct FileMode: RawRepresentable, Sendable, Hashable, Codable { + + /// The raw C mode. + @_alwaysEmitIntoClient + public var rawValue: CInterop.Mode + + /// Creates a strongly-typed `FileMode` from the raw C value. + @_alwaysEmitIntoClient + public init(rawValue: CInterop.Mode) { self.rawValue = rawValue } + + /// Creates a `FileMode` from the given file type and permissions. + /// + /// - Note: This initializer masks the inputs with their respective bit masks. + @_alwaysEmitIntoClient + public init(type: FileType, permissions: FilePermissions) { + self.rawValue = (type.rawValue & _MODE_FILETYPE_MASK) | (permissions.rawValue & _MODE_PERMISSIONS_MASK) + } + + /// The file's type, from the mode's file-type bits. + /// + /// Setting this property will mask the `newValue` with the file-type bit mask `S_IFMT`. + @_alwaysEmitIntoClient + public var type: FileType { + get { FileType(rawValue: rawValue & _MODE_FILETYPE_MASK) } + set { rawValue = (rawValue & ~_MODE_FILETYPE_MASK) | (newValue.rawValue & _MODE_FILETYPE_MASK) } + } + + /// The file's permissions, from the mode's permission bits. + /// + /// Setting this property will mask the `newValue` with the permissions bit mask `0o7777`. + @_alwaysEmitIntoClient + public var permissions: FilePermissions { + get { FilePermissions(rawValue: rawValue & _MODE_PERMISSIONS_MASK) } + set { rawValue = (rawValue & ~_MODE_PERMISSIONS_MASK) | (newValue.rawValue & _MODE_PERMISSIONS_MASK) } + } +} +#endif diff --git a/Sources/System/FileSystem/FileType.swift b/Sources/System/FileSystem/FileType.swift new file mode 100644 index 00000000..91718880 --- /dev/null +++ b/Sources/System/FileSystem/FileType.swift @@ -0,0 +1,102 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift System open source project +// +// Copyright (c) 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 +// +//===----------------------------------------------------------------------===// + +// |------------------------| +// | Swift API to C Mapping | +// |----------------------------------------| +// | FileType | Unix-like Platforms | +// |------------------|---------------------| +// | directory | S_IFDIR | +// | characterSpecial | S_IFCHR | +// | blockSpecial | S_IFBLK | +// | regular | S_IFREG | +// | pipe | S_IFIFO | +// | symbolicLink | S_IFLNK | +// | socket | S_IFSOCK | +// |------------------|---------------------| +// +// |------------------------------------------------------------------| +// | FileType | Darwin | FreeBSD | Other Unix-like Platforms | +// |------------------|---------|---------|---------------------------| +// | whiteout | S_IFWHT | S_IFWHT | N/A | +// |------------------|---------|---------|---------------------------| + +#if !os(Windows) +/// A file type matching those contained in a C `mode_t`. +/// +/// - Note: Only available on Unix-like platforms. +@frozen +// @available(System X.Y.Z, *) +public struct FileType: RawRepresentable, Sendable, Hashable, Codable { + + /// The raw file-type bits from the C mode. + @_alwaysEmitIntoClient + public var rawValue: CInterop.Mode + + /// Creates a strongly-typed file type from the raw C value. + /// + /// - Note: `rawValue` should only contain the mode's file-type bits. Otherwise, + /// use `FileMode(rawValue:)` to get a strongly-typed `FileMode`, then + /// call `.type` to get the properly masked `FileType`. + @_alwaysEmitIntoClient + public init(rawValue: CInterop.Mode) { self.rawValue = rawValue } + + /// Directory + /// + /// The corresponding C constant is `S_IFDIR`. + @_alwaysEmitIntoClient + public static var directory: FileType { FileType(rawValue: _S_IFDIR) } + + /// Character special device + /// + /// The corresponding C constant is `S_IFCHR`. + @_alwaysEmitIntoClient + public static var characterSpecial: FileType { FileType(rawValue: _S_IFCHR) } + + /// Block special device + /// + /// The corresponding C constant is `S_IFBLK`. + @_alwaysEmitIntoClient + public static var blockSpecial: FileType { FileType(rawValue: _S_IFBLK) } + + /// Regular file + /// + /// The corresponding C constant is `S_IFREG`. + @_alwaysEmitIntoClient + public static var regular: FileType { FileType(rawValue: _S_IFREG) } + + /// FIFO (or pipe) + /// + /// The corresponding C constant is `S_IFIFO`. + @_alwaysEmitIntoClient + public static var pipe: FileType { FileType(rawValue: _S_IFIFO) } + + /// Symbolic link + /// + /// The corresponding C constant is `S_IFLNK`. + @_alwaysEmitIntoClient + public static var symbolicLink: FileType { FileType(rawValue: _S_IFLNK) } + + /// Socket + /// + /// The corresponding C constant is `S_IFSOCK`. + @_alwaysEmitIntoClient + public static var socket: FileType { FileType(rawValue: _S_IFSOCK) } + + #if SYSTEM_PACKAGE_DARWIN || os(FreeBSD) + /// Whiteout file + /// + /// The corresponding C constant is `S_IFWHT`. + @_alwaysEmitIntoClient + public static var whiteout: FileType { FileType(rawValue: _S_IFWHT) } + #endif +} +#endif diff --git a/Sources/System/FileSystem/Identifiers.swift b/Sources/System/FileSystem/Identifiers.swift new file mode 100644 index 00000000..df9a01ae --- /dev/null +++ b/Sources/System/FileSystem/Identifiers.swift @@ -0,0 +1,89 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift System open source project +// +// Copyright (c) 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 +// +//===----------------------------------------------------------------------===// + +#if !os(Windows) +/// A Swift wrapper of the C `uid_t` type. +@frozen +// @available(System X.Y.Z, *) +public struct UserID: RawRepresentable, Sendable, Hashable, Codable { + + /// The raw C `uid_t`. + @_alwaysEmitIntoClient + public var rawValue: CInterop.UserID + + /// Creates a strongly-typed `GroupID` from the raw C value. + @_alwaysEmitIntoClient + public init(rawValue: CInterop.UserID) { self.rawValue = rawValue } +} + +/// A Swift wrapper of the C `gid_t` type. +@frozen +// @available(System X.Y.Z, *) +public struct GroupID: RawRepresentable, Sendable, Hashable, Codable { + + /// The raw C `gid_t`. + @_alwaysEmitIntoClient + public var rawValue: CInterop.GroupID + + /// Creates a strongly-typed `GroupID` from the raw C value. + @_alwaysEmitIntoClient + public init(rawValue: CInterop.GroupID) { self.rawValue = rawValue } +} + +/// A Swift wrapper of the C `dev_t` type. +@frozen +// @available(System X.Y.Z, *) +public struct DeviceID: RawRepresentable, Sendable, Hashable, Codable { + + /// The raw C `dev_t`. + @_alwaysEmitIntoClient + public var rawValue: CInterop.DeviceID + + /// Creates a strongly-typed `DeviceID` from the raw C value. + @_alwaysEmitIntoClient + public init(rawValue: CInterop.DeviceID) { self.rawValue = rawValue } + + + /// Creates a `DeviceID` from the given major and minor device numbers. + /// + /// The corresponding C function is `makedev()`. + @_alwaysEmitIntoClient + public static func make(major: CUnsignedInt, minor: CUnsignedInt) -> DeviceID { + DeviceID(rawValue: system_makedev(major, minor)) + } + + /// The major device number + /// + /// The corresponding C function is `major()`. + @_alwaysEmitIntoClient + public var major: CInt { system_major(rawValue) } + + /// The minor device number + /// + /// The corresponding C function is `minor()`. + @_alwaysEmitIntoClient + public var minor: CInt { system_minor(rawValue) } +} + +/// A Swift wrapper of the C `ino_t` type. +@frozen +// @available(System X.Y.Z, *) +public struct Inode: RawRepresentable, Sendable, Hashable, Codable { + + /// The raw C `ino_t`. + @_alwaysEmitIntoClient + public var rawValue: CInterop.Inode + + /// Creates a strongly-typed `Inode` from the raw C value. + @_alwaysEmitIntoClient + public init(rawValue: CInterop.Inode) { self.rawValue = rawValue } +} +#endif // !os(Windows) diff --git a/Sources/System/FileSystem/Stat.swift b/Sources/System/FileSystem/Stat.swift new file mode 100644 index 00000000..76f37b0a --- /dev/null +++ b/Sources/System/FileSystem/Stat.swift @@ -0,0 +1,700 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift System open source project +// +// Copyright (c) 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 +// +//===----------------------------------------------------------------------===// + +#if !os(Windows) + +// Must import here to use C stat properties in @_alwaysEmitIntoClient APIs. +#if SYSTEM_PACKAGE_DARWIN +import Darwin +#elseif canImport(Glibc) +import CSystem +import Glibc +#elseif canImport(Musl) +import CSystem +import Musl +#elseif canImport(WASILibc) +import WASILibc +#elseif canImport(Android) +import CSystem +import Android +#else +#error("Unsupported Platform") +#endif + +// MARK: - Stat + +/// A Swift wrapper of the C `stat` struct. +/// +/// - Note: Only available on Unix-like platforms. +@frozen +// @available(System X.Y.Z, *) +public struct Stat: RawRepresentable, Sendable { + + /// The raw C `stat` struct. + @_alwaysEmitIntoClient + public var rawValue: CInterop.Stat + + /// Creates a Swift `Stat` from the raw C struct. + @_alwaysEmitIntoClient + public init(rawValue: CInterop.Stat) { self.rawValue = rawValue } + + // MARK: Stat.Flags + + /// Flags representing those passed to `fstatat()`. + @frozen + public struct Flags: OptionSet, Sendable, Hashable, Codable { + + /// The raw C flags. + @_alwaysEmitIntoClient + public let rawValue: CInt + + /// Creates a strongly-typed `Stat.Flags` from raw C flags. + @_alwaysEmitIntoClient + public init(rawValue: CInt) { self.rawValue = rawValue } + + /// If the path ends with a symbolic link, return information about the link itself. + /// + /// The corresponding C constant is `AT_SYMLINK_NOFOLLOW`. + @_alwaysEmitIntoClient + public static var symlinkNoFollow: Flags { Flags(rawValue: _AT_SYMLINK_NOFOLLOW) } + + #if SYSTEM_PACKAGE_DARWIN + /// If the path ends with a symbolic link, return information about the link itself. + /// If _any_ symbolic link is encountered during path resolution, return an error. + /// + /// The corresponding C constant is `AT_SYMLINK_NOFOLLOW_ANY`. + /// - Note: Only available on Darwin. + @_alwaysEmitIntoClient + public static var symlinkNoFollowAny: Flags { Flags(rawValue: _AT_SYMLINK_NOFOLLOW_ANY) } + #endif + + #if SYSTEM_PACKAGE_DARWIN || os(FreeBSD) + /// If the path does not reside in the hierarchy beneath the starting directory, return an error. + /// + /// The corresponding C constant is `AT_RESOLVE_BENEATH`. + /// - Note: Only available on Darwin and FreeBSD. + @_alwaysEmitIntoClient + public static var resolveBeneath: Flags { Flags(rawValue: _AT_RESOLVE_BENEATH) } + #endif + + #if os(FreeBSD) || os(Linux) || os(Android) + /// If the path is an empty string (or `NULL` since Linux 6.11), + /// return information about the given file descriptor. + /// + /// The corresponding C constant is `AT_EMPTY_PATH`. + /// - Note: Only available on FreeBSD, Linux, and Android. + @_alwaysEmitIntoClient + public static var emptyPath: Flags { Flags(rawValue: _AT_EMPTY_PATH) } + #endif + } + + // MARK: Initializers + + /// Creates a `Stat` struct from a `FilePath`. + /// + /// `followTargetSymlink` determines the behavior if `path` ends with a symbolic link. + /// By default, `followTargetSymlink` is `true` and this initializer behaves like `stat()`. + /// If `followTargetSymlink` is set to `false`, this initializer behaves like `lstat()` and + /// returns information about the symlink itself. + /// + /// The corresponding C function is `stat()` or `lstat()` as described above. + @_alwaysEmitIntoClient + public init( + _ path: FilePath, + followTargetSymlink: Bool = true, + retryOnInterrupt: Bool = true + ) throws(Errno) { + self.rawValue = try path.withPlatformString { + Self._stat( + $0, + followTargetSymlink: followTargetSymlink, + retryOnInterrupt: retryOnInterrupt + ) + }.get() + } + + /// Creates a `Stat` struct from an`UnsafePointer` path. + /// + /// `followTargetSymlink` determines the behavior if `path` ends with a symbolic link. + /// By default, `followTargetSymlink` is `true` and this initializer behaves like `stat()`. + /// If `followTargetSymlink` is set to `false`, this initializer behaves like `lstat()` and + /// returns information about the symlink itself. + /// + /// The corresponding C function is `stat()` or `lstat()` as described above. + @_alwaysEmitIntoClient + public init( + _ path: UnsafePointer, + followTargetSymlink: Bool = true, + retryOnInterrupt: Bool = true + ) throws(Errno) { + self.rawValue = try Self._stat( + path, + followTargetSymlink: followTargetSymlink, + retryOnInterrupt: retryOnInterrupt + ).get() + } + + @usableFromInline + internal static func _stat( + _ ptr: UnsafePointer, + followTargetSymlink: Bool, + retryOnInterrupt: Bool + ) -> Result { + var result = CInterop.Stat() + return nothingOrErrno(retryOnInterrupt: retryOnInterrupt) { + if followTargetSymlink { + system_stat(ptr, &result) + } else { + system_lstat(ptr, &result) + } + }.map { result } + } + + /// Creates a `Stat` struct from a `FileDescriptor`. + /// + /// The corresponding C function is `fstat()`. + @_alwaysEmitIntoClient + public init( + _ fd: FileDescriptor, + retryOnInterrupt: Bool = true + ) throws(Errno) { + self.rawValue = try Self._fstat( + fd, + retryOnInterrupt: retryOnInterrupt + ).get() + } + + @usableFromInline + internal static func _fstat( + _ fd: FileDescriptor, + retryOnInterrupt: Bool + ) -> Result { + var result = CInterop.Stat() + return nothingOrErrno(retryOnInterrupt: retryOnInterrupt) { + system_fstat(fd.rawValue, &result) + }.map { result } + } + + /// Creates a `Stat` struct from a `FilePath` and `Flags`. + /// + /// If `path` is relative, it is resolved against the current working directory. + /// + /// The corresponding C function is `fstatat()`. + @_alwaysEmitIntoClient + public init( + _ path: FilePath, + flags: Stat.Flags, + retryOnInterrupt: Bool = true + ) throws(Errno) { + self.rawValue = try path.withPlatformString { + Self._fstatat( + $0, + relativeTo: _AT_FDCWD, + flags: flags, + retryOnInterrupt: retryOnInterrupt + ) + }.get() + } + + /// Creates a `Stat` struct from a `FilePath` and `Flags`, + /// including a `FileDescriptor` to resolve a relative path. + /// + /// If `path` is absolute (starts with a forward slash), then `fd` is ignored. + /// If `path` is relative, it is resolved against the directory given by `fd`. + /// + /// The corresponding C function is `fstatat()`. + @_alwaysEmitIntoClient + public init( + _ path: FilePath, + relativeTo fd: FileDescriptor, + flags: Stat.Flags, + retryOnInterrupt: Bool = true + ) throws(Errno) { + self.rawValue = try path.withPlatformString { + Self._fstatat( + $0, + relativeTo: fd.rawValue, + flags: flags, + retryOnInterrupt: retryOnInterrupt + ) + }.get() + } + + /// Creates a `Stat` struct from an `UnsafePointer` path and `Flags`. + /// + /// If `path` is relative, it is resolved against the current working directory. + /// + /// The corresponding C function is `fstatat()`. + @_alwaysEmitIntoClient + public init( + _ path: UnsafePointer, + flags: Stat.Flags, + retryOnInterrupt: Bool = true + ) throws(Errno) { + self.rawValue = try Self._fstatat( + path, + relativeTo: _AT_FDCWD, + flags: flags, + retryOnInterrupt: retryOnInterrupt + ).get() + } + + /// Creates a `Stat` struct from an `UnsafePointer` path and `Flags`, + /// including a `FileDescriptor` to resolve a relative path. + /// + /// If `path` is absolute (starts with a forward slash), then `fd` is ignored. + /// If `path` is relative, it is resolved against the directory given by `fd`. + /// + /// The corresponding C function is `fstatat()`. + @_alwaysEmitIntoClient + public init( + _ path: UnsafePointer, + relativeTo fd: FileDescriptor, + flags: Stat.Flags, + retryOnInterrupt: Bool = true + ) throws(Errno) { + self.rawValue = try Self._fstatat( + path, + relativeTo: fd.rawValue, + flags: flags, + retryOnInterrupt: retryOnInterrupt + ).get() + } + + @usableFromInline + internal static func _fstatat( + _ path: UnsafePointer, + relativeTo fd: FileDescriptor.RawValue, + flags: Stat.Flags, + retryOnInterrupt: Bool + ) -> Result { + var result = CInterop.Stat() + return nothingOrErrno(retryOnInterrupt: retryOnInterrupt) { + system_fstatat(fd, path, &result, flags.rawValue) + }.map { result } + } + + + // MARK: Properties + + /// ID of device containing file + /// + /// The corresponding C property is `st_dev`. + @_alwaysEmitIntoClient + public var deviceID: DeviceID { + get { DeviceID(rawValue: rawValue.st_dev) } + set { rawValue.st_dev = newValue.rawValue } + } + + /// Inode number + /// + /// The corresponding C property is `st_ino`. + @_alwaysEmitIntoClient + public var inode: Inode { + get { Inode(rawValue: rawValue.st_ino) } + set { rawValue.st_ino = newValue.rawValue } + } + + /// File mode + /// + /// The corresponding C property is `st_mode`. + @_alwaysEmitIntoClient + public var mode: FileMode { + get { FileMode(rawValue: rawValue.st_mode) } + set { rawValue.st_mode = newValue.rawValue } + } + + /// File type for the given mode + @_alwaysEmitIntoClient + public var type: FileType { + get { mode.type } + set { + var newMode = mode + newMode.type = newValue + mode = newMode + } + } + + /// File permissions for the given mode + @_alwaysEmitIntoClient + public var permissions: FilePermissions { + get { mode.permissions } + set { + var newMode = mode + newMode.permissions = newValue + mode = newMode + } + } + + /// Number of hard links + /// + /// The corresponding C property is `st_nlink`. + @_alwaysEmitIntoClient + public var linkCount: Int { + get { Int(rawValue.st_nlink) } + set { rawValue.st_nlink = numericCast(newValue) } + } + + /// User ID of owner + /// + /// The corresponding C property is `st_uid`. + @_alwaysEmitIntoClient + public var userID: UserID { + get { UserID(rawValue: rawValue.st_uid) } + set { rawValue.st_uid = newValue.rawValue } + } + + /// Group ID of owner + /// + /// The corresponding C property is `st_gid`. + @_alwaysEmitIntoClient + public var groupID: GroupID { + get { GroupID(rawValue: rawValue.st_gid) } + set { rawValue.st_gid = newValue.rawValue } + } + + /// Device ID (if special file) + /// + /// For character or block special files, the returned `DeviceID` may have + /// meaningful `.major` and `.minor` values. For non-special files, this + /// property is usually meaningless and often set to 0. + /// + /// The corresponding C property is `st_rdev`. + @_alwaysEmitIntoClient + public var specialDeviceID: DeviceID { + get { DeviceID(rawValue: rawValue.st_rdev) } + set { rawValue.st_rdev = newValue.rawValue } + } + + /// Total size, in bytes + /// + /// The corresponding C property is `st_size`. + @_alwaysEmitIntoClient + public var size: Int64 { + get { Int64(rawValue.st_size) } + set { rawValue.st_size = numericCast(newValue) } + } + + /// Block size for filesystem I/O, in bytes + /// + /// The corresponding C property is `st_blksize`. + @_alwaysEmitIntoClient + public var preferredIOBlockSize: Int { + get { Int(rawValue.st_blksize) } + set { rawValue.st_blksize = numericCast(newValue) } + } + + /// Number of 512-byte blocks allocated + /// + /// The corresponding C property is `st_blocks`. + @_alwaysEmitIntoClient + public var blocksAllocated: Int64 { + get { Int64(rawValue.st_blocks) } + set { rawValue.st_blocks = numericCast(newValue) } + } + + /// Total size allocated, in bytes + /// + /// - Note: Calculated as `512 * blocksAllocated`. + @_alwaysEmitIntoClient + public var sizeAllocated: Int64 { + 512 * blocksAllocated + } + + // TODO: jflat - Change time properties to UTCClock.Instant when possible. + + /// Time of last access, given as a `Duration` since the Epoch + /// + /// The corresponding C property is `st_atim` (or `st_atimespec` on Darwin). + @available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *) + public var accessTime: Duration { + get { + #if SYSTEM_PACKAGE_DARWIN + let timespec = rawValue.st_atimespec + #else + let timespec = rawValue.st_atim + #endif + return .seconds(timespec.tv_sec) + .nanoseconds(timespec.tv_nsec) + } + set { + let (seconds, attoseconds) = newValue.components + let timespec = timespec( + tv_sec: numericCast(seconds), + tv_nsec: numericCast(attoseconds / 1_000_000_000) + ) + #if SYSTEM_PACKAGE_DARWIN + rawValue.st_atimespec = timespec + #else + rawValue.st_atim = timespec + #endif + } + } + + /// Time of last modification, given as a `Duration` since the Epoch + /// + /// The corresponding C property is `st_mtim` (or `st_mtimespec` on Darwin). + @available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *) + public var modificationTime: Duration { + get { + #if SYSTEM_PACKAGE_DARWIN + let timespec = rawValue.st_mtimespec + #else + let timespec = rawValue.st_mtim + #endif + return .seconds(timespec.tv_sec) + .nanoseconds(timespec.tv_nsec) + } + set { + let (seconds, attoseconds) = newValue.components + let timespec = timespec( + tv_sec: numericCast(seconds), + tv_nsec: numericCast(attoseconds / 1_000_000_000) + ) + #if SYSTEM_PACKAGE_DARWIN + rawValue.st_mtimespec = timespec + #else + rawValue.st_mtim = timespec + #endif + } + } + + /// Time of last status (inode) change, given as a `Duration` since the Epoch + /// + /// The corresponding C property is `st_ctim` (or `st_ctimespec` on Darwin). + @available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *) + public var changeTime: Duration { + get { + #if SYSTEM_PACKAGE_DARWIN + let timespec = rawValue.st_ctimespec + #else + let timespec = rawValue.st_ctim + #endif + return .seconds(timespec.tv_sec) + .nanoseconds(timespec.tv_nsec) + } + set { + let (seconds, attoseconds) = newValue.components + let timespec = timespec( + tv_sec: numericCast(seconds), + tv_nsec: numericCast(attoseconds / 1_000_000_000) + ) + #if SYSTEM_PACKAGE_DARWIN + rawValue.st_ctimespec = timespec + #else + rawValue.st_ctim = timespec + #endif + } + } + + #if SYSTEM_PACKAGE_DARWIN || os(FreeBSD) + /// Time of file creation, given as a `Duration` since the Epoch + /// + /// The corresponding C property is `st_birthtim` (or `st_birthtimespec` on Darwin). + /// - Note: Only available on Darwin and FreeBSD. + @available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *) + public var creationTime: Duration { + get { + #if SYSTEM_PACKAGE_DARWIN + let timespec = rawValue.st_birthtimespec + #else + let timespec = rawValue.st_birthtim + #endif + return .seconds(timespec.tv_sec) + .nanoseconds(timespec.tv_nsec) + } + set { + let (seconds, attoseconds) = newValue.components + let timespec = timespec( + tv_sec: numericCast(seconds), + tv_nsec: numericCast(attoseconds / 1_000_000_000) + ) + #if SYSTEM_PACKAGE_DARWIN + rawValue.st_birthtimespec = timespec + #else + rawValue.st_birthtim = timespec + #endif + } + } + #endif + + #if SYSTEM_PACKAGE_DARWIN || os(FreeBSD) || os(OpenBSD) + /// File flags + /// + /// The corresponding C property is `st_flags`. + /// - Note: Only available on Darwin, FreeBSD, and OpenBSD. + @_alwaysEmitIntoClient + public var flags: FileFlags { + get { FileFlags(rawValue: rawValue.st_flags) } + set { rawValue.st_flags = newValue.rawValue } + } + + /// File generation number + /// + /// The file generation number is used to distinguish between different files + /// that have used the same inode over time. + /// + /// The corresponding C property is `st_gen`. + /// - Note: Only available on Darwin, FreeBSD, and OpenBSD. + @_alwaysEmitIntoClient + public var generationNumber: Int { + get { Int(rawValue.st_gen) } + set { rawValue.st_gen = numericCast(newValue)} + } + #endif +} + +// MARK: - Equatable and Hashable + +extension Stat: Equatable { + @_alwaysEmitIntoClient + /// Compares the raw bytes of two `Stat` structs for equality. + public static func == (lhs: Self, rhs: Self) -> Bool { + return withUnsafeBytes(of: lhs.rawValue) { lhsBytes in + withUnsafeBytes(of: rhs.rawValue) { rhsBytes in + lhsBytes.elementsEqual(rhsBytes) + } + } + } +} + +extension Stat: Hashable { + @_alwaysEmitIntoClient + /// Hashes the raw bytes of this `Stat` struct. + public func hash(into hasher: inout Hasher) { + withUnsafeBytes(of: rawValue) { bytes in + hasher.combine(bytes: bytes) + } + } +} + +// MARK: - CustomStringConvertible and CustomDebugStringConvertible + +// TODO: jflat + +// MARK: - FileDescriptor Extensions + +// @available(System X.Y.Z, *) +extension FileDescriptor { + + /// Creates a `Stat` struct for the file referenced by this `FileDescriptor`. + /// + /// The corresponding C function is `fstat()`. + @_alwaysEmitIntoClient + public func stat( + retryOnInterrupt: Bool = true + ) throws(Errno) -> Stat { + try _fstat( + retryOnInterrupt: retryOnInterrupt + ).get() + } + + @usableFromInline + internal func _fstat( + retryOnInterrupt: Bool + ) -> Result { + var result = CInterop.Stat() + return nothingOrErrno(retryOnInterrupt: retryOnInterrupt) { + system_fstat(self.rawValue, &result) + }.map { Stat(rawValue: result) } + } +} + +// MARK: - FilePath Extensions + +// @available(System X.Y.Z, *) +extension FilePath { + + /// Creates a `Stat` struct for the file referenced by this `FilePath`. + /// + /// `followTargetSymlink` determines the behavior if `path` ends with a symbolic link. + /// By default, `followTargetSymlink` is `true` and this initializer behaves like `stat()`. + /// If `followTargetSymlink` is set to `false`, this initializer behaves like `lstat()` and + /// returns information about the symlink itself. + /// + /// The corresponding C function is `stat()` or `lstat()` as described above. + @_alwaysEmitIntoClient + public func stat( + followTargetSymlink: Bool = true, + retryOnInterrupt: Bool = true + ) throws(Errno) -> Stat { + try _stat( + followTargetSymlink: followTargetSymlink, + retryOnInterrupt: retryOnInterrupt + ).get() + } + + @usableFromInline + internal func _stat( + followTargetSymlink: Bool, + retryOnInterrupt: Bool + ) -> Result { + var result = CInterop.Stat() + return withPlatformString { ptr in + nothingOrErrno(retryOnInterrupt: retryOnInterrupt) { + if followTargetSymlink { + system_stat(ptr, &result) + } else { + system_lstat(ptr, &result) + } + }.map { Stat(rawValue: result) } + } + } + + /// Creates a `Stat` struct for the file referenced by this`FilePath` using the given `Flags`. + /// + /// If `path` is relative, it is resolved against the current working directory. + /// + /// The corresponding C function is `fstatat()`. + @_alwaysEmitIntoClient + public func stat( + flags: Stat.Flags, + retryOnInterrupt: Bool = true + ) throws(Errno) -> Stat { + try _fstatat( + relativeTo: _AT_FDCWD, + flags: flags, + retryOnInterrupt: retryOnInterrupt + ).get() + } + + /// Creates a `Stat` struct for the file referenced by this`FilePath` using the given `Flags`, + /// including a `FileDescriptor` to resolve a relative path. + /// + /// If `path` is absolute (starts with a forward slash), then `fd` is ignored. + /// If `path` is relative, it is resolved against the directory given by `fd`. + /// + /// The corresponding C function is `fstatat()`. + @_alwaysEmitIntoClient + public func stat( + relativeTo fd: FileDescriptor, + flags: Stat.Flags, + retryOnInterrupt: Bool = true + ) throws(Errno) -> Stat { + try _fstatat( + relativeTo: fd.rawValue, + flags: flags, + retryOnInterrupt: retryOnInterrupt + ).get() + } + + @usableFromInline + internal func _fstatat( + relativeTo fd: FileDescriptor.RawValue, + flags: Stat.Flags, + retryOnInterrupt: Bool + ) -> Result { + var result = CInterop.Stat() + return withPlatformString { ptr in + nothingOrErrno(retryOnInterrupt: retryOnInterrupt) { + system_fstatat(fd, ptr, &result, flags.rawValue) + }.map { Stat(rawValue: result) } + } + } +} + +#endif // !os(Windows) diff --git a/Sources/System/Internals/CInterop.swift b/Sources/System/Internals/CInterop.swift index b6de1233..7a35b09c 100644 --- a/Sources/System/Internals/CInterop.swift +++ b/Sources/System/Internals/CInterop.swift @@ -5,7 +5,7 @@ Licensed under Apache License v2.0 with Runtime Library Exception See https://swift.org/LICENSE.txt for license information -*/ + */ #if SYSTEM_PACKAGE_DARWIN import Darwin @@ -78,4 +78,12 @@ public enum CInterop { /// on API. public typealias PlatformUnicodeEncoding = UTF8 #endif + + #if !os(Windows) + public typealias Stat = stat + public typealias DeviceID = dev_t + public typealias Inode = ino_t + public typealias UserID = uid_t + public typealias GroupID = gid_t + #endif } diff --git a/Sources/System/Internals/Constants.swift b/Sources/System/Internals/Constants.swift index d8cbdcbd..8805ffad 100644 --- a/Sources/System/Internals/Constants.swift +++ b/Sources/System/Internals/Constants.swift @@ -5,7 +5,7 @@ Licensed under Apache License v2.0 with Runtime Library Exception See https://swift.org/LICENSE.txt for license information -*/ + */ // For platform constants redefined in Swift. We define them here so that // they can be used anywhere without imports and without confusion to @@ -17,6 +17,7 @@ import Darwin import CSystem import ucrt #elseif canImport(Glibc) +import CSystem import Glibc #elseif canImport(Musl) import CSystem @@ -438,7 +439,7 @@ internal var _ENOSR: CInt { ENOSR } @_alwaysEmitIntoClient internal var _ENOSTR: CInt { ENOSTR } -#endif +#endif #endif @_alwaysEmitIntoClient @@ -639,3 +640,150 @@ internal var _SEEK_HOLE: CInt { SEEK_HOLE } @_alwaysEmitIntoClient internal var _SEEK_DATA: CInt { SEEK_DATA } #endif + +// MARK: - File System + +#if !os(Windows) + +@_alwaysEmitIntoClient +internal var _AT_FDCWD: CInt { AT_FDCWD } + +// MARK: - fstatat Flags + +@_alwaysEmitIntoClient +internal var _AT_SYMLINK_NOFOLLOW: CInt { AT_SYMLINK_FOLLOW } + +#if SYSTEM_PACKAGE_DARWIN +@_alwaysEmitIntoClient +internal var _AT_SYMLINK_NOFOLLOW_ANY: CInt { AT_SYMLINK_NOFOLLOW_ANY } +#endif + +#if SYSTEM_PACKAGE_DARWIN || os(FreeBSD) +@_alwaysEmitIntoClient +internal var _AT_RESOLVE_BENEATH: CInt { AT_RESOLVE_BENEATH } +#endif + +#if os(FreeBSD) || os(Linux) || os(Android) +@_alwaysEmitIntoClient +internal var _AT_EMPTY_PATH: CInt { AT_EMPTY_PATH } +#endif + +// MARK: - File Mode / File Type + +@_alwaysEmitIntoClient +internal var _MODE_FILETYPE_MASK: CInterop.Mode { S_IFMT } + +@_alwaysEmitIntoClient +internal var _MODE_PERMISSIONS_MASK: CInterop.Mode { 0o7777 } + +@_alwaysEmitIntoClient +internal var _S_IFDIR: CInterop.Mode { S_IFDIR } + +@_alwaysEmitIntoClient +internal var _S_IFCHR: CInterop.Mode { S_IFCHR } + +@_alwaysEmitIntoClient +internal var _S_IFBLK: CInterop.Mode { S_IFBLK } + +@_alwaysEmitIntoClient +internal var _S_IFREG: CInterop.Mode { S_IFREG } + +@_alwaysEmitIntoClient +internal var _S_IFIFO: CInterop.Mode { S_IFIFO } + +@_alwaysEmitIntoClient +internal var _S_IFLNK: CInterop.Mode { S_IFLNK } + +@_alwaysEmitIntoClient +internal var _S_IFSOCK: CInterop.Mode { S_IFSOCK } + +#if SYSTEM_PACKAGE_DARWIN || os(FreeBSD) +@_alwaysEmitIntoClient +internal var _S_IFWHT: CInterop.Mode { S_IFWHT } +#endif + +// MARK: - stat/chflags File Flags + +// MARK: Flags Available on Darwin, FreeBSD, and OpenBSD + +#if SYSTEM_PACKAGE_DARWIN || os(FreeBSD) || os(OpenBSD) +@_alwaysEmitIntoClient +internal var _UF_NODUMP: CInterop.FileFlags { UInt32(bitPattern: UF_NODUMP) } + +@_alwaysEmitIntoClient +internal var _UF_IMMUTABLE: CInterop.FileFlags { UInt32(bitPattern: UF_IMMUTABLE) } + +@_alwaysEmitIntoClient +internal var _UF_APPEND: CInterop.FileFlags { UInt32(bitPattern: UF_APPEND) } + +@_alwaysEmitIntoClient +internal var _SF_ARCHIVED: CInterop.FileFlags { UInt32(bitPattern: SF_ARCHIVED) } + +@_alwaysEmitIntoClient +internal var _SF_IMMUTABLE: CInterop.FileFlags { UInt32(bitPattern: SF_IMMUTABLE) } + +@_alwaysEmitIntoClient +internal var _SF_APPEND: CInterop.FileFlags { UInt32(bitPattern: SF_APPEND) } +#endif + +// MARK: Flags Available on Darwin and FreeBSD + +#if SYSTEM_PACKAGE_DARWIN || os(FreeBSD) +@_alwaysEmitIntoClient +internal var _UF_OPAQUE: CInterop.FileFlags { UInt32(bitPattern: UF_OPAQUE) } + +@_alwaysEmitIntoClient +internal var _UF_COMPRESSED: CInterop.FileFlags { UInt32(bitPattern: UF_COMPRESSED) } + +@_alwaysEmitIntoClient +internal var _UF_TRACKED: CInterop.FileFlags { UInt32(bitPattern: UF_TRACKED) } + +@_alwaysEmitIntoClient +internal var _UF_HIDDEN: CInterop.FileFlags { UInt32(bitPattern: UF_HIDDEN) } + +@_alwaysEmitIntoClient +internal var _SF_RESTRICTED: CInterop.FileFlags { UInt32(bitPattern: SF_RESTRICTED) } + +@_alwaysEmitIntoClient +internal var _SF_NOUNLINK: CInterop.FileFlags { UInt32(bitPattern: SF_NOUNLINK) } +#endif + +// MARK: Flags Available on Darwin Only + +#if SYSTEM_PACKAGE_DARWIN +@_alwaysEmitIntoClient +internal var _UF_DATAVAULT: CInterop.FileFlags { UInt32(bitPattern: UF_DATAVAULT) } + +@_alwaysEmitIntoClient +internal var _SF_FIRMLINK: CInterop.FileFlags { UInt32(bitPattern: SF_FIRMLINK) } + +@_alwaysEmitIntoClient +internal var _SF_DATALESS: CInterop.FileFlags { UInt32(bitPattern: SF_DATALESS) } +#endif + +// MARK: Flags Available on FreeBSD Only + +#if os(FreeBSD) +@_alwaysEmitIntoClient +internal var _UF_NOUNLINK: CInterop.FileFlags { UInt32(bitPattern: UF_NOUNLINK) } + +@_alwaysEmitIntoClient +internal var _UF_OFFLINE: CInterop.FileFlags { UInt32(bitPattern: UF_OFFLINE) } + +@_alwaysEmitIntoClient +internal var _UF_READONLY: CInterop.FileFlags { UInt32(bitPattern: UF_READONLY) } + +@_alwaysEmitIntoClient +internal var _UF_REPARSE: CInterop.FileFlags { UInt32(bitPattern: UF_REPARSE) } + +@_alwaysEmitIntoClient +internal var _UF_SPARSE: CInterop.FileFlags { UInt32(bitPattern: UF_SPARSE) } + +@_alwaysEmitIntoClient +internal var _UF_SYSTEM: CInterop.FileFlags { UInt32(bitPattern: UF_SYSTEM) } + +@_alwaysEmitIntoClient +internal var _SF_SNAPSHOT: CInterop.FileFlags { UInt32(bitPattern: SF_SNAPSHOT) } +#endif + +#endif // !os(Windows) diff --git a/Sources/System/Internals/Exports.swift b/Sources/System/Internals/Exports.swift index c7d9944f..025aefae 100644 --- a/Sources/System/Internals/Exports.swift +++ b/Sources/System/Internals/Exports.swift @@ -5,7 +5,7 @@ Licensed under Apache License v2.0 with Runtime Library Exception See https://swift.org/LICENSE.txt for license information -*/ + */ // Internal wrappers and typedefs which help reduce #if littering in System's // code base. @@ -90,6 +90,34 @@ internal func system_strlen(_ s: UnsafeMutablePointer) -> Int { strlen(s) } +#if !os(Windows) +internal func system_stat(_ p: UnsafePointer, _ s: inout CInterop.Stat) -> Int32 { + stat(p, &s) +} +internal func system_lstat(_ p: UnsafePointer, _ s: inout CInterop.Stat) -> Int32 { + lstat(p, &s) +} +internal func system_fstat(_ fd: CInt, _ s: inout CInterop.Stat) -> Int32 { + fstat(fd, &s) +} +internal func system_fstatat(_ fd: CInt, _ p: UnsafePointer, _ s: inout CInterop.Stat, _ flags: CInt) -> Int32 { + fstatat(fd, p, &s, flags) +} + +@usableFromInline +internal func system_major(_ dev: CInterop.DeviceID) -> CInt { + numericCast((dev >> 24) & 0xff) +} +@usableFromInline +internal func system_minor(_ dev: CInterop.DeviceID) -> CInt { + numericCast(dev & 0xffffff) +} +@usableFromInline +internal func system_makedev(_ maj: CUnsignedInt, _ min: CUnsignedInt) -> CInterop.DeviceID { + CInterop.DeviceID((maj << 24) | min) +} +#endif + // Convention: `system_platform_foo` is a // platform-representation-abstracted wrapper around `foo`-like functionality. // Type and layout differences such as the `char` vs `wchar` are abstracted. @@ -167,20 +195,20 @@ internal typealias _PlatformTLSKey = DWORD #elseif os(WASI) && (swift(<6.1) || !_runtime(_multithreaded)) // Mock TLS storage for single-threaded WASI internal final class _PlatformTLSKey { - fileprivate init() {} + fileprivate init() {} } private final class TLSStorage: @unchecked Sendable { - var storage = [ObjectIdentifier: UnsafeMutableRawPointer]() + var storage = [ObjectIdentifier: UnsafeMutableRawPointer]() } private let sharedTLSStorage = TLSStorage() func pthread_setspecific(_ key: _PlatformTLSKey, _ p: UnsafeMutableRawPointer?) -> Int { - sharedTLSStorage.storage[ObjectIdentifier(key)] = p - return 0 + sharedTLSStorage.storage[ObjectIdentifier(key)] = p + return 0 } func pthread_getspecific(_ key: _PlatformTLSKey) -> UnsafeMutableRawPointer? { - sharedTLSStorage.storage[ObjectIdentifier(key)] + sharedTLSStorage.storage[ObjectIdentifier(key)] } #else internal typealias _PlatformTLSKey = pthread_key_t diff --git a/Tests/SystemTests/FileModeTests.swift b/Tests/SystemTests/FileModeTests.swift new file mode 100644 index 00000000..bc302a71 --- /dev/null +++ b/Tests/SystemTests/FileModeTests.swift @@ -0,0 +1,134 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift System open source project +// +// Copyright (c) 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 +// +//===----------------------------------------------------------------------===// + +#if !os(Windows) + +import Testing + +#if SYSTEM_PACKAGE_DARWIN +import Darwin +#elseif canImport(Glibc) +import Glibc +#elseif canImport(Musl) +import Musl +#elseif canImport(WASILibc) +import WASILibc +#elseif canImport(Android) +import Android +#else +#error("Unsupported Platform") +#endif + +#if SYSTEM_PACKAGE +@testable import SystemPackage +#else +@testable import System +#endif + +@Suite("FileMode") +private struct FileModeTests { + + @Test func basics() async throws { + var mode = FileMode(rawValue: S_IFREG | 0o644) // Regular file, rw-r--r-- + #expect(mode.type == .regular) + #expect(mode.permissions == [.ownerReadWrite, .groupRead, .otherRead]) + + mode.type = .directory // Directory, rw-r--r-- + #expect(mode.type == .directory) + #expect(mode.permissions == [.ownerReadWrite, .groupRead, .otherRead]) + + mode.permissions.insert([.ownerExecute, .groupExecute, .otherExecute]) // Directory, rwxr-xr-x + #expect(mode.type == .directory) + #expect(mode.permissions == [.ownerReadWriteExecute, .groupReadExecute, .otherReadExecute]) + + mode.type = .symbolicLink // Symbolic link, rwxr-xr-x + #expect(mode.type == .symbolicLink) + #expect(mode.permissions == [.ownerReadWriteExecute, .groupReadExecute, .otherReadExecute]) + + let mode1 = FileMode(rawValue: S_IFLNK | 0o755) // Symbolic link, rwxr-xr-x + let mode2 = FileMode(type: .symbolicLink, permissions: [.ownerReadWriteExecute, .groupReadExecute, .otherReadExecute]) + #expect(mode == mode1) + #expect(mode1 == mode2) + + mode.permissions.remove([.otherReadExecute]) // Symbolic link, rwxr-x--- + #expect(mode.permissions == [.ownerReadWriteExecute, .groupReadExecute]) + #expect(mode != mode1) + #expect(mode != mode2) + #expect(mode.type == mode1.type) + #expect(mode.type == mode2.type) + } + + @Test func invalidInput() async throws { + // No permissions, all other bits set + var invalidMode = FileMode(rawValue: ~0o7777) + #expect(invalidMode.permissions.isEmpty) + #expect(invalidMode.type != .directory) + #expect(invalidMode.type != .characterSpecial) + #expect(invalidMode.type != .blockSpecial) + #expect(invalidMode.type != .regular) + #expect(invalidMode.type != .pipe) + #expect(invalidMode.type != .symbolicLink) + #expect(invalidMode.type != .socket) + #if SYSTEM_PACKAGE_DARWIN || os(FreeBSD) + #expect(invalidMode.type != .whiteout) + #endif + + // All file-type bits set + invalidMode = FileMode(rawValue: S_IFMT) + #expect(invalidMode.type != .directory) + #expect(invalidMode.type != .characterSpecial) + #expect(invalidMode.type != .blockSpecial) + #expect(invalidMode.type != .regular) + #expect(invalidMode.type != .pipe) + #expect(invalidMode.type != .symbolicLink) + #expect(invalidMode.type != .socket) + #if SYSTEM_PACKAGE_DARWIN || os(FreeBSD) + #expect(invalidMode.type != .whiteout) + #endif + + // FileMode(type:permissions:) masks its inputs so + // they don't accidentally modify the other bits. + let emptyPermissions = FileMode(type: FileType(rawValue: ~0), permissions: []) + #expect(emptyPermissions.permissions.isEmpty) + #expect(emptyPermissions.type == FileType(rawValue: S_IFMT)) + #expect(emptyPermissions == invalidMode) + + let regularFile = FileMode(type: .regular, permissions: FilePermissions(rawValue: ~0)) + #expect(regularFile.type == .regular) + #expect(regularFile.permissions == FilePermissions(rawValue: 0o7777)) + #expect(regularFile.permissions == [ + .ownerReadWriteExecute, + .groupReadWriteExecute, + .otherReadWriteExecute, + .setUserID, .setGroupID, .saveText + ]) + + // Setting properties should not modify the other bits, either. + var mode = FileMode(rawValue: 0) + mode.type = FileType(rawValue: ~0) + #expect(mode.type == FileType(rawValue: S_IFMT)) + #expect(mode.permissions.isEmpty) + + mode.type.rawValue = 0 + #expect(mode.type == FileType(rawValue: 0)) + #expect(mode.permissions.isEmpty) + + mode.permissions = FilePermissions(rawValue: ~0) + #expect(mode.permissions == FilePermissions(rawValue: 0o7777)) + #expect(mode.type == FileType(rawValue: 0)) + + mode.permissions = [] + #expect(mode.permissions.isEmpty) + #expect(mode.type == FileType(rawValue: 0)) + } + +} +#endif diff --git a/Tests/SystemTests/StatTests.swift b/Tests/SystemTests/StatTests.swift new file mode 100644 index 00000000..af93f6ea --- /dev/null +++ b/Tests/SystemTests/StatTests.swift @@ -0,0 +1,408 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift System open source project +// +// Copyright (c) 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 +// +//===----------------------------------------------------------------------===// + +#if !os(Windows) + +import Testing + +#if SYSTEM_PACKAGE_DARWIN +import Darwin +#elseif canImport(Glibc) +import CSystem +import Glibc +#elseif canImport(Musl) +import CSystem +import Musl +#elseif canImport(WASILibc) +import CSystem +import WASILibc +#elseif canImport(Android) +import Android +#else +#error("Unsupported Platform") +#endif + +#if SYSTEM_PACKAGE +@testable import SystemPackage +#else +@testable import System +#endif + +@Suite("Stat") +private struct StatTests { + + @Test func basics() async throws { + try withTemporaryFilePath(basename: "Stat_basics") { tempDir in + let dirStatFromFilePath = try tempDir.stat() + #expect(dirStatFromFilePath.type == .directory) + + let dirFD = try FileDescriptor.open(tempDir, .readOnly) + defer { + try? dirFD.close() + } + let dirStatFromFD = try dirFD.stat() + #expect(dirStatFromFD.type == .directory) + + let dirStatFromCString = try tempDir.withPlatformString { try Stat($0) } + #expect(dirStatFromCString.type == .directory) + + #expect(dirStatFromFilePath == dirStatFromFD) + #expect(dirStatFromFD == dirStatFromCString) + + let tempFile = tempDir.appending("test.txt") + let fileFD = try FileDescriptor.open(tempFile, .readWrite, options: .create, permissions: [.ownerReadWrite, .groupRead, .otherRead]) + defer { + try? fileFD.close() + } + try fileFD.writeAll("Hello, world!".utf8) + + let fileStatFromFD = try fileFD.stat() + #expect(fileStatFromFD.type == .regular) + #expect(fileStatFromFD.permissions == [.ownerReadWrite, .groupRead, .otherRead]) + #expect(fileStatFromFD.size == "Hello, world!".utf8.count) + + let fileStatFromFilePath = try tempFile.stat() + #expect(fileStatFromFilePath.type == .regular) + + let fileStatFromCString = try tempFile.withPlatformString { try Stat($0) } + #expect(fileStatFromCString.type == .regular) + + #expect(fileStatFromFD == fileStatFromFilePath) + #expect(fileStatFromFilePath == fileStatFromCString) + } + } + + @Test + @available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *) + func followSymlinkInits() async throws { + try withTemporaryFilePath(basename: "Stat_followSymlinkInits") { tempDir in + let targetFilePath = tempDir.appending("target.txt") + let symlinkPath = tempDir.appending("symlink") + let targetFD = try FileDescriptor.open(targetFilePath, .readWrite, options: .create, permissions: .ownerReadWrite) + defer { + try? targetFD.close() + } + try targetFD.writeAll(Array(repeating: UInt8(ascii: "A"), count: 1025)) + + try targetFilePath.withPlatformString { targetPtr in + try symlinkPath.withPlatformString { symlinkPtr in + try #require(symlink(targetPtr, symlinkPtr) == 0, "\(Errno.current)") + } + } + + #if !os(WASI) // Can't open an fd to a symlink on WASI (no O_PATH) + #if SYSTEM_PACKAGE_DARWIN + let symlinkFD = try FileDescriptor.open(symlinkPath, .readOnly, options: .symlink) + #else + // Need O_PATH | O_NOFOLLOW to open the symlink directly + let symlinkFD = try FileDescriptor.open(symlinkPath, .readOnly, options: [.path, .noFollow]) + #endif + defer { + try? symlinkFD.close() + } + #endif // !os(WASI) + + let targetStat = try targetFilePath.stat() + let originalTargetAccessTime = targetStat.accessTime + + let symlinkStat = try symlinkPath.stat(followTargetSymlink: false) + let originalSymlinkAccessTime = symlinkStat.accessTime + + #expect(targetStat != symlinkStat) + #expect(targetStat.type == .regular) + #expect(symlinkStat.type == .symbolicLink) + #expect(symlinkStat.size < targetStat.size) + #expect(symlinkStat.sizeAllocated < targetStat.sizeAllocated) + + // Set each .accessTime back to its original value for comparison + + // FileDescriptor Extensions + + var stat = try targetFD.stat() + stat.accessTime = originalTargetAccessTime + #expect(stat == targetStat) + + #if !os(WASI) + stat = try symlinkFD.stat() + stat.accessTime = originalSymlinkAccessTime + #expect(stat == symlinkStat) + #endif + + // Initializing Stat with FileDescriptor + + stat = try Stat(targetFD) + stat.accessTime = originalTargetAccessTime + #expect(stat == targetStat) + + #if !os(WASI) + stat = try Stat(symlinkFD) + stat.accessTime = originalSymlinkAccessTime + #expect(stat == symlinkStat) + #endif + + // FilePath Extensions + + stat = try symlinkPath.stat(followTargetSymlink: true) + stat.accessTime = originalTargetAccessTime + #expect(stat == targetStat) + + stat = try symlinkPath.stat(followTargetSymlink: false) + stat.accessTime = originalSymlinkAccessTime + #expect(stat == symlinkStat) + + // Initializing Stat with UnsafePointer + + try symlinkPath.withPlatformString { pathPtr in + stat = try Stat(pathPtr, followTargetSymlink: true) + stat.accessTime = originalTargetAccessTime + #expect(stat == targetStat) + + stat = try Stat(pathPtr, followTargetSymlink: false) + stat.accessTime = originalSymlinkAccessTime + #expect(stat == symlinkStat) + } + + // Initializing Stat with FilePath + + stat = try Stat(symlinkPath, followTargetSymlink: true) + stat.accessTime = originalTargetAccessTime + #expect(stat == targetStat) + + stat = try Stat(symlinkPath, followTargetSymlink: false) + stat.accessTime = originalSymlinkAccessTime + #expect(stat == symlinkStat) + + // Initializing Stat with String + + stat = try Stat(symlinkPath.string, followTargetSymlink: true) + stat.accessTime = originalTargetAccessTime + #expect(stat == targetStat) + + stat = try Stat(symlinkPath.string, followTargetSymlink: false) + stat.accessTime = originalSymlinkAccessTime + #expect(stat == symlinkStat) + } + } + + @Test func permissions() async throws { + try withTemporaryFilePath(basename: "Stat_permissions") { tempDir in + let testFile = tempDir.appending("test.txt") + let fd = try FileDescriptor.open(testFile, .writeOnly, options: .create, permissions: [.ownerReadWrite, .groupRead, .otherRead]) + try fd.close() + + let stat = try testFile.stat() + #expect(stat.type == .regular) + #expect(stat.permissions == [.ownerReadWrite, .groupRead, .otherRead]) + + var newMode = stat.mode + newMode.permissions.insert(.ownerExecute) + try testFile.withPlatformString { pathPtr in + try #require(chmod(pathPtr, newMode.permissions.rawValue) == 0, "\(Errno.current)") + } + + let updatedStat = try testFile.stat() + #expect(updatedStat.permissions == newMode.permissions) + + newMode.permissions.remove(.ownerWriteExecute) + try testFile.withPlatformString { pathPtr in + try #require(chmod(pathPtr, newMode.permissions.rawValue) == 0, "\(Errno.current)") + } + + let readOnlyStat = try testFile.stat() + #expect(readOnlyStat.permissions == newMode.permissions) + } + } + + @Test + @available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *) + func times() async throws { + let startTime = Int64(time(nil)) + try #require(startTime >= 0, "\(Errno.current)") + let start: Duration = .seconds(startTime - 1) // A little wiggle room + try withTemporaryFilePath(basename: "Stat_times") { tempDir in + var dirStat = try tempDir.stat() + let dirAccessTime0 = dirStat.accessTime + let dirModificationTime0 = dirStat.modificationTime + let dirChangeTime0 = dirStat.changeTime + #if SYSTEM_PACKAGE_DARWIN || os(FreeBSD) + let dirCreationTime0 = dirStat.creationTime + #endif + + #expect(dirAccessTime0 >= start) + #expect(dirAccessTime0 < start + .seconds(5)) + #expect(dirModificationTime0 >= start) + #expect(dirModificationTime0 < start + .seconds(5)) + #expect(dirChangeTime0 >= start) + #expect(dirChangeTime0 < start + .seconds(5)) + #if SYSTEM_PACKAGE_DARWIN || os(FreeBSD) + #expect(dirCreationTime0 >= start) + #expect(dirCreationTime0 < start + .seconds(5)) + #endif + + // Fails intermittently if less than 5ms + usleep(10000) + + let file1 = tempDir.appending("test1.txt") + let fd1 = try FileDescriptor.open(file1, .writeOnly, options: .create, permissions: .ownerReadWrite) + defer { + try? fd1.close() + } + + dirStat = try tempDir.stat() + let dirAccessTime1 = dirStat.accessTime + let dirModificationTime1 = dirStat.modificationTime + let dirChangeTime1 = dirStat.changeTime + #if SYSTEM_PACKAGE_DARWIN || os(FreeBSD) + let dirCreationTime1 = dirStat.creationTime + #endif + + // Creating a file updates directory modification and change time. + // Access time may not be updated depending on mount options like NOATIME. + + #expect(dirModificationTime1 > dirModificationTime0) + #expect(dirChangeTime1 > dirChangeTime0) + #expect(dirAccessTime1 >= dirAccessTime0) + #if SYSTEM_PACKAGE_DARWIN || os(FreeBSD) + #expect(dirCreationTime1 == dirCreationTime0) + #endif + + usleep(10000) + + // Changing permissions only updates directory change time + + try tempDir.withPlatformString { pathPtr in + var newMode = dirStat.mode + // tempDir only starts with .ownerReadWriteExecute + newMode.permissions.insert(.groupReadWriteExecute) + try #require(chmod(pathPtr, newMode.rawValue) == 0, "\(Errno.current)") + } + + dirStat = try tempDir.stat() + let dirChangeTime2 = dirStat.changeTime + #expect(dirChangeTime2 > dirChangeTime1) + #expect(dirStat.accessTime == dirAccessTime1) + #expect(dirStat.modificationTime == dirModificationTime1) + #if SYSTEM_PACKAGE_DARWIN || os(FreeBSD) + #expect(dirStat.creationTime == dirCreationTime1) + #endif + + var stat1 = try file1.stat() + let file1AccessTime1 = stat1.accessTime + let file1ModificationTime1 = stat1.modificationTime + let file1ChangeTime1 = stat1.changeTime + #if SYSTEM_PACKAGE_DARWIN || os(FreeBSD) + let file1CreationTime1 = stat1.creationTime + #endif + + usleep(10000) + + try fd1.writeAll("Hello, world!".utf8) + stat1 = try file1.stat() + let file1AccessTime2 = stat1.accessTime + let file1ModificationTime2 = stat1.modificationTime + let file1ChangeTime2 = stat1.changeTime + #if SYSTEM_PACKAGE_DARWIN || os(FreeBSD) + let file1CreationTime2 = stat1.creationTime + #endif + + #expect(file1AccessTime2 >= file1AccessTime1) + #expect(file1ModificationTime2 > file1ModificationTime1) + #expect(file1ChangeTime2 > file1ChangeTime1) + #if SYSTEM_PACKAGE_DARWIN || os(FreeBSD) + #expect(file1CreationTime2 == file1CreationTime1) + #endif + + // Changing file metadata or content does not update directory times + + dirStat = try tempDir.stat() + #expect(dirStat.changeTime == dirChangeTime2) + #expect(dirStat.accessTime == dirAccessTime1) + #expect(dirStat.modificationTime == dirModificationTime1) + #if SYSTEM_PACKAGE_DARWIN || os(FreeBSD) + #expect(dirStat.creationTime == dirCreationTime1) + #endif + + usleep(10000) + + let file2 = tempDir.appending("test2.txt") + let fd2 = try FileDescriptor.open(file2, .writeOnly, options: .create, permissions: .ownerReadWrite) + defer { + try? fd2.close() + } + + let stat2 = try file2.stat() + #expect(stat2.accessTime > file1AccessTime2) + #expect(stat2.modificationTime > file1ModificationTime2) + #expect(stat2.changeTime > file1ChangeTime2) + #if SYSTEM_PACKAGE_DARWIN || os(FreeBSD) + #expect(stat2.creationTime > file1CreationTime2) + #endif + } + } + + #if SYSTEM_PACKAGE_DARWIN || os(FreeBSD) || os(OpenBSD) + @Test func flags() async throws { + try withTemporaryFilePath(basename: "Stat_flags") { tempDir in + let filePath = tempDir.appending("test.txt") + let fd = try FileDescriptor.open(filePath, .writeOnly, options: .create, permissions: .ownerReadWrite) + defer { + try? fd.close() + } + var stat = try fd.stat() + var flags = stat.flags + + #if SYSTEM_PACKAGE_DARWIN + let userSettableFlags: FileFlags = [ + .noDump, .userImmutable, .userAppend, + .opaque, .tracked, .hidden, + /* .dataVault (throws EPERM when testing) */ + ] + #elseif os(FreeBSD) + let userSettableFlags: FileFlags = [ + .noDump, .userImmutable, .userAppend, + .opaque, .tracked, .hidden, + .userNoUnlink, + .offline, + .readOnly, + .reparse, + .sparse, + .system + ] + #else // os(OpenBSD) + let userSettableFlags: FileFlags = [ + .noDump, .userImmutable, .userAppend + ] + #endif + + flags.insert(userSettableFlags) + try #require(fchflags(fd.rawValue, flags.rawValue) == 0, "\(Errno.current)") + + stat = try fd.stat() + #expect(stat.flags == flags) + + flags.remove(userSettableFlags) + try #require(fchflags(fd.rawValue, flags.rawValue) == 0, "\(Errno.current)") + + stat = try fd.stat() + #expect(stat.flags == flags) + } + } + #endif + +} + +#if !SYSTEM_PACKAGE_DARWIN && !os(WASI) +private extension FileDescriptor.OpenOptions { + static var path: Self { Self(rawValue: O_PATH) } +} +#endif + +#endif From a07696ace3ea8ce5448543543943a2ec8e84ba04 Mon Sep 17 00:00:00 2001 From: Jonathan Flat Date: Tue, 26 Aug 2025 17:23:41 -0600 Subject: [PATCH 412/427] Fix AT_RESOLVE_BENEATH availability --- Sources/System/FileSystem/Stat.swift | 3 ++- Sources/System/Internals/Constants.swift | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Sources/System/FileSystem/Stat.swift b/Sources/System/FileSystem/Stat.swift index 76f37b0a..4e857273 100644 --- a/Sources/System/FileSystem/Stat.swift +++ b/Sources/System/FileSystem/Stat.swift @@ -76,12 +76,13 @@ public struct Stat: RawRepresentable, Sendable { public static var symlinkNoFollowAny: Flags { Flags(rawValue: _AT_SYMLINK_NOFOLLOW_ANY) } #endif - #if SYSTEM_PACKAGE_DARWIN || os(FreeBSD) + #if canImport(Darwin, _version: 346) || os(FreeBSD) /// If the path does not reside in the hierarchy beneath the starting directory, return an error. /// /// The corresponding C constant is `AT_RESOLVE_BENEATH`. /// - Note: Only available on Darwin and FreeBSD. @_alwaysEmitIntoClient + @available(macOS 26.0, iOS 26.0, tvOS 26.0, watchOS 26.0, visionOS 26.0, *) public static var resolveBeneath: Flags { Flags(rawValue: _AT_RESOLVE_BENEATH) } #endif diff --git a/Sources/System/Internals/Constants.swift b/Sources/System/Internals/Constants.swift index 8805ffad..37b7da33 100644 --- a/Sources/System/Internals/Constants.swift +++ b/Sources/System/Internals/Constants.swift @@ -658,7 +658,7 @@ internal var _AT_SYMLINK_NOFOLLOW: CInt { AT_SYMLINK_FOLLOW } internal var _AT_SYMLINK_NOFOLLOW_ANY: CInt { AT_SYMLINK_NOFOLLOW_ANY } #endif -#if SYSTEM_PACKAGE_DARWIN || os(FreeBSD) +#if canImport(Darwin, _version: 346) || os(FreeBSD) @_alwaysEmitIntoClient internal var _AT_RESOLVE_BENEATH: CInt { AT_RESOLVE_BENEATH } #endif From 905f9f842dc6567cc29bee7470f8769918b84bde Mon Sep 17 00:00:00 2001 From: Jonathan Flat Date: Tue, 26 Aug 2025 17:27:22 -0600 Subject: [PATCH 413/427] Expose C timespec properties until UTCClock can be used --- Sources/System/FileSystem/Stat.swift | 135 ++++++++++++++++----------- Tests/SystemTests/StatTests.swift | 120 ++++++++++++++---------- 2 files changed, 150 insertions(+), 105 deletions(-) diff --git a/Sources/System/FileSystem/Stat.swift b/Sources/System/FileSystem/Stat.swift index 4e857273..9f2e5503 100644 --- a/Sources/System/FileSystem/Stat.swift +++ b/Sources/System/FileSystem/Stat.swift @@ -410,119 +410,146 @@ public struct Stat: RawRepresentable, Sendable { 512 * blocksAllocated } - // TODO: jflat - Change time properties to UTCClock.Instant when possible. - - /// Time of last access, given as a `Duration` since the Epoch + /// Time of last access, given as a C `timespec` since the Epoch. /// /// The corresponding C property is `st_atim` (or `st_atimespec` on Darwin). - @available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *) - public var accessTime: Duration { + @_alwaysEmitIntoClient + public var st_atim: timespec { get { #if SYSTEM_PACKAGE_DARWIN - let timespec = rawValue.st_atimespec + rawValue.st_atimespec #else - let timespec = rawValue.st_atim + rawValue.st_atim #endif - return .seconds(timespec.tv_sec) + .nanoseconds(timespec.tv_nsec) } set { - let (seconds, attoseconds) = newValue.components - let timespec = timespec( - tv_sec: numericCast(seconds), - tv_nsec: numericCast(attoseconds / 1_000_000_000) - ) #if SYSTEM_PACKAGE_DARWIN - rawValue.st_atimespec = timespec + rawValue.st_atimespec = newValue #else - rawValue.st_atim = timespec + rawValue.st_atim = newValue #endif } } - /// Time of last modification, given as a `Duration` since the Epoch + /// Time of last modification, given as a C `timespec` since the Epoch. /// /// The corresponding C property is `st_mtim` (or `st_mtimespec` on Darwin). - @available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *) - public var modificationTime: Duration { + @_alwaysEmitIntoClient + public var st_mtim: timespec { get { #if SYSTEM_PACKAGE_DARWIN - let timespec = rawValue.st_mtimespec + rawValue.st_mtimespec #else - let timespec = rawValue.st_mtim + rawValue.st_mtim #endif - return .seconds(timespec.tv_sec) + .nanoseconds(timespec.tv_nsec) } set { - let (seconds, attoseconds) = newValue.components - let timespec = timespec( - tv_sec: numericCast(seconds), - tv_nsec: numericCast(attoseconds / 1_000_000_000) - ) #if SYSTEM_PACKAGE_DARWIN - rawValue.st_mtimespec = timespec + rawValue.st_mtimespec = newValue #else - rawValue.st_mtim = timespec + rawValue.st_mtim = newValue #endif } } - /// Time of last status (inode) change, given as a `Duration` since the Epoch + /// Time of last status (inode) change, given as a C `timespec` since the Epoch. /// /// The corresponding C property is `st_ctim` (or `st_ctimespec` on Darwin). - @available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *) - public var changeTime: Duration { + @_alwaysEmitIntoClient + public var st_ctim: timespec { get { #if SYSTEM_PACKAGE_DARWIN - let timespec = rawValue.st_ctimespec + rawValue.st_ctimespec #else - let timespec = rawValue.st_ctim + rawValue.st_ctim #endif - return .seconds(timespec.tv_sec) + .nanoseconds(timespec.tv_nsec) } set { - let (seconds, attoseconds) = newValue.components - let timespec = timespec( - tv_sec: numericCast(seconds), - tv_nsec: numericCast(attoseconds / 1_000_000_000) - ) #if SYSTEM_PACKAGE_DARWIN - rawValue.st_ctimespec = timespec + rawValue.st_ctimespec = newValue #else - rawValue.st_ctim = timespec + rawValue.st_ctim = newValue #endif } } #if SYSTEM_PACKAGE_DARWIN || os(FreeBSD) - /// Time of file creation, given as a `Duration` since the Epoch + /// Time of file creation, given as a C `timespec` since the Epoch. /// /// The corresponding C property is `st_birthtim` (or `st_birthtimespec` on Darwin). /// - Note: Only available on Darwin and FreeBSD. - @available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *) - public var creationTime: Duration { + @_alwaysEmitIntoClient + public var st_birthtim: timespec { get { #if SYSTEM_PACKAGE_DARWIN - let timespec = rawValue.st_birthtimespec + rawValue.st_birthtimespec #else - let timespec = rawValue.st_birthtim + rawValue.st_birthtim #endif - return .seconds(timespec.tv_sec) + .nanoseconds(timespec.tv_nsec) } set { - let (seconds, attoseconds) = newValue.components - let timespec = timespec( - tv_sec: numericCast(seconds), - tv_nsec: numericCast(attoseconds / 1_000_000_000) - ) #if SYSTEM_PACKAGE_DARWIN - rawValue.st_birthtimespec = timespec + rawValue.st_birthtimespec = newValue #else - rawValue.st_birthtim = timespec + rawValue.st_birthtim = newValue #endif } } #endif + // TODO: jflat - Change time properties to UTCClock.Instant when possible. + +// /// Time of last access, given as a `UTCClock.Instant` +// /// +// /// The corresponding C property is `st_atim` (or `st_atimespec` on Darwin). +// public var accessTime: UTCClock.Instant { +// get { +// UTCClock.systemEpoch.advanced(by: Duration(st_atim)) +// } +// set { +// st_atim = timespec(UTCClock.systemEpoch.duration(to: newValue)) +// } +// } +// +// /// Time of last modification, given as a `UTCClock.Instant` +// /// +// /// The corresponding C property is `st_mtim` (or `st_mtimespec` on Darwin). +// public var modificationTime: UTCClock.Instant { +// get { +// UTCClock.systemEpoch.advanced(by: Duration(st_mtim)) +// } +// set { +// st_mtim = timespec(UTCClock.systemEpoch.duration(to: newValue)) +// } +// } +// +// /// Time of last status (inode) change, given as a `UTCClock.Instant` +// /// +// /// The corresponding C property is `st_ctim` (or `st_ctimespec` on Darwin). +// public var changeTime: UTCClock.Instant { +// get { +// UTCClock.systemEpoch.advanced(by: Duration(st_ctim)) +// } +// set { +// st_ctim = timespec(UTCClock.systemEpoch.duration(to: newValue)) +// } +// } +// +// #if SYSTEM_PACKAGE_DARWIN || os(FreeBSD) +// /// Time of file creation, given as a `UTCClock.Instant` +// /// +// /// The corresponding C property is `st_birthtim` (or `st_birthtimespec` on Darwin). +// /// - Note: Only available on Darwin and FreeBSD. +// public var creationTime: UTCClock.Instant { +// get { +// UTCClock.systemEpoch.advanced(by: Duration(st_birthtim)) +// } +// set { +// st_birthtim = timespec(UTCClock.systemEpoch.duration(to: newValue)) +// } +// } +// #endif + #if SYSTEM_PACKAGE_DARWIN || os(FreeBSD) || os(OpenBSD) /// File flags /// diff --git a/Tests/SystemTests/StatTests.swift b/Tests/SystemTests/StatTests.swift index af93f6ea..58c34413 100644 --- a/Tests/SystemTests/StatTests.swift +++ b/Tests/SystemTests/StatTests.swift @@ -111,10 +111,10 @@ private struct StatTests { #endif // !os(WASI) let targetStat = try targetFilePath.stat() - let originalTargetAccessTime = targetStat.accessTime + let originalTargetAccessTime = targetStat.st_atim let symlinkStat = try symlinkPath.stat(followTargetSymlink: false) - let originalSymlinkAccessTime = symlinkStat.accessTime + let originalSymlinkAccessTime = symlinkStat.st_atim #expect(targetStat != symlinkStat) #expect(targetStat.type == .regular) @@ -122,72 +122,72 @@ private struct StatTests { #expect(symlinkStat.size < targetStat.size) #expect(symlinkStat.sizeAllocated < targetStat.sizeAllocated) - // Set each .accessTime back to its original value for comparison + // Set each .st_atim back to its original value for comparison // FileDescriptor Extensions var stat = try targetFD.stat() - stat.accessTime = originalTargetAccessTime + stat.st_atim = originalTargetAccessTime #expect(stat == targetStat) #if !os(WASI) stat = try symlinkFD.stat() - stat.accessTime = originalSymlinkAccessTime + stat.st_atim = originalSymlinkAccessTime #expect(stat == symlinkStat) #endif // Initializing Stat with FileDescriptor stat = try Stat(targetFD) - stat.accessTime = originalTargetAccessTime + stat.st_atim = originalTargetAccessTime #expect(stat == targetStat) #if !os(WASI) stat = try Stat(symlinkFD) - stat.accessTime = originalSymlinkAccessTime + stat.st_atim = originalSymlinkAccessTime #expect(stat == symlinkStat) #endif // FilePath Extensions stat = try symlinkPath.stat(followTargetSymlink: true) - stat.accessTime = originalTargetAccessTime + stat.st_atim = originalTargetAccessTime #expect(stat == targetStat) stat = try symlinkPath.stat(followTargetSymlink: false) - stat.accessTime = originalSymlinkAccessTime + stat.st_atim = originalSymlinkAccessTime #expect(stat == symlinkStat) // Initializing Stat with UnsafePointer try symlinkPath.withPlatformString { pathPtr in stat = try Stat(pathPtr, followTargetSymlink: true) - stat.accessTime = originalTargetAccessTime + stat.st_atim = originalTargetAccessTime #expect(stat == targetStat) stat = try Stat(pathPtr, followTargetSymlink: false) - stat.accessTime = originalSymlinkAccessTime + stat.st_atim = originalSymlinkAccessTime #expect(stat == symlinkStat) } // Initializing Stat with FilePath stat = try Stat(symlinkPath, followTargetSymlink: true) - stat.accessTime = originalTargetAccessTime + stat.st_atim = originalTargetAccessTime #expect(stat == targetStat) stat = try Stat(symlinkPath, followTargetSymlink: false) - stat.accessTime = originalSymlinkAccessTime + stat.st_atim = originalSymlinkAccessTime #expect(stat == symlinkStat) // Initializing Stat with String stat = try Stat(symlinkPath.string, followTargetSymlink: true) - stat.accessTime = originalTargetAccessTime + stat.st_atim = originalTargetAccessTime #expect(stat == targetStat) stat = try Stat(symlinkPath.string, followTargetSymlink: false) - stat.accessTime = originalSymlinkAccessTime + stat.st_atim = originalSymlinkAccessTime #expect(stat == symlinkStat) } } @@ -222,29 +222,30 @@ private struct StatTests { } @Test - @available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *) func times() async throws { - let startTime = Int64(time(nil)) - try #require(startTime >= 0, "\(Errno.current)") - let start: Duration = .seconds(startTime - 1) // A little wiggle room + var start = timespec() + try #require(clock_gettime(CLOCK_REALTIME, &start) == 0, "\(Errno.current)") + start.tv_sec -= 1 // A little wiggle room try withTemporaryFilePath(basename: "Stat_times") { tempDir in var dirStat = try tempDir.stat() - let dirAccessTime0 = dirStat.accessTime - let dirModificationTime0 = dirStat.modificationTime - let dirChangeTime0 = dirStat.changeTime + let dirAccessTime0 = dirStat.st_atim + let dirModificationTime0 = dirStat.st_mtim + let dirChangeTime0 = dirStat.st_ctim #if SYSTEM_PACKAGE_DARWIN || os(FreeBSD) - let dirCreationTime0 = dirStat.creationTime + let dirCreationTime0 = dirStat.st_birthtim #endif + var startUpperBound = start + startUpperBound.tv_sec += 5 #expect(dirAccessTime0 >= start) - #expect(dirAccessTime0 < start + .seconds(5)) + #expect(dirAccessTime0 < startUpperBound) #expect(dirModificationTime0 >= start) - #expect(dirModificationTime0 < start + .seconds(5)) + #expect(dirModificationTime0 < startUpperBound) #expect(dirChangeTime0 >= start) - #expect(dirChangeTime0 < start + .seconds(5)) + #expect(dirChangeTime0 < startUpperBound) #if SYSTEM_PACKAGE_DARWIN || os(FreeBSD) #expect(dirCreationTime0 >= start) - #expect(dirCreationTime0 < start + .seconds(5)) + #expect(dirCreationTime0 < startUpperBound) #endif // Fails intermittently if less than 5ms @@ -257,11 +258,11 @@ private struct StatTests { } dirStat = try tempDir.stat() - let dirAccessTime1 = dirStat.accessTime - let dirModificationTime1 = dirStat.modificationTime - let dirChangeTime1 = dirStat.changeTime + let dirAccessTime1 = dirStat.st_atim + let dirModificationTime1 = dirStat.st_mtim + let dirChangeTime1 = dirStat.st_ctim #if SYSTEM_PACKAGE_DARWIN || os(FreeBSD) - let dirCreationTime1 = dirStat.creationTime + let dirCreationTime1 = dirStat.st_birthtim #endif // Creating a file updates directory modification and change time. @@ -286,31 +287,31 @@ private struct StatTests { } dirStat = try tempDir.stat() - let dirChangeTime2 = dirStat.changeTime + let dirChangeTime2 = dirStat.st_ctim #expect(dirChangeTime2 > dirChangeTime1) - #expect(dirStat.accessTime == dirAccessTime1) - #expect(dirStat.modificationTime == dirModificationTime1) + #expect(dirStat.st_atim == dirAccessTime1) + #expect(dirStat.st_mtim == dirModificationTime1) #if SYSTEM_PACKAGE_DARWIN || os(FreeBSD) - #expect(dirStat.creationTime == dirCreationTime1) + #expect(dirStat.st_birthtim == dirCreationTime1) #endif var stat1 = try file1.stat() - let file1AccessTime1 = stat1.accessTime - let file1ModificationTime1 = stat1.modificationTime - let file1ChangeTime1 = stat1.changeTime + let file1AccessTime1 = stat1.st_atim + let file1ModificationTime1 = stat1.st_mtim + let file1ChangeTime1 = stat1.st_ctim #if SYSTEM_PACKAGE_DARWIN || os(FreeBSD) - let file1CreationTime1 = stat1.creationTime + let file1CreationTime1 = stat1.st_birthtim #endif usleep(10000) try fd1.writeAll("Hello, world!".utf8) stat1 = try file1.stat() - let file1AccessTime2 = stat1.accessTime - let file1ModificationTime2 = stat1.modificationTime - let file1ChangeTime2 = stat1.changeTime + let file1AccessTime2 = stat1.st_atim + let file1ModificationTime2 = stat1.st_mtim + let file1ChangeTime2 = stat1.st_ctim #if SYSTEM_PACKAGE_DARWIN || os(FreeBSD) - let file1CreationTime2 = stat1.creationTime + let file1CreationTime2 = stat1.st_birthtim #endif #expect(file1AccessTime2 >= file1AccessTime1) @@ -323,11 +324,11 @@ private struct StatTests { // Changing file metadata or content does not update directory times dirStat = try tempDir.stat() - #expect(dirStat.changeTime == dirChangeTime2) - #expect(dirStat.accessTime == dirAccessTime1) - #expect(dirStat.modificationTime == dirModificationTime1) + #expect(dirStat.st_ctim == dirChangeTime2) + #expect(dirStat.st_atim == dirAccessTime1) + #expect(dirStat.st_mtim == dirModificationTime1) #if SYSTEM_PACKAGE_DARWIN || os(FreeBSD) - #expect(dirStat.creationTime == dirCreationTime1) + #expect(dirStat.st_birthtim == dirCreationTime1) #endif usleep(10000) @@ -339,11 +340,11 @@ private struct StatTests { } let stat2 = try file2.stat() - #expect(stat2.accessTime > file1AccessTime2) - #expect(stat2.modificationTime > file1ModificationTime2) - #expect(stat2.changeTime > file1ChangeTime2) + #expect(stat2.st_atim > file1AccessTime2) + #expect(stat2.st_mtim > file1ModificationTime2) + #expect(stat2.st_ctim > file1ChangeTime2) #if SYSTEM_PACKAGE_DARWIN || os(FreeBSD) - #expect(stat2.creationTime > file1CreationTime2) + #expect(stat2.st_birthtim > file1CreationTime2) #endif } } @@ -405,4 +406,21 @@ private extension FileDescriptor.OpenOptions { } #endif +// Comparison operators for timespec until UTCClock.Instant properties are available +private func >= (lhs: timespec, rhs: timespec) -> Bool { + (lhs.tv_sec, lhs.tv_nsec) >= (rhs.tv_sec, rhs.tv_nsec) +} + +private func < (lhs: timespec, rhs: timespec) -> Bool { + (lhs.tv_sec, lhs.tv_nsec) < (rhs.tv_sec, rhs.tv_nsec) +} + +private func > (lhs: timespec, rhs: timespec) -> Bool { + (lhs.tv_sec, lhs.tv_nsec) > (rhs.tv_sec, rhs.tv_nsec) +} + +private func == (lhs: timespec, rhs: timespec) -> Bool { + lhs.tv_sec == rhs.tv_sec && lhs.tv_nsec == rhs.tv_nsec +} + #endif From 2f5b12b854f5003a7b7950c00e79a15bbb5f7349 Mon Sep 17 00:00:00 2001 From: Jonathan Flat Date: Mon, 29 Sep 2025 11:59:27 -0600 Subject: [PATCH 414/427] Updates for proposal v3 --- Sources/System/FileSystem/FileFlags.swift | 48 +++++++++---------- Sources/System/FileSystem/FileMode.swift | 2 +- Sources/System/FileSystem/FileType.swift | 20 +++++--- Sources/System/FileSystem/Identifiers.swift | 53 ++++++++++++++------- Sources/System/FileSystem/Stat.swift | 34 ++++++++++--- Sources/System/Internals/Constants.swift | 18 +++---- Tests/SystemTests/FileModeTests.swift | 4 +- 7 files changed, 111 insertions(+), 68 deletions(-) diff --git a/Sources/System/FileSystem/FileFlags.swift b/Sources/System/FileSystem/FileFlags.swift index f89cbc15..fd714cb8 100644 --- a/Sources/System/FileSystem/FileFlags.swift +++ b/Sources/System/FileSystem/FileFlags.swift @@ -21,12 +21,12 @@ // | systemImmutable | SF_IMMUTABLE | SF_IMMUTABLE | SF_IMMUTABLE | // | systemAppend | SF_APPEND | SF_APPEND | SF_APPEND | // | opaque | UF_OPAQUE | UF_OPAQUE | N/A | -// | compressed | UF_COMPRESSED | UF_COMPRESSED | N/A | -// | tracked | UF_TRACKED | UF_TRACKED | N/A | // | hidden | UF_HIDDEN | UF_HIDDEN | N/A | -// | restricted | SF_RESTRICTED | SF_RESTRICTED | N/A | // | systemNoUnlink | SF_NOUNLINK | SF_NOUNLINK | N/A | +// | compressed | UF_COMPRESSED | N/A | N/A | +// | tracked | UF_TRACKED | N/A | N/A | // | dataVault | UF_DATAVAULT | N/A | N/A | +// | restricted | SF_RESTRICTED | N/A | N/A | // | firmlink | SF_FIRMLINK | N/A | N/A | // | dataless | SF_DATALESS | N/A | N/A | // | userNoUnlink | N/A | UF_NOUNLINK | N/A | @@ -114,20 +114,6 @@ public struct FileFlags: OptionSet, Sendable, Hashable, Codable { @_alwaysEmitIntoClient public static var opaque: FileFlags { FileFlags(rawValue: _UF_OPAQUE) } - /// File is compressed at the file system level. - /// - /// The corresponding C constant is `UF_COMPRESSED`. - /// - Note: This flag is read-only. Attempting to change it will result in undefined behavior. - @_alwaysEmitIntoClient - public static var compressed: FileFlags { FileFlags(rawValue: _UF_COMPRESSED) } - - /// File is tracked for the purpose of document IDs. - /// - /// The corresponding C constant is `UF_TRACKED`. - /// - Note: This flag may be changed by the file owner or superuser. - @_alwaysEmitIntoClient - public static var tracked: FileFlags { FileFlags(rawValue: _UF_TRACKED) } - /// File should not be displayed in a GUI. /// /// The corresponding C constant is `UF_HIDDEN`. @@ -135,13 +121,6 @@ public struct FileFlags: OptionSet, Sendable, Hashable, Codable { @_alwaysEmitIntoClient public static var hidden: FileFlags { FileFlags(rawValue: _UF_HIDDEN) } - /// File requires an entitlement for writing. - /// - /// The corresponding C constant is `SF_RESTRICTED`. - /// - Note: This flag may only be changed by the superuser. - @_alwaysEmitIntoClient - public static var restricted: FileFlags { FileFlags(rawValue: _SF_RESTRICTED) } - /// File may not be removed or renamed. /// /// The corresponding C constant is `SF_NOUNLINK`. @@ -153,6 +132,20 @@ public struct FileFlags: OptionSet, Sendable, Hashable, Codable { // MARK: Flags Available on Darwin only #if SYSTEM_PACKAGE_DARWIN + /// File is compressed at the file system level. + /// + /// The corresponding C constant is `UF_COMPRESSED`. + /// - Note: This flag is read-only. Attempting to change it will result in undefined behavior. + @_alwaysEmitIntoClient + public static var compressed: FileFlags { FileFlags(rawValue: _UF_COMPRESSED) } + + /// File is tracked for the purpose of document IDs. + /// + /// The corresponding C constant is `UF_TRACKED`. + /// - Note: This flag may be changed by the file owner or superuser. + @_alwaysEmitIntoClient + public static var tracked: FileFlags { FileFlags(rawValue: _UF_TRACKED) } + /// File requires an entitlement for reading and writing. /// /// The corresponding C constant is `UF_DATAVAULT`. @@ -160,6 +153,13 @@ public struct FileFlags: OptionSet, Sendable, Hashable, Codable { @_alwaysEmitIntoClient public static var dataVault: FileFlags { FileFlags(rawValue: _UF_DATAVAULT) } + /// File requires an entitlement for writing. + /// + /// The corresponding C constant is `SF_RESTRICTED`. + /// - Note: This flag may only be changed by the superuser. + @_alwaysEmitIntoClient + public static var restricted: FileFlags { FileFlags(rawValue: _SF_RESTRICTED) } + /// File is a firmlink. /// /// Firmlinks are used by macOS to create transparent links between diff --git a/Sources/System/FileSystem/FileMode.swift b/Sources/System/FileSystem/FileMode.swift index 14ae30ea..9a099476 100644 --- a/Sources/System/FileSystem/FileMode.swift +++ b/Sources/System/FileSystem/FileMode.swift @@ -44,7 +44,7 @@ public struct FileMode: RawRepresentable, Sendable, Hashable, Codable { /// The file's permissions, from the mode's permission bits. /// - /// Setting this property will mask the `newValue` with the permissions bit mask `0o7777`. + /// Setting this property will mask the `newValue` with the permissions bit mask `ALLPERMS`. @_alwaysEmitIntoClient public var permissions: FilePermissions { get { FilePermissions(rawValue: rawValue & _MODE_PERMISSIONS_MASK) } diff --git a/Sources/System/FileSystem/FileType.swift b/Sources/System/FileSystem/FileType.swift index 91718880..638b1472 100644 --- a/Sources/System/FileSystem/FileType.swift +++ b/Sources/System/FileSystem/FileType.swift @@ -18,7 +18,7 @@ // | characterSpecial | S_IFCHR | // | blockSpecial | S_IFBLK | // | regular | S_IFREG | -// | pipe | S_IFIFO | +// | fifo | S_IFIFO | // | symbolicLink | S_IFLNK | // | socket | S_IFSOCK | // |------------------|---------------------| @@ -41,11 +41,17 @@ public struct FileType: RawRepresentable, Sendable, Hashable, Codable { @_alwaysEmitIntoClient public var rawValue: CInterop.Mode - /// Creates a strongly-typed file type from the raw C value. + /// Creates a strongly-typed file type from the raw C `mode_t`. /// - /// - Note: `rawValue` should only contain the mode's file-type bits. Otherwise, - /// use `FileMode(rawValue:)` to get a strongly-typed `FileMode`, then - /// call `.type` to get the properly masked `FileType`. + /// - Note: This initializer stores the `rawValue` directly and **does not** + /// mask the value with `S_IFMT`. If the supplied `rawValue` contains bits + /// outside of the `S_IFMT` mask, the resulting `FileType` will not compare + /// equal to constants like `.directory` and `.symbolicLink`, which may + /// be unexpected. + /// + /// If you're unsure whether the `mode_t` contains bits outside of `S_IFMT`, + /// you can use `FileMode(rawValue:)` instead to get a strongly-typed + /// `FileMode`, then call `.type` to get the properly masked `FileType`. @_alwaysEmitIntoClient public init(rawValue: CInterop.Mode) { self.rawValue = rawValue } @@ -73,11 +79,11 @@ public struct FileType: RawRepresentable, Sendable, Hashable, Codable { @_alwaysEmitIntoClient public static var regular: FileType { FileType(rawValue: _S_IFREG) } - /// FIFO (or pipe) + /// FIFO (or named pipe) /// /// The corresponding C constant is `S_IFIFO`. @_alwaysEmitIntoClient - public static var pipe: FileType { FileType(rawValue: _S_IFIFO) } + public static var fifo: FileType { FileType(rawValue: _S_IFIFO) } /// Symbolic link /// diff --git a/Sources/System/FileSystem/Identifiers.swift b/Sources/System/FileSystem/Identifiers.swift index df9a01ae..7620b601 100644 --- a/Sources/System/FileSystem/Identifiers.swift +++ b/Sources/System/FileSystem/Identifiers.swift @@ -19,9 +19,13 @@ public struct UserID: RawRepresentable, Sendable, Hashable, Codable { @_alwaysEmitIntoClient public var rawValue: CInterop.UserID - /// Creates a strongly-typed `GroupID` from the raw C value. + /// Creates a strongly-typed `UserID` from the raw C value. @_alwaysEmitIntoClient public init(rawValue: CInterop.UserID) { self.rawValue = rawValue } + + /// Creates a strongly-typed `UserID` from the raw C value. + @_alwaysEmitIntoClient + public init(_ rawValue: CInterop.UserID) { self.rawValue = rawValue } } /// A Swift wrapper of the C `gid_t` type. @@ -36,6 +40,10 @@ public struct GroupID: RawRepresentable, Sendable, Hashable, Codable { /// Creates a strongly-typed `GroupID` from the raw C value. @_alwaysEmitIntoClient public init(rawValue: CInterop.GroupID) { self.rawValue = rawValue } + + /// Creates a strongly-typed `GroupID` from the raw C value. + @_alwaysEmitIntoClient + public init(_ rawValue: CInterop.GroupID) { self.rawValue = rawValue } } /// A Swift wrapper of the C `dev_t` type. @@ -51,26 +59,31 @@ public struct DeviceID: RawRepresentable, Sendable, Hashable, Codable { @_alwaysEmitIntoClient public init(rawValue: CInterop.DeviceID) { self.rawValue = rawValue } - - /// Creates a `DeviceID` from the given major and minor device numbers. - /// - /// The corresponding C function is `makedev()`. + /// Creates a strongly-typed `DeviceID` from the raw C value. @_alwaysEmitIntoClient - public static func make(major: CUnsignedInt, minor: CUnsignedInt) -> DeviceID { - DeviceID(rawValue: system_makedev(major, minor)) - } + public init(_ rawValue: CInterop.DeviceID) { self.rawValue = rawValue } - /// The major device number - /// - /// The corresponding C function is `major()`. - @_alwaysEmitIntoClient - public var major: CInt { system_major(rawValue) } + // TODO: API review for ID wrapper functionality - /// The minor device number - /// - /// The corresponding C function is `minor()`. - @_alwaysEmitIntoClient - public var minor: CInt { system_minor(rawValue) } +// /// Creates a `DeviceID` from the given major and minor device numbers. +// /// +// /// The corresponding C function is `makedev()`. +// @_alwaysEmitIntoClient +// private static func make(major: CUnsignedInt, minor: CUnsignedInt) -> DeviceID { +// DeviceID(rawValue: system_makedev(major, minor)) +// } +// +// /// The major device number +// /// +// /// The corresponding C function is `major()`. +// @_alwaysEmitIntoClient +// private var major: CInt { system_major(rawValue) } +// +// /// The minor device number +// /// +// /// The corresponding C function is `minor()`. +// @_alwaysEmitIntoClient +// private var minor: CInt { system_minor(rawValue) } } /// A Swift wrapper of the C `ino_t` type. @@ -85,5 +98,9 @@ public struct Inode: RawRepresentable, Sendable, Hashable, Codable { /// Creates a strongly-typed `Inode` from the raw C value. @_alwaysEmitIntoClient public init(rawValue: CInterop.Inode) { self.rawValue = rawValue } + + /// Creates a strongly-typed `Inode` from the raw C value. + @_alwaysEmitIntoClient + public init(_ rawValue: CInterop.Inode) { self.rawValue = rawValue } } #endif // !os(Windows) diff --git a/Sources/System/FileSystem/Stat.swift b/Sources/System/FileSystem/Stat.swift index 9f2e5503..6bc78ab3 100644 --- a/Sources/System/FileSystem/Stat.swift +++ b/Sources/System/FileSystem/Stat.swift @@ -122,7 +122,7 @@ public struct Stat: RawRepresentable, Sendable { }.get() } - /// Creates a `Stat` struct from an`UnsafePointer` path. + /// Creates a `Stat` struct from an `UnsafePointer` path. /// /// `followTargetSymlink` determines the behavior if `path` ends with a symbolic link. /// By default, `followTargetSymlink` is `true` and this initializer behaves like `stat()`. @@ -314,6 +314,9 @@ public struct Stat: RawRepresentable, Sendable { } /// File type for the given mode + /// + /// - Note: This property is equivalent to `mode.type`. Modifying this + /// property will update the underlying `st_mode` accordingly. @_alwaysEmitIntoClient public var type: FileType { get { mode.type } @@ -325,6 +328,9 @@ public struct Stat: RawRepresentable, Sendable { } /// File permissions for the given mode + /// + /// - Note: This property is equivalent to `mode.permissions`. Modifying + /// this property will update the underlying `st_mode` accordingly. @_alwaysEmitIntoClient public var permissions: FilePermissions { get { mode.permissions } @@ -365,7 +371,7 @@ public struct Stat: RawRepresentable, Sendable { /// Device ID (if special file) /// /// For character or block special files, the returned `DeviceID` may have - /// meaningful `.major` and `.minor` values. For non-special files, this + /// meaningful major and minor values. For non-special files, this /// property is usually meaningless and often set to 0. /// /// The corresponding C property is `st_rdev`. @@ -377,6 +383,12 @@ public struct Stat: RawRepresentable, Sendable { /// Total size, in bytes /// + /// The semantics of this property are tied to the underlying C `st_size` field, + /// which can have file system-dependent behavior. For example, this property + /// can return different values for a file's data fork and resource fork, and some + /// file systems report logical size rather than actual disk usage for compressed + /// or cloned files. + /// /// The corresponding C property is `st_size`. @_alwaysEmitIntoClient public var size: Int64 { @@ -395,6 +407,9 @@ public struct Stat: RawRepresentable, Sendable { /// Number of 512-byte blocks allocated /// + /// The semantics of this property are tied to the underlying C `st_blocks` field, + /// which can have file system-dependent behavior. + /// /// The corresponding C property is `st_blocks`. @_alwaysEmitIntoClient public var blocksAllocated: Int64 { @@ -404,12 +419,19 @@ public struct Stat: RawRepresentable, Sendable { /// Total size allocated, in bytes /// + /// The semantics of this property are tied to the underlying C `st_blocks` field, + /// which can have file system-dependent behavior. + /// /// - Note: Calculated as `512 * blocksAllocated`. @_alwaysEmitIntoClient public var sizeAllocated: Int64 { 512 * blocksAllocated } + // NOTE: "st_" property names are used for the `timespec` properties so + // we can reserve `accessTime`, `modificationTime`, etc. for potential + // `UTCClock.Instant` properties in the future. + /// Time of last access, given as a C `timespec` since the Epoch. /// /// The corresponding C property is `st_atim` (or `st_atimespec` on Darwin). @@ -497,7 +519,7 @@ public struct Stat: RawRepresentable, Sendable { } #endif - // TODO: jflat - Change time properties to UTCClock.Instant when possible. + // TODO: Investigate changing time properties to UTCClock.Instant once available. // /// Time of last access, given as a `UTCClock.Instant` // /// @@ -602,8 +624,6 @@ extension Stat: Hashable { // MARK: - CustomStringConvertible and CustomDebugStringConvertible -// TODO: jflat - // MARK: - FileDescriptor Extensions // @available(System X.Y.Z, *) @@ -673,7 +693,7 @@ extension FilePath { } } - /// Creates a `Stat` struct for the file referenced by this`FilePath` using the given `Flags`. + /// Creates a `Stat` struct for the file referenced by this `FilePath` using the given `Flags`. /// /// If `path` is relative, it is resolved against the current working directory. /// @@ -690,7 +710,7 @@ extension FilePath { ).get() } - /// Creates a `Stat` struct for the file referenced by this`FilePath` using the given `Flags`, + /// Creates a `Stat` struct for the file referenced by this `FilePath` using the given `Flags`, /// including a `FileDescriptor` to resolve a relative path. /// /// If `path` is absolute (starts with a forward slash), then `fd` is ignored. diff --git a/Sources/System/Internals/Constants.swift b/Sources/System/Internals/Constants.swift index 37b7da33..c4fda718 100644 --- a/Sources/System/Internals/Constants.swift +++ b/Sources/System/Internals/Constants.swift @@ -732,18 +732,9 @@ internal var _SF_APPEND: CInterop.FileFlags { UInt32(bitPattern: SF_APPEND) } @_alwaysEmitIntoClient internal var _UF_OPAQUE: CInterop.FileFlags { UInt32(bitPattern: UF_OPAQUE) } -@_alwaysEmitIntoClient -internal var _UF_COMPRESSED: CInterop.FileFlags { UInt32(bitPattern: UF_COMPRESSED) } - -@_alwaysEmitIntoClient -internal var _UF_TRACKED: CInterop.FileFlags { UInt32(bitPattern: UF_TRACKED) } - @_alwaysEmitIntoClient internal var _UF_HIDDEN: CInterop.FileFlags { UInt32(bitPattern: UF_HIDDEN) } -@_alwaysEmitIntoClient -internal var _SF_RESTRICTED: CInterop.FileFlags { UInt32(bitPattern: SF_RESTRICTED) } - @_alwaysEmitIntoClient internal var _SF_NOUNLINK: CInterop.FileFlags { UInt32(bitPattern: SF_NOUNLINK) } #endif @@ -751,9 +742,18 @@ internal var _SF_NOUNLINK: CInterop.FileFlags { UInt32(bitPattern: SF_NOUNLINK) // MARK: Flags Available on Darwin Only #if SYSTEM_PACKAGE_DARWIN +@_alwaysEmitIntoClient +internal var _UF_COMPRESSED: CInterop.FileFlags { UInt32(bitPattern: UF_COMPRESSED) } + +@_alwaysEmitIntoClient +internal var _UF_TRACKED: CInterop.FileFlags { UInt32(bitPattern: UF_TRACKED) } + @_alwaysEmitIntoClient internal var _UF_DATAVAULT: CInterop.FileFlags { UInt32(bitPattern: UF_DATAVAULT) } +@_alwaysEmitIntoClient +internal var _SF_RESTRICTED: CInterop.FileFlags { UInt32(bitPattern: SF_RESTRICTED) } + @_alwaysEmitIntoClient internal var _SF_FIRMLINK: CInterop.FileFlags { UInt32(bitPattern: SF_FIRMLINK) } diff --git a/Tests/SystemTests/FileModeTests.swift b/Tests/SystemTests/FileModeTests.swift index bc302a71..ca010335 100644 --- a/Tests/SystemTests/FileModeTests.swift +++ b/Tests/SystemTests/FileModeTests.swift @@ -74,7 +74,7 @@ private struct FileModeTests { #expect(invalidMode.type != .characterSpecial) #expect(invalidMode.type != .blockSpecial) #expect(invalidMode.type != .regular) - #expect(invalidMode.type != .pipe) + #expect(invalidMode.type != .fifo) #expect(invalidMode.type != .symbolicLink) #expect(invalidMode.type != .socket) #if SYSTEM_PACKAGE_DARWIN || os(FreeBSD) @@ -87,7 +87,7 @@ private struct FileModeTests { #expect(invalidMode.type != .characterSpecial) #expect(invalidMode.type != .blockSpecial) #expect(invalidMode.type != .regular) - #expect(invalidMode.type != .pipe) + #expect(invalidMode.type != .fifo) #expect(invalidMode.type != .symbolicLink) #expect(invalidMode.type != .socket) #if SYSTEM_PACKAGE_DARWIN || os(FreeBSD) From 98712f67f23f3b411e81dba13d42ba3355956872 Mon Sep 17 00:00:00 2001 From: Jonathan Flat Date: Mon, 6 Oct 2025 13:18:14 -0600 Subject: [PATCH 415/427] Remove _GNU_SOURCE define and AT_EMPTY_PATH for now --- Package.swift | 1 - Sources/System/FileSystem/Stat.swift | 19 +++++++++-------- Sources/System/Internals/Constants.swift | 9 ++++---- Tests/SystemTests/StatTests.swift | 26 +++++++++++------------- 4 files changed, 27 insertions(+), 28 deletions(-) diff --git a/Package.swift b/Package.swift index 8aba3315..11a43f61 100644 --- a/Package.swift +++ b/Package.swift @@ -87,7 +87,6 @@ let swiftSettings = swiftSettingsAvailability + swiftSettingsCI + [ let cSettings: [CSetting] = [ .define("_CRT_SECURE_NO_WARNINGS", .when(platforms: [.windows])), - .define("_GNU_SOURCE", .when(platforms: [.linux])), ] #if SYSTEM_ABI_STABLE diff --git a/Sources/System/FileSystem/Stat.swift b/Sources/System/FileSystem/Stat.swift index 6bc78ab3..31243e68 100644 --- a/Sources/System/FileSystem/Stat.swift +++ b/Sources/System/FileSystem/Stat.swift @@ -86,15 +86,16 @@ public struct Stat: RawRepresentable, Sendable { public static var resolveBeneath: Flags { Flags(rawValue: _AT_RESOLVE_BENEATH) } #endif - #if os(FreeBSD) || os(Linux) || os(Android) - /// If the path is an empty string (or `NULL` since Linux 6.11), - /// return information about the given file descriptor. - /// - /// The corresponding C constant is `AT_EMPTY_PATH`. - /// - Note: Only available on FreeBSD, Linux, and Android. - @_alwaysEmitIntoClient - public static var emptyPath: Flags { Flags(rawValue: _AT_EMPTY_PATH) } - #endif + // TODO: Re-enable when _GNU_SOURCE can be defined. +// #if os(FreeBSD) || os(Linux) || os(Android) +// /// If the path is an empty string (or `NULL` since Linux 6.11), +// /// return information about the given file descriptor. +// /// +// /// The corresponding C constant is `AT_EMPTY_PATH`. +// /// - Note: Only available on FreeBSD, Linux, and Android. +// @_alwaysEmitIntoClient +// public static var emptyPath: Flags { Flags(rawValue: _AT_EMPTY_PATH) } +// #endif } // MARK: Initializers diff --git a/Sources/System/Internals/Constants.swift b/Sources/System/Internals/Constants.swift index c4fda718..3d4b7efd 100644 --- a/Sources/System/Internals/Constants.swift +++ b/Sources/System/Internals/Constants.swift @@ -663,10 +663,11 @@ internal var _AT_SYMLINK_NOFOLLOW_ANY: CInt { AT_SYMLINK_NOFOLLOW_ANY } internal var _AT_RESOLVE_BENEATH: CInt { AT_RESOLVE_BENEATH } #endif -#if os(FreeBSD) || os(Linux) || os(Android) -@_alwaysEmitIntoClient -internal var _AT_EMPTY_PATH: CInt { AT_EMPTY_PATH } -#endif +// TODO: Re-enable when _GNU_SOURCE can be defined. +//#if os(FreeBSD) || os(Linux) || os(Android) +//@_alwaysEmitIntoClient +//internal var _AT_EMPTY_PATH: CInt { AT_EMPTY_PATH } +//#endif // MARK: - File Mode / File Type diff --git a/Tests/SystemTests/StatTests.swift b/Tests/SystemTests/StatTests.swift index 58c34413..4fefe7cf 100644 --- a/Tests/SystemTests/StatTests.swift +++ b/Tests/SystemTests/StatTests.swift @@ -81,7 +81,6 @@ private struct StatTests { } @Test - @available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *) func followSymlinkInits() async throws { try withTemporaryFilePath(basename: "Stat_followSymlinkInits") { tempDir in let targetFilePath = tempDir.appending("target.txt") @@ -98,17 +97,15 @@ private struct StatTests { } } - #if !os(WASI) // Can't open an fd to a symlink on WASI (no O_PATH) + // Can't open an fd to a symlink on WASI (no O_PATH) + // On non-Darwin, we need O_PATH | O_NOFOLLOW to open the symlink + // directly, but O_PATH requires _GNU_SOURCE be defined (TODO). #if SYSTEM_PACKAGE_DARWIN let symlinkFD = try FileDescriptor.open(symlinkPath, .readOnly, options: .symlink) - #else - // Need O_PATH | O_NOFOLLOW to open the symlink directly - let symlinkFD = try FileDescriptor.open(symlinkPath, .readOnly, options: [.path, .noFollow]) - #endif defer { try? symlinkFD.close() } - #endif // !os(WASI) + #endif let targetStat = try targetFilePath.stat() let originalTargetAccessTime = targetStat.st_atim @@ -130,7 +127,7 @@ private struct StatTests { stat.st_atim = originalTargetAccessTime #expect(stat == targetStat) - #if !os(WASI) + #if SYSTEM_PACKAGE_DARWIN stat = try symlinkFD.stat() stat.st_atim = originalSymlinkAccessTime #expect(stat == symlinkStat) @@ -142,7 +139,7 @@ private struct StatTests { stat.st_atim = originalTargetAccessTime #expect(stat == targetStat) - #if !os(WASI) + #if SYSTEM_PACKAGE_DARWIN stat = try Stat(symlinkFD) stat.st_atim = originalSymlinkAccessTime #expect(stat == symlinkStat) @@ -400,11 +397,12 @@ private struct StatTests { } -#if !SYSTEM_PACKAGE_DARWIN && !os(WASI) -private extension FileDescriptor.OpenOptions { - static var path: Self { Self(rawValue: O_PATH) } -} -#endif +// TODO: Re-enable for testing when _GNU_SOURCE can be defined. +//#if !SYSTEM_PACKAGE_DARWIN && !os(WASI) +//private extension FileDescriptor.OpenOptions { +// static var path: Self { Self(rawValue: O_PATH) } +//} +//#endif // Comparison operators for timespec until UTCClock.Instant properties are available private func >= (lhs: timespec, rhs: timespec) -> Bool { From 67a2fd2ca1c5035036993e1baa9165897a4cbae2 Mon Sep 17 00:00:00 2001 From: Melissa Kilby Date: Fri, 17 Oct 2025 16:28:56 -0700 Subject: [PATCH 416/427] chore: restrict GitHub workflow permissions - future-proof Signed-off-by: Melissa Kilby --- .github/workflows/pull_request.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index cd2bef9e..49f6dc9b 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -1,5 +1,8 @@ name: Pull request +permissions: + contents: read + on: pull_request: types: [opened, reopened, synchronize] From 501bb7d052f5b41a6bbb0dad1db611f5b527cafc Mon Sep 17 00:00:00 2001 From: Jonathan Flat Date: Tue, 28 Oct 2025 22:30:47 -0600 Subject: [PATCH 417/427] Remove commented-out code for future directions --- Sources/System/FileSystem/Identifiers.swift | 22 ------- Sources/System/FileSystem/Stat.swift | 64 --------------------- Sources/System/Internals/Exports.swift | 13 ----- 3 files changed, 99 deletions(-) diff --git a/Sources/System/FileSystem/Identifiers.swift b/Sources/System/FileSystem/Identifiers.swift index 7620b601..ca2835ea 100644 --- a/Sources/System/FileSystem/Identifiers.swift +++ b/Sources/System/FileSystem/Identifiers.swift @@ -62,28 +62,6 @@ public struct DeviceID: RawRepresentable, Sendable, Hashable, Codable { /// Creates a strongly-typed `DeviceID` from the raw C value. @_alwaysEmitIntoClient public init(_ rawValue: CInterop.DeviceID) { self.rawValue = rawValue } - - // TODO: API review for ID wrapper functionality - -// /// Creates a `DeviceID` from the given major and minor device numbers. -// /// -// /// The corresponding C function is `makedev()`. -// @_alwaysEmitIntoClient -// private static func make(major: CUnsignedInt, minor: CUnsignedInt) -> DeviceID { -// DeviceID(rawValue: system_makedev(major, minor)) -// } -// -// /// The major device number -// /// -// /// The corresponding C function is `major()`. -// @_alwaysEmitIntoClient -// private var major: CInt { system_major(rawValue) } -// -// /// The minor device number -// /// -// /// The corresponding C function is `minor()`. -// @_alwaysEmitIntoClient -// private var minor: CInt { system_minor(rawValue) } } /// A Swift wrapper of the C `ino_t` type. diff --git a/Sources/System/FileSystem/Stat.swift b/Sources/System/FileSystem/Stat.swift index 31243e68..f31fa5d7 100644 --- a/Sources/System/FileSystem/Stat.swift +++ b/Sources/System/FileSystem/Stat.swift @@ -85,17 +85,6 @@ public struct Stat: RawRepresentable, Sendable { @available(macOS 26.0, iOS 26.0, tvOS 26.0, watchOS 26.0, visionOS 26.0, *) public static var resolveBeneath: Flags { Flags(rawValue: _AT_RESOLVE_BENEATH) } #endif - - // TODO: Re-enable when _GNU_SOURCE can be defined. -// #if os(FreeBSD) || os(Linux) || os(Android) -// /// If the path is an empty string (or `NULL` since Linux 6.11), -// /// return information about the given file descriptor. -// /// -// /// The corresponding C constant is `AT_EMPTY_PATH`. -// /// - Note: Only available on FreeBSD, Linux, and Android. -// @_alwaysEmitIntoClient -// public static var emptyPath: Flags { Flags(rawValue: _AT_EMPTY_PATH) } -// #endif } // MARK: Initializers @@ -520,59 +509,6 @@ public struct Stat: RawRepresentable, Sendable { } #endif - // TODO: Investigate changing time properties to UTCClock.Instant once available. - -// /// Time of last access, given as a `UTCClock.Instant` -// /// -// /// The corresponding C property is `st_atim` (or `st_atimespec` on Darwin). -// public var accessTime: UTCClock.Instant { -// get { -// UTCClock.systemEpoch.advanced(by: Duration(st_atim)) -// } -// set { -// st_atim = timespec(UTCClock.systemEpoch.duration(to: newValue)) -// } -// } -// -// /// Time of last modification, given as a `UTCClock.Instant` -// /// -// /// The corresponding C property is `st_mtim` (or `st_mtimespec` on Darwin). -// public var modificationTime: UTCClock.Instant { -// get { -// UTCClock.systemEpoch.advanced(by: Duration(st_mtim)) -// } -// set { -// st_mtim = timespec(UTCClock.systemEpoch.duration(to: newValue)) -// } -// } -// -// /// Time of last status (inode) change, given as a `UTCClock.Instant` -// /// -// /// The corresponding C property is `st_ctim` (or `st_ctimespec` on Darwin). -// public var changeTime: UTCClock.Instant { -// get { -// UTCClock.systemEpoch.advanced(by: Duration(st_ctim)) -// } -// set { -// st_ctim = timespec(UTCClock.systemEpoch.duration(to: newValue)) -// } -// } -// -// #if SYSTEM_PACKAGE_DARWIN || os(FreeBSD) -// /// Time of file creation, given as a `UTCClock.Instant` -// /// -// /// The corresponding C property is `st_birthtim` (or `st_birthtimespec` on Darwin). -// /// - Note: Only available on Darwin and FreeBSD. -// public var creationTime: UTCClock.Instant { -// get { -// UTCClock.systemEpoch.advanced(by: Duration(st_birthtim)) -// } -// set { -// st_birthtim = timespec(UTCClock.systemEpoch.duration(to: newValue)) -// } -// } -// #endif - #if SYSTEM_PACKAGE_DARWIN || os(FreeBSD) || os(OpenBSD) /// File flags /// diff --git a/Sources/System/Internals/Exports.swift b/Sources/System/Internals/Exports.swift index 025aefae..58d0db80 100644 --- a/Sources/System/Internals/Exports.swift +++ b/Sources/System/Internals/Exports.swift @@ -103,19 +103,6 @@ internal func system_fstat(_ fd: CInt, _ s: inout CInterop.Stat) -> Int32 { internal func system_fstatat(_ fd: CInt, _ p: UnsafePointer, _ s: inout CInterop.Stat, _ flags: CInt) -> Int32 { fstatat(fd, p, &s, flags) } - -@usableFromInline -internal func system_major(_ dev: CInterop.DeviceID) -> CInt { - numericCast((dev >> 24) & 0xff) -} -@usableFromInline -internal func system_minor(_ dev: CInterop.DeviceID) -> CInt { - numericCast(dev & 0xffffff) -} -@usableFromInline -internal func system_makedev(_ maj: CUnsignedInt, _ min: CUnsignedInt) -> CInterop.DeviceID { - CInterop.DeviceID((maj << 24) | min) -} #endif // Convention: `system_platform_foo` is a From 90dba1dc68343627ddd80a22dd7a9ed56cd7f72d Mon Sep 17 00:00:00 2001 From: Jonathan Flat Date: Tue, 28 Oct 2025 22:31:34 -0600 Subject: [PATCH 418/427] AT_SYMLINK_FOLLOW -> AT_SYMLINK_NOFOLLOW --- Sources/System/Internals/Constants.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/System/Internals/Constants.swift b/Sources/System/Internals/Constants.swift index 3d4b7efd..1d2030cf 100644 --- a/Sources/System/Internals/Constants.swift +++ b/Sources/System/Internals/Constants.swift @@ -651,7 +651,7 @@ internal var _AT_FDCWD: CInt { AT_FDCWD } // MARK: - fstatat Flags @_alwaysEmitIntoClient -internal var _AT_SYMLINK_NOFOLLOW: CInt { AT_SYMLINK_FOLLOW } +internal var _AT_SYMLINK_NOFOLLOW: CInt { AT_SYMLINK_NOFOLLOW } #if SYSTEM_PACKAGE_DARWIN @_alwaysEmitIntoClient From eee01456718b82983443be29523fca641827f9a2 Mon Sep 17 00:00:00 2001 From: Jonathan Flat Date: Tue, 28 Oct 2025 22:52:34 -0600 Subject: [PATCH 419/427] Standardize on "file system" --- Sources/System/Errno.swift | 2 +- Sources/System/FileSystem/Stat.swift | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Sources/System/Errno.swift b/Sources/System/Errno.swift index 43b46af5..94eb102d 100644 --- a/Sources/System/Errno.swift +++ b/Sources/System/Errno.swift @@ -961,7 +961,7 @@ public struct Errno: RawRepresentable, Error, Hashable, Codable { /// Stale NFS file handle. /// - /// You attempted access an open file on an NFS filesystem, + /// You attempted access an open file on an NFS file system, /// which is now unavailable as referenced by the given file descriptor. /// This may indicate that the file was deleted on the NFS server /// or that some other catastrophic event occurred. diff --git a/Sources/System/FileSystem/Stat.swift b/Sources/System/FileSystem/Stat.swift index f31fa5d7..e1d221bf 100644 --- a/Sources/System/FileSystem/Stat.swift +++ b/Sources/System/FileSystem/Stat.swift @@ -374,7 +374,7 @@ public struct Stat: RawRepresentable, Sendable { /// Total size, in bytes /// /// The semantics of this property are tied to the underlying C `st_size` field, - /// which can have file system-dependent behavior. For example, this property + /// which can have file-system–dependent behavior. For example, this property /// can return different values for a file's data fork and resource fork, and some /// file systems report logical size rather than actual disk usage for compressed /// or cloned files. @@ -386,7 +386,7 @@ public struct Stat: RawRepresentable, Sendable { set { rawValue.st_size = numericCast(newValue) } } - /// Block size for filesystem I/O, in bytes + /// Block size for file system I/O, in bytes /// /// The corresponding C property is `st_blksize`. @_alwaysEmitIntoClient @@ -398,7 +398,7 @@ public struct Stat: RawRepresentable, Sendable { /// Number of 512-byte blocks allocated /// /// The semantics of this property are tied to the underlying C `st_blocks` field, - /// which can have file system-dependent behavior. + /// which can have file-system–dependent behavior. /// /// The corresponding C property is `st_blocks`. @_alwaysEmitIntoClient @@ -410,7 +410,7 @@ public struct Stat: RawRepresentable, Sendable { /// Total size allocated, in bytes /// /// The semantics of this property are tied to the underlying C `st_blocks` field, - /// which can have file system-dependent behavior. + /// which can have file-system–dependent behavior. /// /// - Note: Calculated as `512 * blocksAllocated`. @_alwaysEmitIntoClient From 40e1deb663e499309739adb6a2acf193ebd7aa0e Mon Sep 17 00:00:00 2001 From: Jonathan Flat Date: Wed, 29 Oct 2025 11:47:32 -0600 Subject: [PATCH 420/427] Consolidate internal Stat functions --- Sources/System/FileSystem/Stat.swift | 88 ++++++---------------------- 1 file changed, 17 insertions(+), 71 deletions(-) diff --git a/Sources/System/FileSystem/Stat.swift b/Sources/System/FileSystem/Stat.swift index e1d221bf..f8bdb602 100644 --- a/Sources/System/FileSystem/Stat.swift +++ b/Sources/System/FileSystem/Stat.swift @@ -103,7 +103,7 @@ public struct Stat: RawRepresentable, Sendable { followTargetSymlink: Bool = true, retryOnInterrupt: Bool = true ) throws(Errno) { - self.rawValue = try path.withPlatformString { + self = try path.withPlatformString { Self._stat( $0, followTargetSymlink: followTargetSymlink, @@ -126,7 +126,7 @@ public struct Stat: RawRepresentable, Sendable { followTargetSymlink: Bool = true, retryOnInterrupt: Bool = true ) throws(Errno) { - self.rawValue = try Self._stat( + self = try Self._stat( path, followTargetSymlink: followTargetSymlink, retryOnInterrupt: retryOnInterrupt @@ -138,7 +138,7 @@ public struct Stat: RawRepresentable, Sendable { _ ptr: UnsafePointer, followTargetSymlink: Bool, retryOnInterrupt: Bool - ) -> Result { + ) -> Result { var result = CInterop.Stat() return nothingOrErrno(retryOnInterrupt: retryOnInterrupt) { if followTargetSymlink { @@ -146,7 +146,7 @@ public struct Stat: RawRepresentable, Sendable { } else { system_lstat(ptr, &result) } - }.map { result } + }.map { Stat(rawValue: result) } } /// Creates a `Stat` struct from a `FileDescriptor`. @@ -157,7 +157,7 @@ public struct Stat: RawRepresentable, Sendable { _ fd: FileDescriptor, retryOnInterrupt: Bool = true ) throws(Errno) { - self.rawValue = try Self._fstat( + self = try Self._fstat( fd, retryOnInterrupt: retryOnInterrupt ).get() @@ -167,11 +167,11 @@ public struct Stat: RawRepresentable, Sendable { internal static func _fstat( _ fd: FileDescriptor, retryOnInterrupt: Bool - ) -> Result { + ) -> Result { var result = CInterop.Stat() return nothingOrErrno(retryOnInterrupt: retryOnInterrupt) { system_fstat(fd.rawValue, &result) - }.map { result } + }.map { Stat(rawValue: result) } } /// Creates a `Stat` struct from a `FilePath` and `Flags`. @@ -185,7 +185,7 @@ public struct Stat: RawRepresentable, Sendable { flags: Stat.Flags, retryOnInterrupt: Bool = true ) throws(Errno) { - self.rawValue = try path.withPlatformString { + self = try path.withPlatformString { Self._fstatat( $0, relativeTo: _AT_FDCWD, @@ -209,7 +209,7 @@ public struct Stat: RawRepresentable, Sendable { flags: Stat.Flags, retryOnInterrupt: Bool = true ) throws(Errno) { - self.rawValue = try path.withPlatformString { + self = try path.withPlatformString { Self._fstatat( $0, relativeTo: fd.rawValue, @@ -230,7 +230,7 @@ public struct Stat: RawRepresentable, Sendable { flags: Stat.Flags, retryOnInterrupt: Bool = true ) throws(Errno) { - self.rawValue = try Self._fstatat( + self = try Self._fstatat( path, relativeTo: _AT_FDCWD, flags: flags, @@ -252,7 +252,7 @@ public struct Stat: RawRepresentable, Sendable { flags: Stat.Flags, retryOnInterrupt: Bool = true ) throws(Errno) { - self.rawValue = try Self._fstatat( + self = try Self._fstatat( path, relativeTo: fd.rawValue, flags: flags, @@ -266,11 +266,11 @@ public struct Stat: RawRepresentable, Sendable { relativeTo fd: FileDescriptor.RawValue, flags: Stat.Flags, retryOnInterrupt: Bool - ) -> Result { + ) -> Result { var result = CInterop.Stat() return nothingOrErrno(retryOnInterrupt: retryOnInterrupt) { system_fstatat(fd, path, &result, flags.rawValue) - }.map { result } + }.map { Stat(rawValue: result) } } @@ -573,19 +573,7 @@ extension FileDescriptor { public func stat( retryOnInterrupt: Bool = true ) throws(Errno) -> Stat { - try _fstat( - retryOnInterrupt: retryOnInterrupt - ).get() - } - - @usableFromInline - internal func _fstat( - retryOnInterrupt: Bool - ) -> Result { - var result = CInterop.Stat() - return nothingOrErrno(retryOnInterrupt: retryOnInterrupt) { - system_fstat(self.rawValue, &result) - }.map { Stat(rawValue: result) } + try Stat(self, retryOnInterrupt: retryOnInterrupt) } } @@ -607,27 +595,7 @@ extension FilePath { followTargetSymlink: Bool = true, retryOnInterrupt: Bool = true ) throws(Errno) -> Stat { - try _stat( - followTargetSymlink: followTargetSymlink, - retryOnInterrupt: retryOnInterrupt - ).get() - } - - @usableFromInline - internal func _stat( - followTargetSymlink: Bool, - retryOnInterrupt: Bool - ) -> Result { - var result = CInterop.Stat() - return withPlatformString { ptr in - nothingOrErrno(retryOnInterrupt: retryOnInterrupt) { - if followTargetSymlink { - system_stat(ptr, &result) - } else { - system_lstat(ptr, &result) - } - }.map { Stat(rawValue: result) } - } + try Stat(self, followTargetSymlink: followTargetSymlink, retryOnInterrupt: retryOnInterrupt) } /// Creates a `Stat` struct for the file referenced by this `FilePath` using the given `Flags`. @@ -640,11 +608,7 @@ extension FilePath { flags: Stat.Flags, retryOnInterrupt: Bool = true ) throws(Errno) -> Stat { - try _fstatat( - relativeTo: _AT_FDCWD, - flags: flags, - retryOnInterrupt: retryOnInterrupt - ).get() + try Stat(self, flags: flags, retryOnInterrupt: retryOnInterrupt) } /// Creates a `Stat` struct for the file referenced by this `FilePath` using the given `Flags`, @@ -660,25 +624,7 @@ extension FilePath { flags: Stat.Flags, retryOnInterrupt: Bool = true ) throws(Errno) -> Stat { - try _fstatat( - relativeTo: fd.rawValue, - flags: flags, - retryOnInterrupt: retryOnInterrupt - ).get() - } - - @usableFromInline - internal func _fstatat( - relativeTo fd: FileDescriptor.RawValue, - flags: Stat.Flags, - retryOnInterrupt: Bool - ) -> Result { - var result = CInterop.Stat() - return withPlatformString { ptr in - nothingOrErrno(retryOnInterrupt: retryOnInterrupt) { - system_fstatat(fd, ptr, &result, flags.rawValue) - }.map { Stat(rawValue: result) } - } + try Stat(self, relativeTo: fd, flags: flags, retryOnInterrupt: retryOnInterrupt) } } From 4911642226096fed703dee029786abda0ef28ba5 Mon Sep 17 00:00:00 2001 From: Jonathan Flat Date: Wed, 29 Oct 2025 11:57:30 -0600 Subject: [PATCH 421/427] Add availability, fix CInterop availability issue on old Darwin platforms --- Package.swift | 2 + Sources/System/FileSystem/FileFlags.swift | 4 +- Sources/System/FileSystem/FileMode.swift | 2 +- Sources/System/FileSystem/FileType.swift | 2 +- Sources/System/FileSystem/Identifiers.swift | 8 +-- Sources/System/FileSystem/Stat.swift | 9 +-- Sources/System/Internals/Constants.swift | 64 ++++++++++----------- Tests/SystemTests/FileModeTests.swift | 2 + Tests/SystemTests/StatTests.swift | 11 ++-- 9 files changed, 56 insertions(+), 48 deletions(-) diff --git a/Package.swift b/Package.swift index 11a43f61..c7621c05 100644 --- a/Package.swift +++ b/Package.swift @@ -64,6 +64,8 @@ let availability: [Available] = [ Available("1.5.0", "macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, visionOS 9999"), Available("1.6.0", "macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, visionOS 9999"), Available("1.6.1", "macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, visionOS 9999"), + + Available("99", "macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, visionOS 9999"), ] let swiftSettingsAvailability = availability.map(\.swiftSetting) diff --git a/Sources/System/FileSystem/FileFlags.swift b/Sources/System/FileSystem/FileFlags.swift index fd714cb8..8fb54938 100644 --- a/Sources/System/FileSystem/FileFlags.swift +++ b/Sources/System/FileSystem/FileFlags.swift @@ -39,7 +39,7 @@ // |------------------|---------------|---------------|---------------| #if SYSTEM_PACKAGE_DARWIN || os(FreeBSD) || os(OpenBSD) -// @available(System X.Y.Z, *) +@available(System 99, *) extension CInterop { public typealias FileFlags = UInt32 } @@ -49,7 +49,7 @@ extension CInterop { /// /// - Note: Only available on Darwin, FreeBSD, and OpenBSD. @frozen -// @available(System X.Y.Z, *) +@available(System 99, *) public struct FileFlags: OptionSet, Sendable, Hashable, Codable { /// The raw C flags. diff --git a/Sources/System/FileSystem/FileMode.swift b/Sources/System/FileSystem/FileMode.swift index 9a099476..91329f1a 100644 --- a/Sources/System/FileSystem/FileMode.swift +++ b/Sources/System/FileSystem/FileMode.swift @@ -14,7 +14,7 @@ /// /// - Note: Only available on Unix-like platforms. @frozen -// @available(System X.Y.Z, *) +@available(System 99, *) public struct FileMode: RawRepresentable, Sendable, Hashable, Codable { /// The raw C mode. diff --git a/Sources/System/FileSystem/FileType.swift b/Sources/System/FileSystem/FileType.swift index 638b1472..42134522 100644 --- a/Sources/System/FileSystem/FileType.swift +++ b/Sources/System/FileSystem/FileType.swift @@ -34,7 +34,7 @@ /// /// - Note: Only available on Unix-like platforms. @frozen -// @available(System X.Y.Z, *) +@available(System 99, *) public struct FileType: RawRepresentable, Sendable, Hashable, Codable { /// The raw file-type bits from the C mode. diff --git a/Sources/System/FileSystem/Identifiers.swift b/Sources/System/FileSystem/Identifiers.swift index ca2835ea..b8f90141 100644 --- a/Sources/System/FileSystem/Identifiers.swift +++ b/Sources/System/FileSystem/Identifiers.swift @@ -12,7 +12,7 @@ #if !os(Windows) /// A Swift wrapper of the C `uid_t` type. @frozen -// @available(System X.Y.Z, *) +@available(System 99, *) public struct UserID: RawRepresentable, Sendable, Hashable, Codable { /// The raw C `uid_t`. @@ -30,7 +30,7 @@ public struct UserID: RawRepresentable, Sendable, Hashable, Codable { /// A Swift wrapper of the C `gid_t` type. @frozen -// @available(System X.Y.Z, *) +@available(System 99, *) public struct GroupID: RawRepresentable, Sendable, Hashable, Codable { /// The raw C `gid_t`. @@ -48,7 +48,7 @@ public struct GroupID: RawRepresentable, Sendable, Hashable, Codable { /// A Swift wrapper of the C `dev_t` type. @frozen -// @available(System X.Y.Z, *) +@available(System 99, *) public struct DeviceID: RawRepresentable, Sendable, Hashable, Codable { /// The raw C `dev_t`. @@ -66,7 +66,7 @@ public struct DeviceID: RawRepresentable, Sendable, Hashable, Codable { /// A Swift wrapper of the C `ino_t` type. @frozen -// @available(System X.Y.Z, *) +@available(System 99, *) public struct Inode: RawRepresentable, Sendable, Hashable, Codable { /// The raw C `ino_t`. diff --git a/Sources/System/FileSystem/Stat.swift b/Sources/System/FileSystem/Stat.swift index f8bdb602..98b7dc19 100644 --- a/Sources/System/FileSystem/Stat.swift +++ b/Sources/System/FileSystem/Stat.swift @@ -35,7 +35,7 @@ import Android /// /// - Note: Only available on Unix-like platforms. @frozen -// @available(System X.Y.Z, *) +@available(System 99, *) public struct Stat: RawRepresentable, Sendable { /// The raw C `stat` struct. @@ -82,7 +82,6 @@ public struct Stat: RawRepresentable, Sendable { /// The corresponding C constant is `AT_RESOLVE_BENEATH`. /// - Note: Only available on Darwin and FreeBSD. @_alwaysEmitIntoClient - @available(macOS 26.0, iOS 26.0, tvOS 26.0, watchOS 26.0, visionOS 26.0, *) public static var resolveBeneath: Flags { Flags(rawValue: _AT_RESOLVE_BENEATH) } #endif } @@ -537,6 +536,7 @@ public struct Stat: RawRepresentable, Sendable { // MARK: - Equatable and Hashable +@available(System 99, *) extension Stat: Equatable { @_alwaysEmitIntoClient /// Compares the raw bytes of two `Stat` structs for equality. @@ -549,6 +549,7 @@ extension Stat: Equatable { } } +@available(System 99, *) extension Stat: Hashable { @_alwaysEmitIntoClient /// Hashes the raw bytes of this `Stat` struct. @@ -563,7 +564,7 @@ extension Stat: Hashable { // MARK: - FileDescriptor Extensions -// @available(System X.Y.Z, *) +@available(System 99, *) extension FileDescriptor { /// Creates a `Stat` struct for the file referenced by this `FileDescriptor`. @@ -579,7 +580,7 @@ extension FileDescriptor { // MARK: - FilePath Extensions -// @available(System X.Y.Z, *) +@available(System 99, *) extension FilePath { /// Creates a `Stat` struct for the file referenced by this `FilePath`. diff --git a/Sources/System/Internals/Constants.swift b/Sources/System/Internals/Constants.swift index 1d2030cf..8740d81e 100644 --- a/Sources/System/Internals/Constants.swift +++ b/Sources/System/Internals/Constants.swift @@ -672,35 +672,35 @@ internal var _AT_RESOLVE_BENEATH: CInt { AT_RESOLVE_BENEATH } // MARK: - File Mode / File Type @_alwaysEmitIntoClient -internal var _MODE_FILETYPE_MASK: CInterop.Mode { S_IFMT } +internal var _MODE_FILETYPE_MASK: mode_t { S_IFMT } @_alwaysEmitIntoClient -internal var _MODE_PERMISSIONS_MASK: CInterop.Mode { 0o7777 } +internal var _MODE_PERMISSIONS_MASK: mode_t { 0o7777 } @_alwaysEmitIntoClient -internal var _S_IFDIR: CInterop.Mode { S_IFDIR } +internal var _S_IFDIR: mode_t { S_IFDIR } @_alwaysEmitIntoClient -internal var _S_IFCHR: CInterop.Mode { S_IFCHR } +internal var _S_IFCHR: mode_t { S_IFCHR } @_alwaysEmitIntoClient -internal var _S_IFBLK: CInterop.Mode { S_IFBLK } +internal var _S_IFBLK: mode_t { S_IFBLK } @_alwaysEmitIntoClient -internal var _S_IFREG: CInterop.Mode { S_IFREG } +internal var _S_IFREG: mode_t { S_IFREG } @_alwaysEmitIntoClient -internal var _S_IFIFO: CInterop.Mode { S_IFIFO } +internal var _S_IFIFO: mode_t { S_IFIFO } @_alwaysEmitIntoClient -internal var _S_IFLNK: CInterop.Mode { S_IFLNK } +internal var _S_IFLNK: mode_t { S_IFLNK } @_alwaysEmitIntoClient -internal var _S_IFSOCK: CInterop.Mode { S_IFSOCK } +internal var _S_IFSOCK: mode_t { S_IFSOCK } #if SYSTEM_PACKAGE_DARWIN || os(FreeBSD) @_alwaysEmitIntoClient -internal var _S_IFWHT: CInterop.Mode { S_IFWHT } +internal var _S_IFWHT: mode_t { S_IFWHT } #endif // MARK: - stat/chflags File Flags @@ -709,82 +709,82 @@ internal var _S_IFWHT: CInterop.Mode { S_IFWHT } #if SYSTEM_PACKAGE_DARWIN || os(FreeBSD) || os(OpenBSD) @_alwaysEmitIntoClient -internal var _UF_NODUMP: CInterop.FileFlags { UInt32(bitPattern: UF_NODUMP) } +internal var _UF_NODUMP: UInt32 { UInt32(bitPattern: UF_NODUMP) } @_alwaysEmitIntoClient -internal var _UF_IMMUTABLE: CInterop.FileFlags { UInt32(bitPattern: UF_IMMUTABLE) } +internal var _UF_IMMUTABLE: UInt32 { UInt32(bitPattern: UF_IMMUTABLE) } @_alwaysEmitIntoClient -internal var _UF_APPEND: CInterop.FileFlags { UInt32(bitPattern: UF_APPEND) } +internal var _UF_APPEND: UInt32 { UInt32(bitPattern: UF_APPEND) } @_alwaysEmitIntoClient -internal var _SF_ARCHIVED: CInterop.FileFlags { UInt32(bitPattern: SF_ARCHIVED) } +internal var _SF_ARCHIVED: UInt32 { UInt32(bitPattern: SF_ARCHIVED) } @_alwaysEmitIntoClient -internal var _SF_IMMUTABLE: CInterop.FileFlags { UInt32(bitPattern: SF_IMMUTABLE) } +internal var _SF_IMMUTABLE: UInt32 { UInt32(bitPattern: SF_IMMUTABLE) } @_alwaysEmitIntoClient -internal var _SF_APPEND: CInterop.FileFlags { UInt32(bitPattern: SF_APPEND) } +internal var _SF_APPEND: UInt32 { UInt32(bitPattern: SF_APPEND) } #endif // MARK: Flags Available on Darwin and FreeBSD #if SYSTEM_PACKAGE_DARWIN || os(FreeBSD) @_alwaysEmitIntoClient -internal var _UF_OPAQUE: CInterop.FileFlags { UInt32(bitPattern: UF_OPAQUE) } +internal var _UF_OPAQUE: UInt32 { UInt32(bitPattern: UF_OPAQUE) } @_alwaysEmitIntoClient -internal var _UF_HIDDEN: CInterop.FileFlags { UInt32(bitPattern: UF_HIDDEN) } +internal var _UF_HIDDEN: UInt32 { UInt32(bitPattern: UF_HIDDEN) } @_alwaysEmitIntoClient -internal var _SF_NOUNLINK: CInterop.FileFlags { UInt32(bitPattern: SF_NOUNLINK) } +internal var _SF_NOUNLINK: UInt32 { UInt32(bitPattern: SF_NOUNLINK) } #endif // MARK: Flags Available on Darwin Only #if SYSTEM_PACKAGE_DARWIN @_alwaysEmitIntoClient -internal var _UF_COMPRESSED: CInterop.FileFlags { UInt32(bitPattern: UF_COMPRESSED) } +internal var _UF_COMPRESSED: UInt32 { UInt32(bitPattern: UF_COMPRESSED) } @_alwaysEmitIntoClient -internal var _UF_TRACKED: CInterop.FileFlags { UInt32(bitPattern: UF_TRACKED) } +internal var _UF_TRACKED: UInt32 { UInt32(bitPattern: UF_TRACKED) } @_alwaysEmitIntoClient -internal var _UF_DATAVAULT: CInterop.FileFlags { UInt32(bitPattern: UF_DATAVAULT) } +internal var _UF_DATAVAULT: UInt32 { UInt32(bitPattern: UF_DATAVAULT) } @_alwaysEmitIntoClient -internal var _SF_RESTRICTED: CInterop.FileFlags { UInt32(bitPattern: SF_RESTRICTED) } +internal var _SF_RESTRICTED: UInt32 { UInt32(bitPattern: SF_RESTRICTED) } @_alwaysEmitIntoClient -internal var _SF_FIRMLINK: CInterop.FileFlags { UInt32(bitPattern: SF_FIRMLINK) } +internal var _SF_FIRMLINK: UInt32 { UInt32(bitPattern: SF_FIRMLINK) } @_alwaysEmitIntoClient -internal var _SF_DATALESS: CInterop.FileFlags { UInt32(bitPattern: SF_DATALESS) } +internal var _SF_DATALESS: UInt32 { UInt32(bitPattern: SF_DATALESS) } #endif // MARK: Flags Available on FreeBSD Only #if os(FreeBSD) @_alwaysEmitIntoClient -internal var _UF_NOUNLINK: CInterop.FileFlags { UInt32(bitPattern: UF_NOUNLINK) } +internal var _UF_NOUNLINK: UInt32 { UInt32(bitPattern: UF_NOUNLINK) } @_alwaysEmitIntoClient -internal var _UF_OFFLINE: CInterop.FileFlags { UInt32(bitPattern: UF_OFFLINE) } +internal var _UF_OFFLINE: UInt32 { UInt32(bitPattern: UF_OFFLINE) } @_alwaysEmitIntoClient -internal var _UF_READONLY: CInterop.FileFlags { UInt32(bitPattern: UF_READONLY) } +internal var _UF_READONLY: UInt32 { UInt32(bitPattern: UF_READONLY) } @_alwaysEmitIntoClient -internal var _UF_REPARSE: CInterop.FileFlags { UInt32(bitPattern: UF_REPARSE) } +internal var _UF_REPARSE: UInt32 { UInt32(bitPattern: UF_REPARSE) } @_alwaysEmitIntoClient -internal var _UF_SPARSE: CInterop.FileFlags { UInt32(bitPattern: UF_SPARSE) } +internal var _UF_SPARSE: UInt32 { UInt32(bitPattern: UF_SPARSE) } @_alwaysEmitIntoClient -internal var _UF_SYSTEM: CInterop.FileFlags { UInt32(bitPattern: UF_SYSTEM) } +internal var _UF_SYSTEM: UInt32 { UInt32(bitPattern: UF_SYSTEM) } @_alwaysEmitIntoClient -internal var _SF_SNAPSHOT: CInterop.FileFlags { UInt32(bitPattern: SF_SNAPSHOT) } +internal var _SF_SNAPSHOT: UInt32 { UInt32(bitPattern: SF_SNAPSHOT) } #endif #endif // !os(Windows) diff --git a/Tests/SystemTests/FileModeTests.swift b/Tests/SystemTests/FileModeTests.swift index ca010335..46c3a5d6 100644 --- a/Tests/SystemTests/FileModeTests.swift +++ b/Tests/SystemTests/FileModeTests.swift @@ -36,6 +36,7 @@ import Android @Suite("FileMode") private struct FileModeTests { + @available(System 99, *) @Test func basics() async throws { var mode = FileMode(rawValue: S_IFREG | 0o644) // Regular file, rw-r--r-- #expect(mode.type == .regular) @@ -66,6 +67,7 @@ private struct FileModeTests { #expect(mode.type == mode2.type) } + @available(System 99, *) @Test func invalidInput() async throws { // No permissions, all other bits set var invalidMode = FileMode(rawValue: ~0o7777) diff --git a/Tests/SystemTests/StatTests.swift b/Tests/SystemTests/StatTests.swift index 4fefe7cf..299dd8ee 100644 --- a/Tests/SystemTests/StatTests.swift +++ b/Tests/SystemTests/StatTests.swift @@ -39,6 +39,7 @@ import Android @Suite("Stat") private struct StatTests { + @available(System 99, *) @Test func basics() async throws { try withTemporaryFilePath(basename: "Stat_basics") { tempDir in let dirStatFromFilePath = try tempDir.stat() @@ -80,8 +81,8 @@ private struct StatTests { } } - @Test - func followSymlinkInits() async throws { + @available(System 99, *) + @Test func followSymlinkInits() async throws { try withTemporaryFilePath(basename: "Stat_followSymlinkInits") { tempDir in let targetFilePath = tempDir.appending("target.txt") let symlinkPath = tempDir.appending("symlink") @@ -189,6 +190,7 @@ private struct StatTests { } } + @available(System 99, *) @Test func permissions() async throws { try withTemporaryFilePath(basename: "Stat_permissions") { tempDir in let testFile = tempDir.appending("test.txt") @@ -218,8 +220,8 @@ private struct StatTests { } } - @Test - func times() async throws { + @available(System 99, *) + @Test func times() async throws { var start = timespec() try #require(clock_gettime(CLOCK_REALTIME, &start) == 0, "\(Errno.current)") start.tv_sec -= 1 // A little wiggle room @@ -347,6 +349,7 @@ private struct StatTests { } #if SYSTEM_PACKAGE_DARWIN || os(FreeBSD) || os(OpenBSD) + @available(System 99, *) @Test func flags() async throws { try withTemporaryFilePath(basename: "Stat_flags") { tempDir in let filePath = tempDir.appending("test.txt") From c2e385e849608d12afdbeeba2d113a74b72eb6bd Mon Sep 17 00:00:00 2001 From: Jonathan Flat Date: Wed, 29 Oct 2025 12:01:13 -0600 Subject: [PATCH 422/427] Remove commented-out code pt. 2 --- Sources/System/Internals/Constants.swift | 6 ------ Tests/SystemTests/StatTests.swift | 7 ------- 2 files changed, 13 deletions(-) diff --git a/Sources/System/Internals/Constants.swift b/Sources/System/Internals/Constants.swift index 8740d81e..3e71ec90 100644 --- a/Sources/System/Internals/Constants.swift +++ b/Sources/System/Internals/Constants.swift @@ -663,12 +663,6 @@ internal var _AT_SYMLINK_NOFOLLOW_ANY: CInt { AT_SYMLINK_NOFOLLOW_ANY } internal var _AT_RESOLVE_BENEATH: CInt { AT_RESOLVE_BENEATH } #endif -// TODO: Re-enable when _GNU_SOURCE can be defined. -//#if os(FreeBSD) || os(Linux) || os(Android) -//@_alwaysEmitIntoClient -//internal var _AT_EMPTY_PATH: CInt { AT_EMPTY_PATH } -//#endif - // MARK: - File Mode / File Type @_alwaysEmitIntoClient diff --git a/Tests/SystemTests/StatTests.swift b/Tests/SystemTests/StatTests.swift index 299dd8ee..524226d2 100644 --- a/Tests/SystemTests/StatTests.swift +++ b/Tests/SystemTests/StatTests.swift @@ -400,13 +400,6 @@ private struct StatTests { } -// TODO: Re-enable for testing when _GNU_SOURCE can be defined. -//#if !SYSTEM_PACKAGE_DARWIN && !os(WASI) -//private extension FileDescriptor.OpenOptions { -// static var path: Self { Self(rawValue: O_PATH) } -//} -//#endif - // Comparison operators for timespec until UTCClock.Instant properties are available private func >= (lhs: timespec, rhs: timespec) -> Bool { (lhs.tv_sec, lhs.tv_nsec) >= (rhs.tv_sec, rhs.tv_nsec) From 87f8e49ba79c68598b36cbe5f04f762bbcd32f74 Mon Sep 17 00:00:00 2001 From: Jonathan Flat Date: Thu, 30 Oct 2025 14:11:45 -0600 Subject: [PATCH 423/427] Assign rawValue instead of self --- Sources/System/FileSystem/Stat.swift | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/Sources/System/FileSystem/Stat.swift b/Sources/System/FileSystem/Stat.swift index 98b7dc19..c335cf80 100644 --- a/Sources/System/FileSystem/Stat.swift +++ b/Sources/System/FileSystem/Stat.swift @@ -102,7 +102,7 @@ public struct Stat: RawRepresentable, Sendable { followTargetSymlink: Bool = true, retryOnInterrupt: Bool = true ) throws(Errno) { - self = try path.withPlatformString { + self.rawValue = try path.withPlatformString { Self._stat( $0, followTargetSymlink: followTargetSymlink, @@ -125,7 +125,7 @@ public struct Stat: RawRepresentable, Sendable { followTargetSymlink: Bool = true, retryOnInterrupt: Bool = true ) throws(Errno) { - self = try Self._stat( + self.rawValue = try Self._stat( path, followTargetSymlink: followTargetSymlink, retryOnInterrupt: retryOnInterrupt @@ -137,7 +137,7 @@ public struct Stat: RawRepresentable, Sendable { _ ptr: UnsafePointer, followTargetSymlink: Bool, retryOnInterrupt: Bool - ) -> Result { + ) -> Result { var result = CInterop.Stat() return nothingOrErrno(retryOnInterrupt: retryOnInterrupt) { if followTargetSymlink { @@ -145,7 +145,7 @@ public struct Stat: RawRepresentable, Sendable { } else { system_lstat(ptr, &result) } - }.map { Stat(rawValue: result) } + }.map { result } } /// Creates a `Stat` struct from a `FileDescriptor`. @@ -156,7 +156,7 @@ public struct Stat: RawRepresentable, Sendable { _ fd: FileDescriptor, retryOnInterrupt: Bool = true ) throws(Errno) { - self = try Self._fstat( + self.rawValue = try Self._fstat( fd, retryOnInterrupt: retryOnInterrupt ).get() @@ -166,11 +166,11 @@ public struct Stat: RawRepresentable, Sendable { internal static func _fstat( _ fd: FileDescriptor, retryOnInterrupt: Bool - ) -> Result { + ) -> Result { var result = CInterop.Stat() return nothingOrErrno(retryOnInterrupt: retryOnInterrupt) { system_fstat(fd.rawValue, &result) - }.map { Stat(rawValue: result) } + }.map { result } } /// Creates a `Stat` struct from a `FilePath` and `Flags`. @@ -184,7 +184,7 @@ public struct Stat: RawRepresentable, Sendable { flags: Stat.Flags, retryOnInterrupt: Bool = true ) throws(Errno) { - self = try path.withPlatformString { + self.rawValue = try path.withPlatformString { Self._fstatat( $0, relativeTo: _AT_FDCWD, @@ -208,7 +208,7 @@ public struct Stat: RawRepresentable, Sendable { flags: Stat.Flags, retryOnInterrupt: Bool = true ) throws(Errno) { - self = try path.withPlatformString { + self.rawValue = try path.withPlatformString { Self._fstatat( $0, relativeTo: fd.rawValue, @@ -229,7 +229,7 @@ public struct Stat: RawRepresentable, Sendable { flags: Stat.Flags, retryOnInterrupt: Bool = true ) throws(Errno) { - self = try Self._fstatat( + self.rawValue = try Self._fstatat( path, relativeTo: _AT_FDCWD, flags: flags, @@ -251,7 +251,7 @@ public struct Stat: RawRepresentable, Sendable { flags: Stat.Flags, retryOnInterrupt: Bool = true ) throws(Errno) { - self = try Self._fstatat( + self.rawValue = try Self._fstatat( path, relativeTo: fd.rawValue, flags: flags, @@ -265,11 +265,11 @@ public struct Stat: RawRepresentable, Sendable { relativeTo fd: FileDescriptor.RawValue, flags: Stat.Flags, retryOnInterrupt: Bool - ) -> Result { + ) -> Result { var result = CInterop.Stat() return nothingOrErrno(retryOnInterrupt: retryOnInterrupt) { system_fstatat(fd, path, &result, flags.rawValue) - }.map { Stat(rawValue: result) } + }.map { result } } From 55350cd06dd5cc9e67ffa2373128d1dcf6151dd2 Mon Sep 17 00:00:00 2001 From: Jonathan Flat Date: Sun, 2 Nov 2025 22:31:37 -0700 Subject: [PATCH 424/427] Add availability to new CInterop typealiases --- Sources/System/FileSystem/FileFlags.swift | 4 ---- Sources/System/Internals/CInterop.swift | 8 +++++++- Sources/System/Internals/Exports.swift | 4 ++++ 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/Sources/System/FileSystem/FileFlags.swift b/Sources/System/FileSystem/FileFlags.swift index 8fb54938..5a905ed4 100644 --- a/Sources/System/FileSystem/FileFlags.swift +++ b/Sources/System/FileSystem/FileFlags.swift @@ -39,10 +39,6 @@ // |------------------|---------------|---------------|---------------| #if SYSTEM_PACKAGE_DARWIN || os(FreeBSD) || os(OpenBSD) -@available(System 99, *) -extension CInterop { - public typealias FileFlags = UInt32 -} /// File-specific flags found in the `st_flags` property of a `stat` struct /// or used as input to `chflags()`. diff --git a/Sources/System/Internals/CInterop.swift b/Sources/System/Internals/CInterop.swift index 7a35b09c..46406631 100644 --- a/Sources/System/Internals/CInterop.swift +++ b/Sources/System/Internals/CInterop.swift @@ -78,12 +78,18 @@ public enum CInterop { /// on API. public typealias PlatformUnicodeEncoding = UTF8 #endif +} - #if !os(Windows) +#if !os(Windows) +@available(System 99, *) +extension CInterop { public typealias Stat = stat public typealias DeviceID = dev_t public typealias Inode = ino_t public typealias UserID = uid_t public typealias GroupID = gid_t + #if SYSTEM_PACKAGE_DARWIN || os(FreeBSD) || os(OpenBSD) + public typealias FileFlags = UInt32 #endif } +#endif diff --git a/Sources/System/Internals/Exports.swift b/Sources/System/Internals/Exports.swift index 58d0db80..15ee45c3 100644 --- a/Sources/System/Internals/Exports.swift +++ b/Sources/System/Internals/Exports.swift @@ -91,15 +91,19 @@ internal func system_strlen(_ s: UnsafeMutablePointer) -> Int { } #if !os(Windows) +@available(System 99, *) internal func system_stat(_ p: UnsafePointer, _ s: inout CInterop.Stat) -> Int32 { stat(p, &s) } +@available(System 99, *) internal func system_lstat(_ p: UnsafePointer, _ s: inout CInterop.Stat) -> Int32 { lstat(p, &s) } +@available(System 99, *) internal func system_fstat(_ fd: CInt, _ s: inout CInterop.Stat) -> Int32 { fstat(fd, &s) } +@available(System 99, *) internal func system_fstatat(_ fd: CInt, _ p: UnsafePointer, _ s: inout CInterop.Stat, _ flags: CInt) -> Int32 { fstatat(fd, p, &s, flags) } From e133abb3e0dab21627b98bbc8512abc86d53efb0 Mon Sep 17 00:00:00 2001 From: Jonathan Flat Date: Mon, 3 Nov 2025 18:05:50 -0700 Subject: [PATCH 425/427] Fix Stat build on Android --- Sources/System/FileSystem/Stat.swift | 16 ++++++++-------- Sources/System/Internals/CInterop.swift | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Sources/System/FileSystem/Stat.swift b/Sources/System/FileSystem/Stat.swift index c335cf80..e30e2b16 100644 --- a/Sources/System/FileSystem/Stat.swift +++ b/Sources/System/FileSystem/Stat.swift @@ -280,8 +280,8 @@ public struct Stat: RawRepresentable, Sendable { /// The corresponding C property is `st_dev`. @_alwaysEmitIntoClient public var deviceID: DeviceID { - get { DeviceID(rawValue: rawValue.st_dev) } - set { rawValue.st_dev = newValue.rawValue } + get { DeviceID(rawValue: numericCast(rawValue.st_dev)) } + set { rawValue.st_dev = numericCast(newValue.rawValue) } } /// Inode number @@ -289,8 +289,8 @@ public struct Stat: RawRepresentable, Sendable { /// The corresponding C property is `st_ino`. @_alwaysEmitIntoClient public var inode: Inode { - get { Inode(rawValue: rawValue.st_ino) } - set { rawValue.st_ino = newValue.rawValue } + get { Inode(rawValue: numericCast(rawValue.st_ino)) } + set { rawValue.st_ino = numericCast(newValue.rawValue) } } /// File mode @@ -298,8 +298,8 @@ public struct Stat: RawRepresentable, Sendable { /// The corresponding C property is `st_mode`. @_alwaysEmitIntoClient public var mode: FileMode { - get { FileMode(rawValue: rawValue.st_mode) } - set { rawValue.st_mode = newValue.rawValue } + get { FileMode(rawValue: numericCast(rawValue.st_mode)) } + set { rawValue.st_mode = numericCast(newValue.rawValue) } } /// File type for the given mode @@ -366,8 +366,8 @@ public struct Stat: RawRepresentable, Sendable { /// The corresponding C property is `st_rdev`. @_alwaysEmitIntoClient public var specialDeviceID: DeviceID { - get { DeviceID(rawValue: rawValue.st_rdev) } - set { rawValue.st_rdev = newValue.rawValue } + get { DeviceID(rawValue: numericCast(rawValue.st_rdev)) } + set { rawValue.st_rdev = numericCast(newValue.rawValue) } } /// Total size, in bytes diff --git a/Sources/System/Internals/CInterop.swift b/Sources/System/Internals/CInterop.swift index 46406631..7f85b9e7 100644 --- a/Sources/System/Internals/CInterop.swift +++ b/Sources/System/Internals/CInterop.swift @@ -21,7 +21,7 @@ import Musl #elseif canImport(WASILibc) import WASILibc #elseif canImport(Bionic) -@_implementationOnly import CSystem +import CSystem import Bionic #else #error("Unsupported Platform") From b252c123a7c07422d9b5b509b6722ed8daf94bf8 Mon Sep 17 00:00:00 2001 From: Jonathan Flat Date: Mon, 3 Nov 2025 18:11:02 -0700 Subject: [PATCH 426/427] Enable Android SDK build --- .github/workflows/pull_request.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index 49f6dc9b..5d5a3d2c 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -45,6 +45,7 @@ jobs: {"os_version": "focal", "swift_version": "6.2"}, {"os_version": "focal", "swift_version": "nightly-main"}, ] + enable_android_sdk_build: true build-abi-stable: name: Build ABI Stable From f9a2b9443b27437a5ba9869f861ce5494990e46f Mon Sep 17 00:00:00 2001 From: Jonathan Flat Date: Mon, 3 Nov 2025 18:29:15 -0700 Subject: [PATCH 427/427] Exclude focal for Android build --- .github/workflows/pull_request.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index 5d5a3d2c..1b78b37d 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -46,6 +46,10 @@ jobs: {"os_version": "focal", "swift_version": "nightly-main"}, ] enable_android_sdk_build: true + android_exclude_swift_versions: | + [ + {"os_version": "focal", "swift_version": "nightly-main"}, + ] build-abi-stable: name: Build ABI Stable