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, unsafe { 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 a xpc_object_t
:
rust
pub struct XPCObject(pub xpc_object_t, pub XPCType);
When it is dropped, xpc_release
is called.
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 to create the memory region, and vm_deallocate
when XPCShmem
is dropped.
```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