1use crate::root_dir::RootDir;
6use crate::usize_to_u64_safe;
7use fidl_fuchsia_io as fio;
8use std::sync::Arc;
9use vfs::directory::entry::EntryInfo;
10use vfs::directory::immutable::connection::ImmutableConnection;
11use vfs::directory::traversal_position::TraversalPosition;
12use vfs::execution_scope::ExecutionScope;
13use vfs::{ObjectRequestRef, immutable_attributes};
14
15pub(crate) struct MetaAsDir<S: crate::NonMetaStorage> {
16 root_dir: Arc<RootDir<S>>,
17}
18
19impl<S: crate::NonMetaStorage> MetaAsDir<S> {
20 pub(crate) fn new(root_dir: Arc<RootDir<S>>) -> Arc<Self> {
21 Arc::new(MetaAsDir { root_dir })
22 }
23}
24
25impl<S: crate::NonMetaStorage> vfs::directory::entry::GetEntryInfo for MetaAsDir<S> {
26 fn entry_info(&self) -> EntryInfo {
27 EntryInfo::new(fio::INO_UNKNOWN, fio::DirentType::Directory)
28 }
29}
30
31impl<S: crate::NonMetaStorage> vfs::node::Node for MetaAsDir<S> {
32 async fn get_attributes(
33 &self,
34 requested_attributes: fio::NodeAttributesQuery,
35 ) -> Result<fio::NodeAttributes2, zx::Status> {
36 Ok(immutable_attributes!(
37 requested_attributes,
38 Immutable {
39 protocols: fio::NodeProtocolKinds::DIRECTORY,
40 abilities: crate::DIRECTORY_ABILITIES,
41 content_size: usize_to_u64_safe(self.root_dir.meta_files.len()),
42 storage_size: usize_to_u64_safe(self.root_dir.meta_files.len()),
43 id: 1,
44 }
45 ))
46 }
47}
48
49impl<S: crate::NonMetaStorage> vfs::directory::entry_container::Directory for MetaAsDir<S> {
50 fn open(
51 self: Arc<Self>,
52 scope: ExecutionScope,
53 path: vfs::Path,
54 flags: fio::Flags,
55 object_request: ObjectRequestRef<'_>,
56 ) -> Result<(), zx::Status> {
57 if !flags.difference(crate::ALLOWED_FLAGS).is_empty() {
58 return Err(zx::Status::NOT_SUPPORTED);
59 }
60 if flags.contains(fio::Flags::PERM_EXECUTE) {
62 return Err(zx::Status::NOT_SUPPORTED);
63 }
64
65 if path.is_empty() {
67 object_request
75 .take()
76 .create_connection_sync::<ImmutableConnection<_>, _>(scope, self, flags);
77 return Ok(());
78 }
79
80 let file_path =
82 format!("meta/{}", path.as_ref().strip_suffix('/').unwrap_or_else(|| path.as_ref()));
83
84 if let Some(file) = self.root_dir.get_meta_file(&file_path)? {
85 if path.is_dir() {
86 return Err(zx::Status::NOT_DIR);
87 }
88 return vfs::file::serve(file, scope, &flags, object_request);
89 }
90
91 if let Some(subdir) = self.root_dir.get_meta_subdir(file_path + "/") {
92 return subdir.open(scope, vfs::Path::dot(), flags, object_request);
93 }
94
95 Err(zx::Status::NOT_FOUND)
96 }
97
98 async fn read_dirents(
99 &self,
100 pos: &TraversalPosition,
101 sink: Box<dyn vfs::directory::dirents_sink::Sink + 'static>,
102 ) -> Result<
103 (TraversalPosition, Box<dyn vfs::directory::dirents_sink::Sealed + 'static>),
104 zx::Status,
105 > {
106 vfs::directory::read_dirents::read_dirents(
107 &crate::get_dir_children(self.root_dir.meta_files.keys().map(|s| s.as_str()), "meta/"),
108 pos,
109 sink,
110 )
111 }
112
113 fn register_watcher(
114 self: Arc<Self>,
115 _: ExecutionScope,
116 _: fio::WatchMask,
117 _: vfs::directory::entry_container::DirectoryWatcher,
118 ) -> Result<(), zx::Status> {
119 Err(zx::Status::NOT_SUPPORTED)
120 }
121
122 fn unregister_watcher(self: Arc<Self>, _: usize) {}
124}
125
126#[cfg(test)]
127mod tests {
128 use super::*;
129 use assert_matches::assert_matches;
130 use fuchsia_fs::directory::{DirEntry, DirentKind};
131 use fuchsia_pkg_testing::PackageBuilder;
132 use fuchsia_pkg_testing::blobfs::Fake as FakeBlobfs;
133 use futures::TryStreamExt as _;
134
135 struct TestEnv {
136 _blobfs_fake: FakeBlobfs,
137 }
138
139 impl TestEnv {
140 async fn new() -> (Self, fio::DirectoryProxy) {
141 let pkg = PackageBuilder::new("pkg")
142 .add_resource_at("meta/dir/file", &b"contents"[..])
143 .build()
144 .await
145 .unwrap();
146 let (metafar_blob, _) = pkg.contents();
147 let (blobfs_fake, blobfs_client) = FakeBlobfs::new();
148 blobfs_fake.add_blob(metafar_blob.merkle, metafar_blob.contents);
149 let root_dir = RootDir::new(blobfs_client, metafar_blob.merkle).await.unwrap();
150 let meta_as_dir = MetaAsDir::new(root_dir);
151 (Self { _blobfs_fake: blobfs_fake }, vfs::directory::serve_read_only(meta_as_dir))
152 }
153 }
154
155 #[fuchsia_async::run_singlethreaded(test)]
160 async fn meta_as_dir_cannot_be_served_as_mutable() {
161 let pkg = PackageBuilder::new("pkg")
162 .add_resource_at("meta/dir/file", &b"contents"[..])
163 .build()
164 .await
165 .unwrap();
166 let (metafar_blob, _) = pkg.contents();
167 let (blobfs_fake, blobfs_client) = FakeBlobfs::new();
168 blobfs_fake.add_blob(metafar_blob.merkle, metafar_blob.contents);
169 let meta_as_dir =
170 MetaAsDir::new(RootDir::new(blobfs_client, metafar_blob.merkle).await.unwrap());
171 for flags in [fio::PERM_WRITABLE, fio::PERM_EXECUTABLE] {
172 let proxy = vfs::directory::serve(meta_as_dir.clone(), flags);
173 assert_matches!(
174 proxy.take_event_stream().try_next().await,
175 Err(fidl::Error::ClientChannelClosed { status: zx::Status::NOT_SUPPORTED, .. })
176 );
177 }
178 }
179
180 #[fuchsia_async::run_singlethreaded(test)]
181 async fn meta_as_dir_readdir() {
182 let (_env, meta_as_dir) = TestEnv::new().await;
183 assert_eq!(
184 fuchsia_fs::directory::readdir_inclusive(&meta_as_dir).await.unwrap(),
185 vec![
186 DirEntry { name: ".".to_string(), kind: DirentKind::Directory },
187 DirEntry { name: "contents".to_string(), kind: DirentKind::File },
188 DirEntry { name: "dir".to_string(), kind: DirentKind::Directory },
189 DirEntry { name: "fuchsia.abi".to_string(), kind: DirentKind::Directory },
190 DirEntry { name: "package".to_string(), kind: DirentKind::File }
191 ]
192 );
193 }
194
195 #[fuchsia_async::run_singlethreaded(test)]
196 async fn meta_as_dir_get_attributes() {
197 let (_env, meta_as_dir) = TestEnv::new().await;
198 let (mutable_attributes, immutable_attributes) =
199 meta_as_dir.get_attributes(fio::NodeAttributesQuery::all()).await.unwrap().unwrap();
200 assert_eq!(
201 fio::NodeAttributes2 { mutable_attributes, immutable_attributes },
202 immutable_attributes!(
203 fio::NodeAttributesQuery::all(),
204 Immutable {
205 protocols: fio::NodeProtocolKinds::DIRECTORY,
206 abilities: crate::DIRECTORY_ABILITIES,
207 content_size: 4,
208 storage_size: 4,
209 id: 1,
210 }
211 )
212 );
213 }
214
215 #[fuchsia_async::run_singlethreaded(test)]
216 async fn meta_as_dir_watch_not_supported() {
217 let (_env, meta_as_dir) = TestEnv::new().await;
218 let (_client, server) = fidl::endpoints::create_endpoints();
219 let status = zx::Status::from_raw(
220 meta_as_dir.watch(fio::WatchMask::empty(), 0, server).await.unwrap(),
221 );
222 assert_eq!(status, zx::Status::NOT_SUPPORTED);
223 }
224
225 #[fuchsia_async::run_singlethreaded(test)]
226 async fn meta_as_dir_open_file() {
227 let (_env, meta_as_dir) = TestEnv::new().await;
228 let proxy = fuchsia_fs::directory::open_file(&meta_as_dir, "dir/file", fio::PERM_READABLE)
229 .await
230 .unwrap();
231 assert_eq!(fuchsia_fs::file::read(&proxy).await.unwrap(), b"contents".to_vec());
232 }
233
234 #[fuchsia_async::run_singlethreaded(test)]
235 async fn meta_as_dir_open_directory() {
236 let (_env, meta_as_dir) = TestEnv::new().await;
237 for path in ["dir", "dir/"] {
238 let proxy =
239 fuchsia_fs::directory::open_directory(&meta_as_dir, path, fio::PERM_READABLE)
240 .await
241 .unwrap();
242 assert_eq!(
243 fuchsia_fs::directory::readdir(&proxy).await.unwrap(),
244 vec![DirEntry { name: "file".to_string(), kind: DirentKind::File }]
245 );
246 }
247 }
248}