1use crate::root_dir::RootDir;
6use fidl::endpoints::ServerEnd;
7use fidl_fuchsia_io as fio;
8use std::sync::Arc;
9use vfs::common::send_on_open_with_error;
10use vfs::directory::entry::EntryInfo;
11use vfs::directory::immutable::connection::ImmutableConnection;
12use vfs::directory::traversal_position::TraversalPosition;
13use vfs::execution_scope::ExecutionScope;
14use vfs::{ObjectRequestRef, ToObjectRequest as _, immutable_attributes};
15
16pub(crate) struct MetaSubdir<S: crate::NonMetaStorage> {
17 root_dir: Arc<RootDir<S>>,
18 path: String,
21}
22
23impl<S: crate::NonMetaStorage> MetaSubdir<S> {
24 pub(crate) fn new(root_dir: Arc<RootDir<S>>, path: String) -> Arc<Self> {
25 Arc::new(MetaSubdir { root_dir, path })
26 }
27}
28
29impl<S: crate::NonMetaStorage> vfs::directory::entry::GetEntryInfo for MetaSubdir<S> {
30 fn entry_info(&self) -> EntryInfo {
31 EntryInfo::new(fio::INO_UNKNOWN, fio::DirentType::Directory)
32 }
33}
34
35impl<S: crate::NonMetaStorage> vfs::node::Node for MetaSubdir<S> {
36 async fn get_attributes(
37 &self,
38 requested_attributes: fio::NodeAttributesQuery,
39 ) -> Result<fio::NodeAttributes2, zx::Status> {
40 let size = crate::usize_to_u64_safe(self.root_dir.meta_files.len());
41 Ok(immutable_attributes!(
42 requested_attributes,
43 Immutable {
44 protocols: fio::NodeProtocolKinds::DIRECTORY,
45 abilities: crate::DIRECTORY_ABILITIES,
46 content_size: size,
47 storage_size: size,
48 id: 1,
49 }
50 ))
51 }
52}
53
54impl<S: crate::NonMetaStorage> vfs::directory::entry_container::Directory for MetaSubdir<S> {
55 fn deprecated_open(
56 self: Arc<Self>,
57 scope: ExecutionScope,
58 flags: fio::OpenFlags,
59 path: vfs::Path,
60 server_end: ServerEnd<fio::NodeMarker>,
61 ) {
62 let flags = flags & !(fio::OpenFlags::POSIX_WRITABLE | fio::OpenFlags::POSIX_EXECUTABLE);
63 let describe = flags.contains(fio::OpenFlags::DESCRIBE);
64 if flags.intersects(
68 fio::OpenFlags::RIGHT_WRITABLE
69 | fio::OpenFlags::RIGHT_EXECUTABLE
70 | fio::OpenFlags::TRUNCATE,
71 ) {
72 let () = send_on_open_with_error(describe, server_end, zx::Status::NOT_SUPPORTED);
73 return;
74 }
75 assert!(!flags.intersects(fio::OpenFlags::CREATE | fio::OpenFlags::CREATE_IF_ABSENT));
77
78 if path.is_empty() {
80 flags.to_object_request(server_end).handle(|object_request| {
81 if flags.intersects(fio::OpenFlags::APPEND) {
85 return Err(zx::Status::NOT_SUPPORTED);
86 }
87 object_request
88 .take()
89 .create_connection_sync::<ImmutableConnection<_>, _>(scope, self, flags);
90 Ok(())
91 });
92 return;
93 }
94
95 let file_path = format!(
97 "{}{}",
98 self.path,
99 path.as_ref().strip_suffix('/').unwrap_or_else(|| path.as_ref())
100 );
101
102 match self.root_dir.get_meta_file(&file_path) {
103 Ok(Some(meta_file)) => {
104 flags.to_object_request(server_end).handle(|object_request| {
105 vfs::file::serve(meta_file, scope, &flags, object_request)
106 });
107 return;
108 }
109 Ok(None) => {}
110 Err(status) => {
111 let () = send_on_open_with_error(describe, server_end, status);
112 return;
113 }
114 }
115
116 if let Some(subdir) = self.root_dir.get_meta_subdir(file_path + "/") {
117 let () = subdir.deprecated_open(scope, flags, vfs::Path::dot(), server_end);
118 return;
119 }
120
121 let () = send_on_open_with_error(describe, server_end, zx::Status::NOT_FOUND);
122 }
123
124 fn open(
125 self: Arc<Self>,
126 scope: ExecutionScope,
127 path: vfs::Path,
128 flags: fio::Flags,
129 object_request: ObjectRequestRef<'_>,
130 ) -> Result<(), zx::Status> {
131 if !flags.difference(crate::ALLOWED_FLAGS).is_empty() {
132 return Err(zx::Status::NOT_SUPPORTED);
133 }
134 if flags.contains(fio::Flags::PERM_EXECUTE) {
136 return Err(zx::Status::NOT_SUPPORTED);
137 }
138
139 if path.is_empty() {
141 object_request
143 .take()
144 .create_connection_sync::<ImmutableConnection<_>, _>(scope, self, flags);
145 return Ok(());
146 }
147
148 let file_path = format!(
150 "{}{}",
151 self.path,
152 path.as_ref().strip_suffix('/').unwrap_or_else(|| path.as_ref())
153 );
154
155 if let Some(file) = self.root_dir.get_meta_file(&file_path)? {
156 if path.is_dir() {
157 return Err(zx::Status::NOT_DIR);
158 }
159 return vfs::file::serve(file, scope, &flags, object_request);
160 }
161
162 if let Some(subdir) = self.root_dir.get_meta_subdir(file_path + "/") {
163 return subdir.open(scope, vfs::Path::dot(), flags, object_request);
164 }
165
166 Err(zx::Status::NOT_FOUND)
167 }
168
169 async fn read_dirents(
170 &self,
171 pos: &TraversalPosition,
172 sink: Box<dyn vfs::directory::dirents_sink::Sink + 'static>,
173 ) -> Result<
174 (TraversalPosition, Box<dyn vfs::directory::dirents_sink::Sealed + 'static>),
175 zx::Status,
176 > {
177 vfs::directory::read_dirents::read_dirents(
178 &crate::get_dir_children(
179 self.root_dir.meta_files.keys().map(|s| s.as_str()),
180 &self.path,
181 ),
182 pos,
183 sink,
184 )
185 }
186
187 fn register_watcher(
188 self: Arc<Self>,
189 _: ExecutionScope,
190 _: fio::WatchMask,
191 _: vfs::directory::entry_container::DirectoryWatcher,
192 ) -> Result<(), zx::Status> {
193 Err(zx::Status::NOT_SUPPORTED)
194 }
195
196 fn unregister_watcher(self: Arc<Self>, _: usize) {}
198}
199
200#[cfg(test)]
201mod tests {
202 use super::*;
203 use assert_matches::assert_matches;
204 use fuchsia_fs::directory::{DirEntry, DirentKind};
205 use fuchsia_pkg_testing::PackageBuilder;
206 use fuchsia_pkg_testing::blobfs::Fake as FakeBlobfs;
207 use futures::prelude::*;
208
209 struct TestEnv {
210 _blobfs_fake: FakeBlobfs,
211 }
212
213 impl TestEnv {
214 async fn new() -> (Self, fio::DirectoryProxy) {
215 let pkg = PackageBuilder::new("pkg")
216 .add_resource_at("meta/dir/dir/file", &b"contents"[..])
217 .build()
218 .await
219 .unwrap();
220 let (metafar_blob, _) = pkg.contents();
221 let (blobfs_fake, blobfs_client) = FakeBlobfs::new();
222 blobfs_fake.add_blob(metafar_blob.merkle, metafar_blob.contents);
223 let root_dir = RootDir::new(blobfs_client, metafar_blob.merkle).await.unwrap();
224 let sub_dir = MetaSubdir::new(root_dir, "meta/dir/".to_string());
225 (Self { _blobfs_fake: blobfs_fake }, vfs::directory::serve_read_only(sub_dir))
226 }
227 }
228
229 #[fuchsia_async::run_singlethreaded(test)]
234 async fn meta_subdir_cannot_be_served_as_mutable() {
235 let pkg = PackageBuilder::new("pkg")
236 .add_resource_at("meta/dir/dir/file", &b"contents"[..])
237 .build()
238 .await
239 .unwrap();
240 let (metafar_blob, _) = pkg.contents();
241 let (blobfs_fake, blobfs_client) = FakeBlobfs::new();
242 blobfs_fake.add_blob(metafar_blob.merkle, metafar_blob.contents);
243 let root_dir = RootDir::new(blobfs_client, metafar_blob.merkle).await.unwrap();
244 let sub_dir = MetaSubdir::new(root_dir, "meta/dir/".to_string());
245 for flags in [fio::PERM_WRITABLE, fio::PERM_EXECUTABLE] {
246 let proxy = vfs::directory::serve(sub_dir.clone(), flags);
247 assert_matches!(
248 proxy.take_event_stream().try_next().await,
249 Err(fidl::Error::ClientChannelClosed { status: zx::Status::NOT_SUPPORTED, .. })
250 );
251 }
252 }
253
254 #[fuchsia_async::run_singlethreaded(test)]
255 async fn meta_subdir_readdir() {
256 let (_env, sub_dir) = TestEnv::new().await;
257 assert_eq!(
258 fuchsia_fs::directory::readdir_inclusive(&sub_dir).await.unwrap(),
259 vec![
260 DirEntry { name: ".".to_string(), kind: DirentKind::Directory },
261 DirEntry { name: "dir".to_string(), kind: DirentKind::Directory }
262 ]
263 );
264 }
265
266 #[fuchsia_async::run_singlethreaded(test)]
267 async fn meta_subdir_get_attributes() {
268 let (_env, sub_dir) = TestEnv::new().await;
269 let (mutable_attributes, immutable_attributes) =
270 sub_dir.get_attributes(fio::NodeAttributesQuery::all()).await.unwrap().unwrap();
271 assert_eq!(
272 fio::NodeAttributes2 { mutable_attributes, immutable_attributes },
273 immutable_attributes!(
274 fio::NodeAttributesQuery::all(),
275 Immutable {
276 protocols: fio::NodeProtocolKinds::DIRECTORY,
277 abilities: crate::DIRECTORY_ABILITIES,
278 content_size: 4,
279 storage_size: 4,
280 id: 1,
281 }
282 )
283 );
284 }
285
286 #[fuchsia_async::run_singlethreaded(test)]
287 async fn meta_subdir_watch_not_supported() {
288 let (_env, sub_dir) = TestEnv::new().await;
289 let (_client, server) = fidl::endpoints::create_endpoints();
290 let status =
291 zx::Status::from_raw(sub_dir.watch(fio::WatchMask::empty(), 0, server).await.unwrap());
292 assert_eq!(status, zx::Status::NOT_SUPPORTED);
293 }
294
295 #[fuchsia_async::run_singlethreaded(test)]
296 async fn meta_subdir_open_file() {
297 let (_env, sub_dir) = TestEnv::new().await;
298 let proxy = fuchsia_fs::directory::open_file(&sub_dir, "dir/file", fio::PERM_READABLE)
299 .await
300 .unwrap();
301 assert_eq!(fuchsia_fs::file::read(&proxy).await.unwrap(), b"contents".to_vec());
302 }
303
304 #[fuchsia_async::run_singlethreaded(test)]
305 async fn meta_subdir_open_directory() {
306 let (_env, sub_dir) = TestEnv::new().await;
307 for path in ["dir", "dir/"] {
308 let proxy = fuchsia_fs::directory::open_directory(&sub_dir, path, fio::PERM_READABLE)
309 .await
310 .unwrap();
311 assert_eq!(
312 fuchsia_fs::directory::readdir(&proxy).await.unwrap(),
313 vec![fuchsia_fs::directory::DirEntry {
314 name: "file".to_string(),
315 kind: fuchsia_fs::directory::DirentKind::File
316 }]
317 );
318 }
319 }
320
321 #[fuchsia_async::run_singlethreaded(test)]
322 async fn meta_subdir_deprecated_open_file() {
323 let (_env, sub_dir) = TestEnv::new().await;
324 let (proxy, server_end) = fidl::endpoints::create_proxy::<fio::FileMarker>();
325 sub_dir
326 .deprecated_open(
327 fio::OpenFlags::RIGHT_READABLE,
328 Default::default(),
329 "dir/file",
330 server_end.into_channel().into(),
331 )
332 .unwrap();
333 assert_eq!(fuchsia_fs::file::read(&proxy).await.unwrap(), b"contents".to_vec());
334 }
335
336 #[fuchsia_async::run_singlethreaded(test)]
337 async fn meta_subdir_deprecated_open_directory() {
338 let (_env, sub_dir) = TestEnv::new().await;
339 for path in ["dir", "dir/"] {
340 let (proxy, server_end) = fidl::endpoints::create_proxy::<fio::DirectoryMarker>();
341 sub_dir
342 .deprecated_open(
343 fio::OpenFlags::RIGHT_READABLE,
344 Default::default(),
345 path,
346 server_end.into_channel().into(),
347 )
348 .unwrap();
349 assert_eq!(
350 fuchsia_fs::directory::readdir(&proxy).await.unwrap(),
351 vec![fuchsia_fs::directory::DirEntry {
352 name: "file".to_string(),
353 kind: fuchsia_fs::directory::DirentKind::File
354 }]
355 );
356 }
357 }
358}