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