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