Skip to main content

package_directory/
meta_as_dir.rs

1// Copyright 2021 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.
4
5use crate::root_dir::AsRootDir;
6use crate::usize_to_u64_safe;
7use fidl_fuchsia_io as fio;
8use std::sync::Arc;
9use vfs::directory::entry::EntryInfo;
10use vfs::directory::immutable::connection::ImmutableConnection;
11use vfs::directory::traversal_position::TraversalPosition;
12use vfs::execution_scope::ExecutionScope;
13use vfs::{ObjectRequestRef, immutable_attributes};
14
15pub(crate) struct MetaAsDir<R> {
16    root_dir: Arc<R>,
17}
18
19impl<R: AsRootDir> MetaAsDir<R> {
20    pub(crate) fn new(root_dir: Arc<R>) -> Arc<Self> {
21        Arc::new(MetaAsDir { root_dir })
22    }
23}
24
25impl<R: AsRootDir> vfs::directory::entry::GetEntryInfo for MetaAsDir<R> {
26    fn entry_info(&self) -> EntryInfo {
27        EntryInfo::new(fio::INO_UNKNOWN, fio::DirentType::Directory)
28    }
29}
30
31impl<R: AsRootDir> vfs::node::Node for MetaAsDir<R> {
32    async fn get_attributes(
33        &self,
34        requested_attributes: fio::NodeAttributesQuery,
35    ) -> Result<fio::NodeAttributes2, zx::Status> {
36        let size = usize_to_u64_safe(self.root_dir.as_root_dir().meta_files.element_len());
37        Ok(immutable_attributes!(
38            requested_attributes,
39            Immutable {
40                protocols: fio::NodeProtocolKinds::DIRECTORY,
41                abilities: crate::DIRECTORY_ABILITIES,
42                content_size: size,
43                storage_size: size,
44                id: 1,
45            }
46        ))
47    }
48}
49
50impl<R: AsRootDir> vfs::directory::entry_container::Directory for MetaAsDir<R> {
51    fn open(
52        self: Arc<Self>,
53        scope: ExecutionScope,
54        path: vfs::Path,
55        flags: fio::Flags,
56        object_request: ObjectRequestRef<'_>,
57    ) -> Result<(), zx::Status> {
58        if !flags.difference(crate::ALLOWED_FLAGS).is_empty() {
59            return Err(zx::Status::NOT_SUPPORTED);
60        }
61        // Disallow creating an executable connection to this node or any children.
62        if flags.contains(fio::Flags::PERM_EXECUTE) {
63            return Err(zx::Status::NOT_SUPPORTED);
64        }
65
66        // Handle case where the request is for this directory itself (e.g. ".").
67        if path.is_empty() {
68            // Only MetaAsDir can be obtained from Open calls to MetaAsDir. To obtain the "meta"
69            // file, the Open call must be made on RootDir. This is consistent with pkgfs behavior
70            // and is needed so that Clone'ing MetaAsDir results in MetaAsDir, because VFS handles
71            // Clone by calling Open with a path of ".", a mode of 0, and mostly unmodified flags
72            // and that combination of arguments would normally result in the file being used.
73            //
74            // `ImmutableConnection` will check flags contain only directory-allowed flags.
75            object_request
76                .take()
77                .create_connection_sync::<ImmutableConnection<_>, _>(scope, self, flags);
78            return Ok(());
79        }
80
81        // `path` is relative, and may include a trailing slash.
82        let file_path =
83            format!("meta/{}", path.as_ref().strip_suffix('/').unwrap_or_else(|| path.as_ref()));
84
85        if let Some(file) = self.root_dir.as_root_dir().get_meta_file(&file_path)? {
86            if path.is_dir() {
87                return Err(zx::Status::NOT_DIR);
88            }
89            return vfs::file::serve(file, scope, &flags, object_request);
90        }
91
92        if let Some(subdir) = self.root_dir.get_meta_subdir(file_path + "/") {
93            return subdir.open(scope, vfs::Path::dot(), flags, object_request);
94        }
95
96        Err(zx::Status::NOT_FOUND)
97    }
98
99    async fn read_dirents(
100        &self,
101        pos: &TraversalPosition,
102        sink: Box<dyn vfs::directory::dirents_sink::Sink + 'static>,
103    ) -> Result<
104        (TraversalPosition, Box<dyn vfs::directory::dirents_sink::Sealed + 'static>),
105        zx::Status,
106    > {
107        vfs::directory::read_dirents::read_dirents(
108            &crate::get_dir_children(self.root_dir.as_root_dir().meta_files.keys(), "meta/"),
109            pos,
110            sink,
111        )
112    }
113
114    fn register_watcher(
115        self: Arc<Self>,
116        _: ExecutionScope,
117        _: fio::WatchMask,
118        _: vfs::directory::entry_container::DirectoryWatcher,
119    ) -> Result<(), zx::Status> {
120        Err(zx::Status::NOT_SUPPORTED)
121    }
122
123    // `register_watcher` is unsupported so no need to do anything here.
124    fn unregister_watcher(self: Arc<Self>, _: usize) {}
125}
126
127#[cfg(test)]
128mod tests {
129    use super::*;
130    use crate::root_dir::RootDir;
131    use assert_matches::assert_matches;
132    use fuchsia_fs::directory::{DirEntry, DirentKind};
133    use fuchsia_pkg_testing::PackageBuilder;
134    use fuchsia_pkg_testing::blobfs::Fake as FakeBlobfs;
135    use futures::TryStreamExt as _;
136
137    struct TestEnv {
138        _blobfs_fake: FakeBlobfs,
139    }
140
141    impl TestEnv {
142        async fn new() -> (Self, fio::DirectoryProxy) {
143            let pkg = PackageBuilder::new("pkg")
144                .add_resource_at("meta/dir/file", &b"contents"[..])
145                .build()
146                .await
147                .unwrap();
148            let (metafar_blob, _) = pkg.contents();
149            let (blobfs_fake, blobfs_client) = FakeBlobfs::new();
150            blobfs_fake.add_blob(metafar_blob.merkle, metafar_blob.contents);
151            let root_dir = RootDir::new(blobfs_client, metafar_blob.merkle).await.unwrap();
152            let meta_as_dir = MetaAsDir::new(root_dir);
153            (
154                Self { _blobfs_fake: blobfs_fake },
155                vfs::directory::serve_read_only(
156                    meta_as_dir,
157                    vfs::execution_scope::ExecutionScope::new(),
158                ),
159            )
160        }
161    }
162
163    /// Ensure connections to a [`MetaAsDir`] cannot be created as mutable (i.e. with
164    /// [`fio::PERM_WRITABLE`]) or executable ([`fio::PERM_EXECUTABLE`]). This ensures that the VFS
165    /// will disallow any attempts to create a new file/directory, modify the attributes of any
166    /// nodes, or open any files as writable/executable.
167    #[fuchsia::test]
168    async fn meta_as_dir_cannot_be_served_as_mutable() {
169        let pkg = PackageBuilder::new("pkg")
170            .add_resource_at("meta/dir/file", &b"contents"[..])
171            .build()
172            .await
173            .unwrap();
174        let (metafar_blob, _) = pkg.contents();
175        let (blobfs_fake, blobfs_client) = FakeBlobfs::new();
176        blobfs_fake.add_blob(metafar_blob.merkle, metafar_blob.contents);
177        let meta_as_dir =
178            MetaAsDir::new(RootDir::new(blobfs_client, metafar_blob.merkle).await.unwrap());
179        for flags in [fio::PERM_WRITABLE, fio::PERM_EXECUTABLE] {
180            let proxy = vfs::directory::serve(meta_as_dir.clone(), ExecutionScope::new(), flags);
181            assert_matches!(
182                proxy.take_event_stream().try_next().await,
183                Err(fidl::Error::ClientChannelClosed { epitaph, .. })
184                    if epitaph == zx::Status::NOT_SUPPORTED
185            );
186        }
187    }
188
189    #[fuchsia::test]
190    async fn meta_as_dir_readdir() {
191        let (_env, meta_as_dir) = TestEnv::new().await;
192        assert_eq!(
193            fuchsia_fs::directory::readdir_inclusive(&meta_as_dir).await.unwrap(),
194            vec![
195                DirEntry { name: ".".to_string(), kind: DirentKind::Directory },
196                DirEntry { name: "contents".to_string(), kind: DirentKind::File },
197                DirEntry { name: "dir".to_string(), kind: DirentKind::Directory },
198                DirEntry { name: "fuchsia.abi".to_string(), kind: DirentKind::Directory },
199                DirEntry { name: "package".to_string(), kind: DirentKind::File }
200            ]
201        );
202    }
203
204    #[fuchsia::test]
205    async fn meta_as_dir_get_attributes() {
206        let (_env, meta_as_dir) = TestEnv::new().await;
207        let (mutable_attributes, immutable_attributes) =
208            meta_as_dir.get_attributes(fio::NodeAttributesQuery::all()).await.unwrap().unwrap();
209        assert_eq!(
210            fio::NodeAttributes2 { mutable_attributes, immutable_attributes },
211            immutable_attributes!(
212                fio::NodeAttributesQuery::all(),
213                Immutable {
214                    protocols: fio::NodeProtocolKinds::DIRECTORY,
215                    abilities: crate::DIRECTORY_ABILITIES,
216                    content_size: 4,
217                    storage_size: 4,
218                    id: 1,
219                }
220            )
221        );
222    }
223
224    #[fuchsia::test]
225    async fn meta_as_dir_watch_not_supported() {
226        let (_env, meta_as_dir) = TestEnv::new().await;
227        let (_client, server) = fidl::endpoints::create_endpoints();
228        assert_eq!(
229            zx::Status::ok(meta_as_dir.watch(fio::WatchMask::empty(), 0, server).await.unwrap()),
230            Err(zx::Status::NOT_SUPPORTED)
231        );
232    }
233
234    #[fuchsia::test]
235    async fn meta_as_dir_open_file() {
236        let (_env, meta_as_dir) = TestEnv::new().await;
237        let proxy = fuchsia_fs::directory::open_file(&meta_as_dir, "dir/file", fio::PERM_READABLE)
238            .await
239            .unwrap();
240        assert_eq!(fuchsia_fs::file::read(&proxy).await.unwrap(), b"contents".to_vec());
241    }
242
243    #[fuchsia::test]
244    async fn meta_as_dir_open_directory() {
245        let (_env, meta_as_dir) = TestEnv::new().await;
246        for path in ["dir", "dir/"] {
247            let proxy =
248                fuchsia_fs::directory::open_directory(&meta_as_dir, path, fio::PERM_READABLE)
249                    .await
250                    .unwrap();
251            assert_eq!(
252                fuchsia_fs::directory::readdir(&proxy).await.unwrap(),
253                vec![DirEntry { name: "file".to_string(), kind: DirentKind::File }]
254            );
255        }
256    }
257}