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 }
459
460 fn register_watcher(
461 self: Arc<Self>,
462 _: ExecutionScope,
463 _: fio::WatchMask,
464 _: vfs::directory::entry_container::DirectoryWatcher,
465 ) -> Result<(), zx::Status> {
466 Err(zx::Status::NOT_SUPPORTED)
467 }
468
469 fn unregister_watcher(self: Arc<Self>, _: usize) {}
471}
472
473#[allow(clippy::type_complexity)]
474fn load_package_metadata(
475 meta_far_vmo: &zx::Vmo,
476) -> Result<(HashMap<String, MetaFileLocation>, HashMap<String, fuchsia_hash::Hash>), Error> {
477 let stream =
478 zx::Stream::create(zx::StreamOptions::MODE_READ, meta_far_vmo, 0).map_err(|e| {
479 Error::OpenMetaFar(NonMetaStorageError::ReadBlob(
480 fuchsia_fs::file::ReadError::ReadError(e),
481 ))
482 })?;
483
484 let mut reader = fuchsia_archive::Reader::new(stream).map_err(Error::ArchiveReader)?;
485 let reader_list = reader.list();
486 let mut meta_files = HashMap::with_capacity(reader_list.len());
487 for entry in reader_list {
488 let path = std::str::from_utf8(entry.path())
489 .map_err(|source| Error::NonUtf8MetaEntry { source, path: entry.path().to_owned() })?
490 .to_owned();
491 if path.starts_with("meta/") {
492 for (i, _) in path.match_indices('/').skip(1) {
493 if meta_files.contains_key(&path[..i]) {
494 return Err(Error::FileDirectoryCollision { path: path[..i].to_string() });
495 }
496 }
497 meta_files
498 .insert(path, MetaFileLocation { offset: entry.offset(), length: entry.length() });
499 }
500 }
501
502 let meta_contents_bytes =
503 reader.read_file(b"meta/contents").map_err(Error::ReadMetaContents)?;
504
505 let non_meta_files = MetaContents::deserialize(&meta_contents_bytes[..])
506 .map_err(Error::DeserializeMetaContents)?
507 .into_contents();
508
509 Ok((meta_files, non_meta_files))
510}
511
512#[derive(Clone, Copy, Debug, PartialEq, Eq)]
514pub(crate) struct MetaFileLocation {
515 offset: u64,
516 length: u64,
517}
518
519#[cfg(test)]
520mod tests {
521 use super::*;
522 use assert_matches::assert_matches;
523 use fidl::endpoints::{Proxy as _, create_proxy};
524 use fuchsia_fs::directory::{DirEntry, DirentKind};
525 use fuchsia_pkg_testing::PackageBuilder;
526 use fuchsia_pkg_testing::blobfs::Fake as FakeBlobfs;
527 use futures::TryStreamExt as _;
528 use pretty_assertions::assert_eq;
529 use std::io::Cursor;
530
531 struct TestEnv {
532 _blobfs_fake: FakeBlobfs,
533 root_dir: Arc<RootDir<blobfs::Client>>,
534 }
535
536 impl TestEnv {
537 async fn with_subpackages(
538 subpackages_content: Option<&[u8]>,
539 ) -> (Self, Arc<RootDir<blobfs::Client>>) {
540 let mut pkg = PackageBuilder::new("base-package-0")
541 .add_resource_at("resource", "blob-contents".as_bytes())
542 .add_resource_at("dir/file", "bloblob".as_bytes())
543 .add_resource_at("meta/file", "meta-contents0".as_bytes())
544 .add_resource_at("meta/dir/file", "meta-contents1".as_bytes());
545 if let Some(subpackages_content) = subpackages_content {
546 pkg = pkg.add_resource_at(fuchsia_pkg::MetaSubpackages::PATH, subpackages_content);
547 }
548 let pkg = pkg.build().await.unwrap();
549 let (metafar_blob, content_blobs) = pkg.contents();
550 let (blobfs_fake, blobfs_client) = FakeBlobfs::new();
551 blobfs_fake.add_blob(metafar_blob.merkle, metafar_blob.contents);
552 for (hash, bytes) in content_blobs {
553 blobfs_fake.add_blob(hash, bytes);
554 }
555
556 let root_dir = RootDir::new(blobfs_client, metafar_blob.merkle).await.unwrap();
557 (Self { _blobfs_fake: blobfs_fake, root_dir: root_dir.clone() }, root_dir)
558 }
559
560 async fn new() -> (Self, fio::DirectoryProxy) {
561 let (env, root) = Self::with_subpackages(None).await;
562 (env, vfs::directory::serve_read_only(root))
563 }
564 }
565
566 #[fuchsia_async::run_singlethreaded(test)]
567 async fn new_missing_meta_far_error() {
568 let (_blobfs_fake, blobfs_client) = FakeBlobfs::new();
569 assert_matches!(
570 RootDir::new(blobfs_client, [0; 32].into()).await,
571 Err(Error::MissingMetaFar)
572 );
573 }
574
575 #[fuchsia_async::run_singlethreaded(test)]
576 async fn new_rejects_invalid_utf8() {
577 let (blobfs_fake, blobfs_client) = FakeBlobfs::new();
578 let mut meta_far = vec![];
579 let () = fuchsia_archive::write(
580 &mut meta_far,
581 std::collections::BTreeMap::from_iter([(
582 b"\xff",
583 (0, Box::new("".as_bytes()) as Box<dyn std::io::Read>),
584 )]),
585 )
586 .unwrap();
587 let hash = fuchsia_merkle::from_slice(&meta_far).root();
588 let () = blobfs_fake.add_blob(hash, meta_far);
589
590 assert_matches!(
591 RootDir::new(blobfs_client, hash).await,
592 Err(Error::NonUtf8MetaEntry{path, ..})
593 if path == vec![255]
594 );
595 }
596
597 #[fuchsia_async::run_singlethreaded(test)]
598 async fn new_initializes_maps() {
599 let (_env, root_dir) = TestEnv::with_subpackages(None).await;
600
601 let meta_files = HashMap::from([
602 (String::from("meta/contents"), MetaFileLocation { offset: 4096, length: 148 }),
603 (String::from("meta/package"), MetaFileLocation { offset: 20480, length: 39 }),
604 (String::from("meta/file"), MetaFileLocation { offset: 12288, length: 14 }),
605 (String::from("meta/dir/file"), MetaFileLocation { offset: 8192, length: 14 }),
606 (
607 String::from("meta/fuchsia.abi/abi-revision"),
608 MetaFileLocation { offset: 16384, length: 8 },
609 ),
610 ]);
611 assert_eq!(root_dir.meta_files, meta_files);
612
613 let non_meta_files: HashMap<String, fuchsia_hash::Hash> = [
614 (
615 String::from("resource"),
616 "bd905f783ceae4c5ba8319703d7505ab363733c2db04c52c8405603a02922b15"
617 .parse::<fuchsia_hash::Hash>()
618 .unwrap(),
619 ),
620 (
621 String::from("dir/file"),
622 "5f615dd575994fcbcc174974311d59de258d93cd523d5cb51f0e139b53c33201"
623 .parse::<fuchsia_hash::Hash>()
624 .unwrap(),
625 ),
626 ]
627 .iter()
628 .cloned()
629 .collect();
630 assert_eq!(root_dir.non_meta_files, non_meta_files);
631 }
632
633 #[fuchsia_async::run_singlethreaded(test)]
634 async fn rejects_meta_file_collisions() {
635 let pkg = PackageBuilder::new("base-package-0")
636 .add_resource_at("meta/dir/file", "meta-contents0".as_bytes())
637 .build()
638 .await
639 .unwrap();
640
641 let (metafar_blob, _) = pkg.contents();
643 let mut metafar =
644 fuchsia_archive::Reader::new(Cursor::new(&metafar_blob.contents)).unwrap();
645 let mut entries = std::collections::BTreeMap::new();
646 let farentries =
647 metafar.list().map(|entry| (entry.path().to_vec(), entry.length())).collect::<Vec<_>>();
648 for (path, length) in farentries {
649 let contents = metafar.read_file(&path).unwrap();
650 entries
651 .insert(path, (length, Box::new(Cursor::new(contents)) as Box<dyn std::io::Read>));
652 }
653 let extra_contents = b"meta-contents1";
654 entries.insert(
655 b"meta/dir".to_vec(),
656 (
657 extra_contents.len() as u64,
658 Box::new(Cursor::new(extra_contents)) as Box<dyn std::io::Read>,
659 ),
660 );
661
662 let mut metafar: Vec<u8> = vec![];
663 let () = fuchsia_archive::write(&mut metafar, entries).unwrap();
664 let merkle = fuchsia_merkle::from_slice(&metafar).root();
665
666 let (blobfs_fake, blobfs_client) = FakeBlobfs::new();
668 blobfs_fake.add_blob(merkle, &metafar);
669
670 match RootDir::new(blobfs_client, merkle).await {
671 Ok(_) => panic!("this should not be reached!"),
672 Err(Error::FileDirectoryCollision { path }) => {
673 assert_eq!(path, "meta/dir".to_string());
674 }
675 Err(e) => panic!("Expected collision error, receieved {e:?}"),
676 };
677 }
678
679 #[fuchsia_async::run_singlethreaded(test)]
680 async fn read_file() {
681 let (_env, root_dir) = TestEnv::with_subpackages(None).await;
682
683 assert_eq!(root_dir.read_file("resource").await.unwrap().as_slice(), b"blob-contents");
684 assert_eq!(root_dir.read_file("meta/file").await.unwrap().as_slice(), b"meta-contents0");
685 assert_matches!(
686 root_dir.read_file("missing").await.unwrap_err(),
687 ReadFileError::NoFileAtPath{path} if path == "missing"
688 );
689 }
690
691 #[fuchsia_async::run_singlethreaded(test)]
692 async fn has_file() {
693 let (_env, root_dir) = TestEnv::with_subpackages(None).await;
694
695 assert!(root_dir.has_file("resource"));
696 assert!(root_dir.has_file("meta/file"));
697 assert_eq!(root_dir.has_file("missing"), false);
698 }
699
700 #[fuchsia_async::run_singlethreaded(test)]
701 async fn external_file_hashes() {
702 let (_env, root_dir) = TestEnv::with_subpackages(None).await;
703
704 let mut actual = root_dir.external_file_hashes().copied().collect::<Vec<_>>();
705 actual.sort();
706 assert_eq!(
707 actual,
708 vec![
709 "5f615dd575994fcbcc174974311d59de258d93cd523d5cb51f0e139b53c33201".parse().unwrap(),
710 "bd905f783ceae4c5ba8319703d7505ab363733c2db04c52c8405603a02922b15".parse().unwrap()
711 ]
712 );
713 }
714
715 #[fuchsia_async::run_singlethreaded(test)]
716 async fn path() {
717 let (_env, root_dir) = TestEnv::with_subpackages(None).await;
718
719 assert_eq!(
720 root_dir.path().await.unwrap(),
721 "base-package-0/0".parse::<fuchsia_pkg::PackagePath>().unwrap()
722 );
723 }
724
725 #[fuchsia_async::run_singlethreaded(test)]
726 async fn subpackages_present() {
727 let subpackages = fuchsia_pkg::MetaSubpackages::from_iter([(
728 fuchsia_url::RelativePackageUrl::parse("subpackage-name").unwrap(),
729 "0000000000000000000000000000000000000000000000000000000000000000".parse().unwrap(),
730 )]);
731 let mut subpackages_bytes = vec![];
732 let () = subpackages.serialize(&mut subpackages_bytes).unwrap();
733 let (_env, root_dir) = TestEnv::with_subpackages(Some(&*subpackages_bytes)).await;
734
735 assert_eq!(root_dir.subpackages().await.unwrap(), subpackages);
736 }
737
738 #[fuchsia_async::run_singlethreaded(test)]
739 async fn subpackages_absent() {
740 let (_env, root_dir) = TestEnv::with_subpackages(None).await;
741
742 assert_eq!(root_dir.subpackages().await.unwrap(), fuchsia_pkg::MetaSubpackages::default());
743 }
744
745 #[fuchsia_async::run_singlethreaded(test)]
746 async fn subpackages_error() {
747 let (_env, root_dir) = TestEnv::with_subpackages(Some(b"invalid-json")).await;
748
749 assert_matches!(root_dir.subpackages().await, Err(SubpackagesError::Parse(_)));
750 }
751
752 #[fuchsia_async::run_singlethreaded(test)]
756 async fn root_dir_cannot_be_served_as_mutable() {
757 let (_env, root_dir) = TestEnv::with_subpackages(None).await;
758 let proxy = vfs::directory::serve(root_dir, fio::PERM_WRITABLE);
759 assert_matches!(
760 proxy.take_event_stream().try_next().await,
761 Err(fidl::Error::ClientChannelClosed { status: zx::Status::NOT_SUPPORTED, .. })
762 );
763 }
764
765 #[fuchsia_async::run_singlethreaded(test)]
766 async fn root_dir_readdir() {
767 let (_env, root_dir) = TestEnv::new().await;
768 assert_eq!(
769 fuchsia_fs::directory::readdir_inclusive(&root_dir).await.unwrap(),
770 vec![
771 DirEntry { name: ".".to_string(), kind: DirentKind::Directory },
772 DirEntry { name: "dir".to_string(), kind: DirentKind::Directory },
773 DirEntry { name: "meta".to_string(), kind: DirentKind::Directory },
774 DirEntry { name: "resource".to_string(), kind: DirentKind::File }
775 ]
776 );
777 }
778
779 #[fuchsia_async::run_singlethreaded(test)]
780 async fn root_dir_get_attributes() {
781 let (_env, root_dir) = TestEnv::new().await;
782 let (mutable_attributes, immutable_attributes) =
783 root_dir.get_attributes(fio::NodeAttributesQuery::all()).await.unwrap().unwrap();
784 assert_eq!(
785 fio::NodeAttributes2 { mutable_attributes, immutable_attributes },
786 immutable_attributes!(
787 fio::NodeAttributesQuery::all(),
788 Immutable {
789 protocols: fio::NodeProtocolKinds::DIRECTORY,
790 abilities: crate::DIRECTORY_ABILITIES,
791 id: 1,
792 }
793 )
794 );
795 }
796
797 #[fuchsia_async::run_singlethreaded(test)]
798 async fn root_dir_watch_not_supported() {
799 let (_env, root_dir) = TestEnv::new().await;
800 let (_client, server) = fidl::endpoints::create_endpoints();
801 let status =
802 zx::Status::from_raw(root_dir.watch(fio::WatchMask::empty(), 0, server).await.unwrap());
803 assert_eq!(status, zx::Status::NOT_SUPPORTED);
804 }
805
806 #[fuchsia_async::run_singlethreaded(test)]
807 async fn root_dir_open_non_meta_file() {
808 let (_env, root_dir) = TestEnv::new().await;
809 let proxy = fuchsia_fs::directory::open_file(&root_dir, "resource", fio::PERM_READABLE)
810 .await
811 .unwrap();
812 assert_eq!(fuchsia_fs::file::read(&proxy).await.unwrap(), b"blob-contents".to_vec());
813 }
814
815 #[fuchsia_async::run_singlethreaded(test)]
816 async fn root_dir_open_meta_as_file() {
817 let (env, root_dir) = TestEnv::new().await;
818 let proxy =
819 fuchsia_fs::directory::open_file(&root_dir, "meta", fio::PERM_READABLE).await.unwrap();
820 assert_eq!(
821 fuchsia_fs::file::read(&proxy).await.unwrap(),
822 env.root_dir.hash.to_string().as_bytes()
823 );
824 let (cloned_proxy, server_end) = create_proxy::<fio::FileMarker>();
826 proxy.clone(server_end.into_channel().into()).unwrap();
827 assert_eq!(
828 fuchsia_fs::file::read(&cloned_proxy).await.unwrap(),
829 env.root_dir.hash.to_string().as_bytes()
830 );
831 }
832
833 #[fuchsia_async::run_singlethreaded(test)]
834 async fn root_dir_open_meta_as_dir() {
835 let (_env, root_dir) = TestEnv::new().await;
836 for path in ["meta", "meta/"] {
837 let proxy = fuchsia_fs::directory::open_directory(&root_dir, path, fio::PERM_READABLE)
838 .await
839 .unwrap();
840 assert_eq!(
841 fuchsia_fs::directory::readdir(&proxy).await.unwrap(),
842 vec![
843 DirEntry { name: "contents".to_string(), kind: DirentKind::File },
844 DirEntry { name: "dir".to_string(), kind: DirentKind::Directory },
845 DirEntry { name: "file".to_string(), kind: DirentKind::File },
846 DirEntry { name: "fuchsia.abi".to_string(), kind: DirentKind::Directory },
847 DirEntry { name: "package".to_string(), kind: DirentKind::File },
848 ]
849 );
850 let (cloned_proxy, server_end) = create_proxy::<fio::DirectoryMarker>();
852 proxy.clone(server_end.into_channel().into()).unwrap();
853 assert_eq!(
854 fuchsia_fs::directory::readdir(&cloned_proxy).await.unwrap(),
855 vec![
856 DirEntry { name: "contents".to_string(), kind: DirentKind::File },
857 DirEntry { name: "dir".to_string(), kind: DirentKind::Directory },
858 DirEntry { name: "file".to_string(), kind: DirentKind::File },
859 DirEntry { name: "fuchsia.abi".to_string(), kind: DirentKind::Directory },
860 DirEntry { name: "package".to_string(), kind: DirentKind::File },
861 ]
862 );
863 }
864 }
865
866 #[fuchsia_async::run_singlethreaded(test)]
867 async fn root_dir_open_meta_as_node() {
868 let (_env, root_dir) = TestEnv::new().await;
869 for path in ["meta", "meta/"] {
870 let proxy = fuchsia_fs::directory::open_node(
871 &root_dir,
872 path,
873 fio::Flags::PROTOCOL_NODE
874 | fio::Flags::PROTOCOL_DIRECTORY
875 | fio::Flags::PERM_GET_ATTRIBUTES,
876 )
877 .await
878 .unwrap();
879 let (mutable_attributes, immutable_attributes) = proxy
880 .get_attributes(
881 fio::NodeAttributesQuery::PROTOCOLS | fio::NodeAttributesQuery::ABILITIES,
882 )
883 .await
884 .unwrap()
885 .unwrap();
886 assert_eq!(
887 fio::NodeAttributes2 { mutable_attributes, immutable_attributes },
888 immutable_attributes!(
889 fio::NodeAttributesQuery::PROTOCOLS | fio::NodeAttributesQuery::ABILITIES,
890 Immutable {
891 protocols: fio::NodeProtocolKinds::DIRECTORY,
892 abilities: crate::DIRECTORY_ABILITIES
893 }
894 )
895 );
896 }
897 let proxy = fuchsia_fs::directory::open_node(
899 &root_dir,
900 "meta",
901 fio::Flags::PROTOCOL_NODE | fio::Flags::PERM_GET_ATTRIBUTES,
902 )
903 .await
904 .unwrap();
905 let (mutable_attributes, immutable_attributes) = proxy
906 .get_attributes(
907 fio::NodeAttributesQuery::PROTOCOLS | fio::NodeAttributesQuery::ABILITIES,
908 )
909 .await
910 .unwrap()
911 .unwrap();
912 assert_eq!(
913 fio::NodeAttributes2 { mutable_attributes, immutable_attributes },
914 immutable_attributes!(
915 fio::NodeAttributesQuery::PROTOCOLS | fio::NodeAttributesQuery::ABILITIES,
916 Immutable {
917 protocols: fio::NodeProtocolKinds::FILE,
918 abilities: fio::Abilities::READ_BYTES | fio::Abilities::GET_ATTRIBUTES,
919 }
920 )
921 );
922 }
923
924 #[fuchsia_async::run_singlethreaded(test)]
925 async fn root_dir_open_meta_file() {
926 let (_env, root_dir) = TestEnv::new().await;
927 let proxy = fuchsia_fs::directory::open_file(&root_dir, "meta/file", fio::PERM_READABLE)
928 .await
929 .unwrap();
930 assert_eq!(fuchsia_fs::file::read(&proxy).await.unwrap(), b"meta-contents0".to_vec());
931 }
932
933 #[fuchsia_async::run_singlethreaded(test)]
934 async fn root_dir_open_meta_subdir() {
935 let (_env, root_dir) = TestEnv::new().await;
936 for path in ["meta/dir", "meta/dir/"] {
937 let proxy = fuchsia_fs::directory::open_directory(&root_dir, path, fio::PERM_READABLE)
938 .await
939 .unwrap();
940 assert_eq!(
941 fuchsia_fs::directory::readdir(&proxy).await.unwrap(),
942 vec![DirEntry { name: "file".to_string(), kind: DirentKind::File }]
943 );
944 }
945 }
946
947 #[fuchsia_async::run_singlethreaded(test)]
948 async fn root_dir_open_non_meta_subdir() {
949 let (_env, root_dir) = TestEnv::new().await;
950 for path in ["dir", "dir/"] {
951 let proxy = fuchsia_fs::directory::open_directory(&root_dir, path, fio::PERM_READABLE)
952 .await
953 .unwrap();
954 assert_eq!(
955 fuchsia_fs::directory::readdir(&proxy).await.unwrap(),
956 vec![DirEntry { name: "file".to_string(), kind: DirentKind::File }]
957 );
958 }
959 }
960
961 #[fuchsia_async::run_singlethreaded(test)]
962 async fn root_dir_deprecated_open_self() {
963 let (_env, root_dir) = TestEnv::new().await;
964 let (proxy, server_end) = create_proxy::<fio::DirectoryMarker>();
965 root_dir
966 .deprecated_open(
967 fio::OpenFlags::RIGHT_READABLE,
968 Default::default(),
969 ".",
970 server_end.into_channel().into(),
971 )
972 .unwrap();
973 assert_eq!(
974 fuchsia_fs::directory::readdir(&proxy).await.unwrap(),
975 vec![
976 DirEntry { name: "dir".to_string(), kind: DirentKind::Directory },
977 DirEntry { name: "meta".to_string(), kind: DirentKind::Directory },
978 DirEntry { name: "resource".to_string(), kind: DirentKind::File }
979 ]
980 );
981 }
982
983 #[fuchsia_async::run_singlethreaded(test)]
984 async fn root_dir_deprecated_open_non_meta_file() {
985 let (_env, root_dir) = TestEnv::new().await;
986 let (proxy, server_end) = create_proxy();
987 root_dir
988 .deprecated_open(
989 fio::OpenFlags::RIGHT_READABLE,
990 Default::default(),
991 "resource",
992 server_end,
993 )
994 .unwrap();
995 assert_eq!(
996 fuchsia_fs::file::read(&fio::FileProxy::from_channel(proxy.into_channel().unwrap()))
997 .await
998 .unwrap(),
999 b"blob-contents".to_vec()
1000 );
1001 }
1002
1003 #[fuchsia_async::run_singlethreaded(test)]
1004 async fn root_dir_deprecated_open_meta_as_file() {
1005 let (env, root_dir) = TestEnv::new().await;
1006 let (proxy, server_end) = create_proxy::<fio::FileMarker>();
1007 root_dir
1008 .deprecated_open(
1009 fio::OpenFlags::RIGHT_READABLE | fio::OpenFlags::NOT_DIRECTORY,
1010 Default::default(),
1011 "meta",
1012 server_end.into_channel().into(),
1013 )
1014 .unwrap();
1015 assert_eq!(
1016 fuchsia_fs::file::read(&proxy).await.unwrap(),
1017 env.root_dir.hash.to_string().as_bytes()
1018 );
1019 }
1020
1021 #[fuchsia_async::run_singlethreaded(test)]
1022 async fn root_dir_deprecated_open_meta_as_dir() {
1023 let (_env, root_dir) = TestEnv::new().await;
1024 for path in ["meta", "meta/"] {
1025 let (proxy, server_end) = create_proxy::<fio::DirectoryMarker>();
1026 root_dir
1027 .deprecated_open(
1028 fio::OpenFlags::RIGHT_READABLE | fio::OpenFlags::DIRECTORY,
1029 Default::default(),
1030 path,
1031 server_end.into_channel().into(),
1032 )
1033 .unwrap();
1034 assert_eq!(
1035 fuchsia_fs::directory::readdir(&proxy).await.unwrap(),
1036 vec![
1037 DirEntry { name: "contents".to_string(), kind: DirentKind::File },
1038 DirEntry { name: "dir".to_string(), kind: DirentKind::Directory },
1039 DirEntry { name: "file".to_string(), kind: DirentKind::File },
1040 DirEntry { name: "fuchsia.abi".to_string(), kind: DirentKind::Directory },
1041 DirEntry { name: "package".to_string(), kind: DirentKind::File },
1042 ]
1043 );
1044 }
1045 }
1046
1047 #[fuchsia_async::run_singlethreaded(test)]
1048 async fn root_dir_deprecated_open_meta_as_node_reference() {
1049 let (_env, root_dir) = TestEnv::new().await;
1050 for path in ["meta", "meta/"] {
1051 let (proxy, server_end) = create_proxy::<fio::NodeMarker>();
1052 root_dir
1053 .deprecated_open(
1054 fio::OpenFlags::RIGHT_READABLE | fio::OpenFlags::NODE_REFERENCE,
1055 Default::default(),
1056 path,
1057 server_end.into_channel().into(),
1058 )
1059 .unwrap();
1060 let (_, immutable_attributes) =
1063 proxy.get_attributes(fio::NodeAttributesQuery::PROTOCOLS).await.unwrap().unwrap();
1064
1065 assert_eq!(
1066 immutable_attributes.protocols.unwrap_or_default(),
1067 fio::NodeProtocolKinds::DIRECTORY
1068 );
1069 }
1070 }
1071
1072 #[fuchsia_async::run_singlethreaded(test)]
1073 async fn root_dir_deprecated_open_meta_file() {
1074 let (_env, root_dir) = TestEnv::new().await;
1075 let (proxy, server_end) = create_proxy::<fio::FileMarker>();
1076 root_dir
1077 .deprecated_open(
1078 fio::OpenFlags::RIGHT_READABLE,
1079 Default::default(),
1080 "meta/file",
1081 server_end.into_channel().into(),
1082 )
1083 .unwrap();
1084 assert_eq!(fuchsia_fs::file::read(&proxy).await.unwrap(), b"meta-contents0".to_vec());
1085 }
1086
1087 #[fuchsia_async::run_singlethreaded(test)]
1088 async fn root_dir_deprecated_open_meta_subdir() {
1089 let (_env, root_dir) = TestEnv::new().await;
1090 for path in ["meta/dir", "meta/dir/"] {
1091 let (proxy, server_end) = create_proxy::<fio::DirectoryMarker>();
1092 root_dir
1093 .deprecated_open(
1094 fio::OpenFlags::RIGHT_READABLE,
1095 Default::default(),
1096 path,
1097 server_end.into_channel().into(),
1098 )
1099 .unwrap();
1100 assert_eq!(
1101 fuchsia_fs::directory::readdir(&proxy).await.unwrap(),
1102 vec![DirEntry { name: "file".to_string(), kind: DirentKind::File }]
1103 );
1104 }
1105 }
1106
1107 #[fuchsia_async::run_singlethreaded(test)]
1108 async fn root_dir_deprecated_open_non_meta_subdir() {
1109 let (_env, root_dir) = TestEnv::new().await;
1110 for path in ["dir", "dir/"] {
1111 let (proxy, server_end) = create_proxy::<fio::DirectoryMarker>();
1112 root_dir
1113 .deprecated_open(
1114 fio::OpenFlags::RIGHT_READABLE,
1115 Default::default(),
1116 path,
1117 server_end.into_channel().into(),
1118 )
1119 .unwrap();
1120 assert_eq!(
1121 fuchsia_fs::directory::readdir(&proxy).await.unwrap(),
1122 vec![DirEntry { name: "file".to_string(), kind: DirentKind::File }]
1123 );
1124 }
1125 }
1126}