Files
swift-nio/Sources/_NIOFileSystem/OpenOptions.swift
Joseph Heck 6b33489e64 Updates to present docs on _NIOFileSystem (#3477)
This re-enables documentation lost during refactoring work, under the
target _NIOFileSystem

resolves #3474 

### Motivation:

During the refactoring of NIOFileSystem and recent Swift updates, the
mechanism to "shadow" symbols using `@_exported import` has stopped
working for documentation and imports for symbols, which means that
_NIOFileSystem is the target that needs to host the documentation for
this (for now)

### Modifications:

Moves DocC catalog into _NIOFileSystem target, and updates
disambiguation hashes on overloaded symbols in order to verify no
warnings are presented while generating documentation.
Updates .spi.yml to present _NIOFileSystem instead of NIOFileSystem

### Result:

Previous documentation should be available again, although at a slightly
different URI structure within Swift Package Index.

Co-authored-by: Cory Benfield <lukasa@apple.com>
2026-01-13 14:22:46 +00:00

299 lines
11 KiB
Swift

//===----------------------------------------------------------------------===//
//
// This source file is part of the SwiftNIO open source project
//
// Copyright (c) 2025 Apple Inc. and the SwiftNIO project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of SwiftNIO project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//
import SystemPackage
/// Options for opening file handles.
public enum OpenOptions: Sendable {
/// Options for opening a file for reading.
public struct Read: Hashable, Sendable {
/// If the last path component is a symbolic link then this flag determines whether the
/// link is followed. If `false` and the last path component is a symbolic link then an
/// error is thrown.
public var followSymbolicLinks: Bool
/// Marks the descriptor of the opened file as 'close-on-exec'.
public var closeOnExec: Bool
/// Creates a new set of options for opening a file for reading.
///
/// - Parameters:
/// - followSymbolicLinks: Whether symbolic links should be followed, defaults to `true`.
/// - closeOnExec: Whether the descriptor should be marked as closed-on-exec, defaults
/// to `false`.
public init(
followSymbolicLinks: Bool = true,
closeOnExec: Bool = false
) {
self.followSymbolicLinks = followSymbolicLinks
self.closeOnExec = closeOnExec
}
}
/// Options for opening a directory.
public struct Directory: Hashable, Sendable {
/// If the last path component is a symbolic link then this flag determines whether the
/// link is followed. If `false` and the last path component is a symbolic link then an
/// error is thrown.
public var followSymbolicLinks: Bool
/// Marks the descriptor of the opened file as 'close-on-exec'.
public var closeOnExec: Bool
/// Creates a new set of options for opening a directory.
///
/// - Parameters:
/// - followSymbolicLinks: Whether symbolic links should be followed, defaults to `true`.
/// - closeOnExec: Whether the descriptor should be marked as closed-on-exec, defaults
/// to `false`.
public init(
followSymbolicLinks: Bool = true,
closeOnExec: Bool = false
) {
self.followSymbolicLinks = followSymbolicLinks
self.closeOnExec = closeOnExec
}
}
/// Options for opening a file for writing (or reading and writing).
///
/// You can use the following methods to create commonly used sets of options:
/// - ``newFile(replaceExisting:permissions:)`` to create a new file, optionally replacing
/// one which already exists.
/// - ``modifyFile(createIfNecessary:permissions:)`` to modify an existing file, optionally
/// creating it if it doesn't exist.
public struct Write: Hashable, Sendable {
/// The behavior for opening an existing file.
public var existingFile: OpenOptions.ExistingFile
/// The creation options for a new file, if one should be created. `nil` means that no
/// file should be created.
public var newFile: OpenOptions.NewFile?
/// If the last path component is a symbolic link then this flag determines whether the
/// link is followed. If `false` and the last path component is a symbolic link then an
/// error is thrown.
public var followSymbolicLinks: Bool
/// Marks the descriptor of the opened file as 'close-on-exec'.
public var closeOnExec: Bool
/// Creates a new set of options for opening a directory.
///
/// - Parameters:
/// - existingFile: Options for handling an existing file.
/// - newFile: Options for creating a new file.
/// - followSymbolicLinks: Whether symbolic links should be followed, defaults to `true`.
/// - closeOnExec: Whether the descriptor should be marked as closed-on-exec, defaults
/// to `false`.
public init(
existingFile: OpenOptions.ExistingFile,
newFile: OpenOptions.NewFile?,
followSymbolicLinks: Bool = true,
closeOnExec: Bool = false
) {
self.existingFile = existingFile
self.newFile = newFile
self.followSymbolicLinks = followSymbolicLinks
self.closeOnExec = closeOnExec
}
/// Create a new file for writing to.
///
/// - Parameters:
/// - replaceExisting: Whether any existing file of the same name is replaced. If
/// this is `true` then any existing file of the same name will be replaced with the
/// new file. If this is `false` and a file already exists an error is thrown.
/// - permissions: The permissions to apply to the newly created file. Default permissions
/// (read-write owner permissions and read permissions for everyone else) are applied
/// if `nil`.
/// - Returns: Options for creating a new file for writing.
public static func newFile(
replaceExisting: Bool,
permissions: FilePermissions? = nil
) -> Self {
Write(
existingFile: replaceExisting ? .truncate : .none,
newFile: NewFile(permissions: permissions)
)
}
/// Opens a file for modifying.
///
/// - Parameters:
/// - createIfNecessary: Whether a file should be created if one doesn't exist. If
/// `false` and a file doesn't exist then an error is thrown.
/// - permissions: The permissions to apply to the newly created file. Default permissions
/// (read-write owner permissions and read permissions for everyone else) are applied
/// if `nil`. Ignored if `createIfNonExistent` is `false`.
/// - Returns: Options for modifying an existing file for writing.
public static func modifyFile(
createIfNecessary: Bool,
permissions: FilePermissions? = nil
) -> Self {
Write(
existingFile: .open,
newFile: createIfNecessary ? NewFile(permissions: permissions) : nil
)
}
}
}
extension OpenOptions {
/// Options for opening an existing file.
public enum ExistingFile: Sendable, Hashable {
/// Indicates that no file exists. If a file does exist then an error is thrown when
/// opening the file.
case none
/// Any existing file should be opened without modification.
case open
/// Truncate the existing file.
///
/// Setting this is equivalent to opening a file with `O_TRUNC`.
case truncate
}
/// Options for creating a new file.
public struct NewFile: Sendable, Hashable {
/// The permissions to apply to the new file. `nil` implies default permissions
/// should be applied.
public var permissions: FilePermissions?
/// Whether the file should be created and updated as a single transaction, if
/// applicable.
///
/// When this option is set and applied the newly created file will only materialize
/// on the file system when the file is closed.
/// When used in conjunction with
/// ``FileSystemProtocol/withFileHandle(forWritingAt:options:execute:)`` and
/// ``FileSystemProtocol/withFileHandle(forReadingAndWritingAt:options:execute:)`` the
/// file will only materialize when the file is closed and no errors have been thrown.
///
/// - Important: This flag is only applied if ``OpenOptions/Write/existingFile`` is
/// ``OpenOptions/ExistingFile/none``.
public var transactionalCreation: Bool
public init(
permissions: FilePermissions? = nil,
transactionalCreation: Bool = true
) {
self.permissions = permissions
self.transactionalCreation = transactionalCreation
}
}
}
extension OpenOptions.Write {
@_spi(Testing)
public var permissionsForRegularFile: FilePermissions? {
if let newFile = self.newFile {
return newFile.permissions ?? .defaultsForRegularFile
} else {
return nil
}
}
var descriptorOptions: FileDescriptor.OpenOptions {
var options = FileDescriptor.OpenOptions()
if !self.followSymbolicLinks {
options.insert(.noFollow)
}
if self.closeOnExec {
options.insert(.closeOnExec)
}
if self.newFile != nil {
options.insert(.create)
}
switch self.existingFile {
case .none:
options.insert(.exclusiveCreate)
case .open:
()
case .truncate:
options.insert(.truncate)
}
return options
}
}
extension OpenOptions.Read {
var descriptorOptions: FileDescriptor.OpenOptions {
var options = FileDescriptor.OpenOptions()
if !self.followSymbolicLinks {
options.insert(.noFollow)
}
if self.closeOnExec {
options.insert(.closeOnExec)
}
return options
}
}
extension OpenOptions.Directory {
var descriptorOptions: FileDescriptor.OpenOptions {
var options = FileDescriptor.OpenOptions([.directory])
if !self.followSymbolicLinks {
options.insert(.noFollow)
}
if self.closeOnExec {
options.insert(.closeOnExec)
}
return options
}
}
extension FileDescriptor.OpenOptions {
public init(_ options: OpenOptions.Read) {
self = options.descriptorOptions
}
public init(_ options: OpenOptions.Write) {
self = options.descriptorOptions
}
public init(_ options: OpenOptions.Directory) {
self = options.descriptorOptions
}
}
extension FilePermissions {
/// Default permissions for regular files; owner read-write, group read, other read.
internal static let defaultsForRegularFile: FilePermissions = [
.ownerReadWrite,
.groupRead,
.otherRead,
]
/// Default permissions for directories; owner read-write-execute, group read-execute, other
/// read-execute.
internal static let defaultsForDirectory: FilePermissions = [
.ownerReadWriteExecute,
.groupReadExecute,
.otherReadExecute,
]
}