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