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<cm_types::NamespaceEntry> for Entry {
164 fn from(entry: cm_types::NamespaceEntry) -> Self {
165 Entry { path: entry.path, directory: entry.directory }
166 }
167}
168
169impl From<Tree<ClientEnd<fio::DirectoryMarker>>> for Namespace {
170 fn from(tree: Tree<ClientEnd<fio::DirectoryMarker>>) -> Self {
171 Self { tree }
172 }
173}
174
175impl TryFrom<Vec<cm_types::NamespaceEntry>> for Namespace {
176 type Error = NamespaceError;
177
178 fn try_from(value: Vec<cm_types::NamespaceEntry>) -> Result<Self, Self::Error> {
179 let mut ns = Namespace::new();
180 for entry in value {
181 ns.add(&entry.path, entry.directory)?;
182 }
183 Ok(ns)
184 }
185}
186
187impl TryFrom<Vec<Entry>> for Namespace {
188 type Error = NamespaceError;
189
190 fn try_from(value: Vec<Entry>) -> Result<Self, Self::Error> {
191 let mut ns = Namespace::new();
192 for entry in value {
193 ns.add(&entry.path, entry.directory)?;
194 }
195 Ok(ns)
196 }
197}
198
199impl TryFrom<Vec<fcrunner::ComponentNamespaceEntry>> for Namespace {
200 type Error = NamespaceError;
201
202 fn try_from(entries: Vec<fcrunner::ComponentNamespaceEntry>) -> Result<Self, Self::Error> {
203 let entries: Vec<Entry> =
204 entries.into_iter().map(TryInto::try_into).collect::<Result<Vec<_>, EntryError>>()?;
205 entries.try_into()
206 }
207}
208
209impl TryFrom<Vec<fcomponent::NamespaceEntry>> for Namespace {
210 type Error = NamespaceError;
211
212 fn try_from(entries: Vec<fcomponent::NamespaceEntry>) -> Result<Self, Self::Error> {
213 let entries: Vec<Entry> =
214 entries.into_iter().map(TryInto::try_into).collect::<Result<Vec<_>, EntryError>>()?;
215 entries.try_into()
216 }
217}
218
219impl TryFrom<Vec<fprocess::NameInfo>> for Namespace {
220 type Error = NamespaceError;
221
222 fn try_from(entries: Vec<fprocess::NameInfo>) -> Result<Self, Self::Error> {
223 let entries: Vec<Entry> =
224 entries.into_iter().map(TryInto::try_into).collect::<Result<Vec<_>, EntryError>>()?;
225 entries.try_into()
226 }
227}
228
229#[cfg(target_os = "fuchsia")]
230impl TryFrom<Vec<process_builder::NamespaceEntry>> for Namespace {
231 type Error = NamespaceError;
232
233 fn try_from(entries: Vec<process_builder::NamespaceEntry>) -> Result<Self, Self::Error> {
234 let entries: Vec<Entry> =
235 entries.into_iter().map(TryInto::try_into).collect::<Result<Vec<_>, EntryError>>()?;
236 entries.try_into()
237 }
238}
239
240#[derive(Eq, Ord, PartialOrd, PartialEq, Debug)]
242pub struct Entry {
243 pub path: NamespacePath,
245
246 pub directory: ClientEnd<fio::DirectoryMarker>,
248}
249
250impl From<Entry> for fcrunner::ComponentNamespaceEntry {
251 fn from(entry: Entry) -> Self {
252 Self {
253 path: Some(entry.path.into()),
254 directory: Some(entry.directory),
255 ..Default::default()
256 }
257 }
258}
259
260impl From<Entry> for fcomponent::NamespaceEntry {
261 fn from(entry: Entry) -> Self {
262 Self {
263 path: Some(entry.path.into()),
264 directory: Some(entry.directory),
265 ..Default::default()
266 }
267 }
268}
269
270impl From<Entry> for fprocess::NameInfo {
271 fn from(entry: Entry) -> Self {
272 Self { path: entry.path.into(), directory: entry.directory }
273 }
274}
275
276#[cfg(target_os = "fuchsia")]
277impl From<Entry> for process_builder::NamespaceEntry {
278 fn from(entry: Entry) -> Self {
279 Self { path: entry.path.into(), directory: entry.directory }
280 }
281}
282
283#[derive(Debug, Clone, Error)]
284pub enum EntryError {
285 #[error("path is not set")]
286 MissingPath,
287
288 #[error("directory is not set")]
289 MissingDirectory,
290
291 #[error("entry type is not supported (must be directory or dictionary")]
292 UnsupportedType,
293
294 #[error("path is invalid for a namespace entry: `{0}`")]
295 InvalidPath(#[from] cm_types::ParseError),
296}
297
298impl TryFrom<fcrunner::ComponentNamespaceEntry> for Entry {
299 type Error = EntryError;
300
301 fn try_from(entry: fcrunner::ComponentNamespaceEntry) -> Result<Self, Self::Error> {
302 Ok(Self {
303 path: entry.path.ok_or(EntryError::MissingPath)?.parse()?,
304 directory: entry.directory.ok_or(EntryError::MissingDirectory)?,
305 })
306 }
307}
308
309impl TryFrom<fcomponent::NamespaceEntry> for Entry {
310 type Error = EntryError;
311
312 fn try_from(entry: fcomponent::NamespaceEntry) -> Result<Self, Self::Error> {
313 Ok(Self {
314 path: entry.path.ok_or(EntryError::MissingPath)?.parse()?,
315 directory: entry.directory.ok_or(EntryError::MissingDirectory)?,
316 })
317 }
318}
319
320impl TryFrom<fprocess::NameInfo> for Entry {
321 type Error = EntryError;
322
323 fn try_from(entry: fprocess::NameInfo) -> Result<Self, Self::Error> {
324 Ok(Self { path: entry.path.parse()?, directory: entry.directory })
325 }
326}
327
328#[cfg(target_os = "fuchsia")]
329impl TryFrom<process_builder::NamespaceEntry> for Entry {
330 type Error = EntryError;
331
332 fn try_from(entry: process_builder::NamespaceEntry) -> Result<Self, Self::Error> {
333 Ok(Self { path: entry.path.try_into()?, directory: entry.directory })
334 }
335}
336
337#[cfg(test)]
338mod tests {
339 use super::*;
340 use assert_matches::assert_matches;
341 use fidl::endpoints::Proxy as _;
342 use fuchsia_async as fasync;
343 use zx::{AsHandleRef, Peered};
344
345 fn ns_path(str: &str) -> NamespacePath {
346 str.parse().unwrap()
347 }
348
349 #[test]
350 fn test_try_from_namespace() {
351 {
352 let (client_end_1, _) = fidl::endpoints::create_endpoints();
353 let (client_end_2, _) = fidl::endpoints::create_endpoints();
354 let entries = vec![
355 Entry { path: ns_path("/foo"), directory: client_end_1 },
356 Entry { path: ns_path("/foo/bar"), directory: client_end_2 },
357 ];
358 assert_matches!(
359 Namespace::try_from(entries),
360 Err(NamespaceError::Shadow(path)) if path.to_string() == "/foo/bar"
361 );
362 }
363 {
364 let (client_end_1, _) = fidl::endpoints::create_endpoints();
365 let (client_end_2, _) = fidl::endpoints::create_endpoints();
366 let entries = vec![
367 Entry { path: ns_path("/foo"), directory: client_end_1 },
368 Entry { path: ns_path("/foo"), directory: client_end_2 },
369 ];
370 assert_matches!(
371 Namespace::try_from(entries),
372 Err(NamespaceError::Duplicate(path)) if path.to_string() == "/foo"
373 );
374 }
375 }
376
377 #[cfg(target_os = "fuchsia")]
378 #[fasync::run_singlethreaded(test)]
379 async fn test_clone() {
380 use vfs::file::vmo::read_only;
381
382 let dir = vfs::pseudo_directory! {
384 "foo" => vfs::pseudo_directory! {
385 "bar" => read_only(b"Fuchsia"),
386 },
387 };
388 let client_end = vfs::directory::serve_read_only(dir).into_client_end().unwrap();
389
390 let mut namespace = Namespace::new();
392 namespace.add(&ns_path("/data"), client_end).unwrap();
393
394 let namespace_clone = namespace.clone();
396
397 let mut entries = namespace.flatten();
398 let mut entries_clone = namespace_clone.flatten();
399
400 async fn verify(entry: Entry) {
401 assert_eq!(entry.path.to_string(), "/data");
402 let dir = entry.directory.into_proxy();
403 let file = fuchsia_fs::directory::open_file(&dir, "foo/bar", fio::PERM_READABLE)
404 .await
405 .unwrap();
406 let content = fuchsia_fs::file::read(&file).await.unwrap();
407 assert_eq!(content, b"Fuchsia");
408 }
409
410 verify(entries.remove(0)).await;
411 verify(entries_clone.remove(0)).await;
412 }
413
414 #[test]
415 fn test_flatten() {
416 let mut namespace = Namespace::new();
417 let (client_end, server_end) = fidl::endpoints::create_endpoints();
418 namespace.add(&ns_path("/svc"), client_end).unwrap();
419 let mut entries = namespace.flatten();
420 assert_eq!(entries[0].path.to_string(), "/svc");
421 entries
422 .remove(0)
423 .directory
424 .into_channel()
425 .signal_peer(zx::Signals::empty(), zx::Signals::USER_0)
426 .unwrap();
427 server_end
428 .as_handle_ref()
429 .wait_one(zx::Signals::USER_0, zx::MonotonicInstant::INFINITE)
430 .unwrap();
431 }
432
433 #[test]
434 fn test_remove() {
435 let mut namespace = Namespace::new();
436 let (client_end, server_end) = fidl::endpoints::create_endpoints();
437 namespace.add(&ns_path("/svc"), client_end).unwrap();
438 let client_end = namespace.remove(&ns_path("/svc")).unwrap();
439 let entries = namespace.flatten();
440 assert!(entries.is_empty());
441 client_end.into_channel().signal_peer(zx::Signals::empty(), zx::Signals::USER_0).unwrap();
442 server_end
443 .as_handle_ref()
444 .wait_one(zx::Signals::USER_0, zx::MonotonicInstant::INFINITE)
445 .unwrap();
446 }
447}