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(vmo, 1))
145 }
146
147 pub(crate) fn get_meta_file(&self, path: &str) -> Result<Option<Arc<VmoFile>>, zx::Status> {
149 assert_eq!(zx::system_get_page_size(), 4096);
157
158 let location = match self.meta_files.get(path) {
159 Some(location) => location,
160 None => return Ok(None),
161 };
162 let vmo = self
163 .meta_far_vmo
164 .create_child(
165 zx::VmoChildOptions::SNAPSHOT_AT_LEAST_ON_WRITE | zx::VmoChildOptions::NO_WRITE,
166 location.offset,
167 location.length,
168 )
169 .map_err(|e| {
170 error!("Error creating child vmo for meta file {:?}", e);
171 zx::Status::INTERNAL
172 })?;
173
174 Ok(Some(VmoFile::new_with_inode(vmo, 1)))
175 }
176
177 pub(crate) fn get_meta_subdir(self: &Arc<Self>, path: String) -> Option<Arc<MetaSubdir<S>>> {
179 debug_assert!(path.ends_with("/"));
180 for k in self.meta_files.keys() {
181 if k.starts_with(&path) {
182 return Some(MetaSubdir::new(self.clone(), path));
183 }
184 }
185 None
186 }
187
188 pub(crate) fn get_non_meta_subdir(
190 self: &Arc<Self>,
191 path: String,
192 ) -> Option<Arc<NonMetaSubdir<S>>> {
193 debug_assert!(path.ends_with("/"));
194 for k in self.non_meta_files.keys() {
195 if k.starts_with(&path) {
196 return Some(NonMetaSubdir::new(self.clone(), path));
197 }
198 }
199 None
200 }
201}
202
203#[derive(thiserror::Error, Debug)]
204pub enum ReadFileError {
205 #[error("reading blob")]
206 ReadBlob(#[source] NonMetaStorageError),
207
208 #[error("reading meta file")]
209 ReadMetaFile(#[source] zx::Status),
210
211 #[error("no file exists at path: {path:?}")]
212 NoFileAtPath { path: String },
213}
214
215#[derive(thiserror::Error, Debug)]
216pub enum SubpackagesError {
217 #[error("reading manifest")]
218 Read(#[from] ReadFileError),
219
220 #[error("parsing manifest")]
221 Parse(#[from] fuchsia_pkg::MetaSubpackagesError),
222}
223
224#[derive(thiserror::Error, Debug)]
225pub enum PathError {
226 #[error("reading meta/package")]
227 Read(#[from] ReadFileError),
228
229 #[error("parsing meta/package")]
230 Parse(#[from] fuchsia_pkg::MetaPackageError),
231}
232
233impl<S: crate::NonMetaStorage> vfs::directory::entry::DirectoryEntry for RootDir<S> {
234 fn open_entry(self: Arc<Self>, request: OpenRequest<'_>) -> Result<(), zx::Status> {
235 request.open_dir(self)
236 }
237}
238
239impl<S: crate::NonMetaStorage> vfs::directory::entry::GetEntryInfo for RootDir<S> {
240 fn entry_info(&self) -> EntryInfo {
241 EntryInfo::new(fio::INO_UNKNOWN, fio::DirentType::Directory)
242 }
243}
244
245impl<S: crate::NonMetaStorage> vfs::node::Node for RootDir<S> {
246 async fn get_attributes(
247 &self,
248 requested_attributes: fio::NodeAttributesQuery,
249 ) -> Result<fio::NodeAttributes2, zx::Status> {
250 Ok(immutable_attributes!(
251 requested_attributes,
252 Immutable {
253 protocols: fio::NodeProtocolKinds::DIRECTORY,
254 abilities: crate::DIRECTORY_ABILITIES,
255 id: 1,
256 }
257 ))
258 }
259}
260
261impl<S: crate::NonMetaStorage> vfs::directory::entry_container::Directory for RootDir<S> {
262 fn deprecated_open(
263 self: Arc<Self>,
264 scope: ExecutionScope,
265 flags: fio::OpenFlags,
266 path: vfs::Path,
267 server_end: ServerEnd<fio::NodeMarker>,
268 ) {
269 let flags = flags & !fio::OpenFlags::POSIX_WRITABLE;
270 let describe = flags.contains(fio::OpenFlags::DESCRIBE);
271 if flags.intersects(fio::OpenFlags::RIGHT_WRITABLE | fio::OpenFlags::TRUNCATE) {
275 let () = send_on_open_with_error(describe, server_end, zx::Status::NOT_SUPPORTED);
276 return;
277 }
278 assert!(!flags.intersects(fio::OpenFlags::CREATE | fio::OpenFlags::CREATE_IF_ABSENT));
280
281 if path.is_empty() {
283 flags.to_object_request(server_end).handle(|object_request| {
284 if flags.intersects(fio::OpenFlags::APPEND) {
288 return Err(zx::Status::NOT_SUPPORTED);
289 }
290 object_request
291 .take()
292 .create_connection_sync::<ImmutableConnection<_>, _>(scope, self, flags);
293 Ok(())
294 });
295 return;
296 }
297
298 let canonical_path = path.as_ref().strip_suffix('/').unwrap_or_else(|| path.as_ref());
300
301 if canonical_path == "meta" {
302 let open_meta_as_file =
309 !flags.intersects(fio::OpenFlags::DIRECTORY | fio::OpenFlags::NODE_REFERENCE);
310 if open_meta_as_file {
311 flags.to_object_request(server_end).handle(|object_request| {
312 let file = self.create_meta_as_file().map_err(|e| {
313 error!("Error creating the meta file: {:?}", e);
314 zx::Status::INTERNAL
315 })?;
316 vfs::file::serve(file, scope, &flags, object_request)
317 });
318 } else {
319 let () = MetaAsDir::new(self).deprecated_open(
320 scope,
321 flags,
322 vfs::Path::dot(),
323 server_end,
324 );
325 }
326 return;
327 }
328
329 if canonical_path.starts_with("meta/") {
330 match self.get_meta_file(canonical_path) {
331 Ok(Some(meta_file)) => {
332 flags.to_object_request(server_end).handle(|object_request| {
333 vfs::file::serve(meta_file, scope, &flags, object_request)
334 });
335 return;
336 }
337 Ok(None) => {}
338 Err(status) => {
339 let () = send_on_open_with_error(describe, server_end, status);
340 return;
341 }
342 }
343
344 if let Some(subdir) = self.get_meta_subdir(canonical_path.to_string() + "/") {
345 let () = subdir.deprecated_open(scope, flags, vfs::Path::dot(), server_end);
346 return;
347 }
348
349 let () = send_on_open_with_error(describe, server_end, zx::Status::NOT_FOUND);
350 return;
351 }
352
353 if let Some(blob) = self.non_meta_files.get(canonical_path) {
354 let () = self
355 .non_meta_storage
356 .deprecated_open(blob, flags, scope, server_end)
357 .unwrap_or_else(|e| {
358 error!("Error forwarding content blob open to blobfs: {:#}", anyhow::anyhow!(e))
359 });
360 return;
361 }
362
363 if let Some(subdir) = self.get_non_meta_subdir(canonical_path.to_string() + "/") {
364 let () = subdir.deprecated_open(scope, flags, vfs::Path::dot(), server_end);
365 return;
366 }
367
368 let () = send_on_open_with_error(describe, server_end, zx::Status::NOT_FOUND);
369 }
370
371 fn open(
372 self: Arc<Self>,
373 scope: ExecutionScope,
374 path: vfs::Path,
375 flags: fio::Flags,
376 object_request: ObjectRequestRef<'_>,
377 ) -> Result<(), zx::Status> {
378 if !flags.difference(crate::ALLOWED_FLAGS).is_empty() {
379 return Err(zx::Status::NOT_SUPPORTED);
380 }
381
382 if path.is_empty() {
384 object_request
386 .take()
387 .create_connection_sync::<ImmutableConnection<_>, _>(scope, self, flags);
388 return Ok(());
389 }
390
391 let canonical_path = path.as_ref().strip_suffix('/').unwrap_or_else(|| path.as_ref());
393
394 if canonical_path == "meta" {
395 let open_meta_as_dir = flags.is_dir_allowed() && !flags.is_file_allowed();
404 if !open_meta_as_dir {
405 if path.is_dir() {
406 return Err(zx::Status::NOT_DIR);
407 }
408 let file = self.create_meta_as_file().map_err(|e| {
409 error!("Error creating the meta file: {:?}", e);
410 zx::Status::INTERNAL
411 })?;
412 return vfs::file::serve(file, scope, &flags, object_request);
413 }
414 return MetaAsDir::new(self).open(scope, vfs::Path::dot(), flags, object_request);
415 }
416
417 if canonical_path.starts_with("meta/") {
418 if let Some(file) = self.get_meta_file(canonical_path)? {
419 if path.is_dir() {
420 return Err(zx::Status::NOT_DIR);
421 }
422 return vfs::file::serve(file, scope, &flags, object_request);
423 }
424
425 if let Some(subdir) = self.get_meta_subdir(canonical_path.to_string() + "/") {
426 return subdir.open(scope, vfs::Path::dot(), flags, object_request);
427 }
428 return Err(zx::Status::NOT_FOUND);
429 }
430
431 if let Some(blob) = self.non_meta_files.get(canonical_path) {
432 if path.is_dir() {
433 return Err(zx::Status::NOT_DIR);
434 }
435 return self.non_meta_storage.open(blob, flags, scope, object_request);
436 }
437
438 if let Some(subdir) = self.get_non_meta_subdir(canonical_path.to_string() + "/") {
439 return subdir.open(scope, vfs::Path::dot(), flags, object_request);
440 }
441
442 Err(zx::Status::NOT_FOUND)
443 }
444
445 async fn read_dirents<'a>(
446 &'a self,
447 pos: &'a TraversalPosition,
448 sink: Box<(dyn vfs::directory::dirents_sink::Sink + 'static)>,
449 ) -> Result<
450 (TraversalPosition, Box<(dyn vfs::directory::dirents_sink::Sealed + 'static)>),
451 zx::Status,
452 > {
453 vfs::directory::read_dirents::read_dirents(
454 &crate::get_dir_children(
456 self.non_meta_files.keys().map(|s| s.as_str()).chain(["meta/placeholder"]),
457 "",
458 ),
459 pos,
460 sink,
461 )
462 .await
463 }
464
465 fn register_watcher(
466 self: Arc<Self>,
467 _: ExecutionScope,
468 _: fio::WatchMask,
469 _: vfs::directory::entry_container::DirectoryWatcher,
470 ) -> Result<(), zx::Status> {
471 Err(zx::Status::NOT_SUPPORTED)
472 }
473
474 fn unregister_watcher(self: Arc<Self>, _: usize) {}
476}
477
478#[allow(clippy::type_complexity)]
479fn load_package_metadata(
480 meta_far_vmo: &zx::Vmo,
481) -> Result<(HashMap<String, MetaFileLocation>, HashMap<String, fuchsia_hash::Hash>), Error> {
482 let stream =
483 zx::Stream::create(zx::StreamOptions::MODE_READ, meta_far_vmo, 0).map_err(|e| {
484 Error::OpenMetaFar(NonMetaStorageError::ReadBlob(
485 fuchsia_fs::file::ReadError::ReadError(e),
486 ))
487 })?;
488
489 let mut reader = fuchsia_archive::Reader::new(stream).map_err(Error::ArchiveReader)?;
490 let reader_list = reader.list();
491 let mut meta_files = HashMap::with_capacity(reader_list.len());
492 for entry in reader_list {
493 let path = std::str::from_utf8(entry.path())
494 .map_err(|source| Error::NonUtf8MetaEntry { source, path: entry.path().to_owned() })?
495 .to_owned();
496 if path.starts_with("meta/") {
497 for (i, _) in path.match_indices('/').skip(1) {
498 if meta_files.contains_key(&path[..i]) {
499 return Err(Error::FileDirectoryCollision { path: path[..i].to_string() });
500 }
501 }
502 meta_files
503 .insert(path, MetaFileLocation { offset: entry.offset(), length: entry.length() });
504 }
505 }
506
507 let meta_contents_bytes =
508 reader.read_file(b"meta/contents").map_err(Error::ReadMetaContents)?;
509
510 let non_meta_files = MetaContents::deserialize(&meta_contents_bytes[..])
511 .map_err(Error::DeserializeMetaContents)?
512 .into_contents();
513
514 Ok((meta_files, non_meta_files))
515}
516
517#[derive(Clone, Copy, Debug, PartialEq, Eq)]
519pub(crate) struct MetaFileLocation {
520 offset: u64,
521 length: u64,
522}
523
524#[cfg(test)]
525mod tests {
526 use super::*;
527 use assert_matches::assert_matches;
528 use fidl::endpoints::{create_proxy, Proxy as _};
529 use fuchsia_fs::directory::{DirEntry, DirentKind};
530 use fuchsia_pkg_testing::blobfs::Fake as FakeBlobfs;
531 use fuchsia_pkg_testing::PackageBuilder;
532 use futures::TryStreamExt as _;
533 use pretty_assertions::assert_eq;
534 use std::io::Cursor;
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 = vfs::directory::serve(root_dir, fio::PERM_WRITABLE);
764 assert_matches!(
765 proxy.take_event_stream().try_next().await,
766 Err(fidl::Error::ClientChannelClosed { status: zx::Status::NOT_SUPPORTED, .. })
767 );
768 }
769
770 #[fuchsia_async::run_singlethreaded(test)]
771 async fn root_dir_readdir() {
772 let (_env, root_dir) = TestEnv::new().await;
773 assert_eq!(
774 fuchsia_fs::directory::readdir_inclusive(&root_dir).await.unwrap(),
775 vec![
776 DirEntry { name: ".".to_string(), kind: DirentKind::Directory },
777 DirEntry { name: "dir".to_string(), kind: DirentKind::Directory },
778 DirEntry { name: "meta".to_string(), kind: DirentKind::Directory },
779 DirEntry { name: "resource".to_string(), kind: DirentKind::File }
780 ]
781 );
782 }
783
784 #[fuchsia_async::run_singlethreaded(test)]
785 async fn root_dir_get_attributes() {
786 let (_env, root_dir) = TestEnv::new().await;
787 let (mutable_attributes, immutable_attributes) =
788 root_dir.get_attributes(fio::NodeAttributesQuery::all()).await.unwrap().unwrap();
789 assert_eq!(
790 fio::NodeAttributes2 { mutable_attributes, immutable_attributes },
791 immutable_attributes!(
792 fio::NodeAttributesQuery::all(),
793 Immutable {
794 protocols: fio::NodeProtocolKinds::DIRECTORY,
795 abilities: crate::DIRECTORY_ABILITIES,
796 id: 1,
797 }
798 )
799 );
800 }
801
802 #[fuchsia_async::run_singlethreaded(test)]
803 async fn root_dir_watch_not_supported() {
804 let (_env, root_dir) = TestEnv::new().await;
805 let (_client, server) = fidl::endpoints::create_endpoints();
806 let status =
807 zx::Status::from_raw(root_dir.watch(fio::WatchMask::empty(), 0, server).await.unwrap());
808 assert_eq!(status, zx::Status::NOT_SUPPORTED);
809 }
810
811 #[fuchsia_async::run_singlethreaded(test)]
812 async fn root_dir_open_non_meta_file() {
813 let (_env, root_dir) = TestEnv::new().await;
814 let proxy = fuchsia_fs::directory::open_file(&root_dir, "resource", fio::PERM_READABLE)
815 .await
816 .unwrap();
817 assert_eq!(fuchsia_fs::file::read(&proxy).await.unwrap(), b"blob-contents".to_vec());
818 }
819
820 #[fuchsia_async::run_singlethreaded(test)]
821 async fn root_dir_open_meta_as_file() {
822 let (env, root_dir) = TestEnv::new().await;
823 let proxy =
824 fuchsia_fs::directory::open_file(&root_dir, "meta", fio::PERM_READABLE).await.unwrap();
825 assert_eq!(
826 fuchsia_fs::file::read(&proxy).await.unwrap(),
827 env.root_dir.hash.to_string().as_bytes()
828 );
829 let (cloned_proxy, server_end) = create_proxy::<fio::FileMarker>();
831 proxy.clone(server_end.into_channel().into()).unwrap();
832 assert_eq!(
833 fuchsia_fs::file::read(&cloned_proxy).await.unwrap(),
834 env.root_dir.hash.to_string().as_bytes()
835 );
836 }
837
838 #[fuchsia_async::run_singlethreaded(test)]
839 async fn root_dir_open_meta_as_dir() {
840 let (_env, root_dir) = TestEnv::new().await;
841 for path in ["meta", "meta/"] {
842 let proxy = fuchsia_fs::directory::open_directory(&root_dir, path, fio::PERM_READABLE)
843 .await
844 .unwrap();
845 assert_eq!(
846 fuchsia_fs::directory::readdir(&proxy).await.unwrap(),
847 vec![
848 DirEntry { name: "contents".to_string(), kind: DirentKind::File },
849 DirEntry { name: "dir".to_string(), kind: DirentKind::Directory },
850 DirEntry { name: "file".to_string(), kind: DirentKind::File },
851 DirEntry { name: "fuchsia.abi".to_string(), kind: DirentKind::Directory },
852 DirEntry { name: "package".to_string(), kind: DirentKind::File },
853 ]
854 );
855 let (cloned_proxy, server_end) = create_proxy::<fio::DirectoryMarker>();
857 proxy.clone(server_end.into_channel().into()).unwrap();
858 assert_eq!(
859 fuchsia_fs::directory::readdir(&cloned_proxy).await.unwrap(),
860 vec![
861 DirEntry { name: "contents".to_string(), kind: DirentKind::File },
862 DirEntry { name: "dir".to_string(), kind: DirentKind::Directory },
863 DirEntry { name: "file".to_string(), kind: DirentKind::File },
864 DirEntry { name: "fuchsia.abi".to_string(), kind: DirentKind::Directory },
865 DirEntry { name: "package".to_string(), kind: DirentKind::File },
866 ]
867 );
868 }
869 }
870
871 #[fuchsia_async::run_singlethreaded(test)]
872 async fn root_dir_open_meta_as_node() {
873 let (_env, root_dir) = TestEnv::new().await;
874 for path in ["meta", "meta/"] {
875 let proxy = fuchsia_fs::directory::open_node(
876 &root_dir,
877 path,
878 fio::Flags::PROTOCOL_NODE
879 | fio::Flags::PROTOCOL_DIRECTORY
880 | fio::Flags::PERM_GET_ATTRIBUTES,
881 )
882 .await
883 .unwrap();
884 let (mutable_attributes, immutable_attributes) = proxy
885 .get_attributes(
886 fio::NodeAttributesQuery::PROTOCOLS | fio::NodeAttributesQuery::ABILITIES,
887 )
888 .await
889 .unwrap()
890 .unwrap();
891 assert_eq!(
892 fio::NodeAttributes2 { mutable_attributes, immutable_attributes },
893 immutable_attributes!(
894 fio::NodeAttributesQuery::PROTOCOLS | fio::NodeAttributesQuery::ABILITIES,
895 Immutable {
896 protocols: fio::NodeProtocolKinds::DIRECTORY,
897 abilities: crate::DIRECTORY_ABILITIES
898 }
899 )
900 );
901 }
902 let proxy = fuchsia_fs::directory::open_node(
904 &root_dir,
905 "meta",
906 fio::Flags::PROTOCOL_NODE | fio::Flags::PERM_GET_ATTRIBUTES,
907 )
908 .await
909 .unwrap();
910 let (mutable_attributes, immutable_attributes) = proxy
911 .get_attributes(
912 fio::NodeAttributesQuery::PROTOCOLS | fio::NodeAttributesQuery::ABILITIES,
913 )
914 .await
915 .unwrap()
916 .unwrap();
917 assert_eq!(
918 fio::NodeAttributes2 { mutable_attributes, immutable_attributes },
919 immutable_attributes!(
920 fio::NodeAttributesQuery::PROTOCOLS | fio::NodeAttributesQuery::ABILITIES,
921 Immutable {
922 protocols: fio::NodeProtocolKinds::FILE,
923 abilities: fio::Abilities::READ_BYTES | fio::Abilities::GET_ATTRIBUTES,
924 }
925 )
926 );
927 }
928
929 #[fuchsia_async::run_singlethreaded(test)]
930 async fn root_dir_open_meta_file() {
931 let (_env, root_dir) = TestEnv::new().await;
932 let proxy = fuchsia_fs::directory::open_file(&root_dir, "meta/file", fio::PERM_READABLE)
933 .await
934 .unwrap();
935 assert_eq!(fuchsia_fs::file::read(&proxy).await.unwrap(), b"meta-contents0".to_vec());
936 }
937
938 #[fuchsia_async::run_singlethreaded(test)]
939 async fn root_dir_open_meta_subdir() {
940 let (_env, root_dir) = TestEnv::new().await;
941 for path in ["meta/dir", "meta/dir/"] {
942 let proxy = fuchsia_fs::directory::open_directory(&root_dir, path, fio::PERM_READABLE)
943 .await
944 .unwrap();
945 assert_eq!(
946 fuchsia_fs::directory::readdir(&proxy).await.unwrap(),
947 vec![DirEntry { name: "file".to_string(), kind: DirentKind::File }]
948 );
949 }
950 }
951
952 #[fuchsia_async::run_singlethreaded(test)]
953 async fn root_dir_open_non_meta_subdir() {
954 let (_env, root_dir) = TestEnv::new().await;
955 for path in ["dir", "dir/"] {
956 let proxy = fuchsia_fs::directory::open_directory(&root_dir, path, fio::PERM_READABLE)
957 .await
958 .unwrap();
959 assert_eq!(
960 fuchsia_fs::directory::readdir(&proxy).await.unwrap(),
961 vec![DirEntry { name: "file".to_string(), kind: DirentKind::File }]
962 );
963 }
964 }
965
966 #[fuchsia_async::run_singlethreaded(test)]
967 async fn root_dir_deprecated_open_self() {
968 let (_env, root_dir) = TestEnv::new().await;
969 let (proxy, server_end) = create_proxy::<fio::DirectoryMarker>();
970 root_dir
971 .deprecated_open(
972 fio::OpenFlags::RIGHT_READABLE,
973 Default::default(),
974 ".",
975 server_end.into_channel().into(),
976 )
977 .unwrap();
978 assert_eq!(
979 fuchsia_fs::directory::readdir(&proxy).await.unwrap(),
980 vec![
981 DirEntry { name: "dir".to_string(), kind: DirentKind::Directory },
982 DirEntry { name: "meta".to_string(), kind: DirentKind::Directory },
983 DirEntry { name: "resource".to_string(), kind: DirentKind::File }
984 ]
985 );
986 }
987
988 #[fuchsia_async::run_singlethreaded(test)]
989 async fn root_dir_deprecated_open_non_meta_file() {
990 let (_env, root_dir) = TestEnv::new().await;
991 let (proxy, server_end) = create_proxy();
992 root_dir
993 .deprecated_open(
994 fio::OpenFlags::RIGHT_READABLE,
995 Default::default(),
996 "resource",
997 server_end,
998 )
999 .unwrap();
1000 assert_eq!(
1001 fuchsia_fs::file::read(&fio::FileProxy::from_channel(proxy.into_channel().unwrap()))
1002 .await
1003 .unwrap(),
1004 b"blob-contents".to_vec()
1005 );
1006 }
1007
1008 #[fuchsia_async::run_singlethreaded(test)]
1009 async fn root_dir_deprecated_open_meta_as_file() {
1010 let (env, root_dir) = TestEnv::new().await;
1011 let (proxy, server_end) = create_proxy::<fio::FileMarker>();
1012 root_dir
1013 .deprecated_open(
1014 fio::OpenFlags::RIGHT_READABLE | fio::OpenFlags::NOT_DIRECTORY,
1015 Default::default(),
1016 "meta",
1017 server_end.into_channel().into(),
1018 )
1019 .unwrap();
1020 assert_eq!(
1021 fuchsia_fs::file::read(&proxy).await.unwrap(),
1022 env.root_dir.hash.to_string().as_bytes()
1023 );
1024 }
1025
1026 #[fuchsia_async::run_singlethreaded(test)]
1027 async fn root_dir_deprecated_open_meta_as_dir() {
1028 let (_env, root_dir) = TestEnv::new().await;
1029 for path in ["meta", "meta/"] {
1030 let (proxy, server_end) = create_proxy::<fio::DirectoryMarker>();
1031 root_dir
1032 .deprecated_open(
1033 fio::OpenFlags::RIGHT_READABLE | fio::OpenFlags::DIRECTORY,
1034 Default::default(),
1035 path,
1036 server_end.into_channel().into(),
1037 )
1038 .unwrap();
1039 assert_eq!(
1040 fuchsia_fs::directory::readdir(&proxy).await.unwrap(),
1041 vec![
1042 DirEntry { name: "contents".to_string(), kind: DirentKind::File },
1043 DirEntry { name: "dir".to_string(), kind: DirentKind::Directory },
1044 DirEntry { name: "file".to_string(), kind: DirentKind::File },
1045 DirEntry { name: "fuchsia.abi".to_string(), kind: DirentKind::Directory },
1046 DirEntry { name: "package".to_string(), kind: DirentKind::File },
1047 ]
1048 );
1049 }
1050 }
1051
1052 #[fuchsia_async::run_singlethreaded(test)]
1053 async fn root_dir_deprecated_open_meta_as_node_reference() {
1054 let (_env, root_dir) = TestEnv::new().await;
1055 for path in ["meta", "meta/"] {
1056 let (proxy, server_end) = create_proxy::<fio::NodeMarker>();
1057 root_dir
1058 .deprecated_open(
1059 fio::OpenFlags::RIGHT_READABLE | fio::OpenFlags::NODE_REFERENCE,
1060 Default::default(),
1061 path,
1062 server_end.into_channel().into(),
1063 )
1064 .unwrap();
1065 let (_, immutable_attributes) =
1068 proxy.get_attributes(fio::NodeAttributesQuery::PROTOCOLS).await.unwrap().unwrap();
1069
1070 assert_eq!(
1071 immutable_attributes.protocols.unwrap_or_default(),
1072 fio::NodeProtocolKinds::DIRECTORY
1073 );
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}