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 deprecated_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.deprecated_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 open(
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.open(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
217 struct TestEnv {
218 _blobfs_fake: FakeBlobfs,
219 }
220
221 impl TestEnv {
222 async fn new() -> (Self, fio::DirectoryProxy) {
223 let pkg = PackageBuilder::new("pkg")
224 .add_resource_at("meta/dir/file", &b"contents"[..])
225 .build()
226 .await
227 .unwrap();
228 let (metafar_blob, _) = pkg.contents();
229 let (blobfs_fake, blobfs_client) = FakeBlobfs::new();
230 blobfs_fake.add_blob(metafar_blob.merkle, metafar_blob.contents);
231 let root_dir = RootDir::new(blobfs_client, metafar_blob.merkle).await.unwrap();
232 let meta_as_dir = MetaAsDir::new(root_dir);
233 (Self { _blobfs_fake: blobfs_fake }, vfs::directory::serve_read_only(meta_as_dir))
234 }
235 }
236
237 #[fuchsia_async::run_singlethreaded(test)]
242 async fn meta_as_dir_cannot_be_served_as_mutable() {
243 let pkg = PackageBuilder::new("pkg")
244 .add_resource_at("meta/dir/file", &b"contents"[..])
245 .build()
246 .await
247 .unwrap();
248 let (metafar_blob, _) = pkg.contents();
249 let (blobfs_fake, blobfs_client) = FakeBlobfs::new();
250 blobfs_fake.add_blob(metafar_blob.merkle, metafar_blob.contents);
251 let meta_as_dir =
252 MetaAsDir::new(RootDir::new(blobfs_client, metafar_blob.merkle).await.unwrap());
253 for flags in [fio::PERM_WRITABLE, fio::PERM_EXECUTABLE] {
254 let proxy = vfs::directory::serve(meta_as_dir.clone(), flags);
255 assert_matches!(
256 proxy.take_event_stream().try_next().await,
257 Err(fidl::Error::ClientChannelClosed { status: zx::Status::NOT_SUPPORTED, .. })
258 );
259 }
260 }
261
262 #[fuchsia_async::run_singlethreaded(test)]
263 async fn meta_as_dir_readdir() {
264 let (_env, meta_as_dir) = TestEnv::new().await;
265 assert_eq!(
266 fuchsia_fs::directory::readdir_inclusive(&meta_as_dir).await.unwrap(),
267 vec![
268 DirEntry { name: ".".to_string(), kind: DirentKind::Directory },
269 DirEntry { name: "contents".to_string(), kind: DirentKind::File },
270 DirEntry { name: "dir".to_string(), kind: DirentKind::Directory },
271 DirEntry { name: "fuchsia.abi".to_string(), kind: DirentKind::Directory },
272 DirEntry { name: "package".to_string(), kind: DirentKind::File }
273 ]
274 );
275 }
276
277 #[fuchsia_async::run_singlethreaded(test)]
278 async fn meta_as_dir_get_attributes() {
279 let (_env, meta_as_dir) = TestEnv::new().await;
280 let (mutable_attributes, immutable_attributes) =
281 meta_as_dir.get_attributes(fio::NodeAttributesQuery::all()).await.unwrap().unwrap();
282 assert_eq!(
283 fio::NodeAttributes2 { mutable_attributes, immutable_attributes },
284 immutable_attributes!(
285 fio::NodeAttributesQuery::all(),
286 Immutable {
287 protocols: fio::NodeProtocolKinds::DIRECTORY,
288 abilities: crate::DIRECTORY_ABILITIES,
289 content_size: 4,
290 storage_size: 4,
291 id: 1,
292 }
293 )
294 );
295 }
296
297 #[fuchsia_async::run_singlethreaded(test)]
298 async fn meta_as_dir_watch_not_supported() {
299 let (_env, meta_as_dir) = TestEnv::new().await;
300 let (_client, server) = fidl::endpoints::create_endpoints();
301 let status = zx::Status::from_raw(
302 meta_as_dir.watch(fio::WatchMask::empty(), 0, server).await.unwrap(),
303 );
304 assert_eq!(status, zx::Status::NOT_SUPPORTED);
305 }
306
307 #[fuchsia_async::run_singlethreaded(test)]
308 async fn meta_as_dir_open_file() {
309 let (_env, meta_as_dir) = TestEnv::new().await;
310 let proxy = fuchsia_fs::directory::open_file(&meta_as_dir, "dir/file", fio::PERM_READABLE)
311 .await
312 .unwrap();
313 assert_eq!(fuchsia_fs::file::read(&proxy).await.unwrap(), b"contents".to_vec());
314 }
315
316 #[fuchsia_async::run_singlethreaded(test)]
317 async fn meta_as_dir_open_directory() {
318 let (_env, meta_as_dir) = TestEnv::new().await;
319 for path in ["dir", "dir/"] {
320 let proxy =
321 fuchsia_fs::directory::open_directory(&meta_as_dir, path, fio::PERM_READABLE)
322 .await
323 .unwrap();
324 assert_eq!(
325 fuchsia_fs::directory::readdir(&proxy).await.unwrap(),
326 vec![DirEntry { name: "file".to_string(), kind: DirentKind::File }]
327 );
328 }
329 }
330
331 #[fuchsia_async::run_singlethreaded(test)]
332 async fn meta_as_dir_deprecated_open_file() {
333 let (_env, meta_as_dir) = TestEnv::new().await;
334 let (proxy, server_end) = fidl::endpoints::create_proxy::<fio::FileMarker>();
335 meta_as_dir
336 .deprecated_open(
337 fio::OpenFlags::RIGHT_READABLE,
338 Default::default(),
339 "dir/file",
340 server_end.into_channel().into(),
341 )
342 .unwrap();
343 assert_eq!(fuchsia_fs::file::read(&proxy).await.unwrap(), b"contents".to_vec());
344 }
345
346 #[fuchsia_async::run_singlethreaded(test)]
347 async fn meta_as_dir_deprecated_open_directory() {
348 let (_env, meta_as_dir) = TestEnv::new().await;
349 for path in ["dir", "dir/"] {
350 let (proxy, server_end) = fidl::endpoints::create_proxy::<fio::DirectoryMarker>();
351 meta_as_dir
352 .deprecated_open(
353 fio::OpenFlags::RIGHT_READABLE,
354 Default::default(),
355 path,
356 server_end.into_channel().into(),
357 )
358 .unwrap();
359
360 assert_eq!(
361 fuchsia_fs::directory::readdir(&proxy).await.unwrap(),
362 vec![DirEntry { name: "file".to_string(), kind: DirentKind::File }]
363 );
364 }
365 }
366}