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 anyhow::anyhow;
7use fidl::endpoints::ServerEnd;
8use fidl_fuchsia_io as fio;
9use log::error;
10use std::sync::Arc;
11use vfs::common::send_on_open_with_error;
12use vfs::directory::entry::EntryInfo;
13use vfs::directory::immutable::connection::ImmutableConnection;
14use vfs::directory::traversal_position::TraversalPosition;
15use vfs::execution_scope::ExecutionScope;
16use vfs::{immutable_attributes, ObjectRequestRef, ToObjectRequest as _};
17
18pub(crate) struct NonMetaSubdir<S: crate::NonMetaStorage> {
19    root_dir: Arc<RootDir<S>>,
20    // The object relative path expression of the subdir relative to the package root with a
21    // trailing slash appended.
22    path: String,
23}
24
25impl<S: crate::NonMetaStorage> NonMetaSubdir<S> {
26    pub(crate) fn new(root_dir: Arc<RootDir<S>>, path: String) -> Arc<Self> {
27        Arc::new(NonMetaSubdir { root_dir, path })
28    }
29}
30
31impl<S: crate::NonMetaStorage> vfs::directory::entry::GetEntryInfo for NonMetaSubdir<S> {
32    fn entry_info(&self) -> EntryInfo {
33        EntryInfo::new(fio::INO_UNKNOWN, fio::DirentType::Directory)
34    }
35}
36
37impl<S: crate::NonMetaStorage> vfs::node::Node for NonMetaSubdir<S> {
38    async fn get_attributes(
39        &self,
40        requested_attributes: fio::NodeAttributesQuery,
41    ) -> Result<fio::NodeAttributes2, zx::Status> {
42        Ok(immutable_attributes!(
43            requested_attributes,
44            Immutable {
45                protocols: fio::NodeProtocolKinds::DIRECTORY,
46                abilities: crate::DIRECTORY_ABILITIES,
47                id: 1,
48            }
49        ))
50    }
51}
52
53impl<S: crate::NonMetaStorage> vfs::directory::entry_container::Directory for NonMetaSubdir<S> {
54    fn deprecated_open(
55        self: Arc<Self>,
56        scope: ExecutionScope,
57        flags: fio::OpenFlags,
58        path: vfs::Path,
59        server_end: ServerEnd<fio::NodeMarker>,
60    ) {
61        let flags = flags & !fio::OpenFlags::POSIX_WRITABLE;
62        let describe = flags.contains(fio::OpenFlags::DESCRIBE);
63        // Disallow creating a writable connection to this node or any children. We also disallow
64        // file flags which do not apply. Note that the latter is not required for Open3, as we
65        // require writable rights for the latter flags already.
66        if flags.intersects(fio::OpenFlags::RIGHT_WRITABLE | fio::OpenFlags::TRUNCATE) {
67            let () = send_on_open_with_error(describe, server_end, zx::Status::NOT_SUPPORTED);
68            return;
69        }
70        // The VFS should disallow file creation since we cannot serve a mutable connection.
71        assert!(!flags.intersects(fio::OpenFlags::CREATE | fio::OpenFlags::CREATE_IF_ABSENT));
72
73        // Handle case where the request is for this directory itself (e.g. ".").
74        if path.is_empty() {
75            flags.to_object_request(server_end).handle(|object_request| {
76                // NOTE: Some older CTF tests still rely on being able to use the APPEND flag in
77                // some cases, so we cannot check this flag above. Appending is still not possible.
78                // As we plan to remove this method entirely, we can just leave this for now.
79                if flags.intersects(fio::OpenFlags::APPEND) {
80                    return Err(zx::Status::NOT_SUPPORTED);
81                }
82
83                object_request
84                    .take()
85                    .create_connection_sync::<ImmutableConnection<_>, _>(scope, self, flags);
86                Ok(())
87            });
88            return;
89        }
90
91        // `path` is relative, and may include a trailing slash.
92        let file_path = format!(
93            "{}{}",
94            self.path,
95            path.as_ref().strip_suffix('/').unwrap_or_else(|| path.as_ref())
96        );
97
98        if let Some(blob) = self.root_dir.non_meta_files.get(&file_path) {
99            let () = self
100                .root_dir
101                .non_meta_storage
102                .deprecated_open(blob, flags, scope, server_end)
103                .unwrap_or_else(|e| {
104                    error!("Error forwarding content blob open to blobfs: {:#}", anyhow!(e))
105                });
106            return;
107        }
108
109        if let Some(subdir) = self.root_dir.get_non_meta_subdir(file_path + "/") {
110            let () = subdir.deprecated_open(scope, flags, vfs::Path::dot(), server_end);
111            return;
112        }
113
114        let () = send_on_open_with_error(describe, server_end, zx::Status::NOT_FOUND);
115    }
116
117    fn open(
118        self: Arc<Self>,
119        scope: ExecutionScope,
120        path: vfs::Path,
121        flags: fio::Flags,
122        object_request: ObjectRequestRef<'_>,
123    ) -> Result<(), zx::Status> {
124        if !flags.difference(crate::ALLOWED_FLAGS).is_empty() {
125            return Err(zx::Status::NOT_SUPPORTED);
126        }
127
128        // Handle case where the request is for this directory itself (e.g. ".").
129        if path.is_empty() {
130            // `ImmutableConnection` checks that only directory flags are specified.
131            object_request
132                .take()
133                .create_connection_sync::<ImmutableConnection<_>, _>(scope, self, flags);
134            return Ok(());
135        }
136
137        // `path` is relative, and may include a trailing slash.
138        let file_path = format!(
139            "{}{}",
140            self.path,
141            path.as_ref().strip_suffix('/').unwrap_or_else(|| path.as_ref())
142        );
143
144        if let Some(blob) = self.root_dir.non_meta_files.get(&file_path) {
145            if path.is_dir() {
146                return Err(zx::Status::NOT_DIR);
147            }
148            return self.root_dir.non_meta_storage.open(blob, flags, scope, object_request);
149        }
150
151        if let Some(subdir) = self.root_dir.get_non_meta_subdir(file_path + "/") {
152            return subdir.open(scope, vfs::Path::dot(), flags, object_request);
153        }
154
155        Err(zx::Status::NOT_FOUND)
156    }
157
158    async fn read_dirents<'a>(
159        &'a self,
160        pos: &'a TraversalPosition,
161        sink: Box<(dyn vfs::directory::dirents_sink::Sink + 'static)>,
162    ) -> Result<
163        (TraversalPosition, Box<(dyn vfs::directory::dirents_sink::Sealed + 'static)>),
164        zx::Status,
165    > {
166        vfs::directory::read_dirents::read_dirents(
167            &crate::get_dir_children(
168                self.root_dir.non_meta_files.keys().map(|s| s.as_str()),
169                &self.path,
170            ),
171            pos,
172            sink,
173        )
174        .await
175    }
176
177    fn register_watcher(
178        self: Arc<Self>,
179        _: ExecutionScope,
180        _: fio::WatchMask,
181        _: vfs::directory::entry_container::DirectoryWatcher,
182    ) -> Result<(), zx::Status> {
183        Err(zx::Status::NOT_SUPPORTED)
184    }
185
186    // `register_watcher` is unsupported so no need to do anything here.
187    fn unregister_watcher(self: Arc<Self>, _: usize) {}
188}
189
190#[cfg(test)]
191mod tests {
192    use super::*;
193    use assert_matches::assert_matches;
194    use fuchsia_fs::directory::{DirEntry, DirentKind};
195    use fuchsia_pkg_testing::blobfs::Fake as FakeBlobfs;
196    use fuchsia_pkg_testing::PackageBuilder;
197    use futures::prelude::*;
198
199    struct TestEnv {
200        _blobfs_fake: FakeBlobfs,
201    }
202
203    impl TestEnv {
204        async fn new() -> (Self, fio::DirectoryProxy) {
205            let pkg = PackageBuilder::new("pkg")
206                .add_resource_at("dir0/dir1/file", "bloblob".as_bytes())
207                .build()
208                .await
209                .unwrap();
210            let (metafar_blob, content_blobs) = pkg.contents();
211            let (blobfs_fake, blobfs_client) = FakeBlobfs::new();
212            blobfs_fake.add_blob(metafar_blob.merkle, metafar_blob.contents);
213            for (hash, bytes) in content_blobs {
214                blobfs_fake.add_blob(hash, bytes);
215            }
216            let root_dir = RootDir::new(blobfs_client, metafar_blob.merkle).await.unwrap();
217            let sub_dir = NonMetaSubdir::new(root_dir, "dir0/".to_string());
218            (Self { _blobfs_fake: blobfs_fake }, vfs::directory::serve_read_only(sub_dir))
219        }
220    }
221
222    /// Ensure connections to a [`NonMetaSubdir`] cannot be created as mutable (i.e. with
223    /// [`fio::PERM_WRITABLE`]) This ensures that the VFS will disallow any attempts to create a new
224    /// file/directory, modify the attributes of any nodes, or open any files as writable.
225    #[fuchsia_async::run_singlethreaded(test)]
226    async fn non_meta_subdir_cannot_be_served_as_mutable() {
227        let pkg = PackageBuilder::new("pkg")
228            .add_resource_at("dir0/dir1/file", "bloblob".as_bytes())
229            .build()
230            .await
231            .unwrap();
232        let (metafar_blob, content_blobs) = pkg.contents();
233        let (blobfs_fake, blobfs_client) = FakeBlobfs::new();
234        blobfs_fake.add_blob(metafar_blob.merkle, metafar_blob.contents);
235        for (hash, bytes) in content_blobs {
236            blobfs_fake.add_blob(hash, bytes);
237        }
238        let root_dir = RootDir::new(blobfs_client, metafar_blob.merkle).await.unwrap();
239        let sub_dir = NonMetaSubdir::new(root_dir, "dir0/".to_string());
240        let proxy = vfs::directory::serve(sub_dir, fio::PERM_WRITABLE);
241        assert_matches!(
242            proxy.take_event_stream().try_next().await,
243            Err(fidl::Error::ClientChannelClosed { status: zx::Status::NOT_SUPPORTED, .. })
244        );
245    }
246
247    #[fuchsia_async::run_singlethreaded(test)]
248    async fn non_meta_subdir_readdir() {
249        let (_env, sub_dir) = TestEnv::new().await;
250        assert_eq!(
251            fuchsia_fs::directory::readdir_inclusive(&sub_dir).await.unwrap(),
252            vec![
253                DirEntry { name: ".".to_string(), kind: DirentKind::Directory },
254                DirEntry { name: "dir1".to_string(), kind: DirentKind::Directory }
255            ]
256        );
257    }
258
259    #[fuchsia_async::run_singlethreaded(test)]
260    async fn non_meta_subdir_get_attributes() {
261        let (_env, sub_dir) = TestEnv::new().await;
262        let (mutable_attributes, immutable_attributes) =
263            sub_dir.get_attributes(fio::NodeAttributesQuery::all()).await.unwrap().unwrap();
264        assert_eq!(
265            fio::NodeAttributes2 { mutable_attributes, immutable_attributes },
266            immutable_attributes!(
267                fio::NodeAttributesQuery::all(),
268                Immutable {
269                    protocols: fio::NodeProtocolKinds::DIRECTORY,
270                    abilities: crate::DIRECTORY_ABILITIES,
271                    id: 1,
272                }
273            )
274        );
275    }
276
277    #[fuchsia_async::run_singlethreaded(test)]
278    async fn non_meta_subdir_watch_not_supported() {
279        let (_env, sub_dir) = TestEnv::new().await;
280        let (_client, server) = fidl::endpoints::create_endpoints();
281        let status =
282            zx::Status::from_raw(sub_dir.watch(fio::WatchMask::empty(), 0, server).await.unwrap());
283        assert_eq!(status, zx::Status::NOT_SUPPORTED);
284    }
285
286    #[fuchsia_async::run_singlethreaded(test)]
287    async fn non_meta_subdir_open_directory() {
288        let (_env, sub_dir) = TestEnv::new().await;
289        for path in ["dir1", "dir1/"] {
290            let proxy = fuchsia_fs::directory::open_directory(&sub_dir, path, fio::PERM_READABLE)
291                .await
292                .unwrap();
293            assert_eq!(
294                fuchsia_fs::directory::readdir(&proxy).await.unwrap(),
295                vec![DirEntry { name: "file".to_string(), kind: DirentKind::File }]
296            );
297        }
298    }
299
300    #[fuchsia_async::run_singlethreaded(test)]
301    async fn non_meta_subdir_open_file() {
302        let (_env, sub_dir) = TestEnv::new().await;
303        let proxy = fuchsia_fs::directory::open_file(&sub_dir, "dir1/file", fio::PERM_READABLE)
304            .await
305            .unwrap();
306        assert_eq!(fuchsia_fs::file::read(&proxy).await.unwrap(), b"bloblob".to_vec())
307    }
308
309    #[fuchsia_async::run_singlethreaded(test)]
310    async fn non_meta_subdir_deprecated_open_directory() {
311        let (_env, sub_dir) = TestEnv::new().await;
312        for path in ["dir1", "dir1/"] {
313            let (proxy, server_end) = fidl::endpoints::create_proxy::<fio::DirectoryMarker>();
314            sub_dir
315                .deprecated_open(
316                    fio::OpenFlags::RIGHT_READABLE,
317                    Default::default(),
318                    path,
319                    server_end.into_channel().into(),
320                )
321                .unwrap();
322            assert_eq!(
323                fuchsia_fs::directory::readdir(&proxy).await.unwrap(),
324                vec![DirEntry { name: "file".to_string(), kind: DirentKind::File }]
325            );
326        }
327    }
328
329    #[fuchsia_async::run_singlethreaded(test)]
330    async fn non_meta_subdir_deprecated_open_file() {
331        let (_env, sub_dir) = TestEnv::new().await;
332        let (proxy, server_end) = fidl::endpoints::create_proxy::<fio::FileMarker>();
333        sub_dir
334            .deprecated_open(
335                fio::OpenFlags::RIGHT_READABLE,
336                Default::default(),
337                "dir1/file",
338                server_end.into_channel().into(),
339            )
340            .unwrap();
341        assert_eq!(fuchsia_fs::file::read(&proxy).await.unwrap(), b"bloblob".to_vec());
342    }
343}