Skip to content

Commit ccea4ee

Browse files
authored
Merge pull request #116 from loffgren/machports
Add Mach.Port
2 parents 77d9ca1 + 700b8f8 commit ccea4ee

File tree

3 files changed

+479
-0
lines changed

3 files changed

+479
-0
lines changed

Sources/System/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ add_library(SystemPackage
1313
FileHelpers.swift
1414
FileOperations.swift
1515
FilePermissions.swift
16+
MachPort.swift
1617
PlatformString.swift
1718
SystemString.swift
1819
Util.swift

Sources/System/MachPort.swift

Lines changed: 327 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,327 @@
1+
/*
2+
This source file is part of the Swift System open source project
3+
4+
Copyright (c) 2022 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+
#if $MoveOnly && (os(macOS) || os(iOS) || os(watchOS) || os(tvOS))
11+
12+
import Darwin.Mach
13+
14+
public protocol MachPortRight {}
15+
16+
private func machPrecondition(
17+
file: StaticString = #file,
18+
line: UInt = #line,
19+
_ body: @autoclosure () -> kern_return_t
20+
) {
21+
let kr = body()
22+
let expected = KERN_SUCCESS
23+
precondition(kr == expected, file: file, line: line)
24+
}
25+
26+
public enum Mach {
27+
@_moveOnly
28+
public struct Port<RightType: MachPortRight> {
29+
internal var name: mach_port_name_t
30+
internal var context: mach_port_context_t
31+
32+
/// Transfer ownership of an existing unmanaged Mach port right into a
33+
/// `Mach.Port` by name.
34+
///
35+
/// This initializer traps if `name` is `MACH_PORT_NULL`, or if `name` is
36+
/// `MACH_PORT_DEAD` and the `RightType` is `Mach.ReceiveRight`.
37+
///
38+
/// If the type of the right does not match the `RightType` of the
39+
/// `Mach.Port` being constructed, behavior is undefined.
40+
///
41+
/// The underlying port right will be automatically deallocated at the
42+
/// end of the `Mach.Port` instance's lifetime.
43+
///
44+
/// This initializer makes a syscall to guard the right.
45+
public init(name: mach_port_name_t) {
46+
precondition(name != mach_port_name_t(MACH_PORT_NULL), "Mach.Port cannot be initialized with MACH_PORT_NULL")
47+
self.name = name
48+
49+
if RightType.self == ReceiveRight.self {
50+
precondition(name != 0xFFFFFFFF /* MACH_PORT_DEAD */, "Receive rights cannot be dead names")
51+
52+
let secret = mach_port_context_t(arc4random())
53+
machPrecondition(mach_port_guard(mach_task_self_, name, secret, 0))
54+
self.context = secret
55+
}
56+
else {
57+
self.context = 0
58+
}
59+
}
60+
61+
/// Borrow access to the port name in a block that can perform
62+
/// non-consuming operations.
63+
///
64+
/// Take care when using this function; many operations consume rights,
65+
/// and send-once rights are easily consumed.
66+
///
67+
/// If the right is consumed, behavior is undefined.
68+
///
69+
/// The body block may optionally return something, which will then be
70+
/// returned to the caller of withBorrowedName.
71+
public func withBorrowedName<ReturnType>(body: (mach_port_name_t) -> ReturnType) -> ReturnType {
72+
return body(name)
73+
}
74+
75+
deinit {
76+
if name == 0xFFFFFFFF /* MACH_PORT_DEAD */ {
77+
precondition(RightType.self != ReceiveRight.self, "Receive rights cannot be dead names")
78+
machPrecondition(mach_port_mod_refs(mach_task_self_, name, MACH_PORT_RIGHT_DEAD_NAME, -1))
79+
} else {
80+
if RightType.self == ReceiveRight.self {
81+
machPrecondition(mach_port_destruct(mach_task_self_, name, -1, context))
82+
} else if RightType.self == SendRight.self {
83+
machPrecondition(mach_port_mod_refs(mach_task_self_, name, MACH_PORT_RIGHT_SEND, -1))
84+
} else if RightType.self == SendOnceRight.self {
85+
machPrecondition(mach_port_mod_refs(mach_task_self_, name, MACH_PORT_RIGHT_SEND_ONCE, -1))
86+
}
87+
}
88+
}
89+
}
90+
91+
/// Possible errors that can be thrown by Mach.Port operations.
92+
public enum PortRightError : Error {
93+
/// Returned when an operation cannot be completed, because the Mach
94+
/// port right has become a dead name. This is caused by deallocation of the
95+
/// receive right on the other end.
96+
case deadName
97+
}
98+
99+
/// The MachPortRight type used to manage a receive right.
100+
public struct ReceiveRight: MachPortRight {}
101+
102+
/// The MachPortRight type used to manage a send right.
103+
public struct SendRight: MachPortRight {}
104+
105+
/// The MachPortRight type used to manage a send-once right.
106+
///
107+
/// Send-once rights are the most restrictive type of Mach port rights.
108+
/// They cannot create other rights, and are consumed upon use.
109+
///
110+
/// Upon destruction a send-once notification will be sent to the
111+
/// receiving end.
112+
public struct SendOnceRight: MachPortRight {}
113+
114+
/// Create a connected pair of rights, one receive, and one send.
115+
///
116+
/// This function will abort if the rights could not be created.
117+
/// Callers may assert that valid rights are always returned.
118+
public static func allocatePortRightPair() -> (receive: Mach.Port<Mach.ReceiveRight>, send: Mach.Port<Mach.SendRight>) {
119+
var name = mach_port_name_t(MACH_PORT_NULL)
120+
let secret = mach_port_context_t(arc4random())
121+
withUnsafeMutablePointer(to: &name) { name in
122+
var options = mach_port_options_t()
123+
options.flags = UInt32(MPO_INSERT_SEND_RIGHT);
124+
withUnsafeMutablePointer(to: &options) { options in
125+
machPrecondition(mach_port_construct(mach_task_self_, options, secret, name))
126+
}
127+
}
128+
return (Mach.Port(name: name, context: secret), Mach.Port(name: name))
129+
}
130+
}
131+
132+
public extension Mach.Port where RightType == Mach.ReceiveRight {
133+
/// Transfer ownership of an existing, unmanaged, but already guarded,
134+
/// Mach port right into a Mach.Port by name.
135+
///
136+
/// This initializer aborts if name is MACH_PORT_NULL.
137+
///
138+
/// If the type of the right does not match the type T of Mach.Port<T>
139+
/// being constructed, the behavior is undefined.
140+
///
141+
/// The underlying port right will be automatically deallocated when
142+
/// the Mach.Port object is destroyed.
143+
init(name: mach_port_name_t, context: mach_port_context_t) {
144+
self.name = name
145+
self.context = context
146+
}
147+
148+
/// Allocate a new Mach port with a receive right, creating a
149+
/// Mach.Port<Mach.ReceiveRight> to manage it.
150+
///
151+
/// This initializer will abort if the right could not be created.
152+
/// Callers may assert that a valid right is always returned.
153+
init() {
154+
var storage: mach_port_name_t = mach_port_name_t(MACH_PORT_NULL)
155+
withUnsafeMutablePointer(to: &storage) { storage in
156+
machPrecondition(mach_port_allocate(mach_task_self_, MACH_PORT_RIGHT_RECEIVE, storage))
157+
}
158+
159+
// name-only init will guard ReceiveRights
160+
self.init(name: storage)
161+
}
162+
163+
164+
/// Transfer ownership of the underlying port right to the caller.
165+
///
166+
/// Returns a tuple containing the Mach port name representing the right,
167+
/// and the context value used to guard the right.
168+
///
169+
/// This operation liberates the right from management by the Mach.Port,
170+
/// and the underlying right will no longer be automatically deallocated.
171+
///
172+
/// After this function completes, the Mach.Port is destroyed and no longer
173+
/// usable.
174+
__consuming func relinquish() -> (name: mach_port_name_t, context: mach_port_context_t) {
175+
return (name: name, context: context)
176+
}
177+
178+
/// Remove guard and transfer ownership of the underlying port right to
179+
/// the caller.
180+
///
181+
/// Returns the Mach port name representing the right.
182+
///
183+
/// This operation liberates the right from management by the Mach.Port,
184+
/// and the underlying right will no longer be automatically deallocated.
185+
///
186+
/// After this function completes, the Mach.Port is destroyed and no longer
187+
/// usable.
188+
///
189+
/// This function makes a syscall to remove the guard from
190+
/// Mach.ReceiveRights. Use relinquish() to avoid the syscall and extract
191+
/// the context value along with the port name.
192+
__consuming func unguardAndRelinquish() -> mach_port_name_t {
193+
machPrecondition(mach_port_unguard(mach_task_self_, name, context))
194+
return name
195+
}
196+
197+
/// Borrow access to the port name in a block that can perform
198+
/// non-consuming operations.
199+
///
200+
/// Take care when using this function; many operations consume rights.
201+
///
202+
/// If the right is consumed, behavior is undefined.
203+
///
204+
/// The body block may optionally return something, which will then be
205+
/// returned to the caller of withBorrowedName.
206+
func withBorrowedName<ReturnType>(body: (mach_port_name_t, mach_port_context_t) -> ReturnType) -> ReturnType {
207+
body(name, context)
208+
}
209+
210+
/// Create a send-once right for a given receive right.
211+
///
212+
/// This does not affect the makeSendCount of the receive right.
213+
///
214+
/// This function will abort if the right could not be created.
215+
/// Callers may assert that a valid right is always returned.
216+
func makeSendOnceRight() -> Mach.Port<Mach.SendOnceRight> {
217+
// send once rights do not coalesce
218+
var newRight: mach_port_name_t = mach_port_name_t(MACH_PORT_NULL)
219+
var newRightType: mach_port_type_t = MACH_PORT_TYPE_NONE
220+
221+
withUnsafeMutablePointer(to: &newRight) { newRight in
222+
withUnsafeMutablePointer(to: &newRightType) { newRightType in
223+
machPrecondition(
224+
mach_port_extract_right(mach_task_self_,
225+
name,
226+
mach_msg_type_name_t(MACH_MSG_TYPE_MAKE_SEND_ONCE),
227+
newRight,
228+
newRightType)
229+
)
230+
}
231+
}
232+
233+
// The value of newRight is validated by the Mach.Port initializer
234+
precondition(newRightType == MACH_MSG_TYPE_MOVE_SEND_ONCE)
235+
236+
return Mach.Port(name: newRight)
237+
}
238+
239+
/// Create a send right for a given receive right.
240+
///
241+
/// This increments the makeSendCount of the receive right.
242+
///
243+
/// This function will abort if the right could not be created.
244+
/// Callers may assert that a valid right is always returned.
245+
func makeSendRight() -> Mach.Port<Mach.SendRight> {
246+
let how = MACH_MSG_TYPE_MAKE_SEND
247+
248+
// name is the same because send and recv rights are coalesced
249+
machPrecondition(mach_port_insert_right(mach_task_self_, name, name, mach_msg_type_name_t(how)))
250+
251+
return Mach.Port(name: name)
252+
}
253+
254+
/// Access the make-send count.
255+
///
256+
/// Each get/set of this property makes a syscall.
257+
var makeSendCount : mach_port_mscount_t {
258+
get {
259+
var status: mach_port_status = mach_port_status()
260+
var size: mach_msg_type_number_t = mach_msg_type_number_t(MemoryLayout<mach_port_status>.size / MemoryLayout<natural_t>.size)
261+
withUnsafeMutablePointer(to: &size) { size in
262+
withUnsafeMutablePointer(to: &status) { status in
263+
let info = UnsafeMutableRawPointer(status).bindMemory(to: integer_t.self, capacity: 1)
264+
machPrecondition(mach_port_get_attributes(mach_task_self_, name, MACH_PORT_RECEIVE_STATUS, info, size))
265+
}
266+
}
267+
return status.mps_mscount
268+
}
269+
270+
set {
271+
machPrecondition(mach_port_set_mscount(mach_task_self_, name, newValue))
272+
}
273+
}
274+
}
275+
276+
public extension Mach.Port where RightType == Mach.SendRight {
277+
/// Transfer ownership of the underlying port right to the caller.
278+
///
279+
/// Returns the Mach port name representing the right.
280+
///
281+
/// This operation liberates the right from management by the Mach.Port,
282+
/// and the underlying right will no longer be automatically deallocated.
283+
///
284+
/// After this function completes, the Mach.Port is destroyed and no longer
285+
/// usable.
286+
__consuming func relinquish() -> mach_port_name_t {
287+
return name
288+
}
289+
290+
/// Create another send right from a given send right.
291+
///
292+
/// This does not affect the makeSendCount of the receive right.
293+
///
294+
/// If the send right being copied has become a dead name, meaning the
295+
/// receiving side has been deallocated, then copySendRight() will throw
296+
/// a Mach.PortRightError.deadName error.
297+
func copySendRight() throws -> Mach.Port<Mach.SendRight> {
298+
let how = MACH_MSG_TYPE_COPY_SEND
299+
300+
// name is the same because send rights are coalesced
301+
let kr = mach_port_insert_right(mach_task_self_, name, name, mach_msg_type_name_t(how))
302+
if kr == KERN_INVALID_CAPABILITY {
303+
throw Mach.PortRightError.deadName
304+
}
305+
machPrecondition(kr)
306+
307+
return Mach.Port(name: name)
308+
}
309+
}
310+
311+
312+
public extension Mach.Port where RightType == Mach.SendOnceRight {
313+
/// Transfer ownership of the underlying port right to the caller.
314+
///
315+
/// Returns the Mach port name representing the right.
316+
///
317+
/// This operation liberates the right from management by the Mach.Port,
318+
/// and the underlying right will no longer be automatically deallocated.
319+
///
320+
/// After this function completes, the Mach.Port is destroyed and no longer
321+
/// usable.
322+
__consuming func relinquish() -> mach_port_name_t {
323+
return name
324+
}
325+
}
326+
327+
#endif

0 commit comments

Comments
 (0)