1use cm_types::{IterablePath, NamespacePath};
8use fidl::endpoints::ClientEnd;
9use thiserror::Error;
10use {
11 fidl_fuchsia_component as fcomponent, fidl_fuchsia_component_runner as fcrunner,
12 fidl_fuchsia_io as fio, fidl_fuchsia_process as fprocess,
13};
14
15#[cfg(target_os = "fuchsia")]
16use std::sync::Arc;
17
18mod tree;
19pub use tree::Tree;
20
21#[derive(Debug)]
31pub struct Namespace {
32 tree: Tree<ClientEnd<fio::DirectoryMarker>>,
33}
34
35#[derive(Error, Debug, Clone)]
36pub enum NamespaceError {
37 #[error(
38 "path `{0}` is the parent or child of another namespace entry. \
39 This is not supported."
40 )]
41 Shadow(NamespacePath),
42
43 #[error("duplicate namespace path `{0}`")]
44 Duplicate(NamespacePath),
45
46 #[error("invalid namespace entry `{0}`")]
47 EntryError(#[from] EntryError),
48}
49
50impl Namespace {
51 pub fn new() -> Self {
52 Self { tree: Default::default() }
53 }
54
55 pub fn add(
56 &mut self,
57 path: &NamespacePath,
58 directory: ClientEnd<fio::DirectoryMarker>,
59 ) -> Result<(), NamespaceError> {
60 self.tree.add(path, directory)?;
61 Ok(())
62 }
63
64 pub fn get(&self, path: &NamespacePath) -> Option<&ClientEnd<fio::DirectoryMarker>> {
65 self.tree.get(path)
66 }
67
68 pub fn remove(&mut self, path: &NamespacePath) -> Option<ClientEnd<fio::DirectoryMarker>> {
69 self.tree.remove(path)
70 }
71
72 pub fn flatten(self) -> Vec<Entry> {
73 self.tree.flatten().into_iter().map(|(path, directory)| Entry { path, directory }).collect()
74 }
75
76 pub fn paths(&self) -> Vec<NamespacePath> {
78 self.tree.map_ref(|_| ()).flatten().into_iter().map(|(path, ())| path).collect()
79 }
80}
81
82impl Default for Namespace {
83 fn default() -> Self {
84 Self::new()
85 }
86}
87
88#[cfg(target_os = "fuchsia")]
89impl Clone for Namespace {
90 fn clone(&self) -> Self {
91 let tree = self.tree.map_ref(|dir| {
94 let raw_handle = dir.channel().as_handle_ref().raw_handle();
95 unsafe {
97 let borrowed: zx::Channel = zx::NullableHandle::from_raw(raw_handle).into();
98 let borrowed = fio::DirectorySynchronousProxy::new(borrowed);
99 let (client_end, server_end) =
100 fidl::endpoints::create_endpoints::<fio::DirectoryMarker>();
101 let _ = borrowed.clone(server_end.into_channel().into());
102 std::mem::forget(borrowed.into_channel());
103 client_end
104 }
105 });
106 Self { tree }
107 }
108}
109
110impl From<Namespace> for Vec<Entry> {
111 fn from(namespace: Namespace) -> Self {
112 namespace.flatten()
113 }
114}
115
116impl From<Namespace> for Vec<fcrunner::ComponentNamespaceEntry> {
117 fn from(namespace: Namespace) -> Self {
118 namespace.flatten().into_iter().map(Into::into).collect()
119 }
120}
121
122impl From<Namespace> for Vec<fprocess::NameInfo> {
123 fn from(namespace: Namespace) -> Self {
124 namespace.flatten().into_iter().map(Into::into).collect()
125 }
126}
127
128#[cfg(target_os = "fuchsia")]
129impl From<Namespace> for Vec<process_builder::NamespaceEntry> {
130 fn from(namespace: Namespace) -> Self {
131 namespace.flatten().into_iter().map(Into::into).collect()
132 }
133}
134
135#[cfg(target_os = "fuchsia")]
139impl TryFrom<Namespace> for vfs::tree_builder::TreeBuilder {
140 type Error = vfs::tree_builder::Error;
141
142 fn try_from(namespace: Namespace) -> Result<Self, Self::Error> {
143 let mut builder = vfs::tree_builder::TreeBuilder::empty_dir();
144 for Entry { path, directory } in namespace.flatten().into_iter() {
145 let path: Vec<&str> = path.iter_segments().map(|s| s.as_str()).collect();
146 builder.add_entry(path, vfs::remote::remote_dir(directory.into_proxy()))?;
147 }
148 Ok(builder)
149 }
150}
151
152#[cfg(target_os = "fuchsia")]
154impl TryFrom<Namespace> for Arc<vfs::directory::immutable::simple::Simple> {
155 type Error = vfs::tree_builder::Error;
156
157 fn try_from(namespace: Namespace) -> Result<Self, Self::Error> {
158 let builder: vfs::tree_builder::TreeBuilder = namespace.try_into()?;
159 Ok(builder.build())
160 }
161}
162
163impl From<Tree<ClientEnd<fio::DirectoryMarker>>> for Namespace {
164 fn from(tree: Tree<ClientEnd<fio::DirectoryMarker>>) -> Self {
165 Self { tree }
166 }
167}
168
169impl TryFrom<Vec<Entry>> for Namespace {
170 type Error = NamespaceError;
171
172 fn try_from(value: Vec<Entry>) -> Result<Self, Self::Error> {
173 let mut ns = Namespace::new();
174 for entry in value {
175 ns.add(&entry.path, entry.directory)?;
176 }
177 Ok(ns)
178 }
179}
180
181impl TryFrom<Vec<fcrunner::ComponentNamespaceEntry>> for Namespace {
182 type Error = NamespaceError;
183
184 fn try_from(entries: Vec<fcrunner::ComponentNamespaceEntry>) -> Result<Self, Self::Error> {
185 let entries: Vec<Entry> =
186 entries.into_iter().map(TryInto::try_into).collect::<Result<Vec<_>, EntryError>>()?;
187 entries.try_into()
188 }
189}
190
191impl TryFrom<Vec<fcomponent::NamespaceEntry>> for Namespace {
192 type Error = NamespaceError;
193
194 fn try_from(entries: Vec<fcomponent::NamespaceEntry>) -> Result<Self, Self::Error> {
195 let entries: Vec<Entry> =
196 entries.into_iter().map(TryInto::try_into).collect::<Result<Vec<_>, EntryError>>()?;
197 entries.try_into()
198 }
199}
200
201impl TryFrom<Vec<fprocess::NameInfo>> for Namespace {
202 type Error = NamespaceError;
203
204 fn try_from(entries: Vec<fprocess::NameInfo>) -> Result<Self, Self::Error> {
205 let entries: Vec<Entry> =
206 entries.into_iter().map(TryInto::try_into).collect::<Result<Vec<_>, EntryError>>()?;
207 entries.try_into()
208 }
209}
210
211#[cfg(target_os = "fuchsia")]
212impl TryFrom<Vec<process_builder::NamespaceEntry>> for Namespace {
213 type Error = NamespaceError;
214
215 fn try_from(entries: Vec<process_builder::NamespaceEntry>) -> Result<Self, Self::Error> {
216 let entries: Vec<Entry> =
217 entries.into_iter().map(TryInto::try_into).collect::<Result<Vec<_>, EntryError>>()?;
218 entries.try_into()
219 }
220}
221
222#[derive(Eq, Ord, PartialOrd, PartialEq, Debug)]
224pub struct Entry {
225 pub path: NamespacePath,
227
228 pub directory: ClientEnd<fio::DirectoryMarker>,
230}
231
232impl From<Entry> for fcrunner::ComponentNamespaceEntry {
233 fn from(entry: Entry) -> Self {
234 Self {
235 path: Some(entry.path.into()),
236 directory: Some(entry.directory),
237 ..Default::default()
238 }
239 }
240}
241
242impl From<Entry> for fcomponent::NamespaceEntry {
243 fn from(entry: Entry) -> Self {
244 Self {
245 path: Some(entry.path.into()),
246 directory: Some(entry.directory),
247 ..Default::default()
248 }
249 }
250}
251
252impl From<Entry> for fprocess::NameInfo {
253 fn from(entry: Entry) -> Self {
254 Self { path: entry.path.into(), directory: entry.directory }
255 }
256}
257
258#[cfg(target_os = "fuchsia")]
259impl From<Entry> for process_builder::NamespaceEntry {
260 fn from(entry: Entry) -> Self {
261 Self { path: entry.path.into(), directory: entry.directory }
262 }
263}
264
265#[derive(Debug, Clone, Error)]
266pub enum EntryError {
267 #[error("path is not set")]
268 MissingPath,
269
270 #[error("directory is not set")]
271 MissingDirectory,
272
273 #[error("entry type is not supported (must be directory or dictionary")]
274 UnsupportedType,
275
276 #[error("path is invalid for a namespace entry: `{0}`")]
277 InvalidPath(#[from] cm_types::ParseError),
278}
279
280impl TryFrom<fcrunner::ComponentNamespaceEntry> for Entry {
281 type Error = EntryError;
282
283 fn try_from(entry: fcrunner::ComponentNamespaceEntry) -> Result<Self, Self::Error> {
284 Ok(Self {
285 path: entry.path.ok_or(EntryError::MissingPath)?.parse()?,
286 directory: entry.directory.ok_or(EntryError::MissingDirectory)?,
287 })
288 }
289}
290
291impl TryFrom<fcomponent::NamespaceEntry> for Entry {
292 type Error = EntryError;
293
294 fn try_from(entry: fcomponent::NamespaceEntry) -> Result<Self, Self::Error> {
295 Ok(Self {
296 path: entry.path.ok_or(EntryError::MissingPath)?.parse()?,
297 directory: entry.directory.ok_or(EntryError::MissingDirectory)?,
298 })
299 }
300}
301
302impl TryFrom<fprocess::NameInfo> for Entry {
303 type Error = EntryError;
304
305 fn try_from(entry: fprocess::NameInfo) -> Result<Self, Self::Error> {
306 Ok(Self { path: entry.path.parse()?, directory: entry.directory })
307 }
308}
309
310#[cfg(target_os = "fuchsia")]
311impl TryFrom<process_builder::NamespaceEntry> for Entry {
312 type Error = EntryError;
313
314 fn try_from(entry: process_builder::NamespaceEntry) -> Result<Self, Self::Error> {
315 Ok(Self { path: entry.path.try_into()?, directory: entry.directory })
316 }
317}
318
319#[cfg(test)]
320mod tests {
321 use super::*;
322 use assert_matches::assert_matches;
323 use fidl::endpoints::Proxy as _;
324 use fuchsia_async as fasync;
325 use zx::{AsHandleRef, Peered};
326
327 fn ns_path(str: &str) -> NamespacePath {
328 str.parse().unwrap()
329 }
330
331 #[test]
332 fn test_try_from_namespace() {
333 {
334 let (client_end_1, _) = fidl::endpoints::create_endpoints();
335 let (client_end_2, _) = fidl::endpoints::create_endpoints();
336 let entries = vec![
337 Entry { path: ns_path("/foo"), directory: client_end_1 },
338 Entry { path: ns_path("/foo/bar"), directory: client_end_2 },
339 ];
340 assert_matches!(
341 Namespace::try_from(entries),
342 Err(NamespaceError::Shadow(path)) if path.to_string() == "/foo/bar"
343 );
344 }
345 {
346 let (client_end_1, _) = fidl::endpoints::create_endpoints();
347 let (client_end_2, _) = fidl::endpoints::create_endpoints();
348 let entries = vec![
349 Entry { path: ns_path("/foo"), directory: client_end_1 },
350 Entry { path: ns_path("/foo"), directory: client_end_2 },
351 ];
352 assert_matches!(
353 Namespace::try_from(entries),
354 Err(NamespaceError::Duplicate(path)) if path.to_string() == "/foo"
355 );
356 }
357 }
358
359 #[cfg(target_os = "fuchsia")]
360 #[fasync::run_singlethreaded(test)]
361 async fn test_clone() {
362 use vfs::file::vmo::read_only;
363
364 let dir = vfs::pseudo_directory! {
366 "foo" => vfs::pseudo_directory! {
367 "bar" => read_only(b"Fuchsia"),
368 },
369 };
370 let client_end = vfs::directory::serve_read_only(dir).into_client_end().unwrap();
371
372 let mut namespace = Namespace::new();
374 namespace.add(&ns_path("/data"), client_end).unwrap();
375
376 let namespace_clone = namespace.clone();
378
379 let mut entries = namespace.flatten();
380 let mut entries_clone = namespace_clone.flatten();
381
382 async fn verify(entry: Entry) {
383 assert_eq!(entry.path.to_string(), "/data");
384 let dir = entry.directory.into_proxy();
385 let file = fuchsia_fs::directory::open_file(&dir, "foo/bar", fio::PERM_READABLE)
386 .await
387 .unwrap();
388 let content = fuchsia_fs::file::read(&file).await.unwrap();
389 assert_eq!(content, b"Fuchsia");
390 }
391
392 verify(entries.remove(0)).await;
393 verify(entries_clone.remove(0)).await;
394 }
395
396 #[test]
397 fn test_flatten() {
398 let mut namespace = Namespace::new();
399 let (client_end, server_end) = fidl::endpoints::create_endpoints();
400 namespace.add(&ns_path("/svc"), client_end).unwrap();
401 let mut entries = namespace.flatten();
402 assert_eq!(entries[0].path.to_string(), "/svc");
403 entries
404 .remove(0)
405 .directory
406 .into_channel()
407 .signal_peer(zx::Signals::empty(), zx::Signals::USER_0)
408 .unwrap();
409 server_end
410 .as_handle_ref()
411 .wait_one(zx::Signals::USER_0, zx::MonotonicInstant::INFINITE)
412 .unwrap();
413 }
414
415 #[test]
416 fn test_remove() {
417 let mut namespace = Namespace::new();
418 let (client_end, server_end) = fidl::endpoints::create_endpoints();
419 namespace.add(&ns_path("/svc"), client_end).unwrap();
420 let client_end = namespace.remove(&ns_path("/svc")).unwrap();
421 let entries = namespace.flatten();
422 assert!(entries.is_empty());
423 client_end.into_channel().signal_peer(zx::Signals::empty(), zx::Signals::USER_0).unwrap();
424 server_end
425 .as_handle_ref()
426 .wait_one(zx::Signals::USER_0, zx::MonotonicInstant::INFINITE)
427 .unwrap();
428 }
429}