1use std::io::{Read, Seek};
2use std::path::Path;
3use std::process::{Command, Stdio};
4use thiserror::Error;
5
6use crate::reader::XipReader;
7
8pub mod reader;
9
10#[derive(Error, Debug)]
11pub enum UnxipError {
12 #[error("IO Error: {0}")]
13 IoError(std::io::Error),
14 #[error("XAR Error: {0}")]
15 XarError(apple_xar::Error),
16 #[error("Miscellaneous Error: {0}")]
17 Misc(String),
18}
19pub fn unxip<R: Read + Seek + Sized + std::fmt::Debug>(
20 reader: &mut R,
21 output_path: &Path,
22 cpio_path: Option<String>,
23) -> Result<(), UnxipError> {
24 let cpio_path = cpio_path.unwrap_or_else(|| "cpio".to_string());
25 if Command::new(&cpio_path)
26 .arg("--version")
27 .stdout(Stdio::null())
28 .stderr(Stdio::null())
29 .status()
30 .is_err()
31 {
32 return Err(UnxipError::Misc(
33 "cpio not found or incompatible".to_string(),
34 ));
35 }
36
37 let mut xip_reader = XipReader::new(reader)?;
38
39 std::fs::create_dir_all(output_path).map_err(UnxipError::IoError)?;
40
41 let mut child = Command::new(&cpio_path)
42 .arg("-idm")
43 .current_dir(output_path)
44 .stdin(Stdio::piped())
45 .spawn()
46 .map_err(|e| UnxipError::Misc(format!("Failed to spawn cpio: {}", e)))?;
47
48 {
49 let stdin = child
50 .stdin
51 .as_mut()
52 .ok_or_else(|| UnxipError::Misc("Failed to open cpio stdin".to_string()))?;
53 std::io::copy(&mut xip_reader, stdin).map_err(UnxipError::IoError)?;
54 }
55
56 let status = child.wait().map_err(UnxipError::IoError)?;
57 if !status.success() {
58 return Err(UnxipError::Misc(format!(
59 "cpio failed with status: {}",
60 status
61 )));
62 }
63 Ok(())
64}