Skip to main content

package_directory/
non_meta_subdir.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 fidl_fuchsia_io as fio;
7use std::sync::Arc;
8use vfs::directory::entry::EntryInfo;
9use vfs::directory::immutable::connection::ImmutableConnection;
10use vfs::directory::traversal_position::TraversalPosition;
11use vfs::execution_scope::ExecutionScope;
12use vfs::{ObjectRequestRef, immutable_attributes};
13
14pub(crate) struct NonMetaSubdir<S: crate::NonMetaStorage> {
15    root_dir: Arc<RootDir<S>>,
16    // The object relative path expression of the subdir relative to the package root with a
17    // trailing slash appended.
18    path: String,
19}
20
21impl<S: crate::NonMetaStorage> NonMetaSubdir<S> {
22    pub(crate) fn new(root_dir: Arc<RootDir<S>>, path: String) -> Arc<Self> {
23        Arc::new(NonMetaSubdir { root_dir, path })
24    }
25}
26
27impl<S: crate::NonMetaStorage> vfs::directory::entry::GetEntryInfo for NonMetaSubdir<S> {
28    fn entry_info(&self) -> EntryInfo {
29        EntryInfo::new(fio::INO_UNKNOWN, fio::DirentType::Directory)
30    }
31}
32
33impl<S: crate::NonMetaStorage> vfs::node::Node for NonMetaSubdir<S> {
34    async fn get_attributes(
35        &self,
36        requested_attributes: fio::NodeAttributesQuery,
37    ) -> Result<fio::NodeAttributes2, zx::Status> {
38        Ok(immutable_attributes!(
39            requested_attributes,
40            Immutable {
41                protocols: fio::NodeProtocolKinds::DIRECTORY,
42                abilities: crate::DIRECTORY_ABILITIES,
43                id: 1,
44            }
45        ))
46    }
47}
48
49impl<S: crate::NonMetaStorage> vfs::directory::entry_container::Directory for NonMetaSubdir<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
61        // Handle case where the request is for this directory itself (e.g. ".").
62        if path.is_empty() {
63            // `ImmutableConnection` checks that only directory flags are specified.
64            object_request
65                .take()
66                .create_connection_sync::<ImmutableConnection<_>, _>(scope, self, flags);
67            return Ok(());
68        }
69
70        // `path` is relative, and may include a trailing slash.
71        let file_path = format!(
72            "{}{}",
73            self.path,
74            path.as_ref().strip_suffix('/').unwrap_or_else(|| path.as_ref())
75        );
76
77        if let Some(blob) = self.root_dir.non_meta_files.get(file_path.as_str()) {
78            if path.is_dir() {
79                return Err(zx::Status::NOT_DIR);
80            }
81            return self.root_dir.non_meta_storage.open(blob, flags, scope, object_request);
82        }
83
84        if let Some(subdir) = self.root_dir.get_non_meta_subdir(file_path + "/") {
85            return subdir.open(scope, vfs::Path::dot(), flags, object_request);
86        }
87
88        Err(zx::Status::NOT_FOUND)
89    }
90
91    async fn read_dirents(
92        &self,
93        pos: &TraversalPosition,
94        sink: Box<dyn vfs::directory::dirents_sink::Sink + 'static>,
95    ) -> Result<
96        (TraversalPosition, Box<dyn vfs::directory::dirents_sink::Sealed + 'static>),
97        zx::Status,
98    > {
99        vfs::directory::read_dirents::read_dirents(
100            &crate::get_dir_children(self.root_dir.non_meta_files.keys(), &self.path),
101            pos,
102            sink,
103        )
104    }
105
106    fn register_watcher(
107        self: Arc<Self>,
108        _: ExecutionScope,
109        _: fio::WatchMask,
110        _: vfs::directory::entry_container::DirectoryWatcher,
111    ) -> Result<(), zx::Status> {
112        Err(zx::Status::NOT_SUPPORTED)
113    }
114
115    // `register_watcher` is unsupported so no need to do anything here.
116    fn unregister_watcher(self: Arc<Self>, _: usize) {}
117}
118
119#[cfg(test)]
120mod tests {
121    use super::*;
122    use assert_matches::assert_matches;
123    use fuchsia_fs::directory::{DirEntry, DirentKind};
124    use fuchsia_pkg_testing::PackageBuilder;
125    use fuchsia_pkg_testing::blobfs::Fake as FakeBlobfs;
126    use futures::prelude::*;
127
128    struct TestEnv {
129        _blobfs_fake: FakeBlobfs,
130    }
131
132    impl TestEnv {
133        async fn new() -> (Self, fio::DirectoryProxy) {
134            let pkg = PackageBuilder::new("pkg")
135                .add_resource_at("dir0/dir1/file", "bloblob".as_bytes())
136                .build()
137                .await
138                .unwrap();
139            let (metafar_blob, content_blobs) = pkg.contents();
140            let (blobfs_fake, blobfs_client) = FakeBlobfs::new();
141            blobfs_fake.add_blob(metafar_blob.merkle, metafar_blob.contents);
142            for (hash, bytes) in content_blobs {
143                blobfs_fake.add_blob(hash, bytes);
144            }
145            let root_dir = RootDir::new(blobfs_client, metafar_blob.merkle).await.unwrap();
146            let sub_dir = NonMetaSubdir::new(root_dir, "dir0/".to_string());
147            (
148                Self { _blobfs_fake: blobfs_fake },
149                vfs::directory::serve_read_only(
150                    sub_dir,
151                    vfs::execution_scope::ExecutionScope::new(),
152                ),
153            )
154        }
155    }
156
157    /// Ensure connections to a [`NonMetaSubdir`] cannot be created as mutable (i.e. with
158    /// [`fio::PERM_WRITABLE`]) This ensures that the VFS will disallow any attempts to create a new
159    /// file/directory, modify the attributes of any nodes, or open any files as writable.
160    #[fuchsia::test]
161    async fn non_meta_subdir_cannot_be_served_as_mutable() {
162        let pkg = PackageBuilder::new("pkg")
163            .add_resource_at("dir0/dir1/file", "bloblob".as_bytes())
164            .build()
165            .await
166            .unwrap();
167        let (metafar_blob, content_blobs) = pkg.contents();
168        let (blobfs_fake, blobfs_client) = FakeBlobfs::new();
169        blobfs_fake.add_blob(metafar_blob.merkle, metafar_blob.contents);
170        for (hash, bytes) in content_blobs {
171            blobfs_fake.add_blob(hash, bytes);
172        }
173        let root_dir = RootDir::new(blobfs_client, metafar_blob.merkle).await.unwrap();
174        let sub_dir = NonMetaSubdir::new(root_dir, "dir0/".to_string());
175        let proxy = vfs::directory::serve(sub_dir, ExecutionScope::new(), fio::PERM_WRITABLE);
176        assert_matches!(
177            proxy.take_event_stream().try_next().await,
178            Err(fidl::Error::ClientChannelClosed { status: zx::Status::NOT_SUPPORTED, .. })
179        );
180    }
181
182    #[fuchsia::test]
183    async fn non_meta_subdir_readdir() {
184        let (_env, sub_dir) = TestEnv::new().await;
185        assert_eq!(
186            fuchsia_fs::directory::readdir_inclusive(&sub_dir).await.unwrap(),
187            vec![
188                DirEntry { name: ".".to_string(), kind: DirentKind::Directory },
189                DirEntry { name: "dir1".to_string(), kind: DirentKind::Directory }
190            ]
191        );
192    }
193
194    #[fuchsia::test]
195    async fn non_meta_subdir_get_attributes() {
196        let (_env, sub_dir) = TestEnv::new().await;
197        let (mutable_attributes, immutable_attributes) =
198            sub_dir.get_attributes(fio::NodeAttributesQuery::all()).await.unwrap().unwrap();
199        assert_eq!(
200            fio::NodeAttributes2 { mutable_attributes, immutable_attributes },
201            immutable_attributes!(
202                fio::NodeAttributesQuery::all(),
203                Immutable {
204                    protocols: fio::NodeProtocolKinds::DIRECTORY,
205                    abilities: crate::DIRECTORY_ABILITIES,
206                    id: 1,
207                }
208            )
209        );
210    }
211
212    #[fuchsia::test]
213    async fn non_meta_subdir_watch_not_supported() {
214        let (_env, sub_dir) = TestEnv::new().await;
215        let (_client, server) = fidl::endpoints::create_endpoints();
216        let status =
217            zx::Status::from_raw(sub_dir.watch(fio::WatchMask::empty(), 0, server).await.unwrap());
218        assert_eq!(status, zx::Status::NOT_SUPPORTED);
219    }
220
221    #[fuchsia::test]
222    async fn non_meta_subdir_open_directory() {
223        let (_env, sub_dir) = TestEnv::new().await;
224        for path in ["dir1", "dir1/"] {
225            let proxy = fuchsia_fs::directory::open_directory(&sub_dir, path, fio::PERM_READABLE)
226                .await
227                .unwrap();
228            assert_eq!(
229                fuchsia_fs::directory::readdir(&proxy).await.unwrap(),
230                vec![DirEntry { name: "file".to_string(), kind: DirentKind::File }]
231            );
232        }
233    }
234
235    #[fuchsia::test]
236    async fn non_meta_subdir_open_file() {
237        let (_env, sub_dir) = TestEnv::new().await;
238        let proxy = fuchsia_fs::directory::open_file(&sub_dir, "dir1/file", fio::PERM_READABLE)
239            .await
240            .unwrap();
241        assert_eq!(fuchsia_fs::file::read(&proxy).await.unwrap(), b"bloblob".to_vec())
242    }
243}