#apple #object #routines #back #go #conveniently #wrapper #call #xpc #xpc-dictionary

sys xpc-sys

Conveniently call routines with wrappers for xpc_pipe_routine() and go from Rust types to XPC objects and back!

12 releases (1 stable)

1.0.0 Jun 4, 2025
0.5.1 Apr 15, 2024
0.5.0 Dec 21, 2023
0.4.2 Nov 17, 2023
0.3.1 Jun 15, 2021

#34 in macOS and iOS APIs

33 downloads per month

MIT license

63KB
1.5K SLoC

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.

Rust XPC
i64/i32 _xpc_type_int64
u64/u32 _xpc_type_uint64
f64 _xpc_type_double
bool _xpc_bool_true/false
Into _xpc_type_string
HashMap<Into, Into> _xpc_type_dictionary
Vec<Into> _xpc_type_array
std::os::unix::prelude::RawFd _xpc_type_fd
(MachPortType::Send, mach_port_t) _xpc_type_mach_send
(MachPortType::Recv, mach_port_t) _xpc_type_mach_recv
XPCShmem _xpc_type_shmem

Make XPC objects for anything with From<T>. XPCShmem and file descriptors have their own constructors:

use xpc_sys::api::dict_builder::DictBuilder;

let fd = unsafe { XPCObject::from_raw_fd(42) };

let shmem = XPCShmem::allocate_task_self(
  1_000_000,
  MAP_SHARED,
)?;

// pub type XPCHashMap = HashMap<String, Arc<XPCObject>>
let dict: XPCHashMap = HashMap::new()
    .entry("fd", fd)
    .entry("shmem", &shmem.xpc_object);

Go from an XPC object to value via to_rust() from the TryXPCIntoRust trait. Object types are checked with xpc_get_type() to yield a clear error if trying to read as the wrong type:

#[test]
fn deserialize_as_wrong_type() {
    let an_i64: XPCObject = XPCObject::from(42 as i64);
    let as_u64: Result<u64, XPCError> = an_i64.to_rust();
    assert_eq!(
        as_u64.err().unwrap(),
        XPCValueError("Cannot get int64 as uint64".to_string())
    );
}

Top

Object lifecycle

XPCObject wraps a xpc_object_t:

pub struct XPCObject(pub xpc_object_t, pub XPCType);

When it is dropped, xpc_release is called.

Top

XPC Dictionary

Go from a HashMap to xpc_object_t:

use xpc_sys::api::dict_builder::DictBuilder;

// pub type XPCHashMap = HashMap<String, Arc<XPCObject>>
let dict: XPCHashMap = HashMap::new()
    .entry("type", 1u64);
    .entry("handle", 0u64);
    .entry("subsystem", 3u64);
    .entry("routine", 815u64);
    .entry("legacy", true);

let xpc_object: XPCObject = dict.into()
let ptr: xpc_object_t = xpc_object.as_ptr();

Go from XPCObject back to HashMap:

let xpc_object: XPCObject = unsafe { XPCObject::from_raw(some_pointer) };

// Error if something is wrong during conversion (e.g. the pointer is not a XPC dictionary)
match xpc_object.to_rust() {
  Ok(dict) => dict.get("some_key"),
  Err(e) => ...
}

Top

XPC Array

An XPC array can be made from either Vec<XPCObject> or Vec<Into<XPCObject>>:

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:

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.

let shmem = XPCShmem::new_task_self(
    0x1400000,
    i32::try_from(MAP_SHARED).expect("Must conv flags"),
)?;

// Use _xpc_type_shmem value in XPC Dictionary
let response = HashMap::new()
    .entry("shmem", &shmem)
    .pipe_routine_with_error_handling()?;

To work with the shmem region, use slice_from_raw_parts:

let bytes: &[u8] = unsafe {
    &*slice_from_raw_parts(shmem.region as *mut u8, size)
};

// Make a string from bytes in the shmem
let mut hey_look_a_string = String::new();
bytes.read_to_string(buf);

Top

API

The following XPC functions have Rust friendly wrappers, all of which return Result<XPCObject, XPCError>:

Function Rust API
xpc_pipe_routine api::pipe_routine::pipe_routine
xpc_pipe_routine_with_flags api::pipe_routine::pipe_routine_with_flags
_xpc_pipe_interface_routine api::pipe_routine::pipe_interface_routine

If desired, errors in the XPC reply can be handled by chaining api::handle_reply_dict_errors onto the pipe routine call.

This is an example of sending launchctl bootout via the XPC bootstrap pipe:

let dict = HashMap::new()
    .entry("name", label_string)
    .entry("no-einprogress", true)
    // Current user UID
    .entry("handle", 501u64)
    // Domain
    .entry("type", 8u64);

let reply: XPCHashMap = pipe_interface_routine(
    // Some(xpc_pipe_t) or fall back to `get_xpc_bootstrap_pipe()`
    None,
    // routine
    801,
    dict,
    // flags (or fall back to 0)
    None
)
    // Check for errors in response XPC dictionary (if desired)
    .and_then(handle_reply_dict_errors)
    // Convert reply to a Rust hash map
    .and_then(|o| o.to_rust())

Top

Credits

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

Dependencies

~0.2–2.3MB
~45K SLoC