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