Skip to content

Commit 851820b

Browse files
committed
Add in fcntl raw, and higher-level support.
Add in support for fcntl and all the raw commands and flags it supports. Add in Swiftier representations of common and important invocations. Testing need: sample code.
1 parent 8e3c239 commit 851820b

File tree

4 files changed

+979
-0
lines changed

4 files changed

+979
-0
lines changed

Sources/System/FileControl.swift

Lines changed: 216 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,216 @@
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+
// Strongly typed, Swifty interfaces to the most common and useful `fcntl`
11+
// commands.
12+
13+
extension FileDescriptor {
14+
/// Get the flags associated with this file descriptor
15+
///
16+
/// The corresponding C function is `fcntl` with the `F_GETFD` command.
17+
@_alwaysEmitIntoClient
18+
public func getFlags() throws -> Flags {
19+
try Flags(rawValue: fcntl(.getFlags))
20+
}
21+
22+
/// Set the file descriptor flags.
23+
///
24+
/// The corresponding C function is `fcntl` with the `F_SETFD` command.
25+
@_alwaysEmitIntoClient
26+
public func setFlags(_ value: Flags) throws {
27+
_ = try fcntl(.setFlags, value.rawValue)
28+
}
29+
30+
/// Get descriptor status flags.
31+
///
32+
/// The corresponding C function is `fcntl` with the `F_GETFL` command.
33+
@_alwaysEmitIntoClient
34+
public func getStatusFlags() throws -> StatusFlags {
35+
try StatusFlags(rawValue: fcntl(.getStatusFlags))
36+
}
37+
38+
/// Set descriptor status flags.
39+
///
40+
/// The corresponding C function is `fcntl` with the `F_SETFL` command.
41+
@_alwaysEmitIntoClient
42+
public func setStatusFlags(_ flags: StatusFlags) throws {
43+
_ = try fcntl(.setStatusFlags, flags.rawValue)
44+
}
45+
}
46+
47+
// FIXME: We want a better approach for this one.
48+
extension FileDescriptor {
49+
/// TODO: Flesh out PID work and see if there's a better, common formulation
50+
/// of the process-or-group id concept.
51+
@frozen
52+
public struct PIDOrPGID: RawRepresentable {
53+
@_alwaysEmitIntoClient
54+
public let rawValue: CInt
55+
56+
@_alwaysEmitIntoClient
57+
public init(rawValue: CInt) { self.rawValue = rawValue }
58+
59+
/// TODO: PID type
60+
@_alwaysEmitIntoClient
61+
public var asPID: CInt? { rawValue >= 0 ? rawValue : nil }
62+
63+
/// TODO: PGID type
64+
@_alwaysEmitIntoClient
65+
public var asPositiveGroupID: CInt? {
66+
rawValue >= 0 ? nil : -rawValue
67+
}
68+
69+
/// TODO: PID type
70+
@_alwaysEmitIntoClient
71+
public init(pid id: CInt) {
72+
precondition(id >= 0)
73+
self.init(rawValue: id)
74+
}
75+
76+
/// TODO: PGID type
77+
@_alwaysEmitIntoClient
78+
public init(positiveGroupID id: CInt) {
79+
precondition(id >= 0)
80+
self.init(rawValue: -id)
81+
}
82+
}
83+
84+
/// Get the process ID or process group currently receiv-
85+
/// ing SIGIO and SIGURG signals.
86+
///
87+
/// The corresponding C function is `fcntl` with the `F_GETOWN` command.
88+
@_alwaysEmitIntoClient
89+
public func getOwner() throws -> PIDOrPGID {
90+
try PIDOrPGID(rawValue: fcntl(.getOwner))
91+
}
92+
93+
/// Set the process or process group to receive SIGIO and
94+
/// SIGURG signals.
95+
///
96+
/// The corresponding C function is `fcntl` with the `F_SETOWN` command.
97+
@_alwaysEmitIntoClient
98+
public func setOwner(_ id: PIDOrPGID) throws {
99+
_ = try fcntl(.setOwner, id.rawValue)
100+
}
101+
}
102+
103+
extension FileDescriptor {
104+
/// Duplicate this file descriptor and return the newly created copy.
105+
///
106+
/// - Parameters:
107+
/// - `minRawValue`: A lower bound on the new file descriptor's raw value.
108+
/// - `closeOnExec`: Whether the new descriptor's `closeOnExec` flag is set.
109+
/// - Returns: The lowest numbered available descriptor whose raw value is
110+
/// greater than or equal to `minRawValue`.
111+
///
112+
/// File descriptors are merely references to some underlying system resource.
113+
/// The system does not distinguish between the original and the new file
114+
/// descriptor in any way. For example, read, write and seek operations on
115+
/// one of them also affect the logical file position in the other, and
116+
/// append mode, non-blocking I/O and asynchronous I/O options are shared
117+
/// between the references. If a separate pointer into the file is desired,
118+
/// a different object reference to the file must be obtained by issuing an
119+
/// additional call to `open`.
120+
///
121+
/// However, each file descriptor maintains its own close-on-exec flag.
122+
///
123+
/// The corresponding C functions are `fcntl` with `F_DUPFD` and
124+
/// `F_DUPFD_CLOEXEC`.
125+
@_alwaysEmitIntoClient
126+
public func duplicate(
127+
minRawValue: CInt, closeOnExec: Bool
128+
) throws -> FileDescriptor {
129+
let cmd: Command = closeOnExec ? .duplicateCloseOnExec : .duplicate
130+
return try FileDescriptor(rawValue: fcntl(cmd, minRawValue))
131+
}
132+
133+
// TODO: What is the firmlink thing about?
134+
135+
/// Get the path of the file descriptor
136+
///
137+
/// - Parameters:
138+
/// - `noFirmLink`: Get the non firmlinked path of the file descriptor.
139+
///
140+
/// The corresponding C functions are `fcntl` with `F_GETPATH` and
141+
/// `F_GETPATH_NOFIRMLINK`.
142+
public func getPath(noFirmLink: Bool = false) throws -> FilePath {
143+
let cmd: Command = noFirmLink ? .getPathNoFirmLink : .getPath
144+
// TODO: have a uninitialized init on FilePath / SystemString...
145+
let bytes = try Array<SystemChar>(unsafeUninitializedCapacity: _maxPathLen) {
146+
(bufPtr, count: inout Int) in
147+
_ = try fcntl(cmd, UnsafeMutableRawPointer(bufPtr.baseAddress!))
148+
// TODO: Does fcntl return the length?
149+
// TODO: The below is probably the wrong formulation...
150+
count = system_strlen(
151+
UnsafeRawPointer(bufPtr.baseAddress!).assumingMemoryBound(to: Int8.self))
152+
}
153+
return FilePath(SystemString(nullTerminated: bytes))
154+
}
155+
}
156+
157+
// Record locking
158+
extension FileDescriptor {
159+
/// Get record locking information.
160+
///
161+
/// Get the first lock that blocks the lock description described by `lock`.
162+
/// If no lock is found that would prevent this lock from being created, the
163+
/// structure is left unchanged by this function call except for the lock type
164+
/// which is set to F_UNLCK.
165+
///
166+
/// The corresponding C function is `fcntl` with `F_GETLK`.
167+
@_alwaysEmitIntoClient
168+
public func getLock(blocking lock: FileLock) throws -> FileLock {
169+
var copy = lock
170+
try _fcntlLock(.getLock, &copy, retryOnInterrupt: false).get()
171+
return copy
172+
}
173+
174+
/// Set record locking information.
175+
///
176+
/// Set or clear a file segment lock according to the lock description
177+
/// `lock`.`setLock` is used to establish shared/read locks (`FileLock.read`)
178+
/// or exclusive/write locks, (`FileLock.write`), as well as remove either
179+
/// type of lock (`FileLock.unlock`). If a shared or exclusive lock cannot be
180+
/// set, this throws `Errno.resourceTemporarilyUnavailable`.
181+
///
182+
/// If `waitIfBlocked` is set and a shared or exclusive lock is blocked by
183+
/// other locks, the process waits until the request can be satisfied. If a
184+
/// signal that is to be caught is received while this calls is waiting for a
185+
/// region, the it will be interrupted if the signal handler has not
186+
/// specified the SA_RESTART (see sigaction(2)).
187+
///
188+
/// The corresponding C function is `fcntl` with `F_SETLK`.
189+
@_alwaysEmitIntoClient
190+
public func setLock(
191+
to lock: FileLock,
192+
waitIfBlocked: Bool = false,
193+
retryOnInterrupt: Bool = true
194+
) throws {
195+
let cmd: Command = waitIfBlocked ? .setLockWait : .setLock
196+
var copy = lock
197+
try _fcntlLock(cmd, &copy, retryOnInterrupt: retryOnInterrupt).get()
198+
// TODO: Does `fcntl` update `copy`? Should we return it?
199+
}
200+
201+
202+
}
203+
204+
// TODO: Should we do special interfaces for any of the following?
205+
// - preallocate
206+
// - punchhole
207+
// - setsize
208+
// - rdadvise, rdahead, nocache, log2phys
209+
// - no sigpipe
210+
// - read/write bootstrap?
211+
212+
// TODO: More fsync functionality using `F_BARRIERFSYNC` and `F_FULLFSYNC`,
213+
// coinciding with the sketch for fsync.
214+
215+
216+

0 commit comments

Comments
 (0)