1use crate::root_dir::RootDir;
6use fidl_fuchsia_io as fio;
7use std::sync::Arc;
8use vfs::directory::entry::EntryInfo;
9use vfs::directory::immutable::connection::ImmutableConnection;
10use vfs::directory::traversal_position::TraversalPosition;
11use vfs::execution_scope::ExecutionScope;
12use vfs::{ObjectRequestRef, immutable_attributes};
13
14pub(crate) struct NonMetaSubdir<S: crate::NonMetaStorage> {
15 root_dir: Arc<RootDir<S>>,
16 path: String,
19}
20
21impl<S: crate::NonMetaStorage> NonMetaSubdir<S> {
22 pub(crate) fn new(root_dir: Arc<RootDir<S>>, path: String) -> Arc<Self> {
23 Arc::new(NonMetaSubdir { root_dir, path })
24 }
25}
26
27impl<S: crate::NonMetaStorage> vfs::directory::entry::GetEntryInfo for NonMetaSubdir<S> {
28 fn entry_info(&self) -> EntryInfo {
29 EntryInfo::new(fio::INO_UNKNOWN, fio::DirentType::Directory)
30 }
31}
32
33impl<S: crate::NonMetaStorage> vfs::node::Node for NonMetaSubdir<S> {
34 async fn get_attributes(
35 &self,
36 requested_attributes: fio::NodeAttributesQuery,
37 ) -> Result<fio::NodeAttributes2, zx::Status> {
38 Ok(immutable_attributes!(
39 requested_attributes,
40 Immutable {
41 protocols: fio::NodeProtocolKinds::DIRECTORY,
42 abilities: crate::DIRECTORY_ABILITIES,
43 id: 1,
44 }
45 ))
46 }
47}
48
49impl<S: crate::NonMetaStorage> vfs::directory::entry_container::Directory for NonMetaSubdir<S> {
50 fn open(
51 self: Arc<Self>,
52 scope: ExecutionScope,
53 path: vfs::Path,
54 flags: fio::Flags,
55 object_request: ObjectRequestRef<'_>,
56 ) -> Result<(), zx::Status> {
57 if !flags.difference(crate::ALLOWED_FLAGS).is_empty() {
58 return Err(zx::Status::NOT_SUPPORTED);
59 }
60
61 if path.is_empty() {
63 object_request
65 .take()
66 .create_connection_sync::<ImmutableConnection<_>, _>(scope, self, flags);
67 return Ok(());
68 }
69
70 let file_path = format!(
72 "{}{}",
73 self.path,
74 path.as_ref().strip_suffix('/').unwrap_or_else(|| path.as_ref())
75 );
76
77 if let Some(blob) = self.root_dir.non_meta_files.get(&file_path) {
78 if path.is_dir() {
79 return Err(zx::Status::NOT_DIR);
80 }
81 return self.root_dir.non_meta_storage.open(blob, flags, scope, object_request);
82 }
83
84 if let Some(subdir) = self.root_dir.get_non_meta_subdir(file_path + "/") {
85 return subdir.open(scope, vfs::Path::dot(), flags, object_request);
86 }
87
88 Err(zx::Status::NOT_FOUND)
89 }
90
91 async fn read_dirents(
92 &self,
93 pos: &TraversalPosition,
94 sink: Box<dyn vfs::directory::dirents_sink::Sink + 'static>,
95 ) -> Result<
96 (TraversalPosition, Box<dyn vfs::directory::dirents_sink::Sealed + 'static>),
97 zx::Status,
98 > {
99 vfs::directory::read_dirents::read_dirents(
100 &crate::get_dir_children(
101 self.root_dir.non_meta_files.keys().map(|s| s.as_str()),
102 &self.path,
103 ),
104 pos,
105 sink,
106 )
107 }
108
109 fn register_watcher(
110 self: Arc<Self>,
111 _: ExecutionScope,
112 _: fio::WatchMask,
113 _: vfs::directory::entry_container::DirectoryWatcher,
114 ) -> Result<(), zx::Status> {
115 Err(zx::Status::NOT_SUPPORTED)
116 }
117
118 fn unregister_watcher(self: Arc<Self>, _: usize) {}
120}
121
122#[cfg(test)]
123mod tests {
124 use super::*;
125 use assert_matches::assert_matches;
126 use fuchsia_fs::directory::{DirEntry, DirentKind};
127 use fuchsia_pkg_testing::PackageBuilder;
128 use fuchsia_pkg_testing::blobfs::Fake as FakeBlobfs;
129 use futures::prelude::*;
130
131 struct TestEnv {
132 _blobfs_fake: FakeBlobfs,
133 }
134
135 impl TestEnv {
136 async fn new() -> (Self, fio::DirectoryProxy) {
137 let pkg = PackageBuilder::new("pkg")
138 .add_resource_at("dir0/dir1/file", "bloblob".as_bytes())
139 .build()
140 .await
141 .unwrap();
142 let (metafar_blob, content_blobs) = pkg.contents();
143 let (blobfs_fake, blobfs_client) = FakeBlobfs::new();
144 blobfs_fake.add_blob(metafar_blob.merkle, metafar_blob.contents);
145 for (hash, bytes) in content_blobs {
146 blobfs_fake.add_blob(hash, bytes);
147 }
148 let root_dir = RootDir::new(blobfs_client, metafar_blob.merkle).await.unwrap();
149 let sub_dir = NonMetaSubdir::new(root_dir, "dir0/".to_string());
150 (Self { _blobfs_fake: blobfs_fake }, vfs::directory::serve_read_only(sub_dir))
151 }
152 }
153
154 #[fuchsia_async::run_singlethreaded(test)]
158 async fn non_meta_subdir_cannot_be_served_as_mutable() {
159 let pkg = PackageBuilder::new("pkg")
160 .add_resource_at("dir0/dir1/file", "bloblob".as_bytes())
161 .build()
162 .await
163 .unwrap();
164 let (metafar_blob, content_blobs) = pkg.contents();
165 let (blobfs_fake, blobfs_client) = FakeBlobfs::new();
166 blobfs_fake.add_blob(metafar_blob.merkle, metafar_blob.contents);
167 for (hash, bytes) in content_blobs {
168 blobfs_fake.add_blob(hash, bytes);
169 }
170 let root_dir = RootDir::new(blobfs_client, metafar_blob.merkle).await.unwrap();
171 let sub_dir = NonMetaSubdir::new(root_dir, "dir0/".to_string());
172 let proxy = vfs::directory::serve(sub_dir, fio::PERM_WRITABLE);
173 assert_matches!(
174 proxy.take_event_stream().try_next().await,
175 Err(fidl::Error::ClientChannelClosed { status: zx::Status::NOT_SUPPORTED, .. })
176 );
177 }
178
179 #[fuchsia_async::run_singlethreaded(test)]
180 async fn non_meta_subdir_readdir() {
181 let (_env, sub_dir) = TestEnv::new().await;
182 assert_eq!(
183 fuchsia_fs::directory::readdir_inclusive(&sub_dir).await.unwrap(),
184 vec![
185 DirEntry { name: ".".to_string(), kind: DirentKind::Directory },
186 DirEntry { name: "dir1".to_string(), kind: DirentKind::Directory }
187 ]
188 );
189 }
190
191 #[fuchsia_async::run_singlethreaded(test)]
192 async fn non_meta_subdir_get_attributes() {
193 let (_env, sub_dir) = TestEnv::new().await;
194 let (mutable_attributes, immutable_attributes) =
195 sub_dir.get_attributes(fio::NodeAttributesQuery::all()).await.unwrap().unwrap();
196 assert_eq!(
197 fio::NodeAttributes2 { mutable_attributes, immutable_attributes },
198 immutable_attributes!(
199 fio::NodeAttributesQuery::all(),
200 Immutable {
201 protocols: fio::NodeProtocolKinds::DIRECTORY,
202 abilities: crate::DIRECTORY_ABILITIES,
203 id: 1,
204 }
205 )
206 );
207 }
208
209 #[fuchsia_async::run_singlethreaded(test)]
210 async fn non_meta_subdir_watch_not_supported() {
211 let (_env, sub_dir) = TestEnv::new().await;
212 let (_client, server) = fidl::endpoints::create_endpoints();
213 let status =
214 zx::Status::from_raw(sub_dir.watch(fio::WatchMask::empty(), 0, server).await.unwrap());
215 assert_eq!(status, zx::Status::NOT_SUPPORTED);
216 }
217
218 #[fuchsia_async::run_singlethreaded(test)]
219 async fn non_meta_subdir_open_directory() {
220 let (_env, sub_dir) = TestEnv::new().await;
221 for path in ["dir1", "dir1/"] {
222 let proxy = fuchsia_fs::directory::open_directory(&sub_dir, path, fio::PERM_READABLE)
223 .await
224 .unwrap();
225 assert_eq!(
226 fuchsia_fs::directory::readdir(&proxy).await.unwrap(),
227 vec![DirEntry { name: "file".to_string(), kind: DirentKind::File }]
228 );
229 }
230 }
231
232 #[fuchsia_async::run_singlethreaded(test)]
233 async fn non_meta_subdir_open_file() {
234 let (_env, sub_dir) = TestEnv::new().await;
235 let proxy = fuchsia_fs::directory::open_file(&sub_dir, "dir1/file", fio::PERM_READABLE)
236 .await
237 .unwrap();
238 assert_eq!(fuchsia_fs::file::read(&proxy).await.unwrap(), b"bloblob".to_vec())
239 }
240}