fdio/
spawn_builder.rs

1// Copyright 2020 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use crate::{spawn_etc, transfer_fd, SpawnAction, SpawnOptions};
6
7use std::ffi::CString;
8use std::fs::File;
9
10#[derive(Default)]
11/// Convience wrapper for `spawn_etc`.
12pub struct SpawnBuilder {
13    options: Option<SpawnOptions>,
14    args: Vec<CString>,
15    dirs: Vec<(
16        CString,
17        // Option used for interior mutability. When building the arguments to `spawn_etc` we need
18        // borrowed strings and owned handles, so we want this vector to own the strings but allow
19        // moving out of the handles.
20        //
21        // This is always `Some` until the builder is consumed.
22        Option<zx::Handle>,
23    )>,
24}
25
26impl SpawnBuilder {
27    /// Create a `SpawnBuilder` with empty `SpawnOptions`.
28    pub fn new() -> Self {
29        Self::default()
30    }
31
32    /// Set the `SpawnOptions`.
33    pub fn options(mut self, options: SpawnOptions) -> Self {
34        self.options = Some(options);
35        self
36    }
37
38    /// Add an argument.
39    pub fn arg(self, arg: impl Into<String>) -> Result<Self, Error> {
40        self.arg_impl(arg.into())
41    }
42
43    fn arg_impl(mut self, arg: String) -> Result<Self, Error> {
44        self.args.push(CString::new(arg).map_err(Error::ConvertArgToCString)?);
45        Ok(self)
46    }
47
48    /// Add a directory that will be added to the spawned process's namespace.
49    pub fn add_dir_to_namespace(self, path: impl Into<String>, dir: File) -> Result<Self, Error> {
50        self.add_dir_to_namespace_impl(path.into(), dir)
51    }
52
53    fn add_dir_to_namespace_impl(self, path: String, dir: File) -> Result<Self, Error> {
54        let handle = transfer_fd(dir).map_err(Error::TransferFd)?;
55        self.add_handle_to_namespace(path, handle)
56    }
57
58    /// Add a directory that will be added to the spawned process's namespace.
59    pub fn add_directory_to_namespace(
60        self,
61        path: impl Into<String>,
62        client_end: fidl::endpoints::ClientEnd<fidl_fuchsia_io::DirectoryMarker>,
63    ) -> Result<Self, Error> {
64        self.add_directory_to_namespace_impl(path.into(), client_end)
65    }
66
67    fn add_directory_to_namespace_impl(
68        self,
69        path: String,
70        client_end: fidl::endpoints::ClientEnd<fidl_fuchsia_io::DirectoryMarker>,
71    ) -> Result<Self, Error> {
72        self.add_handle_to_namespace(path, client_end.into())
73    }
74
75    fn add_handle_to_namespace(mut self, path: String, handle: zx::Handle) -> Result<Self, Error> {
76        let path = CString::new(path).map_err(Error::ConvertNamespacePathToCString)?;
77        self.dirs.push((path, Some(handle)));
78        Ok(self)
79    }
80
81    /// Spawn a process from the binary located at `path`.
82    pub fn spawn_from_path(
83        self,
84        path: impl Into<String>,
85        job: &zx::Job,
86    ) -> Result<zx::Process, Error> {
87        self.spawn_from_path_impl(path.into(), job)
88    }
89
90    pub fn spawn_from_path_impl(
91        mut self,
92        path: String,
93        job: &zx::Job,
94    ) -> Result<zx::Process, Error> {
95        let mut actions = self
96            .dirs
97            .iter_mut()
98            .map(|(path, handle)| SpawnAction::add_namespace_entry(path, handle.take().unwrap()))
99            .collect::<Vec<_>>();
100
101        spawn_etc(
102            job,
103            self.options.unwrap_or(SpawnOptions::empty()),
104            &CString::new(path).map_err(Error::ConvertBinaryPathToCString)?,
105            self.args.iter().map(|arg| arg.as_ref()).collect::<Vec<_>>().as_slice(),
106            None,
107            actions.as_mut_slice(),
108        )
109        .map_err(|(status, message)| Error::Spawn { status, message })
110    }
111}
112
113#[derive(Debug, thiserror::Error)]
114pub enum Error {
115    #[error("failed to convert process argument to CString")]
116    ConvertArgToCString(#[source] std::ffi::NulError),
117
118    #[error("failed to convert namespace path to CString")]
119    ConvertNamespacePathToCString(#[source] std::ffi::NulError),
120
121    #[error("failed to convert binary path to CString")]
122    ConvertBinaryPathToCString(#[source] std::ffi::NulError),
123
124    #[error("failed to transfer_fd")]
125    TransferFd(#[source] zx::Status),
126
127    #[error("spawn failed with status: {status} and message: {message}")]
128    Spawn { status: zx::Status, message: String },
129}
130
131#[cfg(test)]
132mod tests {
133    use super::*;
134    use fidl::AsHandleRef as _;
135    use fuchsia_async as fasync;
136    use std::io::Write as _;
137
138    async fn process_exit_success(proc: zx::Process) {
139        assert_eq!(
140            fasync::OnSignals::new(&proc.as_handle_ref(), zx::Signals::PROCESS_TERMINATED)
141                .await
142                .unwrap(),
143            zx::Signals::PROCESS_TERMINATED
144        );
145        assert_eq!(proc.info().expect("process info").return_code, 0);
146    }
147
148    #[fasync::run_singlethreaded(test)]
149    async fn spawn_builder() {
150        let tempdir = tempfile::TempDir::new().unwrap();
151        let () = File::create(tempdir.path().join("injected-file"))
152            .unwrap()
153            .write_all("some-contents".as_bytes())
154            .unwrap();
155        let dir = File::open(tempdir.path()).unwrap();
156
157        let builder = SpawnBuilder::new()
158            .options(SpawnOptions::DEFAULT_LOADER)
159            .arg("arg0")
160            .unwrap()
161            .arg("arg1")
162            .unwrap()
163            .add_dir_to_namespace("/injected-dir", dir)
164            .unwrap();
165        let process = builder
166            .spawn_from_path("/pkg/bin/spawn_builder_test_target", &fuchsia_runtime::job_default())
167            .unwrap();
168
169        process_exit_success(process).await;
170    }
171}