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
63KB
1.5K
SLoC
xpc-sys
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())
);
}
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.
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) => ...
}
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();
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);
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())
Credits
A big thanks to these open source projects and general resources:
- block Obj-C block support, necessary for any XPC function taking
xpc_*_applier_t
- Cursive
- tokio
- plist
- notify
- bitflags
- libc
- lazy_static
- xcrun
- Apple Developer XPC services
- Apple Developer XPC API reference
- MOXIL / launjctl
- geosnow - A Long Evening With macOS' sandbox
- Bits of launchd - @5aelo
- Audit tokens explained (e.g. ASID)
- objc.io XPC guide
- Fortinet XPC RE article
- The various source links found in comments, from Chrome's sandbox and other headers with definitions for private API functions.
Dependencies
~0.2–2.3MB
~45K SLoC