xpc-sys

Rust crates.io

Various utilities for conveniently dealing with XPC in Rust.

Getting Started

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 | _xpctypestring | | HashMap, Into> | _xpctypedictionary | | Vec> | _xpctypearray | | std::os::unix::prelude::RawFd | _xpctypefd | | (MachPortType::Send, machportt) | _xpctypemachsend | | (MachPortType::Recv, machportt) | xpctypemachrecv | | XPCShmem | xpctype_shmem |

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

[test]

fn deserializeaswrongtype() { let ani64: XPCObject = XPCObject::from(42 as i64); let asu64: Result = ani64.xpcvalue(); asserteq!( asu64.err().unwrap(), XPCValueError("Cannot get int64 as uint64".tostring()) ); } ```

Top

Object lifecycle

XPCObject wraps a xpc_object_t:

rust pub struct XPCObject(pub xpc_object_t, pub XPCType);

When it is dropped, xpc_release is called.

Top

QueryBuilder

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.

Top

XPC Dictionary

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 = xpcobject .piperoutine() .andthen(|r| r.try_into());

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 = xpcobject .piperoutine() .andthen(|r: XPCObject| r.tryinto()); .andthen(|d: XPCDictionary| d.get(&["service", "LimitLoadToSessionType"]) .andthen(|lltst: XPCObject| lltst.xpc_value()); ```

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 = xpcobject .piperoutine() .andthen(|r: XPCObject| r.tryinto()); .andthen(|d: XPCDictionary| d.getas_dictionary(&["service"]);

let XPCDictionary(hm) = response.unwrap(); let whatever = hm.get("..."); ```

Top

XPC Array

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();

Top

XPC Shmem

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); ```

Top

Credits

A big thanks to these open source projects and general resources:

Everything else (C) David Stancu & Contributors 2021