mirror of
https://github.com/jkcoxson/idevice.git
synced 2026-03-02 06:26:15 +01:00
RemoteXPC bindings
This commit is contained in:
204
ffi/examples/remotexpc.c
Normal file
204
ffi/examples/remotexpc.c
Normal file
@@ -0,0 +1,204 @@
|
|||||||
|
// Jackson Coxson
|
||||||
|
|
||||||
|
#include "idevice.h"
|
||||||
|
#include <arpa/inet.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
void print_service_details(XPCServiceHandle *service) {
|
||||||
|
printf(" Service Details:\n");
|
||||||
|
printf(" Entitlement: %s\n", service->entitlement);
|
||||||
|
printf(" Port: %d\n", service->port);
|
||||||
|
printf(" Uses Remote XPC: %s\n",
|
||||||
|
service->uses_remote_xpc ? "true" : "false");
|
||||||
|
printf(" Service Version: %lld\n", service->service_version);
|
||||||
|
|
||||||
|
if (service->features_count > 0) {
|
||||||
|
printf(" Features:\n");
|
||||||
|
for (size_t i = 0; i < service->features_count; i++) {
|
||||||
|
printf(" - %s\n", service->features[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
// Initialize logger
|
||||||
|
idevice_init_logger(Debug, Disabled, NULL);
|
||||||
|
|
||||||
|
// Create the socket address (replace with your device's IP)
|
||||||
|
struct sockaddr_in addr;
|
||||||
|
memset(&addr, 0, sizeof(addr));
|
||||||
|
addr.sin_family = AF_INET;
|
||||||
|
addr.sin_port = htons(LOCKDOWN_PORT);
|
||||||
|
inet_pton(AF_INET, "10.7.0.2", &addr.sin_addr);
|
||||||
|
|
||||||
|
// Read pairing file (replace with your pairing file path)
|
||||||
|
IdevicePairingFile *pairing_file = NULL;
|
||||||
|
IdeviceErrorCode err =
|
||||||
|
idevice_pairing_file_read("pairing_file.plist", &pairing_file);
|
||||||
|
if (err != IdeviceSuccess) {
|
||||||
|
fprintf(stderr, "Failed to read pairing file: %d\n", err);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*****************************************************************
|
||||||
|
* TCP Provider and CoreDeviceProxy Test
|
||||||
|
*****************************************************************/
|
||||||
|
printf("=== Testing TCP Provider and CoreDeviceProxy ===\n");
|
||||||
|
|
||||||
|
// Create TCP provider
|
||||||
|
TcpProviderHandle *tcp_provider = NULL;
|
||||||
|
err = idevice_tcp_provider_new((struct sockaddr *)&addr, pairing_file,
|
||||||
|
"CoreDeviceProxyTest", &tcp_provider);
|
||||||
|
if (err != IdeviceSuccess) {
|
||||||
|
fprintf(stderr, "Failed to create TCP provider: %d\n", err);
|
||||||
|
idevice_pairing_file_free(pairing_file);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Connect to CoreDeviceProxy
|
||||||
|
CoreDeviceProxyHandle *core_device = NULL;
|
||||||
|
err = core_device_proxy_connect_tcp(tcp_provider, &core_device);
|
||||||
|
if (err != IdeviceSuccess) {
|
||||||
|
fprintf(stderr, "Failed to connect to CoreDeviceProxy: %d\n", err);
|
||||||
|
tcp_provider_free(tcp_provider);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
tcp_provider_free(tcp_provider);
|
||||||
|
|
||||||
|
// Get client parameters
|
||||||
|
uint16_t mtu;
|
||||||
|
char *address = NULL;
|
||||||
|
char *netmask = NULL;
|
||||||
|
err = core_device_proxy_get_client_parameters(core_device, &mtu, &address,
|
||||||
|
&netmask);
|
||||||
|
if (err != IdeviceSuccess) {
|
||||||
|
fprintf(stderr, "Failed to get client parameters: %d\n", err);
|
||||||
|
core_device_proxy_free(core_device);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
printf("Client Parameters:\n");
|
||||||
|
printf(" MTU: %d\n", mtu);
|
||||||
|
printf(" Address: %s\n", address);
|
||||||
|
printf(" Netmask: %s\n", netmask);
|
||||||
|
idevice_string_free(address);
|
||||||
|
idevice_string_free(netmask);
|
||||||
|
|
||||||
|
// Get server address
|
||||||
|
char *server_address = NULL;
|
||||||
|
err = core_device_proxy_get_server_address(core_device, &server_address);
|
||||||
|
if (err != IdeviceSuccess) {
|
||||||
|
fprintf(stderr, "Failed to get server address: %d\n", err);
|
||||||
|
core_device_proxy_free(core_device);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
printf("Server Address: %s\n", server_address);
|
||||||
|
idevice_string_free(server_address);
|
||||||
|
|
||||||
|
// Get server RSD port
|
||||||
|
uint16_t rsd_port;
|
||||||
|
err = core_device_proxy_get_server_rsd_port(core_device, &rsd_port);
|
||||||
|
if (err != IdeviceSuccess) {
|
||||||
|
fprintf(stderr, "Failed to get server RSD port: %d\n", err);
|
||||||
|
core_device_proxy_free(core_device);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
printf("Server RSD Port: %d\n", rsd_port);
|
||||||
|
|
||||||
|
// Create TCP tunnel adapter
|
||||||
|
AdapterHandle *adapter = NULL;
|
||||||
|
err = core_device_proxy_create_tcp_adapter(core_device, &adapter);
|
||||||
|
if (err != IdeviceSuccess) {
|
||||||
|
fprintf(stderr, "Failed to create TCP adapter: %d\n", err);
|
||||||
|
} else {
|
||||||
|
printf("Successfully created TCP tunnel adapter\n");
|
||||||
|
}
|
||||||
|
err = adapter_connect(adapter, rsd_port);
|
||||||
|
if (err != IdeviceSuccess) {
|
||||||
|
fprintf(stderr, "Failed to connect to RSD port: %d\n", err);
|
||||||
|
} else {
|
||||||
|
printf("Successfully connected to RSD port\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
/*****************************************************************
|
||||||
|
* XPC Device Test
|
||||||
|
*****************************************************************/
|
||||||
|
printf("\n=== Testing XPC Device ===\n");
|
||||||
|
|
||||||
|
// Create XPC device
|
||||||
|
XPCDeviceAdapterHandle *xpc_device = NULL;
|
||||||
|
err = xpc_device_new(adapter, &xpc_device);
|
||||||
|
if (err != IdeviceSuccess) {
|
||||||
|
fprintf(stderr, "Failed to create XPC device: %d\n", err);
|
||||||
|
core_device_proxy_free(core_device);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// List all services
|
||||||
|
char **service_names = NULL;
|
||||||
|
size_t service_count = 0;
|
||||||
|
err =
|
||||||
|
xpc_device_get_service_names(xpc_device, &service_names, &service_count);
|
||||||
|
if (err != IdeviceSuccess) {
|
||||||
|
fprintf(stderr, "Failed to get service names: %d\n", err);
|
||||||
|
xpc_device_free(xpc_device);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("Available Services (%zu):\n", service_count);
|
||||||
|
for (size_t i = 0; i < service_count; i++) {
|
||||||
|
printf("- %s\n", service_names[i]);
|
||||||
|
|
||||||
|
// Get service details for each service
|
||||||
|
XPCServiceHandle *service = NULL;
|
||||||
|
err = xpc_device_get_service(xpc_device, service_names[i], &service);
|
||||||
|
if (err == IdeviceSuccess) {
|
||||||
|
print_service_details(service);
|
||||||
|
xpc_service_free(service);
|
||||||
|
} else {
|
||||||
|
printf(" Failed to get service details: %d\n", err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
xpc_device_free_service_names(service_names, service_count);
|
||||||
|
|
||||||
|
// Test getting a specific service
|
||||||
|
const char *test_service_name = "com.apple.internal.dt.remote.debugproxy";
|
||||||
|
XPCServiceHandle *test_service = NULL;
|
||||||
|
err = xpc_device_get_service(xpc_device, test_service_name, &test_service);
|
||||||
|
if (err == IdeviceSuccess) {
|
||||||
|
printf("\nSuccessfully retrieved service '%s':\n", test_service_name);
|
||||||
|
print_service_details(test_service);
|
||||||
|
xpc_service_free(test_service);
|
||||||
|
} else {
|
||||||
|
printf("\nFailed to get service '%s': %d\n", test_service_name, err);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*****************************************************************
|
||||||
|
* Adapter return
|
||||||
|
*****************************************************************/
|
||||||
|
AdapterHandle *adapter_return = NULL;
|
||||||
|
err = xpc_device_adapter_into_inner(xpc_device, &adapter_return);
|
||||||
|
if (err != IdeviceSuccess) {
|
||||||
|
fprintf(stderr, "Failed to extract adapter: %d\n", err);
|
||||||
|
} else {
|
||||||
|
printf("Successfully extracted adapter\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
err = adapter_close(adapter_return);
|
||||||
|
if (err != IdeviceSuccess) {
|
||||||
|
fprintf(stderr, "Failed to close adapter port: %d\n", err);
|
||||||
|
} else {
|
||||||
|
printf("Successfully closed adapter port\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
/*****************************************************************
|
||||||
|
* Cleanup
|
||||||
|
*****************************************************************/
|
||||||
|
adapter_free(adapter_return);
|
||||||
|
|
||||||
|
printf("\nAll tests completed successfully!\n");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -9,6 +9,7 @@ pub mod logging;
|
|||||||
pub mod mounter;
|
pub mod mounter;
|
||||||
mod pairing_file;
|
mod pairing_file;
|
||||||
pub mod provider;
|
pub mod provider;
|
||||||
|
pub mod remotexpc;
|
||||||
pub mod usbmuxd;
|
pub mod usbmuxd;
|
||||||
pub mod util;
|
pub mod util;
|
||||||
|
|
||||||
|
|||||||
288
ffi/src/remotexpc.rs
Normal file
288
ffi/src/remotexpc.rs
Normal file
@@ -0,0 +1,288 @@
|
|||||||
|
// Jackson Coxson
|
||||||
|
|
||||||
|
use std::ffi::{CStr, CString, c_char};
|
||||||
|
|
||||||
|
use idevice::{tcp::adapter::Adapter, xpc::XPCDevice};
|
||||||
|
|
||||||
|
use crate::{IdeviceErrorCode, RUNTIME, core_device_proxy::AdapterHandle};
|
||||||
|
|
||||||
|
/// Opaque handle to an XPCDevice
|
||||||
|
pub struct XPCDeviceAdapterHandle(pub XPCDevice<Adapter>);
|
||||||
|
|
||||||
|
/// Opaque handle to an XPCService
|
||||||
|
#[repr(C)]
|
||||||
|
pub struct XPCServiceHandle {
|
||||||
|
pub entitlement: *mut c_char,
|
||||||
|
pub port: u16,
|
||||||
|
pub uses_remote_xpc: bool,
|
||||||
|
pub features: *mut *mut c_char,
|
||||||
|
pub features_count: usize,
|
||||||
|
pub service_version: i64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl XPCServiceHandle {
|
||||||
|
pub fn new(
|
||||||
|
entitlement: *mut c_char,
|
||||||
|
port: u16,
|
||||||
|
uses_remote_xpc: bool,
|
||||||
|
features: *mut *mut c_char,
|
||||||
|
features_count: usize,
|
||||||
|
service_version: i64,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
entitlement,
|
||||||
|
port,
|
||||||
|
uses_remote_xpc,
|
||||||
|
features,
|
||||||
|
features_count,
|
||||||
|
service_version,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a new XPCDevice from an adapter
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
/// * [`adapter`] - The adapter to use for communication
|
||||||
|
/// * [`device`] - Pointer to store the newly created XPCDevice handle
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// An error code indicating success or failure
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
/// `adapter` must be a valid pointer to a handle allocated by this library
|
||||||
|
/// `device` must be a valid pointer to a location where the handle will be stored
|
||||||
|
#[unsafe(no_mangle)]
|
||||||
|
pub unsafe extern "C" fn xpc_device_new(
|
||||||
|
adapter: *mut AdapterHandle,
|
||||||
|
device: *mut *mut XPCDeviceAdapterHandle,
|
||||||
|
) -> IdeviceErrorCode {
|
||||||
|
if adapter.is_null() || device.is_null() {
|
||||||
|
return IdeviceErrorCode::InvalidArg;
|
||||||
|
}
|
||||||
|
|
||||||
|
let adapter = unsafe { Box::from_raw(adapter) };
|
||||||
|
let res = RUNTIME.block_on(async move { XPCDevice::new(adapter.0).await });
|
||||||
|
|
||||||
|
match res {
|
||||||
|
// we have to unwrap res to avoid just getting a reference
|
||||||
|
Ok(_) => {
|
||||||
|
let boxed = Box::new(XPCDeviceAdapterHandle(res.unwrap()));
|
||||||
|
unsafe { *device = Box::into_raw(boxed) };
|
||||||
|
IdeviceErrorCode::IdeviceSuccess
|
||||||
|
}
|
||||||
|
Err(e) => e.into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Frees an XPCDevice handle
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
/// * [`handle`] - The handle to free
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
/// `handle` must be a valid pointer to a handle allocated by this library or NULL
|
||||||
|
#[unsafe(no_mangle)]
|
||||||
|
pub unsafe extern "C" fn xpc_device_free(handle: *mut XPCDeviceAdapterHandle) {
|
||||||
|
if !handle.is_null() {
|
||||||
|
let _ = unsafe { Box::from_raw(handle) };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets a service by name from the XPCDevice
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
/// * [`handle`] - The XPCDevice handle
|
||||||
|
/// * [`service_name`] - The name of the service to get
|
||||||
|
/// * [`service`] - Pointer to store the newly created XPCService handle
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// An error code indicating success or failure
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
/// `handle` must be a valid pointer to a handle allocated by this library
|
||||||
|
/// `service_name` must be a valid null-terminated C string
|
||||||
|
/// `service` must be a valid pointer to a location where the handle will be stored
|
||||||
|
#[unsafe(no_mangle)]
|
||||||
|
pub unsafe extern "C" fn xpc_device_get_service(
|
||||||
|
handle: *mut XPCDeviceAdapterHandle,
|
||||||
|
service_name: *const c_char,
|
||||||
|
service: *mut *mut XPCServiceHandle,
|
||||||
|
) -> IdeviceErrorCode {
|
||||||
|
if handle.is_null() || service_name.is_null() || service.is_null() {
|
||||||
|
return IdeviceErrorCode::InvalidArg;
|
||||||
|
}
|
||||||
|
|
||||||
|
let device = unsafe { &(*handle).0 };
|
||||||
|
let service_name_cstr = unsafe { CStr::from_ptr(service_name) };
|
||||||
|
let service_name = match service_name_cstr.to_str() {
|
||||||
|
Ok(s) => s,
|
||||||
|
Err(_) => return IdeviceErrorCode::InvalidArg,
|
||||||
|
};
|
||||||
|
|
||||||
|
let xpc_service = match device.services.get(service_name) {
|
||||||
|
Some(s) => s,
|
||||||
|
None => return IdeviceErrorCode::ServiceNotFound,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Convert features to C array if they exist
|
||||||
|
let (features_ptr, features_count) = if let Some(features) = &xpc_service.features {
|
||||||
|
let mut features_vec: Vec<*mut c_char> = features
|
||||||
|
.iter()
|
||||||
|
.map(|f| CString::new(f.as_str()).unwrap().into_raw())
|
||||||
|
.collect();
|
||||||
|
features_vec.shrink_to_fit();
|
||||||
|
|
||||||
|
let mut features_vec = Box::new(features_vec);
|
||||||
|
let features_ptr = features_vec.as_mut_ptr();
|
||||||
|
let features_len = features_vec.len();
|
||||||
|
|
||||||
|
Box::leak(features_vec);
|
||||||
|
(features_ptr, features_len)
|
||||||
|
} else {
|
||||||
|
(std::ptr::null_mut(), 0)
|
||||||
|
};
|
||||||
|
|
||||||
|
let boxed = Box::new(XPCServiceHandle {
|
||||||
|
entitlement: CString::new(xpc_service.entitlement.as_str())
|
||||||
|
.unwrap()
|
||||||
|
.into_raw(),
|
||||||
|
port: xpc_service.port,
|
||||||
|
uses_remote_xpc: xpc_service.uses_remote_xpc,
|
||||||
|
features: features_ptr,
|
||||||
|
features_count,
|
||||||
|
service_version: xpc_service.service_version.unwrap_or(-1),
|
||||||
|
});
|
||||||
|
|
||||||
|
unsafe { *service = Box::into_raw(boxed) };
|
||||||
|
IdeviceErrorCode::IdeviceSuccess
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the adapter in the RemoteXPC Device
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
/// * [`handle`] - The handle to get the adapter from
|
||||||
|
/// * [`adapter`] - The newly allocated AdapterHandle
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
/// `handle` must be a valid pointer to a handle allocated by this library or NULL, and never used again
|
||||||
|
#[unsafe(no_mangle)]
|
||||||
|
pub unsafe extern "C" fn xpc_device_adapter_into_inner(
|
||||||
|
handle: *mut XPCDeviceAdapterHandle,
|
||||||
|
adapter: *mut *mut AdapterHandle,
|
||||||
|
) -> IdeviceErrorCode {
|
||||||
|
if handle.is_null() {
|
||||||
|
return IdeviceErrorCode::InvalidArg;
|
||||||
|
}
|
||||||
|
let device = unsafe { Box::from_raw(handle).0 };
|
||||||
|
let adapter_obj = device.into_inner();
|
||||||
|
let boxed = Box::new(AdapterHandle(adapter_obj));
|
||||||
|
unsafe { *adapter = Box::into_raw(boxed) };
|
||||||
|
IdeviceErrorCode::IdeviceSuccess
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Frees an XPCService handle
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
/// * [`handle`] - The handle to free
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
/// `handle` must be a valid pointer to a handle allocated by this library or NULL
|
||||||
|
#[unsafe(no_mangle)]
|
||||||
|
pub unsafe extern "C" fn xpc_service_free(handle: *mut XPCServiceHandle) {
|
||||||
|
if !handle.is_null() {
|
||||||
|
let handle = unsafe { Box::from_raw(handle) };
|
||||||
|
|
||||||
|
// Free the entitlement string
|
||||||
|
if !handle.entitlement.is_null() {
|
||||||
|
let _ = unsafe { CString::from_raw(handle.entitlement) };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Free the features array
|
||||||
|
if !handle.features.is_null() {
|
||||||
|
for i in 0..handle.features_count {
|
||||||
|
let feature_ptr = unsafe { *handle.features.add(i) };
|
||||||
|
if !feature_ptr.is_null() {
|
||||||
|
let _ = unsafe { CString::from_raw(feature_ptr) };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let _ = unsafe {
|
||||||
|
Vec::from_raw_parts(
|
||||||
|
handle.features,
|
||||||
|
handle.features_count,
|
||||||
|
handle.features_count,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets the list of available service names
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
/// * [`handle`] - The XPCDevice handle
|
||||||
|
/// * [`names`] - Pointer to store the array of service names
|
||||||
|
/// * [`count`] - Pointer to store the number of services
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// An error code indicating success or failure
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
/// `handle` must be a valid pointer to a handle allocated by this library
|
||||||
|
/// `names` must be a valid pointer to a location where the array will be stored
|
||||||
|
/// `count` must be a valid pointer to a location where the count will be stored
|
||||||
|
#[unsafe(no_mangle)]
|
||||||
|
pub unsafe extern "C" fn xpc_device_get_service_names(
|
||||||
|
handle: *mut XPCDeviceAdapterHandle,
|
||||||
|
names: *mut *mut *mut c_char,
|
||||||
|
count: *mut usize,
|
||||||
|
) -> IdeviceErrorCode {
|
||||||
|
if handle.is_null() || names.is_null() || count.is_null() {
|
||||||
|
return IdeviceErrorCode::InvalidArg;
|
||||||
|
}
|
||||||
|
|
||||||
|
let device = unsafe { &(*handle).0 };
|
||||||
|
let service_names: Vec<CString> = device
|
||||||
|
.services
|
||||||
|
.keys()
|
||||||
|
.map(|k| CString::new(k.as_str()).unwrap())
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let mut name_ptrs: Vec<*mut c_char> = service_names.into_iter().map(|s| s.into_raw()).collect();
|
||||||
|
|
||||||
|
if name_ptrs.is_empty() {
|
||||||
|
unsafe {
|
||||||
|
*names = std::ptr::null_mut();
|
||||||
|
*count = 0;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
name_ptrs.shrink_to_fit();
|
||||||
|
unsafe {
|
||||||
|
*names = name_ptrs.as_mut_ptr();
|
||||||
|
*count = name_ptrs.len();
|
||||||
|
}
|
||||||
|
std::mem::forget(name_ptrs);
|
||||||
|
}
|
||||||
|
|
||||||
|
IdeviceErrorCode::IdeviceSuccess
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Frees a list of service names
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
/// * [`names`] - The array of service names to free
|
||||||
|
/// * [`count`] - The number of services in the array
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
/// `names` must be a valid pointer to an array of `count` C strings
|
||||||
|
#[unsafe(no_mangle)]
|
||||||
|
pub unsafe extern "C" fn xpc_device_free_service_names(names: *mut *mut c_char, count: usize) {
|
||||||
|
if !names.is_null() && count > 0 {
|
||||||
|
let names_vec = unsafe { Vec::from_raw_parts(names, count, count) };
|
||||||
|
for name in names_vec {
|
||||||
|
if !name.is_null() {
|
||||||
|
let _ = unsafe { CString::from_raw(name) };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user