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, OpenCommand, PkgStatusCommand, RepoAddCommand,
9    RepoAddFileCommand, RepoAddSubCommand, RepoAddUrlCommand, RepoCommand, RepoRemoveCommand,
10    RepoShowCommand, RepoSubCommand, ResolveCommand, RuleClearCommand, RuleCommand,
11    RuleDumpDynamicCommand, RuleListCommand, RuleReplaceCommand, RuleReplaceFileCommand,
12    RuleReplaceJsonCommand, 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::PkgStatus(PkgStatusCommand { pkg_url: _ }) => {
77            anyhow::bail!("`pkgctl pkg-status` is being deleted, https://fxbug.dev/552675412");
78        }
79        Command::Open(OpenCommand { meta_far_blob_id: _ }) => {
80            anyhow::bail!("`pkgctl open` is being deleted, https://fxbug.dev/552670958");
81        }
82        Command::Repo(RepoCommand { verbose, subcommand }) => {
83            let repo_manager = connect_to_protocol::<fpkg::RepositoryManagerMarker>()
84                .context("Failed to connect to resolver service")?;
85
86            match subcommand {
87                None => {
88                    if !verbose {
89                        // with no arguments, list available repos
90                        let repos = fetch_repos(repo_manager).await?;
91
92                        let mut urls =
93                            repos.into_iter().map(|r| r.repo_url().to_string()).collect::<Vec<_>>();
94                        urls.sort_unstable();
95                        urls.into_iter().for_each(|url| println!("{url}"));
96                    } else {
97                        let repos = fetch_repos(repo_manager).await?;
98
99                        let s = serde_json::to_string_pretty(&repos).expect("valid json");
100                        println!("{s}");
101                    }
102                    Ok(0)
103                }
104                Some(RepoSubCommand::Add(RepoAddCommand { subcommand })) => {
105                    match subcommand {
106                        RepoAddSubCommand::File(RepoAddFileCommand { persist, name, file }) => {
107                            let mut repo: pkg::RepositoryConfig =
108                                serde_json::from_reader(io::BufReader::new(File::open(file)?))?;
109                            // If a name is specified via the command line, override the
110                            // automatically derived name.
111                            if let Some(n) = name {
112                                repo = pkg::RepositoryConfigBuilder::from(repo)
113                                    .repo_url(RepositoryUrl::parse_host(n)?)
114                                    .build();
115                            }
116                            // The storage type can be overridden to persistent via the
117                            // command line.
118                            if persist {
119                                repo = pkg::RepositoryConfigBuilder::from(repo)
120                                    .repo_storage_type(pkg::RepositoryStorageType::Persistent)
121                                    .build();
122                            }
123
124                            let res = repo_manager.add(&repo.into()).await?;
125                            let () = res.map_err(zx::Status::err_from_raw)?;
126                        }
127                        RepoAddSubCommand::Url(RepoAddUrlCommand { persist, name, repo_url }) => {
128                            let res = fetch_url(repo_url, None, vec![]).await?;
129                            let mut repo: pkg::RepositoryConfig = serde_json::from_slice(&res)?;
130                            // If a name is specified via the command line, override the
131                            // automatically derived name.
132                            if let Some(n) = name {
133                                repo = pkg::RepositoryConfigBuilder::from(repo)
134                                    .repo_url(RepositoryUrl::parse_host(n)?)
135                                    .build();
136                            }
137                            // The storage type can be overridden to persistent via the
138                            // command line.
139                            if persist {
140                                repo = pkg::RepositoryConfigBuilder::from(repo)
141                                    .repo_storage_type(pkg::RepositoryStorageType::Persistent)
142                                    .build();
143                            }
144
145                            let res = repo_manager.add(&repo.into()).await?;
146                            let () = res.map_err(zx::Status::err_from_raw)?;
147                        }
148                    }
149
150                    Ok(0)
151                }
152
153                Some(RepoSubCommand::Remove(RepoRemoveCommand { repo_url })) => {
154                    let res = repo_manager.remove(&repo_url).await?;
155                    let () = res.map_err(zx::Status::err_from_raw)?;
156
157                    Ok(0)
158                }
159
160                Some(RepoSubCommand::Show(RepoShowCommand { repo_url })) => {
161                    let repos = fetch_repos(repo_manager).await?;
162                    for repo in repos.into_iter() {
163                        if repo.repo_url().to_string() == repo_url {
164                            let s = serde_json::to_string_pretty(&repo).expect("valid json");
165                            println!("{s}");
166                            return Ok(0);
167                        }
168                    }
169
170                    println!("Package repository not found: {repo_url:?}");
171                    Ok(1)
172                }
173            }
174        }
175        Command::Rule(RuleCommand { subcommand }) => {
176            let engine = connect_to_protocol::<EngineMarker>()
177                .context("Failed to connect to rewrite engine service")?;
178
179            match subcommand {
180                RuleSubCommand::List(RuleListCommand {}) => {
181                    let (iter, iter_server_end) = fidl::endpoints::create_proxy();
182                    engine.list(iter_server_end)?;
183
184                    let mut rules = Vec::new();
185                    loop {
186                        let more = iter.next().await?;
187                        if more.is_empty() {
188                            break;
189                        }
190                        rules.extend(more);
191                    }
192                    let rules = rules.into_iter().map(|rule| rule.try_into()).collect::<Result<
193                        Vec<RewriteRule>,
194                        _,
195                    >>(
196                    )?;
197
198                    for rule in rules {
199                        println!("{rule:#?}");
200                    }
201                }
202                RuleSubCommand::Clear(RuleClearCommand {}) => {
203                    do_transaction(&engine, |transaction| async move {
204                        transaction.reset_all()?;
205                        Ok(transaction)
206                    })
207                    .await?;
208                }
209                RuleSubCommand::DumpDynamic(RuleDumpDynamicCommand {}) => {
210                    let (transaction, transaction_server_end) = fidl::endpoints::create_proxy();
211                    let () = engine.start_edit_transaction(transaction_server_end)?;
212                    let (iter, iter_server_end) = fidl::endpoints::create_proxy();
213                    transaction.list_dynamic(iter_server_end)?;
214                    let mut rules = Vec::new();
215                    loop {
216                        let more = iter.next().await?;
217                        if more.is_empty() {
218                            break;
219                        }
220                        rules.extend(more);
221                    }
222                    let rules = rules.into_iter().map(|rule| rule.try_into()).collect::<Result<
223                        Vec<RewriteRule>,
224                        _,
225                    >>(
226                    )?;
227                    let rule_configs = RuleConfig::Version1(rules);
228                    let dynamic_rules = serde_json::to_string_pretty(&rule_configs)?;
229                    println!("{dynamic_rules}");
230                }
231                RuleSubCommand::Replace(RuleReplaceCommand { subcommand }) => {
232                    let RuleConfig::Version1(ref rules) = match subcommand {
233                        RuleReplaceSubCommand::File(RuleReplaceFileCommand { file }) => {
234                            serde_json::from_reader(io::BufReader::new(File::open(file)?))?
235                        }
236                        RuleReplaceSubCommand::Json(RuleReplaceJsonCommand { config }) => config,
237                    };
238
239                    do_transaction(&engine, |transaction| {
240                        async move {
241                            transaction.reset_all()?;
242                            // add() inserts rules as highest priority, so iterate over our
243                            // prioritized list of rules so they end up in the right order.
244                            for rule in rules.iter().rev() {
245                                let () = transaction.add(rule.clone()).await?;
246                            }
247                            Ok(transaction)
248                        }
249                    })
250                    .await?;
251                }
252            }
253
254            Ok(0)
255        }
256        Command::Gc(GcCommand {}) => {
257            let space_manager = connect_to_protocol::<fpkg_gc::ManagerMarker>()
258                .context("Failed to connect to space manager service")?;
259            space_manager
260                .gc()
261                .await?
262                .map_err(|err| format_err!("Garbage collection failed with error: {:?}", err))
263                .map(|_| 0i32)
264        }
265    }
266}
267
268async fn fetch_repos(
269    repo_manager: fpkg::RepositoryManagerProxy,
270) -> Result<Vec<pkg::RepositoryConfig>, anyhow::Error> {
271    let (iter, server_end) = fidl::endpoints::create_proxy();
272    repo_manager.list(server_end)?;
273    let mut repos = vec![];
274
275    loop {
276        let chunk = iter.next().await?;
277        if chunk.is_empty() {
278            break;
279        }
280        repos.extend(chunk);
281    }
282
283    repos
284        .into_iter()
285        .map(|repo| pkg::RepositoryConfig::try_from(repo).map_err(anyhow::Error::from))
286        .collect()
287}