1use cm_types::{NamespacePath, Path};
5use fidl::endpoints::ClientEnd;
6use fidl_fuchsia_io as fio;
7use futures::channel::mpsc::{unbounded, UnboundedSender};
8use namespace::{Entry as NamespaceEntry, EntryError, Namespace, NamespaceError, Tree};
9use sandbox::{Capability, Dict, Directory, RemotableCapability};
10use thiserror::Error;
11use vfs::directory::entry::serve_directory;
12use vfs::execution_scope::ExecutionScope;
13
14pub struct NamespaceBuilder {
16 entries: Tree<Capability>,
18
19 not_found: UnboundedSender<String>,
21
22 namespace_scope: ExecutionScope,
26}
27
28#[derive(Error, Debug, Clone)]
29pub enum BuildNamespaceError {
30 #[error(transparent)]
31 NamespaceError(#[from] NamespaceError),
32
33 #[error(
34 "while installing capabilities within the namespace entry `{path}`, \
35 failed to convert the namespace entry to Directory: {err}"
36 )]
37 Conversion {
38 path: NamespacePath,
39 #[source]
40 err: sandbox::ConversionError,
41 },
42
43 #[error("unable to serve `{path}` after converting to directory: {err}")]
44 Serve {
45 path: NamespacePath,
46 #[source]
47 err: fidl::Status,
48 },
49}
50
51impl NamespaceBuilder {
52 pub fn new(namespace_scope: ExecutionScope, not_found: UnboundedSender<String>) -> Self {
53 return NamespaceBuilder { entries: Default::default(), not_found, namespace_scope };
54 }
55
56 pub fn add_object(
59 self: &mut Self,
60 cap: Capability,
61 path: &Path,
62 ) -> Result<(), BuildNamespaceError> {
63 let dirname = path.parent();
64
65 let any = match self.entries.get_mut(&dirname) {
67 Some(dir) => dir,
68 None => {
69 let dict = self.make_dict_with_not_found_logging(dirname.to_string());
70 self.entries.add(&dirname, Capability::Dictionary(dict))?
71 }
72 };
73
74 let dict = match any {
77 Capability::Dictionary(d) => d,
78 _ => Err(NamespaceError::Duplicate(path.clone().into()))?,
79 };
80
81 dict.insert(path.basename().clone(), cap)
83 .map_err(|_| NamespaceError::Duplicate(path.clone().into()).into())
84 }
85
86 pub fn add_entry(
90 self: &mut Self,
91 cap: Capability,
92 path: &NamespacePath,
93 ) -> Result<(), BuildNamespaceError> {
94 match &cap {
95 Capability::Directory(_)
96 | Capability::Dictionary(_)
97 | Capability::DirEntry(_)
98 | Capability::DirConnector(_) => {}
99 _ => return Err(NamespaceError::EntryError(EntryError::UnsupportedType).into()),
100 }
101 self.entries.add(path, cap)?;
102 Ok(())
103 }
104
105 pub fn serve(self: Self) -> Result<Namespace, BuildNamespaceError> {
106 let mut entries = vec![];
107 for (path, cap) in self.entries.flatten() {
108 let directory = match cap {
109 Capability::Directory(d) => d,
110 Capability::DirConnector(c) => {
111 let (directory, server) =
112 fidl::endpoints::create_endpoints::<fio::DirectoryMarker>();
113 let _ = c.send(server);
114 Directory::new(directory)
115 }
116 cap @ Capability::Dictionary(_) => {
117 let entry =
118 cap.try_into_directory_entry(self.namespace_scope.clone()).map_err(
119 |err| BuildNamespaceError::Conversion { path: path.clone(), err },
120 )?;
121 if entry.entry_info().type_() != fio::DirentType::Directory {
122 return Err(BuildNamespaceError::Conversion {
123 path: path.clone(),
124 err: sandbox::ConversionError::NotSupported,
125 });
126 }
127 sandbox::Directory::new(
128 serve_directory(
129 entry,
130 &self.namespace_scope,
131 fio::Flags::PROTOCOL_DIRECTORY
132 | fio::PERM_READABLE
133 | fio::Flags::PERM_INHERIT_WRITE
134 | fio::Flags::PERM_INHERIT_EXECUTE,
135 )
136 .map_err(|err| BuildNamespaceError::Serve { path: path.clone(), err })?,
137 )
138 }
139 _ => return Err(NamespaceError::EntryError(EntryError::UnsupportedType).into()),
140 };
141 let client_end: ClientEnd<fio::DirectoryMarker> = directory.into();
142 entries.push(NamespaceEntry { path, directory: client_end.into() })
143 }
144 let ns = entries.try_into()?;
145 Ok(ns)
146 }
147
148 fn make_dict_with_not_found_logging(&self, root_path: String) -> Dict {
149 let not_found = self.not_found.clone();
150 let new_dict = Dict::new_with_not_found(move |key| {
151 let requested_path = format!("{}/{}", root_path, key);
152 let _ = not_found.unbounded_send(requested_path);
155 });
156 new_dict
157 }
158}
159
160pub fn ignore_not_found() -> UnboundedSender<String> {
162 let (sender, _receiver) = unbounded();
163 sender
164}
165
166#[cfg(test)]
167mod tests {
168 use super::*;
169 use crate::test_util::multishot;
170 use anyhow::Result;
171 use assert_matches::assert_matches;
172 use fidl::endpoints::{self, Proxy, ServerEnd};
173 use fidl::Peered;
174 use fuchsia_fs::directory::DirEntry;
175 use futures::channel::mpsc;
176 use futures::{StreamExt, TryStreamExt};
177 use sandbox::Directory;
178 use std::sync::Arc;
179 use test_case::test_case;
180 use vfs::directory::entry::{DirectoryEntry, EntryInfo, GetEntryInfo, OpenRequest};
181 use vfs::remote::RemoteLike;
182 use vfs::{path, pseudo_directory, ObjectRequestRef};
183 use zx::AsHandleRef;
184 use {fidl_fuchsia_io as fio, fuchsia_async as fasync};
185
186 fn connector_cap() -> Capability {
187 let (sender, _receiver) = multishot();
188 Capability::Connector(sender)
189 }
190
191 fn directory_cap() -> Capability {
192 let (client, _server) = endpoints::create_endpoints();
193 Capability::Directory(Directory::new(client))
194 }
195
196 fn ns_path(str: &str) -> NamespacePath {
197 str.parse().unwrap()
198 }
199
200 fn path(str: &str) -> Path {
201 str.parse().unwrap()
202 }
203
204 fn parents_valid(paths: Vec<&str>) -> Result<(), BuildNamespaceError> {
205 let scope = ExecutionScope::new();
206 let mut shadow = NamespaceBuilder::new(scope, ignore_not_found());
207 for p in paths {
208 shadow.add_object(connector_cap(), &path(p))?;
209 }
210 Ok(())
211 }
212
213 #[fuchsia::test]
214 async fn test_shadow() {
215 assert_matches!(parents_valid(vec!["/svc/foo/bar/Something", "/svc/Something"]), Err(_));
216 assert_matches!(parents_valid(vec!["/svc/Something", "/svc/foo/bar/Something"]), Err(_));
217 assert_matches!(parents_valid(vec!["/svc/Something", "/foo"]), Err(_));
218
219 assert_matches!(parents_valid(vec!["/foo/bar/a", "/foo/bar/b", "/foo/bar/c"]), Ok(()));
220 assert_matches!(parents_valid(vec!["/a", "/b", "/c"]), Ok(()));
221
222 let scope = ExecutionScope::new();
223 let mut shadow = NamespaceBuilder::new(scope, ignore_not_found());
224 shadow.add_object(connector_cap(), &path("/svc/foo")).unwrap();
225 assert_matches!(shadow.add_object(connector_cap(), &path("/svc/foo/bar")), Err(_));
226
227 let scope = ExecutionScope::new();
228 let mut not_shadow = NamespaceBuilder::new(scope, ignore_not_found());
229 not_shadow.add_object(connector_cap(), &path("/svc/foo")).unwrap();
230 assert_matches!(not_shadow.add_entry(directory_cap(), &ns_path("/svc2")), Ok(_));
231 }
232
233 #[fuchsia::test]
234 async fn test_duplicate_object() {
235 let scope = ExecutionScope::new();
236 let mut namespace = NamespaceBuilder::new(scope, ignore_not_found());
237 namespace.add_object(connector_cap(), &path("/svc/a")).expect("");
238 assert_matches!(
240 namespace.add_object(connector_cap(), &path("/svc/a")),
241 Err(BuildNamespaceError::NamespaceError(NamespaceError::Duplicate(path)))
242 if path.to_string() == "/svc/a"
243 );
244 }
245
246 #[fuchsia::test]
247 async fn test_duplicate_entry() {
248 let scope = ExecutionScope::new();
249 let mut namespace = NamespaceBuilder::new(scope, ignore_not_found());
250 namespace.add_entry(directory_cap(), &ns_path("/svc/a")).expect("");
251 assert_matches!(
253 namespace.add_entry(directory_cap(), &ns_path("/svc/a")),
254 Err(BuildNamespaceError::NamespaceError(NamespaceError::Duplicate(path)))
255 if path.to_string() == "/svc/a"
256 );
257 }
258
259 #[fuchsia::test]
260 async fn test_duplicate_object_and_entry() {
261 let scope = ExecutionScope::new();
262 let mut namespace = NamespaceBuilder::new(scope, ignore_not_found());
263 namespace.add_object(connector_cap(), &path("/svc/a")).expect("");
264 assert_matches!(
265 namespace.add_entry(directory_cap(), &ns_path("/svc/a")),
266 Err(BuildNamespaceError::NamespaceError(NamespaceError::Shadow(path)))
267 if path.to_string() == "/svc/a"
268 );
269 }
270
271 #[fuchsia::test]
274 async fn test_duplicate_entry_at_object_parent() {
275 let scope = ExecutionScope::new();
276 let mut namespace = NamespaceBuilder::new(scope, ignore_not_found());
277 namespace.add_object(connector_cap(), &path("/foo/bar")).expect("");
278 assert_matches!(
279 namespace.add_entry(directory_cap(), &ns_path("/foo")),
280 Err(BuildNamespaceError::NamespaceError(NamespaceError::Duplicate(path)))
281 if path.to_string() == "/foo"
282 );
283 }
284
285 #[fuchsia::test]
289 async fn test_duplicate_object_parent_at_entry() {
290 let scope = ExecutionScope::new();
291 let mut namespace = NamespaceBuilder::new(scope, ignore_not_found());
292 namespace.add_entry(directory_cap(), &ns_path("/foo")).expect("");
293 assert_matches!(
294 namespace.add_object(connector_cap(), &path("/foo/bar")),
295 Err(BuildNamespaceError::NamespaceError(NamespaceError::Duplicate(path)))
296 if path.to_string() == "/foo/bar"
297 );
298 }
299
300 #[fuchsia::test]
301 async fn test_empty() {
302 let scope = ExecutionScope::new();
303 let namespace = NamespaceBuilder::new(scope, ignore_not_found());
304 let ns = namespace.serve().unwrap();
305 assert_eq!(ns.flatten().len(), 0);
306 }
307
308 #[fuchsia::test]
309 async fn test_one_connector_end_to_end() {
310 let (sender, receiver) = multishot();
311
312 let scope = ExecutionScope::new();
313 let mut namespace = NamespaceBuilder::new(scope, ignore_not_found());
314 namespace.add_object(sender.into(), &path("/svc/a")).unwrap();
315 let ns = namespace.serve().unwrap();
316
317 let mut ns = ns.flatten();
318 assert_eq!(ns.len(), 1);
319 assert_eq!(ns[0].path.to_string(), "/svc");
320
321 let dir = ns.pop().unwrap().directory.into_proxy();
323 let entries = fuchsia_fs::directory::readdir(&dir).await.unwrap();
324 assert_eq!(
325 entries,
326 vec![DirEntry { name: "a".to_string(), kind: fio::DirentType::Service }]
327 );
328
329 let (client_end, server_end) = zx::Channel::create();
331 fdio::service_connect_at(&dir.into_channel().unwrap().into_zx_channel(), "a", server_end)
332 .unwrap();
333
334 let server_end: zx::Channel = receiver.receive().await.unwrap().channel.into();
336 client_end.signal_peer(zx::Signals::empty(), zx::Signals::USER_0).unwrap();
337 server_end.wait_handle(zx::Signals::USER_0, zx::MonotonicInstant::INFINITE_PAST).unwrap();
338 }
339
340 #[fuchsia::test]
341 async fn test_two_connectors_in_same_namespace_entry() {
342 let scope = ExecutionScope::new();
343 let mut namespace = NamespaceBuilder::new(scope, ignore_not_found());
344 namespace.add_object(connector_cap(), &path("/svc/a")).unwrap();
345 namespace.add_object(connector_cap(), &path("/svc/b")).unwrap();
346 let ns = namespace.serve().unwrap();
347
348 let mut ns = ns.flatten();
349 assert_eq!(ns.len(), 1);
350 assert_eq!(ns[0].path.to_string(), "/svc");
351
352 let dir = ns.pop().unwrap().directory.into_proxy();
354 let mut entries = fuchsia_fs::directory::readdir(&dir).await.unwrap();
355 let mut expectation = vec![
356 DirEntry { name: "a".to_string(), kind: fio::DirentType::Service },
357 DirEntry { name: "b".to_string(), kind: fio::DirentType::Service },
358 ];
359 entries.sort();
360 expectation.sort();
361 assert_eq!(entries, expectation);
362
363 drop(dir);
364 }
365
366 #[fuchsia::test]
367 async fn test_two_connectors_in_different_namespace_entries() {
368 let scope = ExecutionScope::new();
369 let mut namespace = NamespaceBuilder::new(scope, ignore_not_found());
370 namespace.add_object(connector_cap(), &path("/svc1/a")).unwrap();
371 namespace.add_object(connector_cap(), &path("/svc2/b")).unwrap();
372 let ns = namespace.serve().unwrap();
373
374 let ns = ns.flatten();
375 assert_eq!(ns.len(), 2);
376 let (mut svc1, ns): (Vec<_>, Vec<_>) =
377 ns.into_iter().partition(|e| e.path.to_string() == "/svc1");
378 let (mut svc2, _ns): (Vec<_>, Vec<_>) =
379 ns.into_iter().partition(|e| e.path.to_string() == "/svc2");
380
381 {
383 let dir = svc1.pop().unwrap().directory.into_proxy();
384 assert_eq!(
385 fuchsia_fs::directory::readdir(&dir).await.unwrap(),
386 vec![DirEntry { name: "a".to_string(), kind: fio::DirentType::Service },]
387 );
388 }
389 {
390 let dir = svc2.pop().unwrap().directory.into_proxy();
391 assert_eq!(
392 fuchsia_fs::directory::readdir(&dir).await.unwrap(),
393 vec![DirEntry { name: "b".to_string(), kind: fio::DirentType::Service },]
394 );
395 }
396
397 drop(svc1);
398 drop(svc2);
399 }
400
401 #[fuchsia::test]
402 async fn test_not_found() {
403 let (not_found_sender, mut not_found_receiver) = unbounded();
404 let scope = ExecutionScope::new();
405 let mut namespace = NamespaceBuilder::new(scope, not_found_sender);
406 namespace.add_object(connector_cap(), &path("/svc/a")).unwrap();
407 let ns = namespace.serve().unwrap();
408
409 let mut ns = ns.flatten();
410 assert_eq!(ns.len(), 1);
411 assert_eq!(ns[0].path.to_string(), "/svc");
412
413 let dir = ns.pop().unwrap().directory.into_proxy();
414 let (client_end, server_end) = zx::Channel::create();
415 let _ = fdio::service_connect_at(
416 &dir.into_channel().unwrap().into_zx_channel(),
417 "non_existent",
418 server_end,
419 );
420
421 fasync::Channel::from_channel(client_end).on_closed().await.unwrap();
423
424 assert_eq!(not_found_receiver.next().await, Some("/svc/non_existent".to_string()));
426
427 drop(ns);
428 }
429
430 #[fuchsia::test]
431 async fn test_not_directory() {
432 let (not_found_sender, _) = unbounded();
433 let scope = ExecutionScope::new();
434 let mut namespace = NamespaceBuilder::new(scope, not_found_sender);
435 let (_, sender) = sandbox::Connector::new();
436 assert_matches!(
437 namespace.add_entry(sender.into(), &ns_path("/a")),
438 Err(BuildNamespaceError::NamespaceError(NamespaceError::EntryError(
439 EntryError::UnsupportedType
440 )))
441 );
442 }
443
444 #[test_case(fio::PERM_READABLE)]
445 #[test_case(fio::PERM_READABLE | fio::PERM_EXECUTABLE)]
446 #[test_case(fio::PERM_READABLE | fio::PERM_WRITABLE)]
447 #[test_case(fio::PERM_READABLE | fio::Flags::PERM_INHERIT_WRITE | fio::Flags::PERM_INHERIT_EXECUTE)]
448 #[fuchsia::test]
449 async fn test_directory_rights(rights: fio::Flags) {
450 let (open_tx, mut open_rx) = mpsc::channel::<()>(1);
451
452 struct MockDir {
453 tx: mpsc::Sender<()>,
454 rights: fio::Flags,
455 }
456 impl DirectoryEntry for MockDir {
457 fn open_entry(self: Arc<Self>, request: OpenRequest<'_>) -> Result<(), zx::Status> {
458 request.open_remote(self)
459 }
460 }
461 impl GetEntryInfo for MockDir {
462 fn entry_info(&self) -> EntryInfo {
463 EntryInfo::new(fio::INO_UNKNOWN, fio::DirentType::Directory)
464 }
465 }
466 impl RemoteLike for MockDir {
467 fn open(
468 self: Arc<Self>,
469 _scope: ExecutionScope,
470 _flags: fio::OpenFlags,
471 _relative_path: path::Path,
472 _server_end: ServerEnd<fio::NodeMarker>,
473 ) {
474 panic!("fuchsia.io/Directory.DeprecatedOpen should not be called from these tests")
475 }
476
477 fn open3(
478 self: Arc<Self>,
479 _scope: ExecutionScope,
480 relative_path: path::Path,
481 flags: fio::Flags,
482 _object_request: ObjectRequestRef<'_>,
483 ) -> Result<(), zx::Status> {
484 assert_eq!(relative_path.into_string(), "");
485 assert_eq!(flags, fio::Flags::PROTOCOL_DIRECTORY | self.rights);
486 self.tx.clone().try_send(()).unwrap();
487 Ok(())
488 }
489 }
490
491 let mock = Arc::new(MockDir { tx: open_tx, rights });
492
493 let fs = pseudo_directory! {
494 "foo" => mock,
495 };
496 let dir = Directory::from(vfs::directory::serve(fs, rights).into_client_end().unwrap());
497
498 let scope = ExecutionScope::new();
499 let mut namespace = NamespaceBuilder::new(scope, ignore_not_found());
500 namespace.add_entry(dir.into(), &ns_path("/dir")).unwrap();
501 let mut ns = namespace.serve().unwrap();
502 let dir_proxy = ns.remove(&"/dir".parse().unwrap()).unwrap();
503 let dir_proxy = dir_proxy.into_proxy();
504 let (_, server_end) = endpoints::create_endpoints::<fio::NodeMarker>();
505 dir_proxy
506 .open(
507 "foo",
508 fio::Flags::PROTOCOL_DIRECTORY | rights,
509 &fio::Options::default(),
510 server_end.into_channel(),
511 )
512 .unwrap();
513
514 open_rx.next().await.unwrap();
516 }
517
518 #[fuchsia::test]
519 async fn test_directory_non_executable() {
520 let (open_tx, mut open_rx) = mpsc::channel::<()>(1);
521
522 struct MockDir(mpsc::Sender<()>);
523 impl DirectoryEntry for MockDir {
524 fn open_entry(self: Arc<Self>, request: OpenRequest<'_>) -> Result<(), zx::Status> {
525 request.open_remote(self)
526 }
527 }
528 impl GetEntryInfo for MockDir {
529 fn entry_info(&self) -> EntryInfo {
530 EntryInfo::new(fio::INO_UNKNOWN, fio::DirentType::Directory)
531 }
532 }
533 impl RemoteLike for MockDir {
534 fn open(
535 self: Arc<Self>,
536 _scope: ExecutionScope,
537 _flags: fio::OpenFlags,
538 _relative_path: path::Path,
539 _server_end: ServerEnd<fio::NodeMarker>,
540 ) {
541 panic!("fuchsia.io/Directory.DeprecatedOpen should not be called from these tests")
542 }
543
544 fn open3(
545 self: Arc<Self>,
546 _scope: ExecutionScope,
547 relative_path: path::Path,
548 flags: fio::Flags,
549 _object_request: ObjectRequestRef<'_>,
550 ) -> Result<(), zx::Status> {
551 assert_eq!(relative_path.into_string(), "");
552 assert_eq!(flags, fio::Flags::PROTOCOL_DIRECTORY | fio::PERM_READABLE);
553 self.0.clone().try_send(()).unwrap();
554 Ok(())
555 }
556 }
557
558 let mock = Arc::new(MockDir(open_tx));
559
560 let fs = pseudo_directory! {
561 "foo" => mock,
562 };
563 let dir = Directory::from(
564 vfs::directory::serve(fs, fio::PERM_READABLE).into_client_end().unwrap(),
565 );
566
567 let scope = ExecutionScope::new();
568 let mut namespace = NamespaceBuilder::new(scope, ignore_not_found());
569 namespace.add_entry(dir.into(), &ns_path("/dir")).unwrap();
570 let mut ns = namespace.serve().unwrap();
571 let dir_proxy = ns.remove(&"/dir".parse().unwrap()).unwrap();
572 let dir_proxy = dir_proxy.into_proxy();
573
574 let (node, server_end) = endpoints::create_endpoints::<fio::NodeMarker>();
576 dir_proxy
577 .open(
578 "foo",
579 fio::Flags::PROTOCOL_DIRECTORY | fio::PERM_READABLE | fio::PERM_EXECUTABLE,
580 &fio::Options::default(),
581 server_end.into_channel(),
582 )
583 .unwrap();
584 let node = node.into_proxy();
585 let mut node = node.take_event_stream();
586 assert_matches!(
587 node.try_next().await,
588 Err(fidl::Error::ClientChannelClosed { status: zx::Status::ACCESS_DENIED, .. })
589 );
590
591 let (_, server_end) = endpoints::create_endpoints::<fio::NodeMarker>();
593 dir_proxy
594 .open(
595 "foo",
596 fio::Flags::PROTOCOL_DIRECTORY | fio::PERM_READABLE,
597 &fio::Options::default(),
598 server_end.into_channel(),
599 )
600 .unwrap();
601
602 open_rx.next().await.unwrap();
604 }
605}