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::{immutable_attributes, ObjectRequestRef, ToObjectRequest as _};
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 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.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 open3(
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.open3(scope, vfs::Path::dot(), flags, object_request);
164 }
165
166 Err(zx::Status::NOT_FOUND)
167 }
168
169 async fn read_dirents<'a>(
170 &'a self,
171 pos: &'a 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 .await
186 }
187
188 fn register_watcher(
189 self: Arc<Self>,
190 _: ExecutionScope,
191 _: fio::WatchMask,
192 _: vfs::directory::entry_container::DirectoryWatcher,
193 ) -> Result<(), zx::Status> {
194 Err(zx::Status::NOT_SUPPORTED)
195 }
196
197 fn unregister_watcher(self: Arc<Self>, _: usize) {}
199}
200
201#[cfg(test)]
202mod tests {
203 use super::*;
204 use assert_matches::assert_matches;
205 use fuchsia_fs::directory::{DirEntry, DirentKind};
206 use fuchsia_pkg_testing::blobfs::Fake as FakeBlobfs;
207 use fuchsia_pkg_testing::PackageBuilder;
208 use futures::prelude::*;
209 use vfs::directory::entry_container::Directory as _;
210
211 struct TestEnv {
212 _blobfs_fake: FakeBlobfs,
213 }
214
215 impl TestEnv {
216 async fn new() -> (Self, fio::DirectoryProxy) {
217 let pkg = PackageBuilder::new("pkg")
218 .add_resource_at("meta/dir/dir/file", &b"contents"[..])
219 .build()
220 .await
221 .unwrap();
222 let (metafar_blob, _) = pkg.contents();
223 let (blobfs_fake, blobfs_client) = FakeBlobfs::new();
224 blobfs_fake.add_blob(metafar_blob.merkle, metafar_blob.contents);
225 let root_dir = RootDir::new(blobfs_client, metafar_blob.merkle).await.unwrap();
226 let sub_dir = MetaSubdir::new(root_dir, "meta/dir/".to_string());
227 (Self { _blobfs_fake: blobfs_fake }, vfs::directory::serve_read_only(sub_dir))
228 }
229 }
230
231 #[fuchsia_async::run_singlethreaded(test)]
236 async fn meta_subdir_cannot_be_served_as_mutable() {
237 let pkg = PackageBuilder::new("pkg")
238 .add_resource_at("meta/dir/dir/file", &b"contents"[..])
239 .build()
240 .await
241 .unwrap();
242 let (metafar_blob, _) = pkg.contents();
243 let (blobfs_fake, blobfs_client) = FakeBlobfs::new();
244 blobfs_fake.add_blob(metafar_blob.merkle, metafar_blob.contents);
245 let root_dir = RootDir::new(blobfs_client, metafar_blob.merkle).await.unwrap();
246 let sub_dir = MetaSubdir::new(root_dir, "meta/dir/".to_string());
247 for flags in [fio::PERM_WRITABLE, fio::PERM_EXECUTABLE] {
248 let (proxy, server) = fidl::endpoints::create_proxy::<fio::DirectoryMarker>();
249 let request = flags.to_object_request(server);
250 request.handle(|request: &mut vfs::ObjectRequest| {
251 sub_dir.clone().open3(ExecutionScope::new(), vfs::Path::dot(), flags, request)
252 });
253 assert_matches!(
254 proxy.take_event_stream().try_next().await,
255 Err(fidl::Error::ClientChannelClosed { status: zx::Status::NOT_SUPPORTED, .. })
256 );
257 }
258 }
259
260 #[fuchsia_async::run_singlethreaded(test)]
261 async fn meta_subdir_readdir() {
262 let (_env, sub_dir) = TestEnv::new().await;
263 assert_eq!(
264 fuchsia_fs::directory::readdir_inclusive(&sub_dir).await.unwrap(),
265 vec![
266 DirEntry { name: ".".to_string(), kind: DirentKind::Directory },
267 DirEntry { name: "dir".to_string(), kind: DirentKind::Directory }
268 ]
269 );
270 }
271
272 #[fuchsia_async::run_singlethreaded(test)]
273 async fn meta_subdir_get_attributes() {
274 let (_env, sub_dir) = TestEnv::new().await;
275 let (mutable_attributes, immutable_attributes) =
276 sub_dir.get_attributes(fio::NodeAttributesQuery::all()).await.unwrap().unwrap();
277 assert_eq!(
278 fio::NodeAttributes2 { mutable_attributes, immutable_attributes },
279 immutable_attributes!(
280 fio::NodeAttributesQuery::all(),
281 Immutable {
282 protocols: fio::NodeProtocolKinds::DIRECTORY,
283 abilities: crate::DIRECTORY_ABILITIES,
284 content_size: 4,
285 storage_size: 4,
286 id: 1,
287 }
288 )
289 );
290 }
291
292 #[fuchsia_async::run_singlethreaded(test)]
293 async fn meta_subdir_watch_not_supported() {
294 let (_env, sub_dir) = TestEnv::new().await;
295 let (_client, server) = fidl::endpoints::create_endpoints();
296 let status =
297 zx::Status::from_raw(sub_dir.watch(fio::WatchMask::empty(), 0, server).await.unwrap());
298 assert_eq!(status, zx::Status::NOT_SUPPORTED);
299 }
300
301 #[fuchsia_async::run_singlethreaded(test)]
302 async fn meta_subdir_open_file() {
303 let (_env, sub_dir) = TestEnv::new().await;
304 let proxy = fuchsia_fs::directory::open_file(&sub_dir, "dir/file", fio::PERM_READABLE)
305 .await
306 .unwrap();
307 assert_eq!(fuchsia_fs::file::read(&proxy).await.unwrap(), b"contents".to_vec());
308 }
309
310 #[fuchsia_async::run_singlethreaded(test)]
311 async fn meta_subdir_open_directory() {
312 let (_env, sub_dir) = TestEnv::new().await;
313 for path in ["dir", "dir/"] {
314 let proxy = fuchsia_fs::directory::open_directory(&sub_dir, path, fio::PERM_READABLE)
315 .await
316 .unwrap();
317 assert_eq!(
318 fuchsia_fs::directory::readdir(&proxy).await.unwrap(),
319 vec![fuchsia_fs::directory::DirEntry {
320 name: "file".to_string(),
321 kind: fuchsia_fs::directory::DirentKind::File
322 }]
323 );
324 }
325 }
326
327 #[fuchsia_async::run_singlethreaded(test)]
328 async fn meta_subdir_deprecated_open_file() {
329 let (_env, sub_dir) = TestEnv::new().await;
330 let (proxy, server_end) = fidl::endpoints::create_proxy::<fio::FileMarker>();
331 sub_dir
332 .deprecated_open(
333 fio::OpenFlags::RIGHT_READABLE,
334 Default::default(),
335 "dir/file",
336 server_end.into_channel().into(),
337 )
338 .unwrap();
339 assert_eq!(fuchsia_fs::file::read(&proxy).await.unwrap(), b"contents".to_vec());
340 }
341
342 #[fuchsia_async::run_singlethreaded(test)]
343 async fn meta_subdir_deprecated_open_directory() {
344 let (_env, sub_dir) = TestEnv::new().await;
345 for path in ["dir", "dir/"] {
346 let (proxy, server_end) = fidl::endpoints::create_proxy::<fio::DirectoryMarker>();
347 sub_dir
348 .deprecated_open(
349 fio::OpenFlags::RIGHT_READABLE,
350 Default::default(),
351 path,
352 server_end.into_channel().into(),
353 )
354 .unwrap();
355 assert_eq!(
356 fuchsia_fs::directory::readdir(&proxy).await.unwrap(),
357 vec![fuchsia_fs::directory::DirEntry {
358 name: "file".to_string(),
359 kind: fuchsia_fs::directory::DirentKind::File
360 }]
361 );
362 }
363 }
364}