11 releases
0.5.1 | Apr 15, 2024 |
---|---|
0.5.0 | Dec 21, 2023 |
0.4.2 | Nov 17, 2023 |
0.4.1 | Aug 13, 2023 |
0.1.0 | May 29, 2021 |
#30 in macOS and iOS APIs
599 downloads per month
61KB
1K
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. Complex types such as arrays and shared memory objects described in greater detail below.
Rust | XPC |
---|---|
i64 | _xpc_type_int64 |
u64 | _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>
. Make sure to use the correct type for file descriptors and Mach ports:
let mut message: HashMap<&str, XPCObject> = HashMap::new();
message.insert(
"domain-port",
XPCObject::from((MachPortType::Send, unsafe {
get_bootstrap_port() as mach_port_t
})),
);
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:
#[test]
fn deserialize_as_wrong_type() {
let an_i64: XPCObject = XPCObject::from(42 as i64);
let as_u64: Result<u64, XPCError> = an_i64.xpc_value();
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.
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
:
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.
XPC Dictionary
Go from a HashMap
to xpc_object_t
with the XPCObject
type:
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>
:
let xpc_object: XPCObject = message.into();
match xpc_object.pipe_routine() {
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:
let xpc_object: XPCObject = message.into();
let response: Result<XPCDictionary, XPCError> = xpc_object
.pipe_routine()
.and_then(|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:
let xpc_object: XPCObject = message.into();
// A string: either "Aqua", "StandardIO", "Background", "LoginWindow", "System"
let response: Result<String, XPCError> = xpc_object
.pipe_routine()
.and_then(|r: XPCObject| r.try_into());
.and_then(|d: XPCDictionary| d.get(&["service", "LimitLoadToSessionType"])
.and_then(|lltst: XPCObject| lltst.xpc_value());
Or, retrieve the service
key (a child XPC Dictionary) from this response:
let xpc_object: XPCObject = message.into();
// A string: either "Aqua", "StandardIO", "Background", "LoginWindow", "System"
let response: Result<XPCDictionary, XPCError> = xpc_object
.pipe_routine()
.and_then(|r: XPCObject| r.try_into());
.and_then(|d: XPCDictionary| d.get_as_dictionary(&["service"]);
let XPCDictionary(hm) = response.unwrap();
let whatever = hm.get("...");
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 as _xpc_type_shmem argument in XPCDictionary
let response = XPCDictionary::new()
.extend(&DUMPSTATE)
.entry("shmem", shmem.xpc_object.clone())
.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);
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.
- After all, it is Apple's launchd :>)
Everything else (C) David Stancu & Contributors 2021
Dependencies
~0.1–2.1MB
~42K SLoC