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