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 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<'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
210 struct TestEnv {
211 _blobfs_fake: FakeBlobfs,
212 }
213
214 impl TestEnv {
215 async fn new() -> (Self, fio::DirectoryProxy) {
216 let pkg = PackageBuilder::new("pkg")
217 .add_resource_at("meta/dir/dir/file", &b"contents"[..])
218 .build()
219 .await
220 .unwrap();
221 let (metafar_blob, _) = pkg.contents();
222 let (blobfs_fake, blobfs_client) = FakeBlobfs::new();
223 blobfs_fake.add_blob(metafar_blob.merkle, metafar_blob.contents);
224 let root_dir = RootDir::new(blobfs_client, metafar_blob.merkle).await.unwrap();
225 let sub_dir = MetaSubdir::new(root_dir, "meta/dir/".to_string());
226 (Self { _blobfs_fake: blobfs_fake }, vfs::directory::serve_read_only(sub_dir))
227 }
228 }
229
230 #[fuchsia_async::run_singlethreaded(test)]
235 async fn meta_subdir_cannot_be_served_as_mutable() {
236 let pkg = PackageBuilder::new("pkg")
237 .add_resource_at("meta/dir/dir/file", &b"contents"[..])
238 .build()
239 .await
240 .unwrap();
241 let (metafar_blob, _) = pkg.contents();
242 let (blobfs_fake, blobfs_client) = FakeBlobfs::new();
243 blobfs_fake.add_blob(metafar_blob.merkle, metafar_blob.contents);
244 let root_dir = RootDir::new(blobfs_client, metafar_blob.merkle).await.unwrap();
245 let sub_dir = MetaSubdir::new(root_dir, "meta/dir/".to_string());
246 for flags in [fio::PERM_WRITABLE, fio::PERM_EXECUTABLE] {
247 let proxy = vfs::directory::serve(sub_dir.clone(), flags);
248 assert_matches!(
249 proxy.take_event_stream().try_next().await,
250 Err(fidl::Error::ClientChannelClosed { status: zx::Status::NOT_SUPPORTED, .. })
251 );
252 }
253 }
254
255 #[fuchsia_async::run_singlethreaded(test)]
256 async fn meta_subdir_readdir() {
257 let (_env, sub_dir) = TestEnv::new().await;
258 assert_eq!(
259 fuchsia_fs::directory::readdir_inclusive(&sub_dir).await.unwrap(),
260 vec![
261 DirEntry { name: ".".to_string(), kind: DirentKind::Directory },
262 DirEntry { name: "dir".to_string(), kind: DirentKind::Directory }
263 ]
264 );
265 }
266
267 #[fuchsia_async::run_singlethreaded(test)]
268 async fn meta_subdir_get_attributes() {
269 let (_env, sub_dir) = TestEnv::new().await;
270 let (mutable_attributes, immutable_attributes) =
271 sub_dir.get_attributes(fio::NodeAttributesQuery::all()).await.unwrap().unwrap();
272 assert_eq!(
273 fio::NodeAttributes2 { mutable_attributes, immutable_attributes },
274 immutable_attributes!(
275 fio::NodeAttributesQuery::all(),
276 Immutable {
277 protocols: fio::NodeProtocolKinds::DIRECTORY,
278 abilities: crate::DIRECTORY_ABILITIES,
279 content_size: 4,
280 storage_size: 4,
281 id: 1,
282 }
283 )
284 );
285 }
286
287 #[fuchsia_async::run_singlethreaded(test)]
288 async fn meta_subdir_watch_not_supported() {
289 let (_env, sub_dir) = TestEnv::new().await;
290 let (_client, server) = fidl::endpoints::create_endpoints();
291 let status =
292 zx::Status::from_raw(sub_dir.watch(fio::WatchMask::empty(), 0, server).await.unwrap());
293 assert_eq!(status, zx::Status::NOT_SUPPORTED);
294 }
295
296 #[fuchsia_async::run_singlethreaded(test)]
297 async fn meta_subdir_open_file() {
298 let (_env, sub_dir) = TestEnv::new().await;
299 let proxy = fuchsia_fs::directory::open_file(&sub_dir, "dir/file", fio::PERM_READABLE)
300 .await
301 .unwrap();
302 assert_eq!(fuchsia_fs::file::read(&proxy).await.unwrap(), b"contents".to_vec());
303 }
304
305 #[fuchsia_async::run_singlethreaded(test)]
306 async fn meta_subdir_open_directory() {
307 let (_env, sub_dir) = TestEnv::new().await;
308 for path in ["dir", "dir/"] {
309 let proxy = fuchsia_fs::directory::open_directory(&sub_dir, path, fio::PERM_READABLE)
310 .await
311 .unwrap();
312 assert_eq!(
313 fuchsia_fs::directory::readdir(&proxy).await.unwrap(),
314 vec![fuchsia_fs::directory::DirEntry {
315 name: "file".to_string(),
316 kind: fuchsia_fs::directory::DirentKind::File
317 }]
318 );
319 }
320 }
321
322 #[fuchsia_async::run_singlethreaded(test)]
323 async fn meta_subdir_deprecated_open_file() {
324 let (_env, sub_dir) = TestEnv::new().await;
325 let (proxy, server_end) = fidl::endpoints::create_proxy::<fio::FileMarker>();
326 sub_dir
327 .deprecated_open(
328 fio::OpenFlags::RIGHT_READABLE,
329 Default::default(),
330 "dir/file",
331 server_end.into_channel().into(),
332 )
333 .unwrap();
334 assert_eq!(fuchsia_fs::file::read(&proxy).await.unwrap(), b"contents".to_vec());
335 }
336
337 #[fuchsia_async::run_singlethreaded(test)]
338 async fn meta_subdir_deprecated_open_directory() {
339 let (_env, sub_dir) = TestEnv::new().await;
340 for path in ["dir", "dir/"] {
341 let (proxy, server_end) = fidl::endpoints::create_proxy::<fio::DirectoryMarker>();
342 sub_dir
343 .deprecated_open(
344 fio::OpenFlags::RIGHT_READABLE,
345 Default::default(),
346 path,
347 server_end.into_channel().into(),
348 )
349 .unwrap();
350 assert_eq!(
351 fuchsia_fs::directory::readdir(&proxy).await.unwrap(),
352 vec![fuchsia_fs::directory::DirEntry {
353 name: "file".to_string(),
354 kind: fuchsia_fs::directory::DirentKind::File
355 }]
356 );
357 }
358 }
359}