Skip to main content

bootpkg/
lib.rs

1// Copyright 2024 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5pub 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        // prep boot directory.
97        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 =
123            vfs::directory::serve_read_only(boot_dir, vfs::execution_scope::ExecutionScope::new());
124        fasync::unblock(move || {
125            let channel = dir_client.into_channel().unwrap().into_zx_channel();
126            let client: File = fdio::create_fd(channel.into()).unwrap().into();
127            list(&client).unwrap();
128        })
129        .await;
130    }
131
132    #[fasync::run_singlethreaded(test)]
133    async fn show_test() {
134        // prep boot directory.
135        let contents = MetaContents::from([
136            (
137                "foo",
138                Hash::from_str("b21b34f8370687249a9cd9d4b306dee4c81f1f854f84de4626dc00c000c902fe")
139                    .unwrap(),
140            ),
141            (
142                "bar",
143                Hash::from_str("d0ff2aa87c938862d56fff76c9fe362240d2d51699506753e5840e05d41a3bf2")
144                    .unwrap(),
145            ),
146            (
147                "baz",
148                Hash::from_str("d0ff2aa87c938862d56fff76c9fe362240d2d51699506753e5840e05d41a3bf2")
149                    .unwrap(),
150            ),
151        ]);
152        let mut data = Vec::new();
153        contents.serialize(&mut data).unwrap();
154
155        let meta_contents = data.clone();
156        let mut path_content_map: BTreeMap<&str, (u64, Box<dyn Read>)> = BTreeMap::new();
157        path_content_map
158            .insert("meta/contents", (meta_contents.len() as u64, Box::new(&meta_contents[..])));
159        let mut far_contents = Vec::new();
160        fuchsia_archive::write(&mut far_contents, path_content_map).unwrap();
161
162        let boot_dir = pseudo_directory! {
163            "data" => pseudo_directory! {
164                "bootfs_packages" => read_only(data),
165            },
166            "blob" => pseudo_directory! {
167                "b21b34f8370687249a9cd9d4b306dee4c81f1f854f84de4626dc00c000c902fe" => read_only(far_contents),
168            }
169        };
170        let dir_client =
171            vfs::directory::serve_read_only(boot_dir, vfs::execution_scope::ExecutionScope::new());
172        fasync::unblock(move || {
173            let channel = dir_client.into_channel().unwrap().into_zx_channel();
174            let client: File = fdio::create_fd(channel.into()).unwrap().into();
175            show(&client, "foo").unwrap();
176        })
177        .await;
178    }
179}