1use crate::{SpawnAction, SpawnOptions, spawn_etc, transfer_fd};
6
7use std::ffi::CString;
8use std::fs::File;
9
10#[derive(Default)]
11pub struct SpawnBuilder {
13 options: Option<SpawnOptions>,
14 args: Vec<CString>,
15 dirs: Vec<(
16 CString,
17 Option<zx::NullableHandle>,
23 )>,
24}
25
26impl SpawnBuilder {
27 pub fn new() -> Self {
29 Self::default()
30 }
31
32 pub fn options(mut self, options: SpawnOptions) -> Self {
34 self.options = Some(options);
35 self
36 }
37
38 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 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 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(
76 mut self,
77 path: String,
78 handle: zx::NullableHandle,
79 ) -> Result<Self, Error> {
80 let path = CString::new(path).map_err(Error::ConvertNamespacePathToCString)?;
81 self.dirs.push((path, Some(handle)));
82 Ok(self)
83 }
84
85 pub fn spawn_from_path(
87 self,
88 path: impl Into<String>,
89 job: &zx::Job,
90 ) -> Result<zx::Process, Error> {
91 self.spawn_from_path_impl(path.into(), job)
92 }
93
94 pub fn spawn_from_path_impl(
95 mut self,
96 path: String,
97 job: &zx::Job,
98 ) -> Result<zx::Process, Error> {
99 let mut actions = self
100 .dirs
101 .iter_mut()
102 .map(|(path, handle)| SpawnAction::add_namespace_entry(path, handle.take().unwrap()))
103 .collect::<Vec<_>>();
104
105 spawn_etc(
106 job,
107 self.options.unwrap_or(SpawnOptions::empty()),
108 &CString::new(path).map_err(Error::ConvertBinaryPathToCString)?,
109 self.args.iter().map(|arg| arg.as_ref()).collect::<Vec<_>>().as_slice(),
110 None,
111 actions.as_mut_slice(),
112 )
113 .map_err(|(status, message)| Error::Spawn { status, message })
114 }
115}
116
117#[derive(Debug, thiserror::Error)]
118pub enum Error {
119 #[error("failed to convert process argument to CString")]
120 ConvertArgToCString(#[source] std::ffi::NulError),
121
122 #[error("failed to convert namespace path to CString")]
123 ConvertNamespacePathToCString(#[source] std::ffi::NulError),
124
125 #[error("failed to convert binary path to CString")]
126 ConvertBinaryPathToCString(#[source] std::ffi::NulError),
127
128 #[error("failed to transfer_fd")]
129 TransferFd(#[source] zx::Status),
130
131 #[error("spawn failed with status: {status} and message: {message}")]
132 Spawn { status: zx::Status, message: String },
133}
134
135#[cfg(test)]
136mod tests {
137 use super::*;
138 use fidl::AsHandleRef as _;
139 use fuchsia_async as fasync;
140 use std::io::Write as _;
141
142 async fn process_exit_success(proc: zx::Process) {
143 assert_eq!(
144 fasync::OnSignals::new(&proc.as_handle_ref(), zx::Signals::PROCESS_TERMINATED)
145 .await
146 .unwrap(),
147 zx::Signals::PROCESS_TERMINATED
148 );
149 assert_eq!(proc.info().expect("process info").return_code, 0);
150 }
151
152 #[fasync::run_singlethreaded(test)]
153 async fn spawn_builder() {
154 let tempdir = tempfile::TempDir::new().unwrap();
155 let () = File::create(tempdir.path().join("injected-file"))
156 .unwrap()
157 .write_all("some-contents".as_bytes())
158 .unwrap();
159 let dir = File::open(tempdir.path()).unwrap();
160
161 let builder = SpawnBuilder::new()
162 .options(SpawnOptions::DEFAULT_LOADER)
163 .arg("arg0")
164 .unwrap()
165 .arg("arg1")
166 .unwrap()
167 .add_dir_to_namespace("/injected-dir", dir)
168 .unwrap();
169 let process = builder
170 .spawn_from_path("/pkg/bin/spawn_builder_test_target", &fuchsia_runtime::job_default())
171 .unwrap();
172
173 process_exit_success(process).await;
174 }
175}