1use crate::cli::format::{
6 format_create_error, format_destroy_error, format_resolve_error, format_start_error,
7};
8use crate::lifecycle::{
9 ActionError, CreateError, DestroyError, StartError, create_instance_in_collection,
10 destroy_instance_in_collection, resolve_instance, start_instance, start_instance_with_args,
11};
12use anyhow::{Result, bail, format_err};
13#[allow(unused)]
14use flex_client::ProxyHasDomain;
15use flex_client::Socket;
16use flex_fuchsia_component as fcomponent;
17use flex_fuchsia_component_decl as fdecl;
18use flex_fuchsia_process as fprocess;
19use flex_fuchsia_sys2 as fsys;
20use fuchsia_url::fuchsia_pkg::AbsoluteComponentUrl;
21use futures::future::BoxFuture;
22#[allow(unused)]
23use futures::{AsyncReadExt, AsyncWriteExt};
24use moniker::Moniker;
25use std::io::Read;
26
27const TRANSFER_CHUNK_SIZE: usize = 8192;
31
32async fn copy<W: std::io::Write>(source: Socket, mut sink: W) -> Result<()> {
33 #[cfg(not(feature = "fdomain"))]
34 let mut source = fuchsia_async::Socket::from_socket(source);
35 #[cfg(feature = "fdomain")]
36 let mut source = source;
37 let mut buf = [0u8; TRANSFER_CHUNK_SIZE];
38 loop {
39 let bytes_read = source.read(&mut buf).await?;
40 if bytes_read == 0 {
41 return Ok(());
42 }
43 sink.write_all(&buf[..bytes_read])?;
44 sink.flush()?;
45 }
46}
47
48fn handle_id_for_fd(fd: u32) -> u32 {
52 const PA_FD: u32 = 0x30;
53 PA_FD | fd << 16
54}
55
56struct Stdio {
57 local_in: Socket,
58 local_out: Socket,
59 local_err: Socket,
60}
61
62impl Stdio {
63 #[cfg(not(feature = "fdomain"))]
64 fn new() -> (Self, Vec<fprocess::HandleInfo>) {
65 let (local_in, remote_in) = fidl::Socket::create_stream();
66 let (local_out, remote_out) = fidl::Socket::create_stream();
67 let (local_err, remote_err) = fidl::Socket::create_stream();
68
69 (
70 Self { local_in, local_out, local_err },
71 vec![
72 fprocess::HandleInfo { handle: remote_in.into_handle(), id: handle_id_for_fd(0) },
73 fprocess::HandleInfo { handle: remote_out.into_handle(), id: handle_id_for_fd(1) },
74 fprocess::HandleInfo { handle: remote_err.into_handle(), id: handle_id_for_fd(2) },
75 ],
76 )
77 }
78
79 #[cfg(feature = "fdomain")]
80 fn new(client: &std::sync::Arc<flex_client::Client>) -> (Self, Vec<fprocess::HandleInfo>) {
81 use flex_client::HandleBased;
82 let (local_in, remote_in) = client.create_stream_socket();
83 let (local_out, remote_out) = client.create_stream_socket();
84 let (local_err, remote_err) = client.create_stream_socket();
85
86 (
87 Self { local_in, local_out, local_err },
88 vec![
89 fprocess::HandleInfo { handle: remote_in.into_handle(), id: handle_id_for_fd(0) },
90 fprocess::HandleInfo { handle: remote_out.into_handle(), id: handle_id_for_fd(1) },
91 fprocess::HandleInfo { handle: remote_err.into_handle(), id: handle_id_for_fd(2) },
92 ],
93 )
94 }
95
96 async fn forward(self) {
97 let local_in = self.local_in;
98 let local_out = self.local_out;
99 let local_err = self.local_err;
100
101 let mut local_in = flex_client::socket_to_async(local_in);
102
103 std::thread::spawn(move || {
104 let mut term_in = std::io::stdin().lock();
105 let mut buf = [0u8; TRANSFER_CHUNK_SIZE];
106 let mut executor = fuchsia_async::LocalExecutorBuilder::new().build();
107 loop {
108 let bytes_read = term_in.read(&mut buf)?;
109 if bytes_read == 0 {
110 return Ok::<(), anyhow::Error>(());
111 }
112
113 executor.run_singlethreaded(local_in.write_all(&buf[..bytes_read]))?;
114 }
115 });
116
117 std::thread::spawn(move || {
118 let mut executor = fuchsia_async::LocalExecutorBuilder::new().build();
119 let _result: Result<()> = executor
120 .run_singlethreaded(async move { copy(local_err, std::io::stderr()).await });
121 });
122
123 std::thread::spawn(move || {
124 let mut executor = fuchsia_async::LocalExecutorBuilder::new().build();
125 let _result: Result<()> = executor
126 .run_singlethreaded(async move { copy(local_out, std::io::stdout().lock()).await });
127 std::process::exit(0);
128 });
129
130 let () = futures::future::pending().await;
133 }
134}
135
136pub async fn run_cmd<W: std::io::Write>(
137 moniker: Moniker,
138 url: AbsoluteComponentUrl,
139 recreate: bool,
140 connect_stdio: bool,
141 config_overrides: Vec<fdecl::ConfigOverride>,
142 lifecycle_controller_factory: impl Fn()
143 -> BoxFuture<'static, Result<fsys::LifecycleControllerProxy>>,
144 mut writer: W,
145) -> Result<()> {
146 let lifecycle_controller = lifecycle_controller_factory().await?;
147 let parent = moniker
148 .parent()
149 .ok_or_else(|| format_err!("Error: {} does not reference a dynamic instance", moniker))?;
150 let leaf = moniker
151 .leaf()
152 .ok_or_else(|| format_err!("Error: {} does not reference a dynamic instance", moniker))?;
153 let child_name = leaf.name();
154 let collection = leaf
155 .collection()
156 .ok_or_else(|| format_err!("Error: {} does not reference a dynamic instance", moniker))?;
157
158 if recreate {
159 match destroy_instance_in_collection(&lifecycle_controller, &parent, collection, child_name)
161 .await
162 {
163 Ok(()) => {
164 writeln!(writer, "Destroyed existing component instance at {}...", moniker)?;
165 }
166 Err(DestroyError::ActionError(ActionError::InstanceNotFound))
167 | Err(DestroyError::ActionError(ActionError::InstanceNotResolved)) => {
168 }
170 Err(e) => return Err(format_destroy_error(&moniker, e)),
171 }
172 }
173
174 writeln!(writer, "URL: {}", url)?;
175 writeln!(writer, "Moniker: {}", moniker)?;
176 writeln!(writer, "Creating component instance...")?;
177
178 let (mut maybe_stdio, numbered_handles) = if connect_stdio {
181 #[cfg(not(feature = "fdomain"))]
182 let (stdio, numbered_handles) = Stdio::new();
183 #[cfg(feature = "fdomain")]
184 let (stdio, numbered_handles) = Stdio::new(&lifecycle_controller.domain());
185 (Some(stdio), Some(numbered_handles))
186 } else {
187 (None, Some(vec![]))
188 };
189
190 let create_result = create_instance_in_collection(
191 &lifecycle_controller,
192 &parent,
193 collection,
194 child_name,
195 &url,
196 config_overrides.clone(),
197 None,
198 )
199 .await;
200
201 match create_result {
202 Err(CreateError::InstanceAlreadyExists) => {
203 bail!(
204 "\nError: {} already exists.\nUse --recreate to destroy and create a new instance, or provide a different moniker.\n",
205 moniker
206 )
207 }
208 Err(e) => {
209 return Err(format_create_error(&moniker, &parent, collection, e));
210 }
211 Ok(()) => {}
212 }
213
214 writeln!(writer, "Resolving component instance...")?;
215 if let Err(e) = resolve_instance(&lifecycle_controller, &moniker).await {
216 writeln!(writer, "Cleaning up component instance...")?;
217 if let Err(de) =
218 destroy_instance_in_collection(&lifecycle_controller, &parent, collection, child_name)
219 .await
220 {
221 writeln!(writer, "Warning: Failed to clean up component instance: {:?}", de)?;
222 }
223 return Err(format_resolve_error(&moniker, e));
224 }
225
226 writeln!(writer, "Starting component instance...")?;
227 let start_args = fcomponent::StartChildArgs { numbered_handles, ..Default::default() };
228 let res = start_instance_with_args(&lifecycle_controller, &moniker, start_args).await;
229 if let Err(StartError::ActionError(ActionError::Fidl(_e))) = &res {
230 let lifecycle_controller = lifecycle_controller_factory().await?;
237
238 if connect_stdio {
239 #[cfg(not(feature = "fdomain"))]
244 let (stdio, numbered_handles) = Stdio::new();
245 #[cfg(feature = "fdomain")]
246 let (stdio, numbered_handles) = Stdio::new(&lifecycle_controller.domain());
247 maybe_stdio = Some(stdio);
248 let create_args = fcomponent::CreateChildArgs {
249 numbered_handles: Some(numbered_handles),
250 ..Default::default()
251 };
252
253 destroy_instance_in_collection(&lifecycle_controller, &parent, collection, child_name)
254 .await?;
255 create_instance_in_collection(
256 &lifecycle_controller,
257 &parent,
258 collection,
259 child_name,
260 &url,
261 config_overrides,
262 Some(create_args),
263 )
264 .await?;
265 resolve_instance(&lifecycle_controller, &moniker)
266 .await
267 .map_err(|e| format_resolve_error(&moniker, e))?;
268 }
269
270 let _stop_future = start_instance(&lifecycle_controller, &moniker)
271 .await
272 .map_err(|e| format_start_error(&moniker, e))?;
273 } else {
274 let _stop_future = res.map_err(|e| format_start_error(&moniker, e))?;
275 }
276
277 if let Some(stdio) = maybe_stdio {
278 stdio.forward().await;
279 }
280
281 writeln!(writer, "Component instance is running!")?;
282
283 Ok(())
284}
285
286#[cfg(test)]
287mod test {
288 use super::*;
289 use fidl::endpoints::create_proxy_and_stream;
290 use flex_fuchsia_sys2 as fsys;
291 use futures::{FutureExt, TryStreamExt};
292
293 fn setup_fake_lifecycle_controller_ok(
294 expected_parent_moniker: &'static str,
295 expected_collection: &'static str,
296 expected_name: &'static str,
297 expected_url: &'static str,
298 expected_moniker: &'static str,
299 expect_destroy: bool,
300 ) -> fsys::LifecycleControllerProxy {
301 let (lifecycle_controller, mut stream) =
302 create_proxy_and_stream::<fsys::LifecycleControllerMarker>();
303 fuchsia_async::Task::local(async move {
304 if expect_destroy {
305 let req = stream.try_next().await.unwrap().unwrap();
306 match req {
307 fsys::LifecycleControllerRequest::DestroyInstance {
308 parent_moniker,
309 child,
310 responder,
311 } => {
312 assert_eq!(
313 Moniker::parse_str(expected_parent_moniker),
314 Moniker::parse_str(&parent_moniker)
315 );
316 assert_eq!(expected_name, child.name);
317 assert_eq!(expected_collection, child.collection.unwrap());
318 responder.send(Ok(())).unwrap();
319 }
320 _ => panic!("Unexpected Lifecycle Controller request: {:?}", req),
321 }
322 }
323
324 let req = stream.try_next().await.unwrap().unwrap();
325 match req {
326 fsys::LifecycleControllerRequest::CreateInstance {
327 parent_moniker,
328 collection,
329 decl,
330 responder,
331 args: _,
332 } => {
333 assert_eq!(
334 Moniker::parse_str(expected_parent_moniker),
335 Moniker::parse_str(&parent_moniker)
336 );
337 assert_eq!(expected_collection, collection.name);
338 assert_eq!(expected_name, decl.name.unwrap());
339 assert_eq!(expected_url, decl.url.unwrap());
340 responder.send(Ok(())).unwrap();
341 }
342 _ => panic!("Unexpected Lifecycle Controller request: {:?}", req),
343 }
344
345 let req = stream.try_next().await.unwrap().unwrap();
346 match req {
347 fsys::LifecycleControllerRequest::ResolveInstance { moniker, responder } => {
348 assert_eq!(Moniker::parse_str(expected_moniker), Moniker::parse_str(&moniker));
349 responder.send(Ok(())).unwrap();
350 }
351 _ => panic!("Unexpected Lifecycle Controller request: {:?}", req),
352 }
353
354 let req = stream.try_next().await.unwrap().unwrap();
355 match req {
356 fsys::LifecycleControllerRequest::StartInstanceWithArgs {
357 moniker,
358 binder: _,
359 args: _,
360 responder,
361 } => {
362 assert_eq!(Moniker::parse_str(expected_moniker), Moniker::parse_str(&moniker));
363 responder.send(Ok(())).unwrap();
364 }
365 _ => panic!("Unexpected Lifecycle Controller request: {:?}", req),
366 }
367 })
368 .detach();
369 lifecycle_controller
370 }
371
372 fn setup_fake_lifecycle_controller_fail(
373 expected_parent_moniker: &'static str,
374 expected_collection: &'static str,
375 expected_name: &'static str,
376 expected_url: &'static str,
377 ) -> fsys::LifecycleControllerProxy {
378 let (lifecycle_controller, mut stream) =
379 create_proxy_and_stream::<fsys::LifecycleControllerMarker>();
380 fuchsia_async::Task::local(async move {
381 let req = stream.try_next().await.unwrap().unwrap();
382 match req {
383 fsys::LifecycleControllerRequest::DestroyInstance {
384 parent_moniker,
385 child,
386 responder,
387 } => {
388 assert_eq!(
389 Moniker::parse_str(expected_parent_moniker),
390 Moniker::parse_str(&parent_moniker)
391 );
392 assert_eq!(expected_name, child.name);
393 assert_eq!(expected_collection, child.collection.unwrap());
394 responder.send(Ok(())).unwrap();
395 }
396 _ => panic!("Unexpected Lifecycle Controller request: {:?}", req),
397 }
398
399 let req = stream.try_next().await.unwrap().unwrap();
400 match req {
401 fsys::LifecycleControllerRequest::CreateInstance {
402 parent_moniker,
403 collection,
404 decl,
405 responder,
406 args: _,
407 } => {
408 assert_eq!(
409 Moniker::parse_str(expected_parent_moniker),
410 Moniker::parse_str(&parent_moniker)
411 );
412 assert_eq!(expected_collection, collection.name);
413 assert_eq!(expected_name, decl.name.unwrap());
414 assert_eq!(expected_url, decl.url.unwrap());
415 responder.send(Err(fsys::CreateError::InstanceAlreadyExists)).unwrap();
416 }
417 _ => panic!("Unexpected Lifecycle Controller request: {:?}", req),
418 }
419 })
420 .detach();
421 lifecycle_controller
422 }
423
424 fn setup_fake_lifecycle_controller_recreate(
425 expected_parent_moniker: &'static str,
426 expected_collection: &'static str,
427 expected_name: &'static str,
428 expected_url: &'static str,
429 expected_moniker: &'static str,
430 ) -> fsys::LifecycleControllerProxy {
431 let (lifecycle_controller, mut stream) =
432 create_proxy_and_stream::<fsys::LifecycleControllerMarker>();
433 fuchsia_async::Task::local(async move {
434 let req = stream.try_next().await.unwrap().unwrap();
435 match req {
436 fsys::LifecycleControllerRequest::DestroyInstance {
437 parent_moniker,
438 child,
439 responder,
440 } => {
441 assert_eq!(
442 Moniker::parse_str(expected_parent_moniker),
443 Moniker::parse_str(&parent_moniker)
444 );
445 assert_eq!(expected_name, child.name);
446 assert_eq!(expected_collection, child.collection.unwrap());
447 responder.send(Ok(())).unwrap();
448 }
449 _ => panic!("Unexpected Lifecycle Controller request: {:?}", req),
450 }
451
452 let req = stream.try_next().await.unwrap().unwrap();
453 match req {
454 fsys::LifecycleControllerRequest::CreateInstance {
455 parent_moniker,
456 collection,
457 decl,
458 responder,
459 args: _,
460 } => {
461 assert_eq!(
462 Moniker::parse_str(expected_parent_moniker),
463 Moniker::parse_str(&parent_moniker)
464 );
465 assert_eq!(expected_collection, collection.name);
466 assert_eq!(expected_name, decl.name.unwrap());
467 assert_eq!(expected_url, decl.url.unwrap());
468 responder.send(Ok(())).unwrap();
469 }
470 _ => panic!("Unexpected Lifecycle Controller request: {:?}", req),
471 }
472
473 let req = stream.try_next().await.unwrap().unwrap();
474 match req {
475 fsys::LifecycleControllerRequest::ResolveInstance { moniker, responder } => {
476 assert_eq!(Moniker::parse_str(expected_moniker), Moniker::parse_str(&moniker));
477 responder.send(Ok(())).unwrap();
478 }
479 _ => panic!("Unexpected Lifecycle Controller request: {:?}", req),
480 }
481
482 let req = stream.try_next().await.unwrap().unwrap();
483 match req {
484 fsys::LifecycleControllerRequest::StartInstanceWithArgs {
485 moniker,
486 binder: _,
487 args: _,
488 responder,
489 } => {
490 assert_eq!(Moniker::parse_str(expected_moniker), Moniker::parse_str(&moniker));
491 responder.send(Ok(())).unwrap();
492 }
493 _ => panic!("Unexpected Lifecycle Controller request: {:?}", req),
494 }
495 })
496 .detach();
497 lifecycle_controller
498 }
499
500 #[fuchsia_async::run_singlethreaded(test)]
501 async fn test_ok() -> Result<()> {
502 let mut output = Vec::new();
503 let lifecycle_controller = setup_fake_lifecycle_controller_ok(
504 "/some",
505 "collection",
506 "name",
507 "fuchsia-pkg://fuchsia.com/test#meta/test.cm",
508 "/some/collection:name",
509 true,
510 );
511 let response = run_cmd(
512 "/some/collection:name".try_into().unwrap(),
513 "fuchsia-pkg://fuchsia.com/test#meta/test.cm".try_into().unwrap(),
514 true,
515 false,
516 vec![],
517 move || {
518 let lifecycle_controller = lifecycle_controller.clone();
519 async move { Ok(lifecycle_controller) }.boxed()
520 },
521 &mut output,
522 )
523 .await;
524 response.unwrap();
525 Ok(())
526 }
527
528 #[fuchsia_async::run_singlethreaded(test)]
529 async fn test_name() -> Result<()> {
530 let mut output = Vec::new();
531 let lifecycle_controller = setup_fake_lifecycle_controller_ok(
532 "/core",
533 "ffx-laboratory",
534 "foobar",
535 "fuchsia-pkg://fuchsia.com/test#meta/test.cm",
536 "/core/ffx-laboratory:foobar",
537 false,
538 );
539 let response = run_cmd(
540 "/core/ffx-laboratory:foobar".try_into().unwrap(),
541 "fuchsia-pkg://fuchsia.com/test#meta/test.cm".try_into().unwrap(),
542 false,
543 false,
544 vec![],
545 move || {
546 let lifecycle_controller = lifecycle_controller.clone();
547 async move { Ok(lifecycle_controller) }.boxed()
548 },
549 &mut output,
550 )
551 .await;
552 response.unwrap();
553 Ok(())
554 }
555
556 #[fuchsia_async::run_singlethreaded(test)]
557 async fn test_fail() -> Result<()> {
558 let mut output = Vec::new();
559 let lifecycle_controller = setup_fake_lifecycle_controller_fail(
560 "/core",
561 "ffx-laboratory",
562 "test",
563 "fuchsia-pkg://fuchsia.com/test#meta/test.cm",
564 );
565 let response = run_cmd(
566 "/core/ffx-laboratory:test".try_into().unwrap(),
567 "fuchsia-pkg://fuchsia.com/test#meta/test.cm".try_into().unwrap(),
568 true,
569 false,
570 vec![],
571 move || {
572 let lifecycle_controller = lifecycle_controller.clone();
573 async move { Ok(lifecycle_controller) }.boxed()
574 },
575 &mut output,
576 )
577 .await;
578 response.unwrap_err();
579 Ok(())
580 }
581
582 #[fuchsia_async::run_singlethreaded(test)]
583 async fn test_recreate() -> Result<()> {
584 let mut output = Vec::new();
585 let lifecycle_controller = setup_fake_lifecycle_controller_recreate(
586 "/core",
587 "ffx-laboratory",
588 "test",
589 "fuchsia-pkg://fuchsia.com/test#meta/test.cm",
590 "/core/ffx-laboratory:test",
591 );
592 let response = run_cmd(
593 "/core/ffx-laboratory:test".try_into().unwrap(),
594 "fuchsia-pkg://fuchsia.com/test#meta/test.cm".try_into().unwrap(),
595 true,
596 false,
597 vec![],
598 move || {
599 let lifecycle_controller = lifecycle_controller.clone();
600 async move { Ok(lifecycle_controller) }.boxed()
601 },
602 &mut output,
603 )
604 .await;
605 response.unwrap();
606 Ok(())
607 }
608
609 fn setup_fake_lifecycle_controller_resolve_fail_cleanup(
610 expected_parent_moniker: &'static str,
611 expected_collection: &'static str,
612 expected_name: &'static str,
613 expected_url: &'static str,
614 expected_moniker: &'static str,
615 destroy_sender: futures::channel::oneshot::Sender<()>,
616 ) -> fsys::LifecycleControllerProxy {
617 let (lifecycle_controller, mut stream) =
618 create_proxy_and_stream::<fsys::LifecycleControllerMarker>();
619 fuchsia_async::Task::local(async move {
620 let req = stream.try_next().await.unwrap().unwrap();
621 match req {
622 fsys::LifecycleControllerRequest::CreateInstance {
623 parent_moniker,
624 collection,
625 decl,
626 responder,
627 ..
628 } => {
629 assert_eq!(
630 Moniker::parse_str(expected_parent_moniker),
631 Moniker::parse_str(&parent_moniker)
632 );
633 assert_eq!(expected_collection, collection.name);
634 assert_eq!(expected_name, decl.name.unwrap());
635 assert_eq!(expected_url, decl.url.unwrap());
636 responder.send(Ok(())).unwrap();
637 }
638 _ => panic!("Unexpected Lifecycle Controller request: {:?}", req),
639 }
640
641 let req = stream.try_next().await.unwrap().unwrap();
642 match req {
643 fsys::LifecycleControllerRequest::ResolveInstance { moniker, responder } => {
644 assert_eq!(Moniker::parse_str(expected_moniker), Moniker::parse_str(&moniker));
645 responder.send(Err(fsys::ResolveError::PackageNotFound)).unwrap();
646 }
647 _ => panic!("Unexpected Lifecycle Controller request: {:?}", req),
648 }
649
650 let req = stream.try_next().await.unwrap().unwrap();
651 match req {
652 fsys::LifecycleControllerRequest::DestroyInstance {
653 parent_moniker,
654 child,
655 responder,
656 } => {
657 assert_eq!(
658 Moniker::parse_str(expected_parent_moniker),
659 Moniker::parse_str(&parent_moniker)
660 );
661 assert_eq!(expected_name, child.name);
662 assert_eq!(expected_collection, child.collection.unwrap());
663 responder.send(Ok(())).unwrap();
664 let _ = destroy_sender.send(());
665 }
666 _ => panic!("Unexpected Lifecycle Controller request: {:?}", req),
667 }
668 })
669 .detach();
670 lifecycle_controller
671 }
672
673 #[fuchsia_async::run_singlethreaded(test)]
674 async fn test_resolve_fail_cleanup() -> Result<()> {
675 let mut output = Vec::new();
676 let (destroy_sender, mut destroy_receiver) = futures::channel::oneshot::channel::<()>();
677
678 let lifecycle_controller = setup_fake_lifecycle_controller_resolve_fail_cleanup(
679 "/core",
680 "ffx-laboratory",
681 "test",
682 "fuchsia-pkg://fuchsia.com/test#meta/test.cm",
683 "/core/ffx-laboratory:test",
684 destroy_sender,
685 );
686 let response = run_cmd(
687 "/core/ffx-laboratory:test".try_into().unwrap(),
688 "fuchsia-pkg://fuchsia.com/test#meta/test.cm".try_into().unwrap(),
689 false,
690 false,
691 vec![],
692 move || {
693 let lifecycle_controller = lifecycle_controller.clone();
694 async move { Ok(lifecycle_controller) }.boxed()
695 },
696 &mut output,
697 )
698 .await;
699
700 assert!(response.is_err());
701 assert!(destroy_receiver.try_recv().unwrap().is_some());
703 Ok(())
704 }
705}