package_directory/
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 MetaSubdir<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> MetaSubdir<S> {
22    pub(crate) fn new(root_dir: Arc<RootDir<S>>, path: String) -> Arc<Self> {
23        Arc::new(MetaSubdir { root_dir, path })
24    }
25}
26
27impl<S: crate::NonMetaStorage> vfs::directory::entry::GetEntryInfo for MetaSubdir<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 MetaSubdir<S> {
34    async fn get_attributes(
35        &self,
36        requested_attributes: fio::NodeAttributesQuery,
37    ) -> Result<fio::NodeAttributes2, zx::Status> {
38        let size = crate::usize_to_u64_safe(self.root_dir.meta_files.len());
39        Ok(immutable_attributes!(
40            requested_attributes,
41            Immutable {
42                protocols: fio::NodeProtocolKinds::DIRECTORY,
43                abilities: crate::DIRECTORY_ABILITIES,
44                content_size: size,
45                storage_size: size,
46                id: 1,
47            }
48        ))
49    }
50}
51
52impl<S: crate::NonMetaStorage> vfs::directory::entry_container::Directory for MetaSubdir<S> {
53    fn open(
54        self: Arc<Self>,
55        scope: ExecutionScope,
56        path: vfs::Path,
57        flags: fio::Flags,
58        object_request: ObjectRequestRef<'_>,
59    ) -> Result<(), zx::Status> {
60        if !flags.difference(crate::ALLOWED_FLAGS).is_empty() {
61            return Err(zx::Status::NOT_SUPPORTED);
62        }
63        // Disallow creating an executable connection to this node or any children.
64        if flags.contains(fio::Flags::PERM_EXECUTE) {
65            return Err(zx::Status::NOT_SUPPORTED);
66        }
67
68        // Handle case where the request is for this directory itself (e.g. ".").
69        if path.is_empty() {
70            // `ImmutableConnection` checks that only directory flags are specified.
71            object_request
72                .take()
73                .create_connection_sync::<ImmutableConnection<_>, _>(scope, self, flags);
74            return Ok(());
75        }
76
77        // `path` is relative, and may include a trailing slash.
78        let file_path = format!(
79            "{}{}",
80            self.path,
81            path.as_ref().strip_suffix('/').unwrap_or_else(|| path.as_ref())
82        );
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(
108                self.root_dir.meta_files.keys().map(|s| s.as_str()),
109                &self.path,
110            ),
111            pos,
112            sink,
113        )
114    }
115
116    fn register_watcher(
117        self: Arc<Self>,
118        _: ExecutionScope,
119        _: fio::WatchMask,
120        _: vfs::directory::entry_container::DirectoryWatcher,
121    ) -> Result<(), zx::Status> {
122        Err(zx::Status::NOT_SUPPORTED)
123    }
124
125    // `register_watcher` is unsupported so no need to do anything here.
126    fn unregister_watcher(self: Arc<Self>, _: usize) {}
127}
128
129#[cfg(test)]
130mod tests {
131    use super::*;
132    use assert_matches::assert_matches;
133    use fuchsia_fs::directory::{DirEntry, DirentKind};
134    use fuchsia_pkg_testing::PackageBuilder;
135    use fuchsia_pkg_testing::blobfs::Fake as FakeBlobfs;
136    use futures::prelude::*;
137
138    struct TestEnv {
139        _blobfs_fake: FakeBlobfs,
140    }
141
142    impl TestEnv {
143        async fn new() -> (Self, fio::DirectoryProxy) {
144            let pkg = PackageBuilder::new("pkg")
145                .add_resource_at("meta/dir/dir/file", &b"contents"[..])
146                .build()
147                .await
148                .unwrap();
149            let (metafar_blob, _) = pkg.contents();
150            let (blobfs_fake, blobfs_client) = FakeBlobfs::new();
151            blobfs_fake.add_blob(metafar_blob.merkle, metafar_blob.contents);
152            let root_dir = RootDir::new(blobfs_client, metafar_blob.merkle).await.unwrap();
153            let sub_dir = MetaSubdir::new(root_dir, "meta/dir/".to_string());
154            (Self { _blobfs_fake: blobfs_fake }, vfs::directory::serve_read_only(sub_dir))
155        }
156    }
157
158    /// Ensure connections to a [`MetaSubdir`] cannot be created as mutable (i.e. with
159    /// [`fio::PERM_WRITABLE`]) or executable ([`fio::PERM_EXECUTABLE`]). This ensures that the VFS
160    /// will disallow any attempts to create a new file/directory, modify the attributes of any
161    /// nodes, or open any files as writable/executable.
162    #[fuchsia_async::run_singlethreaded(test)]
163    async fn meta_subdir_cannot_be_served_as_mutable() {
164        let pkg = PackageBuilder::new("pkg")
165            .add_resource_at("meta/dir/dir/file", &b"contents"[..])
166            .build()
167            .await
168            .unwrap();
169        let (metafar_blob, _) = pkg.contents();
170        let (blobfs_fake, blobfs_client) = FakeBlobfs::new();
171        blobfs_fake.add_blob(metafar_blob.merkle, metafar_blob.contents);
172        let root_dir = RootDir::new(blobfs_client, metafar_blob.merkle).await.unwrap();
173        let sub_dir = MetaSubdir::new(root_dir, "meta/dir/".to_string());
174        for flags in [fio::PERM_WRITABLE, fio::PERM_EXECUTABLE] {
175            let proxy = vfs::directory::serve(sub_dir.clone(), flags);
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
183    #[fuchsia_async::run_singlethreaded(test)]
184    async fn meta_subdir_readdir() {
185        let (_env, sub_dir) = TestEnv::new().await;
186        assert_eq!(
187            fuchsia_fs::directory::readdir_inclusive(&sub_dir).await.unwrap(),
188            vec![
189                DirEntry { name: ".".to_string(), kind: DirentKind::Directory },
190                DirEntry { name: "dir".to_string(), kind: DirentKind::Directory }
191            ]
192        );
193    }
194
195    #[fuchsia_async::run_singlethreaded(test)]
196    async fn meta_subdir_get_attributes() {
197        let (_env, sub_dir) = TestEnv::new().await;
198        let (mutable_attributes, immutable_attributes) =
199            sub_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_subdir_watch_not_supported() {
217        let (_env, sub_dir) = TestEnv::new().await;
218        let (_client, server) = fidl::endpoints::create_endpoints();
219        let status =
220            zx::Status::from_raw(sub_dir.watch(fio::WatchMask::empty(), 0, server).await.unwrap());
221        assert_eq!(status, zx::Status::NOT_SUPPORTED);
222    }
223
224    #[fuchsia_async::run_singlethreaded(test)]
225    async fn meta_subdir_open_file() {
226        let (_env, sub_dir) = TestEnv::new().await;
227        let proxy = fuchsia_fs::directory::open_file(&sub_dir, "dir/file", fio::PERM_READABLE)
228            .await
229            .unwrap();
230        assert_eq!(fuchsia_fs::file::read(&proxy).await.unwrap(), b"contents".to_vec());
231    }
232
233    #[fuchsia_async::run_singlethreaded(test)]
234    async fn meta_subdir_open_directory() {
235        let (_env, sub_dir) = TestEnv::new().await;
236        for path in ["dir", "dir/"] {
237            let proxy = fuchsia_fs::directory::open_directory(&sub_dir, path, fio::PERM_READABLE)
238                .await
239                .unwrap();
240            assert_eq!(
241                fuchsia_fs::directory::readdir(&proxy).await.unwrap(),
242                vec![fuchsia_fs::directory::DirEntry {
243                    name: "file".to_string(),
244                    kind: fuchsia_fs::directory::DirentKind::File
245                }]
246            );
247        }
248    }
249}