greentic_bundle/bundle_fs/
native_unsquashfs_reader.rs1use 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}