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