1use 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 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 if flags.contains(fio::Flags::PERM_EXECUTE) {
65 return Err(zx::Status::NOT_SUPPORTED);
66 }
67
68 if path.is_empty() {
70 object_request
72 .take()
73 .create_connection_sync::<ImmutableConnection<_>, _>(scope, self, flags);
74 return Ok(());
75 }
76
77 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 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 #[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}