fdio/
spawn_builder.rs
1use crate::{spawn_etc, transfer_fd, SpawnAction, SpawnOptions};
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::Handle>,
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(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 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}