Skip to main content

pkgctl/
main.rs

1// Copyright 2018 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
5#![allow(clippy::let_unit_value)]
6
7use crate::args::{
8    Args, Command, GcCommand, GetHashCommand, RepoAddCommand, RepoAddFileCommand,
9    RepoAddSubCommand, RepoAddUrlCommand, RepoCommand, RepoRemoveCommand, RepoShowCommand,
10    RepoSubCommand, ResolveCommand, RuleClearCommand, RuleCommand, RuleDumpDynamicCommand,
11    RuleListCommand, RuleReplaceCommand, RuleReplaceFileCommand, RuleReplaceJsonCommand,
12    RuleReplaceSubCommand, RuleSubCommand,
13};
14use anyhow::{Context as _, format_err};
15use fetch_url::fetch_url;
16use fidl_fuchsia_pkg as fpkg;
17use fidl_fuchsia_pkg_ext as pkg;
18use fidl_fuchsia_pkg_garbagecollector as fpkg_gc;
19use fidl_fuchsia_pkg_rewrite::EngineMarker;
20use fidl_fuchsia_pkg_rewrite_ext::{Rule as RewriteRule, RuleConfig, do_transaction};
21use fuchsia_async as fasync;
22use fuchsia_component::client::connect_to_protocol;
23use fuchsia_url::RepositoryUrl;
24use futures::stream::TryStreamExt;
25use std::fs::File;
26use std::io;
27use std::process::exit;
28
29mod args;
30
31pub fn main() -> Result<(), anyhow::Error> {
32    let mut executor = fasync::LocalExecutorBuilder::new().build();
33    let Args { command } = argh::from_env();
34    exit(executor.run_singlethreaded(main_helper(command))?)
35}
36
37async fn main_helper(command: Command) -> Result<i32, anyhow::Error> {
38    match command {
39        Command::Resolve(ResolveCommand { pkg_url, verbose }) => {
40            let resolver = connect_to_protocol::<fpkg::PackageResolverMarker>()
41                .context("Failed to connect to resolver service")?;
42            println!("resolving {pkg_url}");
43
44            let (dir, dir_server_end) = fidl::endpoints::create_proxy();
45
46            let _: fpkg::ResolutionContext = resolver
47                .resolve(&pkg_url, dir_server_end)
48                .await?
49                .map_err(fidl_fuchsia_pkg_ext::ResolveError::from)
50                .with_context(|| format!("Failed to resolve {pkg_url}"))?;
51
52            if verbose {
53                println!("package contents:");
54                let mut stream =
55                    fuchsia_fs::directory::readdir_recursive(&dir, /*timeout=*/ None);
56                while let Some(entry) = stream.try_next().await? {
57                    println!("/{}", entry.name);
58                }
59            }
60
61            Ok(0)
62        }
63        Command::GetHash(GetHashCommand { pkg_url }) => {
64            let resolver = connect_to_protocol::<fpkg::PackageResolverMarker>()
65                .context("Failed to connect to resolver service")?;
66            let blob_id =
67                resolver.get_hash(&fpkg::PackageUrl { url: pkg_url }).await?.map_err(|i| {
68                    format_err!(
69                        "Failed to get package hash with error: {}",
70                        zx::Status::err_from_raw(i)
71                    )
72                })?;
73            println!("{}", pkg::BlobId::from(blob_id));
74            Ok(0)
75        }
76        Command::Repo(RepoCommand { verbose, subcommand }) => {
77            let repo_manager = connect_to_protocol::<fpkg::RepositoryManagerMarker>()
78                .context("Failed to connect to resolver service")?;
79
80            match subcommand {
81                None => {
82                    if !verbose {
83                        // with no arguments, list available repos
84                        let repos = fetch_repos(repo_manager).await?;
85
86                        let mut urls =
87                            repos.into_iter().map(|r| r.repo_url().to_string()).collect::<Vec<_>>();
88                        urls.sort_unstable();
89                        urls.into_iter().for_each(|url| println!("{url}"));
90                    } else {
91                        let repos = fetch_repos(repo_manager).await?;
92
93                        let s = serde_json::to_string_pretty(&repos).expect("valid json");
94                        println!("{s}");
95                    }
96                    Ok(0)
97                }
98                Some(RepoSubCommand::Add(RepoAddCommand { subcommand })) => {
99                    match subcommand {
100                        RepoAddSubCommand::File(RepoAddFileCommand { persist, name, file }) => {
101                            let mut repo: pkg::RepositoryConfig =
102                                serde_json::from_reader(io::BufReader::new(File::open(file)?))?;
103                            // If a name is specified via the command line, override the
104                            // automatically derived name.
105                            if let Some(n) = name {
106                                repo = pkg::RepositoryConfigBuilder::from(repo)
107                                    .repo_url(RepositoryUrl::parse_host(n)?)
108                                    .build();
109                            }
110                            // The storage type can be overridden to persistent via the
111                            // command line.
112                            if persist {
113                                repo = pkg::RepositoryConfigBuilder::from(repo)
114                                    .repo_storage_type(pkg::RepositoryStorageType::Persistent)
115                                    .build();
116                            }
117
118                            let res = repo_manager.add(&repo.into()).await?;
119                            let () = res.map_err(zx::Status::err_from_raw)?;
120                        }
121                        RepoAddSubCommand::Url(RepoAddUrlCommand { persist, name, repo_url }) => {
122                            let res = fetch_url(repo_url, None, vec![]).await?;
123                            let mut repo: pkg::RepositoryConfig = serde_json::from_slice(&res)?;
124                            // If a name is specified via the command line, override the
125                            // automatically derived name.
126                            if let Some(n) = name {
127                                repo = pkg::RepositoryConfigBuilder::from(repo)
128                                    .repo_url(RepositoryUrl::parse_host(n)?)
129                                    .build();
130                            }
131                            // The storage type can be overridden to persistent via the
132                            // command line.
133                            if persist {
134                                repo = pkg::RepositoryConfigBuilder::from(repo)
135                                    .repo_storage_type(pkg::RepositoryStorageType::Persistent)
136                                    .build();
137                            }
138
139                            let res = repo_manager.add(&repo.into()).await?;
140                            let () = res.map_err(zx::Status::err_from_raw)?;
141                        }
142                    }
143
144                    Ok(0)
145                }
146
147                Some(RepoSubCommand::Remove(RepoRemoveCommand { repo_url })) => {
148                    let res = repo_manager.remove(&repo_url).await?;
149                    let () = res.map_err(zx::Status::err_from_raw)?;
150
151                    Ok(0)
152                }
153
154                Some(RepoSubCommand::Show(RepoShowCommand { repo_url })) => {
155                    let repos = fetch_repos(repo_manager).await?;
156                    for repo in repos.into_iter() {
157                        if repo.repo_url().to_string() == repo_url {
158                            let s = serde_json::to_string_pretty(&repo).expect("valid json");
159                            println!("{s}");
160                            return Ok(0);
161                        }
162                    }
163
164                    println!("Package repository not found: {repo_url:?}");
165                    Ok(1)
166                }
167            }
168        }
169        Command::Rule(RuleCommand { subcommand }) => {
170            let engine = connect_to_protocol::<EngineMarker>()
171                .context("Failed to connect to rewrite engine service")?;
172
173            match subcommand {
174                RuleSubCommand::List(RuleListCommand {}) => {
175                    let (iter, iter_server_end) = fidl::endpoints::create_proxy();
176                    engine.list(iter_server_end)?;
177
178                    let mut rules = Vec::new();
179                    loop {
180                        let more = iter.next().await?;
181                        if more.is_empty() {
182                            break;
183                        }
184                        rules.extend(more);
185                    }
186                    let rules = rules.into_iter().map(|rule| rule.try_into()).collect::<Result<
187                        Vec<RewriteRule>,
188                        _,
189                    >>(
190                    )?;
191
192                    for rule in rules {
193                        println!("{rule:#?}");
194                    }
195                }
196                RuleSubCommand::Clear(RuleClearCommand {}) => {
197                    do_transaction(&engine, |transaction| async move {
198                        transaction.reset_all()?;
199                        Ok(transaction)
200                    })
201                    .await?;
202                }
203                RuleSubCommand::DumpDynamic(RuleDumpDynamicCommand {}) => {
204                    let (transaction, transaction_server_end) = fidl::endpoints::create_proxy();
205                    let () = engine.start_edit_transaction(transaction_server_end)?;
206                    let (iter, iter_server_end) = fidl::endpoints::create_proxy();
207                    transaction.list_dynamic(iter_server_end)?;
208                    let mut rules = Vec::new();
209                    loop {
210                        let more = iter.next().await?;
211                        if more.is_empty() {
212                            break;
213                        }
214                        rules.extend(more);
215                    }
216                    let rules = rules.into_iter().map(|rule| rule.try_into()).collect::<Result<
217                        Vec<RewriteRule>,
218                        _,
219                    >>(
220                    )?;
221                    let rule_configs = RuleConfig::Version1(rules);
222                    let dynamic_rules = serde_json::to_string_pretty(&rule_configs)?;
223                    println!("{dynamic_rules}");
224                }
225                RuleSubCommand::Replace(RuleReplaceCommand { subcommand }) => {
226                    let RuleConfig::Version1(ref rules) = match subcommand {
227                        RuleReplaceSubCommand::File(RuleReplaceFileCommand { file }) => {
228                            serde_json::from_reader(io::BufReader::new(File::open(file)?))?
229                        }
230                        RuleReplaceSubCommand::Json(RuleReplaceJsonCommand { config }) => config,
231                    };
232
233                    do_transaction(&engine, |transaction| {
234                        async move {
235                            transaction.reset_all()?;
236                            // add() inserts rules as highest priority, so iterate over our
237                            // prioritized list of rules so they end up in the right order.
238                            for rule in rules.iter().rev() {
239                                let () = transaction.add(rule.clone()).await?;
240                            }
241                            Ok(transaction)
242                        }
243                    })
244                    .await?;
245                }
246            }
247
248            Ok(0)
249        }
250        Command::Gc(GcCommand {}) => {
251            let space_manager = connect_to_protocol::<fpkg_gc::ManagerMarker>()
252                .context("Failed to connect to space manager service")?;
253            space_manager
254                .gc()
255                .await?
256                .map_err(|err| format_err!("Garbage collection failed with error: {:?}", err))
257                .map(|_| 0i32)
258        }
259    }
260}
261
262async fn fetch_repos(
263    repo_manager: fpkg::RepositoryManagerProxy,
264) -> Result<Vec<pkg::RepositoryConfig>, anyhow::Error> {
265    let (iter, server_end) = fidl::endpoints::create_proxy();
266    repo_manager.list(server_end)?;
267    let mut repos = vec![];
268
269    loop {
270        let chunk = iter.next().await?;
271        if chunk.is_empty() {
272            break;
273        }
274        repos.extend(chunk);
275    }
276
277    repos
278        .into_iter()
279        .map(|repo| pkg::RepositoryConfig::try_from(repo).map_err(anyhow::Error::from))
280        .collect()
281}