1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
// Copyright 2021 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

use anyhow::Error;
use fdio::Namespace;
use fidl_fuchsia_io as fio;
use std::collections::{hash_map::Entry, HashMap};
use std::sync::Arc;
use vfs::directory::{
    self, entry::DirectoryEntry, helper::DirectlyMutable, mutable::simple as pfs,
};
use vfs::execution_scope::ExecutionScope;

type Directory = Arc<pfs::Simple>;

/// Helper for binding entries in the namespace of the current process.
///
/// Namespace entries for served protocols are unbound when `NamespaceBinder` is dropped.
pub struct NamespaceBinder {
    scope: ExecutionScope,
    dirs: HashMap<String, Directory>,
}

impl NamespaceBinder {
    pub fn new(scope: ExecutionScope) -> NamespaceBinder {
        NamespaceBinder { scope, dirs: HashMap::new() }
    }

    /// Serves a protocol at the given path in the namespace of the current process.
    ///
    /// `path` must be absolute, e.g. "/foo/bar", containing no "." nor ".." entries.
    /// It is relative to the root of the namespace.
    pub fn bind_at_path(
        &mut self,
        path: &str,
        entry: Arc<dyn DirectoryEntry>,
    ) -> Result<(), Error> {
        let ns = Namespace::installed()?;

        let (dir_path, entry_name) =
            path.rsplit_once('/').ok_or_else(|| format_err!("path must be absolute"))?;

        let dir = match self.dirs.entry(dir_path.to_string()) {
            Entry::Occupied(map_entry) => map_entry.into_mut(),
            Entry::Vacant(map_entry) => {
                let dir = pfs::simple();

                let (client, server) = fidl::endpoints::create_endpoints();
                directory::entry_container::Directory::open(
                    dir.clone(),
                    self.scope.clone(),
                    fio::OpenFlags::RIGHT_READABLE
                        | fio::OpenFlags::RIGHT_WRITABLE
                        | fio::OpenFlags::DIRECTORY,
                    vfs::path::Path::dot(),
                    fidl::endpoints::ServerEnd::new(server.into_channel()),
                );

                ns.bind(dir_path, client)?;
                map_entry.insert(dir)
            }
        };

        dir.add_entry(entry_name, entry)?;

        Ok(())
    }
}

impl Drop for NamespaceBinder {
    fn drop(&mut self) {
        let ns = Namespace::installed().unwrap();
        for path in self.dirs.keys() {
            ns.unbind(path).unwrap();
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use fidl::endpoints::ClientEnd;
    use fidl_test_placeholders as echo;
    use fuchsia_zircon as zx;
    use futures::TryStreamExt;

    #[fuchsia_async::run_singlethreaded(test)]
    async fn test_bind_at_path() -> Result<(), Error> {
        const NAMESPACE_PATH: &str = "/some/path/in/the/namespace";

        let scope = ExecutionScope::new();
        let mut ns = NamespaceBinder::new(scope);

        let echo_entry =
            vfs::service::host(move |mut stream: echo::EchoRequestStream| async move {
                while let Ok(Some(request)) = stream.try_next().await {
                    let echo::EchoRequest::EchoString { value, responder } = request;
                    responder.send(Some(&value.unwrap())).expect("responder failed");
                }
            });
        ns.bind_at_path(NAMESPACE_PATH, echo_entry)?;

        let (client, server) = zx::Channel::create();
        fdio::service_connect(NAMESPACE_PATH, server)?;
        let proxy = ClientEnd::<echo::EchoMarker>::new(client).into_proxy()?;

        let res = proxy.echo_string(Some("hello world")).await?;
        assert_eq!(res, Some("hello world".to_string()));

        Ok(())
    }
}