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