Skip to main content

runner/
component.rs

1// Copyright 2019 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 async_trait::async_trait;
6use cm_types::NamespacePath;
7use fidl::endpoints::ServerEnd;
8use fidl::epitaph::ChannelEpitaphExt;
9use fidl::prelude::*;
10use fidl_fuchsia_component as fcomp;
11use fidl_fuchsia_component_runner as fcrunner;
12use fidl_fuchsia_io as fio;
13use fidl_fuchsia_process as fproc;
14use fuchsia_async as fasync;
15use fuchsia_runtime::{HandleInfo, HandleType, job_default};
16use futures::future::{BoxFuture, Either};
17use futures::prelude::*;
18#[cfg(fuchsia_api_level_at_least = "HEAD")]
19use futures::stream::BoxStream;
20use log::*;
21use namespace::Namespace;
22use std::sync::LazyLock;
23use thiserror::Error;
24
25pub static PKG_PATH: LazyLock<NamespacePath> = LazyLock::new(|| "/pkg".parse().unwrap());
26
27/// Object implementing this type can be killed by calling kill function.
28#[async_trait]
29pub trait Controllable {
30    /// Should kill self and do cleanup.
31    /// Should not return error or panic, should log error instead.
32    async fn kill(&mut self);
33
34    /// Stop the component. Once the component is stopped, the
35    /// ComponentControllerControlHandle should be closed. If the component is
36    /// not stopped quickly enough, kill will be called. The amount of time
37    /// `stop` is allowed may vary based on a variety of factors.
38    fn stop<'a>(&mut self) -> BoxFuture<'a, ()>;
39
40    /// Perform any teardown tasks before closing the controller channel.
41    fn teardown<'a>(&mut self) -> BoxFuture<'a, ()> {
42        async {}.boxed()
43    }
44
45    /// Monitor any escrow requests from the component.
46    #[cfg(fuchsia_api_level_at_least = "HEAD")]
47    fn on_escrow<'a>(&self) -> BoxStream<'a, fcrunner::ComponentControllerOnEscrowRequest> {
48        futures::stream::empty().boxed()
49    }
50}
51
52/// Holds information about the component that allows the controller to
53/// interact with and control the component.
54pub struct Controller<C: Controllable> {
55    /// stream via which the component manager will ask the controller to
56    /// manipulate the component
57    request_stream: fcrunner::ComponentControllerRequestStream,
58
59    #[allow(dead_code)] // Only needed at HEAD
60    control: fcrunner::ComponentControllerControlHandle,
61
62    /// Controllable object which controls the underlying component.
63    /// This would be None once the object is killed.
64    controllable: Option<C>,
65
66    /// Task that forwards the `on_escrow` event.
67    #[cfg(fuchsia_api_level_at_least = "HEAD")]
68    on_escrow_monitor: fasync::Task<()>,
69}
70
71/// Information about a component's termination (fuchsia.component.runner/ComponentStopInfo)
72#[derive(Debug, Clone, PartialEq)]
73pub struct StopInfo {
74    pub termination_status: Result<(), zx::Status>,
75    pub exit_code: Option<i64>,
76}
77
78impl StopInfo {
79    pub fn from_status(s: zx::Status, c: Option<i64>) -> Self {
80        Self { termination_status: zx::Status::ok(s.into_raw()), exit_code: c }
81    }
82
83    pub fn from_u32(s: u32, c: Option<i64>) -> Self {
84        Self {
85            termination_status: zx::Status::ok(i32::try_from(s).unwrap_or(i32::MAX)),
86            exit_code: c,
87        }
88    }
89
90    pub fn from_error(s: fcomp::Error, c: Option<i64>) -> Self {
91        Self::from_u32(s.into_primitive().into(), c)
92    }
93
94    pub fn from_ok(c: Option<i64>) -> Self {
95        Self { termination_status: Ok(()), exit_code: c }
96    }
97}
98
99#[cfg(fuchsia_api_level_at_least = "HEAD")]
100impl From<StopInfo> for fcrunner::ComponentStopInfo {
101    fn from(info: StopInfo) -> Self {
102        Self {
103            termination_status: Some(zx::Status::result_into_raw(info.termination_status)),
104            exit_code: info.exit_code,
105            ..Default::default()
106        }
107    }
108}
109
110#[cfg(fuchsia_api_level_at_least = "HEAD")]
111impl From<fcrunner::ComponentStopInfo> for StopInfo {
112    fn from(value: fcrunner::ComponentStopInfo) -> Self {
113        Self {
114            termination_status: zx::Status::ok(value.termination_status.unwrap_or(0)),
115            exit_code: value.exit_code,
116        }
117    }
118}
119
120impl<C: Controllable + 'static> Controller<C> {
121    /// Creates new instance
122    pub fn new(
123        controllable: C,
124        requests: fcrunner::ComponentControllerRequestStream,
125        control: fcrunner::ComponentControllerControlHandle,
126    ) -> Controller<C> {
127        #[cfg(fuchsia_api_level_at_least = "HEAD")]
128        {
129            let on_escrow = controllable.on_escrow();
130            let on_escrow_monitor =
131                fasync::Task::spawn(Self::monitor_events(on_escrow, requests.control_handle()));
132            Controller {
133                controllable: Some(controllable),
134                request_stream: requests,
135                control,
136                on_escrow_monitor,
137            }
138        }
139        #[cfg(fuchsia_api_level_less_than = "HEAD")]
140        Controller { controllable: Some(controllable), request_stream: requests, control }
141    }
142
143    async fn serve_controller(&mut self) -> Result<(), ()> {
144        while let Ok(Some(request)) = self.request_stream.try_next().await {
145            match request {
146                fcrunner::ComponentControllerRequest::Stop { control_handle: _c } => {
147                    // Since stop takes some period of time to complete, call
148                    // it in a separate context so we can respond to other
149                    // requests.
150                    let stop_func = self.stop();
151                    fasync::Task::spawn(stop_func).detach();
152                }
153                fcrunner::ComponentControllerRequest::Kill { control_handle: _c } => {
154                    self.kill().await;
155                    return Ok(());
156                }
157                fcrunner::ComponentControllerRequest::_UnknownMethod { .. } => (),
158            }
159        }
160        // The channel closed
161        Err(())
162    }
163
164    #[cfg(fuchsia_api_level_at_least = "HEAD")]
165    async fn monitor_events(
166        mut on_escrow: impl Stream<Item = fcrunner::ComponentControllerOnEscrowRequest> + Unpin + Send,
167        control_handle: fcrunner::ComponentControllerControlHandle,
168    ) {
169        while let Some(event) = on_escrow.next().await {
170            control_handle
171                .send_on_escrow(event)
172                .unwrap_or_else(|err| error!(err:%; "failed to send OnEscrow event"));
173        }
174    }
175
176    /// Serve the request stream held by this Controller. `exit_fut` should
177    /// complete if the component exits and should return a value which is
178    /// either 0 (ZX_OK) or one of the fuchsia.component/Error constants
179    /// defined as valid in the fuchsia.component.runner/ComponentController
180    /// documentation. This function will return after `exit_fut` completes
181    /// or the request stream closes. In either case the request stream is
182    /// closed once this function returns since the stream itself, which owns
183    /// the channel, is dropped.
184    pub async fn serve(mut self, exit_fut: impl Future<Output = StopInfo> + Unpin) {
185        let stop_info = {
186            // Pin the server_controller future so we can use it with select
187            let request_server = self.serve_controller();
188            futures::pin_mut!(request_server);
189
190            // Get the result of waiting for `exit_fut` to complete while also
191            // polling the request server.
192            match future::select(exit_fut, request_server).await {
193                Either::Left((return_code, _controller_server)) => return_code,
194                Either::Right((serve_result, pending_close)) => match serve_result {
195                    Ok(()) => pending_close.await,
196                    Err(_) => {
197                        // Return directly because the controller channel
198                        // closed so there's no point in an epitaph.
199                        return;
200                    }
201                },
202            }
203        };
204
205        // Before closing the controller channel, perform teardown tasks if the runner configured
206        // them. This will only run if the component was not killed (otherwise `controllable` is
207        // `None`).
208        if let Some(mut controllable) = self.controllable.take() {
209            controllable.teardown().await;
210        }
211
212        // Drain any escrow events.
213        // TODO(https://fxbug.dev/326626515): Drain the escrow requests until no long readable
214        // instead of waiting for an unbounded amount of time if `on_escrow` never completes.
215        #[cfg(fuchsia_api_level_at_least = "HEAD")]
216        {
217            self.on_escrow_monitor.await;
218            _ = self.control.send_on_stop(stop_info.clone().into());
219        }
220        let _ = stop_info; // avoid unused error at stable API level
221        self.request_stream.control_handle().shutdown();
222    }
223
224    /// Kill the job and shutdown control handle supplied to this function.
225    async fn kill(&mut self) {
226        if let Some(mut controllable) = self.controllable.take() {
227            controllable.kill().await;
228        }
229    }
230
231    /// If we have a Controllable, ask it to stop the component.
232    fn stop<'a>(&mut self) -> BoxFuture<'a, ()> {
233        if self.controllable.is_some() {
234            self.controllable.as_mut().unwrap().stop()
235        } else {
236            async {}.boxed()
237        }
238    }
239}
240
241/// An error encountered trying to launch a component.
242#[derive(Clone, Debug, PartialEq, Eq, Error)]
243pub enum LaunchError {
244    #[error("invalid binary path {}", _0)]
245    InvalidBinaryPath(String),
246
247    #[error("/pkg missing in the namespace")]
248    MissingPkg,
249
250    #[error("error loading executable: {:?}", _0)]
251    LoadingExecutable(String),
252
253    #[error("cannot convert proxy to channel")]
254    DirectoryToChannel,
255
256    #[error("cannot create channels: {}", _0)]
257    ChannelCreation(zx_status::Status),
258
259    #[error("error loading 'lib' in /pkg: {:?}", _0)]
260    LibLoadError(String),
261
262    #[error("cannot create job: {}", _0)]
263    JobCreation(zx_status::Status),
264
265    #[error("cannot duplicate job: {}", _0)]
266    DuplicateJob(zx_status::Status),
267
268    #[error("cannot add args to launcher: {:?}", _0)]
269    AddArgs(String),
270
271    #[error("cannot add args to launcher: {:?}", _0)]
272    AddHandles(String),
273
274    #[error("cannot add args to launcher: {:?}", _0)]
275    AddNames(String),
276
277    #[error("cannot add env to launcher: {:?}", _0)]
278    AddEnvirons(String),
279
280    #[error("cannot set options for launcher: {:?}", _0)]
281    SetOptions(String),
282}
283
284/// Arguments to `configure_launcher` function.
285pub struct LauncherConfigArgs<'a> {
286    /// relative binary path to /pkg in `ns`.
287    pub bin_path: &'a str,
288
289    /// Name of the binary to add to `LaunchInfo`. This will be truncated to
290    /// `zx::sys::ZX_MAX_NAME_LEN` bytes.
291    pub name: &'a str,
292
293    /// The options used to create the process.
294    pub options: zx::ProcessOptions,
295
296    /// Arguments to binary. Binary path will be automatically
297    /// prepended so that should not be passed as first argument.
298    pub args: Option<Vec<String>>,
299
300    /// Namespace for binary process to be launched.
301    pub ns: Namespace,
302
303    /// Job in which process is launched. If None, a child job would be created in default one.
304    pub job: Option<zx::Job>,
305
306    /// Extra handle infos to add. This function all ready adds handles for default job and svc
307    /// loader.
308    pub handle_infos: Option<Vec<fproc::HandleInfo>>,
309
310    /// Extra names to add to namespace. by default only names from `ns` are added.
311    pub name_infos: Option<Vec<fproc::NameInfo>>,
312
313    /// Process environment to add to launcher.
314    pub environs: Option<Vec<String>>,
315
316    /// proxy for `fuchsia.proc.Launcher`.
317    pub launcher: &'a fproc::LauncherProxy,
318
319    /// Custom loader proxy. If None, /pkg/lib would be used to load libraries.
320    pub loader_proxy_chan: Option<zx::Channel>,
321
322    /// VMO containing mapping to executable binary. If None, it would be loaded from /pkg.
323    pub executable_vmo: Option<zx::Vmo>,
324}
325
326/// Configures launcher to launch process using passed params and creates launch info.
327/// This starts a library loader service, that will live as long as the handle for it given to the
328/// launcher is alive.
329pub async fn configure_launcher(
330    config_args: LauncherConfigArgs<'_>,
331) -> Result<fproc::LaunchInfo, LaunchError> {
332    // Locate the '/pkg' directory proxy previously added to the new component's namespace.
333    let pkg_dir = config_args.ns.get(&PKG_PATH).ok_or(LaunchError::MissingPkg)?;
334
335    // library_loader provides a helper function that we use to load the main executable from the
336    // package directory as a VMO in the same way that dynamic libraries are loaded. Doing this
337    // first allows launching to fail quickly and clearly in case the main executable can't be
338    // loaded with ZX_RIGHT_EXECUTE from the package directory.
339    let executable_vmo = match config_args.executable_vmo {
340        Some(v) => v,
341        None => library_loader::load_vmo(pkg_dir, &config_args.bin_path)
342            .await
343            .map_err(|e| LaunchError::LoadingExecutable(e.to_string()))?,
344    };
345
346    let ll_client_chan = match config_args.loader_proxy_chan {
347        None => {
348            // The loader service should only be able to load files from `/pkg/lib`. Giving it a
349            // larger scope is potentially a security vulnerability, as it could make it trivial for
350            // parts of applications to get handles to things the application author didn't intend.
351            let lib_proxy = fuchsia_component::directory::open_directory_async(
352                pkg_dir,
353                "lib",
354                fio::RX_STAR_DIR,
355            )
356            .map_err(|e| LaunchError::LibLoadError(e.to_string()))?;
357            let (ll_client_chan, ll_service_chan) = zx::Channel::create();
358            library_loader::start(lib_proxy.into(), ll_service_chan);
359            ll_client_chan
360        }
361        Some(chan) => chan,
362    };
363
364    // Get the provided job to create the new process in, if one was provided, or else create a new
365    // child job of this process's (this process that this code is running in) own 'default job'.
366    let job = config_args
367        .job
368        .unwrap_or(job_default().create_child_job().map_err(LaunchError::JobCreation)?);
369
370    // Build the command line args for the new process and send them to the launcher.
371    let bin_arg = PKG_PATH
372        .to_path_buf()
373        .join(&config_args.bin_path)
374        .to_str()
375        .ok_or_else(|| LaunchError::InvalidBinaryPath(config_args.bin_path.to_string()))?
376        .as_bytes()
377        .to_vec();
378    let mut all_args = vec![bin_arg];
379    if let Some(args) = config_args.args {
380        all_args.extend(args.into_iter().map(|s| s.into_bytes()));
381    }
382    config_args.launcher.add_args(&all_args).map_err(|e| LaunchError::AddArgs(e.to_string()))?;
383
384    // Get any initial handles to provide to the new process, if any were provided by the caller.
385    // Add handles for the new process's default job (by convention, this is the same job that the
386    // new process is launched in) and the fuchsia.ldsvc.Loader service created above, then send to
387    // the launcher.
388    let job_dup =
389        job.duplicate_handle(zx::Rights::SAME_RIGHTS).map_err(LaunchError::DuplicateJob)?;
390    let mut handle_infos = config_args.handle_infos.unwrap_or(vec![]);
391    handle_infos.append(&mut vec![
392        fproc::HandleInfo {
393            handle: ll_client_chan.into_handle(),
394            id: HandleInfo::new(HandleType::LdsvcLoader, 0).as_raw(),
395        },
396        fproc::HandleInfo {
397            handle: job_dup.into_handle(),
398            id: HandleInfo::new(HandleType::DefaultJob, 0).as_raw(),
399        },
400    ]);
401    config_args
402        .launcher
403        .add_handles(handle_infos)
404        .map_err(|e| LaunchError::AddHandles(e.to_string()))?;
405
406    if !config_args.options.is_empty() {
407        config_args
408            .launcher
409            .set_options(config_args.options.bits())
410            .map_err(|e| LaunchError::SetOptions(e.to_string()))?;
411    }
412
413    // Send environment variables for the new process, if any, to the launcher.
414    let environs: Vec<_> = config_args.environs.unwrap_or(vec![]);
415    if environs.len() > 0 {
416        let environs_bytes: Vec<_> = environs.into_iter().map(|s| s.into_bytes()).collect();
417        config_args
418            .launcher
419            .add_environs(&environs_bytes)
420            .map_err(|e| LaunchError::AddEnvirons(e.to_string()))?;
421    }
422
423    // Combine any manually provided namespace entries with the provided Namespace, and
424    // then send the new process's namespace to the launcher.
425    let mut name_infos = config_args.name_infos.unwrap_or(vec![]);
426    let ns: Vec<_> = config_args.ns.into();
427    name_infos.extend(ns.into_iter());
428    config_args.launcher.add_names(name_infos).map_err(|e| LaunchError::AddNames(e.to_string()))?;
429
430    let name = truncate_str(config_args.name, zx::sys::ZX_MAX_NAME_LEN).to_owned();
431
432    Ok(fproc::LaunchInfo { executable: executable_vmo, job, name })
433}
434
435/// Truncates `s` to be at most `max_len` bytes.
436fn truncate_str(s: &str, max_len: usize) -> &str {
437    let index = s.floor_char_boundary(max_len);
438    &s[..index]
439}
440
441static CONNECT_ERROR_HELP: &'static str = "To learn more, see \
442https://fuchsia.dev/go/components/connect-errors";
443
444/// Sets an epitaph on `ComponentController` `server_end` for a runner failure and the outgoing
445/// directory, and logs it.
446pub fn report_start_error(
447    err: zx::Status,
448    err_str: String,
449    resolved_url: &str,
450    controller_server_end: ServerEnd<fcrunner::ComponentControllerMarker>,
451) {
452    let _ = controller_server_end.into_channel().close_with_epitaph(err);
453    warn!("Failed to start component `{}`: {}\n{}", resolved_url, err_str, CONNECT_ERROR_HELP);
454}
455
456#[cfg(test)]
457mod tests {
458    use super::*;
459    use anyhow::Error;
460    use assert_matches::assert_matches;
461    use async_trait::async_trait;
462    use fidl::endpoints::{ClientEnd, create_endpoints, create_proxy};
463    use fidl_fuchsia_component_runner::{self as fcrunner, ComponentControllerProxy};
464    use fidl_fuchsia_io as fio;
465    use fidl_fuchsia_process as fproc;
466    use fuchsia_async as fasync;
467    use fuchsia_runtime::{HandleInfo, HandleType};
468    use futures::future::BoxFuture;
469    use futures::poll;
470    use namespace::{Namespace, NamespaceError};
471    use std::pin::Pin;
472    use std::task::Poll;
473
474    #[test]
475    fn test_truncate_str() {
476        assert_eq!(truncate_str("", 0), "");
477        assert_eq!(truncate_str("", 1), "");
478
479        assert_eq!(truncate_str("été", 0), "");
480        assert_eq!(truncate_str("été", 1), "");
481        assert_eq!(truncate_str("été", 2), "é");
482        assert_eq!(truncate_str("été", 3), "ét");
483        assert_eq!(truncate_str("été", 4), "ét");
484        assert_eq!(truncate_str("été", 5), "été");
485        assert_eq!(truncate_str("été", 6), "été");
486    }
487
488    struct FakeComponent<K, J>
489    where
490        K: FnOnce() + std::marker::Send,
491        J: FnOnce() + std::marker::Send,
492    {
493        pub onkill: Option<K>,
494
495        pub onstop: Option<J>,
496
497        pub onteardown: Option<BoxFuture<'static, ()>>,
498    }
499
500    #[async_trait]
501    impl<K: 'static, J: 'static> Controllable for FakeComponent<K, J>
502    where
503        K: FnOnce() + std::marker::Send,
504        J: FnOnce() + std::marker::Send,
505    {
506        async fn kill(&mut self) {
507            let func = self.onkill.take().unwrap();
508            func();
509        }
510
511        fn stop<'a>(&mut self) -> BoxFuture<'a, ()> {
512            let func = self.onstop.take().unwrap();
513            async move { func() }.boxed()
514        }
515
516        fn teardown<'a>(&mut self) -> BoxFuture<'a, ()> {
517            self.onteardown.take().unwrap()
518        }
519    }
520
521    #[fuchsia::test]
522    async fn test_kill_component() -> Result<(), Error> {
523        let (sender, recv) = futures::channel::oneshot::channel::<()>();
524        let (term_tx, term_rx) = futures::channel::oneshot::channel::<StopInfo>();
525        let stop_info = StopInfo::from_ok(Some(42));
526        let fake_component = FakeComponent {
527            onkill: Some(move || {
528                sender.send(()).unwrap();
529                // After acknowledging that we received kill, send the status
530                // value so `serve` completes.
531                let _ = term_tx.send(stop_info.clone());
532            }),
533            onstop: Some(|| {}),
534            onteardown: Some(async {}.boxed()),
535        };
536
537        let (controller, client_proxy) = create_controller_and_proxy(fake_component)?;
538
539        client_proxy.kill().expect("FIDL error returned from kill request to controller");
540
541        let term_receiver = Box::pin(async move { term_rx.await.unwrap() });
542        // this should return after kill call
543        controller.serve(term_receiver).await;
544
545        // this means kill was called
546        recv.await?;
547
548        // Check the event on the controller channel, this should match what
549        // is sent by `term_tx`
550        let mut event_stream = client_proxy.take_event_stream();
551        assert_matches!(
552            event_stream.try_next().await,
553            Ok(Some(fcrunner::ComponentControllerEvent::OnStop {
554                payload: fcrunner::ComponentStopInfo {
555                    termination_status: Some(0),
556                    exit_code: Some(42),
557                    ..
558                }
559            }))
560        );
561        assert_matches!(event_stream.try_next().await, Ok(None));
562
563        Ok(())
564    }
565
566    #[fuchsia::test]
567    async fn test_stop_component() -> Result<(), Error> {
568        let (sender, recv) = futures::channel::oneshot::channel::<()>();
569        let (teardown_signal_tx, teardown_signal_rx) = futures::channel::oneshot::channel::<()>();
570        let (teardown_fence_tx, teardown_fence_rx) = futures::channel::oneshot::channel::<()>();
571        let (term_tx, term_rx) = futures::channel::oneshot::channel::<StopInfo>();
572        let stop_info = StopInfo::from_ok(Some(42));
573
574        let fake_component = FakeComponent {
575            onstop: Some(move || {
576                sender.send(()).unwrap();
577                let _ = term_tx.send(stop_info.clone());
578            }),
579            onkill: Some(move || {}),
580            onteardown: Some(
581                async move {
582                    teardown_signal_tx.send(()).unwrap();
583                    teardown_fence_rx.await.unwrap();
584                }
585                .boxed(),
586            ),
587        };
588
589        let (controller, client_proxy) = create_controller_and_proxy(fake_component)?;
590
591        client_proxy.stop().expect("FIDL error returned from kill request to controller");
592
593        let term_receiver = Box::pin(async move { term_rx.await.unwrap() });
594
595        // This should return once the channel is closed, that is after stop and teardown
596        let controller_serve = fasync::Task::spawn(controller.serve(term_receiver));
597
598        // This means stop was called
599        recv.await?;
600
601        // Teardown should be called
602        teardown_signal_rx.await?;
603
604        // Teardown is blocked. Verify there's no event on the channel yet, then unblock it.
605        let mut client_stream = client_proxy.take_event_stream();
606        let mut client_stream_fut = client_stream.try_next();
607        assert_matches!(poll!(Pin::new(&mut client_stream_fut)), Poll::Pending);
608        teardown_fence_tx.send(()).unwrap();
609        controller_serve.await;
610
611        // Check the event on the controller channel, this should match what
612        // is sent by `term_tx`
613        assert_matches!(
614            client_stream_fut.await,
615            Ok(Some(fcrunner::ComponentControllerEvent::OnStop {
616                payload: fcrunner::ComponentStopInfo {
617                    termination_status: Some(0),
618                    exit_code: Some(42),
619                    ..
620                }
621            }))
622        );
623        assert_matches!(client_stream.try_next().await, Ok(None));
624
625        Ok(())
626    }
627
628    #[fuchsia::test]
629    fn test_stop_then_kill() -> Result<(), Error> {
630        let mut exec = fasync::TestExecutor::new();
631        let (sender, mut recv) = futures::channel::oneshot::channel::<()>();
632        let (term_tx, term_rx) = futures::channel::oneshot::channel::<StopInfo>();
633        let stop_info = StopInfo::from_ok(Some(42));
634
635        // This component will only 'exit' after kill is called.
636        let fake_component = FakeComponent {
637            onstop: Some(move || {
638                sender.send(()).unwrap();
639            }),
640            onkill: Some(move || {
641                let _ = term_tx.send(stop_info.clone());
642            }),
643            onteardown: Some(async {}.boxed()),
644        };
645
646        let (controller, client_proxy) = create_controller_and_proxy(fake_component)?;
647        // Send a stop request, note that the controller isn't even running
648        // yet, but the request will be waiting in the channel when it does.
649        client_proxy.stop().expect("FIDL error returned from stop request to controller");
650
651        // Set up the controller to run.
652        let term_receiver = Box::pin(async move { term_rx.await.unwrap() });
653        let mut controller_fut = Box::pin(controller.serve(term_receiver));
654
655        // Run the serve loop until it is stalled, it shouldn't return because
656        // stop doesn't automatically call exit.
657        match exec.run_until_stalled(&mut controller_fut) {
658            Poll::Pending => {}
659            x => panic!("Serve future should have been pending but was not {:?}", x),
660        }
661
662        // Check that stop was called
663        assert_eq!(exec.run_until_stalled(&mut recv), Poll::Ready(Ok(())));
664
665        // Kill the component which should call the `onkill` we passed in.
666        // This should cause the termination future to complete, which should then
667        // cause the controller future to complete.
668        client_proxy.kill().expect("FIDL error returned from kill request to controller");
669        match exec.run_until_stalled(&mut controller_fut) {
670            Poll::Ready(()) => {}
671            x => panic!("Unexpected controller poll state {:?}", x),
672        }
673
674        // Check the controller channel closed with an event that matches
675        // what was sent in the `onkill` closure.
676        let mut event_stream = client_proxy.take_event_stream();
677        let mut next_fut = event_stream.try_next();
678        assert_matches!(
679            exec.run_until_stalled(&mut next_fut),
680            Poll::Ready(Ok(Some(fcrunner::ComponentControllerEvent::OnStop {
681                payload: fcrunner::ComponentStopInfo {
682                    termination_status: Some(0),
683                    exit_code: Some(42),
684                    ..
685                }
686            })))
687        );
688
689        let mut next_fut = event_stream.try_next();
690        assert_matches!(exec.run_until_stalled(&mut next_fut), Poll::Ready(Ok(None)));
691        Ok(())
692    }
693
694    fn create_controller_and_proxy<K: 'static, J: 'static>(
695        fake_component: FakeComponent<K, J>,
696    ) -> Result<(Controller<FakeComponent<K, J>>, ComponentControllerProxy), Error>
697    where
698        K: FnOnce() + std::marker::Send,
699        J: FnOnce() + std::marker::Send,
700    {
701        let (client_endpoint, server_endpoint) =
702            create_endpoints::<fcrunner::ComponentControllerMarker>();
703
704        // Get a proxy to the ComponentController channel.
705        let (controller_stream, control) = server_endpoint.into_stream_and_control_handle();
706        Ok((
707            Controller::new(fake_component, controller_stream, control),
708            client_endpoint.into_proxy(),
709        ))
710    }
711
712    mod launch_info {
713        use fidl::endpoints::Proxy;
714
715        use super::*;
716        use anyhow::format_err;
717        use futures::channel::oneshot;
718
719        fn setup_empty_namespace() -> Result<Namespace, NamespaceError> {
720            setup_namespace(false, vec![])
721        }
722
723        fn setup_namespace(
724            include_pkg: bool,
725            // All the handles created for this will have server end closed.
726            // Clients cannot send messages on those handles in ns.
727            extra_paths: Vec<&str>,
728        ) -> Result<Namespace, NamespaceError> {
729            let mut ns = Vec::<fcrunner::ComponentNamespaceEntry>::new();
730            if include_pkg {
731                let pkg_path = "/pkg".to_string();
732                let pkg_chan = fuchsia_fs::directory::open_in_namespace(
733                    "/pkg",
734                    fio::PERM_READABLE | fio::PERM_EXECUTABLE,
735                )
736                .unwrap()
737                .into_channel()
738                .unwrap()
739                .into_zx_channel();
740                let pkg_handle = ClientEnd::new(pkg_chan);
741
742                ns.push(fcrunner::ComponentNamespaceEntry {
743                    path: Some(pkg_path),
744                    directory: Some(pkg_handle),
745                    ..Default::default()
746                });
747            }
748
749            for path in extra_paths {
750                let (client, _server) = create_endpoints::<fio::DirectoryMarker>();
751                ns.push(fcrunner::ComponentNamespaceEntry {
752                    path: Some(path.to_string()),
753                    directory: Some(client),
754                    ..Default::default()
755                });
756            }
757            Namespace::try_from(ns)
758        }
759
760        #[derive(Default)]
761        struct FakeLauncherServiceResults {
762            names: Vec<String>,
763            handles: Vec<u32>,
764            args: Vec<String>,
765            options: zx::ProcessOptions,
766        }
767
768        fn start_launcher()
769        -> Result<(fproc::LauncherProxy, oneshot::Receiver<FakeLauncherServiceResults>), Error>
770        {
771            let (launcher_proxy, server_end) = create_proxy::<fproc::LauncherMarker>();
772            let (sender, receiver) = oneshot::channel();
773            fasync::Task::local(async move {
774                let stream = server_end.into_stream();
775                run_launcher_service(stream, sender)
776                    .await
777                    .expect("error running fake launcher service");
778            })
779            .detach();
780            Ok((launcher_proxy, receiver))
781        }
782
783        async fn run_launcher_service(
784            mut stream: fproc::LauncherRequestStream,
785            sender: oneshot::Sender<FakeLauncherServiceResults>,
786        ) -> Result<(), Error> {
787            let mut res = FakeLauncherServiceResults::default();
788            while let Some(event) = stream.try_next().await? {
789                match event {
790                    fproc::LauncherRequest::AddArgs { args, .. } => {
791                        res.args.extend(
792                            args.into_iter()
793                                .map(|a| {
794                                    std::str::from_utf8(&a)
795                                        .expect("cannot convert bytes to utf8 string")
796                                        .to_owned()
797                                })
798                                .collect::<Vec<String>>(),
799                        );
800                    }
801                    fproc::LauncherRequest::AddEnvirons { .. } => {}
802                    fproc::LauncherRequest::AddNames { names, .. } => {
803                        res.names
804                            .extend(names.into_iter().map(|m| m.path).collect::<Vec<String>>());
805                    }
806                    fproc::LauncherRequest::AddHandles { handles, .. } => {
807                        res.handles.extend(handles.into_iter().map(|m| m.id).collect::<Vec<u32>>());
808                    }
809                    fproc::LauncherRequest::SetOptions { options, .. } => {
810                        res.options = zx::ProcessOptions::from_bits_retain(options);
811                    }
812                    fproc::LauncherRequest::CreateWithoutStarting { .. } => {}
813                    fproc::LauncherRequest::Launch { .. } => {}
814                }
815            }
816            sender.send(res).map_err(|_e| format_err!("can't send result"))?;
817            Ok(())
818        }
819
820        #[fuchsia::test]
821        async fn missing_pkg() -> Result<(), Error> {
822            let (launcher_proxy, _server_end) = create_proxy::<fproc::LauncherMarker>();
823            let ns = setup_empty_namespace()?;
824
825            assert_eq!(
826                configure_launcher(LauncherConfigArgs {
827                    bin_path: "bin/path",
828                    name: "name",
829                    args: None,
830                    options: zx::ProcessOptions::empty(),
831                    ns: ns,
832                    job: None,
833                    handle_infos: None,
834                    name_infos: None,
835                    environs: None,
836                    launcher: &launcher_proxy,
837                    loader_proxy_chan: None,
838                    executable_vmo: None
839                })
840                .await,
841                Err(LaunchError::MissingPkg),
842            );
843
844            drop(_server_end);
845            Ok(())
846        }
847
848        #[fuchsia::test]
849        async fn invalid_executable() -> Result<(), Error> {
850            let (launcher_proxy, _server_end) = create_proxy::<fproc::LauncherMarker>();
851            let ns = setup_namespace(true, vec![])?;
852
853            match configure_launcher(LauncherConfigArgs {
854                bin_path: "test/path",
855                name: "name",
856                args: None,
857                options: zx::ProcessOptions::empty(),
858                ns: ns,
859                job: None,
860                handle_infos: None,
861                name_infos: None,
862                environs: None,
863                launcher: &launcher_proxy,
864                loader_proxy_chan: None,
865                executable_vmo: None,
866            })
867            .await
868            .expect_err("should error out")
869            {
870                LaunchError::LoadingExecutable(_) => {}
871                e => panic!("Expected LoadingExecutable error, got {:?}", e),
872            }
873            Ok(())
874        }
875
876        #[fuchsia::test]
877        async fn invalid_pkg() -> Result<(), Error> {
878            let (launcher_proxy, _server_end) = create_proxy::<fproc::LauncherMarker>();
879            let ns = setup_namespace(false, vec!["/pkg"])?;
880
881            match configure_launcher(LauncherConfigArgs {
882                bin_path: "bin/path",
883                name: "name",
884                args: None,
885                options: zx::ProcessOptions::empty(),
886                ns: ns,
887                job: None,
888                handle_infos: None,
889                name_infos: None,
890                environs: None,
891                launcher: &launcher_proxy,
892                loader_proxy_chan: None,
893                executable_vmo: None,
894            })
895            .await
896            .expect_err("should error out")
897            {
898                LaunchError::LoadingExecutable(_) => {}
899                e => panic!("Expected LoadingExecutable error, got {:?}", e),
900            }
901            Ok(())
902        }
903
904        #[fuchsia::test]
905        async fn default_args() -> Result<(), Error> {
906            let (launcher_proxy, recv) = start_launcher()?;
907
908            let ns = setup_namespace(true, vec![])?;
909
910            let _launch_info = configure_launcher(LauncherConfigArgs {
911                bin_path: "bin/runner_lib_test",
912                name: "name",
913                args: None,
914                options: zx::ProcessOptions::empty(),
915                ns: ns,
916                job: None,
917                handle_infos: None,
918                name_infos: None,
919                environs: None,
920                launcher: &launcher_proxy,
921                loader_proxy_chan: None,
922                executable_vmo: None,
923            })
924            .await?;
925
926            drop(launcher_proxy);
927
928            let ls = recv.await?;
929
930            assert_eq!(ls.args, vec!("/pkg/bin/runner_lib_test".to_owned()));
931
932            Ok(())
933        }
934
935        #[fuchsia::test]
936        async fn custom_executable_vmo() -> Result<(), Error> {
937            let (launcher_proxy, _recv) = start_launcher()?;
938
939            let ns = setup_namespace(true, vec![])?;
940            let vmo = zx::Vmo::create(100)?;
941            vmo.write(b"my_data", 0)?;
942            let launch_info = configure_launcher(LauncherConfigArgs {
943                bin_path: "bin/runner_lib_test",
944                name: "name",
945                args: None,
946                options: zx::ProcessOptions::empty(),
947                ns: ns,
948                job: None,
949                handle_infos: None,
950                name_infos: None,
951                environs: None,
952                launcher: &launcher_proxy,
953                loader_proxy_chan: None,
954                executable_vmo: Some(vmo),
955            })
956            .await?;
957
958            let mut bytes: [u8; 10] = [0; 10];
959            launch_info.executable.read(&mut bytes, 0)?;
960            let expected = b"my_data";
961            assert_eq!(bytes[0..expected.len()], expected[..]);
962            Ok(())
963        }
964
965        #[fuchsia::test]
966        async fn extra_args() -> Result<(), Error> {
967            let (launcher_proxy, recv) = start_launcher()?;
968
969            let ns = setup_namespace(true, vec![])?;
970
971            let args = vec!["args1".to_owned(), "arg2".to_owned()];
972
973            let _launch_info = configure_launcher(LauncherConfigArgs {
974                bin_path: "bin/runner_lib_test",
975                name: "name",
976                args: Some(args.clone()),
977                options: zx::ProcessOptions::empty(),
978                ns: ns,
979                job: None,
980                handle_infos: None,
981                name_infos: None,
982                environs: None,
983                launcher: &launcher_proxy,
984                loader_proxy_chan: None,
985                executable_vmo: None,
986            })
987            .await?;
988
989            drop(launcher_proxy);
990
991            let ls = recv.await?;
992
993            let mut expected = vec!["/pkg/bin/runner_lib_test".to_owned()];
994            expected.extend(args);
995            assert_eq!(ls.args, expected);
996
997            Ok(())
998        }
999
1000        #[fuchsia::test]
1001        async fn namespace_added() -> Result<(), Error> {
1002            let (launcher_proxy, recv) = start_launcher()?;
1003
1004            let ns = setup_namespace(true, vec!["/some_path1", "/some_path2"])?;
1005
1006            let _launch_info = configure_launcher(LauncherConfigArgs {
1007                bin_path: "bin/runner_lib_test",
1008                name: "name",
1009                args: None,
1010                options: zx::ProcessOptions::empty(),
1011                ns: ns,
1012                job: None,
1013                handle_infos: None,
1014                name_infos: None,
1015                environs: None,
1016                launcher: &launcher_proxy,
1017                loader_proxy_chan: None,
1018                executable_vmo: None,
1019            })
1020            .await?;
1021
1022            drop(launcher_proxy);
1023
1024            let ls = recv.await?;
1025
1026            let mut names = ls.names;
1027            names.sort();
1028            assert_eq!(
1029                names,
1030                vec!("/pkg", "/some_path1", "/some_path2")
1031                    .into_iter()
1032                    .map(|s| s.to_string())
1033                    .collect::<Vec<String>>()
1034            );
1035
1036            Ok(())
1037        }
1038
1039        #[fuchsia::test]
1040        async fn extra_namespace_entries() -> Result<(), Error> {
1041            let (launcher_proxy, recv) = start_launcher()?;
1042
1043            let ns = setup_namespace(true, vec!["/some_path1", "/some_path2"])?;
1044
1045            let mut names = vec![];
1046
1047            let extra_paths = vec!["/extra1", "/extra2"];
1048
1049            for path in &extra_paths {
1050                let (client, _server) = create_endpoints::<fio::DirectoryMarker>();
1051
1052                names.push(fproc::NameInfo { path: path.to_string(), directory: client });
1053            }
1054
1055            let _launch_info = configure_launcher(LauncherConfigArgs {
1056                bin_path: "bin/runner_lib_test",
1057                name: "name",
1058                args: None,
1059                options: zx::ProcessOptions::empty(),
1060                ns: ns,
1061                job: None,
1062                handle_infos: None,
1063                name_infos: Some(names),
1064                environs: None,
1065                launcher: &launcher_proxy,
1066                loader_proxy_chan: None,
1067                executable_vmo: None,
1068            })
1069            .await?;
1070
1071            drop(launcher_proxy);
1072
1073            let ls = recv.await?;
1074
1075            let mut paths = vec!["/pkg", "/some_path1", "/some_path2"];
1076            paths.extend(extra_paths.into_iter());
1077            paths.sort();
1078
1079            let mut ls_names = ls.names;
1080            ls_names.sort();
1081
1082            assert_eq!(ls_names, paths.into_iter().map(|s| s.to_string()).collect::<Vec<String>>());
1083
1084            Ok(())
1085        }
1086
1087        #[fuchsia::test]
1088        async fn handles_added() -> Result<(), Error> {
1089            let (launcher_proxy, recv) = start_launcher()?;
1090
1091            let ns = setup_namespace(true, vec![])?;
1092
1093            let _launch_info = configure_launcher(LauncherConfigArgs {
1094                bin_path: "bin/runner_lib_test",
1095                name: "name",
1096                args: None,
1097                options: zx::ProcessOptions::empty(),
1098                ns: ns,
1099                job: None,
1100                handle_infos: None,
1101                name_infos: None,
1102                environs: None,
1103                launcher: &launcher_proxy,
1104                loader_proxy_chan: None,
1105                executable_vmo: None,
1106            })
1107            .await?;
1108
1109            drop(launcher_proxy);
1110
1111            let ls = recv.await?;
1112
1113            assert_eq!(
1114                ls.handles,
1115                vec!(
1116                    HandleInfo::new(HandleType::LdsvcLoader, 0).as_raw(),
1117                    HandleInfo::new(HandleType::DefaultJob, 0).as_raw()
1118                )
1119            );
1120
1121            Ok(())
1122        }
1123
1124        #[fuchsia::test]
1125        async fn handles_added_with_custom_loader_chan() -> Result<(), Error> {
1126            let (launcher_proxy, recv) = start_launcher()?;
1127
1128            let (c1, _c2) = zx::Channel::create();
1129
1130            let ns = setup_namespace(true, vec![])?;
1131
1132            let _launch_info = configure_launcher(LauncherConfigArgs {
1133                bin_path: "bin/runner_lib_test",
1134                name: "name",
1135                args: None,
1136                options: zx::ProcessOptions::empty(),
1137                ns: ns,
1138                job: None,
1139                handle_infos: None,
1140                name_infos: None,
1141                environs: None,
1142                launcher: &launcher_proxy,
1143                loader_proxy_chan: Some(c1),
1144                executable_vmo: None,
1145            })
1146            .await?;
1147
1148            drop(launcher_proxy);
1149
1150            let ls = recv.await?;
1151
1152            assert_eq!(
1153                ls.handles,
1154                vec!(
1155                    HandleInfo::new(HandleType::LdsvcLoader, 0).as_raw(),
1156                    HandleInfo::new(HandleType::DefaultJob, 0).as_raw()
1157                )
1158            );
1159
1160            Ok(())
1161        }
1162
1163        #[fuchsia::test]
1164        async fn extra_handles() -> Result<(), Error> {
1165            let (launcher_proxy, recv) = start_launcher()?;
1166
1167            let ns = setup_namespace(true, vec![])?;
1168
1169            let mut handle_infos = vec![];
1170            for fd in 0..3 {
1171                let (client, _server) = create_endpoints::<fio::DirectoryMarker>();
1172                handle_infos.push(fproc::HandleInfo {
1173                    handle: client.into_channel().into_handle(),
1174                    id: fd,
1175                });
1176            }
1177
1178            let _launch_info = configure_launcher(LauncherConfigArgs {
1179                bin_path: "bin/runner_lib_test",
1180                name: "name",
1181                args: None,
1182                options: zx::ProcessOptions::empty(),
1183                ns: ns,
1184                job: None,
1185                handle_infos: Some(handle_infos),
1186                name_infos: None,
1187                environs: None,
1188                launcher: &launcher_proxy,
1189                loader_proxy_chan: None,
1190                executable_vmo: None,
1191            })
1192            .await?;
1193
1194            drop(launcher_proxy);
1195
1196            let ls = recv.await?;
1197
1198            assert_eq!(
1199                ls.handles,
1200                vec!(
1201                    0,
1202                    1,
1203                    2,
1204                    HandleInfo::new(HandleType::LdsvcLoader, 0).as_raw(),
1205                    HandleInfo::new(HandleType::DefaultJob, 0).as_raw(),
1206                )
1207            );
1208
1209            Ok(())
1210        }
1211    }
1212}