/* This source file is part of the Swift System open source project Copyright (c) 2020 - 2026 Apple Inc. and the Swift System project authors Licensed under Apache License v2.0 with Runtime Library Exception See https://swift.org/LICENSE.txt for license information */ @available(System 0.0.1, *) extension FileDescriptor { /// Opens or creates a file for reading or writing. /// /// - Parameters: /// - path: The location of the file to open. /// - mode: The read and write access to use. /// - options: The behavior for opening the file. /// - permissions: The file permissions to use for created files. /// This value must not be `nil` when `options` contains `.create`; /// passing `nil` in that case is a programmer error and traps at runtime. /// - retryOnInterrupt: Whether to retry the open operation /// if it throws ``Errno/interrupted``. /// The default is `true`. /// Pass `false` to try only once and throw an error upon interruption. /// - Returns: A file descriptor for the open file /// /// The corresponding C function is `open`. @_alwaysEmitIntoClient public static func open( _ path: FilePath, _ mode: FileDescriptor.AccessMode, options: FileDescriptor.OpenOptions = FileDescriptor.OpenOptions(), permissions: FilePermissions? = nil, retryOnInterrupt: Bool = true ) throws -> FileDescriptor { #if !os(Windows) return try path.withCString { try FileDescriptor.open( $0, mode, options: options, permissions: permissions, retryOnInterrupt: retryOnInterrupt) } #else return try path.withPlatformString { try FileDescriptor.open( $0, mode, options: options, permissions: permissions, retryOnInterrupt: retryOnInterrupt) } #endif } #if !os(Windows) // On Darwin, `CInterop.PlatformChar` is less available than // `FileDescriptor.open`, so we need to use `CChar` instead. /// Opens or creates a file for reading or writing. /// /// - Parameters: /// - path: The location of the file to open. /// - mode: The read and write access to use. /// - options: The behavior for opening the file. /// - permissions: The file permissions to use for created files. /// This value must not be `nil` when `options` contains `.create`; /// passing `nil` in that case is a programmer error and traps at runtime. /// - retryOnInterrupt: Whether to retry the open operation /// if it throws ``Errno/interrupted``. /// The default is `true`. /// Pass `false` to try only once and throw an error upon interruption. /// - Returns: A file descriptor for the open file /// /// The corresponding C function is `open`. @_alwaysEmitIntoClient public static func open( _ path: UnsafePointer, _ mode: FileDescriptor.AccessMode, options: FileDescriptor.OpenOptions = FileDescriptor.OpenOptions(), permissions: FilePermissions? = nil, retryOnInterrupt: Bool = true ) throws -> FileDescriptor { try FileDescriptor._open( path, mode, options: options, permissions: permissions, retryOnInterrupt: retryOnInterrupt ).get() } @usableFromInline internal static func _open( _ path: UnsafePointer, _ mode: FileDescriptor.AccessMode, options: FileDescriptor.OpenOptions, permissions: FilePermissions?, retryOnInterrupt: Bool ) -> Result { let oFlag = mode.rawValue | options.rawValue let descOrError: Result = valueOrErrno(retryOnInterrupt: retryOnInterrupt) { if let permissions = permissions { return system_open(path, oFlag, permissions.rawValue) } if options.contains(.create) { fatalError( "FileDescriptor.open: 'permissions' must not be nil when 'options' contains '.create'") } return system_open(path, oFlag) } return descOrError.map { FileDescriptor(rawValue: $0) } } #else /// Opens or creates a file for reading or writing. /// /// - Parameters: /// - path: The location of the file to open. /// - mode: The read and write access to use. /// - options: The behavior for opening the file. /// - permissions: The file permissions to use for created files. /// This value must not be `nil` when `options` contains `.create`; /// passing `nil` in that case is a programmer error and traps at runtime. /// - retryOnInterrupt: Whether to retry the open operation /// if it throws ``Errno/interrupted``. /// The default is `true`. /// Pass `false` to try only once and throw an error upon interruption. /// - Returns: A file descriptor for the open file /// /// The corresponding C function is `open`. @_alwaysEmitIntoClient public static func open( _ path: UnsafePointer, _ mode: FileDescriptor.AccessMode, options: FileDescriptor.OpenOptions = FileDescriptor.OpenOptions(), permissions: FilePermissions? = nil, retryOnInterrupt: Bool = true ) throws -> FileDescriptor { try FileDescriptor._open( path, mode, options: options, permissions: permissions, retryOnInterrupt: retryOnInterrupt ).get() } @usableFromInline internal static func _open( _ path: UnsafePointer, _ mode: FileDescriptor.AccessMode, options: FileDescriptor.OpenOptions, permissions: FilePermissions?, retryOnInterrupt: Bool ) -> Result { let oFlag = mode.rawValue | options.rawValue let descOrError: Result = valueOrErrno(retryOnInterrupt: retryOnInterrupt) { if let permissions = permissions { return system_open(path, oFlag, permissions.rawValue) } return system_open(path, oFlag) } return descOrError.map { FileDescriptor(rawValue: $0) } } #endif /// Deletes a file descriptor. /// /// Deletes the file descriptor from the per-process object reference table. /// If this is the last reference to the underlying object, /// the object will be deactivated. /// /// The corresponding C function is `close`. @_alwaysEmitIntoClient public func close() throws { try _close().get() } @usableFromInline internal func _close() -> Result<(), Errno> { nothingOrErrno(retryOnInterrupt: false) { system_close(self.rawValue) } } /// Repositions the offset for the given file descriptor. /// /// - Parameters: /// - offset: The new offset for the file descriptor. /// - whence: The origin of the new offset. /// - Returns: The file's offset location, /// in bytes from the beginning of the file. /// /// The corresponding C function is `lseek`. @_alwaysEmitIntoClient @discardableResult public func seek( offset: Int64, from whence: FileDescriptor.SeekOrigin ) throws -> Int64 { try _seek(offset: offset, from: whence).get() } @usableFromInline internal func _seek( offset: Int64, from whence: FileDescriptor.SeekOrigin ) -> Result { valueOrErrno(retryOnInterrupt: false) { Int64(system_lseek(self.rawValue, _COffT(offset), whence.rawValue)) } } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "seek") public func lseek( offset: Int64, from whence: FileDescriptor.SeekOrigin ) throws -> Int64 { try seek(offset: offset, from: whence) } /// Reads bytes at the current file offset into a buffer. /// /// - Parameters: /// - buffer: The region of memory to read into. /// - retryOnInterrupt: Whether to retry the read operation /// if it throws ``Errno/interrupted``. /// The default is `true`. /// Pass `false` to try only once and throw an error upon interruption. /// - Returns: The number of bytes that were read. /// /// The property of `buffer` /// determines the maximum number of bytes that are read into that buffer. /// /// After reading, /// this method increments the file's offset by the number of bytes read. /// To change the file's offset, /// call the ``seek(offset:from:)`` method. /// /// The corresponding C function is `read`. @_alwaysEmitIntoClient public func read( into buffer: UnsafeMutableRawBufferPointer, retryOnInterrupt: Bool = true ) throws -> Int { try _read(into: buffer, retryOnInterrupt: retryOnInterrupt).get() } @usableFromInline internal func _read( into buffer: UnsafeMutableRawBufferPointer, retryOnInterrupt: Bool ) -> Result { valueOrErrno(retryOnInterrupt: retryOnInterrupt) { system_read(self.rawValue, buffer.baseAddress, buffer.count) } } /// Reads bytes at the specified offset into a buffer. /// /// - Parameters: /// - offset: The file offset where reading begins. /// - buffer: The region of memory to read into. /// - retryOnInterrupt: Whether to retry the read operation /// if it throws ``Errno/interrupted``. /// The default is `true`. /// Pass `false` to try only once and throw an error upon interruption. /// - Returns: The number of bytes that were read. /// /// The property of `buffer` /// determines the maximum number of bytes that are read into that buffer. /// /// Unlike , /// this method leaves the file's existing offset unchanged. /// /// The corresponding C function is `pread`. @_alwaysEmitIntoClient public func read( fromAbsoluteOffset offset: Int64, into buffer: UnsafeMutableRawBufferPointer, retryOnInterrupt: Bool = true ) throws -> Int { try _read( fromAbsoluteOffset: offset, into: buffer, retryOnInterrupt: retryOnInterrupt ).get() } @usableFromInline internal func _read( fromAbsoluteOffset offset: Int64, into buffer: UnsafeMutableRawBufferPointer, retryOnInterrupt: Bool ) -> Result { valueOrErrno(retryOnInterrupt: retryOnInterrupt) { system_pread(self.rawValue, buffer.baseAddress, buffer.count, _COffT(offset)) } } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "read") public func pread( fromAbsoluteOffset offset: Int64, into buffer: UnsafeMutableRawBufferPointer, retryOnInterrupt: Bool = true ) throws -> Int { try read( fromAbsoluteOffset: offset, into: buffer, retryOnInterrupt: retryOnInterrupt) } /// Writes the contents of a buffer at the current file offset. /// /// - Parameters: /// - buffer: The region of memory that contains the data being written. /// - retryOnInterrupt: Whether to retry the write operation /// if it throws ``Errno/interrupted``. /// The default is `true`. /// Pass `false` to try only once and throw an error upon interruption. /// - Returns: The number of bytes that were written. /// /// After writing, /// this method increments the file's offset by the number of bytes written. /// To change the file's offset, /// call the ``seek(offset:from:)`` method. /// /// The corresponding C function is `write`. @_alwaysEmitIntoClient public func write( _ buffer: UnsafeRawBufferPointer, retryOnInterrupt: Bool = true ) throws -> Int { try _write(buffer, retryOnInterrupt: retryOnInterrupt).get() } @usableFromInline internal func _write( _ buffer: UnsafeRawBufferPointer, retryOnInterrupt: Bool ) -> Result { valueOrErrno(retryOnInterrupt: retryOnInterrupt) { system_write(self.rawValue, buffer.baseAddress, buffer.count) } } /// Writes the contents of a buffer at the specified offset. /// /// - Parameters: /// - offset: The file offset where writing begins. /// - buffer: The region of memory that contains the data being written. /// - retryOnInterrupt: Whether to retry the write operation /// if it throws ``Errno/interrupted``. /// The default is `true`. /// Pass `false` to try only once and throw an error upon interruption. /// - Returns: The number of bytes that were written. /// /// Unlike ``write(_:retryOnInterrupt:)``, /// this method leaves the file's existing offset unchanged. /// /// The corresponding C function is `pwrite`. @_alwaysEmitIntoClient public func write( toAbsoluteOffset offset: Int64, _ buffer: UnsafeRawBufferPointer, retryOnInterrupt: Bool = true ) throws -> Int { try _write(toAbsoluteOffset: offset, buffer, retryOnInterrupt: retryOnInterrupt).get() } @usableFromInline internal func _write( toAbsoluteOffset offset: Int64, _ buffer: UnsafeRawBufferPointer, retryOnInterrupt: Bool ) -> Result { valueOrErrno(retryOnInterrupt: retryOnInterrupt) { system_pwrite(self.rawValue, buffer.baseAddress, buffer.count, _COffT(offset)) } } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "write") public func pwrite( toAbsoluteOffset offset: Int64, into buffer: UnsafeRawBufferPointer, retryOnInterrupt: Bool = true ) throws -> Int { try write( toAbsoluteOffset: offset, buffer, retryOnInterrupt: retryOnInterrupt) } } #if !os(WASI) @available(System 0.0.2, *) extension FileDescriptor { /// Duplicates this file descriptor and returns the newly created copy. /// /// - Parameters: /// - `target`: The desired target file descriptor, or `nil`, in which case /// the copy is assigned to the file descriptor with the lowest raw value /// that is not currently in use by the process. /// - retryOnInterrupt: Whether to retry the duplicate operation /// if it throws ``Errno/interrupted``. The default is `true`. /// Pass `false` to try only once and throw an error upon interruption. /// - Returns: The new file descriptor. /// /// If the `target` descriptor is already in use, then it is first /// deallocated as if a close(2) call had been done first. /// /// File descriptors are merely references to some underlying system resource. /// The system does not distinguish between the original and the new file /// descriptor in any way. For example, read, write and seek operations on /// one of them also affect the logical file position in the other, and /// append mode, non-blocking I/O and asynchronous I/O options are shared /// between the references. If a separate pointer into the file is desired, /// a different object reference to the file must be obtained by issuing an /// additional call to `open`. /// /// However, each file descriptor maintains its own close-on-exec flag. /// /// The corresponding C functions are `dup` and `dup2`. @_alwaysEmitIntoClient @available(System 0.0.2, *) public func duplicate( as target: FileDescriptor? = nil, retryOnInterrupt: Bool = true ) throws -> FileDescriptor { try _duplicate(as: target, retryOnInterrupt: retryOnInterrupt).get() } /// Duplicate this file descriptor and return the newly created copy. /// /// - Parameters: /// - target: The desired target file descriptor. /// - options: The behavior for creating the target file descriptor. /// - retryOnInterrupt: Whether to retry the operation /// if it throws ``Errno/interrupted``. The default is `true`. /// Pass `false` to try only once and throw an error upon interruption. /// - Returns: The new file descriptor. /// /// If the `target` descriptor is the same as `self`, then EINVAL is thrown. /// If the `target` descriptor is already in use, then it is first /// deallocated as if a close(2) call had been done first. /// /// NOTE: This overload called with an empty option set is not necessarily /// equivalent to calling the overload with no options, because `dup3` with /// no set options is not required to behave identically to `dup2`. /// /// File descriptors are merely references to some underlying system resource. /// The system does not distinguish between the original and the new file /// descriptor in any way. For example, read, write and seek operations on /// one of them also affect the logical file position in the other, and /// append mode, non-blocking I/O and asynchronous I/O options are shared /// between the references. If a separate pointer into the file is desired, /// a different object reference to the file must be obtained by issuing an /// additional call to `open`. /// /// However, each file descriptor maintains its own close-on-exec and /// close-on-fork flags. /// /// The corresponding C function is `dup3`. @available(Windows, unavailable) @available(macOS 27.0, iOS 27.0, watchOS 27.0, tvOS 27.0, visionOS 27.0, *) @_alwaysEmitIntoClient @discardableResult public func duplicate( as target: FileDescriptor, options: DuplicateOptions, retryOnInterrupt: Bool = true ) throws(Errno) -> FileDescriptor { let result = _duplicate( as: target, options: options.rawValue, retryOnInterrupt: retryOnInterrupt ) return try result.get() } @available(System 0.0.2, *) @usableFromInline internal func _duplicate( as target: FileDescriptor?, retryOnInterrupt: Bool ) -> Result { valueOrErrno(retryOnInterrupt: retryOnInterrupt) { if let target { system_dup2(self.rawValue, target.rawValue) } else { system_dup(self.rawValue) } }.map(FileDescriptor.init(rawValue:)) } @available(Windows, unavailable) @available(macOS 27.0, iOS 27.0, watchOS 27.0, tvOS 27.0, visionOS 27.0, *) @usableFromInline internal func _duplicate( as target: FileDescriptor, options: Int32, retryOnInterrupt: Bool ) -> Result { valueOrErrno(retryOnInterrupt: retryOnInterrupt) { system_dup3(self.rawValue, target.rawValue, options) }.map(FileDescriptor.init(rawValue:)) } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "duplicate") public func dup() throws -> FileDescriptor { fatalError("Not implemented") } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "duplicate") public func dup2() throws -> FileDescriptor { fatalError("Not implemented") } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "duplicate") public func dup3() throws -> FileDescriptor { fatalError("Not implemented") } } #endif // !os(WASI) #if !os(WASI) @available(System 1.1.0, *) extension FileDescriptor { /// Creates a unidirectional data channel, which can be used for /// interprocess communication. /// /// - Returns: The pair of file descriptors. /// /// The corresponding C function is `pipe`. @_alwaysEmitIntoClient @available(System 1.1.0, *) public static func pipe( ) throws -> (readEnd: FileDescriptor, writeEnd: FileDescriptor) { try _pipe().get() } /// Creates a unidirectional data channel, which can be used for /// interprocess communication. /// /// NOTE: This overload called with an empty option set is not necessarily /// equivalent to calling the overload with no options. On Windows, the /// no-parameter `pipe()` overload enables the `.closeOnExec` behaviour, /// but this overload disables it when called with an empty option set. /// /// - Parameters: /// - options: The behavior for creating the pipe. /// /// - Returns: The pair of file descriptors. /// /// The corresponding C function is `pipe2`. @_alwaysEmitIntoClient @available(macOS 27.0, iOS 27.0, watchOS 27.0, tvOS 27.0, visionOS 27.0, *) public static func pipe( options: PipeOptions ) throws(Errno) -> (readEnd: FileDescriptor, writeEnd: FileDescriptor) { try _pipe(options: options.rawValue).get() } @available(System 1.1.0, *) @usableFromInline internal static func _pipe( ) -> Result<(readEnd: FileDescriptor, writeEnd: FileDescriptor), Errno> { var fds: (Int32, Int32) = (-1, -1) return withUnsafeMutablePointer(to: &fds) { pointer in pointer.withMemoryRebound(to: Int32.self, capacity: 2) { fds in valueOrErrno(retryOnInterrupt: false) { system_pipe(fds) }.map { _ in (.init(rawValue: fds[0]), .init(rawValue: fds[1])) } } } } @available(macOS 27.0, iOS 27.0, watchOS 27.0, tvOS 27.0, visionOS 27.0, *) @usableFromInline internal static func _pipe( options: Int32, ) -> Result<(readEnd: FileDescriptor, writeEnd: FileDescriptor), Errno> { var fds: (Int32, Int32) = (-1, -1) return withUnsafeMutablePointer(to: &fds) { pointer in pointer.withMemoryRebound(to: Int32.self, capacity: 2) { fds in valueOrErrno(retryOnInterrupt: false) { system_pipe2(fds, options) }.map { _ in (.init(rawValue: fds[0]), .init(rawValue: fds[1])) } } } } @_alwaysEmitIntoClient @available(*, unavailable, renamed: "pipe") public static func pipe2() throws -> FileDescriptor { fatalError("Not implemented") } } #endif // !os(WASI) @available(System 1.2.0, *) extension FileDescriptor { /// Truncates or extends the file referenced by this file descriptor. /// /// - Parameters: /// - newSize: The length in bytes to resize the file to. /// - retryOnInterrupt: Whether to retry the write operation /// if it throws ``Errno/interrupted``. The default is `true`. /// Pass `false` to try only once and throw an error upon interruption. /// /// The file referenced by this file descriptor will by truncated (or extended) to `newSize`. /// /// If the current size of the file exceeds `newSize`, any extra data is discarded. If the current /// size of the file is smaller than `newSize`, the file is extended and filled with zeros to the /// provided size. /// /// This function requires that the file has been opened for writing. /// /// - Note: This function does not modify the current offset for any open file descriptors /// associated with the file. /// /// The corresponding C function is `ftruncate`. @available(System 1.2.0, *) @_alwaysEmitIntoClient public func resize( to newSize: Int64, retryOnInterrupt: Bool = true ) throws { try _resize( to: newSize, retryOnInterrupt: retryOnInterrupt ).get() } @available(System 1.2.0, *) @usableFromInline internal func _resize( to newSize: Int64, retryOnInterrupt: Bool ) -> Result<(), Errno> { nothingOrErrno(retryOnInterrupt: retryOnInterrupt) { system_ftruncate(self.rawValue, _COffT(newSize)) } } } #if !os(WASI) // WASI has no umask extension FilePermissions { /// The file creation permission mask (aka "umask"). /// /// Permissions set in this mask will be cleared by functions that create /// files or directories. Note that this mask is process-wide, and that /// *getting* it is not thread safe. internal static var creationMask: FilePermissions { get { let oldMask = _umask(0o22) _ = _umask(oldMask) return FilePermissions(rawValue: oldMask) } set { _ = _umask(newValue.rawValue) } } /// Change the file creation permission mask, run some code, then /// restore it to its original value. /// /// - Parameters: /// - permissions: The new permission mask. /// /// This is more efficient than reading `creationMask` and restoring it /// afterwards, because of the way reading the creation mask works. internal static func withCreationMask( _ permissions: FilePermissions, body: () throws -> R ) rethrows -> R { let oldMask = _umask(permissions.rawValue) defer { _ = _umask(oldMask) } return try body() } internal static func _umask(_ mode: CModeT) -> CModeT { return system_umask(mode) } } #endif // !os(WASI)