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 fuchsia_runtime::{HandleInfo, HandleType, job_default};
11use futures::future::{BoxFuture, Either};
12use futures::prelude::*;
13#[cfg(fuchsia_api_level_at_least = "HEAD")]
14use futures::stream::BoxStream;
15use log::*;
16use namespace::Namespace;
17use std::sync::LazyLock;
18use thiserror::Error;
19use zx::{self as zx, HandleBased, Status};
20use {
21    fidl_fuchsia_component as fcomp, fidl_fuchsia_component_runner as fcrunner,
22    fidl_fuchsia_io as fio, fidl_fuchsia_process as fproc, fuchsia_async as fasync,
23};
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: 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: s, exit_code: c }
81    }
82
83    pub fn from_u32(s: u32, c: Option<i64>) -> Self {
84        Self {
85            termination_status: Status::from_raw(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: 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(info.termination_status.into_raw()),
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::from_raw(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    if s.len() <= max_len {
438        return s;
439    }
440    // TODO(https://github.com/rust-lang/rust/issues/93743): Use floor_char_boundary when stable.
441    let mut index = max_len;
442    while index > 0 && !s.is_char_boundary(index) {
443        index -= 1;
444    }
445    &s[..index]
446}
447
448static CONNECT_ERROR_HELP: &'static str = "To learn more, see \
449https://fuchsia.dev/go/components/connect-errors";
450
451/// Sets an epitaph on `ComponentController` `server_end` for a runner failure and the outgoing
452/// directory, and logs it.
453pub fn report_start_error(
454    err: zx::Status,
455    err_str: String,
456    resolved_url: &str,
457    controller_server_end: ServerEnd<fcrunner::ComponentControllerMarker>,
458) {
459    let _ = controller_server_end.into_channel().close_with_epitaph(err);
460    warn!("Failed to start component `{}`: {}\n{}", resolved_url, err_str, CONNECT_ERROR_HELP);
461}
462
463#[cfg(test)]
464mod tests {
465    use super::*;
466    use anyhow::Error;
467    use assert_matches::assert_matches;
468    use async_trait::async_trait;
469    use fidl::endpoints::{ClientEnd, create_endpoints, create_proxy};
470    use fidl_fuchsia_component_runner::{self as fcrunner, ComponentControllerProxy};
471    use fuchsia_runtime::{HandleInfo, HandleType};
472    use futures::future::BoxFuture;
473    use futures::poll;
474    use namespace::{Namespace, NamespaceError};
475    use std::pin::Pin;
476    use std::task::Poll;
477    use zx::{self as zx, HandleBased};
478    use {fidl_fuchsia_io as fio, fidl_fuchsia_process as fproc, fuchsia_async as fasync};
479
480    #[test]
481    fn test_truncate_str() {
482        assert_eq!(truncate_str("", 0), "");
483        assert_eq!(truncate_str("", 1), "");
484
485        assert_eq!(truncate_str("été", 0), "");
486        assert_eq!(truncate_str("été", 1), "");
487        assert_eq!(truncate_str("été", 2), "é");
488        assert_eq!(truncate_str("été", 3), "ét");
489        assert_eq!(truncate_str("été", 4), "ét");
490        assert_eq!(truncate_str("été", 5), "été");
491        assert_eq!(truncate_str("été", 6), "été");
492    }
493
494    struct FakeComponent<K, J>
495    where
496        K: FnOnce() + std::marker::Send,
497        J: FnOnce() + std::marker::Send,
498    {
499        pub onkill: Option<K>,
500
501        pub onstop: Option<J>,
502
503        pub onteardown: Option<BoxFuture<'static, ()>>,
504    }
505
506    #[async_trait]
507    impl<K: 'static, J: 'static> Controllable for FakeComponent<K, J>
508    where
509        K: FnOnce() + std::marker::Send,
510        J: FnOnce() + std::marker::Send,
511    {
512        async fn kill(&mut self) {
513            let func = self.onkill.take().unwrap();
514            func();
515        }
516
517        fn stop<'a>(&mut self) -> BoxFuture<'a, ()> {
518            let func = self.onstop.take().unwrap();
519            async move { func() }.boxed()
520        }
521
522        fn teardown<'a>(&mut self) -> BoxFuture<'a, ()> {
523            self.onteardown.take().unwrap()
524        }
525    }
526
527    #[fuchsia::test]
528    async fn test_kill_component() -> Result<(), Error> {
529        let (sender, recv) = futures::channel::oneshot::channel::<()>();
530        let (term_tx, term_rx) = futures::channel::oneshot::channel::<StopInfo>();
531        let stop_info = StopInfo::from_ok(Some(42));
532        let fake_component = FakeComponent {
533            onkill: Some(move || {
534                sender.send(()).unwrap();
535                // After acknowledging that we received kill, send the status
536                // value so `serve` completes.
537                let _ = term_tx.send(stop_info.clone());
538            }),
539            onstop: Some(|| {}),
540            onteardown: Some(async {}.boxed()),
541        };
542
543        let (controller, client_proxy) = create_controller_and_proxy(fake_component)?;
544
545        client_proxy.kill().expect("FIDL error returned from kill request to controller");
546
547        let term_receiver = Box::pin(async move { term_rx.await.unwrap() });
548        // this should return after kill call
549        controller.serve(term_receiver).await;
550
551        // this means kill was called
552        recv.await?;
553
554        // Check the event on the controller channel, this should match what
555        // is sent by `term_tx`
556        let mut event_stream = client_proxy.take_event_stream();
557        assert_matches!(
558            event_stream.try_next().await,
559            Ok(Some(fcrunner::ComponentControllerEvent::OnStop {
560                payload: fcrunner::ComponentStopInfo {
561                    termination_status: Some(0),
562                    exit_code: Some(42),
563                    ..
564                }
565            }))
566        );
567        assert_matches!(event_stream.try_next().await, Ok(None));
568
569        Ok(())
570    }
571
572    #[fuchsia::test]
573    async fn test_stop_component() -> Result<(), Error> {
574        let (sender, recv) = futures::channel::oneshot::channel::<()>();
575        let (teardown_signal_tx, teardown_signal_rx) = futures::channel::oneshot::channel::<()>();
576        let (teardown_fence_tx, teardown_fence_rx) = futures::channel::oneshot::channel::<()>();
577        let (term_tx, term_rx) = futures::channel::oneshot::channel::<StopInfo>();
578        let stop_info = StopInfo::from_ok(Some(42));
579
580        let fake_component = FakeComponent {
581            onstop: Some(move || {
582                sender.send(()).unwrap();
583                let _ = term_tx.send(stop_info.clone());
584            }),
585            onkill: Some(move || {}),
586            onteardown: Some(
587                async move {
588                    teardown_signal_tx.send(()).unwrap();
589                    teardown_fence_rx.await.unwrap();
590                }
591                .boxed(),
592            ),
593        };
594
595        let (controller, client_proxy) = create_controller_and_proxy(fake_component)?;
596
597        client_proxy.stop().expect("FIDL error returned from kill request to controller");
598
599        let term_receiver = Box::pin(async move { term_rx.await.unwrap() });
600
601        // This should return once the channel is closed, that is after stop and teardown
602        let controller_serve = fasync::Task::spawn(controller.serve(term_receiver));
603
604        // This means stop was called
605        recv.await?;
606
607        // Teardown should be called
608        teardown_signal_rx.await?;
609
610        // Teardown is blocked. Verify there's no event on the channel yet, then unblock it.
611        let mut client_stream = client_proxy.take_event_stream();
612        let mut client_stream_fut = client_stream.try_next();
613        assert_matches!(poll!(Pin::new(&mut client_stream_fut)), Poll::Pending);
614        teardown_fence_tx.send(()).unwrap();
615        controller_serve.await;
616
617        // Check the event on the controller channel, this should match what
618        // is sent by `term_tx`
619        assert_matches!(
620            client_stream_fut.await,
621            Ok(Some(fcrunner::ComponentControllerEvent::OnStop {
622                payload: fcrunner::ComponentStopInfo {
623                    termination_status: Some(0),
624                    exit_code: Some(42),
625                    ..
626                }
627            }))
628        );
629        assert_matches!(client_stream.try_next().await, Ok(None));
630
631        Ok(())
632    }
633
634    #[fuchsia::test]
635    fn test_stop_then_kill() -> Result<(), Error> {
636        let mut exec = fasync::TestExecutor::new();
637        let (sender, mut recv) = futures::channel::oneshot::channel::<()>();
638        let (term_tx, term_rx) = futures::channel::oneshot::channel::<StopInfo>();
639        let stop_info = StopInfo::from_ok(Some(42));
640
641        // This component will only 'exit' after kill is called.
642        let fake_component = FakeComponent {
643            onstop: Some(move || {
644                sender.send(()).unwrap();
645            }),
646            onkill: Some(move || {
647                let _ = term_tx.send(stop_info.clone());
648            }),
649            onteardown: Some(async {}.boxed()),
650        };
651
652        let (controller, client_proxy) = create_controller_and_proxy(fake_component)?;
653        // Send a stop request, note that the controller isn't even running
654        // yet, but the request will be waiting in the channel when it does.
655        client_proxy.stop().expect("FIDL error returned from stop request to controller");
656
657        // Set up the controller to run.
658        let term_receiver = Box::pin(async move { term_rx.await.unwrap() });
659        let mut controller_fut = Box::pin(controller.serve(term_receiver));
660
661        // Run the serve loop until it is stalled, it shouldn't return because
662        // stop doesn't automatically call exit.
663        match exec.run_until_stalled(&mut controller_fut) {
664            Poll::Pending => {}
665            x => panic!("Serve future should have been pending but was not {:?}", x),
666        }
667
668        // Check that stop was called
669        assert_eq!(exec.run_until_stalled(&mut recv), Poll::Ready(Ok(())));
670
671        // Kill the component which should call the `onkill` we passed in.
672        // This should cause the termination future to complete, which should then
673        // cause the controller future to complete.
674        client_proxy.kill().expect("FIDL error returned from kill request to controller");
675        match exec.run_until_stalled(&mut controller_fut) {
676            Poll::Ready(()) => {}
677            x => panic!("Unexpected controller poll state {:?}", x),
678        }
679
680        // Check the controller channel closed with an event that matches
681        // what was sent in the `onkill` closure.
682        let mut event_stream = client_proxy.take_event_stream();
683        let mut next_fut = event_stream.try_next();
684        assert_matches!(
685            exec.run_until_stalled(&mut next_fut),
686            Poll::Ready(Ok(Some(fcrunner::ComponentControllerEvent::OnStop {
687                payload: fcrunner::ComponentStopInfo {
688                    termination_status: Some(0),
689                    exit_code: Some(42),
690                    ..
691                }
692            })))
693        );
694
695        let mut next_fut = event_stream.try_next();
696        assert_matches!(exec.run_until_stalled(&mut next_fut), Poll::Ready(Ok(None)));
697        Ok(())
698    }
699
700    fn create_controller_and_proxy<K: 'static, J: 'static>(
701        fake_component: FakeComponent<K, J>,
702    ) -> Result<(Controller<FakeComponent<K, J>>, ComponentControllerProxy), Error>
703    where
704        K: FnOnce() + std::marker::Send,
705        J: FnOnce() + std::marker::Send,
706    {
707        let (client_endpoint, server_endpoint) =
708            create_endpoints::<fcrunner::ComponentControllerMarker>();
709
710        // Get a proxy to the ComponentController channel.
711        let (controller_stream, control) = server_endpoint.into_stream_and_control_handle();
712        Ok((
713            Controller::new(fake_component, controller_stream, control),
714            client_endpoint.into_proxy(),
715        ))
716    }
717
718    mod launch_info {
719        use fidl::endpoints::Proxy;
720
721        use super::*;
722        use anyhow::format_err;
723        use futures::channel::oneshot;
724
725        fn setup_empty_namespace() -> Result<Namespace, NamespaceError> {
726            setup_namespace(false, vec![])
727        }
728
729        fn setup_namespace(
730            include_pkg: bool,
731            // All the handles created for this will have server end closed.
732            // Clients cannot send messages on those handles in ns.
733            extra_paths: Vec<&str>,
734        ) -> Result<Namespace, NamespaceError> {
735            let mut ns = Vec::<fcrunner::ComponentNamespaceEntry>::new();
736            if include_pkg {
737                let pkg_path = "/pkg".to_string();
738                let pkg_chan = fuchsia_fs::directory::open_in_namespace(
739                    "/pkg",
740                    fio::PERM_READABLE | fio::PERM_EXECUTABLE,
741                )
742                .unwrap()
743                .into_channel()
744                .unwrap()
745                .into_zx_channel();
746                let pkg_handle = ClientEnd::new(pkg_chan);
747
748                ns.push(fcrunner::ComponentNamespaceEntry {
749                    path: Some(pkg_path),
750                    directory: Some(pkg_handle),
751                    ..Default::default()
752                });
753            }
754
755            for path in extra_paths {
756                let (client, _server) = create_endpoints::<fio::DirectoryMarker>();
757                ns.push(fcrunner::ComponentNamespaceEntry {
758                    path: Some(path.to_string()),
759                    directory: Some(client),
760                    ..Default::default()
761                });
762            }
763            Namespace::try_from(ns)
764        }
765
766        #[derive(Default)]
767        struct FakeLauncherServiceResults {
768            names: Vec<String>,
769            handles: Vec<u32>,
770            args: Vec<String>,
771            options: zx::ProcessOptions,
772        }
773
774        fn start_launcher()
775        -> Result<(fproc::LauncherProxy, oneshot::Receiver<FakeLauncherServiceResults>), Error>
776        {
777            let (launcher_proxy, server_end) = create_proxy::<fproc::LauncherMarker>();
778            let (sender, receiver) = oneshot::channel();
779            fasync::Task::local(async move {
780                let stream = server_end.into_stream();
781                run_launcher_service(stream, sender)
782                    .await
783                    .expect("error running fake launcher service");
784            })
785            .detach();
786            Ok((launcher_proxy, receiver))
787        }
788
789        async fn run_launcher_service(
790            mut stream: fproc::LauncherRequestStream,
791            sender: oneshot::Sender<FakeLauncherServiceResults>,
792        ) -> Result<(), Error> {
793            let mut res = FakeLauncherServiceResults::default();
794            while let Some(event) = stream.try_next().await? {
795                match event {
796                    fproc::LauncherRequest::AddArgs { args, .. } => {
797                        res.args.extend(
798                            args.into_iter()
799                                .map(|a| {
800                                    std::str::from_utf8(&a)
801                                        .expect("cannot convert bytes to utf8 string")
802                                        .to_owned()
803                                })
804                                .collect::<Vec<String>>(),
805                        );
806                    }
807                    fproc::LauncherRequest::AddEnvirons { .. } => {}
808                    fproc::LauncherRequest::AddNames { names, .. } => {
809                        res.names
810                            .extend(names.into_iter().map(|m| m.path).collect::<Vec<String>>());
811                    }
812                    fproc::LauncherRequest::AddHandles { handles, .. } => {
813                        res.handles.extend(handles.into_iter().map(|m| m.id).collect::<Vec<u32>>());
814                    }
815                    fproc::LauncherRequest::SetOptions { options, .. } => {
816                        res.options = zx::ProcessOptions::from_bits_retain(options);
817                    }
818                    fproc::LauncherRequest::CreateWithoutStarting { .. } => {}
819                    fproc::LauncherRequest::Launch { .. } => {}
820                }
821            }
822            sender.send(res).map_err(|_e| format_err!("can't send result"))?;
823            Ok(())
824        }
825
826        #[fuchsia::test]
827        async fn missing_pkg() -> Result<(), Error> {
828            let (launcher_proxy, _server_end) = create_proxy::<fproc::LauncherMarker>();
829            let ns = setup_empty_namespace()?;
830
831            assert_eq!(
832                configure_launcher(LauncherConfigArgs {
833                    bin_path: "bin/path",
834                    name: "name",
835                    args: None,
836                    options: zx::ProcessOptions::empty(),
837                    ns: ns,
838                    job: None,
839                    handle_infos: None,
840                    name_infos: None,
841                    environs: None,
842                    launcher: &launcher_proxy,
843                    loader_proxy_chan: None,
844                    executable_vmo: None
845                })
846                .await,
847                Err(LaunchError::MissingPkg),
848            );
849
850            drop(_server_end);
851            Ok(())
852        }
853
854        #[fuchsia::test]
855        async fn invalid_executable() -> Result<(), Error> {
856            let (launcher_proxy, _server_end) = create_proxy::<fproc::LauncherMarker>();
857            let ns = setup_namespace(true, vec![])?;
858
859            match configure_launcher(LauncherConfigArgs {
860                bin_path: "test/path",
861                name: "name",
862                args: None,
863                options: zx::ProcessOptions::empty(),
864                ns: ns,
865                job: None,
866                handle_infos: None,
867                name_infos: None,
868                environs: None,
869                launcher: &launcher_proxy,
870                loader_proxy_chan: None,
871                executable_vmo: None,
872            })
873            .await
874            .expect_err("should error out")
875            {
876                LaunchError::LoadingExecutable(_) => {}
877                e => panic!("Expected LoadingExecutable error, got {:?}", e),
878            }
879            Ok(())
880        }
881
882        #[fuchsia::test]
883        async fn invalid_pkg() -> Result<(), Error> {
884            let (launcher_proxy, _server_end) = create_proxy::<fproc::LauncherMarker>();
885            let ns = setup_namespace(false, vec!["/pkg"])?;
886
887            match configure_launcher(LauncherConfigArgs {
888                bin_path: "bin/path",
889                name: "name",
890                args: None,
891                options: zx::ProcessOptions::empty(),
892                ns: ns,
893                job: None,
894                handle_infos: None,
895                name_infos: None,
896                environs: None,
897                launcher: &launcher_proxy,
898                loader_proxy_chan: None,
899                executable_vmo: None,
900            })
901            .await
902            .expect_err("should error out")
903            {
904                LaunchError::LoadingExecutable(_) => {}
905                e => panic!("Expected LoadingExecutable error, got {:?}", e),
906            }
907            Ok(())
908        }
909
910        #[fuchsia::test]
911        async fn default_args() -> Result<(), Error> {
912            let (launcher_proxy, recv) = start_launcher()?;
913
914            let ns = setup_namespace(true, vec![])?;
915
916            let _launch_info = configure_launcher(LauncherConfigArgs {
917                bin_path: "bin/runner_lib_test",
918                name: "name",
919                args: None,
920                options: zx::ProcessOptions::empty(),
921                ns: ns,
922                job: None,
923                handle_infos: None,
924                name_infos: None,
925                environs: None,
926                launcher: &launcher_proxy,
927                loader_proxy_chan: None,
928                executable_vmo: None,
929            })
930            .await?;
931
932            drop(launcher_proxy);
933
934            let ls = recv.await?;
935
936            assert_eq!(ls.args, vec!("/pkg/bin/runner_lib_test".to_owned()));
937
938            Ok(())
939        }
940
941        #[fasync::run_singlethreaded(test)]
942        async fn custom_executable_vmo() -> Result<(), Error> {
943            let (launcher_proxy, _recv) = start_launcher()?;
944
945            let ns = setup_namespace(true, vec![])?;
946            let vmo = zx::Vmo::create(100)?;
947            vmo.write(b"my_data", 0)?;
948            let launch_info = configure_launcher(LauncherConfigArgs {
949                bin_path: "bin/runner_lib_test",
950                name: "name",
951                args: None,
952                options: zx::ProcessOptions::empty(),
953                ns: ns,
954                job: None,
955                handle_infos: None,
956                name_infos: None,
957                environs: None,
958                launcher: &launcher_proxy,
959                loader_proxy_chan: None,
960                executable_vmo: Some(vmo),
961            })
962            .await?;
963
964            let mut bytes: [u8; 10] = [0; 10];
965            launch_info.executable.read(&mut bytes, 0)?;
966            let expected = b"my_data";
967            assert_eq!(bytes[0..expected.len()], expected[..]);
968            Ok(())
969        }
970
971        #[fasync::run_singlethreaded(test)]
972        async fn extra_args() -> Result<(), Error> {
973            let (launcher_proxy, recv) = start_launcher()?;
974
975            let ns = setup_namespace(true, vec![])?;
976
977            let args = vec!["args1".to_owned(), "arg2".to_owned()];
978
979            let _launch_info = configure_launcher(LauncherConfigArgs {
980                bin_path: "bin/runner_lib_test",
981                name: "name",
982                args: Some(args.clone()),
983                options: zx::ProcessOptions::empty(),
984                ns: ns,
985                job: None,
986                handle_infos: None,
987                name_infos: None,
988                environs: None,
989                launcher: &launcher_proxy,
990                loader_proxy_chan: None,
991                executable_vmo: None,
992            })
993            .await?;
994
995            drop(launcher_proxy);
996
997            let ls = recv.await?;
998
999            let mut expected = vec!["/pkg/bin/runner_lib_test".to_owned()];
1000            expected.extend(args);
1001            assert_eq!(ls.args, expected);
1002
1003            Ok(())
1004        }
1005
1006        #[fasync::run_singlethreaded(test)]
1007        async fn namespace_added() -> Result<(), Error> {
1008            let (launcher_proxy, recv) = start_launcher()?;
1009
1010            let ns = setup_namespace(true, vec!["/some_path1", "/some_path2"])?;
1011
1012            let _launch_info = configure_launcher(LauncherConfigArgs {
1013                bin_path: "bin/runner_lib_test",
1014                name: "name",
1015                args: None,
1016                options: zx::ProcessOptions::empty(),
1017                ns: ns,
1018                job: None,
1019                handle_infos: None,
1020                name_infos: None,
1021                environs: None,
1022                launcher: &launcher_proxy,
1023                loader_proxy_chan: None,
1024                executable_vmo: None,
1025            })
1026            .await?;
1027
1028            drop(launcher_proxy);
1029
1030            let ls = recv.await?;
1031
1032            let mut names = ls.names;
1033            names.sort();
1034            assert_eq!(
1035                names,
1036                vec!("/pkg", "/some_path1", "/some_path2")
1037                    .into_iter()
1038                    .map(|s| s.to_string())
1039                    .collect::<Vec<String>>()
1040            );
1041
1042            Ok(())
1043        }
1044
1045        #[fasync::run_singlethreaded(test)]
1046        async fn extra_namespace_entries() -> Result<(), Error> {
1047            let (launcher_proxy, recv) = start_launcher()?;
1048
1049            let ns = setup_namespace(true, vec!["/some_path1", "/some_path2"])?;
1050
1051            let mut names = vec![];
1052
1053            let extra_paths = vec!["/extra1", "/extra2"];
1054
1055            for path in &extra_paths {
1056                let (client, _server) = create_endpoints::<fio::DirectoryMarker>();
1057
1058                names.push(fproc::NameInfo { path: path.to_string(), directory: client });
1059            }
1060
1061            let _launch_info = configure_launcher(LauncherConfigArgs {
1062                bin_path: "bin/runner_lib_test",
1063                name: "name",
1064                args: None,
1065                options: zx::ProcessOptions::empty(),
1066                ns: ns,
1067                job: None,
1068                handle_infos: None,
1069                name_infos: Some(names),
1070                environs: None,
1071                launcher: &launcher_proxy,
1072                loader_proxy_chan: None,
1073                executable_vmo: None,
1074            })
1075            .await?;
1076
1077            drop(launcher_proxy);
1078
1079            let ls = recv.await?;
1080
1081            let mut paths = vec!["/pkg", "/some_path1", "/some_path2"];
1082            paths.extend(extra_paths.into_iter());
1083            paths.sort();
1084
1085            let mut ls_names = ls.names;
1086            ls_names.sort();
1087
1088            assert_eq!(ls_names, paths.into_iter().map(|s| s.to_string()).collect::<Vec<String>>());
1089
1090            Ok(())
1091        }
1092
1093        #[fasync::run_singlethreaded(test)]
1094        async fn handles_added() -> Result<(), Error> {
1095            let (launcher_proxy, recv) = start_launcher()?;
1096
1097            let ns = setup_namespace(true, vec![])?;
1098
1099            let _launch_info = configure_launcher(LauncherConfigArgs {
1100                bin_path: "bin/runner_lib_test",
1101                name: "name",
1102                args: None,
1103                options: zx::ProcessOptions::empty(),
1104                ns: ns,
1105                job: None,
1106                handle_infos: None,
1107                name_infos: None,
1108                environs: None,
1109                launcher: &launcher_proxy,
1110                loader_proxy_chan: None,
1111                executable_vmo: None,
1112            })
1113            .await?;
1114
1115            drop(launcher_proxy);
1116
1117            let ls = recv.await?;
1118
1119            assert_eq!(
1120                ls.handles,
1121                vec!(
1122                    HandleInfo::new(HandleType::LdsvcLoader, 0).as_raw(),
1123                    HandleInfo::new(HandleType::DefaultJob, 0).as_raw()
1124                )
1125            );
1126
1127            Ok(())
1128        }
1129
1130        #[fasync::run_singlethreaded(test)]
1131        async fn handles_added_with_custom_loader_chan() -> Result<(), Error> {
1132            let (launcher_proxy, recv) = start_launcher()?;
1133
1134            let (c1, _c2) = zx::Channel::create();
1135
1136            let ns = setup_namespace(true, vec![])?;
1137
1138            let _launch_info = configure_launcher(LauncherConfigArgs {
1139                bin_path: "bin/runner_lib_test",
1140                name: "name",
1141                args: None,
1142                options: zx::ProcessOptions::empty(),
1143                ns: ns,
1144                job: None,
1145                handle_infos: None,
1146                name_infos: None,
1147                environs: None,
1148                launcher: &launcher_proxy,
1149                loader_proxy_chan: Some(c1),
1150                executable_vmo: None,
1151            })
1152            .await?;
1153
1154            drop(launcher_proxy);
1155
1156            let ls = recv.await?;
1157
1158            assert_eq!(
1159                ls.handles,
1160                vec!(
1161                    HandleInfo::new(HandleType::LdsvcLoader, 0).as_raw(),
1162                    HandleInfo::new(HandleType::DefaultJob, 0).as_raw()
1163                )
1164            );
1165
1166            Ok(())
1167        }
1168
1169        #[fasync::run_singlethreaded(test)]
1170        async fn extra_handles() -> Result<(), Error> {
1171            let (launcher_proxy, recv) = start_launcher()?;
1172
1173            let ns = setup_namespace(true, vec![])?;
1174
1175            let mut handle_infos = vec![];
1176            for fd in 0..3 {
1177                let (client, _server) = create_endpoints::<fio::DirectoryMarker>();
1178                handle_infos.push(fproc::HandleInfo {
1179                    handle: client.into_channel().into_handle(),
1180                    id: fd,
1181                });
1182            }
1183
1184            let _launch_info = configure_launcher(LauncherConfigArgs {
1185                bin_path: "bin/runner_lib_test",
1186                name: "name",
1187                args: None,
1188                options: zx::ProcessOptions::empty(),
1189                ns: ns,
1190                job: None,
1191                handle_infos: Some(handle_infos),
1192                name_infos: None,
1193                environs: None,
1194                launcher: &launcher_proxy,
1195                loader_proxy_chan: None,
1196                executable_vmo: None,
1197            })
1198            .await?;
1199
1200            drop(launcher_proxy);
1201
1202            let ls = recv.await?;
1203
1204            assert_eq!(
1205                ls.handles,
1206                vec!(
1207                    0,
1208                    1,
1209                    2,
1210                    HandleInfo::new(HandleType::LdsvcLoader, 0).as_raw(),
1211                    HandleInfo::new(HandleType::DefaultJob, 0).as_raw(),
1212                )
1213            );
1214
1215            Ok(())
1216        }
1217    }
1218}