Skip to main content

component_debug/cli/
run.rs

1// Copyright 2023 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
5use 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::{HandleBased, 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
27// This value is fairly arbitrary. The value matches `MAX_BUF` from `fuchsia.io`, but that
28// constant is for `fuchsia.io.File` transfers, which are unrelated to these `zx::socket`
29// transfers.
30const 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
48// The normal Rust representation of this constant is in fuchsia-runtime, which
49// cannot be used on host. Maybe there's a way to move fruntime::HandleType and
50// fruntime::HandleInfo to a place that can be used on host?
51fn 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        let (local_in, remote_in) = client.create_stream_socket();
82        let (local_out, remote_out) = client.create_stream_socket();
83        let (local_err, remote_err) = client.create_stream_socket();
84
85        (
86            Self { local_in, local_out, local_err },
87            vec![
88                fprocess::HandleInfo { handle: remote_in.into_handle(), id: handle_id_for_fd(0) },
89                fprocess::HandleInfo { handle: remote_out.into_handle(), id: handle_id_for_fd(1) },
90                fprocess::HandleInfo { handle: remote_err.into_handle(), id: handle_id_for_fd(2) },
91            ],
92        )
93    }
94
95    async fn forward(self) {
96        let local_in = self.local_in;
97        let local_out = self.local_out;
98        let local_err = self.local_err;
99
100        let mut local_in = flex_client::socket_to_async(local_in);
101
102        std::thread::spawn(move || {
103            let mut term_in = std::io::stdin().lock();
104            let mut buf = [0u8; TRANSFER_CHUNK_SIZE];
105            let mut executor = fuchsia_async::LocalExecutorBuilder::new().build();
106            loop {
107                let bytes_read = term_in.read(&mut buf)?;
108                if bytes_read == 0 {
109                    return Ok::<(), anyhow::Error>(());
110                }
111
112                executor.run_singlethreaded(local_in.write_all(&buf[..bytes_read]))?;
113            }
114        });
115
116        std::thread::spawn(move || {
117            let mut executor = fuchsia_async::LocalExecutorBuilder::new().build();
118            let _result: Result<()> = executor
119                .run_singlethreaded(async move { copy(local_err, std::io::stderr()).await });
120        });
121
122        std::thread::spawn(move || {
123            let mut executor = fuchsia_async::LocalExecutorBuilder::new().build();
124            let _result: Result<()> = executor
125                .run_singlethreaded(async move { copy(local_out, std::io::stdout().lock()).await });
126            std::process::exit(0);
127        });
128
129        // If we're following stdio, we just wait forever. When stdout is
130        // closed, the whole process will exit.
131        let () = futures::future::pending().await;
132    }
133}
134
135pub async fn run_cmd<W: std::io::Write>(
136    moniker: Moniker,
137    url: AbsoluteComponentUrl,
138    recreate: bool,
139    connect_stdio: bool,
140    config_overrides: Vec<fdecl::ConfigOverride>,
141    lifecycle_controller_factory: impl Fn()
142        -> BoxFuture<'static, Result<fsys::LifecycleControllerProxy>>,
143    mut writer: W,
144) -> Result<()> {
145    let lifecycle_controller = lifecycle_controller_factory().await?;
146    let parent = moniker
147        .parent()
148        .ok_or_else(|| format_err!("Error: {} does not reference a dynamic instance", moniker))?;
149    let leaf = moniker
150        .leaf()
151        .ok_or_else(|| format_err!("Error: {} does not reference a dynamic instance", moniker))?;
152    let child_name = leaf.name();
153    let collection = leaf
154        .collection()
155        .ok_or_else(|| format_err!("Error: {} does not reference a dynamic instance", moniker))?;
156
157    if recreate {
158        // First try to destroy any existing instance at this monker.
159        match destroy_instance_in_collection(&lifecycle_controller, &parent, collection, child_name)
160            .await
161        {
162            Ok(()) => {
163                writeln!(writer, "Destroyed existing component instance at {}...", moniker)?;
164            }
165            Err(DestroyError::ActionError(ActionError::InstanceNotFound))
166            | Err(DestroyError::ActionError(ActionError::InstanceNotResolved)) => {
167                // No resolved component exists at this moniker. Nothing to do.
168            }
169            Err(e) => return Err(format_destroy_error(&moniker, e)),
170        }
171    }
172
173    writeln!(writer, "URL: {}", url)?;
174    writeln!(writer, "Moniker: {}", moniker)?;
175    writeln!(writer, "Creating component instance...")?;
176
177    // First try to use StartWithArgs
178
179    let (mut maybe_stdio, numbered_handles) = if connect_stdio {
180        #[cfg(not(feature = "fdomain"))]
181        let (stdio, numbered_handles) = Stdio::new();
182        #[cfg(feature = "fdomain")]
183        let (stdio, numbered_handles) = Stdio::new(&lifecycle_controller.domain());
184        (Some(stdio), Some(numbered_handles))
185    } else {
186        (None, Some(vec![]))
187    };
188
189    let create_result = create_instance_in_collection(
190        &lifecycle_controller,
191        &parent,
192        collection,
193        child_name,
194        &url,
195        config_overrides.clone(),
196        None,
197    )
198    .await;
199
200    match create_result {
201        Err(CreateError::InstanceAlreadyExists) => {
202            bail!(
203                "\nError: {} already exists.\nUse --recreate to destroy and create a new instance, or provide a different moniker.\n",
204                moniker
205            )
206        }
207        Err(e) => {
208            return Err(format_create_error(&moniker, &parent, collection, e));
209        }
210        Ok(()) => {}
211    }
212
213    writeln!(writer, "Resolving component instance...")?;
214    resolve_instance(&lifecycle_controller, &moniker)
215        .await
216        .map_err(|e| format_resolve_error(&moniker, e))?;
217
218    writeln!(writer, "Starting component instance...")?;
219    let start_args = fcomponent::StartChildArgs { numbered_handles, ..Default::default() };
220    let res = start_instance_with_args(&lifecycle_controller, &moniker, start_args).await;
221    if let Err(StartError::ActionError(ActionError::Fidl(_e))) = &res {
222        // A FIDL error here could indicate that we're talking to a version of component manager
223        // that does not support `fuchsia.sys2/LifecycleController.StartInstanceWithArgs`. Let's
224        // try again with `fuchsia.sys2/LifecycleController.StartInstance`.
225
226        // Component manager will close the lifecycle controller when it encounters a FIDL error,
227        // so we need to create a new one.
228        let lifecycle_controller = lifecycle_controller_factory().await?;
229
230        if connect_stdio {
231            // We want to provide stdio handles to the component, but this is only possible when
232            // creating an instance when we have to use the legacy `StartInstance`. Delete and
233            // recreate the component, providing the handles to the create call.
234
235            #[cfg(not(feature = "fdomain"))]
236            let (stdio, numbered_handles) = Stdio::new();
237            #[cfg(feature = "fdomain")]
238            let (stdio, numbered_handles) = Stdio::new(&lifecycle_controller.domain());
239            maybe_stdio = Some(stdio);
240            let create_args = fcomponent::CreateChildArgs {
241                numbered_handles: Some(numbered_handles),
242                ..Default::default()
243            };
244
245            destroy_instance_in_collection(&lifecycle_controller, &parent, collection, child_name)
246                .await?;
247            create_instance_in_collection(
248                &lifecycle_controller,
249                &parent,
250                collection,
251                child_name,
252                &url,
253                config_overrides,
254                Some(create_args),
255            )
256            .await?;
257            resolve_instance(&lifecycle_controller, &moniker)
258                .await
259                .map_err(|e| format_resolve_error(&moniker, e))?;
260        }
261
262        let _stop_future = start_instance(&lifecycle_controller, &moniker)
263            .await
264            .map_err(|e| format_start_error(&moniker, e))?;
265    } else {
266        let _stop_future = res.map_err(|e| format_start_error(&moniker, e))?;
267    }
268
269    if let Some(stdio) = maybe_stdio {
270        stdio.forward().await;
271    }
272
273    writeln!(writer, "Component instance is running!")?;
274
275    Ok(())
276}
277
278#[cfg(test)]
279mod test {
280    use super::*;
281    use fidl::endpoints::create_proxy_and_stream;
282    use flex_fuchsia_sys2 as fsys;
283    use futures::{FutureExt, TryStreamExt};
284
285    fn setup_fake_lifecycle_controller_ok(
286        expected_parent_moniker: &'static str,
287        expected_collection: &'static str,
288        expected_name: &'static str,
289        expected_url: &'static str,
290        expected_moniker: &'static str,
291        expect_destroy: bool,
292    ) -> fsys::LifecycleControllerProxy {
293        let (lifecycle_controller, mut stream) =
294            create_proxy_and_stream::<fsys::LifecycleControllerMarker>();
295        fuchsia_async::Task::local(async move {
296            if expect_destroy {
297                let req = stream.try_next().await.unwrap().unwrap();
298                match req {
299                    fsys::LifecycleControllerRequest::DestroyInstance {
300                        parent_moniker,
301                        child,
302                        responder,
303                    } => {
304                        assert_eq!(
305                            Moniker::parse_str(expected_parent_moniker),
306                            Moniker::parse_str(&parent_moniker)
307                        );
308                        assert_eq!(expected_name, child.name);
309                        assert_eq!(expected_collection, child.collection.unwrap());
310                        responder.send(Ok(())).unwrap();
311                    }
312                    _ => panic!("Unexpected Lifecycle Controller request: {:?}", req),
313                }
314            }
315
316            let req = stream.try_next().await.unwrap().unwrap();
317            match req {
318                fsys::LifecycleControllerRequest::CreateInstance {
319                    parent_moniker,
320                    collection,
321                    decl,
322                    responder,
323                    args: _,
324                } => {
325                    assert_eq!(
326                        Moniker::parse_str(expected_parent_moniker),
327                        Moniker::parse_str(&parent_moniker)
328                    );
329                    assert_eq!(expected_collection, collection.name);
330                    assert_eq!(expected_name, decl.name.unwrap());
331                    assert_eq!(expected_url, decl.url.unwrap());
332                    responder.send(Ok(())).unwrap();
333                }
334                _ => panic!("Unexpected Lifecycle Controller request: {:?}", req),
335            }
336
337            let req = stream.try_next().await.unwrap().unwrap();
338            match req {
339                fsys::LifecycleControllerRequest::ResolveInstance { moniker, responder } => {
340                    assert_eq!(Moniker::parse_str(expected_moniker), Moniker::parse_str(&moniker));
341                    responder.send(Ok(())).unwrap();
342                }
343                _ => panic!("Unexpected Lifecycle Controller request: {:?}", req),
344            }
345
346            let req = stream.try_next().await.unwrap().unwrap();
347            match req {
348                fsys::LifecycleControllerRequest::StartInstanceWithArgs {
349                    moniker,
350                    binder: _,
351                    args: _,
352                    responder,
353                } => {
354                    assert_eq!(Moniker::parse_str(expected_moniker), Moniker::parse_str(&moniker));
355                    responder.send(Ok(())).unwrap();
356                }
357                _ => panic!("Unexpected Lifecycle Controller request: {:?}", req),
358            }
359        })
360        .detach();
361        lifecycle_controller
362    }
363
364    fn setup_fake_lifecycle_controller_fail(
365        expected_parent_moniker: &'static str,
366        expected_collection: &'static str,
367        expected_name: &'static str,
368        expected_url: &'static str,
369    ) -> fsys::LifecycleControllerProxy {
370        let (lifecycle_controller, mut stream) =
371            create_proxy_and_stream::<fsys::LifecycleControllerMarker>();
372        fuchsia_async::Task::local(async move {
373            let req = stream.try_next().await.unwrap().unwrap();
374            match req {
375                fsys::LifecycleControllerRequest::DestroyInstance {
376                    parent_moniker,
377                    child,
378                    responder,
379                } => {
380                    assert_eq!(
381                        Moniker::parse_str(expected_parent_moniker),
382                        Moniker::parse_str(&parent_moniker)
383                    );
384                    assert_eq!(expected_name, child.name);
385                    assert_eq!(expected_collection, child.collection.unwrap());
386                    responder.send(Ok(())).unwrap();
387                }
388                _ => panic!("Unexpected Lifecycle Controller request: {:?}", req),
389            }
390
391            let req = stream.try_next().await.unwrap().unwrap();
392            match req {
393                fsys::LifecycleControllerRequest::CreateInstance {
394                    parent_moniker,
395                    collection,
396                    decl,
397                    responder,
398                    args: _,
399                } => {
400                    assert_eq!(
401                        Moniker::parse_str(expected_parent_moniker),
402                        Moniker::parse_str(&parent_moniker)
403                    );
404                    assert_eq!(expected_collection, collection.name);
405                    assert_eq!(expected_name, decl.name.unwrap());
406                    assert_eq!(expected_url, decl.url.unwrap());
407                    responder.send(Err(fsys::CreateError::InstanceAlreadyExists)).unwrap();
408                }
409                _ => panic!("Unexpected Lifecycle Controller request: {:?}", req),
410            }
411        })
412        .detach();
413        lifecycle_controller
414    }
415
416    fn setup_fake_lifecycle_controller_recreate(
417        expected_parent_moniker: &'static str,
418        expected_collection: &'static str,
419        expected_name: &'static str,
420        expected_url: &'static str,
421        expected_moniker: &'static str,
422    ) -> fsys::LifecycleControllerProxy {
423        let (lifecycle_controller, mut stream) =
424            create_proxy_and_stream::<fsys::LifecycleControllerMarker>();
425        fuchsia_async::Task::local(async move {
426            let req = stream.try_next().await.unwrap().unwrap();
427            match req {
428                fsys::LifecycleControllerRequest::DestroyInstance {
429                    parent_moniker,
430                    child,
431                    responder,
432                } => {
433                    assert_eq!(
434                        Moniker::parse_str(expected_parent_moniker),
435                        Moniker::parse_str(&parent_moniker)
436                    );
437                    assert_eq!(expected_name, child.name);
438                    assert_eq!(expected_collection, child.collection.unwrap());
439                    responder.send(Ok(())).unwrap();
440                }
441                _ => panic!("Unexpected Lifecycle Controller request: {:?}", req),
442            }
443
444            let req = stream.try_next().await.unwrap().unwrap();
445            match req {
446                fsys::LifecycleControllerRequest::CreateInstance {
447                    parent_moniker,
448                    collection,
449                    decl,
450                    responder,
451                    args: _,
452                } => {
453                    assert_eq!(
454                        Moniker::parse_str(expected_parent_moniker),
455                        Moniker::parse_str(&parent_moniker)
456                    );
457                    assert_eq!(expected_collection, collection.name);
458                    assert_eq!(expected_name, decl.name.unwrap());
459                    assert_eq!(expected_url, decl.url.unwrap());
460                    responder.send(Ok(())).unwrap();
461                }
462                _ => panic!("Unexpected Lifecycle Controller request: {:?}", req),
463            }
464
465            let req = stream.try_next().await.unwrap().unwrap();
466            match req {
467                fsys::LifecycleControllerRequest::ResolveInstance { moniker, responder } => {
468                    assert_eq!(Moniker::parse_str(expected_moniker), Moniker::parse_str(&moniker));
469                    responder.send(Ok(())).unwrap();
470                }
471                _ => panic!("Unexpected Lifecycle Controller request: {:?}", req),
472            }
473
474            let req = stream.try_next().await.unwrap().unwrap();
475            match req {
476                fsys::LifecycleControllerRequest::StartInstanceWithArgs {
477                    moniker,
478                    binder: _,
479                    args: _,
480                    responder,
481                } => {
482                    assert_eq!(Moniker::parse_str(expected_moniker), Moniker::parse_str(&moniker));
483                    responder.send(Ok(())).unwrap();
484                }
485                _ => panic!("Unexpected Lifecycle Controller request: {:?}", req),
486            }
487        })
488        .detach();
489        lifecycle_controller
490    }
491
492    #[fuchsia_async::run_singlethreaded(test)]
493    async fn test_ok() -> Result<()> {
494        let mut output = Vec::new();
495        let lifecycle_controller = setup_fake_lifecycle_controller_ok(
496            "/some",
497            "collection",
498            "name",
499            "fuchsia-pkg://fuchsia.com/test#meta/test.cm",
500            "/some/collection:name",
501            true,
502        );
503        let response = run_cmd(
504            "/some/collection:name".try_into().unwrap(),
505            "fuchsia-pkg://fuchsia.com/test#meta/test.cm".try_into().unwrap(),
506            true,
507            false,
508            vec![],
509            move || {
510                let lifecycle_controller = lifecycle_controller.clone();
511                async move { Ok(lifecycle_controller) }.boxed()
512            },
513            &mut output,
514        )
515        .await;
516        response.unwrap();
517        Ok(())
518    }
519
520    #[fuchsia_async::run_singlethreaded(test)]
521    async fn test_name() -> Result<()> {
522        let mut output = Vec::new();
523        let lifecycle_controller = setup_fake_lifecycle_controller_ok(
524            "/core",
525            "ffx-laboratory",
526            "foobar",
527            "fuchsia-pkg://fuchsia.com/test#meta/test.cm",
528            "/core/ffx-laboratory:foobar",
529            false,
530        );
531        let response = run_cmd(
532            "/core/ffx-laboratory:foobar".try_into().unwrap(),
533            "fuchsia-pkg://fuchsia.com/test#meta/test.cm".try_into().unwrap(),
534            false,
535            false,
536            vec![],
537            move || {
538                let lifecycle_controller = lifecycle_controller.clone();
539                async move { Ok(lifecycle_controller) }.boxed()
540            },
541            &mut output,
542        )
543        .await;
544        response.unwrap();
545        Ok(())
546    }
547
548    #[fuchsia_async::run_singlethreaded(test)]
549    async fn test_fail() -> Result<()> {
550        let mut output = Vec::new();
551        let lifecycle_controller = setup_fake_lifecycle_controller_fail(
552            "/core",
553            "ffx-laboratory",
554            "test",
555            "fuchsia-pkg://fuchsia.com/test#meta/test.cm",
556        );
557        let response = run_cmd(
558            "/core/ffx-laboratory:test".try_into().unwrap(),
559            "fuchsia-pkg://fuchsia.com/test#meta/test.cm".try_into().unwrap(),
560            true,
561            false,
562            vec![],
563            move || {
564                let lifecycle_controller = lifecycle_controller.clone();
565                async move { Ok(lifecycle_controller) }.boxed()
566            },
567            &mut output,
568        )
569        .await;
570        response.unwrap_err();
571        Ok(())
572    }
573
574    #[fuchsia_async::run_singlethreaded(test)]
575    async fn test_recreate() -> Result<()> {
576        let mut output = Vec::new();
577        let lifecycle_controller = setup_fake_lifecycle_controller_recreate(
578            "/core",
579            "ffx-laboratory",
580            "test",
581            "fuchsia-pkg://fuchsia.com/test#meta/test.cm",
582            "/core/ffx-laboratory:test",
583        );
584        let response = run_cmd(
585            "/core/ffx-laboratory:test".try_into().unwrap(),
586            "fuchsia-pkg://fuchsia.com/test#meta/test.cm".try_into().unwrap(),
587            true,
588            false,
589            vec![],
590            move || {
591                let lifecycle_controller = lifecycle_controller.clone();
592                async move { Ok(lifecycle_controller) }.boxed()
593            },
594            &mut output,
595        )
596        .await;
597        response.unwrap();
598        Ok(())
599    }
600}