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