I'm currently developing a crate that provides primitives to manage userspace Mach port handles (aka names) and exchange Mach messages on Darwin platforms (only macOS and iOS for now). The plan is to eventually integrate it into tokio to provide asynchronous Mach port communications.
Why?
Mach ports are an IPC mechanism Darwin platforms use for IPC almost exclusively. Userspace daemons tend to use either XPC or Mach ports directly, all communication with the kernel drivers is done through the I/O Kit framework which is also based on Mach ports.
Currently wrappers for APIs avoided using Mach ports directly by using frameworks provided by Apple to avoid the complexity of Mach messages which is usually what you want, but this also has drawbacks:
- You can't use Rust's async capabilities. While you technically can use asynchronous interfaces, you're forced to either use Apple's event loops (libdispatch and CFRunLoop), but you can't use tokio or some other event loop. This complicates usage of some nice features like asynchronously waiting for devices to connect or detecting other system state changes. In case you decide to use these features anyway, you'll have to bridge the Apple's event loops with what you use adding an additional layer of complexity. Some I/O kit drivers only provide async interfaces which is also a pain. Having such a library is a first step to integration of Mach ports into mio and later tokio (this can be done using kqueue which is used by tokio on Darwin anyway).
- You're missing out on a great native IPC mechanism. Mach ports provide a lot of interesting capabilities like passing handles to certain system resources (which also happen to be represented by Mach ports) through messages or sharing virtual memory regions with copy-on-wriite semantics. It's also a nice way to build faster IPC channels on Darwin.
What's already there
Currently I've implemented a bare minimum required to exchange Mach messages and manage the names. The library can:
- Manage references to almost all kinds of port rights. Port sets are not supported currently.
- Build and send Mach messages with inline data and port descriptors. Out-of-line ports and data as well as guarded port descriptors are not supported currently.
- Receive and parse Mach messages with the same properties as in 2.
Open questions
- Handling OOL ports and data descriptors.
- Error handling. How to best handle unexpected errors returned from Mach APIs? Should it be different from the expected errors? What's the best error type design in that case (looking at
msg::error
module)? - Guarded ports and how to best handle them.
- Various trailers and the types they expose. These can generally be skipped and won't be provided by the kernel unless the caller explicitly asks for that.
-
AsRawName
/IntoRawName
traits design. Is it optimal? - Some more advanced APIs like sending and receiving a message in one call to
mach_msg
.
Conclusion
I'd also like other suggestions, definitely open to name bikeshedding right now, suggestions on docs, anything.