Skip to main content

restorekit_sys/
lib.rs

1//! Raw FFI to the statically-linked idevicerestore client API
2//! (see `vendor/idevicerestore/src/idevicerestore.h`).
3#![allow(non_camel_case_types)]
4
5// Force the vendored static C deps into the final link. The idevicerestore
6// stack references their symbols (OpenSSL, zlib, libcurl) but our Rust code
7// doesn't, so we must keep the crates from being pruned.
8extern crate curl_sys as _;
9extern crate libz_sys as _;
10extern crate openssl_sys as _;
11
12use std::os::raw::{c_char, c_int, c_void};
13
14// Restore flags from idevicerestore.h.
15pub const FLAG_DEBUG: c_int = 1 << 1;
16pub const FLAG_ERASE: c_int = 1 << 2;
17
18// idevicerestore log levels (enum loglevel in log.h).
19pub const LL_ERROR: c_int = 0;
20pub const LL_WARNING: c_int = 1;
21pub const LL_NOTICE: c_int = 2;
22pub const LL_INFO: c_int = 3;
23pub const LL_VERBOSE: c_int = 4;
24pub const LL_DEBUG: c_int = 5;
25
26extern "C" {
27    /// idevicerestore's global logging threshold; messages above it are dropped.
28    static mut log_level: c_int;
29}
30
31/// Set idevicerestore's global log verbosity. Messages more verbose than
32/// `level` are never emitted (by default it dumps everything to stdout).
33pub fn set_log_level(level: c_int) {
34    unsafe {
35        log_level = level;
36    }
37}
38
39use std::sync::atomic::{AtomicBool, Ordering};
40use std::sync::Mutex;
41
42extern "C" {
43    /// Installs our log trampoline (see csrc/log_capture.c).
44    fn restorekit_install_log_capture();
45}
46
47static LOG_LINES: Mutex<Vec<(c_int, String)>> = Mutex::new(Vec::new());
48static LOG_ECHO: AtomicBool = AtomicBool::new(false);
49const LOG_CAPACITY: usize = 512;
50
51/// Called from C (log_capture.c) for each idevicerestore log line.
52///
53/// # Safety
54/// `msg`, if non-null, must be a valid NUL-terminated C string.
55#[no_mangle]
56pub unsafe extern "C" fn restorekit_log_capture(level: c_int, msg: *const c_char) {
57    if msg.is_null() {
58        return;
59    }
60    let text = std::ffi::CStr::from_ptr(msg).to_string_lossy();
61    let line = text.trim_end_matches(['\n', '\r']).to_string();
62    if line.is_empty() {
63        return;
64    }
65    if LOG_ECHO.load(Ordering::Relaxed) {
66        eprintln!("{line}");
67    }
68    if let Ok(mut buf) = LOG_LINES.lock() {
69        if buf.len() >= LOG_CAPACITY {
70            buf.remove(0);
71        }
72        buf.push((level, line));
73    }
74}
75
76/// Route idevicerestore's logging into the capture sink. When `echo` is set,
77/// lines are also printed to stderr (for verbose mode). Clears prior lines.
78pub fn install_log_capture(echo: bool) {
79    LOG_ECHO.store(echo, Ordering::Relaxed);
80    if let Ok(mut buf) = LOG_LINES.lock() {
81        buf.clear();
82    }
83    unsafe { restorekit_install_log_capture() };
84}
85
86/// The last `max_lines` captured error/warning lines, newest-relevant last.
87pub fn error_tail(max_lines: usize) -> String {
88    let buf = match LOG_LINES.lock() {
89        Ok(b) => b,
90        Err(e) => e.into_inner(),
91    };
92    let errors: Vec<&str> = buf
93        .iter()
94        .filter(|(level, _)| *level <= LL_WARNING)
95        .map(|(_, line)| line.as_str())
96        .collect();
97    let start = errors.len().saturating_sub(max_lines);
98    errors[start..].join("\n")
99}
100
101// Progress step numbers (enum in idevicerestore.h).
102pub const RESTORE_STEP_DETECT: c_int = 0;
103pub const RESTORE_STEP_PREPARE: c_int = 1;
104pub const RESTORE_STEP_UPLOAD_FS: c_int = 2;
105pub const RESTORE_STEP_VERIFY_FS: c_int = 3;
106pub const RESTORE_STEP_FLASH_FW: c_int = 4;
107pub const RESTORE_STEP_FLASH_BB: c_int = 5;
108pub const RESTORE_STEP_FUD: c_int = 6;
109pub const RESTORE_STEP_UPLOAD_IMG: c_int = 7;
110
111/// Opaque idevicerestore client handle.
112#[repr(C)]
113pub struct idevicerestore_client_t {
114    _private: [u8; 0],
115}
116
117pub type idevicerestore_progress_cb_t =
118    Option<unsafe extern "C" fn(step: c_int, step_progress: f64, userdata: *mut c_void)>;
119
120extern "C" {
121    pub fn idevicerestore_client_new() -> *mut idevicerestore_client_t;
122    pub fn idevicerestore_client_free(client: *mut idevicerestore_client_t);
123    pub fn idevicerestore_set_ecid(client: *mut idevicerestore_client_t, ecid: u64);
124    pub fn idevicerestore_set_udid(client: *mut idevicerestore_client_t, udid: *const c_char);
125    pub fn idevicerestore_set_flags(client: *mut idevicerestore_client_t, flags: c_int);
126    pub fn idevicerestore_set_ipsw(client: *mut idevicerestore_client_t, path: *const c_char);
127    pub fn idevicerestore_set_cache_path(client: *mut idevicerestore_client_t, path: *const c_char);
128    pub fn idevicerestore_set_progress_callback(
129        client: *mut idevicerestore_client_t,
130        cbfunc: idevicerestore_progress_cb_t,
131        userdata: *mut c_void,
132    );
133    pub fn idevicerestore_start(client: *mut idevicerestore_client_t) -> c_int;
134    /// Register a progress bar (or, with a NULL label, just run the one-time
135    /// init of idevicerestore's global progress mutex).
136    pub fn register_progress(tag: u32, label: *const c_char);
137}
138
139/// Initialize idevicerestore's progress subsystem before a restore.
140///
141/// Several of its progress functions (e.g. `finalize_progress`, called from
142/// `dfu_client_free`) lock a global mutex without the lazy-init guard that
143/// `set_progress`/`register_progress` run. On Windows that mutex is a
144/// `CRITICAL_SECTION`, so locking it uninitialized is an access violation.
145/// `register_progress` with a NULL label runs the init and returns without
146/// adding an entry.
147pub fn init_progress() {
148    unsafe { register_progress(0, std::ptr::null()) };
149}
150
151// ── Embedded usbmuxd server (Linux + Windows) ────────────────────────────────
152
153#[cfg(any(target_os = "linux", target_os = "windows"))]
154extern "C" {
155    fn restorekit_usbmuxd_start(socket_path: *const c_char) -> c_int;
156    fn restorekit_usbmuxd_run();
157    fn restorekit_usbmuxd_stop();
158    fn restorekit_usbmuxd_cleanup();
159}
160
161/// Initialize the embedded usbmuxd server, binding a Unix socket at `path`.
162/// Returns `Ok(())` on success.
163#[cfg(any(target_os = "linux", target_os = "windows"))]
164pub fn usbmuxd_start(path: &std::ffi::CStr) -> std::result::Result<(), c_int> {
165    let rc = unsafe { restorekit_usbmuxd_start(path.as_ptr()) };
166    if rc == 0 {
167        Ok(())
168    } else {
169        Err(rc)
170    }
171}
172
173/// Run the usbmuxd event loop (blocks until [`usbmuxd_stop`] is called).
174#[cfg(any(target_os = "linux", target_os = "windows"))]
175pub fn usbmuxd_run() {
176    unsafe { restorekit_usbmuxd_run() }
177}
178
179/// Signal the event loop to exit.
180#[cfg(any(target_os = "linux", target_os = "windows"))]
181pub fn usbmuxd_stop() {
182    unsafe { restorekit_usbmuxd_stop() }
183}
184
185/// Tear down USB devices, close the listen socket, and unlink the socket file.
186#[cfg(any(target_os = "linux", target_os = "windows"))]
187pub fn usbmuxd_cleanup() {
188    unsafe { restorekit_usbmuxd_cleanup() }
189}
190
191#[cfg(test)]
192mod tests {
193    use super::*;
194    use std::ffi::CString;
195
196    #[test]
197    fn error_tail_filters_by_level() {
198        if let Ok(mut b) = LOG_LINES.lock() {
199            b.clear();
200        }
201        LOG_ECHO.store(false, Ordering::Relaxed);
202
203        let cap = |level, s: &str| {
204            let c = CString::new(s).unwrap();
205            unsafe { restorekit_log_capture(level, c.as_ptr()) };
206        };
207        cap(LL_INFO, "info line");
208        cap(LL_ERROR, "boom -3");
209        cap(LL_VERBOSE, "verbose noise");
210        cap(LL_WARNING, "careful");
211
212        let tail = error_tail(10);
213        assert!(tail.contains("boom -3"));
214        assert!(tail.contains("careful"));
215        assert!(!tail.contains("info line"));
216        assert!(!tail.contains("verbose noise"));
217    }
218}