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