1pub mod args;
6
7use crate::args::{Args, ShowCommand, SubCommand};
8use anyhow::{format_err, Result};
9use fidl_fuchsia_io as fio;
10use fuchsia_pkg::MetaContents;
11use std::collections::{HashMap, HashSet};
12use std::fs::File;
13use std::io::{Cursor, Read};
14
15pub fn bootpkg(boot_dir: File, args: Args) -> Result<()> {
16 match args.command {
17 SubCommand::List(_) => list(&boot_dir),
18 SubCommand::Show(ShowCommand { package_name }) => show(&boot_dir, &package_name),
19 }
20}
21
22type PackageList = HashMap<String, fuchsia_merkle::Hash>;
23
24fn read_file(boot_dir: &File, path: &str) -> Result<Vec<u8>> {
25 let mut file = fdio::open_fd_at(boot_dir, path, fio::Flags::PERM_READ)?;
26 let mut buffer = Vec::new();
27 file.read_to_end(&mut buffer)?;
28 Ok(buffer)
29}
30
31fn get_package_list(boot_dir: &File) -> Result<PackageList> {
32 let contents = read_file(boot_dir, "data/bootfs_packages")?;
33 Ok(MetaContents::deserialize(&contents[..])?.into_contents())
34}
35
36fn list(boot_dir: &File) -> Result<()> {
37 let package_list = get_package_list(boot_dir)?;
38 let mut package_list = package_list.iter().map(|(name, _)| name).collect::<Vec<_>>();
39 package_list.sort();
40 for name in package_list {
41 println!("{name}");
42 }
43
44 Ok(())
45}
46
47fn show(boot_dir: &File, package_name: &str) -> Result<()> {
48 let package_list = get_package_list(boot_dir)?;
49 let Some(merkle) = package_list.get(package_name) else {
50 return Err(format_err!("package '{package_name}' not found in package list"));
51 };
52 let meta_far = read_file(boot_dir, &format!("blob/{merkle}"))?;
53
54 let mut reader = fuchsia_archive::Reader::new(Cursor::new(meta_far))?;
55 let reader_list = reader.list();
56 let mut meta_files = HashSet::with_capacity(reader_list.len());
57 for entry in reader_list {
58 let path = String::from_utf8(entry.path().to_vec())?;
59 if path.starts_with("meta/") {
60 for (i, _) in path.match_indices('/').skip(1) {
61 if meta_files.contains(&path[..i]) {
62 return Err(format_err!("Colliding entries in meta archive"));
63 }
64 }
65 meta_files.insert(path);
66 }
67 }
68
69 let meta_contents_bytes = reader.read_file(b"meta/contents")?;
70 let non_meta_files = MetaContents::deserialize(&meta_contents_bytes[..])?.into_contents();
71
72 let mut files = meta_files
73 .iter()
74 .chain(non_meta_files.iter().map(|(name, _)| name).filter(|name| *name != "/0"))
75 .collect::<Vec<_>>();
76 files.sort();
77
78 for name in files {
79 println!("{name}");
80 }
81
82 Ok(())
83}
84
85#[cfg(test)]
86mod test {
87 use super::*;
88 use fidl::endpoints::Proxy as _;
89 use fuchsia_async as fasync;
90 use fuchsia_merkle::Hash;
91 use maplit::hashmap;
92 use std::collections::BTreeMap;
93 use std::io::Read;
94 use std::str::FromStr;
95 use vfs::file::vmo::read_only;
96 use vfs::pseudo_directory;
97
98 #[fasync::run_singlethreaded(test)]
99 async fn list_test() {
100 let contents = hashmap! {
102 "foo".to_string() =>
103 Hash::from_str("b21b34f8370687249a9cd9d4b306dee4c81f1f854f84de4626dc00c000c902fe").unwrap(),
104 "bar".to_string() =>
105 Hash::from_str("d0ff2aa87c938862d56fff76c9fe362240d2d51699506753e5840e05d41a3bf2").unwrap(),
106 "baz".to_string() =>
107 Hash::from_str("d0ff2aa87c938862d56fff76c9fe362240d2d51699506753e5840e05d41a3bf2").unwrap(),
108 };
109 let contents = MetaContents::from_map(contents).unwrap();
110 let mut data = Vec::new();
111 contents.serialize(&mut data).unwrap();
112
113 let boot_dir = pseudo_directory! {
114 "data" => pseudo_directory! {
115 "bootfs_packages" => read_only(data),
116 },
117 };
118 let dir_client = vfs::directory::serve_read_only(boot_dir);
119 fasync::unblock(move || {
120 let channel = dir_client.into_channel().unwrap().into_zx_channel();
121 let client: File = fdio::create_fd(channel.into()).unwrap().into();
122 list(&client).unwrap();
123 })
124 .await;
125 }
126
127 #[fasync::run_singlethreaded(test)]
128 async fn show_test() {
129 let contents = hashmap! {
131 "foo".to_string() =>
132 Hash::from_str("b21b34f8370687249a9cd9d4b306dee4c81f1f854f84de4626dc00c000c902fe").unwrap(),
133 "bar".to_string() =>
134 Hash::from_str("d0ff2aa87c938862d56fff76c9fe362240d2d51699506753e5840e05d41a3bf2").unwrap(),
135 "baz".to_string() =>
136 Hash::from_str("d0ff2aa87c938862d56fff76c9fe362240d2d51699506753e5840e05d41a3bf2").unwrap(),
137 };
138 let contents = MetaContents::from_map(contents).unwrap();
139 let mut data = Vec::new();
140 contents.serialize(&mut data).unwrap();
141
142 let meta_contents = data.clone();
143 let mut path_content_map: BTreeMap<&str, (u64, Box<dyn Read>)> = BTreeMap::new();
144 path_content_map
145 .insert("meta/contents", (meta_contents.len() as u64, Box::new(&meta_contents[..])));
146 let mut far_contents = Vec::new();
147 fuchsia_archive::write(&mut far_contents, path_content_map).unwrap();
148
149 let boot_dir = pseudo_directory! {
150 "data" => pseudo_directory! {
151 "bootfs_packages" => read_only(data),
152 },
153 "blob" => pseudo_directory! {
154 "b21b34f8370687249a9cd9d4b306dee4c81f1f854f84de4626dc00c000c902fe" => read_only(far_contents),
155 }
156 };
157 let dir_client = vfs::directory::serve_read_only(boot_dir);
158 fasync::unblock(move || {
159 let channel = dir_client.into_channel().unwrap().into_zx_channel();
160 let client: File = fdio::create_fd(channel.into()).unwrap().into();
161 show(&client, "foo").unwrap();
162 })
163 .await;
164 }
165}