From 32b175028f1bb79b8ec5a921d020a67599f41322 Mon Sep 17 00:00:00 2001 From: Jackson Coxson Date: Tue, 8 Apr 2025 17:29:58 -0600 Subject: [PATCH] afc bindings --- ffi/examples/afc.c | 354 ++++++++++++++++++++ ffi/src/afc.rs | 801 +++++++++++++++++++++++++++++++++++++++++++++ ffi/src/lib.rs | 1 + 3 files changed, 1156 insertions(+) create mode 100644 ffi/examples/afc.c create mode 100644 ffi/src/afc.rs diff --git a/ffi/examples/afc.c b/ffi/examples/afc.c new file mode 100644 index 0000000..7c7a5d4 --- /dev/null +++ b/ffi/examples/afc.c @@ -0,0 +1,354 @@ +// Jackson Coxson + +#include "idevice.h" +#include +#include +#include +#include +#include +#include + +void print_usage() { + printf("Usage: afc_tool [options] [command] [args]\n"); + printf("Options:\n"); + printf(" --ip IP_ADDRESS Device IP address (default: 10.7.0.2)\n"); + printf(" --pairing FILE Pairing file path (default: " + "pairing_file.plist)\n"); + printf(" --udid UDID Device UDID (optional)\n"); + printf("\nCommands:\n"); + printf(" list PATH List directory contents\n"); + printf(" mkdir PATH Create directory\n"); + printf(" download SRC DEST Download file from device\n"); + printf(" upload SRC DEST Upload file to device\n"); + printf(" remove PATH Remove file or directory\n"); + printf(" remove_all PATH Recursively remove directory\n"); + printf(" info PATH Get file information\n"); + printf(" device_info Get device filesystem information\n"); + printf(" help Show this help message\n"); +} + +int read_file(const char *filename, uint8_t **data, size_t *length) { + FILE *file = fopen(filename, "rb"); + if (!file) { + perror("Failed to open file"); + return 0; + } + + fseek(file, 0, SEEK_END); + *length = ftell(file); + fseek(file, 0, SEEK_SET); + + *data = malloc(*length); + if (!*data) { + perror("Failed to allocate memory"); + fclose(file); + return 0; + } + + if (fread(*data, 1, *length, file) != *length) { + perror("Failed to read file"); + free(*data); + fclose(file); + return 0; + } + + fclose(file); + return 1; +} + +int write_file(const char *filename, const uint8_t *data, size_t length) { + FILE *file = fopen(filename, "wb"); + if (!file) { + perror("Failed to open file"); + return 0; + } + + if (fwrite(data, 1, length, file) != length) { + perror("Failed to write file"); + fclose(file); + return 0; + } + + fclose(file); + return 1; +} + +void print_file_info(const AfcFileInfo *info) { + printf("File Information:\n"); + printf(" Size: %zu bytes\n", info->size); + printf(" Blocks: %zu\n", info->blocks); + printf(" Created: %lld\n", (long long)info->creation); + printf(" Modified: %lld\n", (long long)info->modified); + printf(" Links: %s\n", info->st_nlink); + printf(" Type: %s\n", info->st_ifmt); + if (info->st_link_target) { + printf(" Link Target: %s\n", info->st_link_target); + } +} + +void print_device_info(const AfcDeviceInfo *info) { + printf("Device Information:\n"); + printf(" Model: %s\n", info->model); + printf(" Total Space: %zu bytes\n", info->total_bytes); + printf(" Free Space: %zu bytes\n", info->free_bytes); + printf(" Block Size: %zu bytes\n", info->block_size); +} + +void free_directory_listing(char **entries, size_t count) { + if (!entries) + return; + for (size_t i = 0; i < count; i++) { + if (entries[i]) { + free(entries[i]); + } + } + free(entries); +} + +int main(int argc, char **argv) { + // Initialize logger + idevice_init_logger(Debug, Disabled, NULL); + + // Default values + char *ip = "10.7.0.2"; + char *pairing_file_path = "pairing_file.plist"; + char *udid = NULL; + char *command = NULL; + + // Parse arguments + for (int i = 1; i < argc; i++) { + if (strcmp(argv[i], "--ip") == 0) { + if (i + 1 >= argc) { + printf("Error: Missing IP address argument\n"); + return 1; + } + ip = argv[++i]; + } else if (strcmp(argv[i], "--pairing") == 0) { + if (i + 1 >= argc) { + printf("Error: Missing pairing file argument\n"); + return 1; + } + pairing_file_path = argv[++i]; + } else if (strcmp(argv[i], "--udid") == 0) { + if (i + 1 >= argc) { + printf("Error: Missing UDID argument\n"); + return 1; + } + udid = argv[++i]; + } else if (strcmp(argv[i], "help") == 0) { + print_usage(); + return 0; + } else { + command = argv[i]; + break; + } + } + + if (!command) { + print_usage(); + return 1; + } + + // Create the socket address + struct sockaddr_in addr; + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = htons(LOCKDOWN_PORT); + if (inet_pton(AF_INET, ip, &addr.sin_addr) != 1) { + fprintf(stderr, "Invalid IP address\n"); + return 1; + } + + // Read pairing file + IdevicePairingFile *pairing_file = NULL; + IdeviceErrorCode err = + idevice_pairing_file_read(pairing_file_path, &pairing_file); + if (err != IdeviceSuccess) { + fprintf(stderr, "Failed to read pairing file: %d\n", err); + return 1; + } + + // Create TCP provider + TcpProviderHandle *provider = NULL; + err = idevice_tcp_provider_new((struct sockaddr *)&addr, pairing_file, + "ImageMounterTest", &provider); + if (err != IdeviceSuccess) { + fprintf(stderr, "Failed to create TCP provider: %d\n", err); + idevice_pairing_file_free(pairing_file); + return 1; + } + + // Connect to AFC service + AfcClientHandle *client = NULL; + err = afc_client_connect_tcp(provider, &client); + if (err != IdeviceSuccess) { + fprintf(stderr, "Failed to connect to AFC service: %d\n", err); + tcp_provider_free(provider); + return 1; + } + tcp_provider_free(provider); + + // Process command + int success = 1; + if (strcmp(command, "list") == 0) { + if (argc < 3) { + printf("Error: Missing path argument\n"); + success = 0; + } else { + char *path = argv[2]; + char **entries = NULL; + size_t count = 0; + + err = afc_list_directory(client, path, &entries, &count); + if (err == IdeviceSuccess) { + printf("Contents of %s:\n", path); + for (size_t i = 0; i < count; i++) { + printf("- %s\n", entries[i]); + } + free_directory_listing(entries, count); + } else { + fprintf(stderr, "Failed to list directory: %d\n", err); + success = 0; + } + } + } else if (strcmp(command, "mkdir") == 0) { + if (argc < 3) { + printf("Error: Missing path argument\n"); + success = 0; + } else { + char *path = argv[2]; + err = afc_make_directory(client, path); + if (err == IdeviceSuccess) { + printf("Directory created successfully\n"); + } else { + fprintf(stderr, "Failed to create directory: %d\n", err); + success = 0; + } + } + } else if (strcmp(command, "download") == 0) { + if (argc < 4) { + printf("Error: Missing source and destination arguments\n"); + success = 0; + } else { + char *src_path = argv[2]; + char *dest_path = argv[3]; + + AfcFileHandle *file = NULL; + err = afc_file_open(client, src_path, AfcRdOnly, &file); + if (err != IdeviceSuccess) { + fprintf(stderr, "Failed to open file: %d\n", err); + success = 0; + } else { + uint8_t *data = NULL; + size_t length = 0; + err = afc_file_read(file, &data, &length); + if (err == IdeviceSuccess) { + if (write_file(dest_path, data, length)) { + printf("File downloaded successfully\n"); + } else { + success = 0; + } + free(data); + } else { + fprintf(stderr, "Failed to read file: %d\n", err); + success = 0; + } + afc_file_close(file); + } + } + } else if (strcmp(command, "upload") == 0) { + if (argc < 4) { + printf("Error: Missing source and destination arguments\n"); + success = 0; + } else { + char *src_path = argv[2]; + char *dest_path = argv[3]; + + uint8_t *data = NULL; + size_t length = 0; + if (!read_file(src_path, &data, &length)) { + success = 0; + } else { + AfcFileHandle *file = NULL; + err = afc_file_open(client, dest_path, AfcWrOnly, &file); + if (err != IdeviceSuccess) { + fprintf(stderr, "Failed to open file: %d\n", err); + success = 0; + } else { + err = afc_file_write(file, data, length); + if (err == IdeviceSuccess) { + printf("File uploaded successfully\n"); + } else { + fprintf(stderr, "Failed to write file: %d\n", err); + success = 0; + } + afc_file_close(file); + } + free(data); + } + } + } else if (strcmp(command, "remove") == 0) { + if (argc < 3) { + printf("Error: Missing path argument\n"); + success = 0; + } else { + char *path = argv[2]; + err = afc_remove_path(client, path); + if (err == IdeviceSuccess) { + printf("Path removed successfully\n"); + } else { + fprintf(stderr, "Failed to remove path: %d\n", err); + success = 0; + } + } + } else if (strcmp(command, "remove_all") == 0) { + if (argc < 3) { + printf("Error: Missing path argument\n"); + success = 0; + } else { + char *path = argv[2]; + err = afc_remove_path_and_contents(client, path); + if (err == IdeviceSuccess) { + printf("Path and contents removed successfully\n"); + } else { + fprintf(stderr, "Failed to remove path and contents: %d\n", err); + success = 0; + } + } + } else if (strcmp(command, "info") == 0) { + if (argc < 3) { + printf("Error: Missing path argument\n"); + success = 0; + } else { + char *path = argv[2]; + AfcFileInfo info = {0}; + err = afc_get_file_info(client, path, &info); + if (err == IdeviceSuccess) { + print_file_info(&info); + afc_file_info_free(&info); + } else { + fprintf(stderr, "Failed to get file info: %d\n", err); + success = 0; + } + } + } else if (strcmp(command, "device_info") == 0) { + AfcDeviceInfo info = {0}; + err = afc_get_device_info(client, &info); + if (err == IdeviceSuccess) { + print_device_info(&info); + afc_device_info_free(&info); + } else { + fprintf(stderr, "Failed to get device info: %d\n", err); + success = 0; + } + } else { + printf("Unknown command: %s\n", command); + print_usage(); + success = 0; + } + + // Cleanup + afc_client_free(client); + + return success ? 0 : 1; +} diff --git a/ffi/src/afc.rs b/ffi/src/afc.rs new file mode 100644 index 0000000..0b5c704 --- /dev/null +++ b/ffi/src/afc.rs @@ -0,0 +1,801 @@ +// Jackson Coxson + +use std::ffi::c_void; + +use idevice::{ + IdeviceError, IdeviceService, + afc::{AfcClient, DeviceInfo, FileInfo}, +}; + +use crate::{ + IdeviceErrorCode, IdeviceHandle, RUNTIME, + provider::{TcpProviderHandle, UsbmuxdProviderHandle}, +}; + +pub struct AfcClientHandle(pub AfcClient); + +/// Connects to the AFC service using a TCP provider +/// +/// # Arguments +/// * [`provider`] - A TcpProvider +/// * [`client`] - On success, will be set to point to a newly allocated AfcClient handle +/// +/// # Returns +/// An error code indicating success or failure +/// +/// # Safety +/// `provider` must be a valid pointer to a handle allocated by this library +/// `client` must be a valid, non-null pointer to a location where the handle will be stored +#[unsafe(no_mangle)] +pub unsafe extern "C" fn afc_client_connect_tcp( + provider: *mut TcpProviderHandle, + client: *mut *mut AfcClientHandle, +) -> IdeviceErrorCode { + if provider.is_null() || client.is_null() { + log::error!("Null pointer provided"); + return IdeviceErrorCode::InvalidArg; + } + + let res: Result = RUNTIME.block_on(async move { + let provider_box = unsafe { Box::from_raw(provider) }; + let provider_ref = &provider_box.0; + let result = AfcClient::connect(provider_ref).await; + std::mem::forget(provider_box); + result + }); + + match res { + Ok(r) => { + let boxed = Box::new(AfcClientHandle(r)); + unsafe { *client = Box::into_raw(boxed) }; + IdeviceErrorCode::IdeviceSuccess + } + Err(e) => { + let _ = unsafe { Box::from_raw(provider) }; + e.into() + } + } +} + +/// Connects to the AFC service using a Usbmuxd provider +/// +/// # Arguments +/// * [`provider`] - A UsbmuxdProvider +/// * [`client`] - On success, will be set to point to a newly allocated AfcClient handle +/// +/// # Returns +/// An error code indicating success or failure +/// +/// # Safety +/// `provider` must be a valid pointer to a handle allocated by this library +/// `client` must be a valid, non-null pointer to a location where the handle will be stored +#[unsafe(no_mangle)] +pub unsafe extern "C" fn afc_client_connect_usbmuxd( + provider: *mut UsbmuxdProviderHandle, + client: *mut *mut AfcClientHandle, +) -> IdeviceErrorCode { + if provider.is_null() { + log::error!("Provider is null"); + return IdeviceErrorCode::InvalidArg; + } + + let res: Result = RUNTIME.block_on(async move { + let provider_box = unsafe { Box::from_raw(provider) }; + let provider_ref = &provider_box.0; + let result = AfcClient::connect(provider_ref).await; + std::mem::forget(provider_box); + result + }); + + match res { + Ok(r) => { + let boxed = Box::new(AfcClientHandle(r)); + unsafe { *client = Box::into_raw(boxed) }; + IdeviceErrorCode::IdeviceSuccess + } + Err(e) => e.into(), + } +} + +/// Creates a new AfcClient from an existing Idevice connection +/// +/// # Arguments +/// * [`socket`] - An IdeviceSocket handle +/// * [`client`] - On success, will be set to point to a newly allocated AfcClient handle +/// +/// # Returns +/// An error code indicating success or failure +/// +/// # Safety +/// `socket` must be a valid pointer to a handle allocated by this library +/// `client` must be a valid, non-null pointer to a location where the handle will be stored +#[unsafe(no_mangle)] +pub unsafe extern "C" fn afc_client_new( + socket: *mut IdeviceHandle, + client: *mut *mut AfcClientHandle, +) -> IdeviceErrorCode { + if socket.is_null() { + return IdeviceErrorCode::InvalidArg; + } + let socket = unsafe { Box::from_raw(socket) }.0; + let r = AfcClient::new(socket); + let boxed = Box::new(AfcClientHandle(r)); + unsafe { *client = Box::into_raw(boxed) }; + IdeviceErrorCode::IdeviceSuccess +} + +/// Frees an AfcClient handle +/// +/// # Arguments +/// * [`handle`] - The handle to free +/// +/// # Safety +/// `handle` must be a valid pointer to the handle that was allocated by this library, +/// or NULL (in which case this function does nothing) +#[unsafe(no_mangle)] +pub unsafe extern "C" fn afc_client_free(handle: *mut AfcClientHandle) { + if !handle.is_null() { + log::debug!("Freeing afc_client"); + let _ = unsafe { Box::from_raw(handle) }; + } +} + +/// Lists the contents of a directory on the device +/// +/// # Arguments +/// * [`client`] - A valid AfcClient handle +/// * [`path`] - Path to the directory to list (UTF-8 null-terminated) +/// * [`entries`] - Will be set to point to an array of directory entries +/// * [`count`] - Will be set to the number of entries +/// +/// # Returns +/// An error code indicating success or failure +/// +/// # Safety +/// All pointers must be valid and non-null +/// `path` must be a valid null-terminated C string +#[unsafe(no_mangle)] +pub unsafe extern "C" fn afc_list_directory( + client: *mut AfcClientHandle, + path: *const libc::c_char, + entries: *mut *mut *mut libc::c_char, + count: *mut libc::size_t, +) -> IdeviceErrorCode { + if path.is_null() || entries.is_null() || count.is_null() { + return IdeviceErrorCode::InvalidArg; + } + + let path_cstr = unsafe { std::ffi::CStr::from_ptr(path) }; + // Use to_string_lossy to handle non-UTF8 paths + let path = path_cstr.to_string_lossy(); + + let res: Result, IdeviceError> = RUNTIME.block_on(async move { + // SAFETY: We're assuming client is a valid pointer here + let client_ref = unsafe { &mut (*client).0 }; + client_ref.list_dir(&path.to_string()).await + }); + + match res { + Ok(items) => { + // Create a heap-allocated array of C strings that will be freed by the caller + let c_strings = items + .into_iter() + .filter_map(|s| std::ffi::CString::new(s).ok()) + .collect::>(); + + // Get the count before we modify anything + let string_count = c_strings.len(); + + // Create memory for array of char pointers (including NULL terminator) + let layout = std::alloc::Layout::array::<*mut libc::c_char>(string_count + 1).unwrap(); + let ptr = unsafe { std::alloc::alloc(layout) as *mut *mut libc::c_char }; + if ptr.is_null() { + return IdeviceErrorCode::InvalidArg; + } + + // Fill the array with pointers to the strings, then leak each CString + for (i, cstring) in c_strings.into_iter().enumerate() { + let string_ptr = cstring.into_raw(); + unsafe { *ptr.add(i) = string_ptr }; + } + + // Set NULL terminator + unsafe { *ptr.add(string_count) = std::ptr::null_mut() }; + + // Store the result and count + unsafe { + *entries = ptr; + *count = string_count; + } + + IdeviceErrorCode::IdeviceSuccess + } + Err(e) => e.into(), + } +} + +/// Creates a new directory on the device +/// +/// # Arguments +/// * [`client`] - A valid AfcClient handle +/// * [`path`] - Path of the directory to create (UTF-8 null-terminated) +/// +/// # Returns +/// An error code indicating success or failure +/// +/// # Safety +/// `client` must be a valid pointer to a handle allocated by this library +/// `path` must be a valid null-terminated C string +#[unsafe(no_mangle)] +pub unsafe extern "C" fn afc_make_directory( + client: *mut AfcClientHandle, + path: *const libc::c_char, +) -> IdeviceErrorCode { + if path.is_null() { + return IdeviceErrorCode::InvalidArg; + } + + let path_cstr = unsafe { std::ffi::CStr::from_ptr(path) }; + let path = match path_cstr.to_str() { + Ok(s) => s, + Err(_) => return IdeviceErrorCode::InvalidArg, + }; + + let res: Result<(), IdeviceError> = RUNTIME.block_on(async move { + let mut client_box = unsafe { Box::from_raw(client) }; + let client_ref = &mut client_box.0; + let result = client_ref.mk_dir(path).await; + std::mem::forget(client_box); + result + }); + + match res { + Ok(_) => IdeviceErrorCode::IdeviceSuccess, + Err(e) => e.into(), + } +} + +/// File information structure for C bindings +#[repr(C)] +pub struct AfcFileInfo { + pub size: libc::size_t, + pub blocks: libc::size_t, + pub creation: i64, + pub modified: i64, + pub st_nlink: *mut libc::c_char, + pub st_ifmt: *mut libc::c_char, + pub st_link_target: *mut libc::c_char, +} + +/// Retrieves information about a file or directory +/// +/// # Arguments +/// * [`client`] - A valid AfcClient handle +/// * [`path`] - Path to the file or directory (UTF-8 null-terminated) +/// * [`info`] - Will be populated with file information +/// +/// # Returns +/// An error code indicating success or failure +/// +/// # Safety +/// `client` and `path` must be valid pointers +/// `info` must be a valid pointer to an AfcFileInfo struct +#[unsafe(no_mangle)] +pub unsafe extern "C" fn afc_get_file_info( + client: *mut AfcClientHandle, + path: *const libc::c_char, + info: *mut AfcFileInfo, +) -> IdeviceErrorCode { + if path.is_null() || info.is_null() { + return IdeviceErrorCode::InvalidArg; + } + + let path_cstr = unsafe { std::ffi::CStr::from_ptr(path) }; + let path = match path_cstr.to_str() { + Ok(s) => s, + Err(_) => return IdeviceErrorCode::InvalidArg, + }; + + let res: Result = RUNTIME.block_on(async move { + let mut client_box = unsafe { Box::from_raw(client) }; + let client_ref = &mut client_box.0; + let result = client_ref.get_file_info(path).await; + std::mem::forget(client_box); + result + }); + + match res { + Ok(file_info) => { + unsafe { + (*info).size = file_info.size; + (*info).blocks = file_info.blocks; + (*info).creation = file_info.creation.and_utc().timestamp(); + (*info).modified = file_info.modified.and_utc().timestamp(); + + (*info).st_nlink = std::ffi::CString::new(file_info.st_nlink) + .unwrap() + .into_raw(); + + (*info).st_ifmt = std::ffi::CString::new(file_info.st_ifmt) + .unwrap() + .into_raw(); + + (*info).st_link_target = match file_info.st_link_target { + Some(target) => std::ffi::CString::new(target).unwrap().into_raw(), + None => std::ptr::null_mut(), + }; + } + IdeviceErrorCode::IdeviceSuccess + } + Err(e) => e.into(), + } +} + +/// Frees memory allocated by afc_get_file_info +/// +/// # Arguments +/// * [`info`] - Pointer to AfcFileInfo struct to free +/// +/// # Safety +/// `info` must be a valid pointer to an AfcFileInfo struct previously returned by afc_get_file_info +#[unsafe(no_mangle)] +pub unsafe extern "C" fn afc_file_info_free(info: *mut AfcFileInfo) { + if !info.is_null() { + unsafe { + if !(*info).st_nlink.is_null() { + let _ = std::ffi::CString::from_raw((*info).st_nlink); + } + if !(*info).st_ifmt.is_null() { + let _ = std::ffi::CString::from_raw((*info).st_ifmt); + } + if !(*info).st_link_target.is_null() { + let _ = std::ffi::CString::from_raw((*info).st_link_target); + } + } + } +} + +/// Device information structure for C bindings +#[repr(C)] +pub struct AfcDeviceInfo { + pub model: *mut libc::c_char, + pub total_bytes: libc::size_t, + pub free_bytes: libc::size_t, + pub block_size: libc::size_t, +} + +/// Retrieves information about the device's filesystem +/// +/// # Arguments +/// * [`client`] - A valid AfcClient handle +/// * [`info`] - Will be populated with device information +/// +/// # Returns +/// An error code indicating success or failure +/// +/// # Safety +/// `client` and `info` must be valid pointers +#[unsafe(no_mangle)] +pub unsafe extern "C" fn afc_get_device_info( + client: *mut AfcClientHandle, + info: *mut AfcDeviceInfo, +) -> IdeviceErrorCode { + if info.is_null() { + return IdeviceErrorCode::InvalidArg; + } + + let res: Result = RUNTIME.block_on(async move { + let mut client_box = unsafe { Box::from_raw(client) }; + let client_ref = &mut client_box.0; + let result = client_ref.get_device_info().await; + std::mem::forget(client_box); + result + }); + + match res { + Ok(device_info) => { + unsafe { + (*info).model = std::ffi::CString::new(device_info.model) + .unwrap() + .into_raw(); + (*info).total_bytes = device_info.total_bytes; + (*info).free_bytes = device_info.free_bytes; + (*info).block_size = device_info.block_size; + } + IdeviceErrorCode::IdeviceSuccess + } + Err(e) => e.into(), + } +} + +/// Frees memory allocated by afc_get_device_info +/// +/// # Arguments +/// * [`info`] - Pointer to AfcDeviceInfo struct to free +/// +/// # Safety +/// `info` must be a valid pointer to an AfcDeviceInfo struct previously returned by afc_get_device_info +#[unsafe(no_mangle)] +pub unsafe extern "C" fn afc_device_info_free(info: *mut AfcDeviceInfo) { + if !info.is_null() && unsafe { !(*info).model.is_null() } { + unsafe { + let _ = std::ffi::CString::from_raw((*info).model); + } + } +} + +/// Removes a file or directory +/// +/// # Arguments +/// * [`client`] - A valid AfcClient handle +/// * [`path`] - Path to the file or directory to remove (UTF-8 null-terminated) +/// +/// # Returns +/// An error code indicating success or failure +/// +/// # Safety +/// `client` must be a valid pointer to a handle allocated by this library +/// `path` must be a valid null-terminated C string +#[unsafe(no_mangle)] +pub unsafe extern "C" fn afc_remove_path( + client: *mut AfcClientHandle, + path: *const libc::c_char, +) -> IdeviceErrorCode { + if path.is_null() { + return IdeviceErrorCode::InvalidArg; + } + + let path_cstr = unsafe { std::ffi::CStr::from_ptr(path) }; + let path = match path_cstr.to_str() { + Ok(s) => s, + Err(_) => return IdeviceErrorCode::InvalidArg, + }; + + let res: Result<(), IdeviceError> = RUNTIME.block_on(async move { + let mut client_box = unsafe { Box::from_raw(client) }; + let client_ref = &mut client_box.0; + let result = client_ref.remove(path).await; + std::mem::forget(client_box); + result + }); + + match res { + Ok(_) => IdeviceErrorCode::IdeviceSuccess, + Err(e) => e.into(), + } +} + +/// Recursively removes a directory and all its contents +/// +/// # Arguments +/// * [`client`] - A valid AfcClient handle +/// * [`path`] - Path to the directory to remove (UTF-8 null-terminated) +/// +/// # Returns +/// An error code indicating success or failure +/// +/// # Safety +/// `client` must be a valid pointer to a handle allocated by this library +/// `path` must be a valid null-terminated C string +#[unsafe(no_mangle)] +pub unsafe extern "C" fn afc_remove_path_and_contents( + client: *mut AfcClientHandle, + path: *const libc::c_char, +) -> IdeviceErrorCode { + if path.is_null() { + return IdeviceErrorCode::InvalidArg; + } + + let path_cstr = unsafe { std::ffi::CStr::from_ptr(path) }; + let path = match path_cstr.to_str() { + Ok(s) => s, + Err(_) => return IdeviceErrorCode::InvalidArg, + }; + + let res: Result<(), IdeviceError> = RUNTIME.block_on(async move { + let mut client_box = unsafe { Box::from_raw(client) }; + let client_ref = &mut client_box.0; + let result = client_ref.remove_all(path).await; + std::mem::forget(client_box); + result + }); + + match res { + Ok(_) => IdeviceErrorCode::IdeviceSuccess, + Err(e) => e.into(), + } +} + +#[repr(C)] +pub enum AfcFopenMode { + AfcRdOnly = 0x00000001, // r O_RDONLY + AfcRw = 0x00000002, // r+ O_RDWR | O_CREAT + AfcWrOnly = 0x00000003, // w O_WRONLY | O_CREAT | O_TRUNC + AfcWr = 0x00000004, // w+ O_RDWR | O_CREAT | O_TRUNC + AfcAppend = 0x00000005, // a O_WRONLY | O_APPEND | O_CREAT + AfcRdAppend = 0x00000006, // a+ O_RDWR | O_APPEND | O_CREAT +} + +impl From for idevice::afc::opcode::AfcFopenMode { + fn from(value: AfcFopenMode) -> Self { + match value { + AfcFopenMode::AfcRdOnly => idevice::afc::opcode::AfcFopenMode::RdOnly, + AfcFopenMode::AfcRw => idevice::afc::opcode::AfcFopenMode::Rw, + AfcFopenMode::AfcWrOnly => idevice::afc::opcode::AfcFopenMode::WrOnly, + AfcFopenMode::AfcWr => idevice::afc::opcode::AfcFopenMode::Wr, + AfcFopenMode::AfcAppend => idevice::afc::opcode::AfcFopenMode::Append, + AfcFopenMode::AfcRdAppend => idevice::afc::opcode::AfcFopenMode::RdAppend, + } + } +} + +/// Handle for an open file on the device +#[allow(dead_code)] +pub struct AfcFileHandle(*mut c_void); // Opaque pointer + +/// Opens a file on the device +/// +/// # Arguments +/// * [`client`] - A valid AfcClient handle +/// * [`path`] - Path to the file to open (UTF-8 null-terminated) +/// * [`mode`] - File open mode +/// * [`handle`] - Will be set to a new file handle on success +/// +/// # Returns +/// An error code indicating success or failure +/// +/// # Safety +/// All pointers must be valid and non-null +/// `path` must be a valid null-terminated C string +#[unsafe(no_mangle)] +pub unsafe extern "C" fn afc_file_open( + client: *mut AfcClientHandle, + path: *const libc::c_char, + mode: AfcFopenMode, + handle: *mut *mut AfcFileHandle, +) -> IdeviceErrorCode { + if path.is_null() || handle.is_null() { + return IdeviceErrorCode::InvalidArg; + } + + let path_cstr = unsafe { std::ffi::CStr::from_ptr(path) }; + let path = match path_cstr.to_str() { + Ok(s) => s, + Err(_) => return IdeviceErrorCode::InvalidArg, + }; + + let mode = mode.into(); + + let res: Result<*mut AfcFileHandle, IdeviceError> = RUNTIME.block_on(async move { + let mut client_box = unsafe { Box::from_raw(client) }; + let client_ref = &mut client_box.0; + let result = client_ref.open(path, mode).await; + let res = match result { + Ok(f) => { + let boxed = Box::new(f); + Ok(Box::into_raw(boxed) as *mut AfcFileHandle) + } + Err(e) => Err(e), + }; + std::mem::forget(client_box); + res + }); + + match res { + Ok(f) => { + unsafe { *handle = f } + IdeviceErrorCode::IdeviceSuccess + } + Err(e) => e.into(), + } +} + +/// Closes a file handle +/// +/// # Arguments +/// * [`handle`] - File handle to close +/// +/// # Returns +/// An error code indicating success or failure +/// +/// # Safety +/// `handle` must be a valid pointer to a handle allocated by this library +#[unsafe(no_mangle)] +pub unsafe extern "C" fn afc_file_close(handle: *mut AfcFileHandle) -> IdeviceErrorCode { + if handle.is_null() { + return IdeviceErrorCode::InvalidArg; + } + + let fd = unsafe { Box::from_raw(handle as *mut idevice::afc::file::FileDescriptor) }; + let res: Result<(), IdeviceError> = RUNTIME.block_on(async move { fd.close().await }); + + match res { + Ok(_) => IdeviceErrorCode::IdeviceSuccess, + Err(e) => e.into(), + } +} + +/// Reads data from an open file +/// +/// # Arguments +/// * [`handle`] - File handle to read from +/// * [`data`] - Will be set to point to the read data +/// * [`length`] - Will be set to the length of the read data +/// +/// # Returns +/// An error code indicating success or failure +/// +/// # Safety +/// All pointers must be valid and non-null +#[unsafe(no_mangle)] +pub unsafe extern "C" fn afc_file_read( + handle: *mut AfcFileHandle, + data: *mut *mut u8, + length: *mut libc::size_t, +) -> IdeviceErrorCode { + if handle.is_null() || data.is_null() || length.is_null() { + return IdeviceErrorCode::InvalidArg; + } + + let fd = unsafe { &mut *(handle as *mut idevice::afc::file::FileDescriptor) }; + let res: Result, IdeviceError> = RUNTIME.block_on(async move { fd.read().await }); + + match res { + Ok(bytes) => { + let mut boxed = bytes.into_boxed_slice(); + unsafe { + *data = boxed.as_mut_ptr(); + *length = boxed.len(); + } + std::mem::forget(boxed); + IdeviceErrorCode::IdeviceSuccess + } + Err(e) => e.into(), + } +} + +/// Writes data to an open file +/// +/// # Arguments +/// * [`handle`] - File handle to write to +/// * [`data`] - Data to write +/// * [`length`] - Length of data to write +/// +/// # Returns +/// An error code indicating success or failure +/// +/// # Safety +/// All pointers must be valid and non-null +/// `data` must point to at least `length` bytes +#[unsafe(no_mangle)] +pub unsafe extern "C" fn afc_file_write( + handle: *mut AfcFileHandle, + data: *const u8, + length: libc::size_t, +) -> IdeviceErrorCode { + if handle.is_null() || data.is_null() { + return IdeviceErrorCode::InvalidArg; + } + + let fd = unsafe { &mut *(handle as *mut idevice::afc::file::FileDescriptor) }; + let data_slice = unsafe { std::slice::from_raw_parts(data, length) }; + + let res: Result<(), IdeviceError> = RUNTIME.block_on(async move { fd.write(data_slice).await }); + + match res { + Ok(_) => IdeviceErrorCode::IdeviceSuccess, + Err(e) => e.into(), + } +} + +/// Link type for creating hard or symbolic links +#[repr(C)] +pub enum AfcLinkType { + Hard = 1, + Symbolic = 2, +} + +/// Creates a hard or symbolic link +/// +/// # Arguments +/// * [`client`] - A valid AfcClient handle +/// * [`target`] - Target path of the link (UTF-8 null-terminated) +/// * [`source`] - Path where the link should be created (UTF-8 null-terminated) +/// * [`link_type`] - Type of link to create +/// +/// # Returns +/// An error code indicating success or failure +/// +/// # Safety +/// All pointers must be valid and non-null +/// `target` and `source` must be valid null-terminated C strings +#[unsafe(no_mangle)] +pub unsafe extern "C" fn afc_make_link( + client: *mut AfcClientHandle, + target: *const libc::c_char, + source: *const libc::c_char, + link_type: AfcLinkType, +) -> IdeviceErrorCode { + if target.is_null() || source.is_null() { + return IdeviceErrorCode::InvalidArg; + } + + let target_cstr = unsafe { std::ffi::CStr::from_ptr(target) }; + let target = match target_cstr.to_str() { + Ok(s) => s, + Err(_) => return IdeviceErrorCode::InvalidArg, + }; + + let source_cstr = unsafe { std::ffi::CStr::from_ptr(source) }; + let source = match source_cstr.to_str() { + Ok(s) => s, + Err(_) => return IdeviceErrorCode::InvalidArg, + }; + + let link_type = match link_type { + AfcLinkType::Hard => idevice::afc::opcode::LinkType::Hardlink, + AfcLinkType::Symbolic => idevice::afc::opcode::LinkType::Symlink, + }; + + let res: Result<(), IdeviceError> = RUNTIME.block_on(async move { + let mut client_box = unsafe { Box::from_raw(client) }; + let client_ref = &mut client_box.0; + let result = client_ref.link(target, source, link_type).await; + std::mem::forget(client_box); + result + }); + + match res { + Ok(_) => IdeviceErrorCode::IdeviceSuccess, + Err(e) => e.into(), + } +} + +/// Renames a file or directory +/// +/// # Arguments +/// * [`client`] - A valid AfcClient handle +/// * [`source`] - Current path of the file/directory (UTF-8 null-terminated) +/// * [`target`] - New path for the file/directory (UTF-8 null-terminated) +/// +/// # Returns +/// An error code indicating success or failure +/// +/// # Safety +/// All pointers must be valid and non-null +/// `source` and `target` must be valid null-terminated C strings +#[unsafe(no_mangle)] +pub unsafe extern "C" fn afc_rename_path( + client: *mut AfcClientHandle, + source: *const libc::c_char, + target: *const libc::c_char, +) -> IdeviceErrorCode { + if source.is_null() || target.is_null() { + return IdeviceErrorCode::InvalidArg; + } + + let source_cstr = unsafe { std::ffi::CStr::from_ptr(source) }; + let source = match source_cstr.to_str() { + Ok(s) => s, + Err(_) => return IdeviceErrorCode::InvalidArg, + }; + + let target_cstr = unsafe { std::ffi::CStr::from_ptr(target) }; + let target = match target_cstr.to_str() { + Ok(s) => s, + Err(_) => return IdeviceErrorCode::InvalidArg, + }; + + let res: Result<(), IdeviceError> = RUNTIME.block_on(async move { + let mut client_box = unsafe { Box::from_raw(client) }; + let client_ref = &mut client_box.0; + let result = client_ref.rename(source, target).await; + std::mem::forget(client_box); + result + }); + + match res { + Ok(_) => IdeviceErrorCode::IdeviceSuccess, + Err(e) => e.into(), + } +} diff --git a/ffi/src/lib.rs b/ffi/src/lib.rs index d41db13..eb3eaea 100644 --- a/ffi/src/lib.rs +++ b/ffi/src/lib.rs @@ -1,6 +1,7 @@ // Jackson Coxson pub mod adapter; +pub mod afc; pub mod amfi; pub mod core_device_proxy; pub mod debug_proxy;