Swift System

repository·main·Indexed 23 days ago

https://github.com/apple/swift-system

Swift System provides idiomatic Swift interfaces to low-level system calls and currency types, serving as a central home for system interfaces across all supported Swift platforms. It includes the SystemPackage, which offers platform-specific APIs for file descriptor manipulation, file path handling, and file metadata retrieval via the Stat struct. Rather than providing a cross-platform abstraction, it closely reflects native OS interfaces for Darwin, POSIX, and Windows.

Tokens
7.1K
Snippets
13
Records
26
Agent score
80%

What's inside swift-system

  1. How Stat provides file metadata

    main

    The Stat type is a low-level, platform-specific abstraction for retrieving file metadata, mirroring the underlying system stat calls. It is designed to be additive and source/ABI compatible with existing code.

    Unlike higher-level abstractions like FileInfo, Stat focuses on providing direct access to system-level information. It can be initialized directly or accessed via extensions on FilePath and FileDescriptor for improved ergonomics and function chaining.

  2. Accessing file metadata via Stat

    main

    The Stat type provides access to file metadata through various properties. While the proposal discusses future ergonomic extensions (like accessTime using UTCClock.Instant), the current implementation focuses on exposing raw system types like timespec to remain faithful to the underlying system calls.

    Key metadata categories include:

    • File Type: Represented by FileType.
    • File Mode: Represented by FileMode.
    • File Flags: Represented by FileFlags (e.g., hidden, compressed).
    • Identifiers: Such as Inode and DeviceID.
    • Timestamps: Accessed via timespec properties (e.g., st_atim, st_mtim, st_ctim).
  3. Identify the public API surface

    main

    The public API of SystemPackage consists of non-underscored declarations marked public.

    Declarations with a leading underscore anywhere in their fully qualified name (e.g., _someMember, _Bar, _FooModule) are considered internal/non-public and may change in any release, including patch releases. Avoid using these in production code.

  4. Understand the platform-specific API design

    main

    Swift System is not a cross-platform abstraction library. Instead, it provides separate APIs and behaviors for every supported platform to closely reflect native OS interfaces.

    When building cross-platform applications, you should use SystemPackage to implement the platform-specific parts of your code, often using #if os() conditionals to handle differences between Darwin, POSIX, and Windows.

  5. Manage file types and permissions with FileMode

    main

    The FileMode struct represents a C mode_t value. It allows you to manage both the file's type and its permissions in a strongly-typed manner.

    • type: A FileType property that represents the file's nature (e.g., .directory, .regular, .symbolicLink). Setting this property automatically masks the value with S_IFMT.
    • permissions: A FilePermissions property representing the access bits. Setting this property masks the value with ALLPERMS.

    To ensure correct masking when working with raw C values, it is recommended to use FileMode(rawValue:) and then access the .type property to get a properly masked FileType.

  6. Add SystemPackage as a dependency

    main

    To use SystemPackage in a SwiftPM project, add the swift-system repository to your Package.swift dependencies and include the SystemPackage product in your target dependencies.

    let package = Package(
        // name, platforms, products, etc.
        dependencies: [
            .package(url: "https://github.com/apple/swift-system", from: "1.7.1"),
            // other dependencies
        ],
        targets: [
            .target(name: "MyTarget", dependencies: [
                .product(name: "SystemPackage", package: "swift-system"),
            ]),
            // other targets
        ]
    )
  7. Migrate unqualified stat() calls to CInterop.stat(_:_:)

    main

    If you have custom extensions on FilePath or FileDescriptor that use unqualified stat() or stat(_:_:) calls, you may encounter build errors when the new System.Stat API is introduced. To maintain compatibility with older deployment targets (macOS 12.0, iOS 15.0+), replace the unqualified calls with CInterop.Stat (the type) and CInterop.stat(_:_:) (the function).

    When to use this: Only use this migration if you meet all these criteria:

    1. You have a custom extension on FilePath or FileDescriptor.
    2. You use unqualified stat() or stat(_:_:) calls inside that extension.
    3. You must support deployment targets older than the new Stat API availability.

    If you already use qualified calls (e.g., Darwin.stat()), no migration is needed.

    // Before (Unqualified calls causing conflicts)
    extension FilePath {
      func isRegularFile() throws -> Bool {
        var s = stat()
        guard stat(self.string, &s) == 0 else {
          throw Errno.current
        }
        return s.st_mode & S_IFMT == S_IFREG
      }
    }
    
    // After (Migrated for compatibility with older targets)
    extension FilePath {
      func isRegularFile() throws -> Bool {
        var s = CInterop.Stat() // Use CInterop.Stat type
        guard CInterop.stat(self.string, &s) == 0 else { // Use CInterop.stat function
          throw Errno.current
        }
        return s.st_mode & S_IFMT == S_IFREG
      }
    }
    
    // Recommended: Migrate to the new System Stat API for newer targets
    extension FilePath {
      func isRegularFile() throws -> Bool {
        if #available(macOS X, iOS Y, *) {
          return try stat().type == .regular // Uses the new type-safe API
        }
        // Fallback for older targets
        var s = CInterop.Stat()
        guard CInterop.stat(self.string, &s) == 0 else {
          throw Errno.current
        }
        return s.st_mode & S_IFMT == S_IFREG
      }
    }
  8. Use SystemPackage for low-level system calls

    main

    Import SystemPackage to access idiomatic interfaces for system calls, such as file descriptor manipulation and file path handling. Note that SystemPackage provides platform-specific APIs that closely reflect the underlying OS; you may still need #if os() conditionals for cross-platform logic.

    import SystemPackage
    
    let message: String = "Hello, world!" + "\n"
    let path: FilePath = "/tmp/log"
    let fd = try FileDescriptor.open(
      path, .writeOnly, options: [.append, .create], permissions: .ownerReadWrite)
    try fd.closeAfter {
      _ = try fd.writeAll(message.utf8)
    }
  9. Use Stat for file metadata retrieval

    main

    To retrieve file metadata, you can use the Stat type. The proposal suggests two primary ways to obtain a Stat instance:

    1. Direct Initialization: Using an initializer that accepts a FilePath.
    2. Convenience Extensions: Using .stat() methods available on FilePath and FileDescriptor to allow for easier function chaining.

    Note: This API is intended to be low-level and platform-specific. For a cross-platform, ergonomic abstraction, look for a FileInfo API (potentially in Foundation or the standard library) in the future.

  10. Use the Stat type to access file metadata

    main

    The Stat struct provides a type-safe Swift wrapper around C stat structures, allowing you to retrieve file metadata such as size, type, permissions, and modification times on Unix-like platforms. You can initialize a Stat object using a file path string, a FilePath, or a FileDescriptor.

    // Get file status from path String
    let stat = try Stat("/path/to/file")
    
    // From FileDescriptor
    let stat = try fd.stat()
    
    // From FilePath
    let stat = try filePath.stat()
    
    // followTargetSymlink: false behaves like lstat()
    let stat = try symlinkPath.stat(followTargetSymlink: false)
    
    // Use fstatat() variant by supplying flags and an optional file descriptor
    let stat = try Stat("path/to/file", relativeTo: fd, flags: .symlinkNoFollow)
    
    print("Size: \(stat.size) bytes")
    print("Size allocated: \(stat.sizeAllocated) bytes")
    print("Type: \(stat.type)") // .regular, .directory, .symbolicLink, etc.
    print("Permissions: \(stat.permissions)")
    print("Modified: \(stat.modificationTime)")
    
    #if canImport(Darwin) || os(FreeBSD)
    print("Creation time: \(stat.creationTime)")
    #endif
  11. Duplicate a file descriptor with options using `FileDescriptor.duplicate(as:options:retryOnInterrupt:)`

    main

    Use FileDescriptor.duplicate(as:options:retryOnInterrupt:) to create a copy of an existing file descriptor with specific flags set atomically. This wraps the POSIX dup3 function.

    Behavior:

    • If target is the same as self, it throws EINVAL.
    • If target is already in use, it is first deallocated (equivalent to a close(2) call).
    • The new descriptor shares the same underlying system resource (file position, append mode, etc.) as the original, but maintains its own close-on-exec and close-on-fork flags.

    Parameters:

    • target: The desired target FileDescriptor.
    • options: A DuplicateOptions set.
    • retryOnInterrupt: Whether to retry the operation if it throws Errno/interrupted. Defaults to true.

    Available Options (DuplicateOptions):

    • .closeOnExec: Sets O_CLOEXEC. Closes the descriptor on exec(2).
    • .closeOnFork: Sets O_CLOFORK. Closes the descriptor on fork(2). Note: This is unavailable on Linux and Android.
    import System
    
    let fd0 = try FileDescriptor.open("/tmp/test.txt", .readOnly)
    let fd1 = FileDescriptor(rawValue: 731)
    let fd2 = try fd0.duplicate(as: fd1, options: [.closeOnFork, .closeOnExec])
  12. Get file metadata using Stat

    main

    The Stat struct provides a Swift wrapper for the C stat struct, allowing you to retrieve file metadata such as size, permissions, ownership, and timestamps. You can create a Stat object using several methods:

    • From a FilePath: Use path.stat(followTargetSymlink: Bool) to behave like stat() (default) or lstat() (if followTargetSymlink is false).
    • From a FileDescriptor: Use fd.stat() to behave like fstat().
    • Using fstatat semantics: Use path.stat(flags: Stat.Flags) or path.stat(relativeTo: FileDescriptor, flags: Stat.Flags) to resolve paths relative to a directory descriptor.

    All initializers can throw Errno if the system call fails.