├── .github ├── CODEOWNERS └── workflows │ └── pull_request.yml ├── .gitignore ├── .spi.yml ├── .swiftci ├── 5_10_ubuntu2204 ├── 5_9_ubuntu2204 ├── nightly_6_0_macos ├── nightly_6_0_ubuntu2204 ├── nightly_main_macos ├── nightly_main_ubuntu2204 └── nightly_main_windows ├── CMakeLists.txt ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE.txt ├── Package.swift ├── README.md ├── Sources ├── CMakeLists.txt ├── CSystem │ ├── CMakeLists.txt │ ├── include │ │ ├── CSystemLinux.h │ │ ├── CSystemWASI.h │ │ ├── CSystemWindows.h │ │ └── module.modulemap │ └── shims.c └── System │ ├── CMakeLists.txt │ ├── Errno.swift │ ├── ErrnoWindows.swift │ ├── FileDescriptor.swift │ ├── FileHelpers.swift │ ├── FileOperations.swift │ ├── FilePath │ ├── FilePath.swift │ ├── FilePathComponentView.swift │ ├── FilePathComponents.swift │ ├── FilePathParsing.swift │ ├── FilePathString.swift │ ├── FilePathSyntax.swift │ ├── FilePathTemp.swift │ ├── FilePathTempPosix.swift │ ├── FilePathTempWindows.swift │ └── FilePathWindows.swift │ ├── FilePermissions.swift │ ├── Internals │ ├── Backcompat.swift │ ├── CInterop.swift │ ├── Constants.swift │ ├── Exports.swift │ ├── Mocking.swift │ ├── RawBuffer.swift │ ├── Syscalls.swift │ └── WindowsSyscallAdapters.swift │ ├── MachPort.swift │ ├── PlatformString.swift │ ├── SystemString.swift │ ├── Util+StringArray.swift │ ├── Util.swift │ └── UtilConsumers.swift ├── Tests └── SystemTests │ ├── ErrnoTest.swift │ ├── FileDescriptorExtras.swift │ ├── FileOperationsTest.swift │ ├── FileOperationsTestWindows.swift │ ├── FilePathTests │ ├── FilePathComponentsTest.swift │ ├── FilePathDecodable.swift │ ├── FilePathExtras.swift │ ├── FilePathParsingTest.swift │ ├── FilePathSyntaxTest.swift │ ├── FilePathTempTest.swift │ └── FilePathTest.swift │ ├── FileTypesTest.swift │ ├── MachPortTests.swift │ ├── MockingTest.swift │ ├── SystemCharTest.swift │ ├── SystemStringTests.swift │ ├── TestingInfrastructure.swift │ └── UtilTests.swift ├── Utilities └── expand-availability.py └── cmake └── modules ├── CMakeLists.txt ├── SwiftSupport.cmake └── SwiftSystemConfig.cmake.in /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # Lines starting with '#' are comments. 2 | # Each line is a case-sensitive file pattern followed by one or more owners. 3 | # Order is important. The last matching pattern has the most precedence. 4 | # More information: https://docs.github.com/en/articles/about-code-owners 5 | # 6 | # Please mirror the repository's file hierarchy in case-sensitive lexicographic 7 | # order. 8 | 9 | # Default owners 10 | * @glessard @lorentey @milseman 11 | 12 | # Swift CI configuration files 13 | .swiftci/ @shahmishal 14 | -------------------------------------------------------------------------------- /.github/workflows/pull_request.yml: -------------------------------------------------------------------------------- 1 | name: Pull request 2 | 3 | on: 4 | pull_request: 5 | types: [opened, reopened, synchronize] 6 | 7 | jobs: 8 | tests: 9 | name: Test 10 | uses: swiftlang/github-workflows/.github/workflows/swift_package_test.yml@main 11 | with: 12 | linux_exclude_swift_versions: '[{"swift_version": "5.8"}]' 13 | soundness: 14 | name: Soundness 15 | uses: swiftlang/github-workflows/.github/workflows/soundness.yml@main 16 | with: 17 | license_header_check_project_name: "Swift.org" 18 | # https://github.com/apple/swift-system/issues/224 19 | docs_check_enabled: false 20 | unacceptable_language_check_enabled: false 21 | license_header_check_enabled: false 22 | format_check_enabled: false 23 | python_lint_check_enabled: false 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /.swiftpm 3 | /.build 4 | /Packages 5 | /*.xcodeproj 6 | xcuserdata/ 7 | .*.sw? 8 | /.swiftpm 9 | .docc-build 10 | -------------------------------------------------------------------------------- /.spi.yml: -------------------------------------------------------------------------------- 1 | version: 1 2 | builder: 3 | configs: 4 | - documentation_targets: [SystemPackage] 5 | -------------------------------------------------------------------------------- /.swiftci/5_10_ubuntu2204: -------------------------------------------------------------------------------- 1 | LinuxSwiftPackageJob { 2 | swift_version_tag = "5.10-jammy" 3 | repo = "swift-system" 4 | branch = "main" 5 | } 6 | -------------------------------------------------------------------------------- /.swiftci/5_9_ubuntu2204: -------------------------------------------------------------------------------- 1 | LinuxSwiftPackageJob { 2 | swift_version_tag = "5.9-jammy" 3 | repo = "swift-system" 4 | branch = "main" 5 | } 6 | -------------------------------------------------------------------------------- /.swiftci/nightly_6_0_macos: -------------------------------------------------------------------------------- 1 | macOSSwiftPackageJob { 2 | swift_version = "6.0" 3 | repo = "swift-system" 4 | branch = "main" 5 | } 6 | -------------------------------------------------------------------------------- /.swiftci/nightly_6_0_ubuntu2204: -------------------------------------------------------------------------------- 1 | LinuxSwiftPackageJob { 2 | nightly_docker_tag = "nightly-6.0-jammy" 3 | repo = "swift-system" 4 | branch = "main" 5 | } 6 | -------------------------------------------------------------------------------- /.swiftci/nightly_main_macos: -------------------------------------------------------------------------------- 1 | macOSSwiftPackageJob { 2 | swift_version = "main" 3 | repo = "swift-system" 4 | branch = "main" 5 | } 6 | -------------------------------------------------------------------------------- /.swiftci/nightly_main_ubuntu2204: -------------------------------------------------------------------------------- 1 | LinuxSwiftPackageJob { 2 | nightly_docker_tag = "nightly-jammy" 3 | repo = "swift-system" 4 | branch = "main" 5 | } 6 | -------------------------------------------------------------------------------- /.swiftci/nightly_main_windows: -------------------------------------------------------------------------------- 1 | WindowsSwiftPackageWithDockerImageJob { 2 | docker_image = "swiftlang/swift:nightly-windowsservercore-1809" 3 | repo = "swift-system" 4 | branch = "main" 5 | sub_dir = "swift-system" 6 | label = "windows-server-2019" 7 | } 8 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | #[[ 2 | This source file is part of the Swift System open source project 3 | 4 | Copyright (c) 2020 Apple Inc. and the Swift System project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | #]] 9 | 10 | cmake_minimum_required(VERSION 3.16.0) 11 | project(SwiftSystem 12 | LANGUAGES C Swift) 13 | 14 | include(GNUInstallDirs) 15 | 16 | list(APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake/modules) 17 | 18 | set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) 19 | set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) 20 | set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) 21 | set(CMAKE_Swift_MODULE_DIRECTORY ${CMAKE_BINARY_DIR}/swift) 22 | 23 | include(SwiftSupport) 24 | 25 | add_subdirectory(Sources) 26 | add_subdirectory(cmake/modules) 27 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Code of Conduct 2 | 3 | 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. 4 | 5 | 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. 6 | 7 | ### Contributor Code of Conduct v1.4 8 | 9 | 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. 10 | 11 | Examples of behavior that contributes to creating a positive environment include: 12 | 13 | * 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.) 14 | * Being respectful of differing viewpoints and experiences 15 | * Gracefully accepting constructive criticism 16 | * Focusing on what is best for the community 17 | * Showing empathy towards other community members 18 | 19 | Examples of unacceptable behavior by participants include: 20 | 21 | * The use of sexualized language or imagery and unwelcome sexual attention or advances 22 | * Trolling, insulting/derogatory comments, and personal or political attacks 23 | * Public or private harassment 24 | * Publishing others’ private information, such as a physical or electronic address, without explicit permission 25 | * Other conduct which could reasonably be considered inappropriate in a professional setting 26 | 27 | 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. 28 | 29 | 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. 30 | 31 | 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. 32 | 33 | 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. 34 | 35 | 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. 36 | If you disagree with a moderation action, you can appeal to the Core Team (or individual Core Team members) privately. 37 | 38 | This policy is adapted from the Contributor Code of Conduct [version 1.4](https://www.contributor-covenant.org/version/1/4/code-of-conduct/). 39 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | By submitting a pull request, you represent that you have the right to license your 2 | contribution to Apple and the community, and agree by submitting the patch that 3 | your contributions are licensed under the [Swift license](https://swift.org/LICENSE.txt). 4 | 5 | --- 6 | 7 | Before submitting the pull request, please make sure you have tested your changes 8 | and that they follow the Swift project [guidelines for contributing 9 | code](https://swift.org/contributing/#contributing-code). 10 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.9 2 | //===----------------------------------------------------------------------===// 3 | // 4 | // This source file is part of the Swift System open source project 5 | // 6 | // Copyright (c) 2020 - 2024 Apple Inc. and the Swift System project authors 7 | // Licensed under Apache License v2.0 with Runtime Library Exception 8 | // 9 | // See https://swift.org/LICENSE.txt for license information 10 | // 11 | //===----------------------------------------------------------------------===// 12 | 13 | import PackageDescription 14 | 15 | let cSettings: [CSetting] = [ 16 | .define("_CRT_SECURE_NO_WARNINGS", .when(platforms: [.windows])), 17 | ] 18 | 19 | let swiftSettings: [SwiftSetting] = [ 20 | .define( 21 | "SYSTEM_PACKAGE_DARWIN", 22 | .when(platforms: [.macOS, .macCatalyst, .iOS, .watchOS, .tvOS, .visionOS])), 23 | .define("SYSTEM_PACKAGE"), 24 | .define("ENABLE_MOCKING", .when(configuration: .debug)), 25 | ] 26 | 27 | let package = Package( 28 | name: "swift-system", 29 | products: [ 30 | .library(name: "SystemPackage", targets: ["SystemPackage"]), 31 | ], 32 | dependencies: [], 33 | targets: [ 34 | .target( 35 | name: "CSystem", 36 | dependencies: [], 37 | exclude: ["CMakeLists.txt"], 38 | cSettings: cSettings), 39 | .target( 40 | name: "SystemPackage", 41 | dependencies: ["CSystem"], 42 | path: "Sources/System", 43 | exclude: ["CMakeLists.txt"], 44 | cSettings: cSettings, 45 | swiftSettings: swiftSettings), 46 | .testTarget( 47 | name: "SystemTests", 48 | dependencies: ["SystemPackage"], 49 | cSettings: cSettings, 50 | swiftSettings: swiftSettings), 51 | ]) 52 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Swift System 2 | 3 | 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. 4 | 5 | ## No Cross-platform Abstractions 6 | 7 | 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. 8 | 9 | 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. 10 | 11 | (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.) 12 | 13 | ## Usage 14 | 15 | ```swift 16 | import SystemPackage 17 | 18 | let message: String = "Hello, world!" + "\n" 19 | let path: FilePath = "/tmp/log" 20 | let fd = try FileDescriptor.open( 21 | path, .writeOnly, options: [.append, .create], permissions: .ownerReadWrite) 22 | try fd.closeAfter { 23 | _ = try fd.writeAll(message.utf8) 24 | } 25 | ``` 26 | 27 | [API documentation](https://swiftpackageindex.com/apple/swift-system/main/documentation/SystemPackage) 28 | 29 | ## Adding `SystemPackage` as a Dependency 30 | 31 | To use the `SystemPackage` library in a SwiftPM project, 32 | add the following line to the dependencies in your `Package.swift` file: 33 | 34 | ```swift 35 | .package(url: "https://github.com/apple/swift-system", from: "1.4.0"), 36 | ``` 37 | 38 | Finally, include `"SystemPackage"` as a dependency for your executable target: 39 | 40 | ```swift 41 | let package = Package( 42 | // name, platforms, products, etc. 43 | dependencies: [ 44 | .package(url: "https://github.com/apple/swift-system", from: "1.4.0"), 45 | // other dependencies 46 | ], 47 | targets: [ 48 | .target(name: "MyTarget", dependencies: [ 49 | .product(name: "SystemPackage", package: "swift-system"), 50 | ]), 51 | // other targets 52 | ] 53 | ) 54 | ``` 55 | 56 | ## Source Stability 57 | 58 | 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: 59 | 60 | | Platform type | Source Stability | 61 | | ----------------- | --------------- | 62 | | Darwin (macOS, iOS, etc.) | Stable | 63 | | POSIX (Linux, WASI, etc.) | Stable | 64 | | Windows | Unstable | 65 | 66 | 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. 67 | 68 | [semver]: https://semver.org 69 | 70 | The public API of the swift-system package consists of non-underscored declarations that are marked `public` in the `SystemPackage` module. 71 | 72 | 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: 73 | 74 | - `FooModule.Bar._someMember(value:)` (underscored member) 75 | - `FooModule._Bar.someMember` (underscored type) 76 | - `_FooModule.Bar` (underscored module) 77 | - `FooModule.Bar.init(_value:)` (underscored initializer) 78 | 79 | 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. 80 | 81 | Future minor versions of the package may update these rules as needed. 82 | 83 | ## Toolchain Requirements 84 | 85 | The following table maps existing package releases to their minimum required Swift toolchain release: 86 | 87 | | Package version | Swift version | Xcode release | 88 | | ----------------------- | --------------- | ------------- | 89 | | swift-system 1.3.x | >= Swift 5.8 | >= Xcode 14.3 | 90 | | swift-system 1.4.x | >= Swift 5.9 | >= Xcode 15.0 | 91 | 92 | 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. 93 | 94 | (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.) 95 | 96 | ## Licensing 97 | 98 | See [LICENSE](LICENSE.txt) for license information. 99 | 100 | ## Contributing 101 | 102 | Before contributing, please read [CONTRIBUTING.md](CONTRIBUTING.md). 103 | 104 | ### Branching Strategy 105 | 106 | We maintain separate branches for each active minor version of the package: 107 | 108 | | Package version | Branch | 109 | | ----------------------- | ----------- | 110 | | swift-system 1.3.x | release/1.3 | 111 | | swift-system 1.4.x (unreleased) | release/1.4 | 112 | | swift-system 1.5.x (unreleased) | main | 113 | 114 | 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: 115 | 116 | `release/1.3` → `release/1.4` → `main` 117 | 118 | 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.) 119 | 120 | ### Code of Conduct 121 | 122 | 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]. 123 | 124 | [coc]: CODE_OF_CONDUCT.md 125 | -------------------------------------------------------------------------------- /Sources/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | #[[ 2 | This source file is part of the Swift System open source project 3 | 4 | Copyright (c) 2020 Apple Inc. and the Swift System project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | #]] 9 | 10 | add_subdirectory(CSystem) 11 | add_subdirectory(System) 12 | -------------------------------------------------------------------------------- /Sources/CSystem/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | #[[ 2 | This source file is part of the Swift System open source project 3 | 4 | Copyright (c) 2020 Apple Inc. and the Swift System project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | #]] 9 | 10 | add_library(CSystem INTERFACE) 11 | target_include_directories(CSystem INTERFACE 12 | "$" 13 | $) 14 | 15 | install(FILES 16 | include/CSystemLinux.h 17 | include/CSystemWindows.h 18 | include/module.modulemap 19 | DESTINATION include/CSystem) 20 | set_property(GLOBAL APPEND PROPERTY SWIFT_SYSTEM_EXPORTS CSystem) 21 | -------------------------------------------------------------------------------- /Sources/CSystem/include/CSystemLinux.h: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift System open source project 3 | 4 | Copyright (c) 2020 Apple Inc. and the Swift System project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | */ 9 | 10 | #ifdef __linux__ 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #endif 25 | 26 | -------------------------------------------------------------------------------- /Sources/CSystem/include/CSystemWASI.h: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift System open source project 3 | 4 | Copyright (c) 2024 Apple Inc. and the Swift System project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | */ 9 | 10 | #pragma once 11 | 12 | #if __wasi__ 13 | 14 | #include 15 | #include 16 | 17 | // wasi-libc defines the following constants in a way that Clang Importer can't 18 | // understand, so we need to expose them manually. 19 | static inline int32_t _getConst_O_ACCMODE(void) { return O_ACCMODE; } 20 | static inline int32_t _getConst_O_APPEND(void) { return O_APPEND; } 21 | static inline int32_t _getConst_O_CREAT(void) { return O_CREAT; } 22 | static inline int32_t _getConst_O_DIRECTORY(void) { return O_DIRECTORY; } 23 | static inline int32_t _getConst_O_EXCL(void) { return O_EXCL; } 24 | static inline int32_t _getConst_O_NONBLOCK(void) { return O_NONBLOCK; } 25 | static inline int32_t _getConst_O_TRUNC(void) { return O_TRUNC; } 26 | static inline int32_t _getConst_O_WRONLY(void) { return O_WRONLY; } 27 | 28 | static inline int32_t _getConst_EWOULDBLOCK(void) { return EWOULDBLOCK; } 29 | static inline int32_t _getConst_EOPNOTSUPP(void) { return EOPNOTSUPP; } 30 | 31 | #endif 32 | -------------------------------------------------------------------------------- /Sources/CSystem/include/CSystemWindows.h: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift System open source project 3 | 4 | Copyright (c) 2020 Apple Inc. and the Swift System project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | */ 9 | 10 | #if defined(_WIN32) 11 | 12 | #define NOMINMAX 13 | #define WIN32_LEAN_AND_MEAN 14 | #define VC_EXTRA_LEAN 15 | #include 16 | 17 | #endif 18 | -------------------------------------------------------------------------------- /Sources/CSystem/include/module.modulemap: -------------------------------------------------------------------------------- 1 | module CSystem { 2 | header "CSystemLinux.h" 3 | header "CSystemWASI.h" 4 | header "CSystemWindows.h" 5 | export * 6 | } 7 | -------------------------------------------------------------------------------- /Sources/CSystem/shims.c: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift System open source project 3 | 4 | Copyright (c) 2020 Apple Inc. and the Swift System project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | */ 9 | 10 | #ifdef __linux__ 11 | 12 | #include 13 | 14 | #endif 15 | 16 | #if defined(_WIN32) 17 | #include 18 | #endif 19 | -------------------------------------------------------------------------------- /Sources/System/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | #[[ 2 | This source file is part of the Swift System open source project 3 | 4 | Copyright (c) 2020 Apple Inc. and the Swift System project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | #]] 9 | 10 | add_library(SystemPackage 11 | Errno.swift 12 | FileDescriptor.swift 13 | FileHelpers.swift 14 | FileOperations.swift 15 | FilePermissions.swift 16 | MachPort.swift 17 | PlatformString.swift 18 | SystemString.swift 19 | Util.swift 20 | Util+StringArray.swift 21 | UtilConsumers.swift) 22 | set_target_properties(SystemPackage PROPERTIES 23 | INTERFACE_INCLUDE_DIRECTORIES ${CMAKE_Swift_MODULE_DIRECTORY}) 24 | target_sources(SystemPackage PRIVATE 25 | FilePath/FilePath.swift 26 | FilePath/FilePathComponents.swift 27 | FilePath/FilePathComponentView.swift 28 | FilePath/FilePathParsing.swift 29 | FilePath/FilePathString.swift 30 | FilePath/FilePathSyntax.swift 31 | FilePath/FilePathWindows.swift) 32 | target_sources(SystemPackage PRIVATE 33 | Internals/Backcompat.swift 34 | Internals/CInterop.swift 35 | Internals/Constants.swift 36 | Internals/Exports.swift 37 | Internals/Mocking.swift 38 | Internals/RawBuffer.swift 39 | Internals/Syscalls.swift 40 | Internals/WindowsSyscallAdapters.swift) 41 | target_link_libraries(SystemPackage PUBLIC 42 | CSystem) 43 | 44 | set(SWIFT_SYSTEM_APPLE_PLATFORMS "Darwin" "iOS" "watchOS" "tvOS" "visionOS") 45 | if(CMAKE_SYSTEM_NAME IN_LIST SWIFT_SYSTEM_APPLE_PLATFORMS) 46 | target_compile_definitions(SystemPackage PRIVATE SYSTEM_PACKAGE_DARWIN) 47 | endif() 48 | 49 | _install_target(SystemPackage) 50 | set_property(GLOBAL APPEND PROPERTY SWIFT_SYSTEM_EXPORTS SystemPackage) 51 | -------------------------------------------------------------------------------- /Sources/System/ErrnoWindows.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift System open source project 3 | 4 | Copyright (c) 2024 Apple Inc. and the Swift System project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | */ 9 | 10 | #if os(Windows) 11 | 12 | import WinSDK 13 | 14 | extension Errno { 15 | internal init(windowsError: DWORD) { 16 | self.init(rawValue: _mapWindowsErrorToErrno(windowsError)) 17 | } 18 | } 19 | 20 | #endif 21 | -------------------------------------------------------------------------------- /Sources/System/FileHelpers.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift System open source project 3 | 4 | Copyright (c) 2020 Apple Inc. and the Swift System project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | */ 9 | 10 | @available(/*System 0.0.1: macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0*/iOS 8, *) 11 | extension FileDescriptor { 12 | /// Runs a closure and then closes the file descriptor, even if an error occurs. 13 | /// 14 | /// - Parameter body: The closure to run. 15 | /// If the closure throws an error, 16 | /// this method closes the file descriptor before it rethrows that error. 17 | /// 18 | /// - Returns: The value returned by the closure. 19 | /// 20 | /// If `body` throws an error 21 | /// or an error occurs while closing the file descriptor, 22 | /// this method rethrows that error. 23 | public func closeAfter(_ body: () throws -> R) throws -> R { 24 | // No underscore helper, since the closure's throw isn't necessarily typed. 25 | let result: R 26 | do { 27 | result = try body() 28 | } catch { 29 | _ = try? self.close() // Squash close error and throw closure's 30 | throw error 31 | } 32 | try self.close() 33 | return result 34 | } 35 | 36 | /// Writes a sequence of bytes to the current offset 37 | /// and then updates the offset. 38 | /// 39 | /// - Parameter sequence: The bytes to write. 40 | /// - Returns: The number of bytes written, equal to the number of elements in `sequence`. 41 | /// 42 | /// This method either writes the entire contents of `sequence`, 43 | /// or throws an error if only part of the content was written. 44 | /// 45 | /// Writes to the position associated with this file descriptor, and 46 | /// increments that position by the number of bytes written. 47 | /// See also ``seek(offset:from:)``. 48 | /// 49 | /// If `sequence` doesn't implement 50 | /// the method, 51 | /// temporary space will be allocated as needed. 52 | @_alwaysEmitIntoClient 53 | @discardableResult 54 | public func writeAll( 55 | _ sequence: S 56 | ) throws -> Int where S.Element == UInt8 { 57 | return try _writeAll(sequence).get() 58 | } 59 | 60 | @usableFromInline 61 | internal func _writeAll( 62 | _ sequence: S 63 | ) -> Result where S.Element == UInt8 { 64 | sequence._withRawBufferPointer { buffer in 65 | var idx = 0 66 | while idx < buffer.count { 67 | switch _write( 68 | UnsafeRawBufferPointer(rebasing: buffer[idx...]), retryOnInterrupt: true 69 | ) { 70 | case .success(let numBytes): idx += numBytes 71 | case .failure(let err): return .failure(err) 72 | } 73 | } 74 | assert(idx == buffer.count) 75 | return .success(buffer.count) 76 | } 77 | } 78 | 79 | /// Writes a sequence of bytes to the given offset. 80 | /// 81 | /// - Parameters: 82 | /// - offset: The file offset where writing begins. 83 | /// - sequence: The bytes to write. 84 | /// - Returns: The number of bytes written, equal to the number of elements in `sequence`. 85 | /// 86 | /// This method either writes the entire contents of `sequence`, 87 | /// or throws an error if only part of the content was written. 88 | /// Unlike ``writeAll(_:)``, 89 | /// this method preserves the file descriptor's existing offset. 90 | /// 91 | /// If `sequence` doesn't implement 92 | /// the method, 93 | /// temporary space will be allocated as needed. 94 | @_alwaysEmitIntoClient 95 | @discardableResult 96 | public func writeAll( 97 | toAbsoluteOffset offset: Int64, _ sequence: S 98 | ) throws -> Int where S.Element == UInt8 { 99 | try _writeAll(toAbsoluteOffset: offset, sequence).get() 100 | } 101 | 102 | @usableFromInline 103 | internal func _writeAll( 104 | toAbsoluteOffset offset: Int64, _ sequence: S 105 | ) -> Result where S.Element == UInt8 { 106 | sequence._withRawBufferPointer { buffer in 107 | var idx = 0 108 | while idx < buffer.count { 109 | switch _write( 110 | toAbsoluteOffset: offset + Int64(idx), 111 | UnsafeRawBufferPointer(rebasing: buffer[idx...]), 112 | retryOnInterrupt: true 113 | ) { 114 | case .success(let numBytes): idx += numBytes 115 | case .failure(let err): return .failure(err) 116 | } 117 | } 118 | assert(idx == buffer.count) 119 | return .success(buffer.count) 120 | } 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /Sources/System/FilePath/FilePath.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift System open source project 3 | 4 | Copyright (c) 2020 Apple Inc. and the Swift System project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | */ 9 | 10 | /// Represents a location in the file system. 11 | /// 12 | /// This structure recognizes directory separators (e.g. `/`), roots, and 13 | /// requires that the content terminates in a NUL (`0x0`). Beyond that, it 14 | /// does not give any meaning to the bytes that it contains. The file system 15 | /// defines how the content is interpreted; for example, by its choice of string 16 | /// encoding. 17 | /// 18 | /// On construction, `FilePath` will normalize separators by removing 19 | /// redundant intermediary separators and stripping any trailing separators. 20 | /// On Windows, `FilePath` will also normalize forward slashes `/` into 21 | /// backslashes `\`, as preferred by the platform. 22 | /// 23 | /// The code below creates a file path from a string literal, 24 | /// and then uses it to open and append to a log file: 25 | /// 26 | /// let message: String = "This is a log message." 27 | /// let path: FilePath = "/tmp/log" 28 | /// let fd = try FileDescriptor.open(path, .writeOnly, options: .append) 29 | /// try fd.closeAfter { try fd.writeAll(message.utf8) } 30 | /// 31 | /// File paths conform to the 32 | /// 33 | /// and protocols 34 | /// by performing the protocols' operations on their raw byte contents. 35 | /// This conformance allows file paths to be used, 36 | /// for example, as keys in a dictionary. 37 | /// However, the rules for path equivalence 38 | /// are file-system–specific and have additional considerations 39 | /// like case insensitivity, Unicode normalization, and symbolic links. 40 | @available(/*System 0.0.1: macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0*/iOS 8, *) 41 | public struct FilePath: Sendable { 42 | // TODO(docs): Section on all the new syntactic operations, lexical normalization, decomposition, 43 | // components, etc. 44 | internal var _storage: SystemString 45 | 46 | /// Creates an empty, null-terminated path. 47 | public init() { 48 | self._storage = SystemString() 49 | _invariantCheck() 50 | } 51 | 52 | // In addition to the empty init, this init will properly normalize 53 | // separators. All other initializers should be implemented by 54 | // ultimately deferring to a normalizing init. 55 | internal init(_ str: SystemString) { 56 | self._storage = str 57 | self._normalizeSeparators() 58 | _invariantCheck() 59 | } 60 | } 61 | 62 | @available(/*System 0.0.1: macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0*/iOS 8, *) 63 | extension FilePath { 64 | /// The length of the file path, excluding the null terminator. 65 | public var length: Int { _storage.length } 66 | } 67 | 68 | @available(/*System 0.0.1: macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0*/iOS 8, *) 69 | extension FilePath: Hashable {} 70 | 71 | @available(/*System 0.0.1: macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0*/iOS 8, *) 72 | extension FilePath: Codable { 73 | // Encoder is synthesized; it probably should have been explicit and used 74 | // a single-value container, but making that change now is somewhat risky. 75 | 76 | // Decoder is written explicitly to ensure that we validate invariants on 77 | // untrusted input. 78 | public init(from decoder: any Decoder) throws { 79 | let container = try decoder.container(keyedBy: CodingKeys.self) 80 | self._storage = try container.decode(SystemString.self, forKey: ._storage) 81 | guard _invariantsSatisfied() else { 82 | throw DecodingError.dataCorruptedError( 83 | forKey: ._storage, 84 | in: container, 85 | debugDescription: 86 | "Encoding does not satisfy the invariants of FilePath" 87 | ) 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /Sources/System/FilePath/FilePathComponentView.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift System open source project 3 | 4 | Copyright (c) 2020 Apple Inc. and the Swift System project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | */ 9 | 10 | // MARK: - API 11 | 12 | @available(/*System 0.0.2: macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0*/iOS 8, *) 13 | extension FilePath { 14 | /// A bidirectional, range replaceable collection of the non-root components 15 | /// that make up a file path. 16 | /// 17 | /// ComponentView provides access to standard `BidirectionalCollection` 18 | /// algorithms for accessing components from the front or back, as well as 19 | /// standard `RangeReplaceableCollection` algorithms for modifying the 20 | /// file path using component or range of components granularity. 21 | /// 22 | /// Example: 23 | /// 24 | /// var path: FilePath = "/./home/./username/scripts/./tree" 25 | /// let scriptIdx = path.components.lastIndex(of: "scripts")! 26 | /// path.components.insert("bin", at: scriptIdx) 27 | /// // path is "/./home/./username/bin/scripts/./tree" 28 | /// 29 | /// path.components.removeAll { $0.kind == .currentDirectory } 30 | /// // path is "/home/username/bin/scripts/tree" 31 | @available(/*System 0.0.2: macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0*/iOS 8, *) 32 | public struct ComponentView: Sendable { 33 | internal var _path: FilePath 34 | internal var _start: SystemString.Index 35 | 36 | internal init(_ path: FilePath) { 37 | self._path = path 38 | self._start = path._relativeStart 39 | _invariantCheck() 40 | } 41 | } 42 | 43 | /// View the non-root components that make up this path. 44 | public var components: ComponentView { 45 | __consuming get { ComponentView(self) } 46 | _modify { 47 | // RRC's empty init means that we can't guarantee that the yielded 48 | // view will restore our root. So copy it out first. 49 | // 50 | // TODO(perf): Small-form root (especially on Unix). Have Root 51 | // always copy out (not worth ref counting). Make sure that we're 52 | // not needlessly sliding values around or triggering a COW 53 | let rootStr = self.root?._systemString ?? SystemString() 54 | var comp = ComponentView(self) 55 | self = FilePath() 56 | defer { 57 | self = comp._path 58 | if root?._slice.elementsEqual(rootStr) != true { 59 | self.root = Root(rootStr) 60 | } 61 | } 62 | yield &comp 63 | } 64 | } 65 | } 66 | 67 | @available(/*System 0.0.2: macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0*/iOS 8, *) 68 | extension FilePath.ComponentView: BidirectionalCollection { 69 | public typealias Element = FilePath.Component 70 | 71 | @available(/*System 0.0.2: macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0*/iOS 8, *) 72 | public struct Index: Sendable, Comparable, Hashable { 73 | internal typealias Storage = SystemString.Index 74 | 75 | internal var _storage: Storage 76 | 77 | public static func < (lhs: Self, rhs: Self) -> Bool { 78 | lhs._storage < rhs._storage 79 | } 80 | 81 | fileprivate init(_ idx: Storage) { 82 | self._storage = idx 83 | } 84 | } 85 | 86 | public var startIndex: Index { Index(_start) } 87 | public var endIndex: Index { Index(_path._storage.endIndex) } 88 | 89 | public func index(after i: Index) -> Index { 90 | return Index(_path._parseComponent(startingAt: i._storage).nextStart) 91 | } 92 | 93 | public func index(before i: Index) -> Index { 94 | Index(_path._parseComponent(priorTo: i._storage).lowerBound) 95 | } 96 | 97 | public subscript(position: Index) -> FilePath.Component { 98 | let end = _path._parseComponent(startingAt: position._storage).componentEnd 99 | return FilePath.Component(_path, position._storage ..< end) 100 | } 101 | } 102 | 103 | @available(/*System 0.0.2: macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0*/iOS 8, *) 104 | extension FilePath.ComponentView: RangeReplaceableCollection { 105 | public init() { 106 | self.init(FilePath()) 107 | } 108 | 109 | // TODO(perf): We probably want to have concrete overrides or generic 110 | // specializations taking FP.ComponentView and 111 | // FP.ComponentView.SubSequence because we 112 | // can just memcpy in those cases. We 113 | // probably want to do that for all RRC operations. 114 | 115 | public mutating func replaceSubrange( 116 | _ subrange: Range, with newElements: C 117 | ) where C : Collection, Self.Element == C.Element { 118 | defer { 119 | _path._invariantCheck() 120 | _invariantCheck() 121 | } 122 | if isEmpty { 123 | _path = FilePath(root: _path.root, newElements) 124 | return 125 | } 126 | let range = subrange.lowerBound._storage ..< subrange.upperBound._storage 127 | if newElements.isEmpty { 128 | let fromEnd = subrange.upperBound == endIndex 129 | _path._storage.removeSubrange(range) 130 | if fromEnd { 131 | _path._removeTrailingSeparator() 132 | } 133 | return 134 | } 135 | 136 | // TODO(perf): Avoid extra allocation by sliding elements down and 137 | // filling in the bytes ourselves. 138 | 139 | // If we're inserting at the end, we need a leading separator. 140 | var str = SystemString() 141 | let atEnd = subrange.lowerBound == endIndex 142 | if atEnd { 143 | str.append(platformSeparator) 144 | } 145 | str.appendComponents(components: newElements) 146 | if !atEnd { 147 | str.append(platformSeparator) 148 | } 149 | _path._storage.replaceSubrange(range, with: str) 150 | } 151 | } 152 | 153 | @available(/*System 0.0.2: macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0*/iOS 8, *) 154 | extension FilePath { 155 | /// Create a file path from a root and a collection of components. 156 | public init( 157 | root: Root?, _ components: C 158 | ) where C.Element == Component { 159 | var str = root?._systemString ?? SystemString() 160 | str.appendComponents(components: components) 161 | self.init(str) 162 | } 163 | 164 | /// Create a file path from a root and any number of components. 165 | public init(root: Root?, components: Component...) { 166 | self.init(root: root, components) 167 | } 168 | 169 | /// Create a file path from an optional root and a slice of another path's 170 | /// components. 171 | public init(root: Root?, _ components: ComponentView.SubSequence) { 172 | var str = root?._systemString ?? SystemString() 173 | let (start, end) = 174 | (components.startIndex._storage, components.endIndex._storage) 175 | str.append(contentsOf: components.base._slice[start.. { 185 | _start ..< _path._storage.endIndex 186 | } 187 | 188 | internal init(_ str: SystemString) { 189 | fatalError("TODO: consider dropping proto req") 190 | } 191 | } 192 | 193 | // MARK: - Invariants 194 | 195 | @available(/*System 0.0.2: macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0*/iOS 8, *) 196 | extension FilePath.ComponentView { 197 | internal func _invariantCheck() { 198 | #if DEBUG 199 | if isEmpty { 200 | precondition(_path.isEmpty == (_path.root == nil)) 201 | return 202 | } 203 | 204 | // If path has a root, 205 | if _path.root != nil { 206 | precondition(first!._slice.startIndex > _path._storage.startIndex) 207 | precondition(first!._slice.startIndex == _path._relativeStart) 208 | } 209 | 210 | self.forEach { $0._invariantCheck() } 211 | 212 | if let base = last { 213 | precondition(base._slice.endIndex == _path._storage.endIndex) 214 | } 215 | 216 | precondition(FilePath(root: _path.root, self) == _path) 217 | #endif // DEBUG 218 | } 219 | } 220 | -------------------------------------------------------------------------------- /Sources/System/FilePath/FilePathComponents.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift System open source project 3 | 4 | Copyright (c) 2020 - 2024 Apple Inc. and the Swift System project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | */ 9 | 10 | // MARK: - API 11 | 12 | @available(/*System 0.0.2: macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0*/iOS 8, *) 13 | extension FilePath { 14 | /// Represents a root of a file path. 15 | /// 16 | /// On Unix, a root is simply the directory separator `/`. 17 | /// 18 | /// On Windows, a root contains the entire path prefix up to and including 19 | /// the final separator. 20 | /// 21 | /// Examples: 22 | /// * Unix: 23 | /// * `/` 24 | /// * Windows: 25 | /// * `C:\` 26 | /// * `C:` 27 | /// * `\` 28 | /// * `\\server\share\` 29 | /// * `\\?\UNC\server\share\` 30 | /// * `\\?\Volume{12345678-abcd-1111-2222-123445789abc}\` 31 | @available(/*System 0.0.2: macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0*/iOS 8, *) 32 | public struct Root: Sendable { 33 | internal var _path: FilePath 34 | internal var _rootEnd: SystemString.Index 35 | 36 | internal init(_ path: FilePath, rootEnd: SystemString.Index) { 37 | self._path = path 38 | self._rootEnd = rootEnd 39 | _invariantCheck() 40 | } 41 | // TODO: Definitely want a small form for this on Windows, 42 | // and intern "/" for Unix. 43 | } 44 | 45 | /// Represents an individual, non-root component of a file path. 46 | /// 47 | /// Components can be one of the special directory components (`.` or `..`) 48 | /// or a file or directory name. Components are never empty and never 49 | /// contain the directory separator. 50 | /// 51 | /// Example: 52 | /// 53 | /// var path: FilePath = "/tmp" 54 | /// let file: FilePath.Component = "foo.txt" 55 | /// file.kind == .regular // true 56 | /// file.extension // "txt" 57 | /// path.append(file) // path is "/tmp/foo.txt" 58 | @available(/*System 0.0.2: macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0*/iOS 8, *) 59 | public struct Component: Sendable { 60 | internal var _path: FilePath 61 | internal var _range: Range 62 | 63 | // TODO: Make a small-component form to save on ARC overhead when 64 | // extracted from a path, and especially to save on allocation overhead 65 | // when constructing one from a String literal. 66 | 67 | internal init(_ path: FilePath, _ range: RE) 68 | where RE.Bound == SystemString.Index { 69 | self._path = path 70 | self._range = range.relative(to: path._storage) 71 | precondition(!self._range.isEmpty, "FilePath components cannot be empty") 72 | self._invariantCheck() 73 | } 74 | } 75 | } 76 | 77 | @available(/*System 0.0.2: macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0*/iOS 8, *) 78 | extension FilePath.Component { 79 | 80 | /// Whether a component is a regular file or directory name, or a special 81 | /// directory `.` or `..` 82 | @frozen 83 | @available(/*System 0.0.2: macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0*/iOS 8, *) 84 | public enum Kind: Sendable { 85 | /// The special directory `.`, representing the current directory. 86 | case currentDirectory 87 | 88 | /// The special directory `..`, representing the parent directory. 89 | case parentDirectory 90 | 91 | /// A file or directory name 92 | case regular 93 | } 94 | 95 | /// The kind of this component 96 | public var kind: Kind { 97 | if _path._isCurrentDirectory(_range) { return .currentDirectory } 98 | if _path._isParentDirectory(_range) { return .parentDirectory } 99 | return .regular 100 | } 101 | } 102 | 103 | @available(/*System 0.0.2: macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0*/iOS 8, *) 104 | extension FilePath.Root { 105 | // TODO: Windows analysis APIs 106 | } 107 | 108 | // MARK: - Internals 109 | 110 | extension SystemString { 111 | // TODO: take insertLeadingSlash: Bool 112 | // TODO: turn into an insert operation with slide 113 | internal mutating func appendComponents( 114 | components: C 115 | ) where C.Element == FilePath.Component { 116 | // TODO(perf): Consider pre-pass to count capacity, slide 117 | 118 | defer { 119 | _removeTrailingSeparator() 120 | FilePath(self)._invariantCheck() 121 | } 122 | 123 | for idx in components.indices { 124 | let component = components[idx] 125 | component._withSystemChars { self.append(contentsOf: $0) } 126 | self.append(platformSeparator) 127 | } 128 | } 129 | } 130 | 131 | // Unifying protocol for common functionality between roots, components, 132 | // and views onto SystemString and FilePath. 133 | internal protocol _StrSlice: _PlatformStringable, Hashable, Codable { 134 | var _storage: SystemString { get } 135 | var _range: Range { get } 136 | 137 | init?(_ str: SystemString) 138 | 139 | func _invariantCheck() 140 | } 141 | extension _StrSlice { 142 | internal var _slice: Slice { 143 | Slice(base: _storage, bounds: _range) 144 | } 145 | 146 | internal func _withSystemChars( 147 | _ f: (UnsafeBufferPointer) throws -> T 148 | ) rethrows -> T { 149 | try _storage.withNullTerminatedSystemChars { 150 | try f(UnsafeBufferPointer(rebasing: $0[_range])) 151 | } 152 | } 153 | internal func _withCodeUnits( 154 | _ f: (UnsafeBufferPointer) throws -> T 155 | ) rethrows -> T { 156 | try _slice.withCodeUnits(f) 157 | } 158 | 159 | internal init?(_platformString s: UnsafePointer) { 160 | self.init(SystemString(platformString: s)) 161 | } 162 | 163 | internal func _withPlatformString( 164 | _ body: (UnsafePointer) throws -> Result 165 | ) rethrows -> Result { 166 | try _slice.withPlatformString(body) 167 | } 168 | 169 | internal var _systemString: SystemString { SystemString(_slice) } 170 | } 171 | extension _StrSlice { 172 | public static func == (lhs: Self, rhs: Self) -> Bool { 173 | lhs._slice.elementsEqual(rhs._slice) 174 | } 175 | public func hash(into hasher: inout Hasher) { 176 | hasher.combine(_slice.count) // discriminator 177 | for element in _slice { 178 | hasher.combine(element) 179 | } 180 | } 181 | } 182 | internal protocol _PathSlice: _StrSlice { 183 | var _path: FilePath { get } 184 | } 185 | extension _PathSlice { 186 | internal var _storage: SystemString { _path._storage } 187 | } 188 | 189 | @available(/*System 0.0.2: macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0*/iOS 8, *) 190 | extension FilePath.Component: _PathSlice { 191 | } 192 | @available(/*System 0.0.2: macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0*/iOS 8, *) 193 | extension FilePath.Root: _PathSlice { 194 | internal var _range: Range { 195 | (..<_rootEnd).relative(to: _path._storage) 196 | } 197 | } 198 | 199 | @available(/*System 0.0.1: macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0*/iOS 8, *) 200 | extension FilePath: _PlatformStringable { 201 | func _withPlatformString(_ body: (UnsafePointer) throws -> Result) rethrows -> Result { 202 | try _storage.withPlatformString(body) 203 | } 204 | 205 | init(_platformString: UnsafePointer) { 206 | self.init(SystemString(platformString: _platformString)) 207 | } 208 | 209 | } 210 | 211 | @available(/*System 0.0.2: macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0*/iOS 8, *) 212 | extension FilePath.Component { 213 | // The index of the `.` denoting an extension 214 | internal func _extensionIndex() -> SystemString.Index? { 215 | guard kind == .regular, 216 | let idx = _slice.lastIndex(of: .dot), 217 | idx != _slice.startIndex 218 | else { return nil } 219 | 220 | return idx 221 | } 222 | 223 | internal func _extensionRange() -> Range? { 224 | guard let idx = _extensionIndex() else { return nil } 225 | return _slice.index(after: idx) ..< _slice.endIndex 226 | } 227 | 228 | internal func _stemRange() -> Range { 229 | _slice.startIndex ..< (_extensionIndex() ?? _slice.endIndex) 230 | } 231 | } 232 | 233 | internal func _makeExtension(_ ext: String) -> SystemString { 234 | var result = SystemString() 235 | result.append(.dot) 236 | result.append(contentsOf: ext.unicodeScalars.lazy.map(SystemChar.init)) 237 | return result 238 | } 239 | 240 | @available(/*System 0.0.2: macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0*/iOS 8, *) 241 | extension FilePath.Component { 242 | internal init?(_ str: SystemString) { 243 | // FIXME: explicit null root? Or something else? 244 | let path = FilePath(str) 245 | guard path.root == nil, path.components.count == 1 else { 246 | return nil 247 | } 248 | self = path.components.first! 249 | self._invariantCheck() 250 | } 251 | } 252 | 253 | @available(/*System 0.0.2: macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0*/iOS 8, *) 254 | extension FilePath.Root { 255 | internal init?(_ str: SystemString) { 256 | // FIXME: explicit null root? Or something else? 257 | let path = FilePath(str) 258 | guard path.root != nil, path.components.isEmpty else { 259 | return nil 260 | } 261 | self = path.root! 262 | self._invariantCheck() 263 | } 264 | } 265 | 266 | // MARK: - Invariants 267 | 268 | @available(/*System 0.0.2: macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0*/iOS 8, *) 269 | extension FilePath.Component { 270 | // TODO: ensure this all gets easily optimized away in release... 271 | internal func _invariantCheck() { 272 | #if DEBUG 273 | precondition(!_slice.isEmpty) 274 | precondition(_slice.last != .null) 275 | precondition(_slice.allSatisfy { !isSeparator($0) } ) 276 | precondition(_path._relativeStart <= _slice.startIndex) 277 | #endif // DEBUG 278 | } 279 | } 280 | 281 | @available(/*System 0.0.2: macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0*/iOS 8, *) 282 | extension FilePath.Root { 283 | internal func _invariantCheck() { 284 | #if DEBUG 285 | precondition(self._rootEnd > _path._storage.startIndex) 286 | 287 | // TODO: Windows root invariants 288 | #endif 289 | } 290 | } 291 | -------------------------------------------------------------------------------- /Sources/System/FilePath/FilePathTemp.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift System open source project 3 | 4 | Copyright (c) 2024 Apple Inc. and the Swift System project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | */ 9 | 10 | // MARK: - API 11 | 12 | /// Create a temporary path for the duration of the closure. 13 | /// 14 | /// - Parameters: 15 | /// - basename: The base name for the temporary path. 16 | /// - body: The closure to execute. 17 | /// 18 | /// Creates a temporary directory with a name based on the given `basename`, 19 | /// executes `body`, passing in the path of the created directory, then 20 | /// deletes the directory and all of its contents before returning. 21 | internal func withTemporaryFilePath( 22 | basename: FilePath.Component, 23 | _ body: (FilePath) throws -> R 24 | ) throws -> R { 25 | let temporaryDir = try createUniqueTemporaryDirectory(basename: basename) 26 | defer { 27 | try? _recursiveRemove(at: temporaryDir) 28 | } 29 | 30 | return try body(temporaryDir) 31 | } 32 | 33 | // MARK: - Internals 34 | 35 | fileprivate let base64 = Array( 36 | "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_".utf8 37 | ) 38 | 39 | /// Create a directory that is only accessible to the current user. 40 | /// 41 | /// - Parameters: 42 | /// - path: The path of the directory to create. 43 | /// - Returns: `true` if a new directory was created. 44 | /// 45 | /// This function will throw if there is an error, except if the error 46 | /// is that the directory exists, in which case it returns `false`. 47 | fileprivate func makeLockedDownDirectory(at path: FilePath) throws -> Bool { 48 | return try path.withPlatformString { 49 | if system_mkdir($0, 0o700) == 0 { 50 | return true 51 | } 52 | let err = system_errno 53 | if err == Errno.fileExists.rawValue { 54 | return false 55 | } else { 56 | throw Errno(rawValue: err) 57 | } 58 | } 59 | } 60 | 61 | /// Generate a random string of base64 filename safe characters. 62 | /// 63 | /// - Parameters: 64 | /// - length: The number of characters in the returned string. 65 | /// - Returns: A random string of length `length`. 66 | fileprivate func createRandomString(length: Int) -> String { 67 | return String( 68 | decoding: (0.. FilePath { 87 | var tempDir = try _getTemporaryDirectory() 88 | tempDir.append(basename) 89 | 90 | while true { 91 | tempDir.extension = createRandomString(length: 16) 92 | 93 | if try makeLockedDownDirectory(at: tempDir) { 94 | return tempDir 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /Sources/System/FilePath/FilePathTempPosix.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift System open source project 3 | 4 | Copyright (c) 2024 Apple Inc. and the Swift System project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | */ 9 | 10 | #if !os(Windows) 11 | 12 | /// Get the path to the system temporary directory. 13 | internal func _getTemporaryDirectory() throws -> FilePath { 14 | guard let tmp = system_getenv("TMPDIR") else { 15 | return "/tmp" 16 | } 17 | 18 | return FilePath(SystemString(platformString: tmp)) 19 | } 20 | 21 | /// Delete the entire contents of a directory, including its subdirectories. 22 | /// 23 | /// - Parameters: 24 | /// - path: The directory to be deleted. 25 | /// 26 | /// Removes a directory completely, including all of its contents. 27 | internal func _recursiveRemove( 28 | at path: FilePath 29 | ) throws { 30 | let dirfd = try FileDescriptor.open(path, .readOnly, options: .directory) 31 | defer { 32 | try? dirfd.close() 33 | } 34 | 35 | let dot: (CInterop.PlatformChar, CInterop.PlatformChar) = (46, 0) 36 | try withUnsafeBytes(of: dot) { 37 | try recursiveRemove( 38 | in: dirfd.rawValue, 39 | name: $0.assumingMemoryBound(to: CInterop.PlatformChar.self).baseAddress! 40 | ) 41 | } 42 | 43 | try path.withPlatformString { 44 | if system_rmdir($0) != 0 { 45 | throw Errno.current 46 | } 47 | } 48 | } 49 | 50 | /// Open a directory by reference to its parent and name. 51 | /// 52 | /// - Parameters: 53 | /// - dirfd: An open file descriptor for the parent directory. 54 | /// - name: The name of the directory to open. 55 | /// - Returns: A pointer to a `DIR` structure. 56 | /// 57 | /// This is like `opendir()`, but instead of taking a path, it uses a 58 | /// file descriptor pointing at the parent, thus avoiding path length 59 | /// limits. 60 | fileprivate func impl_opendirat( 61 | _ dirfd: CInt, 62 | _ name: UnsafePointer 63 | ) -> system_DIRPtr? { 64 | let fd = system_openat(dirfd, name, 65 | FileDescriptor.AccessMode.readOnly.rawValue 66 | | FileDescriptor.OpenOptions.directory.rawValue) 67 | if fd < 0 { 68 | return nil 69 | } 70 | return system_fdopendir(fd) 71 | } 72 | 73 | /// Invoke a closure for each file within a particular directory. 74 | /// 75 | /// - Parameters: 76 | /// - dirfd: The parent of the directory to be enumerated. 77 | /// - subdir: The subdirectory to be enumerated. 78 | /// - body: The closure that will be invoked. 79 | /// 80 | /// We skip the `.` and `..` pseudo-entries. 81 | fileprivate func forEachFile( 82 | in dirfd: CInt, 83 | subdir: UnsafePointer, 84 | _ body: (system_dirent) throws -> () 85 | ) throws { 86 | guard let dir = impl_opendirat(dirfd, subdir) else { 87 | throw Errno.current 88 | } 89 | defer { 90 | _ = system_closedir(dir) 91 | } 92 | 93 | while let dirent = system_readdir(dir) { 94 | // Skip . and .. 95 | if dirent.pointee.d_name.0 == 46 96 | && (dirent.pointee.d_name.1 == 0 97 | || (dirent.pointee.d_name.1 == 46 98 | && dirent.pointee.d_name.2 == 0)) { 99 | continue 100 | } 101 | 102 | try body(dirent.pointee) 103 | } 104 | } 105 | 106 | /// Delete the entire contents of a directory, including its subdirectories. 107 | /// 108 | /// - Parameters: 109 | /// - dirfd: The parent of the directory to be removed. 110 | /// - name: The name of the directory to be removed. 111 | /// 112 | /// Removes a directory completely, including all of its contents. 113 | fileprivate func recursiveRemove( 114 | in dirfd: CInt, 115 | name: UnsafePointer 116 | ) throws { 117 | // First, deal with subdirectories 118 | try forEachFile(in: dirfd, subdir: name) { dirent in 119 | if dirent.d_type == SYSTEM_DT_DIR { 120 | try withUnsafeBytes(of: dirent.d_name) { 121 | try recursiveRemove( 122 | in: dirfd, 123 | name: $0.assumingMemoryBound(to: CInterop.PlatformChar.self) 124 | .baseAddress! 125 | ) 126 | } 127 | } 128 | } 129 | 130 | // Now delete the contents of this directory 131 | try forEachFile(in: dirfd, subdir: name) { dirent in 132 | let flag: CInt 133 | 134 | if dirent.d_type == SYSTEM_DT_DIR { 135 | flag = SYSTEM_AT_REMOVE_DIR 136 | } else { 137 | flag = 0 138 | } 139 | 140 | let result = withUnsafeBytes(of: dirent.d_name) { 141 | system_unlinkat(dirfd, 142 | $0.assumingMemoryBound(to: CInterop.PlatformChar.self) 143 | .baseAddress!, 144 | flag) 145 | } 146 | 147 | if result != 0 { 148 | throw Errno.current 149 | } 150 | } 151 | } 152 | 153 | #endif // !os(Windows) 154 | -------------------------------------------------------------------------------- /Sources/System/FilePath/FilePathTempWindows.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift System open source project 3 | 4 | Copyright (c) 2024 Apple Inc. and the Swift System project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | */ 9 | 10 | #if os(Windows) 11 | 12 | import WinSDK 13 | 14 | /// Get the path to the system temporary directory. 15 | internal func _getTemporaryDirectory() throws -> FilePath { 16 | return try withUnsafeTemporaryAllocation(of: CInterop.PlatformChar.self, 17 | capacity: Int(MAX_PATH) + 1) { 18 | buffer in 19 | 20 | guard GetTempPathW(DWORD(buffer.count), buffer.baseAddress) != 0 else { 21 | throw Errno(windowsError: GetLastError()) 22 | } 23 | 24 | return FilePath(SystemString(platformString: buffer.baseAddress!)) 25 | } 26 | } 27 | 28 | /// Invoke a closure for each file within a particular directory. 29 | /// 30 | /// - Parameters: 31 | /// - path: The path at which we should enumerate items. 32 | /// - body: The closure that will be invoked. 33 | /// 34 | /// We skip the `.` and `..` pseudo-entries. 35 | fileprivate func forEachFile( 36 | at path: FilePath, 37 | _ body: (WIN32_FIND_DATAW) throws -> () 38 | ) rethrows { 39 | let searchPath = path.appending("\\*") 40 | 41 | try searchPath.withPlatformString { szPath in 42 | var findData = WIN32_FIND_DATAW() 43 | let hFind = try szPath.withCanonicalPathRepresentation({ szPath in FindFirstFileW(szPath, &findData) }) 44 | if hFind == INVALID_HANDLE_VALUE { 45 | throw Errno(windowsError: GetLastError()) 46 | } 47 | defer { 48 | FindClose(hFind) 49 | } 50 | 51 | repeat { 52 | // Skip . and .. 53 | if findData.cFileName.0 == 46 54 | && (findData.cFileName.1 == 0 55 | || (findData.cFileName.1 == 46 56 | && findData.cFileName.2 == 0)) { 57 | continue 58 | } 59 | 60 | try body(findData) 61 | } while FindNextFileW(hFind, &findData) 62 | } 63 | } 64 | 65 | /// Delete the entire contents of a directory, including its subdirectories. 66 | /// 67 | /// - Parameters: 68 | /// - path: The directory to be deleted. 69 | /// 70 | /// Removes a directory completely, including all of its contents. 71 | internal func _recursiveRemove( 72 | at path: FilePath 73 | ) throws { 74 | // First, deal with subdirectories 75 | try forEachFile(at: path) { findData in 76 | if (findData.dwFileAttributes & DWORD(FILE_ATTRIBUTE_DIRECTORY)) != 0 { 77 | let name = withUnsafeBytes(of: findData.cFileName) { 78 | return SystemString(platformString: $0.assumingMemoryBound( 79 | to: CInterop.PlatformChar.self).baseAddress!) 80 | } 81 | let component = FilePath.Component(name)! 82 | let subpath = path.appending(component) 83 | 84 | try _recursiveRemove(at: subpath) 85 | } 86 | } 87 | 88 | // Now delete everything else 89 | try forEachFile(at: path) { findData in 90 | let name = withUnsafeBytes(of: findData.cFileName) { 91 | return SystemString(platformString: $0.assumingMemoryBound( 92 | to: CInterop.PlatformChar.self).baseAddress!) 93 | } 94 | let component = FilePath.Component(name)! 95 | let subpath = path.appending(component) 96 | 97 | if (findData.dwFileAttributes & DWORD(FILE_ATTRIBUTE_DIRECTORY)) == 0 { 98 | try subpath.withPlatformString { subpath in 99 | if try !subpath.withCanonicalPathRepresentation({ DeleteFileW($0) }) { 100 | throw Errno(windowsError: GetLastError()) 101 | } 102 | } 103 | } 104 | } 105 | 106 | // Finally, delete the parent 107 | try path.withPlatformString { 108 | if try !$0.withCanonicalPathRepresentation({ RemoveDirectoryW($0) }) { 109 | throw Errno(windowsError: GetLastError()) 110 | } 111 | } 112 | } 113 | 114 | #endif // os(Windows) 115 | -------------------------------------------------------------------------------- /Sources/System/FilePermissions.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift System open source project 3 | 4 | Copyright (c) 2020 Apple Inc. and the Swift System project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | */ 9 | 10 | /// The access permissions for a file. 11 | /// 12 | /// The following example 13 | /// creates an instance of the `FilePermissions` structure 14 | /// from a raw octal literal and compares it 15 | /// to a file permission created using named options: 16 | /// 17 | /// let perms = FilePermissions(rawValue: 0o644) 18 | /// perms == [.ownerReadWrite, .groupRead, .otherRead] // true 19 | @frozen 20 | @available(/*System 0.0.1: macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0*/iOS 8, *) 21 | public struct FilePermissions: OptionSet, Sendable, Hashable, Codable { 22 | /// The raw C file permissions. 23 | @_alwaysEmitIntoClient 24 | public let rawValue: CModeT 25 | 26 | /// Create a strongly-typed file permission from a raw C value. 27 | @_alwaysEmitIntoClient 28 | public init(rawValue: CModeT) { self.rawValue = rawValue } 29 | 30 | /// Indicates that other users have read-only permission. 31 | @_alwaysEmitIntoClient 32 | public static var otherRead: FilePermissions { .init(rawValue: 0o4) } 33 | 34 | /// Indicates that other users have write-only permission. 35 | @_alwaysEmitIntoClient 36 | public static var otherWrite: FilePermissions { .init(rawValue: 0o2) } 37 | 38 | /// Indicates that other users have execute-only permission. 39 | @_alwaysEmitIntoClient 40 | public static var otherExecute: FilePermissions { .init(rawValue: 0o1) } 41 | 42 | /// Indicates that other users have read-write permission. 43 | @_alwaysEmitIntoClient 44 | public static var otherReadWrite: FilePermissions { .init(rawValue: 0o6) } 45 | 46 | /// Indicates that other users have read-execute permission. 47 | @_alwaysEmitIntoClient 48 | public static var otherReadExecute: FilePermissions { .init(rawValue: 0o5) } 49 | 50 | /// Indicates that other users have write-execute permission. 51 | @_alwaysEmitIntoClient 52 | public static var otherWriteExecute: FilePermissions { .init(rawValue: 0o3) } 53 | 54 | /// Indicates that other users have read, write, and execute permission. 55 | @_alwaysEmitIntoClient 56 | public static var otherReadWriteExecute: FilePermissions { .init(rawValue: 0o7) } 57 | 58 | /// Indicates that the group has read-only permission. 59 | @_alwaysEmitIntoClient 60 | public static var groupRead: FilePermissions { .init(rawValue: 0o40) } 61 | 62 | /// Indicates that the group has write-only permission. 63 | @_alwaysEmitIntoClient 64 | public static var groupWrite: FilePermissions { .init(rawValue: 0o20) } 65 | 66 | /// Indicates that the group has execute-only permission. 67 | @_alwaysEmitIntoClient 68 | public static var groupExecute: FilePermissions { .init(rawValue: 0o10) } 69 | 70 | /// Indicates that the group has read-write permission. 71 | @_alwaysEmitIntoClient 72 | public static var groupReadWrite: FilePermissions { .init(rawValue: 0o60) } 73 | 74 | /// Indicates that the group has read-execute permission. 75 | @_alwaysEmitIntoClient 76 | public static var groupReadExecute: FilePermissions { .init(rawValue: 0o50) } 77 | 78 | /// Indicates that the group has write-execute permission. 79 | @_alwaysEmitIntoClient 80 | public static var groupWriteExecute: FilePermissions { .init(rawValue: 0o30) } 81 | 82 | /// Indicates that the group has read, write, and execute permission. 83 | @_alwaysEmitIntoClient 84 | public static var groupReadWriteExecute: FilePermissions { .init(rawValue: 0o70) } 85 | 86 | /// Indicates that the owner has read-only permission. 87 | @_alwaysEmitIntoClient 88 | public static var ownerRead: FilePermissions { .init(rawValue: 0o400) } 89 | 90 | /// Indicates that the owner has write-only permission. 91 | @_alwaysEmitIntoClient 92 | public static var ownerWrite: FilePermissions { .init(rawValue: 0o200) } 93 | 94 | /// Indicates that the owner has execute-only permission. 95 | @_alwaysEmitIntoClient 96 | public static var ownerExecute: FilePermissions { .init(rawValue: 0o100) } 97 | 98 | /// Indicates that the owner has read-write permission. 99 | @_alwaysEmitIntoClient 100 | public static var ownerReadWrite: FilePermissions { .init(rawValue: 0o600) } 101 | 102 | /// Indicates that the owner has read-execute permission. 103 | @_alwaysEmitIntoClient 104 | public static var ownerReadExecute: FilePermissions { .init(rawValue: 0o500) } 105 | 106 | /// Indicates that the owner has write-execute permission. 107 | @_alwaysEmitIntoClient 108 | public static var ownerWriteExecute: FilePermissions { .init(rawValue: 0o300) } 109 | 110 | /// Indicates that the owner has read, write, and execute permission. 111 | @_alwaysEmitIntoClient 112 | public static var ownerReadWriteExecute: FilePermissions { .init(rawValue: 0o700) } 113 | 114 | /// Indicates that the file is executed as the owner. 115 | /// 116 | /// For more information, see the `setuid(2)` man page. 117 | @_alwaysEmitIntoClient 118 | public static var setUserID: FilePermissions { .init(rawValue: 0o4000) } 119 | 120 | /// Indicates that the file is executed as the group. 121 | /// 122 | /// For more information, see the `setgid(2)` man page. 123 | @_alwaysEmitIntoClient 124 | public static var setGroupID: FilePermissions { .init(rawValue: 0o2000) } 125 | 126 | /// Indicates that executable's text segment 127 | /// should be kept in swap space even after it exits. 128 | /// 129 | /// For more information, see the `chmod(2)` man page's 130 | /// discussion of `S_ISVTX` (the sticky bit). 131 | @_alwaysEmitIntoClient 132 | public static var saveText: FilePermissions { .init(rawValue: 0o1000) } 133 | } 134 | 135 | @available(/*System 0.0.1: macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0*/iOS 8, *) 136 | extension FilePermissions 137 | : CustomStringConvertible, CustomDebugStringConvertible 138 | { 139 | /// A textual representation of the file permissions. 140 | @inline(never) 141 | public var description: String { 142 | let descriptions: [(Element, StaticString)] = [ 143 | (.ownerReadWriteExecute, ".ownerReadWriteExecute"), 144 | (.ownerReadWrite, ".ownerReadWrite"), 145 | (.ownerReadExecute, ".ownerReadExecute"), 146 | (.ownerWriteExecute, ".ownerWriteExecute"), 147 | (.ownerRead, ".ownerRead"), 148 | (.ownerWrite, ".ownerWrite"), 149 | (.ownerExecute, ".ownerExecute"), 150 | (.groupReadWriteExecute, ".groupReadWriteExecute"), 151 | (.groupReadWrite, ".groupReadWrite"), 152 | (.groupReadExecute, ".groupReadExecute"), 153 | (.groupWriteExecute, ".groupWriteExecute"), 154 | (.groupRead, ".groupRead"), 155 | (.groupWrite, ".groupWrite"), 156 | (.groupExecute, ".groupExecute"), 157 | (.otherReadWriteExecute, ".otherReadWriteExecute"), 158 | (.otherReadWrite, ".otherReadWrite"), 159 | (.otherReadExecute, ".otherReadExecute"), 160 | (.otherWriteExecute, ".otherWriteExecute"), 161 | (.otherRead, ".otherRead"), 162 | (.otherWrite, ".otherWrite"), 163 | (.otherExecute, ".otherExecute"), 164 | (.setUserID, ".setUserID"), 165 | (.setGroupID, ".setGroupID"), 166 | (.saveText, ".saveText") 167 | ] 168 | 169 | return _buildDescription(descriptions) 170 | } 171 | 172 | /// A textual representation of the file permissions, suitable for debugging. 173 | public var debugDescription: String { self.description } 174 | } 175 | -------------------------------------------------------------------------------- /Sources/System/Internals/Backcompat.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift System open source project 3 | 4 | Copyright (c) 2021 Apple Inc. and the Swift System project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | */ 9 | 10 | extension String { 11 | internal init( 12 | _unsafeUninitializedCapacity capacity: Int, 13 | initializingUTF8With body: (UnsafeMutableBufferPointer) throws -> Int 14 | ) rethrows { 15 | if #available(macOS 11, iOS 14.0, watchOS 7.0, tvOS 14.0, *) { 16 | self = try String( 17 | unsafeUninitializedCapacity: capacity, 18 | initializingUTF8With: body) 19 | return 20 | } 21 | 22 | let array = try Array( 23 | unsafeUninitializedCapacity: capacity 24 | ) { buffer, count in 25 | count = try body(buffer) 26 | } 27 | self = String(decoding: array, as: UTF8.self) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Sources/System/Internals/CInterop.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift System open source project 3 | 4 | Copyright (c) 2020 - 2024 Apple Inc. and the Swift System project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | */ 9 | 10 | #if SYSTEM_PACKAGE_DARWIN 11 | import Darwin 12 | #elseif os(Windows) 13 | import CSystem 14 | import ucrt 15 | #elseif canImport(Glibc) 16 | @_implementationOnly import CSystem 17 | import Glibc 18 | #elseif canImport(Musl) 19 | @_implementationOnly import CSystem 20 | import Musl 21 | #elseif canImport(WASILibc) 22 | import WASILibc 23 | #elseif canImport(Bionic) 24 | @_implementationOnly import CSystem 25 | import Bionic 26 | #else 27 | #error("Unsupported Platform") 28 | #endif 29 | 30 | // MARK: - Public typealiases 31 | 32 | // FIXME: `CModeT` ought to be deprecated and replaced with `CInterop.Mode` 33 | // if/when the compiler becomes less strict about availability checking 34 | // of "namespaced" typealiases. (rdar://81722893) 35 | #if os(Windows) 36 | /// The C `mode_t` type. 37 | public typealias CModeT = CInt 38 | #else 39 | /// The C `mode_t` type. 40 | @available(/*System 0.0.1: macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0*/iOS 8, *) 41 | public typealias CModeT = mode_t 42 | #endif 43 | 44 | /// A namespace for C and platform types 45 | @available(/*System 0.0.2: macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0*/iOS 8, *) 46 | public enum CInterop { 47 | #if os(Windows) 48 | public typealias Mode = CInt 49 | #else 50 | public typealias Mode = mode_t 51 | #endif 52 | 53 | /// The C `char` type 54 | public typealias Char = CChar 55 | 56 | #if os(Windows) 57 | /// The platform's preferred character type. On Unix, this is an 8-bit C 58 | /// `char` (which may be signed or unsigned, depending on platform). On 59 | /// Windows, this is `UInt16` (a "wide" character). 60 | public typealias PlatformChar = UInt16 61 | #else 62 | /// The platform's preferred character type. On Unix, this is an 8-bit C 63 | /// `char` (which may be signed or unsigned, depending on platform). On 64 | /// Windows, this is `UInt16` (a "wide" character). 65 | public typealias PlatformChar = CInterop.Char 66 | #endif 67 | 68 | #if os(Windows) 69 | /// The platform's preferred Unicode encoding. On Unix this is UTF-8 and on 70 | /// Windows it is UTF-16. Native strings may contain invalid Unicode, 71 | /// which will be handled by either error-correction or failing, depending 72 | /// on API. 73 | public typealias PlatformUnicodeEncoding = UTF16 74 | #else 75 | /// The platform's preferred Unicode encoding. On Unix this is UTF-8 and on 76 | /// Windows it is UTF-16. Native strings may contain invalid Unicode, 77 | /// which will be handled by either error-correction or failing, depending 78 | /// on API. 79 | public typealias PlatformUnicodeEncoding = UTF8 80 | #endif 81 | } 82 | -------------------------------------------------------------------------------- /Sources/System/Internals/Exports.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift System open source project 3 | 4 | Copyright (c) 2020 - 2024 Apple Inc. and the Swift System project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | */ 9 | 10 | // Internal wrappers and typedefs which help reduce #if littering in System's 11 | // code base. 12 | 13 | // TODO: Should CSystem just include all the header files we need? 14 | 15 | #if SYSTEM_PACKAGE_DARWIN 16 | import Darwin 17 | #elseif os(Windows) 18 | import CSystem 19 | import ucrt 20 | #elseif canImport(Glibc) 21 | @_implementationOnly import CSystem 22 | import Glibc 23 | #elseif canImport(Musl) 24 | @_implementationOnly import CSystem 25 | import Musl 26 | #elseif canImport(WASILibc) 27 | import WASILibc 28 | #elseif canImport(Android) 29 | @_implementationOnly import CSystem 30 | import Android 31 | #else 32 | #error("Unsupported Platform") 33 | #endif 34 | 35 | internal typealias _COffT = off_t 36 | 37 | // MARK: syscalls and variables 38 | 39 | #if SYSTEM_PACKAGE_DARWIN 40 | internal var system_errno: CInt { 41 | get { Darwin.errno } 42 | set { Darwin.errno = newValue } 43 | } 44 | #elseif os(Windows) 45 | internal var system_errno: CInt { 46 | get { 47 | var value: CInt = 0 48 | // TODO(compnerd) handle the error? 49 | _ = ucrt._get_errno(&value) 50 | return value 51 | } 52 | set { 53 | _ = ucrt._set_errno(newValue) 54 | } 55 | } 56 | #elseif canImport(Glibc) 57 | internal var system_errno: CInt { 58 | get { Glibc.errno } 59 | set { Glibc.errno = newValue } 60 | } 61 | #elseif canImport(Musl) 62 | internal var system_errno: CInt { 63 | get { Musl.errno } 64 | set { Musl.errno = newValue } 65 | } 66 | #elseif canImport(WASILibc) 67 | internal var system_errno: CInt { 68 | get { WASILibc.errno } 69 | set { WASILibc.errno = newValue } 70 | } 71 | #elseif canImport(Android) 72 | internal var system_errno: CInt { 73 | get { Android.errno } 74 | set { Android.errno = newValue } 75 | } 76 | #endif 77 | 78 | // MARK: C stdlib decls 79 | 80 | // Convention: `system_foo` is system's wrapper for `foo`. 81 | 82 | internal func system_strerror(_ __errnum: Int32) -> UnsafeMutablePointer! { 83 | strerror(__errnum) 84 | } 85 | 86 | internal func system_strlen(_ s: UnsafePointer) -> Int { 87 | strlen(s) 88 | } 89 | internal func system_strlen(_ s: UnsafeMutablePointer) -> Int { 90 | strlen(s) 91 | } 92 | 93 | // Convention: `system_platform_foo` is a 94 | // platform-representation-abstracted wrapper around `foo`-like functionality. 95 | // Type and layout differences such as the `char` vs `wchar` are abstracted. 96 | // 97 | 98 | // strlen for the platform string 99 | internal func system_platform_strlen(_ s: UnsafePointer) -> Int { 100 | #if os(Windows) 101 | return wcslen(s) 102 | #else 103 | return strlen(s) 104 | #endif 105 | } 106 | 107 | // memset for raw buffers 108 | // FIXME: Do we really not have something like this in the stdlib already? 109 | internal func system_memset( 110 | _ buffer: UnsafeMutableRawBufferPointer, 111 | to byte: UInt8 112 | ) { 113 | guard buffer.count > 0 else { return } 114 | memset(buffer.baseAddress!, CInt(byte), buffer.count) 115 | } 116 | 117 | // Interop between String and platfrom string 118 | extension String { 119 | internal func _withPlatformString( 120 | _ body: (UnsafePointer) throws -> Result 121 | ) rethrows -> Result { 122 | // Need to #if because CChar may be signed 123 | #if os(Windows) 124 | return try withCString(encodedAs: CInterop.PlatformUnicodeEncoding.self, body) 125 | #else 126 | return try withCString(body) 127 | #endif 128 | } 129 | 130 | internal init?(_platformString platformString: UnsafePointer) { 131 | // Need to #if because CChar may be signed 132 | #if os(Windows) 133 | guard let strRes = String.decodeCString( 134 | platformString, 135 | as: CInterop.PlatformUnicodeEncoding.self, 136 | repairingInvalidCodeUnits: false 137 | ) else { return nil } 138 | assert(strRes.repairsMade == false) 139 | self = strRes.result 140 | return 141 | 142 | #else 143 | self.init(validatingUTF8: platformString) 144 | #endif 145 | } 146 | 147 | internal init( 148 | _errorCorrectingPlatformString platformString: UnsafePointer 149 | ) { 150 | // Need to #if because CChar may be signed 151 | #if os(Windows) 152 | let strRes = String.decodeCString( 153 | platformString, 154 | as: CInterop.PlatformUnicodeEncoding.self, 155 | repairingInvalidCodeUnits: true) 156 | self = strRes!.result 157 | return 158 | #else 159 | self.init(cString: platformString) 160 | #endif 161 | } 162 | } 163 | 164 | // TLS 165 | #if os(Windows) 166 | internal typealias _PlatformTLSKey = DWORD 167 | #elseif os(WASI) && (swift(<6.1) || !_runtime(_multithreaded)) 168 | // Mock TLS storage for single-threaded WASI 169 | internal final class _PlatformTLSKey { 170 | fileprivate init() {} 171 | } 172 | private final class TLSStorage: @unchecked Sendable { 173 | var storage = [ObjectIdentifier: UnsafeMutableRawPointer]() 174 | } 175 | private let sharedTLSStorage = TLSStorage() 176 | 177 | func pthread_setspecific(_ key: _PlatformTLSKey, _ p: UnsafeMutableRawPointer?) -> Int { 178 | sharedTLSStorage.storage[ObjectIdentifier(key)] = p 179 | return 0 180 | } 181 | 182 | func pthread_getspecific(_ key: _PlatformTLSKey) -> UnsafeMutableRawPointer? { 183 | sharedTLSStorage.storage[ObjectIdentifier(key)] 184 | } 185 | #else 186 | internal typealias _PlatformTLSKey = pthread_key_t 187 | #endif 188 | 189 | internal func makeTLSKey() -> _PlatformTLSKey { 190 | #if os(Windows) 191 | let raw: DWORD = FlsAlloc(nil) 192 | if raw == FLS_OUT_OF_INDEXES { 193 | fatalError("Unable to create key") 194 | } 195 | return raw 196 | #elseif os(WASI) && (swift(<6.1) || !_runtime(_multithreaded)) 197 | return _PlatformTLSKey() 198 | #else 199 | var raw = pthread_key_t() 200 | guard 0 == pthread_key_create(&raw, nil) else { 201 | fatalError("Unable to create key") 202 | } 203 | return raw 204 | #endif 205 | } 206 | internal func setTLS(_ key: _PlatformTLSKey, _ p: UnsafeMutableRawPointer?) { 207 | #if os(Windows) 208 | guard FlsSetValue(key, p) else { 209 | fatalError("Unable to set TLS") 210 | } 211 | #else 212 | guard 0 == pthread_setspecific(key, p) else { 213 | fatalError("Unable to set TLS") 214 | } 215 | #endif 216 | } 217 | internal func getTLS(_ key: _PlatformTLSKey) -> UnsafeMutableRawPointer? { 218 | #if os(Windows) 219 | return FlsGetValue(key) 220 | #else 221 | return pthread_getspecific(key) 222 | #endif 223 | } 224 | -------------------------------------------------------------------------------- /Sources/System/Internals/Mocking.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift System open source project 3 | 4 | Copyright (c) 2020 Apple Inc. and the Swift System project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | */ 9 | 10 | // Syscall mocking support. 11 | // 12 | // NOTE: This is currently the bare minimum needed for System's testing purposes, though we do 13 | // eventually want to expose some solution to users. 14 | // 15 | // Mocking is contextual, accessible through MockingDriver.withMockingEnabled. Mocking 16 | // state, including whether it is enabled, is stored in thread-local storage. Mocking is only 17 | // enabled in testing builds of System currently, to minimize runtime overhead of release builds. 18 | // 19 | 20 | #if ENABLE_MOCKING 21 | internal struct Trace { 22 | internal struct Entry { 23 | 24 | internal var name: String 25 | internal var arguments: [AnyHashable] 26 | 27 | internal init(name: String, _ arguments: [AnyHashable]) { 28 | self.name = name 29 | self.arguments = arguments 30 | } 31 | } 32 | 33 | private var entries: [Entry] = [] 34 | private var firstEntry: Int = 0 35 | 36 | internal var isEmpty: Bool { firstEntry >= entries.count } 37 | 38 | internal mutating func dequeue() -> Entry? { 39 | guard !self.isEmpty else { return nil } 40 | defer { firstEntry += 1 } 41 | return entries[firstEntry] 42 | } 43 | 44 | fileprivate mutating func add(_ e: Entry) { 45 | entries.append(e) 46 | } 47 | } 48 | 49 | internal enum ForceErrno: Equatable { 50 | case none 51 | case always(errno: CInt) 52 | 53 | case counted(errno: CInt, count: Int) 54 | } 55 | 56 | // Provide access to the driver, context, and trace stack of mocking 57 | internal class MockingDriver { 58 | // Record syscalls and their arguments 59 | internal var trace = Trace() 60 | 61 | // Mock errors inside syscalls 62 | internal var forceErrno = ForceErrno.none 63 | 64 | // Whether we should pretend to be Windows for syntactic operations 65 | // inside FilePath 66 | fileprivate var forceWindowsSyntaxForPaths: Bool? = nil 67 | } 68 | 69 | private let driverKey: _PlatformTLSKey = { makeTLSKey() }() 70 | 71 | internal var currentMockingDriver: MockingDriver? { 72 | #if !ENABLE_MOCKING 73 | fatalError("Contextual mocking in non-mocking build") 74 | #endif 75 | 76 | guard let rawPtr = getTLS(driverKey) else { return nil } 77 | 78 | return Unmanaged.fromOpaque(rawPtr).takeUnretainedValue() 79 | } 80 | 81 | extension MockingDriver { 82 | /// Enables mocking for the duration of `f` with a clean trace queue 83 | /// Restores prior mocking status and trace queue after execution 84 | internal static func withMockingEnabled( 85 | _ f: (MockingDriver) throws -> () 86 | ) rethrows { 87 | let priorMocking = currentMockingDriver 88 | let driver = MockingDriver() 89 | 90 | defer { 91 | if let object = priorMocking { 92 | setTLS(driverKey, Unmanaged.passUnretained(object).toOpaque()) 93 | } else { 94 | setTLS(driverKey, nil) 95 | } 96 | _fixLifetime(driver) 97 | } 98 | 99 | setTLS(driverKey, Unmanaged.passUnretained(driver).toOpaque()) 100 | return try f(driver) 101 | } 102 | } 103 | 104 | // Check TLS for mocking 105 | @inline(never) 106 | private var contextualMockingEnabled: Bool { 107 | return currentMockingDriver != nil 108 | } 109 | 110 | extension MockingDriver { 111 | internal static var enabled: Bool { mockingEnabled } 112 | 113 | internal static var forceWindowsPaths: Bool? { 114 | currentMockingDriver?.forceWindowsSyntaxForPaths 115 | } 116 | } 117 | 118 | #endif // ENABLE_MOCKING 119 | 120 | @inline(__always) 121 | internal var mockingEnabled: Bool { 122 | // Fast constant-foldable check for release builds 123 | #if ENABLE_MOCKING 124 | return contextualMockingEnabled 125 | #else 126 | return false 127 | #endif 128 | } 129 | 130 | @inline(__always) 131 | internal var forceWindowsPaths: Bool? { 132 | #if !ENABLE_MOCKING 133 | return nil 134 | #else 135 | return MockingDriver.forceWindowsPaths 136 | #endif 137 | } 138 | 139 | 140 | #if ENABLE_MOCKING 141 | // Strip the mock_system prefix and the arg list suffix 142 | private func originalSyscallName(_ function: String) -> String { 143 | // `function` must be of format `system_()` 144 | precondition(function.starts(with: "system_")) 145 | return String(function.dropFirst("system_".count).prefix { $0 != "(" }) 146 | } 147 | 148 | private func mockImpl( 149 | name: String, 150 | path: UnsafePointer?, 151 | _ args: [AnyHashable] 152 | ) -> CInt { 153 | precondition(mockingEnabled) 154 | let origName = originalSyscallName(name) 155 | guard let driver = currentMockingDriver else { 156 | fatalError("Mocking requested from non-mocking context") 157 | } 158 | var mockArgs: Array = [] 159 | if let p = path { 160 | mockArgs.append(String(_errorCorrectingPlatformString: p)) 161 | } 162 | mockArgs.append(contentsOf: args) 163 | driver.trace.add(Trace.Entry(name: origName, mockArgs)) 164 | 165 | switch driver.forceErrno { 166 | case .none: break 167 | case .always(let e): 168 | system_errno = e 169 | return -1 170 | case .counted(let e, let count): 171 | assert(count >= 1) 172 | system_errno = e 173 | driver.forceErrno = count > 1 ? .counted(errno: e, count: count-1) : .none 174 | return -1 175 | } 176 | 177 | return 0 178 | } 179 | 180 | internal func _mock( 181 | name: String = #function, path: UnsafePointer? = nil, _ args: AnyHashable... 182 | ) -> CInt { 183 | return mockImpl(name: name, path: path, args) 184 | } 185 | internal func _mockInt( 186 | name: String = #function, path: UnsafePointer? = nil, _ args: AnyHashable... 187 | ) -> Int { 188 | Int(mockImpl(name: name, path: path, args)) 189 | } 190 | 191 | internal func _mockOffT( 192 | name: String = #function, path: UnsafePointer? = nil, _ args: AnyHashable... 193 | ) -> _COffT { 194 | _COffT(mockImpl(name: name, path: path, args)) 195 | } 196 | #endif // ENABLE_MOCKING 197 | 198 | // Force paths to be treated as Windows syntactically if `enabled` is 199 | // true, and as POSIX syntactically if not. 200 | internal func _withWindowsPaths(enabled: Bool, _ body: () -> ()) { 201 | #if ENABLE_MOCKING 202 | MockingDriver.withMockingEnabled { driver in 203 | driver.forceWindowsSyntaxForPaths = enabled 204 | body() 205 | } 206 | #else 207 | body() 208 | #endif 209 | } 210 | -------------------------------------------------------------------------------- /Sources/System/Internals/RawBuffer.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift System open source project 3 | 4 | Copyright (c) 2021 Apple Inc. and the Swift System project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | */ 9 | 10 | // A copy-on-write fixed-size buffer of raw memory. 11 | internal struct _RawBuffer { 12 | internal var _storage: Storage? 13 | 14 | internal init() { 15 | self._storage = nil 16 | } 17 | 18 | internal init(minimumCapacity: Int) { 19 | if minimumCapacity > 0 { 20 | self._storage = Storage.create(minimumCapacity: minimumCapacity) 21 | } else { 22 | self._storage = nil 23 | } 24 | } 25 | } 26 | 27 | extension _RawBuffer { 28 | internal var capacity: Int { 29 | _storage?.header ?? 0 // Note: not capacity! 30 | } 31 | 32 | internal mutating func ensureUnique() { 33 | guard _storage != nil else { return } 34 | let unique = isKnownUniquelyReferenced(&_storage) 35 | if !unique { 36 | _storage = _copy(capacity: capacity) 37 | } 38 | } 39 | 40 | internal func _grow(desired: Int) -> Int { 41 | let next = Int(1.75 * Double(self.capacity)) 42 | return Swift.max(next, desired) 43 | } 44 | 45 | internal mutating func ensureUnique(capacity: Int) { 46 | let unique = isKnownUniquelyReferenced(&_storage) 47 | if !unique || self.capacity < capacity { 48 | _storage = _copy(capacity: _grow(desired: capacity)) 49 | } 50 | } 51 | 52 | internal func withUnsafeBytes( 53 | _ body: (UnsafeRawBufferPointer) throws -> R 54 | ) rethrows -> R { 55 | guard let storage = _storage else { 56 | return try body(UnsafeRawBufferPointer(start: nil, count: 0)) 57 | } 58 | return try storage.withUnsafeMutablePointers { count, bytes in 59 | let buffer = UnsafeRawBufferPointer(start: bytes, count: count.pointee) 60 | return try body(buffer) 61 | } 62 | } 63 | 64 | internal mutating func withUnsafeMutableBytes( 65 | _ body: (UnsafeMutableRawBufferPointer) throws -> R 66 | ) rethrows -> R { 67 | guard _storage != nil else { 68 | return try body(UnsafeMutableRawBufferPointer(start: nil, count: 0)) 69 | } 70 | ensureUnique() 71 | return try _storage!.withUnsafeMutablePointers { count, bytes in 72 | let buffer = UnsafeMutableRawBufferPointer(start: bytes, count: count.pointee) 73 | return try body(buffer) 74 | } 75 | } 76 | } 77 | 78 | extension _RawBuffer { 79 | internal class Storage: ManagedBuffer { 80 | internal static func create(minimumCapacity: Int) -> Storage { 81 | Storage.create( 82 | minimumCapacity: minimumCapacity, 83 | makingHeaderWith: { 84 | #if os(OpenBSD) 85 | minimumCapacity 86 | #else 87 | $0.capacity 88 | #endif 89 | } 90 | ) as! Storage 91 | } 92 | } 93 | 94 | internal func _copy(capacity: Int) -> Storage { 95 | let copy = Storage.create(minimumCapacity: capacity) 96 | copy.withUnsafeMutablePointers { dstlen, dst in 97 | self.withUnsafeBytes { src in 98 | guard src.count > 0 else { return } 99 | assert(src.count <= dstlen.pointee) 100 | UnsafeMutableRawPointer(dst) 101 | .copyMemory( 102 | from: src.baseAddress!, 103 | byteCount: Swift.min(src.count, dstlen.pointee)) 104 | } 105 | } 106 | return copy 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /Sources/System/Internals/Syscalls.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift System open source project 3 | 4 | Copyright (c) 2020 - 2024 Apple Inc. and the Swift System project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | */ 9 | 10 | #if SYSTEM_PACKAGE_DARWIN 11 | import Darwin 12 | #elseif canImport(Glibc) 13 | import Glibc 14 | #elseif canImport(Musl) 15 | import Musl 16 | #elseif canImport(WASILibc) 17 | import WASILibc 18 | #elseif os(Windows) 19 | import ucrt 20 | #elseif canImport(Android) 21 | import Android 22 | #else 23 | #error("Unsupported Platform") 24 | #endif 25 | 26 | // Interacting with the mocking system, tracing, etc., is a potentially significant 27 | // amount of code size, so we hand outline that code for every syscall 28 | 29 | // open 30 | internal func system_open( 31 | _ path: UnsafePointer, _ oflag: Int32 32 | ) -> CInt { 33 | #if ENABLE_MOCKING 34 | if mockingEnabled { 35 | return _mock(path: path, oflag) 36 | } 37 | #endif 38 | return open(path, oflag) 39 | } 40 | 41 | internal func system_open( 42 | _ path: UnsafePointer, 43 | _ oflag: Int32, _ mode: CInterop.Mode 44 | ) -> CInt { 45 | #if ENABLE_MOCKING 46 | if mockingEnabled { 47 | return _mock(path: path, oflag, mode) 48 | } 49 | #endif 50 | return open(path, oflag, mode) 51 | } 52 | 53 | // close 54 | internal func system_close(_ fd: Int32) -> Int32 { 55 | #if ENABLE_MOCKING 56 | if mockingEnabled { return _mock(fd) } 57 | #endif 58 | return close(fd) 59 | } 60 | 61 | // read 62 | internal func system_read( 63 | _ fd: Int32, _ buf: UnsafeMutableRawPointer?, _ nbyte: Int 64 | ) -> Int { 65 | #if ENABLE_MOCKING 66 | if mockingEnabled { return _mockInt(fd, buf, nbyte) } 67 | #endif 68 | return read(fd, buf, nbyte) 69 | } 70 | 71 | // pread 72 | internal func system_pread( 73 | _ fd: Int32, _ buf: UnsafeMutableRawPointer?, _ nbyte: Int, _ offset: off_t 74 | ) -> Int { 75 | #if ENABLE_MOCKING 76 | if mockingEnabled { return _mockInt(fd, buf, nbyte, offset) } 77 | #endif 78 | #if os(Android) 79 | var zero = UInt8.zero 80 | return withUnsafeMutablePointer(to: &zero) { 81 | // this pread has a non-nullable `buf` pointer 82 | pread(fd, buf ?? UnsafeMutableRawPointer($0), nbyte, offset) 83 | } 84 | #else 85 | return pread(fd, buf, nbyte, offset) 86 | #endif 87 | } 88 | 89 | // lseek 90 | internal func system_lseek( 91 | _ fd: Int32, _ off: off_t, _ whence: Int32 92 | ) -> off_t { 93 | #if ENABLE_MOCKING 94 | if mockingEnabled { return _mockOffT(fd, off, whence) } 95 | #endif 96 | return lseek(fd, off, whence) 97 | } 98 | 99 | // write 100 | internal func system_write( 101 | _ fd: Int32, _ buf: UnsafeRawPointer?, _ nbyte: Int 102 | ) -> Int { 103 | #if ENABLE_MOCKING 104 | if mockingEnabled { return _mockInt(fd, buf, nbyte) } 105 | #endif 106 | return write(fd, buf, nbyte) 107 | } 108 | 109 | // pwrite 110 | internal func system_pwrite( 111 | _ fd: Int32, _ buf: UnsafeRawPointer?, _ nbyte: Int, _ offset: off_t 112 | ) -> Int { 113 | #if ENABLE_MOCKING 114 | if mockingEnabled { return _mockInt(fd, buf, nbyte, offset) } 115 | #endif 116 | #if os(Android) 117 | var zero = UInt8.zero 118 | return withUnsafeMutablePointer(to: &zero) { 119 | // this pwrite has a non-nullable `buf` pointer 120 | pwrite(fd, buf ?? UnsafeRawPointer($0), nbyte, offset) 121 | } 122 | #else 123 | return pwrite(fd, buf, nbyte, offset) 124 | #endif 125 | } 126 | 127 | #if !os(WASI) 128 | internal func system_dup(_ fd: Int32) -> Int32 { 129 | #if ENABLE_MOCKING 130 | if mockingEnabled { return _mock(fd) } 131 | #endif 132 | return dup(fd) 133 | } 134 | 135 | internal func system_dup2(_ fd: Int32, _ fd2: Int32) -> Int32 { 136 | #if ENABLE_MOCKING 137 | if mockingEnabled { return _mock(fd, fd2) } 138 | #endif 139 | return dup2(fd, fd2) 140 | } 141 | #endif 142 | 143 | #if !os(WASI) 144 | internal func system_pipe(_ fds: UnsafeMutablePointer) -> CInt { 145 | #if ENABLE_MOCKING 146 | if mockingEnabled { return _mock(fds) } 147 | #endif 148 | return pipe(fds) 149 | } 150 | #endif 151 | 152 | internal func system_ftruncate(_ fd: Int32, _ length: off_t) -> Int32 { 153 | #if ENABLE_MOCKING 154 | if mockingEnabled { return _mock(fd, length) } 155 | #endif 156 | return ftruncate(fd, length) 157 | } 158 | 159 | internal func system_mkdir( 160 | _ path: UnsafePointer, 161 | _ mode: CInterop.Mode 162 | ) -> CInt { 163 | #if ENABLE_MOCKING 164 | if mockingEnabled { return _mock(path: path, mode) } 165 | #endif 166 | return mkdir(path, mode) 167 | } 168 | 169 | internal func system_rmdir( 170 | _ path: UnsafePointer 171 | ) -> CInt { 172 | #if ENABLE_MOCKING 173 | if mockingEnabled { return _mock(path: path) } 174 | #endif 175 | return rmdir(path) 176 | } 177 | 178 | #if SYSTEM_PACKAGE_DARWIN 179 | internal let SYSTEM_CS_DARWIN_USER_TEMP_DIR = _CS_DARWIN_USER_TEMP_DIR 180 | 181 | internal func system_confstr( 182 | _ name: CInt, 183 | _ buf: UnsafeMutablePointer, 184 | _ len: Int 185 | ) -> Int { 186 | return confstr(name, buf, len) 187 | } 188 | #endif 189 | 190 | #if !os(Windows) 191 | internal let SYSTEM_AT_REMOVE_DIR = AT_REMOVEDIR 192 | internal let SYSTEM_DT_DIR = DT_DIR 193 | internal typealias system_dirent = dirent 194 | #if os(Linux) || os(Android) || os(FreeBSD) || os(OpenBSD) 195 | internal typealias system_DIRPtr = OpaquePointer 196 | #else 197 | internal typealias system_DIRPtr = UnsafeMutablePointer 198 | #endif 199 | 200 | internal func system_unlinkat( 201 | _ fd: CInt, 202 | _ path: UnsafePointer, 203 | _ flag: CInt 204 | ) -> CInt { 205 | #if ENABLE_MOCKING 206 | if mockingEnabled { return _mock(fd, path, flag) } 207 | #endif 208 | return unlinkat(fd, path, flag) 209 | } 210 | 211 | internal func system_fdopendir( 212 | _ fd: CInt 213 | ) -> system_DIRPtr? { 214 | return fdopendir(fd) 215 | } 216 | 217 | internal func system_readdir( 218 | _ dir: system_DIRPtr 219 | ) -> UnsafeMutablePointer? { 220 | return readdir(dir) 221 | } 222 | 223 | internal func system_rewinddir( 224 | _ dir: system_DIRPtr 225 | ) { 226 | return rewinddir(dir) 227 | } 228 | 229 | internal func system_closedir( 230 | _ dir: system_DIRPtr 231 | ) -> CInt { 232 | return closedir(dir) 233 | } 234 | 235 | internal func system_openat( 236 | _ fd: CInt, 237 | _ path: UnsafePointer, 238 | _ oflag: Int32 239 | ) -> CInt { 240 | #if ENABLE_MOCKING 241 | if mockingEnabled { 242 | return _mock(fd, path, oflag) 243 | } 244 | #endif 245 | return openat(fd, path, oflag) 246 | } 247 | #endif 248 | 249 | internal func system_umask( 250 | _ mode: CInterop.Mode 251 | ) -> CInterop.Mode { 252 | return umask(mode) 253 | } 254 | 255 | internal func system_getenv( 256 | _ name: UnsafePointer 257 | ) -> UnsafeMutablePointer? { 258 | return getenv(name) 259 | } 260 | -------------------------------------------------------------------------------- /Sources/System/PlatformString.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift System open source project 3 | 4 | Copyright (c) 2020 Apple Inc. and the Swift System project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | */ 9 | 10 | @available(/*System 0.0.2: macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0*/iOS 8, *) 11 | extension String { 12 | /// Creates a string by interpreting the null-terminated platform string as 13 | /// UTF-8 on Unix and UTF-16 on Windows. 14 | /// 15 | /// - Parameter platformString: The null-terminated platform string to be 16 | /// interpreted as `CInterop.PlatformUnicodeEncoding`. 17 | /// 18 | /// If the content of the platform string isn't well-formed Unicode, 19 | /// this initializer replaces invalid bytes with U+FFFD. 20 | /// This means that, depending on the semantics of the specific platform, 21 | /// conversion to a string and back might result in a value that's different 22 | /// from the original platform string. 23 | @_disfavoredOverload 24 | public init(platformString: UnsafePointer) { 25 | self.init(_errorCorrectingPlatformString: platformString) 26 | } 27 | 28 | /// Creates a string by interpreting the null-terminated platform string as 29 | /// UTF-8 on Unix and UTF-16 on Windows. 30 | /// 31 | /// - Parameter platformString: The null-terminated platform string to be 32 | /// interpreted as `CInterop.PlatformUnicodeEncoding`. 33 | /// 34 | /// - Note It is a precondition that `platformString` must be null-terminated. 35 | /// The absence of a null byte will trigger a runtime error. 36 | /// 37 | /// If the content of the platform string isn't well-formed Unicode, 38 | /// this initializer replaces invalid bytes with U+FFFD. 39 | /// This means that, depending on the semantics of the specific platform, 40 | /// conversion to a string and back might result in a value that's different 41 | /// from the original platform string. 42 | @inlinable 43 | @_alwaysEmitIntoClient 44 | public init(platformString: [CInterop.PlatformChar]) { 45 | guard let _ = platformString.firstIndex(of: 0) else { 46 | fatalError( 47 | "input of String.init(platformString:) must be null-terminated" 48 | ) 49 | } 50 | self = platformString.withUnsafeBufferPointer { 51 | String(platformString: $0.baseAddress!) 52 | } 53 | } 54 | 55 | @inlinable 56 | @_alwaysEmitIntoClient 57 | @available(*, deprecated, message: "Use String.init(_ scalar: Unicode.Scalar)") 58 | public init(platformString: inout CInterop.PlatformChar) { 59 | guard platformString == 0 else { 60 | fatalError( 61 | "input of String.init(platformString:) must be null-terminated" 62 | ) 63 | } 64 | self = "" 65 | } 66 | 67 | @inlinable 68 | @_alwaysEmitIntoClient 69 | @available(*, deprecated, message: "Use a copy of the String argument") 70 | public init(platformString: String) { 71 | if let nullLoc = platformString.firstIndex(of: "\0") { 72 | self = String(platformString[.. 88 | ) { 89 | self.init(_platformString: platformString) 90 | } 91 | 92 | /// Creates a string by interpreting the null-terminated platform string as 93 | /// UTF-8 on Unix and UTF-16 on Windows. 94 | /// 95 | /// - Parameter platformString: The null-terminated platform string to be 96 | /// interpreted as `CInterop.PlatformUnicodeEncoding`. 97 | /// 98 | /// - Note It is a precondition that `platformString` must be null-terminated. 99 | /// The absence of a null byte will trigger a runtime error. 100 | /// 101 | /// If the contents of the platform string isn't well-formed Unicode, 102 | /// this initializer returns `nil`. 103 | @inlinable 104 | @_alwaysEmitIntoClient 105 | public init?( 106 | validatingPlatformString platformString: [CInterop.PlatformChar] 107 | ) { 108 | guard let _ = platformString.firstIndex(of: 0) else { 109 | fatalError( 110 | "input of String.init(validatingPlatformString:) must be null-terminated" 111 | ) 112 | } 113 | guard let string = platformString.withUnsafeBufferPointer({ 114 | String(validatingPlatformString: $0.baseAddress!) 115 | }) else { 116 | return nil 117 | } 118 | self = string 119 | } 120 | 121 | @inlinable 122 | @_alwaysEmitIntoClient 123 | @available(*, deprecated, message: "Use String(_ scalar: Unicode.Scalar)") 124 | public init?( 125 | validatingPlatformString platformString: inout CInterop.PlatformChar 126 | ) { 127 | guard platformString == 0 else { 128 | fatalError( 129 | "input of String.init(validatingPlatformString:) must be null-terminated" 130 | ) 131 | } 132 | self = "" 133 | } 134 | 135 | @inlinable 136 | @_alwaysEmitIntoClient 137 | @available(*, deprecated, message: "Use a copy of the String argument") 138 | public init?( 139 | validatingPlatformString platformString: String 140 | ) { 141 | if let nullLoc = platformString.firstIndex(of: "\0") { 142 | self = String(platformString[..( 161 | _ body: (UnsafePointer) throws -> Result 162 | ) rethrows -> Result { 163 | try _withPlatformString(body) 164 | } 165 | 166 | } 167 | 168 | @available(/*System 0.0.2: macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0*/iOS 8, *) 169 | extension CInterop.PlatformChar { 170 | internal var _platformCodeUnit: CInterop.PlatformUnicodeEncoding.CodeUnit { 171 | #if os(Windows) 172 | return self 173 | #else 174 | return CInterop.PlatformUnicodeEncoding.CodeUnit(bitPattern: self) 175 | #endif 176 | } 177 | } 178 | 179 | @available(/*System 0.0.2: macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0*/iOS 8, *) 180 | extension CInterop.PlatformUnicodeEncoding.CodeUnit { 181 | internal var _platformChar: CInterop.PlatformChar { 182 | #if os(Windows) 183 | return self 184 | #else 185 | return CInterop.PlatformChar(bitPattern: self) 186 | #endif 187 | } 188 | } 189 | 190 | internal protocol _PlatformStringable { 191 | func _withPlatformString( 192 | _ body: (UnsafePointer) throws -> Result 193 | ) rethrows -> Result 194 | 195 | init?(_platformString: UnsafePointer) 196 | } 197 | extension String: _PlatformStringable {} 198 | -------------------------------------------------------------------------------- /Sources/System/Util+StringArray.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift System open source project 3 | 4 | Copyright (c) 2021 Apple Inc. and the Swift System project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | */ 9 | 10 | extension Array where Element == String { 11 | internal typealias CStr = UnsafePointer? 12 | 13 | /// Call `body` with a buffer of `UnsafePointer?` values, 14 | /// suitable for passing to a C function that expects a C string array. 15 | /// The buffer is guaranteed to be followed by an extra storage slot 16 | /// containing a null pointer. (For C functions that expect an array 17 | /// terminated with a null pointer.) 18 | /// 19 | /// This function is careful not to heap allocate memory unless there are 20 | /// too many strings, or if it needs to copy too much character data. 21 | internal func _withCStringArray( 22 | _ body: (UnsafeBufferPointer?>) throws -> R 23 | ) rethrows -> R { 24 | if self.count == 0 { 25 | // Fast path: empty array. 26 | let p: CStr = nil 27 | return try Swift.withUnsafePointer(to: p) { array in 28 | try body(UnsafeBufferPointer(start: array, count: 0)) 29 | } 30 | } 31 | #if SYSTEM_OS_BUILD // String._guts isn't accessible from SwiftPM or CMake 32 | if self.count == 1, self[0]._guts._isLargeZeroTerminatedContiguousUTF8 { 33 | // Fast path: Single fast string. 34 | let start = self[0]._guts._largeContiguousUTF8CodeUnits.baseAddress! 35 | var p: (CStr, CStr) = ( 36 | UnsafeRawPointer(start).assumingMemoryBound(to: CChar.self), 37 | nil 38 | ) 39 | return try Swift.withUnsafeBytes(of: &p) { buffer in 40 | let start = buffer.baseAddress!.assumingMemoryBound(to: CStr.self) 41 | return try body(UnsafeBufferPointer(start: start, count: 1)) 42 | } 43 | } 44 | #endif 45 | // We need to create a buffer for the C array. 46 | return try _withStackBuffer( 47 | capacity: (self.count + 1) * MemoryLayout.stride 48 | ) { array in 49 | let array = array.bindMemory(to: CStr.self) 50 | // Calculate number of bytes we need for character storage 51 | let bytes = self.reduce(into: 0) { count, string in 52 | #if SYSTEM_OS_BUILD 53 | if string._guts._isLargeZeroTerminatedContiguousUTF8 { return } 54 | #endif 55 | count += string.utf8.count + 1 // Plus one for terminating NUL 56 | } 57 | #if SYSTEM_OS_BUILD 58 | if bytes == 0 { 59 | // Fast path: we only contain strings with stable null-terminated storage 60 | for i in self.indices { 61 | let string = self[i] 62 | precondition(string._guts._isLargeZeroTerminatedContiguousUTF8) 63 | let address = string._guts._largeContiguousUTF8CodeUnits.baseAddress! 64 | array[i] = UnsafeRawPointer(address).assumingMemoryBound(to: CChar.self) 65 | } 66 | array[self.count] = nil 67 | return try body(UnsafeBufferPointer(rebasing: array.dropLast())) 68 | } 69 | #endif 70 | return try _withStackBuffer(capacity: bytes) { chars in 71 | var chars = chars 72 | for i in self.indices { 73 | let (cstr, scratchUsed) = self[i]._getCStr(with: chars) 74 | array[i] = cstr.assumingMemoryBound(to: CChar.self) 75 | chars = .init(rebasing: chars[scratchUsed...]) 76 | } 77 | array[self.count] = nil 78 | return try body(UnsafeBufferPointer(rebasing: array.dropLast())) 79 | } 80 | } 81 | } 82 | } 83 | 84 | extension String { 85 | fileprivate func _getCStr( 86 | with scratch: UnsafeMutableRawBufferPointer 87 | ) -> (cstr: UnsafeRawPointer, scratchUsed: Int) { 88 | #if SYSTEM_OS_BUILD 89 | if _guts._isLargeZeroTerminatedContiguousUTF8 { 90 | // This is a wonderful string, we can just use its storage address. 91 | let address = _guts._largeContiguousUTF8CodeUnits.baseAddress! 92 | return (UnsafeRawPointer(address), 0) 93 | } 94 | #endif 95 | let r: (UnsafeRawPointer, Int)? = self.utf8.withContiguousStorageIfAvailable { source in 96 | // This is a somewhat okay string -- we need to use memcpy. 97 | precondition(source.count <= scratch.count) 98 | let start = scratch.baseAddress! 99 | start.copyMemory(from: source.baseAddress!, byteCount: source.count) 100 | start.storeBytes(of: 0, toByteOffset: source.count, as: UInt8.self) 101 | return (UnsafeRawPointer(start), source.count + 1) 102 | } 103 | if let r = r { return r } 104 | 105 | // What a horrible string; we need to copy individual bytes. 106 | precondition(self.utf8.count <= scratch.count) 107 | var c = 0 108 | for byte in self.utf8 { 109 | scratch[c] = byte 110 | c += 1 111 | } 112 | scratch[c] = 0 113 | c += 1 114 | return (UnsafeRawPointer(scratch.baseAddress!), c) 115 | } 116 | } 117 | 118 | -------------------------------------------------------------------------------- /Sources/System/Util.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift System open source project 3 | 4 | Copyright (c) 2020 - 2021 Apple Inc. and the Swift System project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | */ 9 | 10 | // Results in errno if i == -1 11 | @available(/*System 0.0.1: macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0*/iOS 8, *) 12 | private func valueOrErrno( 13 | _ i: I 14 | ) -> Result { 15 | i == -1 ? .failure(Errno.current) : .success(i) 16 | } 17 | 18 | @available(/*System 0.0.1: macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0*/iOS 8, *) 19 | private func nothingOrErrno( 20 | _ i: I 21 | ) -> Result<(), Errno> { 22 | valueOrErrno(i).map { _ in () } 23 | } 24 | 25 | @available(/*System 0.0.1: macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0*/iOS 8, *) 26 | internal func valueOrErrno( 27 | retryOnInterrupt: Bool, _ f: () -> I 28 | ) -> Result { 29 | repeat { 30 | switch valueOrErrno(f()) { 31 | case .success(let r): return .success(r) 32 | case .failure(let err): 33 | guard retryOnInterrupt && err == .interrupted else { return .failure(err) } 34 | break 35 | } 36 | } while true 37 | } 38 | 39 | @available(/*System 0.0.1: macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0*/iOS 8, *) 40 | internal func nothingOrErrno( 41 | retryOnInterrupt: Bool, _ f: () -> I 42 | ) -> Result<(), Errno> { 43 | valueOrErrno(retryOnInterrupt: retryOnInterrupt, f).map { _ in () } 44 | } 45 | 46 | // Run a precondition for debug client builds 47 | internal func _debugPrecondition( 48 | _ condition: @autoclosure () -> Bool, 49 | _ message: StaticString = StaticString(), 50 | file: StaticString = #file, line: UInt = #line 51 | ) { 52 | // Only check in debug mode. 53 | if _slowPath(_isDebugAssertConfiguration()) { 54 | precondition( 55 | condition(), String(describing: message), file: file, line: line) 56 | } 57 | } 58 | 59 | extension OpaquePointer { 60 | internal var _isNULL: Bool { 61 | OpaquePointer(bitPattern: Int(bitPattern: self)) == nil 62 | } 63 | } 64 | 65 | extension Sequence { 66 | // Tries to recast contiguous pointer if available, otherwise allocates memory. 67 | internal func _withRawBufferPointer( 68 | _ body: (UnsafeRawBufferPointer) throws -> R 69 | ) rethrows -> R { 70 | guard let result = try self.withContiguousStorageIfAvailable({ 71 | try body(UnsafeRawBufferPointer($0)) 72 | }) else { 73 | return try Array(self).withUnsafeBytes(body) 74 | } 75 | return result 76 | } 77 | } 78 | 79 | extension OptionSet { 80 | // Helper method for building up a comma-separated list of options 81 | // 82 | // Taking an array of descriptions reduces code size vs 83 | // a series of calls due to avoiding register copies. Make sure 84 | // to pass an array literal and not an array built up from a series of 85 | // append calls, else that will massively bloat code size. This takes 86 | // StaticStrings because otherwise we get a warning about getting evicted 87 | // from the shared cache. 88 | @inline(never) 89 | internal func _buildDescription( 90 | _ descriptions: [(Element, StaticString)] 91 | ) -> String { 92 | var copy = self 93 | var result = "[" 94 | 95 | for (option, name) in descriptions { 96 | if _slowPath(copy.contains(option)) { 97 | result += name.description 98 | copy.remove(option) 99 | if !copy.isEmpty { result += ", " } 100 | } 101 | } 102 | 103 | if _slowPath(!copy.isEmpty) { 104 | result += "\(Self.self)(rawValue: \(copy.rawValue))" 105 | } 106 | result += "]" 107 | return result 108 | } 109 | } 110 | 111 | internal func _dropCommonPrefix( 112 | _ lhs: C, _ rhs: C 113 | ) -> (C.SubSequence, C.SubSequence) 114 | where C.Element: Equatable { 115 | var (lhs, rhs) = (lhs[...], rhs[...]) 116 | while lhs.first != nil && lhs.first == rhs.first { 117 | lhs.removeFirst() 118 | rhs.removeFirst() 119 | } 120 | return (lhs, rhs) 121 | } 122 | 123 | extension MutableCollection where Element: Equatable { 124 | mutating func _replaceAll(_ e: Element, with new: Element) { 125 | for idx in self.indices { 126 | if self[idx] == e { self[idx] = new } 127 | } 128 | } 129 | } 130 | 131 | internal func _withOptionalUnsafePointerOrNull( 132 | to value: T?, 133 | _ body: (UnsafePointer?) throws -> R 134 | ) rethrows -> R { 135 | guard let value = value else { 136 | return try body(nil) 137 | } 138 | return try withUnsafePointer(to: value, body) 139 | } 140 | 141 | /// Calls `body` with a temporary buffer of the indicated size, 142 | /// possibly stack-allocated. 143 | internal func _withStackBuffer( 144 | capacity: Int, 145 | _ body: (UnsafeMutableRawBufferPointer) throws -> R 146 | ) rethrows -> R { 147 | typealias StackStorage = ( 148 | UInt64, UInt64, UInt64, UInt64, 149 | UInt64, UInt64, UInt64, UInt64, 150 | UInt64, UInt64, UInt64, UInt64, 151 | UInt64, UInt64, UInt64, UInt64 152 | ) 153 | if capacity > MemoryLayout.size { 154 | var buffer = _RawBuffer(minimumCapacity: capacity) 155 | return try buffer.withUnsafeMutableBytes { buffer in 156 | try body(.init(rebasing: buffer[.. Bool) -> Element? { 14 | guard let s = self.first, p(s) else { return nil } 15 | self = self.dropFirst() 16 | return s 17 | } 18 | internal mutating func _eat(_ e: Element) -> Element? { 19 | _eat(if: { $0 == e }) 20 | } 21 | 22 | internal mutating func _eat(asserting e: Element) { 23 | let p = _eat(e) 24 | assert(p != nil) 25 | } 26 | 27 | internal mutating func _eat(count c: Int) -> Slice { 28 | defer { self = self.dropFirst(c) } 29 | return self.prefix(c) 30 | } 31 | 32 | internal mutating func _eatSequence(_ es: C) -> Slice? 33 | where C.Element == Element 34 | { 35 | guard self.starts(with: es) else { return nil } 36 | return _eat(count: es.count) 37 | } 38 | 39 | internal mutating func _eatUntil(_ idx: Index) -> Slice { 40 | precondition(idx >= startIndex && idx <= endIndex) 41 | defer { self = self[idx...] } 42 | return self[.. Slice { 46 | precondition(idx >= startIndex && idx <= endIndex) 47 | guard idx != endIndex else { 48 | defer { self = self[endIndex ..< endIndex] } 49 | return self 50 | } 51 | defer { self = self[index(after: idx)...] } 52 | return self[...idx] 53 | } 54 | 55 | // If `e` is present, eat up to first occurence of `e` 56 | internal mutating func _eatUntil(_ e: Element) -> Slice? { 57 | guard let idx = self.firstIndex(of: e) else { return nil } 58 | return _eatUntil(idx) 59 | } 60 | 61 | // If `e` is present, eat up to and through first occurence of `e` 62 | internal mutating func _eatThrough(_ e: Element) -> Slice? { 63 | guard let idx = self.firstIndex(of: e) else { return nil } 64 | return _eatThrough(idx) 65 | } 66 | 67 | // Eat any elements from the front matching the predicate 68 | internal mutating func _eatWhile(_ p: (Element) -> Bool) -> Slice? { 69 | let idx = firstIndex(where: { !p($0) }) ?? endIndex 70 | guard idx != startIndex else { return nil } 71 | return _eatUntil(idx) 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /Tests/SystemTests/ErrnoTest.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift System open source project 3 | 4 | Copyright (c) 2020 Apple Inc. and the Swift System project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | */ 9 | 10 | import XCTest 11 | 12 | #if SYSTEM_PACKAGE 13 | import SystemPackage 14 | #else 15 | import System 16 | #endif 17 | 18 | #if os(Windows) 19 | import WinSDK 20 | #endif 21 | 22 | @available(/*System 0.0.1: macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0*/iOS 8, *) 23 | final class ErrnoTest: XCTestCase { 24 | func testConstants() { 25 | XCTAssert(EPERM == Errno.notPermitted.rawValue) 26 | XCTAssert(ENOENT == Errno.noSuchFileOrDirectory.rawValue) 27 | XCTAssert(ESRCH == Errno.noSuchProcess.rawValue) 28 | XCTAssert(EINTR == Errno.interrupted.rawValue) 29 | XCTAssert(EIO == Errno.ioError.rawValue) 30 | XCTAssert(ENXIO == Errno.noSuchAddressOrDevice.rawValue) 31 | XCTAssert(E2BIG == Errno.argListTooLong.rawValue) 32 | XCTAssert(ENOEXEC == Errno.execFormatError.rawValue) 33 | XCTAssert(EBADF == Errno.badFileDescriptor.rawValue) 34 | XCTAssert(ECHILD == Errno.noChildProcess.rawValue) 35 | XCTAssert(EDEADLK == Errno.deadlock.rawValue) 36 | XCTAssert(ENOMEM == Errno.noMemory.rawValue) 37 | XCTAssert(EACCES == Errno.permissionDenied.rawValue) 38 | XCTAssert(EFAULT == Errno.badAddress.rawValue) 39 | #if !os(Windows) 40 | XCTAssert(ENOTBLK == Errno.notBlockDevice.rawValue) 41 | #endif 42 | XCTAssert(EBUSY == Errno.resourceBusy.rawValue) 43 | XCTAssert(EEXIST == Errno.fileExists.rawValue) 44 | XCTAssert(EXDEV == Errno.improperLink.rawValue) 45 | XCTAssert(ENODEV == Errno.operationNotSupportedByDevice.rawValue) 46 | XCTAssert(ENOTDIR == Errno.notDirectory.rawValue) 47 | XCTAssert(EISDIR == Errno.isDirectory.rawValue) 48 | XCTAssert(EINVAL == Errno.invalidArgument.rawValue) 49 | XCTAssert(ENFILE == Errno.tooManyOpenFilesInSystem.rawValue) 50 | XCTAssert(EMFILE == Errno.tooManyOpenFiles.rawValue) 51 | #if !os(Windows) 52 | XCTAssert(ENOTTY == Errno.inappropriateIOCTLForDevice.rawValue) 53 | XCTAssert(ETXTBSY == Errno.textFileBusy.rawValue) 54 | #endif 55 | XCTAssert(EFBIG == Errno.fileTooLarge.rawValue) 56 | XCTAssert(ENOSPC == Errno.noSpace.rawValue) 57 | XCTAssert(ESPIPE == Errno.illegalSeek.rawValue) 58 | XCTAssert(EROFS == Errno.readOnlyFileSystem.rawValue) 59 | XCTAssert(EMLINK == Errno.tooManyLinks.rawValue) 60 | XCTAssert(EPIPE == Errno.brokenPipe.rawValue) 61 | XCTAssert(EDOM == Errno.outOfDomain.rawValue) 62 | XCTAssert(ERANGE == Errno.outOfRange.rawValue) 63 | XCTAssert(EAGAIN == Errno.resourceTemporarilyUnavailable.rawValue) 64 | XCTAssert(EINPROGRESS == Errno.nowInProgress.rawValue) 65 | XCTAssert(EALREADY == Errno.alreadyInProcess.rawValue) 66 | XCTAssert(ENOTSOCK == Errno.notSocket.rawValue) 67 | XCTAssert(EDESTADDRREQ == Errno.addressRequired.rawValue) 68 | XCTAssert(EMSGSIZE == Errno.messageTooLong.rawValue) 69 | XCTAssert(EPROTOTYPE == Errno.protocolWrongTypeForSocket.rawValue) 70 | XCTAssert(ENOPROTOOPT == Errno.protocolNotAvailable.rawValue) 71 | XCTAssert(EPROTONOSUPPORT == Errno.protocolNotSupported.rawValue) 72 | #if os(Windows) 73 | XCTAssert(WSAESOCKTNOSUPPORT == Errno.socketTypeNotSupported.rawValue) 74 | XCTAssert(WSAEOPNOTSUPP == Errno.notSupported.rawValue) 75 | XCTAssert(WSAEPFNOSUPPORT == Errno.protocolFamilyNotSupported.rawValue) 76 | #else 77 | XCTAssert(ESOCKTNOSUPPORT == Errno.socketTypeNotSupported.rawValue) 78 | XCTAssert(ENOTSUP == Errno.notSupported.rawValue) 79 | XCTAssert(EPFNOSUPPORT == Errno.protocolFamilyNotSupported.rawValue) 80 | #endif 81 | XCTAssert(EAFNOSUPPORT == Errno.addressFamilyNotSupported.rawValue) 82 | XCTAssert(EADDRINUSE == Errno.addressInUse.rawValue) 83 | XCTAssert(EADDRNOTAVAIL == Errno.addressNotAvailable.rawValue) 84 | XCTAssert(ENETDOWN == Errno.networkDown.rawValue) 85 | XCTAssert(ENETUNREACH == Errno.networkUnreachable.rawValue) 86 | XCTAssert(ENETRESET == Errno.networkReset.rawValue) 87 | XCTAssert(ECONNABORTED == Errno.connectionAbort.rawValue) 88 | XCTAssert(ECONNRESET == Errno.connectionReset.rawValue) 89 | XCTAssert(ENOBUFS == Errno.noBufferSpace.rawValue) 90 | XCTAssert(EISCONN == Errno.socketIsConnected.rawValue) 91 | XCTAssert(ENOTCONN == Errno.socketNotConnected.rawValue) 92 | #if os(Windows) 93 | XCTAssert(WSAESHUTDOWN == Errno.socketShutdown.rawValue) 94 | #else 95 | XCTAssert(ESHUTDOWN == Errno.socketShutdown.rawValue) 96 | #endif 97 | XCTAssert(ETIMEDOUT == Errno.timedOut.rawValue) 98 | XCTAssert(ECONNREFUSED == Errno.connectionRefused.rawValue) 99 | XCTAssert(ELOOP == Errno.tooManySymbolicLinkLevels.rawValue) 100 | XCTAssert(ENAMETOOLONG == Errno.fileNameTooLong.rawValue) 101 | #if os(Windows) 102 | XCTAssert(WSAEHOSTDOWN == Errno.hostIsDown.rawValue) 103 | #else 104 | XCTAssert(EHOSTDOWN == Errno.hostIsDown.rawValue) 105 | #endif 106 | XCTAssert(EHOSTUNREACH == Errno.noRouteToHost.rawValue) 107 | XCTAssert(ENOTEMPTY == Errno.directoryNotEmpty.rawValue) 108 | 109 | #if SYSTEM_PACKAGE_DARWIN 110 | XCTAssert(EPROCLIM == Errno.tooManyProcesses.rawValue) 111 | #endif 112 | 113 | #if os(Windows) 114 | XCTAssert(WSAEUSERS == Errno.tooManyUsers.rawValue) 115 | XCTAssert(WSAEDQUOT == Errno.diskQuotaExceeded.rawValue) 116 | XCTAssert(WSAESTALE == Errno.staleNFSFileHandle.rawValue) 117 | #else 118 | XCTAssert(EUSERS == Errno.tooManyUsers.rawValue) 119 | XCTAssert(EDQUOT == Errno.diskQuotaExceeded.rawValue) 120 | XCTAssert(ESTALE == Errno.staleNFSFileHandle.rawValue) 121 | #endif 122 | 123 | #if SYSTEM_PACKAGE_DARWIN 124 | XCTAssert(EBADRPC == Errno.rpcUnsuccessful.rawValue) 125 | XCTAssert(ERPCMISMATCH == Errno.rpcVersionMismatch.rawValue) 126 | XCTAssert(EPROGUNAVAIL == Errno.rpcProgramUnavailable.rawValue) 127 | XCTAssert(EPROGMISMATCH == Errno.rpcProgramVersionMismatch.rawValue) 128 | XCTAssert(EPROCUNAVAIL == Errno.rpcProcedureUnavailable.rawValue) 129 | #endif 130 | 131 | XCTAssert(ENOLCK == Errno.noLocks.rawValue) 132 | XCTAssert(ENOSYS == Errno.noFunction.rawValue) 133 | 134 | #if SYSTEM_PACKAGE_DARWIN || os(FreeBSD) 135 | XCTAssert(EFTYPE == Errno.badFileTypeOrFormat.rawValue) 136 | XCTAssert(EAUTH == Errno.authenticationError.rawValue) 137 | XCTAssert(ENEEDAUTH == Errno.needAuthenticator.rawValue) 138 | #endif 139 | 140 | #if SYSTEM_PACKAGE_DARWIN 141 | XCTAssert(EPWROFF == Errno.devicePowerIsOff.rawValue) 142 | XCTAssert(EDEVERR == Errno.deviceError.rawValue) 143 | #endif 144 | 145 | #if !os(Windows) 146 | XCTAssert(EOVERFLOW == Errno.overflow.rawValue) 147 | #endif 148 | 149 | #if SYSTEM_PACKAGE_DARWIN 150 | XCTAssert(EBADEXEC == Errno.badExecutable.rawValue) 151 | XCTAssert(EBADARCH == Errno.badCPUType.rawValue) 152 | XCTAssert(ESHLIBVERS == Errno.sharedLibraryVersionMismatch.rawValue) 153 | XCTAssert(EBADMACHO == Errno.malformedMachO.rawValue) 154 | #endif 155 | 156 | XCTAssert(ECANCELED == Errno.canceled.rawValue) 157 | #if !os(Windows) 158 | XCTAssert(EIDRM == Errno.identifierRemoved.rawValue) 159 | XCTAssert(ENOMSG == Errno.noMessage.rawValue) 160 | #endif 161 | XCTAssert(EILSEQ == Errno.illegalByteSequence.rawValue) 162 | 163 | #if SYSTEM_PACKAGE_DARWIN 164 | XCTAssert(ENOATTR == Errno.attributeNotFound.rawValue) 165 | #endif 166 | 167 | #if !os(Windows) 168 | XCTAssert(EBADMSG == Errno.badMessage.rawValue) 169 | XCTAssert(EMULTIHOP == Errno.multiHop.rawValue) 170 | XCTAssert(ENOLINK == Errno.noLink.rawValue) 171 | XCTAssert(EPROTO == Errno.protocolError.rawValue) 172 | #endif 173 | 174 | #if !os(Windows) && !os(FreeBSD) 175 | XCTAssert(ENODATA == Errno.noData.rawValue) 176 | XCTAssert(ENOSR == Errno.noStreamResources.rawValue) 177 | XCTAssert(ENOSTR == Errno.notStream.rawValue) 178 | XCTAssert(ETIME == Errno.timeout.rawValue) 179 | #endif 180 | 181 | XCTAssert(EOPNOTSUPP == Errno.notSupportedOnSocket.rawValue) 182 | 183 | // From headers but not man page 184 | XCTAssert(EWOULDBLOCK == Errno.wouldBlock.rawValue) 185 | #if os(Windows) 186 | XCTAssert(WSAETOOMANYREFS == Errno.tooManyReferences.rawValue) 187 | XCTAssert(WSAEREMOTE == Errno.tooManyRemoteLevels.rawValue) 188 | #else 189 | XCTAssert(ETOOMANYREFS == Errno.tooManyReferences.rawValue) 190 | XCTAssert(EREMOTE == Errno.tooManyRemoteLevels.rawValue) 191 | #endif 192 | 193 | #if SYSTEM_PACKAGE_DARWIN 194 | XCTAssert(ENOPOLICY == Errno.noSuchPolicy.rawValue) 195 | #endif 196 | 197 | #if !os(Windows) 198 | XCTAssert(ENOTRECOVERABLE == Errno.notRecoverable.rawValue) 199 | XCTAssert(EOWNERDEAD == Errno.previousOwnerDied.rawValue) 200 | #endif 201 | 202 | #if os(FreeBSD) 203 | XCTAssert(ENOTCAPABLE == Errno.notCapable.rawValue) 204 | XCTAssert(ECAPMODE == Errno.capabilityMode.rawValue) 205 | XCTAssert(EINTEGRITY == Errno.integrityCheckFailed.rawValue) 206 | #endif 207 | 208 | #if SYSTEM_PACKAGE_DARWIN 209 | XCTAssert(EQFULL == Errno.outputQueueFull.rawValue) 210 | XCTAssert(ELAST == Errno.lastErrnoValue.rawValue) 211 | #endif 212 | } 213 | 214 | func testPatternMatching() { 215 | func throwsEPERM() throws { 216 | throw Errno.notPermitted 217 | } 218 | 219 | do { 220 | try throwsEPERM() 221 | } catch Errno.noSuchProcess { 222 | XCTAssert(false) 223 | } catch Errno.notPermitted { 224 | // pass 225 | } catch { 226 | XCTAssert(false) 227 | } 228 | } 229 | 230 | // TODO: `_code/_domain` for NSError bridging 231 | 232 | // TODO: `description` 233 | } 234 | -------------------------------------------------------------------------------- /Tests/SystemTests/FileDescriptorExtras.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift System open source project 3 | 4 | Copyright (c) 2020 Apple Inc. and the Swift System project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | */ 9 | 10 | #if SYSTEM_PACKAGE 11 | @testable import SystemPackage 12 | #else 13 | @testable import System 14 | #endif 15 | 16 | extension FileDescriptor { 17 | internal func fileSize( 18 | retryOnInterrupt: Bool = true 19 | ) throws -> Int64 { 20 | let current = try seek(offset: 0, from: .current) 21 | let size = try seek(offset: 0, from: .end) 22 | try seek(offset: current, from: .start) 23 | return size 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Tests/SystemTests/FileOperationsTest.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift System open source project 3 | 4 | Copyright (c) 2020 Apple Inc. and the Swift System project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | */ 9 | 10 | import XCTest 11 | 12 | #if SYSTEM_PACKAGE 13 | @testable import SystemPackage 14 | #else 15 | @testable import System 16 | #endif 17 | #if canImport(Android) 18 | import Android 19 | #endif 20 | 21 | @available(/*System 0.0.1: macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0*/iOS 8, *) 22 | final class FileOperationsTest: XCTestCase { 23 | func testSyscalls() { 24 | let fd = FileDescriptor(rawValue: 1) 25 | 26 | let rawBuf = UnsafeMutableRawBufferPointer.allocate(byteCount: 100, alignment: 4) 27 | defer { rawBuf.deallocate() } 28 | let bufAddr = rawBuf.baseAddress 29 | let rawFD = fd.rawValue 30 | let bufCount = rawBuf.count 31 | let writeBuf = UnsafeRawBufferPointer(rawBuf) 32 | let writeBufAddr = writeBuf.baseAddress 33 | 34 | let syscallTestCases: Array = [ 35 | MockTestCase(name: "open", .interruptable, "a path", O_RDWR | O_APPEND) { 36 | retryOnInterrupt in 37 | _ = try FileDescriptor.open( 38 | "a path", .readWrite, options: [.append], retryOnInterrupt: retryOnInterrupt) 39 | }, 40 | 41 | MockTestCase(name: "open", .interruptable, "a path", O_WRONLY | O_CREAT | O_APPEND, 0o777) { 42 | retryOnInterrupt in 43 | _ = try FileDescriptor.open( 44 | "a path", .writeOnly, options: [.create, .append], 45 | permissions: [.groupReadWriteExecute, .ownerReadWriteExecute, .otherReadWriteExecute], 46 | retryOnInterrupt: retryOnInterrupt) 47 | }, 48 | 49 | MockTestCase(name: "read", .interruptable, rawFD, bufAddr, bufCount) { 50 | retryOnInterrupt in 51 | _ = try fd.read(into: rawBuf, retryOnInterrupt: retryOnInterrupt) 52 | }, 53 | 54 | MockTestCase(name: "pread", .interruptable, rawFD, bufAddr, bufCount, 5) { 55 | retryOnInterrupt in 56 | _ = try fd.read(fromAbsoluteOffset: 5, into: rawBuf, retryOnInterrupt: retryOnInterrupt) 57 | }, 58 | 59 | MockTestCase(name: "lseek", .noInterrupt, rawFD, -2, SEEK_END) { 60 | _ in 61 | _ = try fd.seek(offset: -2, from: .end) 62 | }, 63 | 64 | MockTestCase(name: "write", .interruptable, rawFD, writeBufAddr, bufCount) { 65 | retryOnInterrupt in 66 | _ = try fd.write(writeBuf, retryOnInterrupt: retryOnInterrupt) 67 | }, 68 | 69 | MockTestCase(name: "pwrite", .interruptable, rawFD, writeBufAddr, bufCount, 7) { 70 | retryOnInterrupt in 71 | _ = try fd.write(toAbsoluteOffset: 7, writeBuf, retryOnInterrupt: retryOnInterrupt) 72 | }, 73 | 74 | MockTestCase(name: "close", .noInterrupt, rawFD) { 75 | _ in 76 | _ = try fd.close() 77 | }, 78 | 79 | MockTestCase(name: "dup", .interruptable, rawFD) { retryOnInterrupt in 80 | _ = try fd.duplicate(retryOnInterrupt: retryOnInterrupt) 81 | }, 82 | 83 | MockTestCase(name: "dup2", .interruptable, rawFD, 42) { retryOnInterrupt in 84 | _ = try fd.duplicate(as: FileDescriptor(rawValue: 42), 85 | retryOnInterrupt: retryOnInterrupt) 86 | }, 87 | ] 88 | 89 | for test in syscallTestCases { test.runAllTests() } 90 | } 91 | 92 | func testWriteFromEmptyBuffer() throws { 93 | #if os(Windows) 94 | let fd = try FileDescriptor.open(FilePath("NUL"), .writeOnly) 95 | #else 96 | let fd = try FileDescriptor.open(FilePath("/dev/null"), .writeOnly) 97 | #endif 98 | let written1 = try fd.write(toAbsoluteOffset: 0, .init(start: nil, count: 0)) 99 | XCTAssertEqual(written1, 0) 100 | 101 | let pointer = UnsafeMutableRawPointer.allocate(byteCount: 8, alignment: 8) 102 | defer { pointer.deallocate() } 103 | let empty = UnsafeRawBufferPointer(start: pointer, count: 0) 104 | let written2 = try fd.write(toAbsoluteOffset: 0, empty) 105 | XCTAssertEqual(written2, 0) 106 | } 107 | 108 | #if os(Windows) 109 | // Generate a file containing random bytes; this should not be used 110 | // for cryptography, it's just for testing. 111 | func generateRandomData(at path: FilePath, count: Int) throws { 112 | let fd = try FileDescriptor.open(path, .readWrite, 113 | options: [.create, .truncate]) 114 | defer { 115 | try! fd.close() 116 | } 117 | let data = [UInt8]( 118 | sequence(first: 0, 119 | next: { 120 | _ in UInt8.random(in: UInt8.min...UInt8.max) 121 | }).dropFirst().prefix(count) 122 | ) 123 | 124 | try data.withUnsafeBytes { 125 | _ = try fd.write($0) 126 | } 127 | } 128 | #endif 129 | 130 | func testReadToEmptyBuffer() throws { 131 | try withTemporaryFilePath(basename: "testReadToEmptyBuffer") { path in 132 | #if os(Windows) 133 | // Windows doesn't have an equivalent to /dev/random, so generate 134 | // some random bytes and write them to a file for the next step. 135 | let randomPath = path.appending("random.txt") 136 | try generateRandomData(at: randomPath, count: 16) 137 | let fd = try FileDescriptor.open(randomPath, .readOnly) 138 | #else // !os(Windows) 139 | let fd = try FileDescriptor.open(FilePath("/dev/random"), .readOnly) 140 | #endif 141 | let read1 = try fd.read(fromAbsoluteOffset: 0, into: .init(start: nil, count: 0)) 142 | XCTAssertEqual(read1, 0) 143 | 144 | let pointer = UnsafeMutableRawPointer.allocate(byteCount: 8, alignment: 8) 145 | defer { pointer.deallocate() } 146 | let empty = UnsafeMutableRawBufferPointer(start: pointer, count: 0) 147 | let read2 = try fd.read(fromAbsoluteOffset: 0, into: empty) 148 | XCTAssertEqual(read2, 0) 149 | } 150 | } 151 | 152 | func testHelpers() { 153 | // TODO: Test writeAll, writeAll(toAbsoluteOffset), closeAfter 154 | } 155 | 156 | func testAdHocPipe() throws { 157 | // Ad-hoc test testing `Pipe` functionality. 158 | // 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`. 159 | let pipe = try FileDescriptor.pipe() 160 | try pipe.readEnd.closeAfter { 161 | try pipe.writeEnd.closeAfter { 162 | var abc = "abc" 163 | try abc.withUTF8 { 164 | _ = try pipe.writeEnd.write(UnsafeRawBufferPointer($0)) 165 | } 166 | let readLen = 3 167 | let readBytes = try Array(unsafeUninitializedCapacity: readLen) { buf, count in 168 | count = try pipe.readEnd.read(into: UnsafeMutableRawBufferPointer(buf)) 169 | } 170 | XCTAssertEqual(readBytes, Array(abc.utf8)) 171 | } 172 | } 173 | } 174 | 175 | func testAdHocOpen() { 176 | // Ad-hoc test touching a file system. 177 | do { 178 | // TODO: Test this against a virtual in-memory file system 179 | try withTemporaryFilePath(basename: "testAdhocOpen") { path in 180 | let fd = try FileDescriptor.open(path.appending("b.txt"), .readWrite, options: [.create, .truncate], permissions: .ownerReadWrite) 181 | try fd.closeAfter { 182 | try fd.writeAll("abc".utf8) 183 | var def = "def" 184 | try def.withUTF8 { 185 | _ = try fd.write(UnsafeRawBufferPointer($0)) 186 | } 187 | try fd.seek(offset: 1, from: .start) 188 | 189 | let readLen = 3 190 | let readBytes = try Array(unsafeUninitializedCapacity: readLen) { (buf, count) in 191 | count = try fd.read(into: UnsafeMutableRawBufferPointer(buf)) 192 | } 193 | let preadBytes = try Array(unsafeUninitializedCapacity: readLen) { (buf, count) in 194 | count = try fd.read(fromAbsoluteOffset: 1, into: UnsafeMutableRawBufferPointer(buf)) 195 | } 196 | 197 | XCTAssertEqual(readBytes.first!, "b".utf8.first!) 198 | XCTAssertEqual(readBytes, preadBytes) 199 | 200 | // TODO: seek 201 | } 202 | } 203 | } catch let err as Errno { 204 | print("caught \(err))") 205 | // Should we assert? I'd be interested in knowing if this happened 206 | XCTAssert(false) 207 | } catch { 208 | fatalError("FATAL: `testAdHocOpen`") 209 | } 210 | } 211 | 212 | func testGithubIssues() { 213 | // https://github.com/apple/swift-system/issues/26 214 | let issue26 = MockTestCase( 215 | name: "open", .interruptable, "a path", O_WRONLY | O_CREAT, 0o020 216 | ) { 217 | retryOnInterrupt in 218 | _ = try FileDescriptor.open( 219 | "a path", .writeOnly, options: [.create], 220 | permissions: [.groupWrite], 221 | retryOnInterrupt: retryOnInterrupt) 222 | } 223 | issue26.runAllTests() 224 | 225 | } 226 | 227 | func testResizeFile() throws { 228 | try withTemporaryFilePath(basename: "testResizeFile") { path in 229 | let fd = try FileDescriptor.open(path.appending("\(UUID().uuidString).txt"), .readWrite, options: [.create, .truncate], permissions: .ownerReadWrite) 230 | try fd.closeAfter { 231 | // File should be empty initially. 232 | XCTAssertEqual(try fd.fileSize(), 0) 233 | // Write 3 bytes. 234 | try fd.writeAll("abc".utf8) 235 | // File should now be 3 bytes. 236 | XCTAssertEqual(try fd.fileSize(), 3) 237 | // Resize to 6 bytes. 238 | try fd.resize(to: 6) 239 | // File should now be 6 bytes. 240 | XCTAssertEqual(try fd.fileSize(), 6) 241 | // Read in the 6 bytes. 242 | let readBytes = try Array(unsafeUninitializedCapacity: 6) { (buf, count) in 243 | try fd.seek(offset: 0, from: .start) 244 | // Should have read all 6 bytes. 245 | count = try fd.read(into: UnsafeMutableRawBufferPointer(buf)) 246 | XCTAssertEqual(count, 6) 247 | } 248 | // First 3 bytes should be unaffected by resize. 249 | XCTAssertEqual(Array(readBytes[..<3]), Array("abc".utf8)) 250 | // Extension should be padded with zeros. 251 | XCTAssertEqual(Array(readBytes[3...]), Array(repeating: 0, count: 3)) 252 | // File should still be 6 bytes. 253 | XCTAssertEqual(try fd.fileSize(), 6) 254 | // Resize to 2 bytes. 255 | try fd.resize(to: 2) 256 | // File should now be 2 bytes. 257 | XCTAssertEqual(try fd.fileSize(), 2) 258 | // Read in file with a buffer big enough for 6 bytes. 259 | let readBytesAfterTruncation = try Array(unsafeUninitializedCapacity: 6) { (buf, count) in 260 | try fd.seek(offset: 0, from: .start) 261 | count = try fd.read(into: UnsafeMutableRawBufferPointer(buf)) 262 | // Should only have read 2 bytes. 263 | XCTAssertEqual(count, 2) 264 | } 265 | // Written content was trunctated. 266 | XCTAssertEqual(readBytesAfterTruncation, Array("ab".utf8)) 267 | } 268 | } 269 | } 270 | } 271 | 272 | -------------------------------------------------------------------------------- /Tests/SystemTests/FileOperationsTestWindows.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift System open source project 3 | 4 | Copyright (c) 2024 Apple Inc. and the Swift System project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | */ 9 | 10 | import XCTest 11 | 12 | #if os(Windows) 13 | 14 | #if SYSTEM_PACKAGE 15 | @testable import SystemPackage 16 | #else 17 | @testable import System 18 | #endif 19 | 20 | import WinSDK 21 | 22 | @available(iOS 8, *) 23 | final class FileOperationsTestWindows: XCTestCase { 24 | private let r = ACCESS_MASK( 25 | FILE_READ_ATTRIBUTES 26 | | FILE_READ_DATA 27 | | FILE_READ_EA 28 | | STANDARD_RIGHTS_READ 29 | | SYNCHRONIZE 30 | ) 31 | private let w = ACCESS_MASK( 32 | FILE_APPEND_DATA 33 | | FILE_WRITE_ATTRIBUTES 34 | | FILE_WRITE_DATA 35 | | FILE_WRITE_EA 36 | | STANDARD_RIGHTS_WRITE 37 | | SYNCHRONIZE 38 | ) 39 | private let x = ACCESS_MASK( 40 | FILE_EXECUTE 41 | | FILE_READ_ATTRIBUTES 42 | | STANDARD_RIGHTS_EXECUTE 43 | | SYNCHRONIZE 44 | ) 45 | 46 | private struct Test { 47 | var permissions: CModeT 48 | var ownerAccess: ACCESS_MASK 49 | var groupAccess: ACCESS_MASK 50 | var otherAccess: ACCESS_MASK 51 | 52 | init(_ permissions: CModeT, 53 | _ ownerAccess: ACCESS_MASK, 54 | _ groupAccess: ACCESS_MASK, 55 | _ otherAccess: ACCESS_MASK) { 56 | self.permissions = permissions 57 | self.ownerAccess = ownerAccess 58 | self.groupAccess = groupAccess 59 | self.otherAccess = otherAccess 60 | } 61 | } 62 | 63 | /// Retrieve the owner, group and other access masks for a given file. 64 | /// 65 | /// - Parameters: 66 | /// - path: The path to the file to inspect 67 | /// - Returns: A tuple of ACCESS_MASK values. 68 | func getAccessMasks( 69 | path: FilePath 70 | ) -> (ACCESS_MASK, ACCESS_MASK, ACCESS_MASK) { 71 | var SIDAuthWorld = SID_IDENTIFIER_AUTHORITY(Value: (0, 0, 0, 0, 0, 1)) 72 | var psidEveryone: PSID? = nil 73 | 74 | XCTAssert(AllocateAndInitializeSid(&SIDAuthWorld, 1, 75 | DWORD(SECURITY_WORLD_RID), 76 | 0, 0, 0, 0, 0, 0, 0, 77 | &psidEveryone)) 78 | defer { 79 | FreeSid(psidEveryone) 80 | } 81 | 82 | var everyone = TRUSTEE_W( 83 | pMultipleTrustee: nil, 84 | MultipleTrusteeOperation: NO_MULTIPLE_TRUSTEE, 85 | TrusteeForm: TRUSTEE_IS_SID, 86 | TrusteeType: TRUSTEE_IS_GROUP, 87 | ptstrName: 88 | psidEveryone!.assumingMemoryBound(to: CInterop.PlatformChar.self) 89 | ) 90 | 91 | return path.withPlatformString { objectName in 92 | var psidOwner: PSID? = nil 93 | var psidGroup: PSID? = nil 94 | var pDacl: PACL? = nil 95 | var pSD: PSECURITY_DESCRIPTOR? = nil 96 | 97 | XCTAssertEqual(GetNamedSecurityInfoW( 98 | objectName, 99 | SE_FILE_OBJECT, 100 | SECURITY_INFORMATION( 101 | DACL_SECURITY_INFORMATION 102 | | GROUP_SECURITY_INFORMATION 103 | | OWNER_SECURITY_INFORMATION 104 | ), 105 | &psidOwner, 106 | &psidGroup, 107 | &pDacl, 108 | nil, 109 | &pSD), DWORD(ERROR_SUCCESS)) 110 | defer { 111 | LocalFree(pSD) 112 | } 113 | 114 | var owner = TRUSTEE_W( 115 | pMultipleTrustee: nil, 116 | MultipleTrusteeOperation: NO_MULTIPLE_TRUSTEE, 117 | TrusteeForm: TRUSTEE_IS_SID, 118 | TrusteeType: TRUSTEE_IS_USER, 119 | ptstrName: 120 | psidOwner!.assumingMemoryBound(to: CInterop.PlatformChar.self) 121 | ) 122 | var group = TRUSTEE_W( 123 | pMultipleTrustee: nil, 124 | MultipleTrusteeOperation: NO_MULTIPLE_TRUSTEE, 125 | TrusteeForm: TRUSTEE_IS_SID, 126 | TrusteeType: TRUSTEE_IS_GROUP, 127 | ptstrName: 128 | psidGroup!.assumingMemoryBound(to: CInterop.PlatformChar.self) 129 | ) 130 | 131 | var ownerAccess = ACCESS_MASK(0) 132 | var groupAccess = ACCESS_MASK(0) 133 | var otherAccess = ACCESS_MASK(0) 134 | 135 | XCTAssertEqual(GetEffectiveRightsFromAclW( 136 | pDacl, 137 | &owner, 138 | &ownerAccess), DWORD(ERROR_SUCCESS)) 139 | XCTAssertEqual(GetEffectiveRightsFromAclW( 140 | pDacl, 141 | &group, 142 | &groupAccess), DWORD(ERROR_SUCCESS)) 143 | XCTAssertEqual(GetEffectiveRightsFromAclW( 144 | pDacl, 145 | &everyone, 146 | &otherAccess), DWORD(ERROR_SUCCESS)) 147 | 148 | return (ownerAccess, groupAccess, otherAccess) 149 | } 150 | } 151 | 152 | private func runTests(_ tests: [Test], at path: FilePath) throws { 153 | for test in tests { 154 | let octal = String(test.permissions, radix: 8) 155 | let testPath = path.appending("test-\(octal).txt") 156 | let fd = try FileDescriptor.open( 157 | testPath, 158 | .readWrite, 159 | options: [.create, .truncate], 160 | permissions: FilePermissions(rawValue: test.permissions) 161 | ) 162 | _ = try fd.closeAfter { 163 | try fd.writeAll("Hello World".utf8) 164 | } 165 | 166 | let (ownerAccess, groupAccess, otherAccess) 167 | = getAccessMasks(path: testPath) 168 | 169 | XCTAssertEqual(ownerAccess, test.ownerAccess) 170 | XCTAssertEqual(groupAccess, test.groupAccess) 171 | XCTAssertEqual(otherAccess, test.otherAccess) 172 | } 173 | } 174 | 175 | /// Test that the umask works properly 176 | func testUmask() throws { 177 | // See https://learn.microsoft.com/en-us/virtualization/windowscontainers/manage-containers/persistent-storage#permissions 178 | try XCTSkipIf(NSUserName() == "ContainerAdministrator", "containers use a different permission model") 179 | 180 | // Default mask should be 0o022 181 | XCTAssertEqual(FilePermissions.creationMask, [.groupWrite, .otherWrite]) 182 | 183 | try withTemporaryFilePath(basename: "testUmask") { path in 184 | let tests = [ 185 | Test(0o000, 0, 0, 0), 186 | Test(0o700, r|w|x, 0, 0), 187 | Test(0o770, r|w|x, r|x, 0), 188 | Test(0o777, r|w|x, r|x, r|x) 189 | ] 190 | 191 | try runTests(tests, at: path) 192 | } 193 | 194 | try FilePermissions.withCreationMask([.groupWrite, .groupExecute, 195 | .otherWrite, .otherExecute]) { 196 | try withTemporaryFilePath(basename: "testUmask") { path in 197 | let tests = [ 198 | Test(0o000, 0, 0, 0), 199 | Test(0o700, r|w|x, 0, 0), 200 | Test(0o770, r|w|x, r, 0), 201 | Test(0o777, r|w|x, r, r) 202 | ] 203 | 204 | try runTests(tests, at: path) 205 | } 206 | } 207 | } 208 | 209 | /// Test that setting permissions on a file works as expected 210 | func testPermissions() throws { 211 | // See https://learn.microsoft.com/en-us/virtualization/windowscontainers/manage-containers/persistent-storage#permissions 212 | try XCTSkipIf(NSUserName() == "ContainerAdministrator", "containers use a different permission model") 213 | 214 | try FilePermissions.withCreationMask([]) { 215 | try withTemporaryFilePath(basename: "testPermissions") { path in 216 | let tests = [ 217 | Test(0o000, 0, 0, 0), 218 | 219 | Test(0o400, r, 0, 0), 220 | Test(0o200, w, 0, 0), 221 | Test(0o100, x, 0, 0), 222 | Test(0o040, 0, r, 0), 223 | Test(0o020, 0, w, 0), 224 | Test(0o010, 0, x, 0), 225 | Test(0o004, 0, 0, r), 226 | Test(0o002, 0, 0, w), 227 | Test(0o001, 0, 0, x), 228 | 229 | Test(0o700, r|w|x, 0, 0), 230 | Test(0o770, r|w|x, r|w|x, 0), 231 | Test(0o777, r|w|x, r|w|x, r|w|x), 232 | 233 | Test(0o755, r|w|x, r|x, r|x), 234 | Test(0o644, r|w, r, r), 235 | 236 | Test(0o007, 0, 0, r|w|x), 237 | Test(0o070, 0, r|w|x, 0), 238 | Test(0o077, 0, r|w|x, r|w|x), 239 | ] 240 | 241 | try runTests(tests, at: path) 242 | } 243 | } 244 | } 245 | } 246 | 247 | #endif // os(Windows) 248 | -------------------------------------------------------------------------------- /Tests/SystemTests/FilePathTests/FilePathComponentsTest.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift System open source project 3 | 4 | Copyright (c) 2020 Apple Inc. and the Swift System project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | */ 9 | 10 | import XCTest 11 | 12 | #if SYSTEM_PACKAGE 13 | @testable import SystemPackage 14 | #else 15 | @testable import System 16 | #endif 17 | 18 | @available(/*System 0.0.2: macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0*/iOS 8, *) 19 | struct TestPathComponents: TestCase { 20 | var path: FilePath 21 | var expectedRoot: FilePath.Root? 22 | var expectedComponents: [FilePath.Component] 23 | 24 | var pathComponents: Array { Array(path.components) } 25 | 26 | let file: StaticString 27 | let line: UInt 28 | 29 | func failureMessage(_ reason: String?) -> String { 30 | """ 31 | 32 | Fail \(reason ?? "") 33 | path: \(path) 34 | components: \(pathComponents)) 35 | expected: \(expectedComponents) 36 | """ 37 | } 38 | 39 | init( 40 | _ path: FilePath, 41 | root: FilePath.Root?, 42 | _ components: C, 43 | file: StaticString = #file, line: UInt = #line 44 | ) where C.Element == FilePath.Component { 45 | self.path = path 46 | self.expectedRoot = root 47 | self.expectedComponents = Array(components) 48 | self.file = file 49 | self.line = line 50 | } 51 | 52 | func testComponents() { 53 | expectEqual(expectedRoot, path.root) 54 | expectEqualSequence( 55 | expectedComponents, Array(path.components), "testComponents()") 56 | } 57 | 58 | func testBidi() { 59 | expectEqualSequence( 60 | expectedComponents.reversed(), path.components.reversed(), "reversed()") 61 | expectEqualSequence( 62 | path.components, path.components.reversed().reversed(), 63 | "reversed().reversed()") 64 | for i in 0 ..< path.components.count { 65 | expectEqualSequence( 66 | expectedComponents.dropLast(i), path.components.dropLast(i), "dropLast") 67 | expectEqualSequence( 68 | expectedComponents.suffix(i), path.components.suffix(i), "suffix") 69 | } 70 | } 71 | 72 | func testRRC() { 73 | // TODO: programmatic tests showing parity with Array 74 | } 75 | 76 | func testModify() { 77 | if path.root == nil { 78 | let rootedPath = FilePath(root: "/", path.components) 79 | expectNotEqual(rootedPath, path) 80 | var pathCopy = path 81 | expectEqual(path, pathCopy) 82 | pathCopy.components = rootedPath.components 83 | expectNil(pathCopy.root, "components.set doesn't assign root") 84 | expectEqual(path, pathCopy) 85 | } else { 86 | let rootlessPath = FilePath(root: nil, path.components) 87 | var pathCopy = path 88 | expectEqual(path, pathCopy) 89 | pathCopy.components = rootlessPath.components 90 | expectNotNil(pathCopy.root, "components.set preserves root") 91 | expectEqual(path, pathCopy) 92 | } 93 | } 94 | 95 | func runAllTests() { 96 | testComponents() 97 | testBidi() 98 | testRRC() 99 | testModify() 100 | } 101 | } 102 | 103 | @available(/*System 0.0.2: macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0*/iOS 8, *) 104 | final class FilePathComponentsTest: XCTestCase { 105 | func testAdHocRRC() { 106 | var path: FilePath = "/usr/local/bin" 107 | 108 | func expect( 109 | _ s: String, 110 | _ file: StaticString = #file, 111 | _ line: UInt = #line 112 | ) { 113 | if path == FilePath(s) { return } 114 | 115 | defer { print("expected: \(s), actual: \(path)") } 116 | XCTAssert(false, file: file, line: line) 117 | } 118 | 119 | // Run `body`, restoring `path` afterwards 120 | func restoreAfter( 121 | body: () -> () 122 | ) { 123 | let copy = path 124 | defer { path = copy } 125 | body() 126 | } 127 | 128 | // Interior removal keeps path prefix intact, if there is one 129 | restoreAfter { 130 | path = "prefix//middle1/middle2////suffix" 131 | let suffix = path.components.indices.last! 132 | path.components.removeSubrange(.. = [ 235 | TestPathComponents("", root: nil, []), 236 | TestPathComponents("/", root: "/", []), 237 | TestPathComponents("foo", root: nil, ["foo"]), 238 | TestPathComponents("foo/", root: nil, ["foo"]), 239 | TestPathComponents("/foo", root: "/", ["foo"]), 240 | TestPathComponents("foo/bar", root: nil, ["foo", "bar"]), 241 | TestPathComponents("foo/bar/", root: nil, ["foo", "bar"]), 242 | TestPathComponents("/foo/bar", root: "/", ["foo", "bar"]), 243 | TestPathComponents("/foo///bar", root: "/", ["foo", "bar"]), 244 | TestPathComponents("foo/bar/", root: nil, ["foo", "bar"]), 245 | TestPathComponents("foo///bar/baz/", root: nil, ["foo", "bar", "baz"]), 246 | TestPathComponents("./", root: nil, ["."]), 247 | TestPathComponents("./..", root: nil, [".", ".."]), 248 | TestPathComponents("/./..//", root: "/", [".", ".."]), 249 | ] 250 | #if !os(Windows) 251 | testPaths.append(contentsOf:[ 252 | TestPathComponents("///foo//", root: "/", ["foo"]), 253 | TestPathComponents("//foo///bar/baz/", root: "/", ["foo", "bar", "baz"]) 254 | ]) 255 | #else 256 | // On Windows, these are UNC paths 257 | testPaths.append(contentsOf:[ 258 | TestPathComponents("///foo//", root: "///foo//", []), 259 | TestPathComponents("//foo///bar/baz/", root: "//foo//", ["bar", "baz"]) 260 | ]) 261 | #endif 262 | testPaths.forEach { 263 | $0.runAllTests() 264 | } 265 | } 266 | 267 | func testSeparatorNormalization() { 268 | var paths: Array = [ 269 | "/a/b", 270 | "/a/b/", 271 | "/a//b/", 272 | "/a/b//", 273 | "/a/b////", 274 | "/a////b/", 275 | ] 276 | #if !os(Windows) 277 | paths.append("//a/b") 278 | paths.append("///a/b") 279 | paths.append("///a////b") 280 | paths.append("///a////b///") 281 | #endif 282 | 283 | for path in paths { 284 | var path = path 285 | path._normalizeSeparators() 286 | XCTAssertEqual(path, "/a/b") 287 | } 288 | } 289 | } 290 | 291 | // TODO: Test hashValue and equatable for equal components, i.e. make 292 | // sure indices are not part of the hash. 293 | -------------------------------------------------------------------------------- /Tests/SystemTests/FilePathTests/FilePathDecodable.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift System open source project 3 | 4 | Copyright (c)2024 Apple Inc. and the Swift System project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | */ 9 | 10 | import XCTest 11 | 12 | #if SYSTEM_PACKAGE 13 | @testable import SystemPackage 14 | #else 15 | @testable import System 16 | #endif 17 | 18 | @available(/*System 0.0.1: macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0*/iOS 8, *) 19 | final class FilePathDecodableTest: XCTestCase { 20 | func testInvalidFilePath() { 21 | // _storage is a valid SystemString, but the invariants of FilePath are 22 | // violated (specifically, _storage is not normal). 23 | let input: [UInt8] = [ 24 | 123, 34, 95,115,116,111,114, 97,103,101, 34, 58,123, 34,110,117,108,108, 25 | 84,101,114,109,105,110, 97,116,101,100, 83,116,111,114, 97,103,101, 34, 26 | 58, 91, 49, 48, 57, 44, 45, 55, 54, 44, 53, 53, 44, 55, 49, 44, 49, 52, 27 | 44, 53, 57, 44, 45, 49, 49, 50, 44, 45, 56, 52, 44, 52, 50, 44, 45, 55, 28 | 48, 44, 45, 49, 48, 52, 44, 55, 51, 44, 45, 54, 44, 50, 44, 53, 55, 44, 29 | 54, 50, 44, 45, 56, 55, 44, 45, 53, 44, 45, 54, 53, 44, 45, 51, 57, 44, 30 | 45, 49, 48, 57, 44, 45, 55, 54, 44, 51, 48, 44, 53, 50, 44, 45, 56, 50, 31 | 44, 45, 54, 48, 44, 45, 50, 44, 56, 53, 44, 49, 50, 51, 44, 45, 56, 52, 32 | 44, 45, 53, 56, 44, 49, 49, 52, 44, 49, 44, 45, 49, 49, 54, 44, 56, 48, 33 | 44, 49, 48, 52, 44, 45, 55, 56, 44, 45, 52, 53, 44, 49, 54, 44, 45, 52, 34 | 54, 44, 55, 44, 49, 49, 56, 44, 45, 50, 52, 44, 54, 50, 44, 54, 52, 44, 35 | 45, 52, 49, 44, 45, 49, 48, 51, 44, 53, 44, 45, 55, 53, 44, 50, 50, 44, 36 | 45, 49, 48, 53, 44, 45, 49, 54, 44, 52, 55, 44, 52, 55, 44, 49, 50, 52, 37 | 44, 45, 53, 55, 44, 53, 51, 44, 49, 49, 49, 44, 49, 53, 44, 45, 50, 55, 38 | 44, 54, 54, 44, 45, 49, 54, 44, 49, 48, 50, 44, 49, 48, 54, 44, 49, 51, 39 | 44, 49, 48, 53, 44, 45, 49, 49, 50, 44, 55, 56, 44, 45, 53, 48, 44, 50, 40 | 48, 44, 56, 44, 45, 50, 55, 44, 52, 52, 44, 52, 44, 56, 44, 54, 53, 44, 41 | 50, 51, 44, 57, 55, 44, 45, 50, 56, 44, 56, 56, 44, 52, 50, 44, 45, 51, 42 | 54, 44, 45, 50, 51, 44, 49, 48, 51, 44, 57, 57, 44, 45, 53, 56, 44, 45, 43 | 49, 49, 48, 44, 45, 53, 52, 44, 45, 49, 49, 55, 44, 45, 57, 52, 44, 45, 44 | 55, 50, 44, 50, 57, 44, 45, 50, 52, 44, 45, 56, 52, 44, 53, 55, 44, 45, 45 | 49, 50, 54, 44, 52, 52, 44, 55, 53, 44, 55, 54, 44, 52, 57, 44, 45, 52, 46 | 49, 44, 45, 50, 53, 44, 50, 52, 44, 45, 49, 50, 54, 44, 55, 44, 50, 56, 47 | 44, 45, 52, 56, 44, 56, 55, 44, 51, 49, 44, 45, 49, 49, 53, 44, 55, 44, 48 | 45, 54, 48, 44, 53, 57, 44, 49, 51, 44, 55, 57, 44, 53, 48, 44, 45, 57, 49 | 54, 44, 45, 50, 44, 45, 50, 52, 44, 45, 57, 49, 44, 55, 49, 44, 45, 49, 50 | 50, 53, 44, 52, 50, 44, 45, 56, 52, 44, 52, 44, 53, 57, 44, 49, 50, 53, 51 | 44, 49, 50, 49, 44, 45, 50, 54, 44, 45, 49, 50, 44, 45, 49, 48, 53, 44, 52 | 53, 54, 44, 49, 49, 48, 44, 49, 52, 44, 45, 49, 48, 52, 44, 45, 53, 50, 53 | 44, 45, 53, 56, 44, 45, 54, 44, 45, 50, 54, 44, 45, 52, 55, 44, 53, 57, 54 | 44, 52, 50, 44, 49, 50, 51, 44, 52, 52, 44, 45, 57, 50, 44, 45, 50, 57, 55 | 44, 45, 51, 54, 44, 45, 54, 50, 44, 50, 54, 44, 45, 49, 55, 44, 45, 49, 56 | 48, 44, 45, 56, 49, 44, 54, 49, 44, 52, 55, 44, 45, 57, 52, 44, 45, 49, 57 | 48, 54, 44, 49, 53, 44, 49, 48, 48, 44, 45, 49, 50, 49, 44, 45, 49, 49, 58 | 49, 44, 51, 44, 45, 57, 44, 52, 54, 44, 45, 55, 48, 44, 45, 49, 57, 44, 59 | 52, 56, 44, 45, 49, 50, 44, 45, 57, 49, 44, 45, 50, 48, 44, 49, 51, 44, 60 | 54, 53, 44, 45, 55, 48, 44, 52, 49, 44, 45, 57, 53, 44, 49, 48, 52, 44, 61 | 45, 55, 53, 44, 45, 49, 49, 53, 44, 49, 48, 49, 44, 45, 57, 52, 44, 45, 62 | 49, 50, 51, 44, 45, 51, 53, 44, 45, 50, 49, 44, 45, 52, 50, 44, 45, 51, 63 | 48, 44, 45, 55, 49, 44, 45, 49, 49, 57, 44, 52, 52, 44, 49, 49, 49, 44, 64 | 49, 48, 53, 44, 54, 54, 44, 45, 49, 50, 54, 44, 55, 50, 44, 45, 52, 48, 65 | 44, 49, 50, 49, 44, 45, 50, 49, 44, 52, 50, 44, 45, 55, 56, 44, 49, 50, 66 | 54, 44, 56, 49, 44, 45, 57, 52, 44, 55, 52, 44, 49, 49, 50, 44, 45, 56, 67 | 54, 44, 51, 50, 44, 55, 54, 44, 49, 49, 55, 44, 45, 56, 44, 56, 54, 44, 68 | 49, 48, 51, 44, 54, 50, 44, 49, 49, 55, 44, 54, 55, 44, 45, 56, 54, 44, 69 | 45, 49, 48, 48, 44, 45, 49, 48, 57, 44, 45, 53, 52, 44, 45, 51, 49, 44, 70 | 45, 56, 57, 44, 48, 93,125,125, 71 | ] 72 | 73 | XCTAssertThrowsError(try JSONDecoder().decode( 74 | FilePath.self, 75 | from: Data(input) 76 | )) 77 | } 78 | 79 | func testInvalidSystemString() { 80 | // _storage is a SystemString whose invariants are violated; it contains 81 | // a non-terminating null byte. 82 | let input: [UInt8] = [ 83 | 123, 34, 95,115,116,111,114, 97,103,101, 34, 58,123, 34,110,117,108,108, 84 | 84,101,114,109,105,110, 97,116,101,100, 83,116,111,114, 97,103,101, 34, 85 | 58, 91, 49, 49, 49, 44, 48, 44, 45, 49, 54, 44, 57, 49, 44, 52, 54, 44, 86 | 45, 49, 48, 50, 44, 49, 49, 53, 44, 45, 50, 49, 44, 45, 49, 49, 56, 44, 87 | 52, 57, 44, 57, 50, 44, 45, 49, 48, 44, 53, 56, 44, 45, 55, 48, 44, 57, 88 | 55, 44, 56, 44, 57, 57, 44, 48, 93,125, 125 89 | ] 90 | 91 | XCTAssertThrowsError(try JSONDecoder().decode( 92 | FilePath.self, 93 | from: Data(input) 94 | )) 95 | } 96 | 97 | func testInvalidExample() { 98 | // Another misformed example from Johannes that violates FilePath's 99 | // invariants by virtue of not being normalized. 100 | let input: [UInt8] = [ 101 | 123, 34, 95,115,116,111,114, 97,103,101, 34, 58,123, 34,110,117,108,108, 102 | 84,101,114,109,105,110, 97,116,101,100, 83,116,111,114, 97,103,101, 34, 103 | 58, 91, 56, 55, 44, 50, 52, 44, 45, 49, 49, 53, 44, 45, 49, 57, 44, 49, 104 | 50, 50, 44, 45, 54, 56, 44, 57, 49, 44, 45, 49, 48, 54, 44, 45, 49, 48, 105 | 48, 44, 45, 49, 49, 52, 44, 53, 54, 44, 45, 54, 53, 44, 49, 49, 56, 44, 106 | 45, 54, 48, 44, 54, 54, 44, 45, 52, 50, 44, 55, 55, 44, 45, 54, 44, 45, 107 | 52, 50, 44, 45, 56, 56, 44, 52, 55, 44, 48, 93,125, 125 108 | ] 109 | 110 | XCTAssertThrowsError(try JSONDecoder().decode( 111 | FilePath.self, 112 | from: Data(input) 113 | )) 114 | } 115 | 116 | func testEmptyString() { 117 | // FilePath with an empty (and hence not null-terminated) SystemString. 118 | let input: [UInt8] = [ 119 | 123, 34, 95,115,116,111,114, 97,103,101, 34, 58,123, 34,110,117,108,108, 120 | 84,101,114,109,105,110, 97,116,101,100, 83,116,111,114, 97,103,101, 34, 121 | 58, 91, 93,125,125 122 | ] 123 | 124 | XCTAssertThrowsError(try JSONDecoder().decode( 125 | FilePath.self, 126 | from: Data(input) 127 | )) 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /Tests/SystemTests/FilePathTests/FilePathExtras.swift: -------------------------------------------------------------------------------- 1 | 2 | #if SYSTEM_PACKAGE 3 | @testable import SystemPackage 4 | #else 5 | @testable import System 6 | #endif 7 | 8 | // Why can't I write this extension on `FilePath.ComponentView.SubSequence`? 9 | @available(/*System 0.0.2: macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0*/iOS 8, *) 10 | extension Slice where Base == FilePath.ComponentView { 11 | internal var _storageSlice: SystemString.SubSequence { 12 | base._path._storage[self.startIndex._storage ..< self.endIndex._storage] 13 | } 14 | } 15 | 16 | 17 | // Proposed API that didn't make the cut, but we stil want to keep our testing for 18 | @available(/*System 0.0.2: macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0*/iOS 8, *) 19 | extension FilePath { 20 | /// Returns `self` relative to `base`. 21 | /// This does not cosult the file system or resolve symlinks. 22 | /// 23 | /// Returns `nil` if `self.root != base.root`. 24 | /// 25 | /// On Windows, if any component of either path could be interpreted as the root of 26 | /// a traditional DOS path (e.g. a directory named `C:`), returns `nil`. 27 | /// 28 | /// Example: 29 | /// 30 | /// let path: FilePath = "/usr/local/bin" 31 | /// path.lexicallyRelative(toBase: "/usr/local") == "bin" 32 | /// path.lexicallyRelative(toBase: "/usr/local/bin/ls") == ".." 33 | /// path.lexicallyRelative(toBase: "/tmp/foo.txt") == "../../usr/local/bin" 34 | /// path.lexicallyRelative(toBase: "local/bin") == nil 35 | internal func lexicallyRelative(toBase base: FilePath) -> FilePath? { 36 | guard root == base.root else { return nil } 37 | 38 | // FIXME: On Windows, return nil if any component looks like a root 39 | 40 | let (tail, baseTail) = _dropCommonPrefix(components, base.components) 41 | 42 | var prefix = SystemString() 43 | for _ in 0.. Bool { 55 | guard !other.isEmpty else { return true } 56 | guard !isEmpty else { return false } 57 | 58 | let (selfLex, otherLex) = 59 | (self.lexicallyNormalized(), other.lexicallyNormalized()) 60 | if otherLex.isAbsolute { return selfLex.starts(with: otherLex) } 61 | 62 | // FIXME: Windows semantics with relative roots? 63 | 64 | // TODO: better than this naive algorithm 65 | var slice = selfLex.components[...] 66 | while !slice.isEmpty { 67 | if slice.starts(with: otherLex.components) { return true } 68 | slice = slice.dropFirst() 69 | } 70 | return false 71 | } 72 | } 73 | 74 | extension Collection where Element: Equatable, SubSequence == Slice { 75 | // Mock up RangeSet functionality until it's real 76 | func indices(where p: (Element) throws -> Bool) rethrows -> [Range] { 77 | var result = Array>() 78 | guard !isEmpty else { return result } 79 | 80 | var i = startIndex 81 | while i != endIndex { 82 | let next = index(after: i) 83 | if try p(self[i]) { 84 | result.append(i..]) { 94 | guard !subranges.isEmpty else { return } 95 | 96 | var result = Self() 97 | var idx = startIndex 98 | for range in subranges { 99 | result.append(contentsOf: self[idx.. ParsingTestCase { 39 | ParsingTestCase( 40 | isWindows: false, 41 | pathStr: path, normalized: normalized, 42 | file: file, line: line) 43 | } 44 | 45 | static func windows( 46 | _ path: String, normalized: String, 47 | file: StaticString = #file, line: UInt = #line 48 | ) -> ParsingTestCase { 49 | ParsingTestCase( 50 | isWindows: true, 51 | pathStr: path, normalized: normalized, 52 | file: file, line: line) 53 | } 54 | } 55 | 56 | extension ParsingTestCase { 57 | func runAllTests() { 58 | withWindowsPaths(enabled: isWindows) { 59 | let path = FilePath(pathStr) 60 | expectEqual(normalized, path.description) 61 | } 62 | } 63 | } 64 | 65 | #if SYSTEM_PACKAGE 66 | @testable import SystemPackage 67 | #else 68 | @testable import System 69 | #endif 70 | 71 | @available(/*System 0.0.2: macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0*/iOS 8, *) 72 | final class FilePathParsingTest: XCTestCase { 73 | func testNormalization() { 74 | let unixPaths: Array = [ 75 | .unix("/", normalized: "/"), 76 | .unix("", normalized: ""), 77 | .unix("//", normalized: "/"), 78 | .unix("///", normalized: "/"), 79 | .unix("/foo/bar/", normalized: "/foo/bar"), 80 | .unix("foo//bar", normalized: "foo/bar"), 81 | .unix("//foo/bar//baz/", normalized: "/foo/bar/baz"), 82 | .unix("/foo/bar/baz//", normalized: "/foo/bar/baz"), 83 | .unix("/foo/bar/baz///", normalized: "/foo/bar/baz"), 84 | ] 85 | 86 | let windowsPaths: Array = [ 87 | .windows(#"C:\\folder\file\"#, normalized: #"C:\folder\file"#), 88 | .windows(#"C:folder\\\file\\\"#, normalized: #"C:folder\file"#), 89 | .windows(#"C:/foo//bar/"#, normalized: #"C:\foo\bar"#), 90 | 91 | .windows(#"\\server\share\"#, normalized: #"\\server\share\"#), 92 | .windows(#"//server/share/"#, normalized: #"\\server\share\"#), 93 | .windows(#"\\?\UNC/server\share\"#, normalized: #"\\?\UNC\server\share\"#), 94 | 95 | .windows(#"\\.\C:\"#, normalized: #"\\.\C:\"#), 96 | .windows(#"C:\"#, normalized: #"C:\"#), 97 | .windows(#"\"#, normalized: #"\"#), 98 | ] 99 | 100 | for test in unixPaths { 101 | test.runAllTests() 102 | } 103 | for test in windowsPaths { 104 | test.runAllTests() 105 | } 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /Tests/SystemTests/FilePathTests/FilePathTempTest.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift System open source project 3 | 4 | Copyright (c) 2024 Apple Inc. and the Swift System project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | */ 9 | 10 | import XCTest 11 | 12 | #if SYSTEM_PACKAGE 13 | @testable import SystemPackage 14 | #else 15 | @testable import System 16 | #endif 17 | 18 | final class TemporaryPathTest: XCTestCase { 19 | #if SYSTEM_PACKAGE_DARWIN 20 | func testNotInSlashTmp() throws { 21 | try withTemporaryFilePath(basename: "NotInSlashTmp") { path in 22 | // We shouldn't be using "/tmp" on Darwin 23 | XCTAssertNotEqual(path.components.first!, "tmp") 24 | } 25 | } 26 | #endif 27 | 28 | func testUnique() throws { 29 | try withTemporaryFilePath(basename: "test") { path in 30 | let strPath = String(decoding: path) 31 | XCTAssert(strPath.contains("test")) 32 | try withTemporaryFilePath(basename: "test") { path2 in 33 | let strPath2 = String(decoding: path2) 34 | XCTAssertNotEqual(strPath, strPath2) 35 | } 36 | } 37 | } 38 | 39 | func testCleanup() throws { 40 | var thePath: FilePath? = nil 41 | 42 | try withTemporaryFilePath(basename: "test") { path in 43 | thePath = path.appending("foo.txt") 44 | let fd = try FileDescriptor.open(thePath!, .readWrite, 45 | options: [.create, .truncate], 46 | permissions: .ownerReadWrite) 47 | _ = try fd.closeAfter { 48 | try fd.writeAll("Hello World".utf8) 49 | } 50 | } 51 | 52 | XCTAssertThrowsError(try FileDescriptor.open(thePath!, .readOnly)) { 53 | error in 54 | 55 | XCTAssertEqual(error as! Errno, Errno.noSuchFileOrDirectory) 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /Tests/SystemTests/FilePathTests/FilePathTest.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift System open source project 3 | 4 | Copyright (c) 2020 Apple Inc. and the Swift System project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | */ 9 | 10 | import XCTest 11 | 12 | #if SYSTEM_PACKAGE 13 | import SystemPackage 14 | #else 15 | import System 16 | #endif 17 | 18 | @available(/*System 0.0.1: macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0*/iOS 8, *) 19 | func filePathFromInvalidCodePointSequence(_ bytes: S) -> FilePath where S.Element == CInterop.PlatformUnicodeEncoding.CodeUnit { 20 | var array = Array(bytes) 21 | assert(array.last != 0, "already null terminated") 22 | array += [0] 23 | 24 | return array.withUnsafeBufferPointer { 25 | $0.withMemoryRebound(to: CInterop.PlatformChar.self) { 26 | FilePath(platformString: $0.baseAddress!) 27 | } 28 | } 29 | } 30 | 31 | @available(/*System 0.0.2: macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0*/iOS 8, *) 32 | final class FilePathTest: XCTestCase { 33 | struct TestPath { 34 | let filePath: FilePath 35 | let string: String 36 | let validString: Bool 37 | 38 | init(filePath: FilePath, string: String, validString: Bool) { 39 | self.filePath = filePath 40 | #if os(Windows) 41 | self.string = string.replacingOccurrences(of: "/", with: "\\") 42 | #else 43 | self.string = string 44 | #endif 45 | self.validString = validString 46 | } 47 | } 48 | 49 | #if os(Windows) 50 | static let invalidSequence: [UTF16.CodeUnit] = [0xd800, 0x0020] 51 | static let invalidSequenceTest = 52 | TestPath(filePath: filePathFromInvalidCodePointSequence(invalidSequence), 53 | string: String(decoding: invalidSequence, as: UTF16.self), 54 | validString: false) 55 | #else 56 | static let invalidSequence: [UTF8.CodeUnit] = [0x2F, 0x61, 0x2F, 0x62, 0x2F, 0x83] 57 | static let invalidSequenceTest = 58 | TestPath(filePath: filePathFromInvalidCodePointSequence(invalidSequence), 59 | string: String(decoding: invalidSequence, as: UTF8.self), 60 | validString: false) 61 | #endif 62 | 63 | var testPaths: [TestPath] = [ 64 | // empty 65 | TestPath(filePath: FilePath(), string: String(), validString: true), 66 | 67 | // valid ascii 68 | TestPath(filePath: "/a/b/c", string: "/a/b/c", validString: true), 69 | 70 | // valid utf8 71 | TestPath(filePath: "/あ/🧟‍♀️", string: "/あ/🧟‍♀️", validString: true), 72 | 73 | // invalid sequence 74 | invalidSequenceTest, 75 | ] 76 | 77 | func testFilePath() { 78 | 79 | XCTAssertEqual(0, FilePath().length) 80 | 81 | for testPath in testPaths { 82 | 83 | XCTAssertEqual(testPath.string, String(decoding: testPath.filePath)) 84 | 85 | // TODO: test component CodeUnit representation validation 86 | if testPath.validString { 87 | XCTAssertEqual(testPath.filePath, FilePath(testPath.string)) 88 | XCTAssertEqual(testPath.string, String(validating: testPath.filePath)) 89 | } else { 90 | XCTAssertNotEqual(testPath.filePath, FilePath(testPath.string)) 91 | XCTAssertNil(String(validating: testPath.filePath)) 92 | } 93 | 94 | testPath.filePath.withPlatformString { 95 | #if os(Windows) 96 | XCTAssertEqual(testPath.string, String(decodingCString: $0, as: UTF16.self)) 97 | #else 98 | XCTAssertEqual(testPath.string, String(cString: $0)) 99 | #endif 100 | XCTAssertEqual(testPath.filePath, FilePath(platformString: $0)) 101 | } 102 | } 103 | } 104 | } 105 | 106 | -------------------------------------------------------------------------------- /Tests/SystemTests/FileTypesTest.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift System open source project 3 | 4 | Copyright (c) 2020 - 2021 Apple Inc. and the Swift System project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | */ 9 | 10 | import XCTest 11 | 12 | #if SYSTEM_PACKAGE 13 | import SystemPackage 14 | #else 15 | import System 16 | #endif 17 | #if canImport(Android) 18 | import Android 19 | #endif 20 | 21 | @available(/*System 0.0.1: macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0*/iOS 8, *) 22 | final class FileDescriptorTest: XCTestCase { 23 | func testStandardDescriptors() { 24 | XCTAssertEqual(FileDescriptor.standardInput.rawValue, 0) 25 | XCTAssertEqual(FileDescriptor.standardOutput.rawValue, 1) 26 | XCTAssertEqual(FileDescriptor.standardError.rawValue, 2) 27 | } 28 | 29 | // Test the constants match the C header values. For various reasons, 30 | func testConstants() { 31 | XCTAssertEqual(O_RDONLY, FileDescriptor.AccessMode.readOnly.rawValue) 32 | XCTAssertEqual(O_WRONLY, FileDescriptor.AccessMode.writeOnly.rawValue) 33 | XCTAssertEqual(O_RDWR, FileDescriptor.AccessMode.readWrite.rawValue) 34 | 35 | #if !os(Windows) 36 | XCTAssertEqual(O_NONBLOCK, FileDescriptor.OpenOptions.nonBlocking.rawValue) 37 | #endif 38 | XCTAssertEqual(O_APPEND, FileDescriptor.OpenOptions.append.rawValue) 39 | XCTAssertEqual(O_CREAT, FileDescriptor.OpenOptions.create.rawValue) 40 | XCTAssertEqual(O_TRUNC, FileDescriptor.OpenOptions.truncate.rawValue) 41 | XCTAssertEqual(O_EXCL, FileDescriptor.OpenOptions.exclusiveCreate.rawValue) 42 | #if !os(Windows) 43 | XCTAssertEqual(O_NOFOLLOW, FileDescriptor.OpenOptions.noFollow.rawValue) 44 | XCTAssertEqual(O_CLOEXEC, FileDescriptor.OpenOptions.closeOnExec.rawValue) 45 | #endif 46 | 47 | // BSD only 48 | #if SYSTEM_PACKAGE_DARWIN || os(FreeBSD) 49 | XCTAssertEqual(O_SHLOCK, FileDescriptor.OpenOptions.sharedLock.rawValue) 50 | XCTAssertEqual(O_EXLOCK, FileDescriptor.OpenOptions.exclusiveLock.rawValue) 51 | #endif 52 | 53 | #if SYSTEM_PACKAGE_DARWIN 54 | XCTAssertEqual(O_SYMLINK, FileDescriptor.OpenOptions.symlink.rawValue) 55 | XCTAssertEqual(O_EVTONLY, FileDescriptor.OpenOptions.eventOnly.rawValue) 56 | #endif 57 | 58 | #if os(FreeBSD) 59 | XCTAssertEqual(O_SYNC, FileDescriptor.OpenOptions.sync.rawValue) 60 | #endif 61 | 62 | XCTAssertEqual(SEEK_SET, FileDescriptor.SeekOrigin.start.rawValue) 63 | XCTAssertEqual(SEEK_CUR, FileDescriptor.SeekOrigin.current.rawValue) 64 | XCTAssertEqual(SEEK_END, FileDescriptor.SeekOrigin.end.rawValue) 65 | 66 | #if SYSTEM_PACKAGE_DARWIN || os(FreeBSD) 67 | XCTAssertEqual(SEEK_HOLE, FileDescriptor.SeekOrigin.nextHole.rawValue) 68 | XCTAssertEqual(SEEK_DATA, FileDescriptor.SeekOrigin.nextData.rawValue) 69 | #endif 70 | } 71 | 72 | // TODO: test string conversion 73 | // TODO: test option set string conversion 74 | 75 | } 76 | 77 | @available(/*System 0.0.1: macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0*/iOS 8, *) 78 | final class FilePermissionsTest: XCTestCase { 79 | 80 | func testPermissions() { 81 | // TODO: exhaustive tests 82 | 83 | XCTAssert(FilePermissions(rawValue: 0o664) == [.ownerReadWrite, .groupReadWrite, .otherRead]) 84 | XCTAssert(FilePermissions(rawValue: 0o644) == [.ownerReadWrite, .groupRead, .otherRead]) 85 | XCTAssert(FilePermissions(rawValue: 0o777) == [.otherReadWriteExecute, .groupReadWriteExecute, .ownerReadWriteExecute]) 86 | 87 | // From the docs for FilePermissions 88 | do { 89 | let perms = FilePermissions(rawValue: 0o644) 90 | XCTAssert(perms == [.ownerReadWrite, .groupRead, .otherRead]) 91 | XCTAssert(perms.contains(.ownerRead)) 92 | } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /Tests/SystemTests/MachPortTests.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift System open source project 3 | 4 | Copyright (c) 2022 Apple Inc. and the Swift System project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | */ 9 | 10 | #if swift(>=5.9) && SYSTEM_PACKAGE_DARWIN 11 | 12 | import XCTest 13 | import Darwin.Mach 14 | 15 | #if SYSTEM_PACKAGE 16 | import SystemPackage 17 | #else 18 | import System 19 | #endif 20 | 21 | @available(/*System 1.4.0: macOS 9999, iOS 9999, watchOS 9999, tvOS 9999*/iOS 8, *) 22 | final class MachPortTests: XCTestCase { 23 | func refCountForMachPortName(name:mach_port_name_t, kind:mach_port_right_t) -> mach_port_urefs_t { 24 | var refCount:mach_port_urefs_t = .max 25 | let kr = mach_port_get_refs(mach_task_self_, name, kind, &refCount) 26 | if kr == KERN_INVALID_NAME { 27 | refCount = 0 28 | } else { 29 | XCTAssertEqual(kr, KERN_SUCCESS) 30 | } 31 | return refCount 32 | } 33 | 34 | func scopedReceiveRight(name:mach_port_name_t) -> mach_port_urefs_t { 35 | let right = Mach.Port(name:name) // this should automatically deallocate when going out of scope 36 | defer { _ = right } 37 | return refCountForMachPortName(name:name, kind:MACH_PORT_RIGHT_RECEIVE) 38 | } 39 | 40 | func testReceiveRightDeallocation() throws { 41 | var name: mach_port_name_t = 0xFFFFFFFF 42 | let kr = mach_port_allocate(mach_task_self_, MACH_PORT_RIGHT_RECEIVE, &name) 43 | XCTAssertEqual(kr, KERN_SUCCESS) 44 | 45 | XCTAssertNotEqual(name, 0xFFFFFFFF) 46 | 47 | let originalCount = refCountForMachPortName(name: name, kind: MACH_PORT_RIGHT_RECEIVE) 48 | XCTAssertEqual(originalCount, 1) 49 | 50 | let incrementedCount = scopedReceiveRight(name:name) 51 | XCTAssertEqual(incrementedCount, 1); 52 | 53 | let deallocated = refCountForMachPortName(name:name, kind: MACH_PORT_RIGHT_RECEIVE) 54 | XCTAssertEqual(deallocated, 0); 55 | } 56 | 57 | func consumeSendRightAutomatically(name:mach_port_name_t) -> mach_port_urefs_t { 58 | let send = Mach.Port(name:name) // this should automatically deallocate when going out of scope 59 | return send.withBorrowedName { name in 60 | // Get the ref count before automatic deallocation happens 61 | return refCountForMachPortName(name:name, kind:MACH_PORT_RIGHT_SEND) 62 | } 63 | } 64 | 65 | func testSendRightDeallocation() throws { 66 | let recv = Mach.Port() 67 | recv.withBorrowedName { name in 68 | let kr = mach_port_insert_right(mach_task_self_, name, name, mach_msg_type_name_t(MACH_MSG_TYPE_MAKE_SEND)) 69 | XCTAssertEqual(kr, KERN_SUCCESS) 70 | let one = consumeSendRightAutomatically(name:name) 71 | XCTAssertEqual(one, 1); 72 | let zero = refCountForMachPortName(name:name, kind:MACH_PORT_RIGHT_SEND) 73 | XCTAssertEqual(zero, 0); 74 | } 75 | } 76 | 77 | func testSendRightRelinquishment() throws { 78 | let recv = Mach.Port() 79 | 80 | let name = ({ 81 | let send = recv.makeSendRight() 82 | let one = send.withBorrowedName { name in 83 | return self.refCountForMachPortName(name:name, kind:MACH_PORT_RIGHT_SEND) 84 | } 85 | XCTAssertEqual(one, 1) 86 | 87 | return send.relinquish() 88 | })() 89 | 90 | let stillOne = refCountForMachPortName(name:name, kind:MACH_PORT_RIGHT_SEND) 91 | XCTAssertEqual(stillOne, 1) 92 | 93 | recv.withBorrowedName { 94 | let alsoOne = refCountForMachPortName(name: $0, kind: MACH_PORT_RIGHT_RECEIVE) 95 | XCTAssertEqual(alsoOne, 1) 96 | } 97 | } 98 | 99 | func testSendOnceRightRelinquishment() throws { 100 | let recv = Mach.Port() 101 | 102 | let name = ({ 103 | let send = recv.makeSendOnceRight() 104 | let one = send.withBorrowedName { name in 105 | return self.refCountForMachPortName(name: name, kind: MACH_PORT_RIGHT_SEND_ONCE) 106 | } 107 | XCTAssertEqual(one, 1) 108 | 109 | return send.relinquish() 110 | })() 111 | 112 | let stillOne = refCountForMachPortName(name: name, kind: MACH_PORT_RIGHT_SEND_ONCE) 113 | XCTAssertEqual(stillOne, 1) 114 | 115 | recv.withBorrowedName { 116 | let alsoOne = refCountForMachPortName(name: $0, kind: MACH_PORT_RIGHT_RECEIVE) 117 | XCTAssertEqual(alsoOne, 1) 118 | } 119 | } 120 | 121 | func testReceiveRightRelinquishment() throws { 122 | let recv = Mach.Port() 123 | 124 | let one = recv.withBorrowedName { 125 | self.refCountForMachPortName(name: $0, kind: MACH_PORT_RIGHT_RECEIVE) 126 | } 127 | XCTAssertEqual(one, 1) 128 | 129 | let name = recv.unguardAndRelinquish() 130 | 131 | let stillOne = refCountForMachPortName(name: name, kind: MACH_PORT_RIGHT_RECEIVE) 132 | XCTAssertEqual(stillOne, 1) 133 | } 134 | 135 | func testMakeSendCountSettable() throws { 136 | var recv = Mach.Port() 137 | XCTAssertEqual(recv.makeSendCount, 0) 138 | recv.makeSendCount = 7 139 | XCTAssertEqual(recv.makeSendCount, 7) 140 | } 141 | 142 | func makeSendRight() throws -> Mach.Port { 143 | let recv = Mach.Port() 144 | let zero = recv.makeSendCount 145 | XCTAssertEqual(zero, 0) 146 | let send = recv.makeSendRight() 147 | let one = recv.makeSendCount 148 | XCTAssertEqual(one, 1) 149 | return send 150 | } 151 | 152 | func testMakeSendCountIncrement() throws { 153 | _ = try makeSendRight() 154 | } 155 | 156 | func testMakeSendOnceDoesntIncrementMakeSendCount() throws { 157 | let recv = Mach.Port() 158 | let zero = recv.makeSendCount 159 | XCTAssertEqual(zero, 0) 160 | _ = recv.makeSendOnceRight() 161 | let same = recv.makeSendCount 162 | XCTAssertEqual(same, zero) 163 | } 164 | 165 | func testMakeSendOnceIsUnique() throws { 166 | let recv = Mach.Port() 167 | let once = recv.makeSendOnceRight() 168 | recv.withBorrowedName { rname in 169 | once.withBorrowedName { oname in 170 | XCTAssertNotEqual(oname, rname) 171 | } 172 | } 173 | } 174 | 175 | func testCopySend() throws { 176 | let recv = Mach.Port() 177 | let zero = recv.makeSendCount 178 | XCTAssertEqual(zero, 0) 179 | let send = recv.makeSendRight() 180 | let one = recv.makeSendCount 181 | XCTAssertEqual(one, 1) 182 | _ = try send.copySendRight() 183 | let same = recv.makeSendCount 184 | XCTAssertEqual(same, one) 185 | 186 | } 187 | 188 | func testCopyDeadName() throws { 189 | let recv = Mach.Port() 190 | let send = recv.makeSendRight() 191 | _ = consume recv // and turn `send` into a dead name 192 | XCTAssertThrowsError( 193 | _ = try send.copySendRight(), 194 | "Copying a dead name should throw" 195 | ) { error in 196 | XCTAssertEqual( 197 | error as! Mach.PortRightError, Mach.PortRightError.deadName 198 | ) 199 | } 200 | } 201 | 202 | func testCopyDeadName2() throws { 203 | let send = Mach.Port(name: 0xffffffff) 204 | XCTAssertThrowsError( 205 | _ = try send.copySendRight(), 206 | "Copying a dead name should throw" 207 | ) { error in 208 | XCTAssertEqual( 209 | error as! Mach.PortRightError, Mach.PortRightError.deadName 210 | ) 211 | } 212 | } 213 | 214 | func testMakeReceiveRightFromExistingName() throws { 215 | var name = mach_port_name_t(MACH_PORT_NULL) 216 | var kr = mach_port_allocate(mach_task_self_, MACH_PORT_RIGHT_RECEIVE, &name) 217 | XCTAssertEqual(kr, KERN_SUCCESS) 218 | XCTAssertNotEqual(name, mach_port_name_t(MACH_PORT_NULL)) 219 | let context = mach_port_context_t(arc4random()) 220 | kr = mach_port_guard(mach_task_self_, name, context, 0) 221 | XCTAssertEqual(kr, KERN_SUCCESS) 222 | 223 | let right = Mach.Port(name: name, context: context) 224 | right.withBorrowedName { 225 | XCTAssertEqual(name, $0) 226 | XCTAssertEqual(context, $1) 227 | } 228 | } 229 | 230 | func testDeinitDeadSendRights() throws { 231 | let recv = Mach.Port() 232 | let send = recv.makeSendRight() 233 | let send1 = recv.makeSendOnceRight() 234 | 235 | _ = consume recv 236 | // `send` and `send1` have become dead names 237 | _ = consume send 238 | _ = consume send1 239 | } 240 | } 241 | 242 | #endif 243 | -------------------------------------------------------------------------------- /Tests/SystemTests/MockingTest.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift System open source project 3 | 4 | Copyright (c) 2020 Apple Inc. and the Swift System project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | */ 9 | 10 | import XCTest 11 | 12 | #if SYSTEM_PACKAGE 13 | @testable import SystemPackage 14 | #else 15 | @testable import System 16 | #endif 17 | 18 | @available(/*System 0.0.2: macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0*/iOS 8, *) 19 | final class MockingTest: XCTestCase { 20 | func testMocking() { 21 | XCTAssertFalse(mockingEnabled) 22 | MockingDriver.withMockingEnabled { driver in 23 | XCTAssertTrue(mockingEnabled) 24 | XCTAssertTrue(driver === currentMockingDriver) 25 | 26 | XCTAssertEqual(driver.forceErrno, .none) 27 | let forced = ForceErrno.always(errno: 42) 28 | driver.forceErrno = forced 29 | XCTAssertEqual(driver.forceErrno, forced) 30 | 31 | // Test that a nested call swaps in a new driver and restores the old one after 32 | MockingDriver.withMockingEnabled { nestedDriver in 33 | XCTAssertTrue(mockingEnabled) 34 | XCTAssertTrue(nestedDriver === currentMockingDriver) 35 | XCTAssertFalse(nestedDriver === driver) 36 | XCTAssertEqual(nestedDriver.forceErrno, .none) 37 | } 38 | 39 | XCTAssertTrue(mockingEnabled) 40 | XCTAssertEqual(driver.forceErrno, forced) 41 | } 42 | XCTAssertFalse(mockingEnabled) 43 | 44 | // Mocking should be enabled even if we do not refer to the driver 45 | MockingDriver.withMockingEnabled { _ in 46 | XCTAssertTrue(mockingEnabled) 47 | } 48 | XCTAssertFalse(mockingEnabled) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /Tests/SystemTests/SystemCharTest.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift System open source project 3 | 4 | Copyright (c) 2020 - 2021 Apple Inc. and the Swift System project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | */ 9 | 10 | import XCTest 11 | 12 | #if SYSTEM_PACKAGE 13 | @testable import SystemPackage 14 | #else 15 | @testable import System 16 | #endif 17 | 18 | final class SystemCharTest: XCTestCase { 19 | func testIsLetter() { 20 | let valid = SystemString( 21 | "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") 22 | for char in valid { 23 | XCTAssertTrue(char.isLetter) 24 | } 25 | 26 | // non printable 27 | for value in 0..<(UInt8(ascii: " ")) { 28 | XCTAssertFalse(SystemChar(codeUnit: CInterop.PlatformUnicodeEncoding.CodeUnit(value)).isLetter) 29 | } 30 | XCTAssertFalse(SystemChar(codeUnit: 0x7F).isLetter) // DEL 31 | 32 | // misc other 33 | let invalid = SystemString( 34 | ##" !"#$%&'()*+,-./0123456789:;<=>?@[\]^_`{|}~"##) 35 | for char in invalid { 36 | XCTAssertFalse(char.isLetter) 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Tests/SystemTests/TestingInfrastructure.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift System open source project 3 | 4 | Copyright (c) 2020 Apple Inc. and the Swift System project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | */ 9 | 10 | import XCTest 11 | 12 | #if SYSTEM_PACKAGE 13 | @testable import SystemPackage 14 | #else 15 | @testable import System 16 | #endif 17 | 18 | internal struct Wildcard: Hashable {} 19 | 20 | extension Trace.Entry { 21 | /// This implements `==` with wildcard matching. 22 | /// (`Entry` cannot conform to `Equatable`/`Hashable` this way because 23 | /// the wildcard matching `==` relation isn't transitive.) 24 | internal func matches(_ other: Self) -> Bool { 25 | guard self.name == other.name else { return false } 26 | guard self.arguments.count == other.arguments.count else { return false } 27 | for i in self.arguments.indices { 28 | if self.arguments[i] is Wildcard || other.arguments[i] is Wildcard { 29 | continue 30 | } 31 | guard self.arguments[i] == other.arguments[i] else { return false } 32 | } 33 | return true 34 | } 35 | } 36 | 37 | // To aid debugging, force failures to fatal error 38 | internal var forceFatalFailures = false 39 | 40 | internal protocol TestCase { 41 | // TODO: want a source location stack, more fidelity, kinds of stack entries, etc 42 | var file: StaticString { get } 43 | var line: UInt { get } 44 | 45 | // TODO: Instead have an attribute to register a test in a allTests var, similar to the argument parser. 46 | func runAllTests() 47 | 48 | // Customization hook: add adornment to reported failure reason 49 | // Defaut: reason or empty 50 | func failureMessage(_ reason: String?) -> String 51 | } 52 | 53 | extension TestCase { 54 | // Default implementation 55 | func failureMessage(_ reason: String?) -> String { reason ?? "" } 56 | 57 | func expectEqualSequence( 58 | _ expected: S1, _ actual: S2, 59 | _ message: String? = nil 60 | ) where S1.Element: Equatable, S1.Element == S2.Element { 61 | if !expected.elementsEqual(actual) { 62 | defer { print("expected: \(expected)\n actual: \(actual)") } 63 | fail(message) 64 | } 65 | } 66 | func expectEqual( 67 | _ expected: E, _ actual: E, 68 | _ message: String? = nil 69 | ) { 70 | if actual != expected { 71 | defer { print("expected: \(expected)\n actual: \(actual)") } 72 | fail(message) 73 | } 74 | } 75 | func expectNotEqual( 76 | _ expected: E, _ actual: E, 77 | _ message: String? = nil 78 | ) { 79 | if actual == expected { 80 | defer { print("expected not equal: \(expected) and \(actual)") } 81 | fail(message) 82 | } 83 | } 84 | func expectMatch( 85 | _ expected: Trace.Entry?, _ actual: Trace.Entry?, 86 | _ message: String? = nil 87 | ) { 88 | func check() -> Bool { 89 | switch (expected, actual) { 90 | case let (expected?, actual?): 91 | return expected.matches(actual) 92 | case (nil, nil): 93 | return true 94 | default: 95 | return false 96 | } 97 | } 98 | if !check() { 99 | let e = expected.map { "\($0)" } ?? "nil" 100 | let a = actual.map { "\($0)" } ?? "nil" 101 | defer { print("expected: \(e)\n actual: \(a)") } 102 | fail(message) 103 | } 104 | } 105 | func expectNil( 106 | _ actual: T?, 107 | _ message: String? = nil 108 | ) { 109 | if actual != nil { 110 | defer { print("expected nil: \(actual!)") } 111 | fail(message) 112 | } 113 | } 114 | func expectNotNil( 115 | _ actual: T?, 116 | _ message: String? = nil 117 | ) { 118 | if actual == nil { 119 | defer { print("expected non-nil") } 120 | fail(message) 121 | } 122 | } 123 | func expectTrue( 124 | _ actual: Bool, 125 | _ message: String? = nil 126 | ) { 127 | if !actual { fail(message) } 128 | } 129 | func expectFalse( 130 | _ actual: Bool, 131 | _ message: String? = nil 132 | ) { 133 | if actual { fail(message) } 134 | } 135 | 136 | func fail(_ reason: String? = nil) { 137 | XCTAssert(false, failureMessage(reason), file: file, line: line) 138 | if forceFatalFailures { 139 | fatalError(reason ?? "") 140 | } 141 | } 142 | 143 | } 144 | 145 | internal struct MockTestCase: TestCase { 146 | var file: StaticString 147 | var line: UInt 148 | 149 | var expected: Trace.Entry 150 | var interruptBehavior: InterruptBehavior 151 | 152 | var interruptable: Bool { return interruptBehavior == .interruptable } 153 | 154 | internal enum InterruptBehavior { 155 | // Retry the syscall on EINTR 156 | case interruptable 157 | 158 | // Cannot return EINTR 159 | case noInterrupt 160 | 161 | // Cannot error at all 162 | case noError 163 | } 164 | 165 | var body: (_ retryOnInterrupt: Bool) throws -> () 166 | 167 | init( 168 | _ file: StaticString = #file, 169 | _ line: UInt = #line, 170 | name: String, 171 | _ interruptable: InterruptBehavior, 172 | _ args: AnyHashable..., 173 | body: @escaping (_ retryOnInterrupt: Bool) throws -> () 174 | ) { 175 | self.file = file 176 | self.line = line 177 | self.expected = Trace.Entry(name: name, args) 178 | self.interruptBehavior = interruptable 179 | self.body = body 180 | } 181 | 182 | func runAllTests() { 183 | XCTAssertFalse(MockingDriver.enabled) 184 | MockingDriver.withMockingEnabled { mocking in 185 | // Make sure we completely match the trace queue 186 | self.expectTrue(mocking.trace.isEmpty) 187 | defer { self.expectTrue(mocking.trace.isEmpty) } 188 | 189 | // Test our API mappings to the lower-level syscall invocation 190 | do { 191 | try body(true) 192 | self.expectMatch(self.expected, mocking.trace.dequeue()) 193 | } catch { 194 | self.fail() 195 | } 196 | 197 | // Non-error-ing syscalls shouldn't ever throw 198 | guard interruptBehavior != .noError else { 199 | do { 200 | try body(interruptable) 201 | self.expectMatch(self.expected, mocking.trace.dequeue()) 202 | try body(!interruptable) 203 | self.expectMatch(self.expected, mocking.trace.dequeue()) 204 | } catch { 205 | self.fail() 206 | } 207 | return 208 | } 209 | 210 | // Test interupt behavior. Interruptable calls will be told not to 211 | // retry to catch the EINTR. Non-interruptable calls will be told to 212 | // retry, to make sure they don't spin (e.g. if API changes to include 213 | // interruptable) 214 | do { 215 | mocking.forceErrno = .always(errno: EINTR) 216 | try body(!interruptable) 217 | self.fail() 218 | } catch Errno.interrupted { 219 | // Success! 220 | self.expectMatch(self.expected, mocking.trace.dequeue()) 221 | } catch { 222 | self.fail() 223 | } 224 | 225 | // Force a limited number of EINTRs, and make sure interruptable functions 226 | // retry that number of times. Non-interruptable functions should throw it. 227 | do { 228 | mocking.forceErrno = .counted(errno: EINTR, count: 3) 229 | 230 | try body(interruptable) 231 | self.expectMatch(self.expected, mocking.trace.dequeue()) // EINTR 232 | self.expectMatch(self.expected, mocking.trace.dequeue()) // EINTR 233 | self.expectMatch(self.expected, mocking.trace.dequeue()) // EINTR 234 | self.expectMatch(self.expected, mocking.trace.dequeue()) // Success 235 | } catch Errno.interrupted { 236 | self.expectFalse(interruptable) 237 | self.expectMatch(self.expected, mocking.trace.dequeue()) // EINTR 238 | } catch { 239 | self.fail() 240 | } 241 | } 242 | } 243 | } 244 | 245 | internal func withWindowsPaths(enabled: Bool, _ body: () -> ()) { 246 | _withWindowsPaths(enabled: enabled, body) 247 | } 248 | -------------------------------------------------------------------------------- /Tests/SystemTests/UtilTests.swift: -------------------------------------------------------------------------------- 1 | /* 2 | This source file is part of the Swift System open source project 3 | 4 | Copyright (c) 2021 Apple Inc. and the Swift System project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | */ 9 | 10 | import XCTest 11 | 12 | #if SYSTEM_PACKAGE 13 | @testable import SystemPackage 14 | #else 15 | @testable import System 16 | #endif 17 | 18 | class UtilTests: XCTestCase { 19 | func testStackBuffer() { 20 | // Exercises _withStackBuffer a bit, in hopes any bugs will 21 | // show up as ASan failures. 22 | for size in stride(from: 0, to: 1000, by: 5) { 23 | var called = false 24 | _withStackBuffer(capacity: size) { buffer in 25 | XCTAssertFalse(called) 26 | called = true 27 | 28 | buffer.initializeMemory(as: UInt8.self, repeating: 42) 29 | } 30 | XCTAssertTrue(called) 31 | } 32 | } 33 | 34 | func testCStringArray() { 35 | func check( 36 | _ array: [String], 37 | file: StaticString = #file, 38 | line: UInt = #line 39 | ) { 40 | array._withCStringArray { carray in 41 | let actual = carray.map { $0.map { String(cString: $0) } ?? "" } 42 | XCTAssertEqual(actual, array, file: file, line: line) 43 | // Verify that there is a null pointer following the last item in 44 | // carray. (Note: this is intentionally addressing beyond the 45 | // end of the buffer, as the function promises that is going to be okay.) 46 | XCTAssertNil((carray.baseAddress! + carray.count).pointee) 47 | } 48 | } 49 | 50 | check([]) 51 | check([""]) 52 | check(["", ""]) 53 | check(["", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", ""]) 54 | // String literals of various sizes and counts 55 | check(["hello"]) 56 | check(["hello", "world"]) 57 | check(["This is a rather large string literal"]) 58 | check([ 59 | "This is a rather large string literal", 60 | "This is small", 61 | "This one is not that small -- it's even longer than the first", 62 | ]) 63 | check([ 64 | "This is a rather large string literal", 65 | "This one is not that small -- it's even longer than the first", 66 | "And this is the largest of them all. I wonder if it even fits on a line" 67 | ]) 68 | check(Array(repeating: "", count: 100)) 69 | check(Array(repeating: "Hiii", count: 100)) 70 | check(["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m"]) 71 | check(["", "b", "", "d", "", "f", "", "h", "", "j", "", "👨‍👨‍👧‍👦👩‍❤️‍💋‍👨", "m"]) 72 | 73 | var girls = ["Dörothy", "Róse", "Blánche", "Sőphia"] 74 | check(girls) // Small strings 75 | for i in girls.indices { 76 | // Convert to native 77 | girls[i] = "\(girls[i]) \(girls[i]) \(girls[i])" 78 | } 79 | check(girls) // Native large strings 80 | for i in girls.indices { 81 | let data = girls[i].data(using: .utf16)! 82 | girls[i] = NSString( 83 | data: data, 84 | encoding: String.Encoding.utf16.rawValue 85 | )! as String 86 | } 87 | check(girls) // UTF-16 Cocoa strings 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /Utilities/expand-availability.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # This script can be used to automatically add/remove `@available` attributes to 4 | # declarations in Swift sources in this package. 5 | # 6 | # In order for this to work, ABI-impacting declarations need to be annotated 7 | # with special comments in the following format: 8 | # 9 | # @available(/*System 0.0.2*/iOS 8, *) 10 | # public func greeting() -> String { 11 | # "Hello" 12 | # } 13 | # 14 | # (The iOS 8 availability is a dummy no-op declaration -- it only has to be 15 | # there because `@available(*)` isn't valid syntax, and commenting out the 16 | # entire `@available` attribute would interfere with parser tools for doc 17 | # comments. `iOS 8` is the shortest version string that matches the minimum 18 | # possible deployment target for Swift code, so we use that as our dummy 19 | # availability version. `@available(iOS 8, *)` is functionally equivalent to not 20 | # having an `@available` attribute at all.) 21 | # 22 | # The script adds full availability incantations to these comments. It can run 23 | # in one of two modes: 24 | # 25 | # By default, `expand-availability.py` expands availability macros within the 26 | # comments. This is useful during package development to cross-reference 27 | # availability across `SystemPackage` and the ABI-stable `System` module that 28 | # ships in Apple's OS releases: 29 | # 30 | # @available(/*System 0.0.2: macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0*/iOS 8, *) 31 | # public func greeting() -> String { 32 | # "Hello" 33 | # } 34 | # 35 | # `expand-availability.py --attributes` adds actual availability declarations. 36 | # This is used by maintainers to build ABI stable releases of System on Apple's 37 | # platforms: 38 | # 39 | # @available(/*System 0.0.2: */macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *) 40 | # public func greeting() -> String { 41 | # "Hello" 42 | # } 43 | # 44 | # The script recognizes all three forms of these annotations and updates them on 45 | # every run, so we can run the script to enable/disable attributes as needed. 46 | 47 | import os 48 | import os.path 49 | import fileinput 50 | import re 51 | import sys 52 | import argparse 53 | 54 | versions = { 55 | "System 0.0.1": "macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0", 56 | "System 0.0.2": "macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0", 57 | "System 1.1.0": "macOS 12.3, iOS 15.4, watchOS 8.5, tvOS 15.4", 58 | "System 1.2.0": "macOS 9999, iOS 9999, watchOS 9999, tvOS 9999", 59 | "System 1.3.0": "macOS 9999, iOS 9999, watchOS 9999, tvOS 9999", 60 | "System 1.4.0": "macOS 9999, iOS 9999, watchOS 9999, tvOS 9999", 61 | } 62 | 63 | parser = argparse.ArgumentParser(description="Expand availability macros.") 64 | parser.add_argument("--attributes", help="Add @available attributes", 65 | action="store_true") 66 | args = parser.parse_args() 67 | 68 | def swift_sources_in(path): 69 | result = [] 70 | for (dir, _, files) in os.walk(path): 71 | for file in files: 72 | extension = os.path.splitext(file)[1] 73 | if extension == ".swift": 74 | result.append(os.path.join(dir, file)) 75 | return result 76 | 77 | # Old-style syntax: 78 | # /*System 0.0.2*/ 79 | # /*System 0.0.2, @available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *)*/ 80 | # /*System 0.0.2*/@available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *) 81 | old_macro_pattern = re.compile( 82 | r"/\*(System [^ *]+)(, @available\([^)]*\))?\*/(@available\([^)]*\))?") 83 | 84 | # New-style comments: 85 | # @available(/*SwiftSystem 0.0.2*/macOS 10, *) 86 | # @available(/*SwiftSystem 0.0.2: macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0*/iOS 8, *) 87 | # @available(/*SwiftSystem 0.0.2*/macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *) 88 | # 89 | # These do not interfere with our tools' ability to find doc comments. 90 | macro_pattern = re.compile( 91 | r"@available\(/\*(System [^ *:]+)[^*/)]*\*/([^)]*)\*\)") 92 | 93 | def available_attribute(filename, lineno, symbolic_version): 94 | expansion = versions[symbolic_version] 95 | if expansion is None: 96 | raise ValueError("{0}:{1}: error: Unknown System version '{0}'" 97 | .format(fileinput.filename(), fileinput.lineno(), symbolic_version)) 98 | if args.attributes: 99 | attribute = "@available(/*{0}*/{1}, *)".format(symbolic_version, expansion) 100 | else: 101 | # Sadly `@available(*)` is not valid syntax, so we have to mention at 102 | # least one actual platform here. 103 | attribute = "@available(/*{0}: {1}*/iOS 8, *)".format(symbolic_version, expansion) 104 | return attribute 105 | 106 | 107 | sources = swift_sources_in("Sources") + swift_sources_in("Tests") 108 | for line in fileinput.input(files=sources, inplace=True): 109 | match = re.search(macro_pattern, line) 110 | if match is None: 111 | match = re.search(old_macro_pattern, line) 112 | if match: 113 | symbolic_version = match.group(1) 114 | replacement = available_attribute( 115 | fileinput.filename(), fileinput.lineno(), symbolic_version) 116 | line = line[:match.start()] + replacement + line[match.end():] 117 | print(line, end="") 118 | -------------------------------------------------------------------------------- /cmake/modules/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | #[[ 2 | This source file is part of the Swift System open source Project 3 | 4 | Copyright (c) 2021 Apple Inc. and the Swift System project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | #]] 9 | 10 | set(SWIFT_SYSTEM_EXPORTS_FILE ${CMAKE_CURRENT_BINARY_DIR}/SwiftSystemExports.cmake) 11 | 12 | configure_file(SwiftSystemConfig.cmake.in 13 | ${CMAKE_CURRENT_BINARY_DIR}/SwiftSystemConfig.cmake) 14 | 15 | get_property(SWIFT_SYSTEM_EXPORTS GLOBAL PROPERTY SWIFT_SYSTEM_EXPORTS) 16 | export(TARGETS ${SWIFT_SYSTEM_EXPORTS} 17 | NAMESPACE SwiftSystem:: 18 | FILE ${SWIFT_SYSTEM_EXPORTS_FILE} 19 | EXPORT_LINK_INTERFACE_LIBRARIES) 20 | -------------------------------------------------------------------------------- /cmake/modules/SwiftSupport.cmake: -------------------------------------------------------------------------------- 1 | #[[ 2 | This source file is part of the Swift System open source project 3 | 4 | Copyright (c) 2020 Apple Inc. and the Swift System project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | #]] 9 | 10 | # Returns the architecture name in a variable 11 | # 12 | # Usage: 13 | # get_swift_host_arch(result_var_name) 14 | # 15 | # Sets ${result_var_name} with the converted architecture name derived from 16 | # CMAKE_SYSTEM_PROCESSOR. 17 | function(get_swift_host_arch result_var_name) 18 | if("${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "x86_64") 19 | set("${result_var_name}" "x86_64" PARENT_SCOPE) 20 | elseif ("${CMAKE_SYSTEM_PROCESSOR}" MATCHES "AArch64|aarch64|arm64|ARM64") 21 | if(CMAKE_SYSTEM_NAME MATCHES Darwin) 22 | set("${result_var_name}" "arm64" PARENT_SCOPE) 23 | else() 24 | set("${result_var_name}" "aarch64" PARENT_SCOPE) 25 | endif() 26 | elseif("${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "ppc64") 27 | set("${result_var_name}" "powerpc64" PARENT_SCOPE) 28 | elseif("${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "ppc64le") 29 | set("${result_var_name}" "powerpc64le" PARENT_SCOPE) 30 | elseif("${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "s390x") 31 | set("${result_var_name}" "s390x" PARENT_SCOPE) 32 | elseif("${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "armv6l") 33 | set("${result_var_name}" "armv6" PARENT_SCOPE) 34 | elseif("${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "armv7l") 35 | set("${result_var_name}" "armv7" PARENT_SCOPE) 36 | elseif("${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "armv7-a") 37 | set("${result_var_name}" "armv7" PARENT_SCOPE) 38 | elseif("${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "amd64") 39 | set("${result_var_name}" "x86_64" PARENT_SCOPE) 40 | elseif("${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "AMD64") 41 | set("${result_var_name}" "x86_64" PARENT_SCOPE) 42 | elseif("${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "IA64") 43 | set("${result_var_name}" "itanium" PARENT_SCOPE) 44 | elseif("${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "x86") 45 | set("${result_var_name}" "i686" PARENT_SCOPE) 46 | elseif("${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "i686") 47 | set("${result_var_name}" "i686" PARENT_SCOPE) 48 | elseif("${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "riscv64") 49 | set("${result_var_name}" "riscv64" PARENT_SCOPE) 50 | else() 51 | message(FATAL_ERROR "Unrecognized architecture on host system: ${CMAKE_SYSTEM_PROCESSOR}") 52 | endif() 53 | endfunction() 54 | 55 | # Returns the os name in a variable 56 | # 57 | # Usage: 58 | # get_swift_host_os(result_var_name) 59 | # 60 | # 61 | # Sets ${result_var_name} with the converted OS name derived from 62 | # CMAKE_SYSTEM_NAME. 63 | function(get_swift_host_os result_var_name) 64 | if(CMAKE_SYSTEM_NAME STREQUAL Darwin) 65 | set(${result_var_name} macosx PARENT_SCOPE) 66 | else() 67 | string(TOLOWER ${CMAKE_SYSTEM_NAME} cmake_system_name_lc) 68 | set(${result_var_name} ${cmake_system_name_lc} PARENT_SCOPE) 69 | endif() 70 | endfunction() 71 | 72 | if(NOT Swift_MODULE_TRIPLE) 73 | # Attempt to get the module triple from the Swift compiler. 74 | set(module_triple_command "${CMAKE_Swift_COMPILER}" -print-target-info) 75 | if(CMAKE_Swift_COMPILER_TARGET) 76 | list(APPEND module_triple_command -target ${CMAKE_Swift_COMPILER_TARGET}) 77 | endif() 78 | execute_process(COMMAND ${module_triple_command} 79 | OUTPUT_VARIABLE target_info_json) 80 | string(JSON module_triple GET "${target_info_json}" "target" "moduleTriple") 81 | 82 | # Exit now if we failed to infer the triple. 83 | if(NOT module_triple) 84 | message(FATAL_ERROR 85 | "Failed to get module triple from Swift compiler. " 86 | "Compiler output: ${target_info_json}") 87 | endif() 88 | 89 | # Cache the module triple for future use. 90 | set(Swift_MODULE_TRIPLE "${module_triple}" CACHE STRING "swift module triple used for installed swiftmodule and swiftinterface files") 91 | mark_as_advanced(Swift_MODULE_TRIPLE) 92 | endif() 93 | 94 | function(_install_target module) 95 | get_swift_host_os(swift_os) 96 | get_target_property(type ${module} TYPE) 97 | 98 | if(type STREQUAL STATIC_LIBRARY) 99 | set(swift swift_static) 100 | else() 101 | set(swift swift) 102 | endif() 103 | 104 | install(TARGETS ${module}) 105 | if(type STREQUAL EXECUTABLE) 106 | return() 107 | endif() 108 | 109 | get_target_property(module_name ${module} Swift_MODULE_NAME) 110 | if(NOT module_name) 111 | set(module_name ${module}) 112 | endif() 113 | 114 | install(FILES $/${module_name}.swiftdoc 115 | DESTINATION ${CMAKE_INSTALL_LIBDIR}/${swift}/${swift_os}/${module_name}.swiftmodule 116 | RENAME ${Swift_MODULE_TRIPLE}.swiftdoc) 117 | install(FILES $/${module_name}.swiftmodule 118 | DESTINATION ${CMAKE_INSTALL_LIBDIR}/${swift}/${swift_os}/${module_name}.swiftmodule 119 | RENAME ${Swift_MODULE_TRIPLE}.swiftmodule) 120 | endfunction() 121 | -------------------------------------------------------------------------------- /cmake/modules/SwiftSystemConfig.cmake.in: -------------------------------------------------------------------------------- 1 | #[[ 2 | This source file is part of the Swift System open source Project 3 | 4 | Copyright (c) 2021 Apple Inc. and the Swift System project authors 5 | Licensed under Apache License v2.0 with Runtime Library Exception 6 | 7 | See https://swift.org/LICENSE.txt for license information 8 | #]] 9 | 10 | if(NOT TARGET SystemPackage) 11 | include("@SWIFT_SYSTEM_EXPORTS_FILE@") 12 | endif() 13 | --------------------------------------------------------------------------------