Skip to main content

greentic_bundle/bundle_fs/
native_unsquashfs_reader.rs

1use std::io::ErrorKind;
2use std::path::Path;
3use std::process::Command;
4
5use anyhow::{Result, bail};
6
7use super::{BundleEntry, BundleEntryKind, BundleFsReader, READER_ENV};
8
9pub struct UnsquashfsBundleFsReader;
10
11impl BundleFsReader for UnsquashfsBundleFsReader {
12    fn list_bundle(&self, bundle_file: &Path) -> Result<Vec<BundleEntry>> {
13        let output = Command::new("unsquashfs")
14            .args(["-ls", bundle_file.to_str().unwrap_or_default()])
15            .output()
16            .map_err(map_spawn_error)?;
17        if !output.status.success() {
18            bail!(
19                "unsquashfs failed while listing {}: {}",
20                bundle_file.display(),
21                String::from_utf8_lossy(&output.stderr).trim()
22            );
23        }
24
25        let stdout = String::from_utf8(output.stdout)?;
26        Ok(stdout
27            .lines()
28            .map(str::trim)
29            .filter(|line| !line.is_empty())
30            .map(normalize_unsquashfs_list_entry)
31            .filter(|entry| !entry.is_empty() && entry != ".")
32            .map(|path| BundleEntry {
33                path,
34                kind: BundleEntryKind::Other,
35            })
36            .collect())
37    }
38
39    fn extract_bundle(&self, bundle_file: &Path, output_dir: &Path) -> Result<()> {
40        std::fs::create_dir_all(output_dir)?;
41        let output = Command::new("unsquashfs")
42            .args([
43                "-no-progress",
44                "-quiet",
45                "-dest",
46                output_dir.to_str().unwrap_or_default(),
47                bundle_file.to_str().unwrap_or_default(),
48            ])
49            .output()
50            .map_err(map_spawn_error)?;
51        if !output.status.success() {
52            bail!(
53                "unsquashfs failed while extracting {} into {}: {}",
54                bundle_file.display(),
55                output_dir.display(),
56                String::from_utf8_lossy(&output.stderr).trim()
57            );
58        }
59        Ok(())
60    }
61}
62
63pub fn read_bundle_file_with_unsquashfs(bundle_file: &Path, inner_path: &str) -> Result<Vec<u8>> {
64    let output = Command::new("unsquashfs")
65        .args(["-cat", bundle_file.to_str().unwrap_or_default(), inner_path])
66        .output()
67        .map_err(map_spawn_error)?;
68    if !output.status.success() {
69        bail!(
70            "unsquashfs failed for {}:{}: {}",
71            bundle_file.display(),
72            inner_path,
73            String::from_utf8_lossy(&output.stderr).trim()
74        );
75    }
76    Ok(output.stdout)
77}
78
79fn map_spawn_error(error: std::io::Error) -> anyhow::Error {
80    match error.kind() {
81        ErrorKind::NotFound => anyhow::anyhow!(
82            "{READER_ENV}=unsquashfs was requested, but unsquashfs was not found. Install squashfs-tools or unset {READER_ENV} to use the Rust-native reader."
83        ),
84        _ => anyhow::Error::new(error).context("spawn unsquashfs"),
85    }
86}
87
88fn normalize_unsquashfs_list_entry(line: &str) -> String {
89    let trimmed = line.trim_matches('/');
90    let Some(rest) = trimmed.strip_prefix("squashfs-root") else {
91        return trimmed.to_string();
92    };
93    rest.trim_matches('/').to_string()
94}
95
96#[cfg(test)]
97mod tests {
98    use super::normalize_unsquashfs_list_entry;
99
100    #[test]
101    fn normalizes_unsquashfs_root_entries() {
102        assert_eq!(normalize_unsquashfs_list_entry("squashfs-root"), "");
103        assert_eq!(
104            normalize_unsquashfs_list_entry("squashfs-root/bundle.yaml"),
105            "bundle.yaml"
106        );
107    }
108}