Skip to content

Re-enable Mach.Port #129

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

Merged
merged 18 commits into from
Aug 16, 2023
Merged
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
112 changes: 68 additions & 44 deletions Sources/System/MachPort.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
See https://swift.org/LICENSE.txt for license information
*/

#if false && swift(>=5.8) && $MoveOnly && (os(macOS) || os(iOS) || os(watchOS) || os(tvOS))
#if swift(>=5.9) && (os(macOS) || os(iOS) || os(watchOS) || os(tvOS))

import Darwin.Mach

Expand Down Expand Up @@ -48,11 +48,13 @@ public enum Mach {
///
/// This initializer makes a syscall to guard the right.
public init(name: mach_port_name_t) {
precondition(name != mach_port_name_t(MACH_PORT_NULL), "Mach.Port cannot be initialized with MACH_PORT_NULL")
precondition(name != mach_port_name_t(MACH_PORT_NULL),
"Mach.Port cannot be initialized with MACH_PORT_NULL")
self._name = name

if RightType.self == ReceiveRight.self {
precondition(name != 0xFFFFFFFF /* MACH_PORT_DEAD */, "Receive rights cannot be dead names")
precondition(name != 0xFFFFFFFF /* MACH_PORT_DEAD */,
"Receive rights cannot be dead names")

let secret = mach_port_context_t(arc4random())
_machPrecondition(mach_port_guard(mach_task_self_, name, secret, 0))
Expand Down Expand Up @@ -81,17 +83,18 @@ public enum Mach {
}

deinit {
if _name == 0xFFFFFFFF /* MACH_PORT_DEAD */ {
precondition(RightType.self != ReceiveRight.self, "Receive rights cannot be dead names")
_machPrecondition(mach_port_mod_refs(mach_task_self_, _name, MACH_PORT_RIGHT_DEAD_NAME, -1))
if RightType.self == ReceiveRight.self {
precondition(_name != 0xFFFFFFFF /* MACH_PORT_DEAD */,
"Receive rights cannot be dead names")
_machPrecondition(
mach_port_destruct(mach_task_self_, _name, 0, _context)
)
} else {
if RightType.self == ReceiveRight.self {
_machPrecondition(mach_port_destruct(mach_task_self_, _name, -1, _context))
} else if RightType.self == SendRight.self {
_machPrecondition(mach_port_mod_refs(mach_task_self_, _name, MACH_PORT_RIGHT_SEND, -1))
} else if RightType.self == SendOnceRight.self {
_machPrecondition(mach_port_mod_refs(mach_task_self_, _name, MACH_PORT_RIGHT_SEND_ONCE, -1))
}
assert(
RightType.self == SendRight.self ||
RightType.self == SendOnceRight.self
)
_machPrecondition(mach_port_deallocate(mach_task_self_, _name))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While this does simplify the logic, it also means the kernel will no longer validate the right type on deinit. e.g. If you have a Mach.Port<Mach.RecieveRight> incorrectly wrapping a send right, the old code would fail this precondition (the mach call would return KERN_INVALID_RIGHT, whereas this new code would (silently, but not incorrectly) decrement the reference count of the send right.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a good point. I went this way because it had ballooned to 6 branches; there is probably a reasonable alternative somewhere in the middle.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A note from an out-of-band conversation: the previously-used mach_port_mod_refs() calls do not succeed when _name has become a dead name, because it is no longer a send right (or a send-once right.) It is unclear whether there's a reasonable and performant way to re-validate the type of _name in the deinit.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we find a better solution for the deinit, we'll update it in another PR.

}
}
}
Expand Down Expand Up @@ -135,6 +138,8 @@ extension Mach.Port where RightType == Mach.ReceiveRight {
/// The underlying port right will be automatically deallocated when
/// the Mach.Port object is destroyed.
public init(name: mach_port_name_t, context: mach_port_context_t) {
precondition(name != mach_port_name_t(MACH_PORT_NULL),
"Mach.Port cannot be initialized with MACH_PORT_NULL")
self._name = name
self._context = context
}
Expand All @@ -147,9 +152,9 @@ extension Mach.Port where RightType == Mach.ReceiveRight {
@inlinable
public init() {
var storage: mach_port_name_t = mach_port_name_t(MACH_PORT_NULL)
withUnsafeMutablePointer(to: &storage) { storage in
_machPrecondition(mach_port_allocate(mach_task_self_, MACH_PORT_RIGHT_RECEIVE, storage))
}
_machPrecondition(
mach_port_allocate(mach_task_self_, MACH_PORT_RIGHT_RECEIVE, &storage)
)

// name-only init will guard ReceiveRights
self.init(name: storage)
Expand All @@ -167,9 +172,11 @@ extension Mach.Port where RightType == Mach.ReceiveRight {
/// After this function completes, the Mach.Port is destroyed and no longer
/// usable.
@inlinable
public __consuming func relinquish(
public consuming func relinquish(
) -> (name: mach_port_name_t, context: mach_port_context_t) {
return (name: _name, context: _context)
let destructured = (name: _name, context: _context)
discard self
return destructured
}

/// Remove guard and transfer ownership of the underlying port right to
Expand All @@ -187,9 +194,10 @@ extension Mach.Port where RightType == Mach.ReceiveRight {
/// Mach.ReceiveRights. Use relinquish() to avoid the syscall and extract
/// the context value along with the port name.
@inlinable
public __consuming func unguardAndRelinquish() -> mach_port_name_t {
_machPrecondition(mach_port_unguard(mach_task_self_, _name, _context))
return _name
public consuming func unguardAndRelinquish() -> mach_port_name_t {
let (name, context) = self.relinquish()
_machPrecondition(mach_port_unguard(mach_task_self_, name, context))
return name
}

/// Borrow access to the port name in a block that can perform
Expand Down Expand Up @@ -220,17 +228,15 @@ extension Mach.Port where RightType == Mach.ReceiveRight {
var newRight: mach_port_name_t = mach_port_name_t(MACH_PORT_NULL)
var newRightType: mach_port_type_t = MACH_PORT_TYPE_NONE

withUnsafeMutablePointer(to: &newRight) { newRight in
withUnsafeMutablePointer(to: &newRightType) { newRightType in
_machPrecondition(
mach_port_extract_right(mach_task_self_,
_name,
mach_msg_type_name_t(MACH_MSG_TYPE_MAKE_SEND_ONCE),
newRight,
newRightType)
)
}
}
_machPrecondition(
mach_port_extract_right(
mach_task_self_,
_name,
mach_msg_type_name_t(MACH_MSG_TYPE_MAKE_SEND_ONCE),
&newRight,
&newRightType
)
)

// The value of newRight is validated by the Mach.Port initializer
precondition(newRightType == MACH_MSG_TYPE_MOVE_SEND_ONCE)
Expand All @@ -249,7 +255,11 @@ extension Mach.Port where RightType == Mach.ReceiveRight {
let how = MACH_MSG_TYPE_MAKE_SEND

// name is the same because send and recv rights are coalesced
_machPrecondition(mach_port_insert_right(mach_task_self_, _name, _name, mach_msg_type_name_t(how)))
_machPrecondition(
mach_port_insert_right(
mach_task_self_, _name, _name, mach_msg_type_name_t(how)
)
)

return Mach.Port(name: _name)
}
Expand All @@ -261,11 +271,19 @@ extension Mach.Port where RightType == Mach.ReceiveRight {
public var makeSendCount: mach_port_mscount_t {
get {
var status: mach_port_status = mach_port_status()
var size: mach_msg_type_number_t = mach_msg_type_number_t(MemoryLayout<mach_port_status>.size / MemoryLayout<natural_t>.size)
withUnsafeMutablePointer(to: &size) { size in
withUnsafeMutablePointer(to: &status) { status in
let info = UnsafeMutableRawPointer(status).bindMemory(to: integer_t.self, capacity: 1)
_machPrecondition(mach_port_get_attributes(mach_task_self_, _name, MACH_PORT_RECEIVE_STATUS, info, size))
var size = mach_msg_type_number_t(
MemoryLayout<mach_port_status>.size / MemoryLayout<natural_t>.size
)

withUnsafeMutablePointer(to: &status) {
let status = UnsafeMutableBufferPointer(start: $0, count: 1)
status.withMemoryRebound(to: integer_t.self) {
let info = $0.baseAddress
_machPrecondition(
mach_port_get_attributes(
mach_task_self_, _name, MACH_PORT_RECEIVE_STATUS, info, &size
)
)
}
}
return status.mps_mscount
Expand All @@ -288,8 +306,10 @@ extension Mach.Port where RightType == Mach.SendRight {
/// After this function completes, the Mach.Port is destroyed and no longer
/// usable.
@inlinable
public __consuming func relinquish() -> mach_port_name_t {
return _name
public consuming func relinquish() -> mach_port_name_t {
let name = _name
discard self
return name
}

/// Create another send right from a given send right.
Expand All @@ -304,8 +324,10 @@ extension Mach.Port where RightType == Mach.SendRight {
let how = MACH_MSG_TYPE_COPY_SEND

// name is the same because send rights are coalesced
let kr = mach_port_insert_right(mach_task_self_, _name, _name, mach_msg_type_name_t(how))
if kr == KERN_INVALID_CAPABILITY {
let kr = mach_port_insert_right(
mach_task_self_, _name, _name, mach_msg_type_name_t(how)
)
if kr == KERN_INVALID_NAME || kr == KERN_INVALID_CAPABILITY {
throw Mach.PortRightError.deadName
}
_machPrecondition(kr)
Expand All @@ -326,8 +348,10 @@ extension Mach.Port where RightType == Mach.SendOnceRight {
/// After this function completes, the Mach.Port is destroyed and no longer
/// usable.
@inlinable
public __consuming func relinquish() -> mach_port_name_t {
return _name
public consuming func relinquish() -> mach_port_name_t {
let name = _name
discard self
return name
}
}

Expand Down
Loading