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::{ObjectRequestRef, ToObjectRequest as _, immutable_attributes};
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 }
175
176 fn register_watcher(
177 self: Arc<Self>,
178 _: ExecutionScope,
179 _: fio::WatchMask,
180 _: vfs::directory::entry_container::DirectoryWatcher,
181 ) -> Result<(), zx::Status> {
182 Err(zx::Status::NOT_SUPPORTED)
183 }
184
185 fn unregister_watcher(self: Arc<Self>, _: usize) {}
187}
188
189#[cfg(test)]
190mod tests {
191 use super::*;
192 use assert_matches::assert_matches;
193 use fuchsia_fs::directory::{DirEntry, DirentKind};
194 use fuchsia_pkg_testing::PackageBuilder;
195 use fuchsia_pkg_testing::blobfs::Fake as FakeBlobfs;
196 use futures::prelude::*;
197
198 struct TestEnv {
199 _blobfs_fake: FakeBlobfs,
200 }
201
202 impl TestEnv {
203 async fn new() -> (Self, fio::DirectoryProxy) {
204 let pkg = PackageBuilder::new("pkg")
205 .add_resource_at("dir0/dir1/file", "bloblob".as_bytes())
206 .build()
207 .await
208 .unwrap();
209 let (metafar_blob, content_blobs) = pkg.contents();
210 let (blobfs_fake, blobfs_client) = FakeBlobfs::new();
211 blobfs_fake.add_blob(metafar_blob.merkle, metafar_blob.contents);
212 for (hash, bytes) in content_blobs {
213 blobfs_fake.add_blob(hash, bytes);
214 }
215 let root_dir = RootDir::new(blobfs_client, metafar_blob.merkle).await.unwrap();
216 let sub_dir = NonMetaSubdir::new(root_dir, "dir0/".to_string());
217 (Self { _blobfs_fake: blobfs_fake }, vfs::directory::serve_read_only(sub_dir))
218 }
219 }
220
221 #[fuchsia_async::run_singlethreaded(test)]
225 async fn non_meta_subdir_cannot_be_served_as_mutable() {
226 let pkg = PackageBuilder::new("pkg")
227 .add_resource_at("dir0/dir1/file", "bloblob".as_bytes())
228 .build()
229 .await
230 .unwrap();
231 let (metafar_blob, content_blobs) = pkg.contents();
232 let (blobfs_fake, blobfs_client) = FakeBlobfs::new();
233 blobfs_fake.add_blob(metafar_blob.merkle, metafar_blob.contents);
234 for (hash, bytes) in content_blobs {
235 blobfs_fake.add_blob(hash, bytes);
236 }
237 let root_dir = RootDir::new(blobfs_client, metafar_blob.merkle).await.unwrap();
238 let sub_dir = NonMetaSubdir::new(root_dir, "dir0/".to_string());
239 let proxy = vfs::directory::serve(sub_dir, fio::PERM_WRITABLE);
240 assert_matches!(
241 proxy.take_event_stream().try_next().await,
242 Err(fidl::Error::ClientChannelClosed { status: zx::Status::NOT_SUPPORTED, .. })
243 );
244 }
245
246 #[fuchsia_async::run_singlethreaded(test)]
247 async fn non_meta_subdir_readdir() {
248 let (_env, sub_dir) = TestEnv::new().await;
249 assert_eq!(
250 fuchsia_fs::directory::readdir_inclusive(&sub_dir).await.unwrap(),
251 vec![
252 DirEntry { name: ".".to_string(), kind: DirentKind::Directory },
253 DirEntry { name: "dir1".to_string(), kind: DirentKind::Directory }
254 ]
255 );
256 }
257
258 #[fuchsia_async::run_singlethreaded(test)]
259 async fn non_meta_subdir_get_attributes() {
260 let (_env, sub_dir) = TestEnv::new().await;
261 let (mutable_attributes, immutable_attributes) =
262 sub_dir.get_attributes(fio::NodeAttributesQuery::all()).await.unwrap().unwrap();
263 assert_eq!(
264 fio::NodeAttributes2 { mutable_attributes, immutable_attributes },
265 immutable_attributes!(
266 fio::NodeAttributesQuery::all(),
267 Immutable {
268 protocols: fio::NodeProtocolKinds::DIRECTORY,
269 abilities: crate::DIRECTORY_ABILITIES,
270 id: 1,
271 }
272 )
273 );
274 }
275
276 #[fuchsia_async::run_singlethreaded(test)]
277 async fn non_meta_subdir_watch_not_supported() {
278 let (_env, sub_dir) = TestEnv::new().await;
279 let (_client, server) = fidl::endpoints::create_endpoints();
280 let status =
281 zx::Status::from_raw(sub_dir.watch(fio::WatchMask::empty(), 0, server).await.unwrap());
282 assert_eq!(status, zx::Status::NOT_SUPPORTED);
283 }
284
285 #[fuchsia_async::run_singlethreaded(test)]
286 async fn non_meta_subdir_open_directory() {
287 let (_env, sub_dir) = TestEnv::new().await;
288 for path in ["dir1", "dir1/"] {
289 let proxy = fuchsia_fs::directory::open_directory(&sub_dir, path, fio::PERM_READABLE)
290 .await
291 .unwrap();
292 assert_eq!(
293 fuchsia_fs::directory::readdir(&proxy).await.unwrap(),
294 vec![DirEntry { name: "file".to_string(), kind: DirentKind::File }]
295 );
296 }
297 }
298
299 #[fuchsia_async::run_singlethreaded(test)]
300 async fn non_meta_subdir_open_file() {
301 let (_env, sub_dir) = TestEnv::new().await;
302 let proxy = fuchsia_fs::directory::open_file(&sub_dir, "dir1/file", fio::PERM_READABLE)
303 .await
304 .unwrap();
305 assert_eq!(fuchsia_fs::file::read(&proxy).await.unwrap(), b"bloblob".to_vec())
306 }
307
308 #[fuchsia_async::run_singlethreaded(test)]
309 async fn non_meta_subdir_deprecated_open_directory() {
310 let (_env, sub_dir) = TestEnv::new().await;
311 for path in ["dir1", "dir1/"] {
312 let (proxy, server_end) = fidl::endpoints::create_proxy::<fio::DirectoryMarker>();
313 sub_dir
314 .deprecated_open(
315 fio::OpenFlags::RIGHT_READABLE,
316 Default::default(),
317 path,
318 server_end.into_channel().into(),
319 )
320 .unwrap();
321 assert_eq!(
322 fuchsia_fs::directory::readdir(&proxy).await.unwrap(),
323 vec![DirEntry { name: "file".to_string(), kind: DirentKind::File }]
324 );
325 }
326 }
327
328 #[fuchsia_async::run_singlethreaded(test)]
329 async fn non_meta_subdir_deprecated_open_file() {
330 let (_env, sub_dir) = TestEnv::new().await;
331 let (proxy, server_end) = fidl::endpoints::create_proxy::<fio::FileMarker>();
332 sub_dir
333 .deprecated_open(
334 fio::OpenFlags::RIGHT_READABLE,
335 Default::default(),
336 "dir1/file",
337 server_end.into_channel().into(),
338 )
339 .unwrap();
340 assert_eq!(fuchsia_fs::file::read(&proxy).await.unwrap(), b"bloblob".to_vec());
341 }
342}