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