serve_processargs/
lib.rs

1// Copyright 2023 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4use fuchsia_runtime::{HandleInfo, HandleType};
5use futures::channel::mpsc::UnboundedSender;
6use process_builder::StartupHandle;
7use processargs::ProcessArgs;
8use sandbox::{Capability, Dict, DictKey};
9use std::collections::HashMap;
10use std::iter::once;
11use thiserror::Error;
12use vfs::execution_scope::ExecutionScope;
13
14mod namespace;
15
16pub use crate::namespace::{ignore_not_found, BuildNamespaceError, NamespaceBuilder};
17
18/// How to deliver a particular capability from a dict to an Elf process. Broadly speaking,
19/// one could either deliver a capability using namespace entries, or using numbered handles.
20pub enum Delivery {
21    /// Install the capability as a `fuchsia.io` object, within some parent directory serviced by
22    /// the framework, and discoverable at a path such as "/svc/foo/bar".
23    ///
24    /// As a result, a namespace entry will be created in the resulting processargs, corresponding
25    /// to the parent directory, e.g. "/svc/foo".
26    ///
27    /// For example, installing a `sandbox::Sender` at "/svc/fuchsia.examples.Echo" will
28    /// cause the framework to spin up a `fuchsia.io/Directory` implementation backing "/svc",
29    /// containing a filesystem object named "fuchsia.examples.Echo".
30    ///
31    /// Not all capability types are installable as `fuchsia.io` objects. A one-shot handle is not
32    /// supported because `fuchsia.io` does not have a protocol for delivering one-shot handles.
33    /// Use [Delivery::Handle] for those.
34    NamespacedObject(cm_types::Path),
35
36    /// Install the capability as a `fuchsia.io` object by creating a namespace entry at the
37    /// provided path. The difference between [Delivery::NamespacedObject] and
38    /// [Delivery::NamespaceEntry] is that the former will create a namespace entry at the parent
39    /// directory.
40    ///
41    /// For example, installing a `sandbox::Directory` at "/data" will result in a namespace entry
42    /// at "/data". A request will be sent to the capability when the user writes to the
43    /// namespace entry.
44    NamespaceEntry(cm_types::Path),
45
46    /// Installs the Zircon handle representation of this capability at the processargs slot
47    /// described by [HandleInfo].
48    ///
49    /// The following handle types are disallowed because they will collide with the implementation
50    /// of incoming namespace and outgoing directory:
51    ///
52    /// - [HandleType::NamespaceDirectory]
53    /// - [HandleType::DirectoryRequest]
54    ///
55    Handle(HandleInfo),
56}
57
58pub enum DeliveryMapEntry {
59    Delivery(Delivery),
60    Dict(DeliveryMap),
61}
62
63/// A nested dictionary mapping capability names to delivery method.
64///
65/// Each entry in a [Dict] should have a corresponding entry here describing how the
66/// capability will be delivered to the process. If a [Dict] has a nested [Dict], then there
67/// will be a corresponding nested [DeliveryMapEntry::Dict] containing the [DeliveryMap] for the
68/// capabilities in the nested [Dict].
69pub type DeliveryMap = HashMap<DictKey, DeliveryMapEntry>;
70
71/// Visits `dict` and installs its capabilities into appropriate locations in the
72/// `processargs`, as determined by a `delivery_map`.
73///
74/// If the process opens non-existent paths within one of the namespace entries served
75/// by the framework, that path will be sent down `not_found`. Callers should either monitor
76/// the stream, or drop the receiver, to prevent unbounded buffering.
77///
78/// On success, returns a future that services the namespace.
79// TODO: This is only used by tests. Is there a reason to keep it?
80#[allow(unused)]
81async fn add_to_processargs(
82    scope: ExecutionScope,
83    dict: Dict,
84    processargs: &mut ProcessArgs,
85    delivery_map: &DeliveryMap,
86    not_found: UnboundedSender<String>,
87) -> Result<(), DeliveryError> {
88    let mut namespace = NamespaceBuilder::new(scope, not_found);
89
90    // Iterate over the delivery map.
91    // Take entries away from dict and install them accordingly.
92    visit_map(delivery_map, dict, &mut |cap: Capability, delivery: &Delivery| match delivery {
93        Delivery::NamespacedObject(path) => {
94            namespace.add_object(cap, &path).map_err(DeliveryError::NamespaceError)
95        }
96        Delivery::NamespaceEntry(path) => {
97            namespace.add_entry(cap, &path.clone().into()).map_err(DeliveryError::NamespaceError)
98        }
99        Delivery::Handle(info) => {
100            processargs.add_handles(once(translate_handle(cap, info)?));
101            Ok(())
102        }
103    })?;
104
105    let namespace = namespace.serve().map_err(DeliveryError::NamespaceError)?;
106    let namespace: Vec<_> = namespace.into();
107    processargs.namespace_entries.extend(namespace);
108
109    Ok(())
110}
111
112#[derive(Error, Debug)]
113pub enum DeliveryError {
114    #[error("the key `{0}` is not found in the dict")]
115    NotInDict(DictKey),
116
117    #[error("wrong type: the delivery map expected `{0}` to be a nested Dict in the dict")]
118    NotADict(DictKey),
119
120    #[error("unused capabilities in dict: `{0:?}`")]
121    UnusedCapabilities(Vec<DictKey>),
122
123    #[error("handle type `{0:?}` is not allowed to be installed into processargs")]
124    UnsupportedHandleType(HandleType),
125
126    #[error("namespace configuration error: `{0}`")]
127    NamespaceError(namespace::BuildNamespaceError),
128
129    #[error("capability `{0:?}` is not allowed to be installed into processargs")]
130    UnsupportedCapability(Capability),
131}
132
133fn translate_handle(cap: Capability, info: &HandleInfo) -> Result<StartupHandle, DeliveryError> {
134    validate_handle_type(info.handle_type())?;
135
136    let handle = match cap {
137        Capability::Handle(h) => h,
138        c => return Err(DeliveryError::UnsupportedCapability(c)),
139    };
140    let handle = handle.into();
141
142    Ok(StartupHandle { handle, info: *info })
143}
144
145fn visit_map(
146    map: &DeliveryMap,
147    dict: Dict,
148    f: &mut impl FnMut(Capability, &Delivery) -> Result<(), DeliveryError>,
149) -> Result<(), DeliveryError> {
150    for (key, entry) in map {
151        match dict.remove(key) {
152            Some(value) => match entry {
153                DeliveryMapEntry::Delivery(delivery) => f(value, delivery)?,
154                DeliveryMapEntry::Dict(sub_map) => {
155                    let nested_dict: Dict = match value {
156                        Capability::Dictionary(d) => d,
157                        _ => return Err(DeliveryError::NotADict(key.to_owned())),
158                    };
159                    visit_map(sub_map, nested_dict, f)?;
160                }
161            },
162            None => return Err(DeliveryError::NotInDict(key.to_owned())),
163        }
164    }
165    let keys: Vec<_> = dict.keys().collect();
166    if !keys.is_empty() {
167        return Err(DeliveryError::UnusedCapabilities(keys));
168    }
169    Ok(())
170}
171
172fn validate_handle_type(handle_type: HandleType) -> Result<(), DeliveryError> {
173    match handle_type {
174        HandleType::NamespaceDirectory | HandleType::DirectoryRequest => {
175            Err(DeliveryError::UnsupportedHandleType(handle_type))
176        }
177        _ => Ok(()),
178    }
179}
180
181#[cfg(test)]
182mod test_util {
183    use fidl::endpoints::ServerEnd;
184    use fidl_fuchsia_io as fio;
185    use sandbox::{Connector, Receiver};
186    use std::sync::Arc;
187    use vfs::directory::entry::{DirectoryEntry, EntryInfo, GetEntryInfo, OpenRequest};
188    use vfs::execution_scope::ExecutionScope;
189    use vfs::path::Path;
190    use vfs::remote::RemoteLike;
191    use vfs::ObjectRequestRef;
192
193    pub fn multishot() -> (Connector, Receiver) {
194        let (receiver, sender) = Connector::new();
195        (sender, receiver)
196    }
197
198    pub fn mock_dir() -> (Arc<impl DirectoryEntry>, async_channel::Receiver<(Path, zx::Channel)>) {
199        let (sender, receiver) = async_channel::unbounded::<(Path, zx::Channel)>();
200
201        struct Sender(async_channel::Sender<(Path, zx::Channel)>);
202
203        impl DirectoryEntry for Sender {
204            fn open_entry(self: Arc<Self>, request: OpenRequest<'_>) -> Result<(), zx::Status> {
205                request.open_remote(self)
206            }
207        }
208
209        impl GetEntryInfo for Sender {
210            fn entry_info(&self) -> EntryInfo {
211                EntryInfo::new(fio::INO_UNKNOWN, fio::DirentType::Directory)
212            }
213        }
214
215        impl RemoteLike for Sender {
216            fn open(
217                self: Arc<Self>,
218                _scope: ExecutionScope,
219                _flags: fio::OpenFlags,
220                _relative_path: Path,
221                _server_end: ServerEnd<fio::NodeMarker>,
222            ) {
223                panic!("fuchsia.io/Directory.DeprecatedOpen should not be called from these tests")
224            }
225
226            fn open3(
227                self: Arc<Self>,
228                scope: ExecutionScope,
229                relative_path: Path,
230                _flags: fio::Flags,
231                object_request: ObjectRequestRef<'_>,
232            ) -> Result<(), zx::Status> {
233                let object_request = object_request.take();
234                scope.spawn(async move {
235                    self.0.send((relative_path, object_request.into_channel())).await.unwrap();
236                });
237                Ok(())
238            }
239
240            fn lazy(&self, path: &Path) -> bool {
241                path.is_empty()
242            }
243        }
244
245        (Arc::new(Sender(sender)), receiver)
246    }
247}
248
249#[cfg(test)]
250mod tests {
251    use super::*;
252    use crate::namespace::ignore_not_found as ignore;
253    use anyhow::{anyhow, Result};
254    use assert_matches::assert_matches;
255    use fidl::endpoints::{Proxy, ServerEnd};
256    use fuchsia_fs::directory::DirEntry;
257    use futures::TryStreamExt;
258    use maplit::hashmap;
259    use sandbox::Handle;
260    use std::pin::pin;
261    use std::str::FromStr;
262    use test_util::{mock_dir, multishot};
263    use vfs::directory::entry::serve_directory;
264    use zx::{AsHandleRef, HandleBased, MonotonicInstant, Peered, Signals};
265    use {fidl_fuchsia_io as fio, fuchsia_async as fasync};
266
267    #[fuchsia::test]
268    async fn test_empty() -> Result<()> {
269        let mut processargs = ProcessArgs::new();
270        let dict = Dict::new();
271        let delivery_map = DeliveryMap::new();
272        let scope = ExecutionScope::new();
273        add_to_processargs(scope.clone(), dict, &mut processargs, &delivery_map, ignore()).await?;
274
275        assert_eq!(processargs.namespace_entries.len(), 0);
276        assert_eq!(processargs.handles.len(), 0);
277
278        drop(processargs);
279        scope.wait().await;
280        Ok(())
281    }
282
283    #[fuchsia::test]
284    async fn test_handle() -> Result<()> {
285        let (sock0, sock1) = zx::Socket::create_stream();
286
287        let mut processargs = ProcessArgs::new();
288        let dict = Dict::new();
289        dict.insert(
290            "stdin".parse().unwrap(),
291            Capability::Handle(Handle::from(sock0.into_handle().into_handle())),
292        )
293        .map_err(|e| anyhow!("{e:?}"))?;
294        let delivery_map = hashmap! {
295            "stdin".parse().unwrap() => DeliveryMapEntry::Delivery(
296                Delivery::Handle(HandleInfo::new(HandleType::FileDescriptor, 0))
297            )
298        };
299        let scope = ExecutionScope::new();
300        add_to_processargs(scope.clone(), dict, &mut processargs, &delivery_map, ignore()).await?;
301
302        assert_eq!(processargs.namespace_entries.len(), 0);
303        assert_eq!(processargs.handles.len(), 1);
304
305        assert_eq!(processargs.handles[0].info.handle_type(), HandleType::FileDescriptor);
306        assert_eq!(processargs.handles[0].info.arg(), 0);
307
308        // Test connectivity.
309        const PAYLOAD: &'static [u8] = b"Hello";
310        let handles = std::mem::take(&mut processargs.handles);
311        let sock0 = zx::Socket::from(handles.into_iter().next().unwrap().handle);
312        assert_eq!(sock0.write(PAYLOAD).unwrap(), 5);
313        let mut buf = [0u8; PAYLOAD.len() + 1];
314        assert_eq!(sock1.read(&mut buf[..]), Ok(PAYLOAD.len()));
315        assert_eq!(&buf[..PAYLOAD.len()], PAYLOAD);
316
317        drop(processargs);
318        scope.wait().await;
319        Ok(())
320    }
321
322    #[fuchsia::test]
323    async fn test_create_nested_dict() -> Result<()> {
324        let (sock0, _sock1) = zx::Socket::create_stream();
325
326        let mut processargs = ProcessArgs::new();
327
328        // Put a socket at "/handles/stdin". This implements a capability bundling pattern.
329        let handles = Dict::new();
330        handles
331            .insert("stdin".parse().unwrap(), Capability::Handle(Handle::from(sock0.into_handle())))
332            .map_err(|e| anyhow!("{e:?}"))?;
333        let dict = Dict::new();
334        dict.insert("handles".parse().unwrap(), Capability::Dictionary(handles))
335            .map_err(|e| anyhow!("{e:?}"))?;
336
337        let delivery_map = hashmap! {
338            "handles".parse().unwrap() => DeliveryMapEntry::Dict(hashmap! {
339                "stdin".parse().unwrap() => DeliveryMapEntry::Delivery(
340                    Delivery::Handle(HandleInfo::new(HandleType::FileDescriptor, 0))
341                )
342            })
343        };
344        let scope = ExecutionScope::new();
345        add_to_processargs(scope.clone(), dict, &mut processargs, &delivery_map, ignore()).await?;
346
347        assert_eq!(processargs.namespace_entries.len(), 0);
348        assert_eq!(processargs.handles.len(), 1);
349
350        assert_eq!(processargs.handles[0].info.handle_type(), HandleType::FileDescriptor);
351        assert_eq!(processargs.handles[0].info.arg(), 0);
352
353        drop(processargs);
354        scope.wait().await;
355        Ok(())
356    }
357
358    /// Test accessing capabilities from a Dict inside a Dict.
359    #[fuchsia::test]
360    async fn test_access_in_nested_dict() -> Result<()> {
361        let (ep0, ep1) = zx::EventPair::create();
362
363        let mut processargs = ProcessArgs::new();
364
365        let handles = Dict::new();
366        handles
367            .insert("stdin".parse().unwrap(), Capability::Handle(Handle::from(ep0.into_handle())))
368            .map_err(|e| anyhow!("{e:?}"))?;
369        let dict = Dict::new();
370        dict.insert("handles".parse().unwrap(), Capability::Dictionary(handles))
371            .map_err(|e| anyhow!("{e:?}"))?;
372
373        let delivery_map = hashmap! {
374            "handles".parse().unwrap() => DeliveryMapEntry::Dict(hashmap! {
375                "stdin".parse().unwrap() => DeliveryMapEntry::Delivery(
376                    Delivery::Handle(HandleInfo::new(HandleType::FileDescriptor, 0))
377                )
378            })
379        };
380        let scope = ExecutionScope::new();
381        add_to_processargs(scope.clone(), dict, &mut processargs, &delivery_map, ignore()).await?;
382
383        assert_eq!(processargs.namespace_entries.len(), 0);
384        assert_eq!(processargs.handles.len(), 1);
385
386        assert_eq!(processargs.handles[0].info.handle_type(), HandleType::FileDescriptor);
387        assert_eq!(processargs.handles[0].info.arg(), 0);
388
389        let ep0 = processargs.handles.pop().unwrap().handle;
390        ep1.signal_peer(Signals::NONE, Signals::USER_1).unwrap();
391        assert_eq!(
392            ep0.wait_handle(Signals::USER_1, MonotonicInstant::INFINITE).unwrap(),
393            Signals::USER_1
394        );
395
396        drop(ep0);
397        drop(processargs);
398        scope.wait().await;
399        Ok(())
400    }
401
402    #[fuchsia::test]
403    async fn test_wrong_dict_destructuring() {
404        let (sock0, _sock1) = zx::Socket::create_stream();
405
406        let mut processargs = ProcessArgs::new();
407
408        // The type of "/handles" is a socket capability but we try to open it as a dict and extract
409        // a "stdin" inside. This should fail.
410        let dict = Dict::new();
411        dict.insert(
412            "handles".parse().unwrap(),
413            Capability::Handle(Handle::from(sock0.into_handle())),
414        )
415        .expect("dict entry already exists");
416
417        let delivery_map = hashmap! {
418            "handles".parse().unwrap() => DeliveryMapEntry::Dict(hashmap! {
419                "stdin".parse().unwrap() => DeliveryMapEntry::Delivery(
420                    Delivery::Handle(HandleInfo::new(HandleType::FileDescriptor, 0))
421                )
422            })
423        };
424
425        assert_matches!(
426            add_to_processargs(ExecutionScope::new(), dict, &mut processargs, &delivery_map, ignore()).await.err().unwrap(),
427            DeliveryError::NotADict(name)
428            if name.as_str() == "handles"
429        );
430    }
431
432    #[fuchsia::test]
433    async fn test_handle_unused() {
434        let (sock0, _sock1) = zx::Socket::create_stream();
435
436        let mut processargs = ProcessArgs::new();
437        let dict = Dict::new();
438        dict.insert(
439            "stdin".parse().unwrap(),
440            Capability::Handle(Handle::from(sock0.into_handle())),
441        )
442        .expect("dict entry already exists");
443        let delivery_map = DeliveryMap::new();
444
445        assert_matches!(
446            add_to_processargs(ExecutionScope::new(), dict, &mut processargs, &delivery_map, ignore()).await.err().unwrap(),
447            DeliveryError::UnusedCapabilities(keys)
448            if keys == vec![DictKey::from_str("stdin").unwrap()]
449        );
450    }
451
452    #[fuchsia::test]
453    async fn test_handle_unsupported() {
454        let (sock0, _sock1) = zx::Socket::create_stream();
455
456        let mut processargs = ProcessArgs::new();
457        let dict = Dict::new();
458        dict.insert(
459            "stdin".parse().unwrap(),
460            Capability::Handle(Handle::from(sock0.into_handle())),
461        )
462        .expect("dict entry already exists");
463        let delivery_map = hashmap! {
464            "stdin".parse().unwrap() => DeliveryMapEntry::Delivery(
465                Delivery::Handle(HandleInfo::new(HandleType::DirectoryRequest, 0))
466            )
467        };
468
469        assert_matches!(
470            add_to_processargs(ExecutionScope::new(), dict, &mut processargs, &delivery_map, ignore()).await.err().unwrap(),
471            DeliveryError::UnsupportedHandleType(handle_type)
472            if handle_type == HandleType::DirectoryRequest
473        );
474    }
475
476    #[fuchsia::test]
477    async fn test_handle_not_found() {
478        let mut processargs = ProcessArgs::new();
479        let dict = Dict::new();
480        let delivery_map = hashmap! {
481            "stdin".parse().unwrap() => DeliveryMapEntry::Delivery(
482                Delivery::Handle(HandleInfo::new(HandleType::FileDescriptor, 0))
483            )
484        };
485
486        assert_matches!(
487            add_to_processargs(ExecutionScope::new(), dict, &mut processargs, &delivery_map, ignore()).await.err().unwrap(),
488            DeliveryError::NotInDict(name)
489            if name.as_str() == "stdin"
490        );
491    }
492
493    /// Two protocol capabilities in `/svc`. One of them has a receiver waiting for incoming
494    /// requests. The other is disconnected from the receiver, which should close all incoming
495    /// connections to that protocol.
496    #[fuchsia::test]
497    async fn test_namespace_object_end_to_end() -> Result<()> {
498        let (sender, receiver) = multishot();
499        let peer_closed_open = multishot().0;
500
501        let mut processargs = ProcessArgs::new();
502        let dict = {
503            let dict = Dict::new();
504            dict.insert("normal".parse().unwrap(), sender.into())
505                .expect("dict entry already exists");
506            dict.insert("closed".parse().unwrap(), peer_closed_open.into())
507                .expect("dict entry already exists");
508            dict
509        };
510        let delivery_map = hashmap! {
511            "normal".parse().unwrap() => DeliveryMapEntry::Delivery(
512                Delivery::NamespacedObject(cm_types::Path::from_str("/svc/fuchsia.Normal").unwrap())
513            ),
514            "closed".parse().unwrap() => DeliveryMapEntry::Delivery(
515                Delivery::NamespacedObject(cm_types::Path::from_str("/svc/fuchsia.Closed").unwrap())
516            )
517        };
518        let scope = ExecutionScope::new();
519        add_to_processargs(scope.clone(), dict, &mut processargs, &delivery_map, ignore()).await?;
520
521        assert_eq!(processargs.handles.len(), 0);
522        assert_eq!(processargs.namespace_entries.len(), 1);
523        let entry = processargs.namespace_entries.pop().unwrap();
524        assert_eq!(entry.path.to_str().unwrap(), "/svc");
525
526        // Check that there are the expected two protocols inside the svc directory.
527        let dir = entry.directory.into_proxy();
528        let mut entries = fuchsia_fs::directory::readdir(&dir).await.unwrap();
529        let mut expectation = vec![
530            DirEntry { name: "fuchsia.Normal".to_string(), kind: fio::DirentType::Service },
531            DirEntry { name: "fuchsia.Closed".to_string(), kind: fio::DirentType::Service },
532        ];
533        entries.sort();
534        expectation.sort();
535        assert_eq!(entries, expectation);
536
537        let dir = dir.into_channel().unwrap().into_zx_channel();
538
539        // Connect to the protocol using namespace functionality.
540        let (client_end, server_end) = zx::Channel::create();
541        fdio::service_connect_at(&dir, "fuchsia.Normal", server_end).unwrap();
542
543        // Make sure the server_end is received, and test connectivity.
544        let server_end: zx::Channel = receiver.receive().await.unwrap().channel.into();
545        client_end.signal_peer(zx::Signals::empty(), zx::Signals::USER_0).unwrap();
546        server_end.wait_handle(zx::Signals::USER_0, zx::MonotonicInstant::INFINITE_PAST).unwrap();
547
548        // Connect to the closed protocol. Because the receiver is discarded, anything we send
549        // should get peer-closed.
550        let (client_end, server_end) = zx::Channel::create();
551        fdio::service_connect_at(&dir, "fuchsia.Closed", server_end).unwrap();
552        fasync::Channel::from_channel(client_end).on_closed().await.unwrap();
553
554        drop(dir);
555        drop(processargs);
556        scope.wait().await;
557        Ok(())
558    }
559
560    #[fuchsia::test]
561    async fn test_namespace_scope_shutdown() -> Result<()> {
562        let (sender, receiver) = multishot();
563
564        let mut processargs = ProcessArgs::new();
565        let dict = {
566            let dict = Dict::new();
567            dict.insert("normal".parse().unwrap(), sender.into())
568                .expect("dict entry already exists");
569            dict
570        };
571        let delivery_map = hashmap! {
572            "normal".parse().unwrap() => DeliveryMapEntry::Delivery(
573                Delivery::NamespacedObject(cm_types::Path::from_str("/svc/fuchsia.Normal").unwrap())
574            ),
575        };
576        let scope = ExecutionScope::new();
577        add_to_processargs(scope.clone(), dict, &mut processargs, &delivery_map, ignore()).await?;
578
579        assert_eq!(processargs.handles.len(), 0);
580        assert_eq!(processargs.namespace_entries.len(), 1);
581        let entry = processargs.namespace_entries.pop().unwrap();
582        assert_eq!(entry.path.to_str().unwrap(), "/svc");
583
584        let dir = entry.directory.into_proxy();
585        let dir = dir.into_channel().unwrap().into_zx_channel();
586
587        // Connect to the protocol using namespace functionality.
588        let (client_end, server_end) = zx::Channel::create();
589        fdio::service_connect_at(&dir, "fuchsia.Normal", server_end).unwrap();
590
591        // Make sure the server_end is received, and test connectivity.
592        let server_end: zx::Channel = receiver.receive().await.unwrap().channel.into();
593        client_end.signal_peer(zx::Signals::empty(), zx::Signals::USER_0).unwrap();
594        server_end.wait_handle(zx::Signals::USER_0, zx::MonotonicInstant::INFINITE_PAST).unwrap();
595
596        // Shutdown the execution scope.
597        scope.shutdown();
598        scope.wait().await;
599
600        // Connect to the protocol again. This time, because the namespace was shutdown, anything we send
601        // should get peer-closed.
602        let (client_end, server_end) = zx::Channel::create();
603        fdio::service_connect_at(&dir, "fuchsia.Normal", server_end).unwrap();
604        fasync::Channel::from_channel(client_end).on_closed().await.unwrap();
605
606        drop(dir);
607        drop(processargs);
608        Ok(())
609    }
610
611    /// Install an `Open` capability at "/data". Test that opening "/data/abc" means the
612    /// open capability receives a request to open the current node, and the server endpoint
613    /// in that request has buffered an open request for "abc". This replicates what
614    /// component_manager does for directory capabilities.
615    #[test]
616    fn test_namespace_entry_end_to_end() -> Result<()> {
617        use futures::task::Poll;
618        let mut exec = fasync::TestExecutor::new();
619        let (dir, receiver) = mock_dir();
620        let scope = ExecutionScope::new();
621        let dir_proxy = serve_directory(dir, &scope, fio::PERM_READABLE).unwrap();
622
623        let mut processargs = ProcessArgs::new();
624        let dict = Dict::new();
625        dict.insert(
626            "data".parse().unwrap(),
627            Capability::Directory(sandbox::Directory::new(dir_proxy)),
628        )
629        .map_err(|e| anyhow!("{e:?}"))?;
630        let delivery_map = hashmap! {
631            "data".parse().unwrap() => DeliveryMapEntry::Delivery(
632                Delivery::NamespaceEntry(cm_types::Path::from_str("/data").unwrap())
633            ),
634        };
635        let scope = ExecutionScope::new();
636        exec.run_singlethreaded(&mut pin!(add_to_processargs(
637            scope.clone(),
638            dict,
639            &mut processargs,
640            &delivery_map,
641            ignore(),
642        )))
643        .unwrap();
644
645        assert_eq!(processargs.handles.len(), 0);
646        assert_eq!(processargs.namespace_entries.len(), 1);
647        let entry = processargs.namespace_entries.pop().unwrap();
648        assert_eq!(entry.path.to_str().unwrap(), "/data");
649
650        // No request yet. Not until we write to the client endpoint.
651        assert_matches!(exec.run_until_stalled(&mut receiver.recv()), Poll::Pending);
652
653        let dir = entry.directory.into_proxy();
654        let dir = dir.into_channel().unwrap().into_zx_channel();
655        let (client_end, server_end) = zx::Channel::create();
656
657        // Test that the flags are passed correctly.
658        let flags_for_abc = fio::Flags::PROTOCOL_DIRECTORY | fio::Flags::FLAG_SEND_REPRESENTATION;
659        fdio::open_at(&dir, "abc", flags_for_abc, server_end).unwrap();
660
661        // Capability is opened with "." and a `server_end`.
662        let (relative_path, server_end) = exec.run_singlethreaded(&mut receiver.recv()).unwrap();
663        assert!(relative_path.is_dot());
664
665        // Verify there is an open message for "abc" with `flags_for_abc` on `server_end`.
666        let server_end: ServerEnd<fio::DirectoryMarker> = server_end.into();
667        let mut stream = server_end.into_stream();
668        let request = exec.run_singlethreaded(&mut stream.try_next()).unwrap().unwrap();
669        assert_matches!(
670            &request,
671            fio::DirectoryRequest::Open { path, flags, .. }
672            if path == "abc" && *flags == flags_for_abc
673        );
674
675        let client_end = fasync::Channel::from_channel(client_end.into());
676        assert_matches!(exec.run_until_stalled(&mut client_end.on_closed()), Poll::Pending);
677        // Drop the request, including the server endpoint.
678        drop(request);
679        // Client should observe that the server endpoint was dropped.
680        exec.run_singlethreaded(&mut client_end.on_closed()).unwrap();
681
682        drop(dir);
683        drop(processargs);
684        exec.run_singlethreaded(pin!(scope.wait()));
685        Ok(())
686    }
687
688    #[fuchsia::test]
689    async fn test_handle_unsupported_in_namespace() {
690        let (sock0, _sock1) = zx::Socket::create_stream();
691
692        let mut processargs = ProcessArgs::new();
693        let dict = Dict::new();
694        dict.insert(
695            "stdin".parse().unwrap(),
696            Capability::Handle(Handle::from(sock0.into_handle())),
697        )
698        .expect("dict entry already exists");
699        let delivery_map = hashmap! {
700            "stdin".parse().unwrap() => DeliveryMapEntry::Delivery(
701                Delivery::NamespacedObject(cm_types::Path::from_str("/svc/fuchsia.Normal").unwrap())
702            )
703        };
704
705        assert_matches!(
706            add_to_processargs(ExecutionScope::new(), dict, &mut processargs, &delivery_map, ignore()).await.err().unwrap(),
707            DeliveryError::NamespaceError(BuildNamespaceError::Conversion {
708                path, ..
709            })
710            if path.to_string() == "/svc"
711        );
712    }
713}