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) {
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(
101                self.root_dir.non_meta_files.keys().map(|s| s.as_str()),
102                &self.path,
103            ),
104            pos,
105            sink,
106        )
107    }
108
109    fn register_watcher(
110        self: Arc<Self>,
111        _: ExecutionScope,
112        _: fio::WatchMask,
113        _: vfs::directory::entry_container::DirectoryWatcher,
114    ) -> Result<(), zx::Status> {
115        Err(zx::Status::NOT_SUPPORTED)
116    }
117
118    // `register_watcher` is unsupported so no need to do anything here.
119    fn unregister_watcher(self: Arc<Self>, _: usize) {}
120}
121
122#[cfg(test)]
123mod tests {
124    use super::*;
125    use assert_matches::assert_matches;
126    use fuchsia_fs::directory::{DirEntry, DirentKind};
127    use fuchsia_pkg_testing::PackageBuilder;
128    use fuchsia_pkg_testing::blobfs::Fake as FakeBlobfs;
129    use futures::prelude::*;
130
131    struct TestEnv {
132        _blobfs_fake: FakeBlobfs,
133    }
134
135    impl TestEnv {
136        async fn new() -> (Self, fio::DirectoryProxy) {
137            let pkg = PackageBuilder::new("pkg")
138                .add_resource_at("dir0/dir1/file", "bloblob".as_bytes())
139                .build()
140                .await
141                .unwrap();
142            let (metafar_blob, content_blobs) = pkg.contents();
143            let (blobfs_fake, blobfs_client) = FakeBlobfs::new();
144            blobfs_fake.add_blob(metafar_blob.merkle, metafar_blob.contents);
145            for (hash, bytes) in content_blobs {
146                blobfs_fake.add_blob(hash, bytes);
147            }
148            let root_dir = RootDir::new(blobfs_client, metafar_blob.merkle).await.unwrap();
149            let sub_dir = NonMetaSubdir::new(root_dir, "dir0/".to_string());
150            (Self { _blobfs_fake: blobfs_fake }, vfs::directory::serve_read_only(sub_dir))
151        }
152    }
153
154    /// Ensure connections to a [`NonMetaSubdir`] cannot be created as mutable (i.e. with
155    /// [`fio::PERM_WRITABLE`]) This ensures that the VFS will disallow any attempts to create a new
156    /// file/directory, modify the attributes of any nodes, or open any files as writable.
157    #[fuchsia_async::run_singlethreaded(test)]
158    async fn non_meta_subdir_cannot_be_served_as_mutable() {
159        let pkg = PackageBuilder::new("pkg")
160            .add_resource_at("dir0/dir1/file", "bloblob".as_bytes())
161            .build()
162            .await
163            .unwrap();
164        let (metafar_blob, content_blobs) = pkg.contents();
165        let (blobfs_fake, blobfs_client) = FakeBlobfs::new();
166        blobfs_fake.add_blob(metafar_blob.merkle, metafar_blob.contents);
167        for (hash, bytes) in content_blobs {
168            blobfs_fake.add_blob(hash, bytes);
169        }
170        let root_dir = RootDir::new(blobfs_client, metafar_blob.merkle).await.unwrap();
171        let sub_dir = NonMetaSubdir::new(root_dir, "dir0/".to_string());
172        let proxy = vfs::directory::serve(sub_dir, fio::PERM_WRITABLE);
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    #[fuchsia_async::run_singlethreaded(test)]
180    async fn non_meta_subdir_readdir() {
181        let (_env, sub_dir) = TestEnv::new().await;
182        assert_eq!(
183            fuchsia_fs::directory::readdir_inclusive(&sub_dir).await.unwrap(),
184            vec![
185                DirEntry { name: ".".to_string(), kind: DirentKind::Directory },
186                DirEntry { name: "dir1".to_string(), kind: DirentKind::Directory }
187            ]
188        );
189    }
190
191    #[fuchsia_async::run_singlethreaded(test)]
192    async fn non_meta_subdir_get_attributes() {
193        let (_env, sub_dir) = TestEnv::new().await;
194        let (mutable_attributes, immutable_attributes) =
195            sub_dir.get_attributes(fio::NodeAttributesQuery::all()).await.unwrap().unwrap();
196        assert_eq!(
197            fio::NodeAttributes2 { mutable_attributes, immutable_attributes },
198            immutable_attributes!(
199                fio::NodeAttributesQuery::all(),
200                Immutable {
201                    protocols: fio::NodeProtocolKinds::DIRECTORY,
202                    abilities: crate::DIRECTORY_ABILITIES,
203                    id: 1,
204                }
205            )
206        );
207    }
208
209    #[fuchsia_async::run_singlethreaded(test)]
210    async fn non_meta_subdir_watch_not_supported() {
211        let (_env, sub_dir) = TestEnv::new().await;
212        let (_client, server) = fidl::endpoints::create_endpoints();
213        let status =
214            zx::Status::from_raw(sub_dir.watch(fio::WatchMask::empty(), 0, server).await.unwrap());
215        assert_eq!(status, zx::Status::NOT_SUPPORTED);
216    }
217
218    #[fuchsia_async::run_singlethreaded(test)]
219    async fn non_meta_subdir_open_directory() {
220        let (_env, sub_dir) = TestEnv::new().await;
221        for path in ["dir1", "dir1/"] {
222            let proxy = fuchsia_fs::directory::open_directory(&sub_dir, path, fio::PERM_READABLE)
223                .await
224                .unwrap();
225            assert_eq!(
226                fuchsia_fs::directory::readdir(&proxy).await.unwrap(),
227                vec![DirEntry { name: "file".to_string(), kind: DirentKind::File }]
228            );
229        }
230    }
231
232    #[fuchsia_async::run_singlethreaded(test)]
233    async fn non_meta_subdir_open_file() {
234        let (_env, sub_dir) = TestEnv::new().await;
235        let proxy = fuchsia_fs::directory::open_file(&sub_dir, "dir1/file", fio::PERM_READABLE)
236            .await
237            .unwrap();
238        assert_eq!(fuchsia_fs::file::read(&proxy).await.unwrap(), b"bloblob".to_vec())
239    }
240}