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::{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::{create_endpoints, ServerEnd};
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::directory::entry_container::Directory;
96    use vfs::execution_scope::ExecutionScope;
97    use vfs::file::vmo::read_only;
98    use vfs::pseudo_directory;
99
100    #[fasync::run_singlethreaded(test)]
101    async fn list_test() {
102        // prep boot directory.
103        let contents = hashmap! {
104            "foo".to_string() =>
105                Hash::from_str("b21b34f8370687249a9cd9d4b306dee4c81f1f854f84de4626dc00c000c902fe").unwrap(),
106            "bar".to_string() =>
107                Hash::from_str("d0ff2aa87c938862d56fff76c9fe362240d2d51699506753e5840e05d41a3bf2").unwrap(),
108            "baz".to_string() =>
109                Hash::from_str("d0ff2aa87c938862d56fff76c9fe362240d2d51699506753e5840e05d41a3bf2").unwrap(),
110        };
111        let contents = MetaContents::from_map(contents).unwrap();
112        let mut data = Vec::new();
113        contents.serialize(&mut data).unwrap();
114
115        let boot_dir = pseudo_directory! {
116            "data" => pseudo_directory! {
117                "bootfs_packages" => read_only(data),
118            },
119        };
120        let (client, server) = create_endpoints::<fidl_fuchsia_io::DirectoryMarker>();
121        let scope = ExecutionScope::new();
122        boot_dir.open(
123            scope,
124            fio::OpenFlags::RIGHT_READABLE | fio::OpenFlags::DIRECTORY,
125            vfs::path::Path::dot(),
126            ServerEnd::new(server.into_channel()),
127        );
128        fasync::unblock(move || {
129            let client: File = fdio::create_fd(client.into_channel().into()).unwrap().into();
130            list(&client).unwrap();
131        })
132        .await;
133    }
134
135    #[fasync::run_singlethreaded(test)]
136    async fn show_test() {
137        // prep boot directory.
138        let contents = hashmap! {
139            "foo".to_string() =>
140                Hash::from_str("b21b34f8370687249a9cd9d4b306dee4c81f1f854f84de4626dc00c000c902fe").unwrap(),
141            "bar".to_string() =>
142                Hash::from_str("d0ff2aa87c938862d56fff76c9fe362240d2d51699506753e5840e05d41a3bf2").unwrap(),
143            "baz".to_string() =>
144                Hash::from_str("d0ff2aa87c938862d56fff76c9fe362240d2d51699506753e5840e05d41a3bf2").unwrap(),
145        };
146        let contents = MetaContents::from_map(contents).unwrap();
147        let mut data = Vec::new();
148        contents.serialize(&mut data).unwrap();
149
150        let meta_contents = data.clone();
151        let mut path_content_map: BTreeMap<&str, (u64, Box<dyn Read>)> = BTreeMap::new();
152        path_content_map
153            .insert("meta/contents", (meta_contents.len() as u64, Box::new(&meta_contents[..])));
154        let mut far_contents = Vec::new();
155        fuchsia_archive::write(&mut far_contents, path_content_map).unwrap();
156
157        let boot_dir = pseudo_directory! {
158            "data" => pseudo_directory! {
159                "bootfs_packages" => read_only(data),
160            },
161            "blob" => pseudo_directory! {
162                "b21b34f8370687249a9cd9d4b306dee4c81f1f854f84de4626dc00c000c902fe" => read_only(far_contents),
163            }
164        };
165        let (client, server) = create_endpoints::<fidl_fuchsia_io::DirectoryMarker>();
166        let scope = ExecutionScope::new();
167        boot_dir.open(
168            scope,
169            fio::OpenFlags::RIGHT_READABLE | fio::OpenFlags::DIRECTORY,
170            vfs::path::Path::dot(),
171            ServerEnd::new(server.into_channel()),
172        );
173        fasync::unblock(move || {
174            let client: File = fdio::create_fd(client.into_channel().into()).unwrap().into();
175            show(&client, "foo").unwrap();
176        })
177        .await;
178    }
179}