1use crate::root_dir::RootDir;
6use crate::usize_to_u64_safe;
7use fidl::endpoints::ServerEnd;
8use fidl_fuchsia_io as fio;
9use std::sync::Arc;
10use vfs::common::send_on_open_with_error;
11use vfs::directory::entry::EntryInfo;
12use vfs::directory::immutable::connection::ImmutableConnection;
13use vfs::directory::traversal_position::TraversalPosition;
14use vfs::execution_scope::ExecutionScope;
15use vfs::{immutable_attributes, ObjectRequestRef, ToObjectRequest as _};
16
17pub(crate) struct MetaAsDir<S: crate::NonMetaStorage> {
18 root_dir: Arc<RootDir<S>>,
19}
20
21impl<S: crate::NonMetaStorage> MetaAsDir<S> {
22 pub(crate) fn new(root_dir: Arc<RootDir<S>>) -> Arc<Self> {
23 Arc::new(MetaAsDir { root_dir })
24 }
25}
26
27impl<S: crate::NonMetaStorage> vfs::directory::entry::GetEntryInfo for MetaAsDir<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 MetaAsDir<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 content_size: usize_to_u64_safe(self.root_dir.meta_files.len()),
44 storage_size: usize_to_u64_safe(self.root_dir.meta_files.len()),
45 id: 1,
46 }
47 ))
48 }
49}
50
51impl<S: crate::NonMetaStorage> vfs::directory::entry_container::Directory for MetaAsDir<S> {
52 fn open(
53 self: Arc<Self>,
54 scope: ExecutionScope,
55 flags: fio::OpenFlags,
56 path: vfs::Path,
57 server_end: ServerEnd<fio::NodeMarker>,
58 ) {
59 let flags = flags & !(fio::OpenFlags::POSIX_WRITABLE | fio::OpenFlags::POSIX_EXECUTABLE);
60 let describe = flags.contains(fio::OpenFlags::DESCRIBE);
61 if flags.intersects(
65 fio::OpenFlags::RIGHT_WRITABLE
66 | fio::OpenFlags::RIGHT_EXECUTABLE
67 | fio::OpenFlags::TRUNCATE,
68 ) {
69 let () = send_on_open_with_error(describe, server_end, zx::Status::NOT_SUPPORTED);
70 return;
71 }
72 assert!(!flags.intersects(fio::OpenFlags::CREATE | fio::OpenFlags::CREATE_IF_ABSENT));
74
75 if path.is_empty() {
76 flags.to_object_request(server_end).handle(|object_request| {
77 if flags.intersects(fio::OpenFlags::APPEND) {
81 return Err(zx::Status::NOT_SUPPORTED);
82 }
83
84 object_request
91 .take()
92 .create_connection_sync::<ImmutableConnection<_>, _>(scope, self, flags);
93 Ok(())
94 });
95 return;
96 }
97
98 let file_path =
107 format!("meta/{}", path.as_ref().strip_suffix('/').unwrap_or_else(|| path.as_ref()));
108
109 match self.root_dir.get_meta_file(&file_path) {
110 Ok(Some(meta_file)) => {
111 flags.to_object_request(server_end).handle(|object_request| {
112 vfs::file::serve(meta_file, scope, &flags, object_request)
113 });
114 return;
115 }
116 Ok(None) => {}
117 Err(status) => {
118 let () = send_on_open_with_error(describe, server_end, status);
119 return;
120 }
121 }
122
123 if let Some(subdir) = self.root_dir.get_meta_subdir(file_path + "/") {
124 let () = subdir.open(scope, flags, vfs::Path::dot(), server_end);
125 return;
126 }
127
128 let () = send_on_open_with_error(describe, server_end, zx::Status::NOT_FOUND);
129 }
130
131 fn open3(
132 self: Arc<Self>,
133 scope: ExecutionScope,
134 path: vfs::Path,
135 flags: fio::Flags,
136 object_request: ObjectRequestRef<'_>,
137 ) -> Result<(), zx::Status> {
138 if !flags.difference(crate::ALLOWED_FLAGS).is_empty() {
139 return Err(zx::Status::NOT_SUPPORTED);
140 }
141 if flags.contains(fio::Flags::PERM_EXECUTE) {
143 return Err(zx::Status::NOT_SUPPORTED);
144 }
145
146 if path.is_empty() {
148 object_request
156 .take()
157 .create_connection_sync::<ImmutableConnection<_>, _>(scope, self, flags);
158 return Ok(());
159 }
160
161 let file_path =
163 format!("meta/{}", path.as_ref().strip_suffix('/').unwrap_or_else(|| path.as_ref()));
164
165 if let Some(file) = self.root_dir.get_meta_file(&file_path)? {
166 if path.is_dir() {
167 return Err(zx::Status::NOT_DIR);
168 }
169 return vfs::file::serve(file, scope, &flags, object_request);
170 }
171
172 if let Some(subdir) = self.root_dir.get_meta_subdir(file_path + "/") {
173 return subdir.open3(scope, vfs::Path::dot(), flags, object_request);
174 }
175
176 Err(zx::Status::NOT_FOUND)
177 }
178
179 async fn read_dirents<'a>(
180 &'a self,
181 pos: &'a TraversalPosition,
182 sink: Box<(dyn vfs::directory::dirents_sink::Sink + 'static)>,
183 ) -> Result<
184 (TraversalPosition, Box<(dyn vfs::directory::dirents_sink::Sealed + 'static)>),
185 zx::Status,
186 > {
187 vfs::directory::read_dirents::read_dirents(
188 &crate::get_dir_children(self.root_dir.meta_files.keys().map(|s| s.as_str()), "meta/"),
189 pos,
190 sink,
191 )
192 .await
193 }
194
195 fn register_watcher(
196 self: Arc<Self>,
197 _: ExecutionScope,
198 _: fio::WatchMask,
199 _: vfs::directory::entry_container::DirectoryWatcher,
200 ) -> Result<(), zx::Status> {
201 Err(zx::Status::NOT_SUPPORTED)
202 }
203
204 fn unregister_watcher(self: Arc<Self>, _: usize) {}
206}
207
208#[cfg(test)]
209mod tests {
210 use super::*;
211 use assert_matches::assert_matches;
212 use fuchsia_fs::directory::{DirEntry, DirentKind};
213 use fuchsia_pkg_testing::blobfs::Fake as FakeBlobfs;
214 use fuchsia_pkg_testing::PackageBuilder;
215 use futures::TryStreamExt as _;
216 use vfs::directory::entry_container::Directory as _;
217
218 struct TestEnv {
219 _blobfs_fake: FakeBlobfs,
220 }
221
222 impl TestEnv {
223 async fn new() -> (Self, fio::DirectoryProxy) {
224 let pkg = PackageBuilder::new("pkg")
225 .add_resource_at("meta/dir/file", &b"contents"[..])
226 .build()
227 .await
228 .unwrap();
229 let (metafar_blob, _) = pkg.contents();
230 let (blobfs_fake, blobfs_client) = FakeBlobfs::new();
231 blobfs_fake.add_blob(metafar_blob.merkle, metafar_blob.contents);
232 let root_dir = RootDir::new(blobfs_client, metafar_blob.merkle).await.unwrap();
233 let meta_as_dir = MetaAsDir::new(root_dir);
234 (Self { _blobfs_fake: blobfs_fake }, vfs::directory::serve_read_only(meta_as_dir))
235 }
236 }
237
238 #[fuchsia_async::run_singlethreaded(test)]
243 async fn meta_as_dir_cannot_be_served_as_mutable() {
244 let pkg = PackageBuilder::new("pkg")
245 .add_resource_at("meta/dir/file", &b"contents"[..])
246 .build()
247 .await
248 .unwrap();
249 let (metafar_blob, _) = pkg.contents();
250 let (blobfs_fake, blobfs_client) = FakeBlobfs::new();
251 blobfs_fake.add_blob(metafar_blob.merkle, metafar_blob.contents);
252 let meta_as_dir =
253 MetaAsDir::new(RootDir::new(blobfs_client, metafar_blob.merkle).await.unwrap());
254 for flags in [fio::PERM_WRITABLE, fio::PERM_EXECUTABLE] {
255 let (proxy, server) = fidl::endpoints::create_proxy::<fio::DirectoryMarker>();
256 let request = flags.to_object_request(server);
257 request.handle(|request: &mut vfs::ObjectRequest| {
258 meta_as_dir.clone().open3(ExecutionScope::new(), vfs::Path::dot(), flags, request)
259 });
260 assert_matches!(
261 proxy.take_event_stream().try_next().await,
262 Err(fidl::Error::ClientChannelClosed { status: zx::Status::NOT_SUPPORTED, .. })
263 );
264 }
265 }
266
267 #[fuchsia_async::run_singlethreaded(test)]
268 async fn meta_as_dir_readdir() {
269 let (_env, meta_as_dir) = TestEnv::new().await;
270 assert_eq!(
271 fuchsia_fs::directory::readdir_inclusive(&meta_as_dir).await.unwrap(),
272 vec![
273 DirEntry { name: ".".to_string(), kind: DirentKind::Directory },
274 DirEntry { name: "contents".to_string(), kind: DirentKind::File },
275 DirEntry { name: "dir".to_string(), kind: DirentKind::Directory },
276 DirEntry { name: "fuchsia.abi".to_string(), kind: DirentKind::Directory },
277 DirEntry { name: "package".to_string(), kind: DirentKind::File }
278 ]
279 );
280 }
281
282 #[fuchsia_async::run_singlethreaded(test)]
283 async fn meta_as_dir_get_attributes() {
284 let (_env, meta_as_dir) = TestEnv::new().await;
285 let (mutable_attributes, immutable_attributes) =
286 meta_as_dir.get_attributes(fio::NodeAttributesQuery::all()).await.unwrap().unwrap();
287 assert_eq!(
288 fio::NodeAttributes2 { mutable_attributes, immutable_attributes },
289 immutable_attributes!(
290 fio::NodeAttributesQuery::all(),
291 Immutable {
292 protocols: fio::NodeProtocolKinds::DIRECTORY,
293 abilities: crate::DIRECTORY_ABILITIES,
294 content_size: 4,
295 storage_size: 4,
296 id: 1,
297 }
298 )
299 );
300 }
301
302 #[fuchsia_async::run_singlethreaded(test)]
303 async fn meta_as_dir_watch_not_supported() {
304 let (_env, meta_as_dir) = TestEnv::new().await;
305 let (_client, server) = fidl::endpoints::create_endpoints();
306 let status = zx::Status::from_raw(
307 meta_as_dir.watch(fio::WatchMask::empty(), 0, server).await.unwrap(),
308 );
309 assert_eq!(status, zx::Status::NOT_SUPPORTED);
310 }
311
312 #[fuchsia_async::run_singlethreaded(test)]
313 async fn meta_as_dir_open_file() {
314 let (_env, meta_as_dir) = TestEnv::new().await;
315 let proxy = fuchsia_fs::directory::open_file(&meta_as_dir, "dir/file", fio::PERM_READABLE)
316 .await
317 .unwrap();
318 assert_eq!(fuchsia_fs::file::read(&proxy).await.unwrap(), b"contents".to_vec());
319 }
320
321 #[fuchsia_async::run_singlethreaded(test)]
322 async fn meta_as_dir_open_directory() {
323 let (_env, meta_as_dir) = TestEnv::new().await;
324 for path in ["dir", "dir/"] {
325 let proxy =
326 fuchsia_fs::directory::open_directory(&meta_as_dir, path, fio::PERM_READABLE)
327 .await
328 .unwrap();
329 assert_eq!(
330 fuchsia_fs::directory::readdir(&proxy).await.unwrap(),
331 vec![DirEntry { name: "file".to_string(), kind: DirentKind::File }]
332 );
333 }
334 }
335
336 #[fuchsia_async::run_singlethreaded(test)]
337 async fn meta_as_dir_deprecated_open_file() {
338 let (_env, meta_as_dir) = TestEnv::new().await;
339 let (proxy, server_end) = fidl::endpoints::create_proxy::<fio::FileMarker>();
340 meta_as_dir
341 .deprecated_open(
342 fio::OpenFlags::RIGHT_READABLE,
343 Default::default(),
344 "dir/file",
345 server_end.into_channel().into(),
346 )
347 .unwrap();
348 assert_eq!(fuchsia_fs::file::read(&proxy).await.unwrap(), b"contents".to_vec());
349 }
350
351 #[fuchsia_async::run_singlethreaded(test)]
352 async fn meta_as_dir_deprecated_open_directory() {
353 let (_env, meta_as_dir) = TestEnv::new().await;
354 for path in ["dir", "dir/"] {
355 let (proxy, server_end) = fidl::endpoints::create_proxy::<fio::DirectoryMarker>();
356 meta_as_dir
357 .deprecated_open(
358 fio::OpenFlags::RIGHT_READABLE,
359 Default::default(),
360 path,
361 server_end.into_channel().into(),
362 )
363 .unwrap();
364
365 assert_eq!(
366 fuchsia_fs::directory::readdir(&proxy).await.unwrap(),
367 vec![DirEntry { name: "file".to_string(), kind: DirentKind::File }]
368 );
369 }
370 }
371}