1#![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, 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 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 let Some(n) = name {
112 repo = pkg::RepositoryConfigBuilder::from(repo)
113 .repo_url(RepositoryUrl::parse_host(n)?)
114 .build();
115 }
116 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 let Some(n) = name {
133 repo = pkg::RepositoryConfigBuilder::from(repo)
134 .repo_url(RepositoryUrl::parse_host(n)?)
135 .build();
136 }
137 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 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}