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}