unxip_rs/
lib.rs

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}