starnix_core/task/
container_namespace.rs1use fidl::endpoints::create_endpoints;
5use std::collections::HashMap;
6use std::path::{Path, PathBuf};
7use {fidl_fuchsia_component_runner as frunner, fidl_fuchsia_unknown as funknown};
8
9#[derive(Debug)]
10pub struct ContainerNamespace {
11 namespace_entries: HashMap<PathBuf, funknown::CloneableSynchronousProxy>,
13}
14
15impl ContainerNamespace {
16 pub fn new() -> Self {
17 Self { namespace_entries: Default::default() }
18 }
19
20 pub fn has_channel_entry(&self, channel_path: impl AsRef<Path>) -> bool {
23 return self.namespace_entries.contains_key(channel_path.as_ref());
24 }
25
26 pub fn get_namespace_channel(
28 &self,
29 channel_path: impl AsRef<Path>,
30 ) -> Result<zx::Channel, anyhow::Error> {
31 let path = channel_path.as_ref();
32 if !path.is_absolute() {
33 anyhow::bail!(
34 "Invalid parameter provided to get_namespace_channel: {}",
35 path.display()
36 );
37 }
38
39 match self.namespace_entries.get(path) {
40 Some(cloneable_proxy) => {
41 let (cloned_client, cloned_server) =
42 create_endpoints::<funknown::CloneableMarker>();
43 let clone_result = cloneable_proxy.clone(cloned_server);
44 if clone_result.is_err() {
45 anyhow::bail!("Unable to clone the proxy channel for {}!", path.display())
46 }
47 Ok(cloned_client.into_channel())
48 }
49 None => anyhow::bail!("Could not find an entry for {}", path.display()),
50 }
51 }
52
53 pub fn find_closest_channel(
62 &self,
63 search_path: impl AsRef<Path>,
64 ) -> Result<(zx::Channel, String), anyhow::Error> {
65 let search_path = search_path.as_ref();
66 if !search_path.is_absolute() {
67 anyhow::bail!(
68 "Invalid parameter provided to find_closest_channel: {}",
69 search_path.display()
70 );
71 }
72
73 let mut root_channel = None;
74 let mut remaining_subdir = String::new();
75
76 let mut ns_path_ancestors = search_path.ancestors();
77 while let Some(path) = ns_path_ancestors.next() {
78 if !self.has_channel_entry(path) {
81 let last_segment = path
82 .components()
83 .next_back()
84 .and_then(|component| component.as_os_str().to_str())
85 .unwrap_or("");
86 remaining_subdir.insert_str(0, &format!("{last_segment}/"));
87 continue;
88 }
89
90 root_channel = Some(self.get_namespace_channel(path)?);
92 break;
93 }
94
95 if let Some(channel) = root_channel {
96 remaining_subdir.pop();
98 Ok((channel, remaining_subdir))
99 } else {
100 anyhow::bail!("Unable to find a namespace corresponding to {}", search_path.display());
101 }
102 }
103
104 pub fn try_clone(&self) -> Result<ContainerNamespace, anyhow::Error> {
107 let mut cloned_entries = HashMap::new();
108 for (path, _) in &self.namespace_entries {
109 match self.get_namespace_channel(path) {
110 Ok(cloned_channel) => {
111 cloned_entries.insert(
112 path.clone(),
113 funknown::CloneableSynchronousProxy::new(cloned_channel),
114 );
115 }
116 Err(err) => {
117 anyhow::bail!(
118 "The ContainerNamespace clone operation for {} has failed: {}",
119 path.display(),
120 err,
121 )
122 }
123 }
124 }
125 Ok(ContainerNamespace { namespace_entries: cloned_entries })
126 }
127}
128
129impl From<Vec<frunner::ComponentNamespaceEntry>> for ContainerNamespace {
130 fn from(namespace: Vec<frunner::ComponentNamespaceEntry>) -> Self {
131 let mut namespace_entries = HashMap::new();
132 for mut entry in namespace {
133 if let (Some(entry_name), Some(entry_dir)) =
134 (entry.path.clone(), entry.directory.take())
135 {
136 let entry_channel = entry_dir.into_channel();
137 namespace_entries.insert(
138 PathBuf::from(entry_name),
139 funknown::CloneableSynchronousProxy::new(entry_channel),
140 );
141 }
142 }
143 ContainerNamespace { namespace_entries }
144 }
145}
146
147#[cfg(test)]
148mod test {
149 use super::*;
150 use fidl::endpoints::{ClientEnd, Proxy};
151 use fidl_fuchsia_io as fio;
152 use fuchsia_fs::directory;
153
154 #[::fuchsia::test]
155 fn correctly_reports_entries() {
156 let _stub_exec = fuchsia_async::TestExecutor::new();
158 let mut ns = Vec::<frunner::ComponentNamespaceEntry>::new();
159 let pkg_channel: zx::Channel =
160 directory::open_in_namespace("/pkg", fio::PERM_READABLE | fio::PERM_EXECUTABLE)
161 .expect("failed to open /pkg")
162 .into_channel()
163 .expect("into_channel")
164 .into();
165 let data_handle = ClientEnd::new(pkg_channel);
166 ns.push(frunner::ComponentNamespaceEntry {
167 path: Some("/pkg".to_string()),
168 directory: Some(data_handle),
169 ..Default::default()
170 });
171
172 let cn_under_test = ContainerNamespace::from(ns);
174 assert!(cn_under_test.has_channel_entry("/pkg"));
175 assert_eq!(cn_under_test.has_channel_entry("/data"), false);
176 }
177
178 #[::fuchsia::test]
179 fn correctly_provides_and_retains_channel_entries() {
180 let _stub_exec = fuchsia_async::TestExecutor::new();
182 let mut ns = Vec::<frunner::ComponentNamespaceEntry>::new();
183 let pkg_channel: zx::Channel =
184 directory::open_in_namespace("/pkg", fio::PERM_READABLE | fio::PERM_EXECUTABLE)
185 .expect("failed to open /pkg")
186 .into_channel()
187 .expect("into_channel")
188 .into();
189 let data_handle = ClientEnd::new(pkg_channel);
190 ns.push(frunner::ComponentNamespaceEntry {
191 path: Some("/pkg".to_string()),
192 directory: Some(data_handle),
193 ..Default::default()
194 });
195
196 let cn_under_test = ContainerNamespace::from(ns);
199 let returned_channel = cn_under_test
200 .get_namespace_channel("/pkg")
201 .expect("get_namespace_channel should return a valid /pkg channel.");
202 assert!(returned_channel.write(b"hello", &mut vec![]).is_ok());
203 assert!(cn_under_test.has_channel_entry("/pkg"));
204 }
205
206 #[::fuchsia::test]
207 fn returns_err_on_invalid_request() {
208 let _stub_exec = fuchsia_async::TestExecutor::new();
209
210 let cn_under_test = ContainerNamespace::new();
212 assert!(cn_under_test.get_namespace_channel("/pkg").is_err());
213 }
214
215 #[::fuchsia::test]
216 fn correctly_returns_closest_channel_partial_match() {
217 let _stub_exec = fuchsia_async::TestExecutor::new();
219 let mut ns = Vec::<frunner::ComponentNamespaceEntry>::new();
220 let pkg_channel: zx::Channel =
221 directory::open_in_namespace("/pkg", fio::PERM_READABLE | fio::PERM_EXECUTABLE)
222 .expect("failed to open /pkg")
223 .into_channel()
224 .expect("into_channel")
225 .into();
226 let data_handle = ClientEnd::new(pkg_channel);
227 ns.push(frunner::ComponentNamespaceEntry {
228 path: Some("/pkg".to_string()),
229 directory: Some(data_handle),
230 ..Default::default()
231 });
232
233 let cn_under_test = ContainerNamespace::from(ns);
236 let (returned_channel, subdir) = cn_under_test
237 .find_closest_channel("/pkg/foo/bar")
238 .expect("get_namespace_channel should return a valid /pkg channel.");
239
240 assert!(returned_channel.write(b"hello", &mut vec![]).is_ok());
243 assert!(cn_under_test.has_channel_entry("/pkg"));
244
245 assert_eq!(subdir, "foo/bar");
247 }
248
249 #[::fuchsia::test]
250 fn correctly_returns_closest_channel_exact_match() {
251 let _stub_exec = fuchsia_async::TestExecutor::new();
253 let mut ns = Vec::<frunner::ComponentNamespaceEntry>::new();
254 let pkg_channel: zx::Channel =
255 directory::open_in_namespace("/pkg", fio::PERM_READABLE | fio::PERM_EXECUTABLE)
256 .expect("failed to open /pkg")
257 .into_channel()
258 .expect("into_channel")
259 .into();
260 let data_handle = ClientEnd::new(pkg_channel);
261 ns.push(frunner::ComponentNamespaceEntry {
262 path: Some("/pkg".to_string()),
263 directory: Some(data_handle),
264 ..Default::default()
265 });
266
267 let cn_under_test = ContainerNamespace::from(ns);
270 let (returned_channel, subdir) = cn_under_test
271 .find_closest_channel("/pkg")
272 .expect("get_namespace_channel should return a valid /pkg channel.");
273
274 assert!(returned_channel.write(b"hello", &mut vec![]).is_ok());
277 assert!(cn_under_test.has_channel_entry("/pkg"));
278
279 assert_eq!(subdir, "");
281 }
282
283 #[::fuchsia::test]
284 fn correctly_clones() {
285 let _stub_exec = fuchsia_async::TestExecutor::new();
287 let mut ns = Vec::<frunner::ComponentNamespaceEntry>::new();
288 let pkg_channel: zx::Channel =
289 directory::open_in_namespace("/pkg", fio::PERM_READABLE | fio::PERM_EXECUTABLE)
290 .expect("failed to open /pkg")
291 .into_channel()
292 .expect("into_channel")
293 .into();
294 let data_handle = ClientEnd::new(pkg_channel);
295 ns.push(frunner::ComponentNamespaceEntry {
296 path: Some("/pkg".to_string()),
297 directory: Some(data_handle),
298 ..Default::default()
299 });
300
301 let cn_under_test = ContainerNamespace::from(ns);
302 let clone_under_test = cn_under_test.try_clone().expect("Clone should succeed.");
303
304 let original_channel = cn_under_test
307 .get_namespace_channel("/pkg")
308 .expect("get_namespace_channel should return a valid /pkg channel.");
309 let cloned_channel = clone_under_test
310 .get_namespace_channel("/pkg")
311 .expect("get_namespace_channel should return a valid /pkg channel.");
312 assert!(original_channel.write(b"hello", &mut vec![]).is_ok());
313 assert!(cloned_channel.write(b"hello", &mut vec![]).is_ok());
314
315 assert!(cn_under_test.has_channel_entry("/pkg"));
317 assert!(clone_under_test.has_channel_entry("/pkg"));
318 }
319}