Various utilities for conveniently dealing with XPC in Rust.
Conversions to/from Rust/XPC objects uses the xpc.h functions documented on Apple Developer using the From
trait. Complex types such as arrays and shared memory objects described in greater detail below.
| Rust | XPC |
|----------------------------------------|----------------------------|
| i64 | xpctypeint64 |
| u64 | _xpctypeuint64 |
| f64 | _xpctypedouble |
| bool | _xpcbooltrue/false |
| Into
Make XPC objects for anything with From<T>
. Make sure to use the correct type for file descriptors and Mach ports:
```rust
let mut message: HashMap<&str, XPCObject> = HashMap::new();
message.insert( "domain-port", XPCObject::from((MachPortType::Send, getbootstrapport() as machportt)), ); ```
Go from an XPC object to value via the TryXPCValue
trait. It checks your object's type via xpc_get_type()
and yields a clear error if you're using the wrong type:
```rust
fn deserializeaswrongtype() {
let ani64: XPCObject = XPCObject::from(42 as i64);
let asu64: Result
XPCObject wraps xpc_object_t
in an Arc
. Drop
will invoke xpc_release()
on objects being dropped with no other strong refs.
NOTE: When using Objective-C blocks with the block crate (e.g. looping over an array), make sure to invoke xpc_retain()
on any object you wish to keep after the closure is dropped, or else the XPC objects in the closure will be dropped as well! See the XPCDictionary
implementation for more details. xpc-sys handles this for you for its conversions.
While we can go from HashMap<&str, XPCObject>
to XPCObject
, it can be a little verbose. A QueryBuilder
trait exposes some builder methods to make building an XPC dictionary a little easier (without all of the into()
s, and some additional error checking).
To write the query for launchctl list
:
```rust let LIST_SERVICES: XPCDictionary = XPCDictionary::new() // "list com.apple.Spotlight" (if specified) // .entry("name", "com.apple.Spotlight"); .entry("subsystem", 3 as u64) .entry("handle", 0 as u64) .entry("routine", 815 as u64) .entry("legacy", true);
let reply: Result<XPCDictionary, XPCError> = XPCDictionary::new()
// LIST_SERVICES is a proto
.extend(&LIST_SERVICES)
// Specify the domain type, or fall back on requester domain
.with_domain_type_or_default(Some(domain_type))
.entry_if_present("name", name)
.pipe_routine_with_error_handling();
```
In addition to checking errno
is 0, pipe_routine_with_error_handling
also looks for possible error
and errors
keys in the response dictionary and provides an Err()
with xpc_strerror
contents.
Go from a HashMap
to xpc_object_t
with the XPCObject
type:
```rust let mut message: HashMap<&str, XPCObject> = HashMap::new(); message.insert("type", XPCObject::from(1 as u64)); message.insert("handle", XPCObject::from(0 as u64)); message.insert("subsystem", XPCObject::from(3 as u64)); message.insert("routine", XPCObject::from(815 as u64)); message.insert("legacy", XPCObject::from(true));
let xpc_object: XPCObject = message.into(); ```
Call xpc_pipe_routine
and receive Result<XPCObject, XPCError>
:
```rust let xpc_object: XPCObject = message.into();
match xpcobject.piperoutine() { Ok(xpc_object) => { /* do stuff and things / }, Err(XPCError::PipeError(err)) => { / err is a string w/strerror(errno) */ } } ```
The response is likely an XPC dictionary -- go back to a HashMap:
```rust
let xpcobject: XPCObject = message.into();
let response: Result
let XPCDictionary(hm) = response.unwrap(); let whatever = hm.get("..."); ```
Response dictionaries can be nested, so XPCDictionary
has a helper included for this scenario:
```rust let xpc_object: XPCObject = message.into();
// A string: either "Aqua", "StandardIO", "Background", "LoginWindow", "System"
let response: Result
Or, retrieve the service
key (a child XPC Dictionary) from this response:
```rust let xpc_object: XPCObject = message.into();
// A string: either "Aqua", "StandardIO", "Background", "LoginWindow", "System"
let response: Result
let XPCDictionary(hm) = response.unwrap(); let whatever = hm.get("..."); ```
An XPC array can be made from either Vec<XPCObject>
or Vec<Into<XPCObject>>
:
```rust let xpc_array = XPCObject::from(vec![XPCObject::from("eins"), XPCObject::from("zwei"), XPCObject::from("polizei")]);
let xpc_array = XPCObject::from(vec!["eins", "zwei", "polizei"]); ```
Go back to Vec
using xpc_value
:
rust
let rs_vec: Vec<XPCObject> = xpc_array.xpc_value().unwrap();
Make XPC shared memory objects by providing a size and vm_allocate/mmap flags. vm_allocate
is used under the hood:
```rust let shmem = XPCShmem::newtaskself( 0x1400000, i32::tryfrom(MAPSHARED).expect("Must conv flags"), )?;
// Use as xpctypeshmem argument in XPCDictionary let response = XPCDictionary::new() .extend(&DUMPSTATE) .entry("shmem", shmem.xpcobject.clone()) .piperoutinewitherrorhandling()?; ```
To work with the shmem region, use slice_from_raw_parts
:
```rust let bytes: &[u8] = unsafe { &*slicefromraw_parts(shmem.region as *mut u8, size) };
// Make a string from bytes in the shmem let mut heylookastring = String::new(); bytes.readto_string(buf); ```
A big thanks to these open source projects and general resources:
xpc_*_applier_t
Everything else (C) David Stancu & Contributors 2021