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, 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 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.

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

Top

Credits

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

Everything else (C) David Stancu & Contributors 2021