From b5a4522966d8e642892ae1daa4ce144939d76a40 Mon Sep 17 00:00:00 2001 From: Michael Ilseman Date: Sun, 14 Mar 2021 17:48:45 -0600 Subject: [PATCH 1/4] Emit withPlatformString as an availability workaround for open --- Sources/System/FilePath/FilePathString.swift | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Sources/System/FilePath/FilePathString.swift b/Sources/System/FilePath/FilePathString.swift index 842367c1..d6996e31 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 { + #if !os(Windows) + try withCString(body) + #else 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 d18b09dbc4cc66aa24ce8901790904a78ed99abd Mon Sep 17 00:00:00 2001 From: Michael Ilseman Date: Mon, 15 Mar 2021 13:35:36 -0600 Subject: [PATCH 2/4] 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 caa00999c259a5014a08e78ac1582330d9879bda Mon Sep 17 00:00:00 2001 From: Michael Ilseman Date: Mon, 15 Mar 2021 13:34:02 -0600 Subject: [PATCH 3/4] 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 6813ed3ebe404b3b03cb7638aae1594acd3b455f Mon Sep 17 00:00:00 2001 From: Michael Ilseman Date: Mon, 15 Mar 2021 13:35:23 -0600 Subject: [PATCH 4/4] Sketch: Introduce SystemConfig --- Sources/System/Internals/Syscalls.swift | 27 ++ Sources/System/Limits.swift | 397 ++++++++++++++++++++++++ Tests/SystemTests/LimitsTest.swift | 38 +++ Tests/SystemTests/XCTestManifests.swift | 10 + 4 files changed, 472 insertions(+) create mode 100644 Sources/System/Limits.swift create mode 100644 Tests/SystemTests/LimitsTest.swift diff --git a/Sources/System/Internals/Syscalls.swift b/Sources/System/Internals/Syscalls.swift index ecfdc843..9baa5f60 100644 --- a/Sources/System/Internals/Syscalls.swift +++ b/Sources/System/Internals/Syscalls.swift @@ -115,3 +115,30 @@ internal func system_dup2(_ fd: Int32, _ fd2: Int32) -> Int32 { #endif return dup2(fd, fd2) } + +internal func system_sysconf(_ name: CInt) -> Int { + #if ENABLE_MOCKING + if mockingEnabled { return _mockInt(name) } + #endif + return sysconf(name) +} + +internal func system_pathconf( + _ path: UnsafePointer, _ name: CInt +) -> Int { +#if ENABLE_MOCKING + if mockingEnabled { + return _mockInt(path: path, name) + } +#endif + return pathconf(path, name) +} + +internal func system_fpathconf( + _ fd: CInt, _ name: CInt +) -> Int { +#if ENABLE_MOCKING + if mockingEnabled { return _mockInt(fd, name) } +#endif + return fpathconf(fd, name) +} diff --git a/Sources/System/Limits.swift b/Sources/System/Limits.swift new file mode 100644 index 00000000..c5f387ee --- /dev/null +++ b/Sources/System/Limits.swift @@ -0,0 +1,397 @@ +/* + 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 namespace to access system variables +public enum SystemConfig {} + +extension SystemConfig { + public struct Name: RawRepresentable, Hashable { + @_alwaysEmitIntoClient + public var rawValue: CInt + + @_alwaysEmitIntoClient + public init(rawValue: CInt) { self.rawValue = rawValue } + + @_alwaysEmitIntoClient + fileprivate init(_ raw: CInt) { self.init(rawValue: raw) } + } + + /// Get configurable system variables. + /// + /// The corresponding C function is `sysconf`. + @_alwaysEmitIntoClient + public static func get(_ name: Name) throws -> Int { + try _get(name).get() + } + + @usableFromInline + internal static func _get(_ name: Name) -> Result { + valueOrErrno(system_sysconf(name.rawValue)) + } +} + +extension SystemConfig { + public struct PathName: RawRepresentable, Hashable { + @_alwaysEmitIntoClient + public var rawValue: CInt + + @_alwaysEmitIntoClient + public init(rawValue: CInt) { self.rawValue = rawValue } + + @_alwaysEmitIntoClient + fileprivate init(_ raw: CInt) { self.init(rawValue: raw) } + } + + /// Get configurable pathname variables. + /// + /// The corresponding C function is `pathconf`. + @_alwaysEmitIntoClient + public static func get(_ name: PathName, for path: FilePath) throws -> Int { + try _get(name, for: path).get() + } + + @usableFromInline + internal static func _get( + _ name: PathName, for path: FilePath + ) -> Result { + path.withPlatformString { + valueOrErrno(system_pathconf($0, name.rawValue)) + } + } + + /// Get configurable pathname variables. + /// + /// The corresponding C function is `fpathconf`. + @_alwaysEmitIntoClient + public static func get(_ name: PathName, for fd: FileDescriptor) throws -> Int { + try _get(name, for: fd).get() + } + + @usableFromInline + internal static func _get( + _ name: PathName, for fd: FileDescriptor + ) -> Result { + valueOrErrno(system_fpathconf(fd.rawValue, name.rawValue)) + } + +} + +// MARK: - Constants + +#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") +#endif + +extension SystemConfig.Name { + /// The maximum bytes of argument to execve(2). + /// + /// The corresponding C constant is _SC_ARG_MAX) `` + @_alwaysEmitIntoClient + public static var maxArgumentBytes: Self { Self(_SC_ARG_MAX) } + + /// The maximum number of simultaneous processes per user id. + /// + /// The corresponding C constant is `_SC_CHILD_MAX)` + @_alwaysEmitIntoClient + public static var maxUserProcesses: Self { Self(_SC_CHILD_MAX) } + + /// The frequency of the statistics clock in ticks per second. + /// + /// The corresponding C constant is _SC_CLK_TCK) `` + @_alwaysEmitIntoClient + public static var clockTicks: Self { Self(_SC_CLK_TCK) } + + /// The maximum number of elements in the I/O vector used by readv(2), + /// writev(2), recvmsg(2), and sendmsg(2). + /// + /// The corresponding C constant is _SC_IOV_MAX) `` + @_alwaysEmitIntoClient + public static var maxIOV: Self { Self(_SC_IOV_MAX) } + + /// The maximum number of supplemental groups. + /// + /// The corresponding C constant is `_SC_NGROUPS_MAX` + @_alwaysEmitIntoClient + public static var maxGroups: Self { Self(_SC_NGROUPS_MAX) } + + /// The number of processors configured. + /// + /// The corresponding C constant is `Name` + @_alwaysEmitIntoClient + public static var processorsConfigured: Self { Self(_SC_NPROCESSORS_CONF) } + + /// The number of processors currently online. + /// + /// The corresponding C constant is `Name` + @_alwaysEmitIntoClient + public static var processorsOnline: Self { Self(_SC_NPROCESSORS_ONLN) } + + /// The maximum number of open files per user id. + /// + /// The corresponding C constant is _SC_OPEN_MAX)`` + @_alwaysEmitIntoClient + public static var maxOpenFiles: Self { Self(_SC_OPEN_MAX) } + + /// The size of a system page in bytes. + /// + /// The corresponding C constant is _SC_PAGESIZE)`` + @_alwaysEmitIntoClient + public static var pageSize: Self { Self(_SC_PAGESIZE) } + + /// The minimum maximum number of streams that a process may have open at + /// any one time. + /// + /// The corresponding C constant is `_SC_STREAM_MAX` + @_alwaysEmitIntoClient + public static var maxStreams: Self { Self(_SC_STREAM_MAX) } + + /// The minimum maximum number of types supported for the name of a + /// timezone. + /// + /// The corresponding C constant is `_SC_TZNAME_MAX` + @_alwaysEmitIntoClient + public static var maxTimezones: Self { Self(_SC_TZNAME_MAX) } + + /// Return 1 if job control is available on this system, otherwise -1. + /// + /// The corresponding C constant is `_SC_JOB_CONTROL` + @_alwaysEmitIntoClient + public static var jobControl: Self { Self(_SC_JOB_CONTROL) } + + /// Returns 1 if saved set-group and saved set-user ID is available, + /// otherwise -1. + /// + /// The corresponding C constant is `_SC_SAVED_IDS)` + @_alwaysEmitIntoClient + public static var savedIds: Self { Self(_SC_SAVED_IDS) } + + /// The version of IEEE Std 1003.1 (``POSIX.1'') with which the system + /// attempts to comply. + /// + /// The corresponding C constant is _SC_VERSION) `` + @_alwaysEmitIntoClient + public static var posixVersion: Self { Self(_SC_VERSION) } + + /// The maximum ibase/obase values in the bc(1) utility. + /// + /// The corresponding C constant is `_SC_BC_BASE_MAX` + @_alwaysEmitIntoClient + public static var maxBCBase: Self { Self(_SC_BC_BASE_MAX) } + + /// The maximum array size in the bc(1) utility. + /// + /// The corresponding C constant is `_SC_BC_DIM_MAX` + @_alwaysEmitIntoClient + public static var maxBCArray: Self { Self(_SC_BC_DIM_MAX) } + + /// The maximum scale value in the bc(1) utility. + /// + /// The corresponding C constant is `_SC_BC_SCALE_MAX` + @_alwaysEmitIntoClient + public static var maxBCScale: Self { Self(_SC_BC_SCALE_MAX) } + + /// The maximum string length in the bc(1) utility. + /// + /// The corresponding C constant is `_SC_BC_STRING_MAX` + @_alwaysEmitIntoClient + public static var maxBCString: Self { Self(_SC_BC_STRING_MAX) } + + /// The maximum number of weights that can be assigned to any entry of the + /// LC_COLLATE order keyword in the locale definition file. + /// + /// The corresponding C constant is `Name` + @_alwaysEmitIntoClient + public static var maxCollationWeights: Self { Self(_SC_COLL_WEIGHTS_MAX) } + + /// The maximum number of expressions that can be nested within parenthesis + /// by the expr(1) utility. + /// + /// The corresponding C constant is `_SC_EXPR_NEST_MAX` + @_alwaysEmitIntoClient + public static var maxNestedExpressions: Self { Self(_SC_EXPR_NEST_MAX) } + + /// The maximum length in bytes of a text-processing utility's input line. + /// + /// The corresponding C constant is _SC_LINE_MAX)`` + @_alwaysEmitIntoClient + public static var maxLineBytes: Self { Self(_SC_LINE_MAX) } + + /// The maximum number of repeated occurrences of a regular expression + /// permitted when using interval notation. + /// + /// The corresponding C constant is `_SC_RE_DUP_MAX` + @_alwaysEmitIntoClient + public static var maxRERepeated: Self { Self(_SC_RE_DUP_MAX) } + + /// The version of IEEE Std 1003.2 (``POSIX.2'') with which the system + /// attempts to comply. + /// + /// The corresponding C constant is `_SC_2_VERSION)` + @_alwaysEmitIntoClient + public static var posix2Version: Self { Self(_SC_2_VERSION) } + + /// Return 1 if the system's C-language development facilities support the + /// C-Language Bindings Option, otherwise -1. + /// + /// The corresponding C constant is _SC_2_C_BIND)`` + @_alwaysEmitIntoClient + public static var supportsCBindings: Self { Self(_SC_2_C_BIND) } + + /// Return 1 if the system supports the C-Language Development Utilities + /// Option, otherwise -1. + /// + /// The corresponding C constant is _SC_2_C_DEV) `` + @_alwaysEmitIntoClient + public static var supportsCDevelopment: Self { Self(_SC_2_C_DEV) } + + /// Return 1 if the system supports at least one terminal type capable of + /// all operations described in IEEE Std 1003.2 (``POSIX.2''), otherwise -1. + /// + /// The corresponding C constant is `_SC_2_CHAR_TERM` + @_alwaysEmitIntoClient + public static var supportsPOSIXTerminals: Self { Self(_SC_2_CHAR_TERM) } + + /// Return 1 if the system supports the FORTRAN Development Utilities + /// Option, otherwise -1. + /// + /// The corresponding C constant is `_SC_2_FORT_DEV` + @_alwaysEmitIntoClient + public static var supportsFORTRANDevelopment: Self { Self(_SC_2_FORT_DEV) } + + /// Return 1 if the system supports the FORTRAN Runtime Utilities Option, + /// otherwise -1. + /// + /// The corresponding C constant is `_SC_2_FORT_RUN` + @_alwaysEmitIntoClient + public static var supportsFORTRANRuntime: Self { Self(_SC_2_FORT_RUN) } + + /// Return 1 if the system supports the creation of locales, otherwise -1. + /// + /// The corresponding C constant is `_SC_2_LOCALEDEF` + @_alwaysEmitIntoClient + public static var supportsLocales: Self { Self(_SC_2_LOCALEDEF) } + + /// Return 1 if the system supports the Software Development Utilities + /// Option, otherwise -1. + /// + /// The corresponding C constant is _SC_2_SW_DEV)`` + @_alwaysEmitIntoClient + public static var supportsSoftwareDevelopment: Self { Self(_SC_2_SW_DEV) } + + /// Return 1 if the system supports the User Portability Utilities Option, + /// otherwise -1. + /// + /// The corresponding C constant _SC_2_UPE)is `` + @_alwaysEmitIntoClient + public static var supportsUserPortability: Self { Self(_SC_2_UPE) } + + /// The number of pages of physical memory. Note that it is possible that + /// the product of this value and the value of _SC_PAGESIZE will overflow a + /// long in some configurations on a 32bit machine. + /// + /// The corresponding C constant is `_SC_PHYS_PAGES` + @_alwaysEmitIntoClient + public static var physicalMemoryPages: Self { Self(_SC_PHYS_PAGES) } +} + +extension SystemConfig.PathName { + + /// The maximum file link count. + /// + /// The corresponding C constant is `_PC_LINK_MAX`. + @_alwaysEmitIntoClient + public static var maxLink: Self { Self(_PC_LINK_MAX) } + + /// The maximum number of bytes in terminal canonical input line. + /// + /// The corresponding C constant is `_PC_MAX_CANON`. + @_alwaysEmitIntoClient + public static var maxTerminalCanonicalLineBytes: Self { + Self(_PC_MAX_CANON) + } + + /// The minimum maximum number of bytes for which space is available in a + /// terminal input queue. + /// + /// The corresponding C constant is `_PC_MAX_INPUT`. + @_alwaysEmitIntoClient + public static var maxTerminalInputBytes: Self { Self(_PC_MAX_INPUT) } + + /// The maximum number of bytes in a file name. + /// + /// The corresponding C constant is `_PC_NAME_MAX`. + @_alwaysEmitIntoClient + public static var maxFileNameBytes: Self { Self(_PC_NAME_MAX) } + + /// The maximum number of bytes in a pathname. + /// + /// The corresponding C constant is `_PC_PATH_MAX`. + @_alwaysEmitIntoClient + public static var maxPathBytes: Self { Self(_PC_PATH_MAX) } + + /// The maximum number of bytes which will be written atomically to a pipe. + /// + /// The corresponding C constant is `_PC_PIPE_BUF`. + @_alwaysEmitIntoClient + public static var maxPipeBufferBytes: Self { Self(_PC_PIPE_BUF) } + + /// Return 1 if appropriate privileges are required for the chown(2) system + /// call, otherwise 0. + /// + /// The corresponding C constant is `_PC_CHOWN_RESTRICTED`. + @_alwaysEmitIntoClient + public static var isCHOWNRestricted: Self { + Self(_PC_CHOWN_RESTRICTED) + } + + /// Return 1 if file names longer than KERN_NAME_MAX are truncated. + /// + /// The corresponding C constant is `_PC_NO_TRUNC`. + @_alwaysEmitIntoClient + public static var isNameTruncated: Self { Self(_PC_NO_TRUNC) } + + /// Returns the terminal character disabling value. + /// + /// The corresponding C constant is `_PC_VDISABLE`. + @_alwaysEmitIntoClient + public static var disableTerminalCharacter: Self { Self(_PC_VDISABLE) } + + /// Returns the number of bits used to store maximum extended attribute size + /// in bytes. For example, if the maximum attribute size supported by a + /// file system is 128K, the value returned will be 18. However a value 18 + /// can mean that the maximum attribute size can be anywhere from (256KB - + /// 1) to 128KB. As a special case, the resource fork can have much larger + /// size, and some file system specific extended attributes can have smaller + /// and preset size; for example, Finder Info is always 32 bytes. + /// + /// The corresponding C constant is `_PC_XATTR_SIZE_BITS`. + @_alwaysEmitIntoClient + public static var maxExtendedAttributeByteSizeInBits: Self { + Self(_PC_XATTR_SIZE_BITS) + } + + /// If a file system supports the reporting of holes (see lseek(2)), + /// pathconf() and fpathconf() return a positive number that represents the + /// minimum hole size returned in bytes. The offsets of holes returned will + /// be aligned to this same value. A special value of 1 is returned if the + /// file system does not specify the minimum hole size but still reports + /// holes. + /// + /// The corresponding C constant is `_PC_MIN_HOLE_SIZE`. + @_alwaysEmitIntoClient + public static var minimumHoleSize: Self { Self(_PC_MIN_HOLE_SIZE) } +} diff --git a/Tests/SystemTests/LimitsTest.swift b/Tests/SystemTests/LimitsTest.swift new file mode 100644 index 00000000..69e48940 --- /dev/null +++ b/Tests/SystemTests/LimitsTest.swift @@ -0,0 +1,38 @@ +/* + 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 +import SystemPackage +#else +import System +#endif + +final class SystemConfigTest: XCTestCase { + func testSystemConfig() { + let fd = FileDescriptor(rawValue: 1) + let tests: Array = [ + MockTestCase(name: "sysconf", _SC_CHILD_MAX, interruptable: false) { + _ in + _ = try SystemConfig.get(.maxUserProcesses) + }, + MockTestCase(name: "pathconf", "a_path", _PC_NAME_MAX, interruptable: false) { + _ in + _ = try SystemConfig.get(.maxFileNameBytes, for: "a_path") + }, + MockTestCase(name: "fpathconf", 1, _PC_NAME_MAX, interruptable: false) { + _ in + _ = try SystemConfig.get(.maxFileNameBytes, for: fd) + }, + ] + tests.forEach { $0.runAllTests() } + } + +} diff --git a/Tests/SystemTests/XCTestManifests.swift b/Tests/SystemTests/XCTestManifests.swift index 68eb9e82..92574858 100644 --- a/Tests/SystemTests/XCTestManifests.swift +++ b/Tests/SystemTests/XCTestManifests.swift @@ -94,6 +94,15 @@ extension MockingTest { ] } +extension SystemConfigTest { + // DO NOT MODIFY: This is autogenerated, use: + // `swift test --generate-linuxmain` + // to regenerate. + static let __allTests__SystemConfigTest = [ + ("testSystemConfig", testSystemConfig), + ] +} + 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(SystemConfigTest.__allTests__SystemConfigTest), testCase(SystemStringTest.__allTests__SystemStringTest), ] }