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