1use crate::meta_as_dir::MetaAsDir;
6use crate::meta_subdir::MetaSubdir;
7use crate::non_meta_subdir::NonMetaSubdir;
8use crate::{usize_to_u64_safe, Error, NonMetaStorageError};
9use fidl::endpoints::ServerEnd;
10use fidl_fuchsia_io as fio;
11use fuchsia_pkg::MetaContents;
12use log::error;
13use std::collections::HashMap;
14use std::sync::Arc;
15use vfs::common::send_on_open_with_error;
16use vfs::directory::entry::{EntryInfo, OpenRequest};
17use vfs::directory::immutable::connection::ImmutableConnection;
18use vfs::directory::traversal_position::TraversalPosition;
19use vfs::execution_scope::ExecutionScope;
20use vfs::file::vmo::VmoFile;
21use vfs::{immutable_attributes, ObjectRequestRef, ProtocolsExt as _, ToObjectRequest as _};
22
23#[derive(Debug)]
25pub struct RootDir<S> {
26 pub(crate) non_meta_storage: S,
27 pub(crate) hash: fuchsia_hash::Hash,
28 pub(crate) meta_files: HashMap<String, MetaFileLocation>,
30 pub(crate) non_meta_files: HashMap<String, fuchsia_hash::Hash>,
32 pub(crate) meta_far_vmo: zx::Vmo,
33 dropper: Option<Box<dyn crate::OnRootDirDrop>>,
34}
35
36impl<S: crate::NonMetaStorage> RootDir<S> {
37 pub async fn new(non_meta_storage: S, hash: fuchsia_hash::Hash) -> Result<Arc<Self>, Error> {
40 Ok(Arc::new(Self::new_raw(non_meta_storage, hash, None).await?))
41 }
42
43 pub async fn new_with_dropper(
47 non_meta_storage: S,
48 hash: fuchsia_hash::Hash,
49 dropper: Box<dyn crate::OnRootDirDrop>,
50 ) -> Result<Arc<Self>, Error> {
51 Ok(Arc::new(Self::new_raw(non_meta_storage, hash, Some(dropper)).await?))
52 }
53
54 pub async fn new_raw(
59 non_meta_storage: S,
60 hash: fuchsia_hash::Hash,
61 dropper: Option<Box<dyn crate::OnRootDirDrop>>,
62 ) -> Result<Self, Error> {
63 let meta_far_vmo = non_meta_storage.get_blob_vmo(&hash).await.map_err(|e| {
64 if e.is_not_found_error() {
65 Error::MissingMetaFar
66 } else {
67 Error::OpenMetaFar(e)
68 }
69 })?;
70 let (meta_files, non_meta_files) = load_package_metadata(&meta_far_vmo)?;
71
72 Ok(RootDir { non_meta_storage, hash, meta_files, non_meta_files, meta_far_vmo, dropper })
73 }
74
75 pub fn set_dropper(
77 &mut self,
78 dropper: Box<dyn crate::OnRootDirDrop>,
79 ) -> Result<(), Box<dyn crate::OnRootDirDrop>> {
80 match self.dropper {
81 Some(_) => Err(dropper),
82 None => {
83 self.dropper = Some(dropper);
84 Ok(())
85 }
86 }
87 }
88
89 pub async fn read_file(&self, path: &str) -> Result<Vec<u8>, ReadFileError> {
92 if let Some(hash) = self.non_meta_files.get(path) {
93 self.non_meta_storage.read_blob(hash).await.map_err(ReadFileError::ReadBlob)
94 } else if let Some(location) = self.meta_files.get(path) {
95 self.meta_far_vmo
96 .read_to_vec(location.offset, location.length)
97 .map_err(ReadFileError::ReadMetaFile)
98 } else {
99 Err(ReadFileError::NoFileAtPath { path: path.to_string() })
100 }
101 }
102
103 pub fn has_file(&self, path: &str) -> bool {
106 self.non_meta_files.contains_key(path) || self.meta_files.contains_key(path)
107 }
108
109 pub fn hash(&self) -> &fuchsia_hash::Hash {
111 &self.hash
112 }
113
114 pub fn external_file_hashes(&self) -> impl ExactSizeIterator<Item = &fuchsia_hash::Hash> {
117 self.non_meta_files.values()
118 }
119
120 pub async fn path(&self) -> Result<fuchsia_pkg::PackagePath, PathError> {
122 Ok(fuchsia_pkg::MetaPackage::deserialize(&self.read_file("meta/package").await?[..])?
123 .into_path())
124 }
125
126 pub async fn subpackages(&self) -> Result<fuchsia_pkg::MetaSubpackages, SubpackagesError> {
128 let contents = match self.read_file(fuchsia_pkg::MetaSubpackages::PATH).await {
129 Ok(contents) => contents,
130 Err(ReadFileError::NoFileAtPath { .. }) => {
131 return Ok(fuchsia_pkg::MetaSubpackages::default())
132 }
133 Err(e) => Err(e)?,
134 };
135
136 Ok(fuchsia_pkg::MetaSubpackages::deserialize(&*contents)?)
137 }
138
139 fn create_meta_as_file(&self) -> Result<Arc<VmoFile>, zx::Status> {
141 let file_contents = self.hash.to_string();
142 let vmo = zx::Vmo::create(usize_to_u64_safe(file_contents.len()))?;
143 let () = vmo.write(file_contents.as_bytes(), 0)?;
144 Ok(VmoFile::new_with_inode(
145 vmo, true, false, false,
146 1,
147 ))
148 }
149
150 pub(crate) fn get_meta_file(&self, path: &str) -> Result<Option<Arc<VmoFile>>, zx::Status> {
152 assert_eq!(zx::system_get_page_size(), 4096);
160
161 let location = match self.meta_files.get(path) {
162 Some(location) => location,
163 None => return Ok(None),
164 };
165 let vmo = self
166 .meta_far_vmo
167 .create_child(
168 zx::VmoChildOptions::SNAPSHOT_AT_LEAST_ON_WRITE | zx::VmoChildOptions::NO_WRITE,
169 location.offset,
170 location.length,
171 )
172 .map_err(|e| {
173 error!("Error creating child vmo for meta file {:?}", e);
174 zx::Status::INTERNAL
175 })?;
176
177 Ok(Some(VmoFile::new_with_inode(
178 vmo, true, false, false,
179 1,
180 )))
181 }
182
183 pub(crate) fn get_meta_subdir(self: &Arc<Self>, path: String) -> Option<Arc<MetaSubdir<S>>> {
185 debug_assert!(path.ends_with("/"));
186 for k in self.meta_files.keys() {
187 if k.starts_with(&path) {
188 return Some(MetaSubdir::new(self.clone(), path));
189 }
190 }
191 None
192 }
193
194 pub(crate) fn get_non_meta_subdir(
196 self: &Arc<Self>,
197 path: String,
198 ) -> Option<Arc<NonMetaSubdir<S>>> {
199 debug_assert!(path.ends_with("/"));
200 for k in self.non_meta_files.keys() {
201 if k.starts_with(&path) {
202 return Some(NonMetaSubdir::new(self.clone(), path));
203 }
204 }
205 None
206 }
207}
208
209#[derive(thiserror::Error, Debug)]
210pub enum ReadFileError {
211 #[error("reading blob")]
212 ReadBlob(#[source] NonMetaStorageError),
213
214 #[error("reading meta file")]
215 ReadMetaFile(#[source] zx::Status),
216
217 #[error("no file exists at path: {path:?}")]
218 NoFileAtPath { path: String },
219}
220
221#[derive(thiserror::Error, Debug)]
222pub enum SubpackagesError {
223 #[error("reading manifest")]
224 Read(#[from] ReadFileError),
225
226 #[error("parsing manifest")]
227 Parse(#[from] fuchsia_pkg::MetaSubpackagesError),
228}
229
230#[derive(thiserror::Error, Debug)]
231pub enum PathError {
232 #[error("reading meta/package")]
233 Read(#[from] ReadFileError),
234
235 #[error("parsing meta/package")]
236 Parse(#[from] fuchsia_pkg::MetaPackageError),
237}
238
239impl<S: crate::NonMetaStorage> vfs::directory::entry::DirectoryEntry for RootDir<S> {
240 fn open_entry(self: Arc<Self>, request: OpenRequest<'_>) -> Result<(), zx::Status> {
241 request.open_dir(self)
242 }
243}
244
245impl<S: crate::NonMetaStorage> vfs::directory::entry::GetEntryInfo for RootDir<S> {
246 fn entry_info(&self) -> EntryInfo {
247 EntryInfo::new(fio::INO_UNKNOWN, fio::DirentType::Directory)
248 }
249}
250
251impl<S: crate::NonMetaStorage> vfs::node::Node for RootDir<S> {
252 async fn get_attributes(
253 &self,
254 requested_attributes: fio::NodeAttributesQuery,
255 ) -> Result<fio::NodeAttributes2, zx::Status> {
256 Ok(immutable_attributes!(
257 requested_attributes,
258 Immutable {
259 protocols: fio::NodeProtocolKinds::DIRECTORY,
260 abilities: crate::DIRECTORY_ABILITIES,
261 id: 1,
262 }
263 ))
264 }
265}
266
267impl<S: crate::NonMetaStorage> vfs::directory::entry_container::Directory for RootDir<S> {
268 fn open(
269 self: Arc<Self>,
270 scope: ExecutionScope,
271 flags: fio::OpenFlags,
272 path: vfs::Path,
273 server_end: ServerEnd<fio::NodeMarker>,
274 ) {
275 let flags = flags & !fio::OpenFlags::POSIX_WRITABLE;
276 let describe = flags.contains(fio::OpenFlags::DESCRIBE);
277 if flags.intersects(fio::OpenFlags::RIGHT_WRITABLE | fio::OpenFlags::TRUNCATE) {
281 let () = send_on_open_with_error(describe, server_end, zx::Status::NOT_SUPPORTED);
282 return;
283 }
284 assert!(!flags.intersects(fio::OpenFlags::CREATE | fio::OpenFlags::CREATE_IF_ABSENT));
286
287 if path.is_empty() {
289 flags.to_object_request(server_end).handle(|object_request| {
290 if flags.intersects(fio::OpenFlags::APPEND) {
294 return Err(zx::Status::NOT_SUPPORTED);
295 }
296 object_request
297 .take()
298 .create_connection_sync::<ImmutableConnection<_>, _>(scope, self, flags);
299 Ok(())
300 });
301 return;
302 }
303
304 let canonical_path = path.as_ref().strip_suffix('/').unwrap_or_else(|| path.as_ref());
306
307 if canonical_path == "meta" {
308 let open_meta_as_file =
315 !flags.intersects(fio::OpenFlags::DIRECTORY | fio::OpenFlags::NODE_REFERENCE);
316 if open_meta_as_file {
317 flags.to_object_request(server_end).handle(|object_request| {
318 let file = self.create_meta_as_file().map_err(|e| {
319 error!("Error creating the meta file: {:?}", e);
320 zx::Status::INTERNAL
321 })?;
322 vfs::file::serve(file, scope, &flags, object_request)
323 });
324 } else {
325 let () = MetaAsDir::new(self).open(scope, flags, vfs::Path::dot(), server_end);
326 }
327 return;
328 }
329
330 if canonical_path.starts_with("meta/") {
331 match self.get_meta_file(canonical_path) {
332 Ok(Some(meta_file)) => {
333 flags.to_object_request(server_end).handle(|object_request| {
334 vfs::file::serve(meta_file, scope, &flags, object_request)
335 });
336 return;
337 }
338 Ok(None) => {}
339 Err(status) => {
340 let () = send_on_open_with_error(describe, server_end, status);
341 return;
342 }
343 }
344
345 if let Some(subdir) = self.get_meta_subdir(canonical_path.to_string() + "/") {
346 let () = subdir.open(scope, flags, vfs::Path::dot(), server_end);
347 return;
348 }
349
350 let () = send_on_open_with_error(describe, server_end, zx::Status::NOT_FOUND);
351 return;
352 }
353
354 if let Some(blob) = self.non_meta_files.get(canonical_path) {
355 let () =
356 self.non_meta_storage.open(blob, flags, scope, server_end).unwrap_or_else(|e| {
357 error!("Error forwarding content blob open to blobfs: {:#}", anyhow::anyhow!(e))
358 });
359 return;
360 }
361
362 if let Some(subdir) = self.get_non_meta_subdir(canonical_path.to_string() + "/") {
363 let () = subdir.open(scope, flags, vfs::Path::dot(), server_end);
364 return;
365 }
366
367 let () = send_on_open_with_error(describe, server_end, zx::Status::NOT_FOUND);
368 }
369
370 fn open3(
371 self: Arc<Self>,
372 scope: ExecutionScope,
373 path: vfs::Path,
374 flags: fio::Flags,
375 object_request: ObjectRequestRef<'_>,
376 ) -> Result<(), zx::Status> {
377 if !flags.difference(crate::ALLOWED_FLAGS).is_empty() {
378 return Err(zx::Status::NOT_SUPPORTED);
379 }
380
381 if path.is_empty() {
383 object_request
385 .take()
386 .create_connection_sync::<ImmutableConnection<_>, _>(scope, self, flags);
387 return Ok(());
388 }
389
390 let canonical_path = path.as_ref().strip_suffix('/').unwrap_or_else(|| path.as_ref());
392
393 if canonical_path == "meta" {
394 let open_meta_as_dir = flags.is_dir_allowed() && !flags.is_file_allowed();
403 if !open_meta_as_dir {
404 if path.is_dir() {
405 return Err(zx::Status::NOT_DIR);
406 }
407 let file = self.create_meta_as_file().map_err(|e| {
408 error!("Error creating the meta file: {:?}", e);
409 zx::Status::INTERNAL
410 })?;
411 return vfs::file::serve(file, scope, &flags, object_request);
412 }
413 return MetaAsDir::new(self).open3(scope, vfs::Path::dot(), flags, object_request);
414 }
415
416 if canonical_path.starts_with("meta/") {
417 if let Some(file) = self.get_meta_file(canonical_path)? {
418 if path.is_dir() {
419 return Err(zx::Status::NOT_DIR);
420 }
421 return vfs::file::serve(file, scope, &flags, object_request);
422 }
423
424 if let Some(subdir) = self.get_meta_subdir(canonical_path.to_string() + "/") {
425 return subdir.open3(scope, vfs::Path::dot(), flags, object_request);
426 }
427 return Err(zx::Status::NOT_FOUND);
428 }
429
430 if let Some(blob) = self.non_meta_files.get(canonical_path) {
431 if path.is_dir() {
432 return Err(zx::Status::NOT_DIR);
433 }
434 return self.non_meta_storage.open3(blob, flags, scope, object_request);
435 }
436
437 if let Some(subdir) = self.get_non_meta_subdir(canonical_path.to_string() + "/") {
438 return subdir.open3(scope, vfs::Path::dot(), flags, object_request);
439 }
440
441 Err(zx::Status::NOT_FOUND)
442 }
443
444 async fn read_dirents<'a>(
445 &'a self,
446 pos: &'a TraversalPosition,
447 sink: Box<(dyn vfs::directory::dirents_sink::Sink + 'static)>,
448 ) -> Result<
449 (TraversalPosition, Box<(dyn vfs::directory::dirents_sink::Sealed + 'static)>),
450 zx::Status,
451 > {
452 vfs::directory::read_dirents::read_dirents(
453 &crate::get_dir_children(
455 self.non_meta_files.keys().map(|s| s.as_str()).chain(["meta/placeholder"]),
456 "",
457 ),
458 pos,
459 sink,
460 )
461 .await
462 }
463
464 fn register_watcher(
465 self: Arc<Self>,
466 _: ExecutionScope,
467 _: fio::WatchMask,
468 _: vfs::directory::entry_container::DirectoryWatcher,
469 ) -> Result<(), zx::Status> {
470 Err(zx::Status::NOT_SUPPORTED)
471 }
472
473 fn unregister_watcher(self: Arc<Self>, _: usize) {}
475}
476
477#[allow(clippy::type_complexity)]
478fn load_package_metadata(
479 meta_far_vmo: &zx::Vmo,
480) -> Result<(HashMap<String, MetaFileLocation>, HashMap<String, fuchsia_hash::Hash>), Error> {
481 let stream =
482 zx::Stream::create(zx::StreamOptions::MODE_READ, meta_far_vmo, 0).map_err(|e| {
483 Error::OpenMetaFar(NonMetaStorageError::ReadBlob(
484 fuchsia_fs::file::ReadError::ReadError(e),
485 ))
486 })?;
487
488 let mut reader = fuchsia_archive::Reader::new(stream).map_err(Error::ArchiveReader)?;
489 let reader_list = reader.list();
490 let mut meta_files = HashMap::with_capacity(reader_list.len());
491 for entry in reader_list {
492 let path = std::str::from_utf8(entry.path())
493 .map_err(|source| Error::NonUtf8MetaEntry { source, path: entry.path().to_owned() })?
494 .to_owned();
495 if path.starts_with("meta/") {
496 for (i, _) in path.match_indices('/').skip(1) {
497 if meta_files.contains_key(&path[..i]) {
498 return Err(Error::FileDirectoryCollision { path: path[..i].to_string() });
499 }
500 }
501 meta_files
502 .insert(path, MetaFileLocation { offset: entry.offset(), length: entry.length() });
503 }
504 }
505
506 let meta_contents_bytes =
507 reader.read_file(b"meta/contents").map_err(Error::ReadMetaContents)?;
508
509 let non_meta_files = MetaContents::deserialize(&meta_contents_bytes[..])
510 .map_err(Error::DeserializeMetaContents)?
511 .into_contents();
512
513 Ok((meta_files, non_meta_files))
514}
515
516#[derive(Clone, Copy, Debug, PartialEq, Eq)]
518pub(crate) struct MetaFileLocation {
519 offset: u64,
520 length: u64,
521}
522
523#[cfg(test)]
524mod tests {
525 use super::*;
526 use assert_matches::assert_matches;
527 use fidl::endpoints::{create_proxy, Proxy as _};
528 use fuchsia_fs::directory::{DirEntry, DirentKind};
529 use fuchsia_pkg_testing::blobfs::Fake as FakeBlobfs;
530 use fuchsia_pkg_testing::PackageBuilder;
531 use futures::TryStreamExt as _;
532 use pretty_assertions::assert_eq;
533 use std::io::Cursor;
534 use vfs::directory::entry_container::Directory as _;
535
536 struct TestEnv {
537 _blobfs_fake: FakeBlobfs,
538 root_dir: Arc<RootDir<blobfs::Client>>,
539 }
540
541 impl TestEnv {
542 async fn with_subpackages(
543 subpackages_content: Option<&[u8]>,
544 ) -> (Self, Arc<RootDir<blobfs::Client>>) {
545 let mut pkg = PackageBuilder::new("base-package-0")
546 .add_resource_at("resource", "blob-contents".as_bytes())
547 .add_resource_at("dir/file", "bloblob".as_bytes())
548 .add_resource_at("meta/file", "meta-contents0".as_bytes())
549 .add_resource_at("meta/dir/file", "meta-contents1".as_bytes());
550 if let Some(subpackages_content) = subpackages_content {
551 pkg = pkg.add_resource_at(fuchsia_pkg::MetaSubpackages::PATH, subpackages_content);
552 }
553 let pkg = pkg.build().await.unwrap();
554 let (metafar_blob, content_blobs) = pkg.contents();
555 let (blobfs_fake, blobfs_client) = FakeBlobfs::new();
556 blobfs_fake.add_blob(metafar_blob.merkle, metafar_blob.contents);
557 for (hash, bytes) in content_blobs {
558 blobfs_fake.add_blob(hash, bytes);
559 }
560
561 let root_dir = RootDir::new(blobfs_client, metafar_blob.merkle).await.unwrap();
562 (Self { _blobfs_fake: blobfs_fake, root_dir: root_dir.clone() }, root_dir)
563 }
564
565 async fn new() -> (Self, fio::DirectoryProxy) {
566 let (env, root) = Self::with_subpackages(None).await;
567 (env, vfs::directory::serve_read_only(root))
568 }
569 }
570
571 #[fuchsia_async::run_singlethreaded(test)]
572 async fn new_missing_meta_far_error() {
573 let (_blobfs_fake, blobfs_client) = FakeBlobfs::new();
574 assert_matches!(
575 RootDir::new(blobfs_client, [0; 32].into()).await,
576 Err(Error::MissingMetaFar)
577 );
578 }
579
580 #[fuchsia_async::run_singlethreaded(test)]
581 async fn new_rejects_invalid_utf8() {
582 let (blobfs_fake, blobfs_client) = FakeBlobfs::new();
583 let mut meta_far = vec![];
584 let () = fuchsia_archive::write(
585 &mut meta_far,
586 std::collections::BTreeMap::from_iter([(
587 b"\xff",
588 (0, Box::new("".as_bytes()) as Box<dyn std::io::Read>),
589 )]),
590 )
591 .unwrap();
592 let hash = fuchsia_merkle::from_slice(&meta_far).root();
593 let () = blobfs_fake.add_blob(hash, meta_far);
594
595 assert_matches!(
596 RootDir::new(blobfs_client, hash).await,
597 Err(Error::NonUtf8MetaEntry{path, ..})
598 if path == vec![255]
599 );
600 }
601
602 #[fuchsia_async::run_singlethreaded(test)]
603 async fn new_initializes_maps() {
604 let (_env, root_dir) = TestEnv::with_subpackages(None).await;
605
606 let meta_files = HashMap::from([
607 (String::from("meta/contents"), MetaFileLocation { offset: 4096, length: 148 }),
608 (String::from("meta/package"), MetaFileLocation { offset: 20480, length: 39 }),
609 (String::from("meta/file"), MetaFileLocation { offset: 12288, length: 14 }),
610 (String::from("meta/dir/file"), MetaFileLocation { offset: 8192, length: 14 }),
611 (
612 String::from("meta/fuchsia.abi/abi-revision"),
613 MetaFileLocation { offset: 16384, length: 8 },
614 ),
615 ]);
616 assert_eq!(root_dir.meta_files, meta_files);
617
618 let non_meta_files: HashMap<String, fuchsia_hash::Hash> = [
619 (
620 String::from("resource"),
621 "bd905f783ceae4c5ba8319703d7505ab363733c2db04c52c8405603a02922b15"
622 .parse::<fuchsia_hash::Hash>()
623 .unwrap(),
624 ),
625 (
626 String::from("dir/file"),
627 "5f615dd575994fcbcc174974311d59de258d93cd523d5cb51f0e139b53c33201"
628 .parse::<fuchsia_hash::Hash>()
629 .unwrap(),
630 ),
631 ]
632 .iter()
633 .cloned()
634 .collect();
635 assert_eq!(root_dir.non_meta_files, non_meta_files);
636 }
637
638 #[fuchsia_async::run_singlethreaded(test)]
639 async fn rejects_meta_file_collisions() {
640 let pkg = PackageBuilder::new("base-package-0")
641 .add_resource_at("meta/dir/file", "meta-contents0".as_bytes())
642 .build()
643 .await
644 .unwrap();
645
646 let (metafar_blob, _) = pkg.contents();
648 let mut metafar =
649 fuchsia_archive::Reader::new(Cursor::new(&metafar_blob.contents)).unwrap();
650 let mut entries = std::collections::BTreeMap::new();
651 let farentries =
652 metafar.list().map(|entry| (entry.path().to_vec(), entry.length())).collect::<Vec<_>>();
653 for (path, length) in farentries {
654 let contents = metafar.read_file(&path).unwrap();
655 entries
656 .insert(path, (length, Box::new(Cursor::new(contents)) as Box<dyn std::io::Read>));
657 }
658 let extra_contents = b"meta-contents1";
659 entries.insert(
660 b"meta/dir".to_vec(),
661 (
662 extra_contents.len() as u64,
663 Box::new(Cursor::new(extra_contents)) as Box<dyn std::io::Read>,
664 ),
665 );
666
667 let mut metafar: Vec<u8> = vec![];
668 let () = fuchsia_archive::write(&mut metafar, entries).unwrap();
669 let merkle = fuchsia_merkle::from_slice(&metafar).root();
670
671 let (blobfs_fake, blobfs_client) = FakeBlobfs::new();
673 blobfs_fake.add_blob(merkle, &metafar);
674
675 match RootDir::new(blobfs_client, merkle).await {
676 Ok(_) => panic!("this should not be reached!"),
677 Err(Error::FileDirectoryCollision { path }) => {
678 assert_eq!(path, "meta/dir".to_string());
679 }
680 Err(e) => panic!("Expected collision error, receieved {e:?}"),
681 };
682 }
683
684 #[fuchsia_async::run_singlethreaded(test)]
685 async fn read_file() {
686 let (_env, root_dir) = TestEnv::with_subpackages(None).await;
687
688 assert_eq!(root_dir.read_file("resource").await.unwrap().as_slice(), b"blob-contents");
689 assert_eq!(root_dir.read_file("meta/file").await.unwrap().as_slice(), b"meta-contents0");
690 assert_matches!(
691 root_dir.read_file("missing").await.unwrap_err(),
692 ReadFileError::NoFileAtPath{path} if path == "missing"
693 );
694 }
695
696 #[fuchsia_async::run_singlethreaded(test)]
697 async fn has_file() {
698 let (_env, root_dir) = TestEnv::with_subpackages(None).await;
699
700 assert!(root_dir.has_file("resource"));
701 assert!(root_dir.has_file("meta/file"));
702 assert_eq!(root_dir.has_file("missing"), false);
703 }
704
705 #[fuchsia_async::run_singlethreaded(test)]
706 async fn external_file_hashes() {
707 let (_env, root_dir) = TestEnv::with_subpackages(None).await;
708
709 let mut actual = root_dir.external_file_hashes().copied().collect::<Vec<_>>();
710 actual.sort();
711 assert_eq!(
712 actual,
713 vec![
714 "5f615dd575994fcbcc174974311d59de258d93cd523d5cb51f0e139b53c33201".parse().unwrap(),
715 "bd905f783ceae4c5ba8319703d7505ab363733c2db04c52c8405603a02922b15".parse().unwrap()
716 ]
717 );
718 }
719
720 #[fuchsia_async::run_singlethreaded(test)]
721 async fn path() {
722 let (_env, root_dir) = TestEnv::with_subpackages(None).await;
723
724 assert_eq!(
725 root_dir.path().await.unwrap(),
726 "base-package-0/0".parse::<fuchsia_pkg::PackagePath>().unwrap()
727 );
728 }
729
730 #[fuchsia_async::run_singlethreaded(test)]
731 async fn subpackages_present() {
732 let subpackages = fuchsia_pkg::MetaSubpackages::from_iter([(
733 fuchsia_url::RelativePackageUrl::parse("subpackage-name").unwrap(),
734 "0000000000000000000000000000000000000000000000000000000000000000".parse().unwrap(),
735 )]);
736 let mut subpackages_bytes = vec![];
737 let () = subpackages.serialize(&mut subpackages_bytes).unwrap();
738 let (_env, root_dir) = TestEnv::with_subpackages(Some(&*subpackages_bytes)).await;
739
740 assert_eq!(root_dir.subpackages().await.unwrap(), subpackages);
741 }
742
743 #[fuchsia_async::run_singlethreaded(test)]
744 async fn subpackages_absent() {
745 let (_env, root_dir) = TestEnv::with_subpackages(None).await;
746
747 assert_eq!(root_dir.subpackages().await.unwrap(), fuchsia_pkg::MetaSubpackages::default());
748 }
749
750 #[fuchsia_async::run_singlethreaded(test)]
751 async fn subpackages_error() {
752 let (_env, root_dir) = TestEnv::with_subpackages(Some(b"invalid-json")).await;
753
754 assert_matches!(root_dir.subpackages().await, Err(SubpackagesError::Parse(_)));
755 }
756
757 #[fuchsia_async::run_singlethreaded(test)]
761 async fn root_dir_cannot_be_served_as_mutable() {
762 let (_env, root_dir) = TestEnv::with_subpackages(None).await;
763 let (proxy, server) = fidl::endpoints::create_proxy::<fio::DirectoryMarker>();
764 let request = fio::PERM_WRITABLE.to_object_request(server);
765 request.handle(|request: &mut vfs::ObjectRequest| {
766 root_dir.open3(ExecutionScope::new(), vfs::Path::dot(), fio::PERM_WRITABLE, request)
767 });
768 assert_matches!(
769 proxy.take_event_stream().try_next().await,
770 Err(fidl::Error::ClientChannelClosed { status: zx::Status::NOT_SUPPORTED, .. })
771 );
772 }
773
774 #[fuchsia_async::run_singlethreaded(test)]
775 async fn root_dir_readdir() {
776 let (_env, root_dir) = TestEnv::new().await;
777 assert_eq!(
778 fuchsia_fs::directory::readdir_inclusive(&root_dir).await.unwrap(),
779 vec![
780 DirEntry { name: ".".to_string(), kind: DirentKind::Directory },
781 DirEntry { name: "dir".to_string(), kind: DirentKind::Directory },
782 DirEntry { name: "meta".to_string(), kind: DirentKind::Directory },
783 DirEntry { name: "resource".to_string(), kind: DirentKind::File }
784 ]
785 );
786 }
787
788 #[fuchsia_async::run_singlethreaded(test)]
789 async fn root_dir_get_attributes() {
790 let (_env, root_dir) = TestEnv::new().await;
791 let (mutable_attributes, immutable_attributes) =
792 root_dir.get_attributes(fio::NodeAttributesQuery::all()).await.unwrap().unwrap();
793 assert_eq!(
794 fio::NodeAttributes2 { mutable_attributes, immutable_attributes },
795 immutable_attributes!(
796 fio::NodeAttributesQuery::all(),
797 Immutable {
798 protocols: fio::NodeProtocolKinds::DIRECTORY,
799 abilities: crate::DIRECTORY_ABILITIES,
800 id: 1,
801 }
802 )
803 );
804 }
805
806 #[fuchsia_async::run_singlethreaded(test)]
807 async fn root_dir_watch_not_supported() {
808 let (_env, root_dir) = TestEnv::new().await;
809 let (_client, server) = fidl::endpoints::create_endpoints();
810 let status =
811 zx::Status::from_raw(root_dir.watch(fio::WatchMask::empty(), 0, server).await.unwrap());
812 assert_eq!(status, zx::Status::NOT_SUPPORTED);
813 }
814
815 #[fuchsia_async::run_singlethreaded(test)]
816 async fn root_dir_open_non_meta_file() {
817 let (_env, root_dir) = TestEnv::new().await;
818 let proxy = fuchsia_fs::directory::open_file(&root_dir, "resource", fio::PERM_READABLE)
819 .await
820 .unwrap();
821 assert_eq!(fuchsia_fs::file::read(&proxy).await.unwrap(), b"blob-contents".to_vec());
822 }
823
824 #[fuchsia_async::run_singlethreaded(test)]
825 async fn root_dir_open_meta_as_file() {
826 let (env, root_dir) = TestEnv::new().await;
827 let proxy =
828 fuchsia_fs::directory::open_file(&root_dir, "meta", fio::PERM_READABLE).await.unwrap();
829 assert_eq!(
830 fuchsia_fs::file::read(&proxy).await.unwrap(),
831 env.root_dir.hash.to_string().as_bytes()
832 );
833 let (cloned_proxy, server_end) = create_proxy::<fio::FileMarker>();
835 proxy.clone(server_end.into_channel().into()).unwrap();
836 assert_eq!(
837 fuchsia_fs::file::read(&cloned_proxy).await.unwrap(),
838 env.root_dir.hash.to_string().as_bytes()
839 );
840 }
841
842 #[fuchsia_async::run_singlethreaded(test)]
843 async fn root_dir_open_meta_as_dir() {
844 let (_env, root_dir) = TestEnv::new().await;
845 for path in ["meta", "meta/"] {
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![
852 DirEntry { name: "contents".to_string(), kind: DirentKind::File },
853 DirEntry { name: "dir".to_string(), kind: DirentKind::Directory },
854 DirEntry { name: "file".to_string(), kind: DirentKind::File },
855 DirEntry { name: "fuchsia.abi".to_string(), kind: DirentKind::Directory },
856 DirEntry { name: "package".to_string(), kind: DirentKind::File },
857 ]
858 );
859 let (cloned_proxy, server_end) = create_proxy::<fio::DirectoryMarker>();
861 proxy.clone(server_end.into_channel().into()).unwrap();
862 assert_eq!(
863 fuchsia_fs::directory::readdir(&cloned_proxy).await.unwrap(),
864 vec![
865 DirEntry { name: "contents".to_string(), kind: DirentKind::File },
866 DirEntry { name: "dir".to_string(), kind: DirentKind::Directory },
867 DirEntry { name: "file".to_string(), kind: DirentKind::File },
868 DirEntry { name: "fuchsia.abi".to_string(), kind: DirentKind::Directory },
869 DirEntry { name: "package".to_string(), kind: DirentKind::File },
870 ]
871 );
872 }
873 }
874
875 #[fuchsia_async::run_singlethreaded(test)]
876 async fn root_dir_open_meta_as_node() {
877 let (_env, root_dir) = TestEnv::new().await;
878 for path in ["meta", "meta/"] {
879 let proxy = fuchsia_fs::directory::open_node(
880 &root_dir,
881 path,
882 fio::Flags::PROTOCOL_NODE
883 | fio::Flags::PROTOCOL_DIRECTORY
884 | fio::Flags::PERM_GET_ATTRIBUTES,
885 )
886 .await
887 .unwrap();
888 let (mutable_attributes, immutable_attributes) = proxy
889 .get_attributes(
890 fio::NodeAttributesQuery::PROTOCOLS | fio::NodeAttributesQuery::ABILITIES,
891 )
892 .await
893 .unwrap()
894 .unwrap();
895 assert_eq!(
896 fio::NodeAttributes2 { mutable_attributes, immutable_attributes },
897 immutable_attributes!(
898 fio::NodeAttributesQuery::PROTOCOLS | fio::NodeAttributesQuery::ABILITIES,
899 Immutable {
900 protocols: fio::NodeProtocolKinds::DIRECTORY,
901 abilities: crate::DIRECTORY_ABILITIES
902 }
903 )
904 );
905 }
906 let proxy = fuchsia_fs::directory::open_node(
908 &root_dir,
909 "meta",
910 fio::Flags::PROTOCOL_NODE | fio::Flags::PERM_GET_ATTRIBUTES,
911 )
912 .await
913 .unwrap();
914 let (mutable_attributes, immutable_attributes) = proxy
915 .get_attributes(
916 fio::NodeAttributesQuery::PROTOCOLS | fio::NodeAttributesQuery::ABILITIES,
917 )
918 .await
919 .unwrap()
920 .unwrap();
921 assert_eq!(
922 fio::NodeAttributes2 { mutable_attributes, immutable_attributes },
923 immutable_attributes!(
924 fio::NodeAttributesQuery::PROTOCOLS | fio::NodeAttributesQuery::ABILITIES,
925 Immutable {
926 protocols: fio::NodeProtocolKinds::FILE,
927 abilities: fio::Abilities::READ_BYTES | fio::Abilities::GET_ATTRIBUTES,
928 }
929 )
930 );
931 }
932
933 #[fuchsia_async::run_singlethreaded(test)]
934 async fn root_dir_open_meta_file() {
935 let (_env, root_dir) = TestEnv::new().await;
936 let proxy = fuchsia_fs::directory::open_file(&root_dir, "meta/file", fio::PERM_READABLE)
937 .await
938 .unwrap();
939 assert_eq!(fuchsia_fs::file::read(&proxy).await.unwrap(), b"meta-contents0".to_vec());
940 }
941
942 #[fuchsia_async::run_singlethreaded(test)]
943 async fn root_dir_open_meta_subdir() {
944 let (_env, root_dir) = TestEnv::new().await;
945 for path in ["meta/dir", "meta/dir/"] {
946 let proxy = fuchsia_fs::directory::open_directory(&root_dir, path, fio::PERM_READABLE)
947 .await
948 .unwrap();
949 assert_eq!(
950 fuchsia_fs::directory::readdir(&proxy).await.unwrap(),
951 vec![DirEntry { name: "file".to_string(), kind: DirentKind::File }]
952 );
953 }
954 }
955
956 #[fuchsia_async::run_singlethreaded(test)]
957 async fn root_dir_open_non_meta_subdir() {
958 let (_env, root_dir) = TestEnv::new().await;
959 for path in ["dir", "dir/"] {
960 let proxy = fuchsia_fs::directory::open_directory(&root_dir, path, fio::PERM_READABLE)
961 .await
962 .unwrap();
963 assert_eq!(
964 fuchsia_fs::directory::readdir(&proxy).await.unwrap(),
965 vec![DirEntry { name: "file".to_string(), kind: DirentKind::File }]
966 );
967 }
968 }
969
970 #[fuchsia_async::run_singlethreaded(test)]
971 async fn root_dir_deprecated_open_self() {
972 let (_env, root_dir) = TestEnv::new().await;
973 let (proxy, server_end) = create_proxy::<fio::DirectoryMarker>();
974 root_dir
975 .deprecated_open(
976 fio::OpenFlags::RIGHT_READABLE,
977 Default::default(),
978 ".",
979 server_end.into_channel().into(),
980 )
981 .unwrap();
982 assert_eq!(
983 fuchsia_fs::directory::readdir(&proxy).await.unwrap(),
984 vec![
985 DirEntry { name: "dir".to_string(), kind: DirentKind::Directory },
986 DirEntry { name: "meta".to_string(), kind: DirentKind::Directory },
987 DirEntry { name: "resource".to_string(), kind: DirentKind::File }
988 ]
989 );
990 }
991
992 #[fuchsia_async::run_singlethreaded(test)]
993 async fn root_dir_deprecated_open_non_meta_file() {
994 let (_env, root_dir) = TestEnv::new().await;
995 let (proxy, server_end) = create_proxy();
996 root_dir
997 .deprecated_open(
998 fio::OpenFlags::RIGHT_READABLE,
999 Default::default(),
1000 "resource",
1001 server_end,
1002 )
1003 .unwrap();
1004 assert_eq!(
1005 fuchsia_fs::file::read(&fio::FileProxy::from_channel(proxy.into_channel().unwrap()))
1006 .await
1007 .unwrap(),
1008 b"blob-contents".to_vec()
1009 );
1010 }
1011
1012 #[fuchsia_async::run_singlethreaded(test)]
1013 async fn root_dir_deprecated_open_meta_as_file() {
1014 let (env, root_dir) = TestEnv::new().await;
1015 let (proxy, server_end) = create_proxy::<fio::FileMarker>();
1016 root_dir
1017 .deprecated_open(
1018 fio::OpenFlags::RIGHT_READABLE | fio::OpenFlags::NOT_DIRECTORY,
1019 Default::default(),
1020 "meta",
1021 server_end.into_channel().into(),
1022 )
1023 .unwrap();
1024 assert_eq!(
1025 fuchsia_fs::file::read(&proxy).await.unwrap(),
1026 env.root_dir.hash.to_string().as_bytes()
1027 );
1028 }
1029
1030 #[fuchsia_async::run_singlethreaded(test)]
1031 async fn root_dir_deprecated_open_meta_as_dir() {
1032 let (_env, root_dir) = TestEnv::new().await;
1033 for path in ["meta", "meta/"] {
1034 let (proxy, server_end) = create_proxy::<fio::DirectoryMarker>();
1035 root_dir
1036 .deprecated_open(
1037 fio::OpenFlags::RIGHT_READABLE | fio::OpenFlags::DIRECTORY,
1038 Default::default(),
1039 path,
1040 server_end.into_channel().into(),
1041 )
1042 .unwrap();
1043 assert_eq!(
1044 fuchsia_fs::directory::readdir(&proxy).await.unwrap(),
1045 vec![
1046 DirEntry { name: "contents".to_string(), kind: DirentKind::File },
1047 DirEntry { name: "dir".to_string(), kind: DirentKind::Directory },
1048 DirEntry { name: "file".to_string(), kind: DirentKind::File },
1049 DirEntry { name: "fuchsia.abi".to_string(), kind: DirentKind::Directory },
1050 DirEntry { name: "package".to_string(), kind: DirentKind::File },
1051 ]
1052 );
1053 }
1054 }
1055
1056 #[fuchsia_async::run_singlethreaded(test)]
1057 async fn root_dir_deprecated_open_meta_as_node_reference() {
1058 let (_env, root_dir) = TestEnv::new().await;
1059 for path in ["meta", "meta/"] {
1060 let (proxy, server_end) = create_proxy::<fio::NodeMarker>();
1061 root_dir
1062 .deprecated_open(
1063 fio::OpenFlags::RIGHT_READABLE | fio::OpenFlags::NODE_REFERENCE,
1064 Default::default(),
1065 path,
1066 server_end.into_channel().into(),
1067 )
1068 .unwrap();
1069 let (status, attr) = proxy.get_attr().await.expect("get_attr failed");
1072 assert_eq!(zx::Status::from_raw(status), zx::Status::OK);
1073 assert_eq!(attr.mode & fio::MODE_TYPE_MASK, fio::MODE_TYPE_DIRECTORY);
1074 }
1075 }
1076
1077 #[fuchsia_async::run_singlethreaded(test)]
1078 async fn root_dir_deprecated_open_meta_file() {
1079 let (_env, root_dir) = TestEnv::new().await;
1080 let (proxy, server_end) = create_proxy::<fio::FileMarker>();
1081 root_dir
1082 .deprecated_open(
1083 fio::OpenFlags::RIGHT_READABLE,
1084 Default::default(),
1085 "meta/file",
1086 server_end.into_channel().into(),
1087 )
1088 .unwrap();
1089 assert_eq!(fuchsia_fs::file::read(&proxy).await.unwrap(), b"meta-contents0".to_vec());
1090 }
1091
1092 #[fuchsia_async::run_singlethreaded(test)]
1093 async fn root_dir_deprecated_open_meta_subdir() {
1094 let (_env, root_dir) = TestEnv::new().await;
1095 for path in ["meta/dir", "meta/dir/"] {
1096 let (proxy, server_end) = create_proxy::<fio::DirectoryMarker>();
1097 root_dir
1098 .deprecated_open(
1099 fio::OpenFlags::RIGHT_READABLE,
1100 Default::default(),
1101 path,
1102 server_end.into_channel().into(),
1103 )
1104 .unwrap();
1105 assert_eq!(
1106 fuchsia_fs::directory::readdir(&proxy).await.unwrap(),
1107 vec![DirEntry { name: "file".to_string(), kind: DirentKind::File }]
1108 );
1109 }
1110 }
1111
1112 #[fuchsia_async::run_singlethreaded(test)]
1113 async fn root_dir_deprecated_open_non_meta_subdir() {
1114 let (_env, root_dir) = TestEnv::new().await;
1115 for path in ["dir", "dir/"] {
1116 let (proxy, server_end) = create_proxy::<fio::DirectoryMarker>();
1117 root_dir
1118 .deprecated_open(
1119 fio::OpenFlags::RIGHT_READABLE,
1120 Default::default(),
1121 path,
1122 server_end.into_channel().into(),
1123 )
1124 .unwrap();
1125 assert_eq!(
1126 fuchsia_fs::directory::readdir(&proxy).await.unwrap(),
1127 vec![DirEntry { name: "file".to_string(), kind: DirentKind::File }]
1128 );
1129 }
1130 }
1131}