1use crate::meta_as_dir::MetaAsDir;
6use crate::meta_subdir::MetaSubdir;
7use crate::non_meta_subdir::NonMetaSubdir;
8use crate::{Error, NonMetaStorageError, usize_to_u64_safe};
9use fidl_fuchsia_io as fio;
10use fuchsia_pkg::MetaContents;
11use log::error;
12use packed::PackedMap;
13use std::sync::Arc;
14use vfs::directory::entry::{EntryInfo, OpenRequest};
15use vfs::directory::immutable::connection::ImmutableConnection;
16use vfs::directory::traversal_position::TraversalPosition;
17use vfs::execution_scope::ExecutionScope;
18use vfs::file::vmo::VmoFile;
19use vfs::{ObjectRequestRef, ProtocolsExt as _, immutable_attributes};
20
21#[derive(Debug)]
23pub struct RootDir<S> {
24 pub(crate) non_meta_storage: S,
25 pub(crate) hash: fuchsia_hash::Hash,
26 pub(crate) meta_files: PackedMap<str, MetaFileLocation>,
28 pub(crate) non_meta_files: PackedMap<str, fuchsia_hash::Hash>,
30 pub(crate) meta_far_vmo: zx::Vmo,
31 dropper: Option<Box<dyn crate::OnRootDirDrop>>,
32}
33
34impl<S: crate::NonMetaStorage> RootDir<S> {
35 pub async fn new(non_meta_storage: S, hash: fuchsia_hash::Hash) -> Result<Arc<Self>, Error> {
38 Ok(Arc::new(Self::new_raw(non_meta_storage, hash, None).await?))
39 }
40
41 pub async fn new_with_dropper(
45 non_meta_storage: S,
46 hash: fuchsia_hash::Hash,
47 dropper: Box<dyn crate::OnRootDirDrop>,
48 ) -> Result<Arc<Self>, Error> {
49 Ok(Arc::new(Self::new_raw(non_meta_storage, hash, Some(dropper)).await?))
50 }
51
52 pub async fn new_raw(
57 non_meta_storage: S,
58 hash: fuchsia_hash::Hash,
59 dropper: Option<Box<dyn crate::OnRootDirDrop>>,
60 ) -> Result<Self, Error> {
61 let meta_far_vmo = non_meta_storage.get_blob_vmo(&hash).await.map_err(|e| {
62 if e.is_not_found_error() { Error::MissingMetaFar } else { Error::OpenMetaFar(e) }
63 })?;
64 let (meta_files, non_meta_files) = load_package_metadata(&meta_far_vmo)?;
65
66 Ok(RootDir { non_meta_storage, hash, meta_files, non_meta_files, meta_far_vmo, dropper })
67 }
68
69 pub fn set_dropper(
71 &mut self,
72 dropper: Box<dyn crate::OnRootDirDrop>,
73 ) -> Result<(), Box<dyn crate::OnRootDirDrop>> {
74 match self.dropper {
75 Some(_) => Err(dropper),
76 None => {
77 self.dropper = Some(dropper);
78 Ok(())
79 }
80 }
81 }
82
83 pub async fn read_file(&self, path: &str) -> Result<Vec<u8>, ReadFileError> {
86 if let Some(hash) = self.non_meta_files.get(path) {
87 self.non_meta_storage.read_blob(hash).await.map_err(ReadFileError::ReadBlob)
88 } else if let Some(location) = self.meta_files.get(path) {
89 self.meta_far_vmo
90 .read_to_vec(location.offset, location.length)
91 .map_err(ReadFileError::ReadMetaFile)
92 } else {
93 Err(ReadFileError::NoFileAtPath { path: path.to_string() })
94 }
95 }
96
97 pub fn has_file(&self, path: &str) -> bool {
100 self.non_meta_files.contains_key(path) || self.meta_files.contains_key(path)
101 }
102
103 pub fn hash(&self) -> &fuchsia_hash::Hash {
105 &self.hash
106 }
107
108 pub fn external_file_hashes(&self) -> impl ExactSizeIterator<Item = &fuchsia_hash::Hash> {
111 self.non_meta_files.values()
112 }
113
114 pub async fn path(&self) -> Result<fuchsia_pkg::PackagePath, PathError> {
116 Ok(fuchsia_pkg::MetaPackage::deserialize(&self.read_file("meta/package").await?[..])?
117 .into_path())
118 }
119
120 pub async fn subpackages(&self) -> Result<fuchsia_pkg::MetaSubpackages, SubpackagesError> {
122 let contents = match self.read_file(fuchsia_pkg::MetaSubpackages::PATH).await {
123 Ok(contents) => contents,
124 Err(ReadFileError::NoFileAtPath { .. }) => {
125 return Ok(fuchsia_pkg::MetaSubpackages::default());
126 }
127 Err(e) => Err(e)?,
128 };
129
130 Ok(fuchsia_pkg::MetaSubpackages::deserialize(&*contents)?)
131 }
132
133 fn create_meta_as_file(&self) -> Result<Arc<VmoFile>, zx::Status> {
135 let file_contents = self.hash.to_string();
136 let vmo = zx::Vmo::create(usize_to_u64_safe(file_contents.len()))?;
137 let () = vmo.write(file_contents.as_bytes(), 0)?;
138 Ok(VmoFile::new_with_inode(vmo, 1))
139 }
140
141 pub(crate) fn get_meta_file(&self, path: &str) -> Result<Option<Arc<VmoFile>>, zx::Status> {
143 assert_eq!(zx::system_get_page_size(), 4096);
151
152 let location = match self.meta_files.get(path) {
153 Some(location) => location,
154 None => return Ok(None),
155 };
156 let vmo = self
157 .meta_far_vmo
158 .create_child(
159 zx::VmoChildOptions::SNAPSHOT_AT_LEAST_ON_WRITE | zx::VmoChildOptions::NO_WRITE,
160 location.offset,
161 location.length,
162 )
163 .map_err(|e| {
164 error!("Error creating child vmo for meta file {:?}", e);
165 zx::Status::INTERNAL
166 })?;
167
168 Ok(Some(VmoFile::new_with_inode(vmo, 1)))
169 }
170
171 pub(crate) fn get_meta_subdir(self: &Arc<Self>, path: String) -> Option<Arc<MetaSubdir<S>>> {
173 debug_assert!(path.ends_with("/"));
174 if let Some((k, _)) = self.meta_files.range(path.as_str()..).next()
175 && k.starts_with(&path)
176 {
177 return Some(MetaSubdir::new(self.clone(), path));
178 }
179 None
180 }
181
182 pub(crate) fn get_non_meta_subdir(
184 self: &Arc<Self>,
185 path: String,
186 ) -> Option<Arc<NonMetaSubdir<S>>> {
187 debug_assert!(path.ends_with("/"));
188 if let Some((k, _)) = self.non_meta_files.range(path.as_str()..).next()
189 && k.starts_with(&path)
190 {
191 return Some(NonMetaSubdir::new(self.clone(), path));
192 }
193 None
194 }
195}
196
197#[derive(thiserror::Error, Debug)]
198pub enum ReadFileError {
199 #[error("reading blob")]
200 ReadBlob(#[source] NonMetaStorageError),
201
202 #[error("reading meta file")]
203 ReadMetaFile(#[source] zx::Status),
204
205 #[error("no file exists at path: {path:?}")]
206 NoFileAtPath { path: String },
207}
208
209#[derive(thiserror::Error, Debug)]
210pub enum SubpackagesError {
211 #[error("reading manifest")]
212 Read(#[from] ReadFileError),
213
214 #[error("parsing manifest")]
215 Parse(#[from] fuchsia_pkg::MetaSubpackagesError),
216}
217
218#[derive(thiserror::Error, Debug)]
219pub enum PathError {
220 #[error("reading meta/package")]
221 Read(#[from] ReadFileError),
222
223 #[error("parsing meta/package")]
224 Parse(#[from] fuchsia_pkg::MetaPackageError),
225}
226
227impl<S: crate::NonMetaStorage> vfs::directory::entry::DirectoryEntry for RootDir<S> {
228 fn open_entry(self: Arc<Self>, request: OpenRequest<'_>) -> Result<(), zx::Status> {
229 request.open_dir(self)
230 }
231}
232
233impl<S: crate::NonMetaStorage> vfs::directory::entry::GetEntryInfo for RootDir<S> {
234 fn entry_info(&self) -> EntryInfo {
235 EntryInfo::new(fio::INO_UNKNOWN, fio::DirentType::Directory)
236 }
237}
238
239impl<S: crate::NonMetaStorage> vfs::node::Node for RootDir<S> {
240 async fn get_attributes(
241 &self,
242 requested_attributes: fio::NodeAttributesQuery,
243 ) -> Result<fio::NodeAttributes2, zx::Status> {
244 Ok(immutable_attributes!(
245 requested_attributes,
246 Immutable {
247 protocols: fio::NodeProtocolKinds::DIRECTORY,
248 abilities: crate::DIRECTORY_ABILITIES,
249 id: 1,
250 }
251 ))
252 }
253}
254
255impl<S: crate::NonMetaStorage> vfs::directory::entry_container::Directory for RootDir<S> {
256 fn open(
257 self: Arc<Self>,
258 scope: ExecutionScope,
259 path: vfs::Path,
260 flags: fio::Flags,
261 object_request: ObjectRequestRef<'_>,
262 ) -> Result<(), zx::Status> {
263 if !flags.difference(crate::ALLOWED_FLAGS).is_empty() {
264 return Err(zx::Status::NOT_SUPPORTED);
265 }
266
267 if path.is_empty() {
269 object_request
271 .take()
272 .create_connection_sync::<ImmutableConnection<_>, _>(scope, self, flags);
273 return Ok(());
274 }
275
276 let canonical_path = path.as_ref().strip_suffix('/').unwrap_or_else(|| path.as_ref());
278
279 if canonical_path == "meta" {
280 let open_meta_as_dir = flags.is_dir_allowed() && !flags.is_file_allowed();
289 if !open_meta_as_dir {
290 if path.is_dir() {
291 return Err(zx::Status::NOT_DIR);
292 }
293 let file = self.create_meta_as_file().map_err(|e| {
294 error!("Error creating the meta file: {:?}", e);
295 zx::Status::INTERNAL
296 })?;
297 return vfs::file::serve(file, scope, &flags, object_request);
298 }
299 return MetaAsDir::new(self).open(scope, vfs::Path::dot(), flags, object_request);
300 }
301
302 if canonical_path.starts_with("meta/") {
303 if let Some(file) = self.get_meta_file(canonical_path)? {
304 if path.is_dir() {
305 return Err(zx::Status::NOT_DIR);
306 }
307 return vfs::file::serve(file, scope, &flags, object_request);
308 }
309
310 if let Some(subdir) = self.get_meta_subdir(canonical_path.to_string() + "/") {
311 return subdir.open(scope, vfs::Path::dot(), flags, object_request);
312 }
313 return Err(zx::Status::NOT_FOUND);
314 }
315
316 if let Some(blob) = self.non_meta_files.get(canonical_path) {
317 if path.is_dir() {
318 return Err(zx::Status::NOT_DIR);
319 }
320 return self.non_meta_storage.open(blob, flags, scope, object_request);
321 }
322
323 if let Some(subdir) = self.get_non_meta_subdir(canonical_path.to_string() + "/") {
324 return subdir.open(scope, vfs::Path::dot(), flags, object_request);
325 }
326
327 Err(zx::Status::NOT_FOUND)
328 }
329
330 async fn read_dirents(
331 &self,
332 pos: &TraversalPosition,
333 sink: Box<dyn vfs::directory::dirents_sink::Sink + 'static>,
334 ) -> Result<
335 (TraversalPosition, Box<dyn vfs::directory::dirents_sink::Sealed + 'static>),
336 zx::Status,
337 > {
338 vfs::directory::read_dirents::read_dirents(
339 &crate::get_dir_children(self.non_meta_files.keys().chain(["meta/placeholder"]), ""),
341 pos,
342 sink,
343 )
344 }
345
346 fn register_watcher(
347 self: Arc<Self>,
348 _: ExecutionScope,
349 _: fio::WatchMask,
350 _: vfs::directory::entry_container::DirectoryWatcher,
351 ) -> Result<(), zx::Status> {
352 Err(zx::Status::NOT_SUPPORTED)
353 }
354
355 fn unregister_watcher(self: Arc<Self>, _: usize) {}
357}
358
359#[allow(clippy::type_complexity)]
360fn load_package_metadata(
361 meta_far_vmo: &zx::Vmo,
362) -> Result<(PackedMap<str, MetaFileLocation>, PackedMap<str, fuchsia_hash::Hash>), Error> {
363 let stream =
364 zx::Stream::create(zx::StreamOptions::MODE_READ, meta_far_vmo, 0).map_err(|e| {
365 Error::OpenMetaFar(NonMetaStorageError::ReadBlob(
366 fuchsia_fs::file::ReadError::ReadError(e),
367 ))
368 })?;
369
370 let mut reader = fuchsia_archive::Reader::new(stream).map_err(Error::ArchiveReader)?;
371 let mut meta_files = {
372 let reader_list = reader.list();
373 PackedMap::<str, MetaFileLocation>::with_capacity(
374 reader_list.len(),
375 reader_list.map(|entry| entry.path().len()).sum(),
376 )
377 };
378 let reader_list = reader.list();
379 for entry in reader_list {
382 let path = std::str::from_utf8(entry.path())
383 .map_err(|source| Error::NonUtf8MetaEntry { source, path: entry.path().to_owned() })?;
384 if path.starts_with("meta/") {
385 for (i, _) in path.match_indices('/').skip(1) {
386 if meta_files.contains_key(&path[..i]) {
387 return Err(Error::FileDirectoryCollision { path: path[..i].to_string() });
388 }
389 }
390 meta_files
391 .append_or_update(
392 path,
393 MetaFileLocation { offset: entry.offset(), length: entry.length() },
394 )
395 .expect("FAR entries are sorted");
396 }
397 }
398
399 let meta_contents_bytes =
400 reader.read_file(b"meta/contents").map_err(Error::ReadMetaContents)?;
401
402 let mut non_meta_files = MetaContents::deserialize(&meta_contents_bytes[..])
403 .map_err(Error::DeserializeMetaContents)?
404 .into_contents();
405
406 non_meta_files.shrink_to_fit();
409
410 Ok((meta_files, non_meta_files))
411}
412
413#[derive(Clone, Copy, Debug, PartialEq, Eq)]
415pub(crate) struct MetaFileLocation {
416 offset: u64,
417 length: u64,
418}
419
420#[cfg(test)]
421mod tests {
422 use super::*;
423 use assert_matches::assert_matches;
424 use fidl::endpoints::create_proxy;
425 use fuchsia_fs::directory::{DirEntry, DirentKind};
426 use fuchsia_pkg_testing::PackageBuilder;
427 use fuchsia_pkg_testing::blobfs::Fake as FakeBlobfs;
428 use futures::TryStreamExt as _;
429 use pretty_assertions::assert_eq;
430 use std::io::Cursor;
431
432 struct TestEnv {
433 _blobfs_fake: FakeBlobfs,
434 root_dir: Arc<RootDir<blobfs::Client>>,
435 }
436
437 impl TestEnv {
438 async fn with_subpackages(
439 subpackages_content: Option<&[u8]>,
440 ) -> (Self, Arc<RootDir<blobfs::Client>>) {
441 let mut pkg = PackageBuilder::new("base-package-0")
442 .add_resource_at("resource", "blob-contents".as_bytes())
443 .add_resource_at("dir/file", "bloblob".as_bytes())
444 .add_resource_at("meta/file", "meta-contents0".as_bytes())
445 .add_resource_at("meta/dir/file", "meta-contents1".as_bytes());
446 if let Some(subpackages_content) = subpackages_content {
447 pkg = pkg.add_resource_at(fuchsia_pkg::MetaSubpackages::PATH, subpackages_content);
448 }
449 let pkg = pkg.build().await.unwrap();
450 let (metafar_blob, content_blobs) = pkg.contents();
451 let (blobfs_fake, blobfs_client) = FakeBlobfs::new();
452 blobfs_fake.add_blob(metafar_blob.merkle, metafar_blob.contents);
453 for (hash, bytes) in content_blobs {
454 blobfs_fake.add_blob(hash, bytes);
455 }
456
457 let root_dir = RootDir::new(blobfs_client, metafar_blob.merkle).await.unwrap();
458 (Self { _blobfs_fake: blobfs_fake, root_dir: root_dir.clone() }, root_dir)
459 }
460
461 async fn new() -> (Self, fio::DirectoryProxy) {
462 let (env, root) = Self::with_subpackages(None).await;
463 (env, vfs::directory::serve_read_only(root))
464 }
465 }
466
467 #[fuchsia::test]
468 async fn new_missing_meta_far_error() {
469 let (_blobfs_fake, blobfs_client) = FakeBlobfs::new();
470 assert_matches!(
471 RootDir::new(blobfs_client, [0; 32].into()).await,
472 Err(Error::MissingMetaFar)
473 );
474 }
475
476 #[fuchsia::test]
477 async fn new_rejects_invalid_utf8() {
478 let (blobfs_fake, blobfs_client) = FakeBlobfs::new();
479 let mut meta_far = vec![];
480 let () = fuchsia_archive::write(
481 &mut meta_far,
482 std::collections::BTreeMap::from_iter([(
483 b"\xff",
484 (0, Box::new("".as_bytes()) as Box<dyn std::io::Read>),
485 )]),
486 )
487 .unwrap();
488 let hash = fuchsia_merkle::root_from_slice(&meta_far);
489 let () = blobfs_fake.add_blob(hash, meta_far);
490
491 assert_matches!(
492 RootDir::new(blobfs_client, hash).await,
493 Err(Error::NonUtf8MetaEntry{path, ..})
494 if path == vec![255]
495 );
496 }
497
498 #[fuchsia::test]
499 async fn new_initializes_maps() {
500 let (_env, root_dir) = TestEnv::with_subpackages(None).await;
501
502 let meta_files = PackedMap::from([
503 ("meta/contents", MetaFileLocation { offset: 4096, length: 148 }),
504 ("meta/package", MetaFileLocation { offset: 20480, length: 39 }),
505 ("meta/file", MetaFileLocation { offset: 12288, length: 14 }),
506 ("meta/dir/file", MetaFileLocation { offset: 8192, length: 14 }),
507 ("meta/fuchsia.abi/abi-revision", MetaFileLocation { offset: 16384, length: 8 }),
508 ]);
509 assert_eq!(root_dir.meta_files, meta_files);
510
511 let non_meta_files = PackedMap::from([
512 (
513 "resource",
514 "bd905f783ceae4c5ba8319703d7505ab363733c2db04c52c8405603a02922b15"
515 .parse::<fuchsia_hash::Hash>()
516 .unwrap(),
517 ),
518 (
519 "dir/file",
520 "5f615dd575994fcbcc174974311d59de258d93cd523d5cb51f0e139b53c33201"
521 .parse::<fuchsia_hash::Hash>()
522 .unwrap(),
523 ),
524 ]);
525 assert_eq!(root_dir.non_meta_files, non_meta_files);
526 }
527
528 #[fuchsia::test]
529 async fn rejects_meta_file_collisions() {
530 let pkg = PackageBuilder::new("base-package-0")
531 .add_resource_at("meta/dir/file", "meta-contents0".as_bytes())
532 .build()
533 .await
534 .unwrap();
535
536 let (metafar_blob, _) = pkg.contents();
538 let mut metafar =
539 fuchsia_archive::Reader::new(Cursor::new(&metafar_blob.contents)).unwrap();
540 let mut entries = std::collections::BTreeMap::new();
541 let farentries =
542 metafar.list().map(|entry| (entry.path().to_vec(), entry.length())).collect::<Vec<_>>();
543 for (path, length) in farentries {
544 let contents = metafar.read_file(&path).unwrap();
545 entries
546 .insert(path, (length, Box::new(Cursor::new(contents)) as Box<dyn std::io::Read>));
547 }
548 let extra_contents = b"meta-contents1";
549 entries.insert(
550 b"meta/dir".to_vec(),
551 (
552 extra_contents.len() as u64,
553 Box::new(Cursor::new(extra_contents)) as Box<dyn std::io::Read>,
554 ),
555 );
556
557 let mut metafar: Vec<u8> = vec![];
558 let () = fuchsia_archive::write(&mut metafar, entries).unwrap();
559 let merkle = fuchsia_merkle::root_from_slice(&metafar);
560
561 let (blobfs_fake, blobfs_client) = FakeBlobfs::new();
563 blobfs_fake.add_blob(merkle, &metafar);
564
565 match RootDir::new(blobfs_client, merkle).await {
566 Ok(_) => panic!("this should not be reached!"),
567 Err(Error::FileDirectoryCollision { path }) => {
568 assert_eq!(path, "meta/dir".to_string());
569 }
570 Err(e) => panic!("Expected collision error, receieved {e:?}"),
571 };
572 }
573
574 #[fuchsia::test]
575 async fn read_file() {
576 let (_env, root_dir) = TestEnv::with_subpackages(None).await;
577
578 assert_eq!(root_dir.read_file("resource").await.unwrap().as_slice(), b"blob-contents");
579 assert_eq!(root_dir.read_file("meta/file").await.unwrap().as_slice(), b"meta-contents0");
580 assert_matches!(
581 root_dir.read_file("missing").await.unwrap_err(),
582 ReadFileError::NoFileAtPath{path} if path == "missing"
583 );
584 }
585
586 #[fuchsia::test]
587 async fn has_file() {
588 let (_env, root_dir) = TestEnv::with_subpackages(None).await;
589
590 assert!(root_dir.has_file("resource"));
591 assert!(root_dir.has_file("meta/file"));
592 assert_eq!(root_dir.has_file("missing"), false);
593 }
594
595 #[fuchsia::test]
596 async fn external_file_hashes() {
597 let (_env, root_dir) = TestEnv::with_subpackages(None).await;
598
599 let mut actual = root_dir.external_file_hashes().copied().collect::<Vec<_>>();
600 actual.sort();
601 assert_eq!(
602 actual,
603 vec![
604 "5f615dd575994fcbcc174974311d59de258d93cd523d5cb51f0e139b53c33201".parse().unwrap(),
605 "bd905f783ceae4c5ba8319703d7505ab363733c2db04c52c8405603a02922b15".parse().unwrap()
606 ]
607 );
608 }
609
610 #[fuchsia::test]
611 async fn path() {
612 let (_env, root_dir) = TestEnv::with_subpackages(None).await;
613
614 assert_eq!(
615 root_dir.path().await.unwrap(),
616 "base-package-0/0".parse::<fuchsia_pkg::PackagePath>().unwrap()
617 );
618 }
619
620 #[fuchsia::test]
621 async fn subpackages_present() {
622 let subpackages = fuchsia_pkg::MetaSubpackages::from_iter([(
623 fuchsia_url::RelativePackageUrl::parse("subpackage-name").unwrap(),
624 "0000000000000000000000000000000000000000000000000000000000000000".parse().unwrap(),
625 )]);
626 let mut subpackages_bytes = vec![];
627 let () = subpackages.serialize(&mut subpackages_bytes).unwrap();
628 let (_env, root_dir) = TestEnv::with_subpackages(Some(&*subpackages_bytes)).await;
629
630 assert_eq!(root_dir.subpackages().await.unwrap(), subpackages);
631 }
632
633 #[fuchsia::test]
634 async fn subpackages_absent() {
635 let (_env, root_dir) = TestEnv::with_subpackages(None).await;
636
637 assert_eq!(root_dir.subpackages().await.unwrap(), fuchsia_pkg::MetaSubpackages::default());
638 }
639
640 #[fuchsia::test]
641 async fn subpackages_error() {
642 let (_env, root_dir) = TestEnv::with_subpackages(Some(b"invalid-json")).await;
643
644 assert_matches!(root_dir.subpackages().await, Err(SubpackagesError::Parse(_)));
645 }
646
647 #[fuchsia::test]
651 async fn root_dir_cannot_be_served_as_mutable() {
652 let (_env, root_dir) = TestEnv::with_subpackages(None).await;
653 let proxy = vfs::directory::serve(root_dir, fio::PERM_WRITABLE);
654 assert_matches!(
655 proxy.take_event_stream().try_next().await,
656 Err(fidl::Error::ClientChannelClosed { status: zx::Status::NOT_SUPPORTED, .. })
657 );
658 }
659
660 #[fuchsia::test]
661 async fn root_dir_readdir() {
662 let (_env, root_dir) = TestEnv::new().await;
663 assert_eq!(
664 fuchsia_fs::directory::readdir_inclusive(&root_dir).await.unwrap(),
665 vec![
666 DirEntry { name: ".".to_string(), kind: DirentKind::Directory },
667 DirEntry { name: "dir".to_string(), kind: DirentKind::Directory },
668 DirEntry { name: "meta".to_string(), kind: DirentKind::Directory },
669 DirEntry { name: "resource".to_string(), kind: DirentKind::File }
670 ]
671 );
672 }
673
674 #[fuchsia::test]
675 async fn root_dir_get_attributes() {
676 let (_env, root_dir) = TestEnv::new().await;
677 let (mutable_attributes, immutable_attributes) =
678 root_dir.get_attributes(fio::NodeAttributesQuery::all()).await.unwrap().unwrap();
679 assert_eq!(
680 fio::NodeAttributes2 { mutable_attributes, immutable_attributes },
681 immutable_attributes!(
682 fio::NodeAttributesQuery::all(),
683 Immutable {
684 protocols: fio::NodeProtocolKinds::DIRECTORY,
685 abilities: crate::DIRECTORY_ABILITIES,
686 id: 1,
687 }
688 )
689 );
690 }
691
692 #[fuchsia::test]
693 async fn root_dir_watch_not_supported() {
694 let (_env, root_dir) = TestEnv::new().await;
695 let (_client, server) = fidl::endpoints::create_endpoints();
696 let status =
697 zx::Status::from_raw(root_dir.watch(fio::WatchMask::empty(), 0, server).await.unwrap());
698 assert_eq!(status, zx::Status::NOT_SUPPORTED);
699 }
700
701 #[fuchsia::test]
702 async fn root_dir_open_non_meta_file() {
703 let (_env, root_dir) = TestEnv::new().await;
704 let proxy = fuchsia_fs::directory::open_file(&root_dir, "resource", fio::PERM_READABLE)
705 .await
706 .unwrap();
707 assert_eq!(fuchsia_fs::file::read(&proxy).await.unwrap(), b"blob-contents".to_vec());
708 }
709
710 #[fuchsia::test]
711 async fn root_dir_open_meta_as_file() {
712 let (env, root_dir) = TestEnv::new().await;
713 let proxy =
714 fuchsia_fs::directory::open_file(&root_dir, "meta", fio::PERM_READABLE).await.unwrap();
715 assert_eq!(
716 fuchsia_fs::file::read(&proxy).await.unwrap(),
717 env.root_dir.hash.to_string().as_bytes()
718 );
719 let (cloned_proxy, server_end) = create_proxy::<fio::FileMarker>();
721 proxy.clone(server_end.into_channel().into()).unwrap();
722 assert_eq!(
723 fuchsia_fs::file::read(&cloned_proxy).await.unwrap(),
724 env.root_dir.hash.to_string().as_bytes()
725 );
726 }
727
728 #[fuchsia::test]
729 async fn root_dir_open_meta_as_dir() {
730 let (_env, root_dir) = TestEnv::new().await;
731 for path in ["meta", "meta/"] {
732 let proxy = fuchsia_fs::directory::open_directory(&root_dir, path, fio::PERM_READABLE)
733 .await
734 .unwrap();
735 assert_eq!(
736 fuchsia_fs::directory::readdir(&proxy).await.unwrap(),
737 vec![
738 DirEntry { name: "contents".to_string(), kind: DirentKind::File },
739 DirEntry { name: "dir".to_string(), kind: DirentKind::Directory },
740 DirEntry { name: "file".to_string(), kind: DirentKind::File },
741 DirEntry { name: "fuchsia.abi".to_string(), kind: DirentKind::Directory },
742 DirEntry { name: "package".to_string(), kind: DirentKind::File },
743 ]
744 );
745 let (cloned_proxy, server_end) = create_proxy::<fio::DirectoryMarker>();
747 proxy.clone(server_end.into_channel().into()).unwrap();
748 assert_eq!(
749 fuchsia_fs::directory::readdir(&cloned_proxy).await.unwrap(),
750 vec![
751 DirEntry { name: "contents".to_string(), kind: DirentKind::File },
752 DirEntry { name: "dir".to_string(), kind: DirentKind::Directory },
753 DirEntry { name: "file".to_string(), kind: DirentKind::File },
754 DirEntry { name: "fuchsia.abi".to_string(), kind: DirentKind::Directory },
755 DirEntry { name: "package".to_string(), kind: DirentKind::File },
756 ]
757 );
758 }
759 }
760
761 #[fuchsia::test]
762 async fn root_dir_open_meta_as_node() {
763 let (_env, root_dir) = TestEnv::new().await;
764 for path in ["meta", "meta/"] {
765 let proxy = fuchsia_fs::directory::open_node(
766 &root_dir,
767 path,
768 fio::Flags::PROTOCOL_NODE
769 | fio::Flags::PROTOCOL_DIRECTORY
770 | fio::Flags::PERM_GET_ATTRIBUTES,
771 )
772 .await
773 .unwrap();
774 let (mutable_attributes, immutable_attributes) = proxy
775 .get_attributes(
776 fio::NodeAttributesQuery::PROTOCOLS | fio::NodeAttributesQuery::ABILITIES,
777 )
778 .await
779 .unwrap()
780 .unwrap();
781 assert_eq!(
782 fio::NodeAttributes2 { mutable_attributes, immutable_attributes },
783 immutable_attributes!(
784 fio::NodeAttributesQuery::PROTOCOLS | fio::NodeAttributesQuery::ABILITIES,
785 Immutable {
786 protocols: fio::NodeProtocolKinds::DIRECTORY,
787 abilities: crate::DIRECTORY_ABILITIES
788 }
789 )
790 );
791 }
792 let proxy = fuchsia_fs::directory::open_node(
794 &root_dir,
795 "meta",
796 fio::Flags::PROTOCOL_NODE | fio::Flags::PERM_GET_ATTRIBUTES,
797 )
798 .await
799 .unwrap();
800 let (mutable_attributes, immutable_attributes) = proxy
801 .get_attributes(
802 fio::NodeAttributesQuery::PROTOCOLS | fio::NodeAttributesQuery::ABILITIES,
803 )
804 .await
805 .unwrap()
806 .unwrap();
807 assert_eq!(
808 fio::NodeAttributes2 { mutable_attributes, immutable_attributes },
809 immutable_attributes!(
810 fio::NodeAttributesQuery::PROTOCOLS | fio::NodeAttributesQuery::ABILITIES,
811 Immutable {
812 protocols: fio::NodeProtocolKinds::FILE,
813 abilities: fio::Abilities::READ_BYTES | fio::Abilities::GET_ATTRIBUTES,
814 }
815 )
816 );
817 }
818
819 #[fuchsia::test]
820 async fn root_dir_open_meta_file() {
821 let (_env, root_dir) = TestEnv::new().await;
822 let proxy = fuchsia_fs::directory::open_file(&root_dir, "meta/file", fio::PERM_READABLE)
823 .await
824 .unwrap();
825 assert_eq!(fuchsia_fs::file::read(&proxy).await.unwrap(), b"meta-contents0".to_vec());
826 }
827
828 #[fuchsia::test]
829 async fn root_dir_open_meta_subdir() {
830 let (_env, root_dir) = TestEnv::new().await;
831 for path in ["meta/dir", "meta/dir/"] {
832 let proxy = fuchsia_fs::directory::open_directory(&root_dir, path, fio::PERM_READABLE)
833 .await
834 .unwrap();
835 assert_eq!(
836 fuchsia_fs::directory::readdir(&proxy).await.unwrap(),
837 vec![DirEntry { name: "file".to_string(), kind: DirentKind::File }]
838 );
839 }
840 }
841
842 #[fuchsia::test]
843 async fn root_dir_open_non_meta_subdir() {
844 let (_env, root_dir) = TestEnv::new().await;
845 for path in ["dir", "dir/"] {
846 let proxy = fuchsia_fs::directory::open_directory(&root_dir, path, fio::PERM_READABLE)
847 .await
848 .unwrap();
849 assert_eq!(
850 fuchsia_fs::directory::readdir(&proxy).await.unwrap(),
851 vec![DirEntry { name: "file".to_string(), kind: DirentKind::File }]
852 );
853 }
854 }
855}