1use crate::meta_as_dir::MetaAsDir;
6use crate::meta_subdir::MetaSubdir;
7use crate::non_meta_subdir::NonMetaSubdir;
8use crate::{Error, NonMetaStorage as _, NonMetaStorageError, usize_to_u64_safe};
9use fidl_fuchsia_io as fio;
10use fuchsia_pkg::MetaContents;
11use log::error;
12use packed::PackedMap;
13use std::sync::Arc;
14use vfs::directory::entry::{EntryInfo, OpenRequest};
15use vfs::directory::entry_container::Directory as _;
16use vfs::directory::immutable::connection::ImmutableConnection;
17use vfs::directory::traversal_position::TraversalPosition;
18use vfs::execution_scope::ExecutionScope;
19use vfs::file::vmo::VmoFile;
20use vfs::{ObjectRequestRef, ProtocolsExt as _, immutable_attributes};
21
22#[derive(Debug)]
24pub struct RootDir<S> {
25 pub(crate) non_meta_storage: S,
26 pub(crate) hash: fuchsia_hash::Hash,
27 pub(crate) meta_files: PackedMap<str, MetaFileLocation>,
29 pub(crate) non_meta_files: PackedMap<str, fuchsia_hash::Hash>,
31 pub(crate) meta_far_vmo: zx::Vmo,
32}
33
34impl<S: crate::NonMetaStorage> RootDir<S> {
35 pub async fn new(non_meta_storage: S, hash: fuchsia_hash::Hash) -> Result<Arc<Self>, Error> {
38 Ok(Arc::new(Self::new_raw(non_meta_storage, hash).await?))
39 }
40
41 pub async fn new_raw(non_meta_storage: S, hash: fuchsia_hash::Hash) -> Result<Self, Error> {
45 let meta_far_vmo = non_meta_storage.get_blob_vmo(&hash).await.map_err(|e| {
46 if e.is_not_found_error() { Error::MissingMetaFar } else { Error::OpenMetaFar(e) }
47 })?;
48 let (meta_files, non_meta_files) = load_package_metadata(&meta_far_vmo)?;
49
50 Ok(RootDir { non_meta_storage, hash, meta_files, non_meta_files, meta_far_vmo })
51 }
52
53 pub async fn read_file(&self, path: &str) -> Result<Vec<u8>, ReadFileError> {
56 if let Some(hash) = self.non_meta_files.get(path) {
57 self.non_meta_storage.read_blob(hash).await.map_err(ReadFileError::ReadBlob)
58 } else if let Some(location) = self.meta_files.get(path) {
59 self.meta_far_vmo
60 .read_to_vec(location.offset, location.length)
61 .map_err(ReadFileError::ReadMetaFile)
62 } else {
63 Err(ReadFileError::NoFileAtPath { path: path.to_string() })
64 }
65 }
66
67 pub async fn get_file_vmo(&self, path: &str) -> Result<(zx::Vmo, u64), ReadFileError> {
71 if let Some(hash) = self.non_meta_files.get(path) {
72 let vmo = self
73 .non_meta_storage
74 .get_blob_vmo(hash)
75 .await
76 .map_err(ReadFileError::GetBlobVmo)?;
77 let size = vmo.get_content_size().map_err(ReadFileError::GetContentSize)?;
78 Ok((vmo, size))
79 } else if let Some(location) = self.meta_files.get(path) {
80 let vmo =
81 self.create_meta_file_vmo(location).map_err(ReadFileError::CreateMetaFileVmo)?;
82 Ok((vmo, location.length))
83 } else {
84 Err(ReadFileError::NoFileAtPath { path: path.to_string() })
85 }
86 }
87
88 pub fn has_file(&self, path: &str) -> bool {
91 self.non_meta_files.contains_key(path) || self.meta_files.contains_key(path)
92 }
93
94 pub fn hash(&self) -> &fuchsia_hash::Hash {
96 &self.hash
97 }
98
99 pub fn external_file_hashes(&self) -> impl ExactSizeIterator<Item = &fuchsia_hash::Hash> {
102 self.non_meta_files.values()
103 }
104
105 pub async fn path(&self) -> Result<fuchsia_pkg::PackagePath, PathError> {
107 Ok(fuchsia_pkg::MetaPackage::deserialize(&self.read_file("meta/package").await?[..])?
108 .into_path())
109 }
110
111 pub async fn subpackages(&self) -> Result<fuchsia_pkg::MetaSubpackages, SubpackagesError> {
113 let contents = match self.read_file(fuchsia_pkg::MetaSubpackages::PATH).await {
114 Ok(contents) => contents,
115 Err(ReadFileError::NoFileAtPath { .. }) => {
116 return Ok(fuchsia_pkg::MetaSubpackages::default());
117 }
118 Err(e) => Err(e)?,
119 };
120
121 Ok(fuchsia_pkg::MetaSubpackages::deserialize(&*contents)?)
122 }
123
124 fn create_meta_as_file(&self) -> Result<Arc<VmoFile>, zx::Status> {
126 let file_contents = self.hash.to_string();
127 let vmo = zx::Vmo::create(usize_to_u64_safe(file_contents.len()))?;
128 let () = vmo.write(file_contents.as_bytes(), 0)?;
129 Ok(VmoFile::new_with_inode(vmo, 1))
130 }
131
132 fn create_meta_file_vmo(&self, location: &MetaFileLocation) -> Result<zx::Vmo, zx::Status> {
133 assert_eq!(zx::system_get_page_size(), 4096);
141
142 self.meta_far_vmo.create_child(
143 zx::VmoChildOptions::SNAPSHOT_AT_LEAST_ON_WRITE | zx::VmoChildOptions::NO_WRITE,
144 location.offset,
145 location.length,
146 )
147 }
148
149 pub(crate) fn get_meta_file(&self, path: &str) -> Result<Option<Arc<VmoFile>>, zx::Status> {
151 let location = match self.meta_files.get(path) {
152 Some(location) => location,
153 None => return Ok(None),
154 };
155 let vmo = self.create_meta_file_vmo(location).map_err(|e| {
156 error!("Error creating child vmo for meta file {:?}", e);
157 zx::Status::INTERNAL
158 })?;
159
160 Ok(Some(VmoFile::new_with_inode(vmo, 1)))
161 }
162}
163
164pub(crate) trait AsRootDir: Send + Sync + Sized + 'static {
165 type Storage: crate::NonMetaStorage;
166 fn as_root_dir(&self) -> &RootDir<Self::Storage>;
167
168 fn get_meta_subdir(self: &Arc<Self>, path: String) -> Option<Arc<MetaSubdir<Self>>> {
170 debug_assert!(path.ends_with("/"));
171 if let Some((k, _)) = self.as_root_dir().meta_files.range(path.as_str()..).next()
172 && k.starts_with(&path)
173 {
174 return Some(MetaSubdir::new(self.clone(), path));
175 }
176 None
177 }
178
179 fn get_non_meta_subdir(self: &Arc<Self>, path: String) -> Option<Arc<NonMetaSubdir<Self>>> {
181 debug_assert!(path.ends_with("/"));
182 if let Some((k, _)) = self.as_root_dir().non_meta_files.range(path.as_str()..).next()
183 && k.starts_with(&path)
184 {
185 return Some(NonMetaSubdir::new(self.clone(), path));
186 }
187 None
188 }
189}
190
191impl<S: crate::NonMetaStorage> AsRootDir for RootDir<S> {
192 type Storage = S;
193 fn as_root_dir(&self) -> &RootDir<S> {
194 self
195 }
196}
197
198#[derive(thiserror::Error, Debug)]
199pub enum ReadFileError {
200 #[error("reading blob")]
201 ReadBlob(#[source] NonMetaStorageError),
202
203 #[error("getting blob vmo")]
204 GetBlobVmo(#[source] NonMetaStorageError),
205
206 #[error("getting blob vmo content size")]
207 GetContentSize(#[source] zx::Status),
208
209 #[error("reading meta file")]
210 ReadMetaFile(#[source] zx::Status),
211
212 #[error("creating meta file child vmo")]
213 CreateMetaFileVmo(#[source] zx::Status),
214
215 #[error("no file exists at path: {path:?}")]
216 NoFileAtPath { path: String },
217}
218
219#[derive(thiserror::Error, Debug)]
220pub enum SubpackagesError {
221 #[error("reading manifest")]
222 Read(#[from] ReadFileError),
223
224 #[error("parsing manifest")]
225 Parse(#[from] fuchsia_pkg::MetaSubpackagesError),
226}
227
228#[derive(thiserror::Error, Debug)]
229pub enum PathError {
230 #[error("reading meta/package")]
231 Read(#[from] ReadFileError),
232
233 #[error("parsing meta/package")]
234 Parse(#[from] fuchsia_pkg::MetaPackageError),
235}
236
237impl<S: crate::NonMetaStorage> vfs::directory::entry::DirectoryEntry for RootDir<S> {
238 fn open_entry(self: Arc<Self>, request: OpenRequest<'_>) -> Result<(), zx::Status> {
239 request.open_dir(self)
240 }
241}
242
243impl<S: crate::NonMetaStorage> vfs::directory::entry::GetEntryInfo for RootDir<S> {
244 fn entry_info(&self) -> EntryInfo {
245 EntryInfo::new(fio::INO_UNKNOWN, fio::DirentType::Directory)
246 }
247}
248
249impl<S: crate::NonMetaStorage> vfs::node::Node for RootDir<S> {
250 async fn get_attributes(
251 &self,
252 requested_attributes: fio::NodeAttributesQuery,
253 ) -> Result<fio::NodeAttributes2, zx::Status> {
254 Ok(immutable_attributes!(
255 requested_attributes,
256 Immutable {
257 protocols: fio::NodeProtocolKinds::DIRECTORY,
258 abilities: crate::DIRECTORY_ABILITIES,
259 id: 1,
260 }
261 ))
262 }
263}
264
265pub(crate) fn open_impl<R>(
266 this: Arc<R>,
267 scope: ExecutionScope,
268 path: vfs::Path,
269 flags: fio::Flags,
270 object_request: ObjectRequestRef<'_>,
271) -> Result<(), zx::Status>
272where
273 R: AsRootDir + vfs::directory::entry_container::Directory,
274{
275 if !flags.difference(crate::ALLOWED_FLAGS).is_empty() {
276 return Err(zx::Status::NOT_SUPPORTED);
277 }
278
279 if path.is_empty() {
281 object_request
283 .take()
284 .create_connection_sync::<ImmutableConnection<_>, _>(scope, this, flags);
285 return Ok(());
286 }
287
288 let canonical_path = path.as_ref().strip_suffix('/').unwrap_or_else(|| path.as_ref());
290
291 if canonical_path == "meta" {
292 let open_meta_as_dir = flags.is_dir_allowed() && !flags.is_file_allowed();
301 if !open_meta_as_dir {
302 if path.is_dir() {
303 return Err(zx::Status::NOT_DIR);
304 }
305 let file = this.as_root_dir().create_meta_as_file().map_err(|e| {
306 error!("Error creating the meta file: {:?}", e);
307 zx::Status::INTERNAL
308 })?;
309 return vfs::file::serve(file, scope, &flags, object_request);
310 }
311 return MetaAsDir::new(this).open(scope, vfs::Path::dot(), flags, object_request);
312 }
313
314 if canonical_path.starts_with("meta/") {
315 if let Some(file) = this.as_root_dir().get_meta_file(canonical_path)? {
316 if path.is_dir() {
317 return Err(zx::Status::NOT_DIR);
318 }
319 return vfs::file::serve(file, scope, &flags, object_request);
320 }
321
322 if let Some(subdir) = this.get_meta_subdir(canonical_path.to_string() + "/") {
323 return subdir.open(scope, vfs::Path::dot(), flags, object_request);
324 }
325 return Err(zx::Status::NOT_FOUND);
326 }
327
328 if let Some(blob) = this.as_root_dir().non_meta_files.get(canonical_path) {
329 if path.is_dir() {
330 return Err(zx::Status::NOT_DIR);
331 }
332 return this.as_root_dir().non_meta_storage.open(blob, flags, scope, object_request);
333 }
334
335 if let Some(subdir) = this.get_non_meta_subdir(canonical_path.to_string() + "/") {
336 return subdir.open(scope, vfs::Path::dot(), flags, object_request);
337 }
338
339 Err(zx::Status::NOT_FOUND)
340}
341
342impl<S: crate::NonMetaStorage> vfs::directory::entry_container::Directory for RootDir<S> {
343 fn open(
344 self: Arc<Self>,
345 scope: ExecutionScope,
346 path: vfs::Path,
347 flags: fio::Flags,
348 object_request: ObjectRequestRef<'_>,
349 ) -> Result<(), zx::Status> {
350 open_impl(self, scope, path, flags, object_request)
351 }
352
353 async fn read_dirents(
354 &self,
355 pos: &TraversalPosition,
356 sink: Box<dyn vfs::directory::dirents_sink::Sink + 'static>,
357 ) -> Result<
358 (TraversalPosition, Box<dyn vfs::directory::dirents_sink::Sealed + 'static>),
359 zx::Status,
360 > {
361 vfs::directory::read_dirents::read_dirents(
362 &crate::get_dir_children(self.non_meta_files.keys().chain(["meta/placeholder"]), ""),
364 pos,
365 sink,
366 )
367 }
368
369 fn register_watcher(
370 self: Arc<Self>,
371 _: ExecutionScope,
372 _: fio::WatchMask,
373 _: vfs::directory::entry_container::DirectoryWatcher,
374 ) -> Result<(), zx::Status> {
375 Err(zx::Status::NOT_SUPPORTED)
376 }
377
378 fn unregister_watcher(self: Arc<Self>, _: usize) {}
380}
381
382#[allow(clippy::type_complexity)]
383fn load_package_metadata(
384 meta_far_vmo: &zx::Vmo,
385) -> Result<(PackedMap<str, MetaFileLocation>, PackedMap<str, fuchsia_hash::Hash>), Error> {
386 let stream =
387 zx::Stream::create(zx::StreamOptions::MODE_READ, meta_far_vmo, 0).map_err(|e| {
388 Error::OpenMetaFar(NonMetaStorageError::ReadBlob(
389 fuchsia_fs::file::ReadError::ReadError(e),
390 ))
391 })?;
392
393 let mut reader = fuchsia_archive::Reader::new(stream).map_err(Error::ArchiveReader)?;
394 let mut meta_files = {
395 let reader_list = reader.list();
396 PackedMap::<str, MetaFileLocation>::with_capacity(
397 reader_list.len(),
398 reader_list.map(|entry| entry.path().len()).sum(),
399 )
400 };
401 let reader_list = reader.list();
402 for entry in reader_list {
405 let path = std::str::from_utf8(entry.path())
406 .map_err(|source| Error::NonUtf8MetaEntry { source, path: entry.path().to_owned() })?;
407 if path.starts_with("meta/") {
408 for (i, _) in path.match_indices('/').skip(1) {
409 if meta_files.contains_key(&path[..i]) {
410 return Err(Error::FileDirectoryCollision { path: path[..i].to_string() });
411 }
412 }
413 meta_files
414 .append_or_update(
415 path,
416 MetaFileLocation { offset: entry.offset(), length: entry.length() },
417 )
418 .expect("FAR entries are sorted");
419 }
420 }
421
422 let meta_contents_bytes =
423 reader.read_file(b"meta/contents").map_err(Error::ReadMetaContents)?;
424
425 let mut non_meta_files = MetaContents::deserialize(&meta_contents_bytes[..])
426 .map_err(Error::DeserializeMetaContents)?
427 .into_contents();
428
429 non_meta_files.shrink_to_fit();
432
433 Ok((meta_files, non_meta_files))
434}
435
436#[derive(Clone, Copy, Debug, PartialEq, Eq)]
438pub(crate) struct MetaFileLocation {
439 offset: u64,
440 length: u64,
441}
442
443#[cfg(test)]
444mod tests {
445 use super::*;
446 use assert_matches::assert_matches;
447 use fidl::endpoints::create_proxy;
448 use fuchsia_fs::directory::{DirEntry, DirentKind};
449 use fuchsia_pkg_testing::PackageBuilder;
450 use fuchsia_pkg_testing::blobfs::Fake as FakeBlobfs;
451 use futures::TryStreamExt as _;
452 use pretty_assertions::assert_eq;
453 use std::io::Cursor;
454
455 struct TestEnv {
456 _blobfs_fake: FakeBlobfs,
457 root_dir: Arc<RootDir<blobfs::Client>>,
458 }
459
460 impl TestEnv {
461 async fn with_subpackages(
462 subpackages_content: Option<&[u8]>,
463 ) -> (Self, Arc<RootDir<blobfs::Client>>) {
464 let mut pkg = PackageBuilder::new("base-package-0")
465 .add_resource_at("resource", "blob-contents".as_bytes())
466 .add_resource_at("dir/file", "bloblob".as_bytes())
467 .add_resource_at("meta/file", "meta-contents0".as_bytes())
468 .add_resource_at("meta/dir/file", "meta-contents1".as_bytes());
469 if let Some(subpackages_content) = subpackages_content {
470 pkg = pkg.add_resource_at(fuchsia_pkg::MetaSubpackages::PATH, subpackages_content);
471 }
472 let pkg = pkg.build().await.unwrap();
473 let (metafar_blob, content_blobs) = pkg.contents();
474 let (blobfs_fake, blobfs_client) = FakeBlobfs::new();
475 blobfs_fake.add_blob(metafar_blob.merkle, metafar_blob.contents);
476 for (hash, bytes) in content_blobs {
477 blobfs_fake.add_blob(hash, bytes);
478 }
479
480 let root_dir = RootDir::new(blobfs_client, metafar_blob.merkle).await.unwrap();
481 (Self { _blobfs_fake: blobfs_fake, root_dir: root_dir.clone() }, root_dir)
482 }
483
484 async fn new() -> (Self, fio::DirectoryProxy) {
485 let (env, root) = Self::with_subpackages(None).await;
486 (env, vfs::directory::serve_read_only(root, ExecutionScope::new()))
487 }
488 }
489
490 #[fuchsia::test]
491 async fn new_missing_meta_far_error() {
492 let (_blobfs_fake, blobfs_client) = FakeBlobfs::new();
493 assert_matches!(
494 RootDir::new(blobfs_client, [0; 32].into()).await,
495 Err(Error::MissingMetaFar)
496 );
497 }
498
499 #[fuchsia::test]
500 async fn new_rejects_invalid_utf8() {
501 let (blobfs_fake, blobfs_client) = FakeBlobfs::new();
502 let mut meta_far = vec![];
503 let () = fuchsia_archive::write(
504 &mut meta_far,
505 std::collections::BTreeMap::from_iter([(
506 b"\xff",
507 (0, Box::new("".as_bytes()) as Box<dyn std::io::Read>),
508 )]),
509 )
510 .unwrap();
511 let hash = fuchsia_merkle::root_from_slice(&meta_far);
512 let () = blobfs_fake.add_blob(hash, meta_far);
513
514 assert_matches!(
515 RootDir::new(blobfs_client, hash).await,
516 Err(Error::NonUtf8MetaEntry{path, ..})
517 if path == vec![255]
518 );
519 }
520
521 #[fuchsia::test]
522 async fn new_initializes_maps() {
523 let (_env, root_dir) = TestEnv::with_subpackages(None).await;
524
525 let meta_files = PackedMap::from([
526 ("meta/contents", MetaFileLocation { offset: 4096, length: 148 }),
527 ("meta/package", MetaFileLocation { offset: 20480, length: 39 }),
528 ("meta/file", MetaFileLocation { offset: 12288, length: 14 }),
529 ("meta/dir/file", MetaFileLocation { offset: 8192, length: 14 }),
530 ("meta/fuchsia.abi/abi-revision", MetaFileLocation { offset: 16384, length: 8 }),
531 ]);
532 assert_eq!(root_dir.meta_files, meta_files);
533
534 let non_meta_files = PackedMap::from([
535 (
536 "resource",
537 "bd905f783ceae4c5ba8319703d7505ab363733c2db04c52c8405603a02922b15"
538 .parse::<fuchsia_hash::Hash>()
539 .unwrap(),
540 ),
541 (
542 "dir/file",
543 "5f615dd575994fcbcc174974311d59de258d93cd523d5cb51f0e139b53c33201"
544 .parse::<fuchsia_hash::Hash>()
545 .unwrap(),
546 ),
547 ]);
548 assert_eq!(root_dir.non_meta_files, non_meta_files);
549 }
550
551 #[fuchsia::test]
552 async fn rejects_meta_file_collisions() {
553 let pkg = PackageBuilder::new("base-package-0")
554 .add_resource_at("meta/dir/file", "meta-contents0".as_bytes())
555 .build()
556 .await
557 .unwrap();
558
559 let (metafar_blob, _) = pkg.contents();
561 let mut metafar =
562 fuchsia_archive::Reader::new(Cursor::new(&metafar_blob.contents)).unwrap();
563 let mut entries = std::collections::BTreeMap::new();
564 let farentries =
565 metafar.list().map(|entry| (entry.path().to_vec(), entry.length())).collect::<Vec<_>>();
566 for (path, length) in farentries {
567 let contents = metafar.read_file(&path).unwrap();
568 entries
569 .insert(path, (length, Box::new(Cursor::new(contents)) as Box<dyn std::io::Read>));
570 }
571 let extra_contents = b"meta-contents1";
572 entries.insert(
573 b"meta/dir".to_vec(),
574 (
575 extra_contents.len() as u64,
576 Box::new(Cursor::new(extra_contents)) as Box<dyn std::io::Read>,
577 ),
578 );
579
580 let mut metafar: Vec<u8> = vec![];
581 let () = fuchsia_archive::write(&mut metafar, entries).unwrap();
582 let merkle = fuchsia_merkle::root_from_slice(&metafar);
583
584 let (blobfs_fake, blobfs_client) = FakeBlobfs::new();
586 blobfs_fake.add_blob(merkle, &metafar);
587
588 match RootDir::new(blobfs_client, merkle).await {
589 Ok(_) => panic!("this should not be reached!"),
590 Err(Error::FileDirectoryCollision { path }) => {
591 assert_eq!(path, "meta/dir".to_string());
592 }
593 Err(e) => panic!("Expected collision error, receieved {e:?}"),
594 };
595 }
596
597 #[fuchsia::test]
598 async fn read_file() {
599 let (_env, root_dir) = TestEnv::with_subpackages(None).await;
600
601 assert_eq!(root_dir.read_file("resource").await.unwrap().as_slice(), b"blob-contents");
602 assert_eq!(root_dir.read_file("meta/file").await.unwrap().as_slice(), b"meta-contents0");
603 assert_matches!(
604 root_dir.read_file("missing").await.unwrap_err(),
605 ReadFileError::NoFileAtPath{path} if path == "missing"
606 );
607 }
608
609 #[fuchsia::test]
610 async fn get_file_vmo() {
611 let (_env, root_dir) = TestEnv::with_subpackages(None).await;
612
613 let (vmo, size) = root_dir.get_file_vmo("resource").await.unwrap();
614 assert_eq!(size, 13);
615 assert_eq!(vmo.read_to_vec::<u8>(0, size).unwrap().as_slice(), b"blob-contents");
616
617 let (vmo, size) = root_dir.get_file_vmo("meta/file").await.unwrap();
618 assert_eq!(size, 14);
619 assert_eq!(vmo.read_to_vec::<u8>(0, size).unwrap().as_slice(), b"meta-contents0");
620
621 assert_matches!(
622 root_dir.get_file_vmo("missing").await.unwrap_err(),
623 ReadFileError::NoFileAtPath{path} if path == "missing"
624 );
625 }
626
627 #[fuchsia::test]
628 async fn has_file() {
629 let (_env, root_dir) = TestEnv::with_subpackages(None).await;
630
631 assert!(root_dir.has_file("resource"));
632 assert!(root_dir.has_file("meta/file"));
633 assert_eq!(root_dir.has_file("missing"), false);
634 }
635
636 #[fuchsia::test]
637 async fn external_file_hashes() {
638 let (_env, root_dir) = TestEnv::with_subpackages(None).await;
639
640 let mut actual = root_dir.external_file_hashes().copied().collect::<Vec<_>>();
641 actual.sort();
642 assert_eq!(
643 actual,
644 vec![
645 "5f615dd575994fcbcc174974311d59de258d93cd523d5cb51f0e139b53c33201".parse().unwrap(),
646 "bd905f783ceae4c5ba8319703d7505ab363733c2db04c52c8405603a02922b15".parse().unwrap()
647 ]
648 );
649 }
650
651 #[fuchsia::test]
652 async fn path() {
653 let (_env, root_dir) = TestEnv::with_subpackages(None).await;
654
655 assert_eq!(
656 root_dir.path().await.unwrap(),
657 "base-package-0/0".parse::<fuchsia_pkg::PackagePath>().unwrap()
658 );
659 }
660
661 #[fuchsia::test]
662 async fn subpackages_present() {
663 let subpackages = fuchsia_pkg::MetaSubpackages::from_iter([(
664 fuchsia_url::RelativePackageUrl::parse("subpackage-name").unwrap(),
665 "0000000000000000000000000000000000000000000000000000000000000000".parse().unwrap(),
666 )]);
667 let mut subpackages_bytes = vec![];
668 let () = subpackages.serialize(&mut subpackages_bytes).unwrap();
669 let (_env, root_dir) = TestEnv::with_subpackages(Some(&*subpackages_bytes)).await;
670
671 assert_eq!(root_dir.subpackages().await.unwrap(), subpackages);
672 }
673
674 #[fuchsia::test]
675 async fn subpackages_absent() {
676 let (_env, root_dir) = TestEnv::with_subpackages(None).await;
677
678 assert_eq!(root_dir.subpackages().await.unwrap(), fuchsia_pkg::MetaSubpackages::default());
679 }
680
681 #[fuchsia::test]
682 async fn subpackages_error() {
683 let (_env, root_dir) = TestEnv::with_subpackages(Some(b"invalid-json")).await;
684
685 assert_matches!(root_dir.subpackages().await, Err(SubpackagesError::Parse(_)));
686 }
687
688 #[fuchsia::test]
692 async fn root_dir_cannot_be_served_as_mutable() {
693 let (_env, root_dir) = TestEnv::with_subpackages(None).await;
694 let proxy = vfs::directory::serve(root_dir, ExecutionScope::new(), fio::PERM_WRITABLE);
695 assert_matches!(
696 proxy.take_event_stream().try_next().await,
697 Err(fidl::Error::ClientChannelClosed { epitaph, .. })
698 if epitaph == zx::Status::NOT_SUPPORTED
699 );
700 }
701
702 #[fuchsia::test]
703 async fn root_dir_readdir() {
704 let (_env, root_dir) = TestEnv::new().await;
705 assert_eq!(
706 fuchsia_fs::directory::readdir_inclusive(&root_dir).await.unwrap(),
707 vec![
708 DirEntry { name: ".".to_string(), kind: DirentKind::Directory },
709 DirEntry { name: "dir".to_string(), kind: DirentKind::Directory },
710 DirEntry { name: "meta".to_string(), kind: DirentKind::Directory },
711 DirEntry { name: "resource".to_string(), kind: DirentKind::File }
712 ]
713 );
714 }
715
716 #[fuchsia::test]
717 async fn root_dir_get_attributes() {
718 let (_env, root_dir) = TestEnv::new().await;
719 let (mutable_attributes, immutable_attributes) =
720 root_dir.get_attributes(fio::NodeAttributesQuery::all()).await.unwrap().unwrap();
721 assert_eq!(
722 fio::NodeAttributes2 { mutable_attributes, immutable_attributes },
723 immutable_attributes!(
724 fio::NodeAttributesQuery::all(),
725 Immutable {
726 protocols: fio::NodeProtocolKinds::DIRECTORY,
727 abilities: crate::DIRECTORY_ABILITIES,
728 id: 1,
729 }
730 )
731 );
732 }
733
734 #[fuchsia::test]
735 async fn root_dir_watch_not_supported() {
736 let (_env, root_dir) = TestEnv::new().await;
737 let (_client, server) = fidl::endpoints::create_endpoints();
738 assert_eq!(
739 zx::Status::ok(root_dir.watch(fio::WatchMask::empty(), 0, server).await.unwrap()),
740 Err(zx::Status::NOT_SUPPORTED)
741 );
742 }
743
744 #[fuchsia::test]
745 async fn root_dir_open_non_meta_file() {
746 let (_env, root_dir) = TestEnv::new().await;
747 let proxy = fuchsia_fs::directory::open_file(&root_dir, "resource", fio::PERM_READABLE)
748 .await
749 .unwrap();
750 assert_eq!(fuchsia_fs::file::read(&proxy).await.unwrap(), b"blob-contents".to_vec());
751 }
752
753 #[fuchsia::test]
754 async fn root_dir_open_non_meta_file_get_attributes() {
755 let (env, root_dir) = TestEnv::new().await;
756 let proxy = fuchsia_fs::directory::open_file(&root_dir, "resource", fio::PERM_READABLE)
757 .await
758 .unwrap();
759 let expected_hash = env.root_dir.non_meta_files.get("resource").unwrap();
760 let attrs =
761 proxy.get_attributes(fio::NodeAttributesQuery::ROOT_HASH).await.unwrap().unwrap();
762 assert_eq!(attrs.1.root_hash, Some(expected_hash.as_bytes().to_vec()));
763 }
764
765 #[fuchsia::test]
766 async fn root_dir_open_meta_as_file() {
767 let (env, root_dir) = TestEnv::new().await;
768 let proxy =
769 fuchsia_fs::directory::open_file(&root_dir, "meta", fio::PERM_READABLE).await.unwrap();
770 assert_eq!(
771 fuchsia_fs::file::read(&proxy).await.unwrap(),
772 env.root_dir.hash.to_string().as_bytes()
773 );
774 let (cloned_proxy, server_end) = create_proxy::<fio::FileMarker>();
776 proxy.clone(server_end.into_channel().into()).unwrap();
777 assert_eq!(
778 fuchsia_fs::file::read(&cloned_proxy).await.unwrap(),
779 env.root_dir.hash.to_string().as_bytes()
780 );
781 }
782
783 #[fuchsia::test]
784 async fn root_dir_open_meta_as_dir() {
785 let (_env, root_dir) = TestEnv::new().await;
786 for path in ["meta", "meta/"] {
787 let proxy = fuchsia_fs::directory::open_directory(&root_dir, path, fio::PERM_READABLE)
788 .await
789 .unwrap();
790 assert_eq!(
791 fuchsia_fs::directory::readdir(&proxy).await.unwrap(),
792 vec![
793 DirEntry { name: "contents".to_string(), kind: DirentKind::File },
794 DirEntry { name: "dir".to_string(), kind: DirentKind::Directory },
795 DirEntry { name: "file".to_string(), kind: DirentKind::File },
796 DirEntry { name: "fuchsia.abi".to_string(), kind: DirentKind::Directory },
797 DirEntry { name: "package".to_string(), kind: DirentKind::File },
798 ]
799 );
800 let (cloned_proxy, server_end) = create_proxy::<fio::DirectoryMarker>();
802 proxy.clone(server_end.into_channel().into()).unwrap();
803 assert_eq!(
804 fuchsia_fs::directory::readdir(&cloned_proxy).await.unwrap(),
805 vec![
806 DirEntry { name: "contents".to_string(), kind: DirentKind::File },
807 DirEntry { name: "dir".to_string(), kind: DirentKind::Directory },
808 DirEntry { name: "file".to_string(), kind: DirentKind::File },
809 DirEntry { name: "fuchsia.abi".to_string(), kind: DirentKind::Directory },
810 DirEntry { name: "package".to_string(), kind: DirentKind::File },
811 ]
812 );
813 }
814 }
815
816 #[fuchsia::test]
817 async fn root_dir_open_meta_as_node() {
818 let (_env, root_dir) = TestEnv::new().await;
819 for path in ["meta", "meta/"] {
820 let proxy = fuchsia_fs::directory::open_node(
821 &root_dir,
822 path,
823 fio::Flags::PROTOCOL_NODE
824 | fio::Flags::PROTOCOL_DIRECTORY
825 | fio::Flags::PERM_GET_ATTRIBUTES,
826 )
827 .await
828 .unwrap();
829 let (mutable_attributes, immutable_attributes) = proxy
830 .get_attributes(
831 fio::NodeAttributesQuery::PROTOCOLS | fio::NodeAttributesQuery::ABILITIES,
832 )
833 .await
834 .unwrap()
835 .unwrap();
836 assert_eq!(
837 fio::NodeAttributes2 { mutable_attributes, immutable_attributes },
838 immutable_attributes!(
839 fio::NodeAttributesQuery::PROTOCOLS | fio::NodeAttributesQuery::ABILITIES,
840 Immutable {
841 protocols: fio::NodeProtocolKinds::DIRECTORY,
842 abilities: crate::DIRECTORY_ABILITIES
843 }
844 )
845 );
846 }
847 let proxy = fuchsia_fs::directory::open_node(
849 &root_dir,
850 "meta",
851 fio::Flags::PROTOCOL_NODE | fio::Flags::PERM_GET_ATTRIBUTES,
852 )
853 .await
854 .unwrap();
855 let (mutable_attributes, immutable_attributes) = proxy
856 .get_attributes(
857 fio::NodeAttributesQuery::PROTOCOLS | fio::NodeAttributesQuery::ABILITIES,
858 )
859 .await
860 .unwrap()
861 .unwrap();
862 assert_eq!(
863 fio::NodeAttributes2 { mutable_attributes, immutable_attributes },
864 immutable_attributes!(
865 fio::NodeAttributesQuery::PROTOCOLS | fio::NodeAttributesQuery::ABILITIES,
866 Immutable {
867 protocols: fio::NodeProtocolKinds::FILE,
868 abilities: fio::Abilities::READ_BYTES | fio::Abilities::GET_ATTRIBUTES,
869 }
870 )
871 );
872 }
873
874 #[fuchsia::test]
875 async fn root_dir_open_meta_file() {
876 let (_env, root_dir) = TestEnv::new().await;
877 let proxy = fuchsia_fs::directory::open_file(&root_dir, "meta/file", fio::PERM_READABLE)
878 .await
879 .unwrap();
880 assert_eq!(fuchsia_fs::file::read(&proxy).await.unwrap(), b"meta-contents0".to_vec());
881 }
882
883 #[fuchsia::test]
884 async fn root_dir_open_meta_subdir() {
885 let (_env, root_dir) = TestEnv::new().await;
886 for path in ["meta/dir", "meta/dir/"] {
887 let proxy = fuchsia_fs::directory::open_directory(&root_dir, path, fio::PERM_READABLE)
888 .await
889 .unwrap();
890 assert_eq!(
891 fuchsia_fs::directory::readdir(&proxy).await.unwrap(),
892 vec![DirEntry { name: "file".to_string(), kind: DirentKind::File }]
893 );
894 }
895 }
896
897 #[fuchsia::test]
898 async fn root_dir_open_non_meta_subdir() {
899 let (_env, root_dir) = TestEnv::new().await;
900 for path in ["dir", "dir/"] {
901 let proxy = fuchsia_fs::directory::open_directory(&root_dir, path, fio::PERM_READABLE)
902 .await
903 .unwrap();
904 assert_eq!(
905 fuchsia_fs::directory::readdir(&proxy).await.unwrap(),
906 vec![DirEntry { name: "file".to_string(), kind: DirentKind::File }]
907 );
908 }
909 }
910}