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