1#![allow(non_camel_case_types)]
4
5extern 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
14pub const FLAG_DEBUG: c_int = 1 << 1;
16pub const FLAG_ERASE: c_int = 1 << 2;
17
18pub 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 static mut log_level: c_int;
29}
30
31pub 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 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
51type LogSink = Box<dyn Fn(c_int, &str) + Send>;
54static LOG_SINK: Mutex<Option<LogSink>> = Mutex::new(None);
55
56pub fn set_log_sink(sink: Option<LogSink>) {
59 *LOG_SINK.lock().unwrap_or_else(|e| e.into_inner()) = sink;
60}
61
62#[no_mangle]
67pub unsafe extern "C" fn restorekit_log_capture(level: c_int, msg: *const c_char) {
68 if msg.is_null() {
69 return;
70 }
71 let text = std::ffi::CStr::from_ptr(msg).to_string_lossy();
72 let line = text.trim_end_matches(['\n', '\r']).to_string();
73 if line.is_empty() {
74 return;
75 }
76 if LOG_ECHO.load(Ordering::Relaxed) {
77 eprintln!("{line}");
78 }
79 if let Ok(guard) = LOG_SINK.lock() {
80 if let Some(sink) = guard.as_ref() {
81 sink(level, &line);
82 }
83 }
84 if let Ok(mut buf) = LOG_LINES.lock() {
85 if buf.len() >= LOG_CAPACITY {
86 buf.remove(0);
87 }
88 buf.push((level, line));
89 }
90}
91
92pub fn install_log_capture(echo: bool) {
95 LOG_ECHO.store(echo, Ordering::Relaxed);
96 if let Ok(mut buf) = LOG_LINES.lock() {
97 buf.clear();
98 }
99 unsafe { restorekit_install_log_capture() };
100}
101
102pub fn error_tail(max_lines: usize) -> String {
104 let buf = match LOG_LINES.lock() {
105 Ok(b) => b,
106 Err(e) => e.into_inner(),
107 };
108 let errors: Vec<&str> = buf
109 .iter()
110 .filter(|(level, _)| *level <= LL_WARNING)
111 .map(|(_, line)| line.as_str())
112 .collect();
113 let start = errors.len().saturating_sub(max_lines);
114 errors[start..].join("\n")
115}
116
117pub const RESTORE_STEP_DETECT: c_int = 0;
119pub const RESTORE_STEP_PREPARE: c_int = 1;
120pub const RESTORE_STEP_UPLOAD_FS: c_int = 2;
121pub const RESTORE_STEP_VERIFY_FS: c_int = 3;
122pub const RESTORE_STEP_FLASH_FW: c_int = 4;
123pub const RESTORE_STEP_FLASH_BB: c_int = 5;
124pub const RESTORE_STEP_FUD: c_int = 6;
125pub const RESTORE_STEP_UPLOAD_IMG: c_int = 7;
126
127#[repr(C)]
129pub struct idevicerestore_client_t {
130 _private: [u8; 0],
131}
132
133pub type idevicerestore_progress_cb_t =
134 Option<unsafe extern "C" fn(step: c_int, step_progress: f64, userdata: *mut c_void)>;
135
136extern "C" {
137 pub fn idevicerestore_client_new() -> *mut idevicerestore_client_t;
138 pub fn idevicerestore_client_free(client: *mut idevicerestore_client_t);
139 pub fn idevicerestore_set_ecid(client: *mut idevicerestore_client_t, ecid: u64);
140 pub fn idevicerestore_set_udid(client: *mut idevicerestore_client_t, udid: *const c_char);
141 pub fn idevicerestore_set_flags(client: *mut idevicerestore_client_t, flags: c_int);
142 pub fn idevicerestore_set_ipsw(client: *mut idevicerestore_client_t, path: *const c_char);
143 pub fn idevicerestore_set_cache_path(client: *mut idevicerestore_client_t, path: *const c_char);
144 pub fn idevicerestore_set_progress_callback(
145 client: *mut idevicerestore_client_t,
146 cbfunc: idevicerestore_progress_cb_t,
147 userdata: *mut c_void,
148 );
149 pub fn idevicerestore_start(client: *mut idevicerestore_client_t) -> c_int;
150 pub fn register_progress(tag: u32, label: *const c_char);
153 fn set_update_progress_func(
156 func: Option<unsafe extern "C" fn(list: *mut *mut c_void, count: c_int)>,
157 );
158}
159
160unsafe extern "C" fn discard_progress(_list: *mut *mut c_void, _count: c_int) {}
162
163pub fn suppress_tagged_progress() {
168 unsafe { set_update_progress_func(Some(discard_progress)) };
169}
170
171pub fn init_progress() {
180 unsafe { register_progress(0, std::ptr::null()) };
181}
182
183#[cfg(any(target_os = "linux", target_os = "windows"))]
186extern "C" {
187 fn restorekit_usbmuxd_start(socket_path: *const c_char) -> c_int;
188 fn restorekit_usbmuxd_run();
189 fn restorekit_usbmuxd_stop();
190 fn restorekit_usbmuxd_cleanup();
191}
192
193#[cfg(any(target_os = "linux", target_os = "windows"))]
196pub fn usbmuxd_start(path: &std::ffi::CStr) -> std::result::Result<(), c_int> {
197 let rc = unsafe { restorekit_usbmuxd_start(path.as_ptr()) };
198 if rc == 0 {
199 Ok(())
200 } else {
201 Err(rc)
202 }
203}
204
205#[cfg(any(target_os = "linux", target_os = "windows"))]
207pub fn usbmuxd_run() {
208 unsafe { restorekit_usbmuxd_run() }
209}
210
211#[cfg(any(target_os = "linux", target_os = "windows"))]
213pub fn usbmuxd_stop() {
214 unsafe { restorekit_usbmuxd_stop() }
215}
216
217#[cfg(any(target_os = "linux", target_os = "windows"))]
219pub fn usbmuxd_cleanup() {
220 unsafe { restorekit_usbmuxd_cleanup() }
221}
222
223#[cfg(test)]
224mod tests {
225 use super::*;
226 use std::ffi::CString;
227
228 #[test]
229 fn error_tail_filters_by_level() {
230 if let Ok(mut b) = LOG_LINES.lock() {
231 b.clear();
232 }
233 LOG_ECHO.store(false, Ordering::Relaxed);
234
235 let cap = |level, s: &str| {
236 let c = CString::new(s).unwrap();
237 unsafe { restorekit_log_capture(level, c.as_ptr()) };
238 };
239 cap(LL_INFO, "info line");
240 cap(LL_ERROR, "boom -3");
241 cap(LL_VERBOSE, "verbose noise");
242 cap(LL_WARNING, "careful");
243
244 let tail = error_tail(10);
245 assert!(tail.contains("boom -3"));
246 assert!(tail.contains("careful"));
247 assert!(!tail.contains("info line"));
248 assert!(!tail.contains("verbose noise"));
249 }
250}