Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

File control: fcntl raw and higher-level support. #44

Open
wants to merge 27 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Sources/CSystem/include/CSystemLinux.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

#include <sys/epoll.h>
#include <sys/eventfd.h>
#include <sys/param.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <sys/sysinfo.h>
Expand Down
292 changes: 292 additions & 0 deletions Sources/System/FileControl.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,292 @@
/*
This source file is part of the Swift System open source project

Copyright (c) 2021 Apple Inc. and the Swift System project authors
Licensed under Apache License v2.0 with Runtime Library Exception

See https://swift.org/LICENSE.txt for license information
*/

#if !os(Windows)


// Strongly typed, Swifty interfaces to the most common and useful `fcntl`
// commands.

extension FileDescriptor {
/// Get the flags associated with this file descriptor
///
/// The corresponding C function is `fcntl` with the `F_GETFD` command.
@_alwaysEmitIntoClient
public func getFlags(retryOnInterrupt: Bool = true) throws -> Flags {
try Flags(rawValue: control(
.getFlags, retryOnInterrupt: retryOnInterrupt))
}

/// Set the file descriptor flags.
///
/// The corresponding C function is `fcntl` with the `F_SETFD` command.
@_alwaysEmitIntoClient
public func setFlags(
_ value: Flags, retryOnInterrupt: Bool = true
) throws {
_ = try control(
.setFlags, value.rawValue, retryOnInterrupt: retryOnInterrupt)
}

/// Get descriptor status flags.
///
/// The corresponding C function is `fcntl` with the `F_GETFL` command.
@_alwaysEmitIntoClient
public func getStatusFlags(
retryOnInterrupt: Bool = true
) throws -> StatusFlags {
try StatusFlags(rawValue: control(
.getStatusFlags, retryOnInterrupt: retryOnInterrupt))
}

/// Set descriptor status flags.
///
/// The corresponding C function is `fcntl` with the `F_SETFL` command.
@_alwaysEmitIntoClient
public func setStatusFlags(
_ flags: StatusFlags, retryOnInterrupt: Bool = true
) throws {
_ = try control(
.setStatusFlags, flags.rawValue, retryOnInterrupt: retryOnInterrupt)
}

/// Get the process ID or process ID group currently receiving SIGIO and
/// SIGURG signals.
///
/// The corresponding C function is `fcntl` with the `F_GETOWN` command.
@_alwaysEmitIntoClient
public func getOwner(
retryOnInterrupt: Bool = true
) throws -> (ProcessID, isGroup: Bool) {
let pidOrPGID = try control(
.getOwner, retryOnInterrupt: retryOnInterrupt)
if pidOrPGID < 0 {
return (ProcessID(rawValue: -pidOrPGID), isGroup: true)
}
return (ProcessID(rawValue: pidOrPGID), isGroup: false)
}

/// Set the process ID or process group ID to receive SIGIO and SIGURG
/// signals.
///
/// The corresponding C function is `fcntl` with the `F_SETOWN` command.
@_alwaysEmitIntoClient
public func setOwner(
_ id: ProcessID, isGroup: Bool, retryOnInterrupt: Bool = true
) throws {
let pidOrPGID = isGroup ? -id.rawValue : id.rawValue
_ = try control(.setOwner, pidOrPGID, retryOnInterrupt: retryOnInterrupt)
}
}

extension FileDescriptor {
/// Duplicate this file descriptor and return the newly created copy.
///
/// - Parameters:
/// - `minRawValue`: A lower bound on the new file descriptor's raw value.
/// - `closeOnExec`: Whether the new descriptor's `closeOnExec` flag is set.
/// - Returns: The lowest numbered available descriptor whose raw value is
/// greater than or equal to `minRawValue`.
///
/// 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 `fcntl` with `F_DUPFD` and
/// `F_DUPFD_CLOEXEC`.
@_alwaysEmitIntoClient
public func duplicate(
minRawValue: CInt, closeOnExec: Bool, retryOnInterrupt: Bool = true
) throws -> FileDescriptor {
let cmd: Command = closeOnExec ? .duplicateCloseOnExec : .duplicate
return try FileDescriptor(rawValue: control(
cmd, minRawValue, retryOnInterrupt: retryOnInterrupt))
}

#if !os(Linux)

/// Get the path of the file descriptor
///
/// - Parameters:
/// - `noFirmLink`: Get the non firmlinked path of the file descriptor.
///
/// The corresponding C functions are `fcntl` with `F_GETPATH` and
/// `F_GETPATH_NOFIRMLINK`.
public func getPath(
noFirmLink: Bool = false, retryOnInterrupt: Bool = true
) throws -> FilePath {
let cmd: Command = noFirmLink ? .getPathNoFirmLink : .getPath
// TODO: have a uninitialized init on FilePath / SystemString...
let bytes = try Array<SystemChar>(unsafeUninitializedCapacity: _maxPathLen) {
(bufPtr, count: inout Int) in
_ = try control(
cmd,
UnsafeMutableRawPointer(bufPtr.baseAddress!),
retryOnInterrupt: retryOnInterrupt)
// TODO: The below is probably the wrong formulation...
count = system_strlen(
UnsafeRawPointer(
bufPtr.baseAddress!
).assumingMemoryBound(to: Int8.self))
}
return FilePath(SystemString(nullTerminated: bytes))
}

#endif // !os(Linux)

}

extension FileDescriptor {
/// Low-level interface equivalent to C's `fcntl`. Note, most common
/// operations have Swiftier alternatives directly on `FileDescriptor`.
@_alwaysEmitIntoClient
public func control(
_ cmd: Command, retryOnInterrupt: Bool = true
) throws -> CInt {
try _control(cmd, retryOnInterrupt: retryOnInterrupt).get()
}

/// Low-level interface equivalent to C's `fcntl`. Note, most common
/// operations have Swiftier alternatives directly on `FileDescriptor`.
@_alwaysEmitIntoClient
public func control(
_ cmd: Command, _ arg: CInt, retryOnInterrupt: Bool = true
) throws -> CInt {
try _control(cmd, arg, retryOnInterrupt: retryOnInterrupt).get()
}

/// Low-level interface equivalent to C's `fcntl`. Note, most common
/// operations have Swiftier alternatives directly on `FileDescriptor`.
@_alwaysEmitIntoClient
public func control(
_ cmd: Command,
_ ptr: UnsafeMutableRawPointer,
retryOnInterrupt: Bool = true
) throws -> CInt {
try _control(cmd, ptr, retryOnInterrupt: retryOnInterrupt).get()
}

/// Low-level interface equivalent to C's `fcntl`. Note, most common
/// operations have Swiftier alternatives directly on `FileDescriptor`.
@_alwaysEmitIntoClient
public func control(
_ cmd: Command,
_ lock: inout FileDescriptor.FileLock,
retryOnInterrupt: Bool = true
) throws -> CInt {
try _control(cmd, &lock, retryOnInterrupt: retryOnInterrupt).get()
}

#if !os(Linux)

// TODO: Worth calling out the command to supply in docs?

/// Low-level interface equivalent to C's `fcntl`. Note, most common
/// operations have Swiftier alternatives directly on `FileDescriptor`.
@_alwaysEmitIntoClient
public func control(
_ cmd: Command,
_ type: inout FileDescriptor.ControlTypes.Store,
retryOnInterrupt: Bool = true
) throws -> CInt {
try withUnsafeMutablePointer(to: &type) {
try _control(cmd, $0, retryOnInterrupt: retryOnInterrupt).get()
}
}

/// Low-level interface equivalent to C's `fcntl`. Note, most common
/// operations have Swiftier alternatives directly on `FileDescriptor`.
@_alwaysEmitIntoClient
public func control(
_ cmd: Command,
_ type: inout FileDescriptor.ControlTypes.Punchhole,
retryOnInterrupt: Bool = true
) throws -> CInt {
try withUnsafeMutablePointer(to: &type) {
try _control(cmd, $0, retryOnInterrupt: retryOnInterrupt).get()
}
}

/// Low-level interface equivalent to C's `fcntl`. Note, most common
/// operations have Swiftier alternatives directly on `FileDescriptor`.
@_alwaysEmitIntoClient
public func control(
_ cmd: Command,
_ type: inout FileDescriptor.ControlTypes.ReadAdvisory,
retryOnInterrupt: Bool = true
) throws -> CInt {
try withUnsafeMutablePointer(to: &type) {
try _control(cmd, $0, retryOnInterrupt: retryOnInterrupt).get()
}
}

/// Low-level interface equivalent to C's `fcntl`. Note, most common
/// operations have Swiftier alternatives directly on `FileDescriptor`.
@_alwaysEmitIntoClient
public func control(
_ cmd: Command,
_ type: inout FileDescriptor.ControlTypes.LogicalToPhysical,
retryOnInterrupt: Bool = true
) throws -> CInt {
try withUnsafeMutablePointer(to: &type) {
try _control(cmd, $0, retryOnInterrupt: retryOnInterrupt).get()
}
}

#endif // !os(Linux)

@usableFromInline
internal func _control(
_ cmd: Command, retryOnInterrupt: Bool
) -> Result<CInt, Errno> {
valueOrErrno(retryOnInterrupt: retryOnInterrupt) {
system_fcntl(self.rawValue, cmd.rawValue)
}
}
@usableFromInline
internal func _control(
_ cmd: Command, _ arg: CInt, retryOnInterrupt: Bool
) -> Result<CInt, Errno> {
valueOrErrno(retryOnInterrupt: retryOnInterrupt) {
system_fcntl(self.rawValue, cmd.rawValue, arg)
}
}
@usableFromInline
internal func _control(
_ cmd: Command,
_ ptr: UnsafeMutableRawPointer,
retryOnInterrupt: Bool
) -> Result<CInt, Errno> {
valueOrErrno(retryOnInterrupt: retryOnInterrupt) {
system_fcntl(self.rawValue, cmd.rawValue, ptr)
}
}

@usableFromInline
internal func _control(
_ cmd: Command,
_ lock: inout FileDescriptor.FileLock,
retryOnInterrupt: Bool
) -> Result<(), Errno> {
nothingOrErrno(retryOnInterrupt: retryOnInterrupt) {
withUnsafeMutablePointer(to: &lock) {
system_fcntl(self.rawValue, cmd.rawValue, $0)
}
}
}
}

#endif // !os(Windows)
Loading