pseudo_fs/
lazy_pseudo_directory.rs1use crate::PseudoDirectory;
6use fidl_fuchsia_io as fio;
7use fuchsia_sync::{MappedMutexGuard, Mutex, MutexGuard};
8use futures::lock::Mutex as AsyncMutex;
9use std::sync::Arc;
10use vfs::directory::entry::{
11 DirectoryEntry, DirectoryEntryAsync, EntryInfo, GetEntryInfo, OpenRequest,
12};
13use zx_status::Status;
14
15pub trait ToPseudoDirectory: Send + 'static {
16 fn to_pseudo_directory(self) -> Arc<PseudoDirectory>;
20}
21
22pub trait ToPseudoDirectoryAsync: Send + 'static {
23 fn to_pseudo_directory(self) -> impl Future<Output = Arc<PseudoDirectory>> + Send;
27}
28
29pub struct LazyPseudoDirectory<T>(Mutex<Inner<T>>);
35
36impl<T: ToPseudoDirectory> LazyPseudoDirectory<T> {
37 pub fn new(data: T) -> Arc<Self> {
38 Arc::new(Self(Mutex::new(Inner::Data(data))))
39 }
40
41 pub fn state(&self) -> LazyPseudoDirectoryState<'_, T> {
44 let inner = self.0.lock();
45 match &*inner {
46 Inner::Data(_) => LazyPseudoDirectoryState::Data(MutexGuard::map(inner, |inner| {
47 let Inner::Data(data) = inner else { unreachable!() };
48 data
49 })),
50 Inner::Directory(dir) => LazyPseudoDirectoryState::Directory(dir.clone()),
51 Inner::Intermediate => unreachable!(),
52 }
53 }
54}
55
56pub enum LazyPseudoDirectoryState<'a, T> {
57 Data(MappedMutexGuard<'a, T>),
58 Directory(Arc<PseudoDirectory>),
59}
60
61impl<T> LazyPseudoDirectoryState<'_, T> {
62 pub fn is_data(&self) -> bool {
63 match self {
64 Self::Data(_) => true,
65 _ => false,
66 }
67 }
68
69 pub fn is_directory(&self) -> bool {
70 match self {
71 Self::Directory(_) => true,
72 _ => false,
73 }
74 }
75}
76
77enum Inner<T> {
78 Data(T),
79 Directory(Arc<PseudoDirectory>),
80
81 Intermediate,
84}
85
86impl<T: ToPseudoDirectory> Inner<T> {
87 fn get_or_init_directory(&mut self) -> Arc<PseudoDirectory> {
88 if let Self::Directory(dir) = self {
89 return dir.clone();
90 }
91
92 let Self::Data(data) = std::mem::replace(self, Self::Intermediate) else {
93 unreachable!();
94 };
95 let dir = data.to_pseudo_directory();
96 *self = Self::Directory(dir.clone());
97
98 debug_assert!(
101 dir.entry_info().inode() == fio::INO_UNKNOWN,
102 "The directory must not have an inode number"
103 );
104 dir
105 }
106}
107
108impl<T> GetEntryInfo for LazyPseudoDirectory<T> {
109 fn entry_info(&self) -> EntryInfo {
110 EntryInfo::new(fio::INO_UNKNOWN, fio::DirentType::Directory)
111 }
112}
113
114impl<T: ToPseudoDirectory> DirectoryEntry for LazyPseudoDirectory<T> {
115 fn open_entry(self: Arc<Self>, request: OpenRequest<'_>) -> Result<(), Status> {
116 let mut this = self.0.lock();
117 if let Inner::Directory(dir) = &*this {
118 return dir.clone().open_entry(request);
119 }
120 if request.requires_event() || !request.path().is_empty() {
121 this.get_or_init_directory().open_entry(request)
122 } else {
123 std::mem::drop(this);
124 request.spawn(self);
125 Ok(())
126 }
127 }
128}
129
130impl<T: ToPseudoDirectory> DirectoryEntryAsync for LazyPseudoDirectory<T> {
131 async fn open_entry_async(self: Arc<Self>, request: OpenRequest<'_>) -> Result<(), Status> {
132 if !request.wait_till_ready().await {
133 return Ok(());
135 }
136 let mut this = self.0.lock();
137 this.get_or_init_directory().open_entry(request)
138 }
139}
140
141pub struct LazyPseudoDirectoryAsync<T>(AsyncMutex<InnerAsync<T>>);
152
153enum InnerAsync<T> {
154 Data(Option<T>),
155 Directory(Arc<PseudoDirectory>),
156}
157
158impl<T: ToPseudoDirectoryAsync> LazyPseudoDirectoryAsync<T> {
159 pub fn new(data: T) -> Arc<Self> {
160 Arc::new(Self(AsyncMutex::new(InnerAsync::Data(Some(data)))))
161 }
162
163 pub async fn is_data(&self) -> bool {
164 let inner = self.0.lock().await;
165 matches!(&*inner, InnerAsync::Data(_))
166 }
167
168 pub async fn is_directory(&self) -> bool {
169 let inner = self.0.lock().await;
170 matches!(&*inner, InnerAsync::Directory(_))
171 }
172}
173
174impl<T> GetEntryInfo for LazyPseudoDirectoryAsync<T> {
175 fn entry_info(&self) -> EntryInfo {
176 EntryInfo::new(fio::INO_UNKNOWN, fio::DirentType::Directory)
177 }
178}
179
180impl<T: ToPseudoDirectoryAsync> DirectoryEntry for LazyPseudoDirectoryAsync<T> {
181 fn open_entry(self: Arc<Self>, request: OpenRequest<'_>) -> Result<(), Status> {
182 if let Some(this) = self.0.try_lock() {
183 if let InnerAsync::Directory(dir) = &*this {
184 return dir.clone().open_entry(request);
185 }
186 }
187 request.spawn(self);
188 Ok(())
189 }
190}
191
192impl<T: ToPseudoDirectoryAsync> DirectoryEntryAsync for LazyPseudoDirectoryAsync<T> {
193 async fn open_entry_async(self: Arc<Self>, request: OpenRequest<'_>) -> Result<(), Status> {
194 if !request.wait_till_ready().await {
195 return Ok(());
196 }
197
198 let mut this = self.0.lock().await;
199
200 let dir = match &mut *this {
201 InnerAsync::Directory(dir) => dir.clone(),
202 InnerAsync::Data(opt) => {
203 let data = opt.take().expect("Data already taken");
204 let dir = data.to_pseudo_directory().await;
205 debug_assert!(
206 dir.entry_info().inode() == fio::INO_UNKNOWN,
207 "The directory must not have an inode number"
208 );
209 *this = InnerAsync::Directory(dir.clone());
210 dir
211 }
212 };
213
214 dir.open_entry(request)
215 }
216}
217
218#[cfg(all(test))]
219mod tests {
220 use super::*;
221 use crate::PseudoFile;
222 use fidl::endpoints::create_proxy;
223 use vfs::directory::helper::DirectlyMutable;
224 use vfs::{ExecutionScope, Path, ToObjectRequest};
225
226 #[cfg(target_os = "fuchsia")]
227 use fuchsia_async::TestExecutor;
228
229 struct MockData;
230
231 fn open(
232 dir: Arc<LazyPseudoDirectory<MockData>>,
233 flags: fio::Flags,
234 path: Path,
235 ) -> fio::DirectoryProxy {
236 let (client, server) = create_proxy::<fio::DirectoryMarker>();
237 flags
238 .to_object_request(server)
239 .handle(|object_request| {
240 dir.open_entry(OpenRequest::new(
241 ExecutionScope::new(),
242 flags,
243 path,
244 object_request,
245 ))
246 .unwrap();
247 Ok(())
248 })
249 .unwrap();
250 client
251 }
252
253 #[cfg(target_os = "fuchsia")]
254 fn run_ready_tasks(executor: &mut TestExecutor) {
255 let _ = executor.run_until_stalled(&mut std::future::pending::<()>());
256 }
257
258 impl ToPseudoDirectory for MockData {
259 fn to_pseudo_directory(self) -> Arc<PseudoDirectory> {
260 let inner = PseudoDirectory::new();
261 inner.add_entry("file", PseudoFile::from_data("1234")).unwrap();
262 let dir = PseudoDirectory::new();
263 dir.add_entry("inner", inner).unwrap();
264 dir
265 }
266 }
267
268 #[cfg(target_os = "fuchsia")]
269 #[fuchsia::test]
270 fn test_open_entry_with_no_request_does_not_create_directory() {
271 let mut exec = TestExecutor::new();
272 let lazy_dir = LazyPseudoDirectory::new(MockData);
273 let _client = open(lazy_dir.clone(), fio::PERM_READABLE, Path::dot());
274 run_ready_tasks(&mut exec);
275 assert!(lazy_dir.state().is_data());
276 }
277
278 #[cfg(target_os = "fuchsia")]
279 #[fuchsia::test]
280 fn test_open_entry_with_representation_creates_directory() {
281 let mut exec = TestExecutor::new();
282 let lazy_dir = LazyPseudoDirectory::new(MockData);
283 let _client = open(
284 lazy_dir.clone(),
285 fio::PERM_READABLE | fio::Flags::FLAG_SEND_REPRESENTATION,
286 Path::dot(),
287 );
288 run_ready_tasks(&mut exec);
289 assert!(lazy_dir.state().is_directory());
290 }
291
292 #[cfg(target_os = "fuchsia")]
293 #[fuchsia::test]
294 fn test_open_entry_with_path_creates_directory() {
295 let mut exec = TestExecutor::new();
296 let lazy_dir = LazyPseudoDirectory::new(MockData);
297 let _client = open(lazy_dir.clone(), fio::PERM_READABLE, "inner".try_into().unwrap());
298 run_ready_tasks(&mut exec);
299 assert!(lazy_dir.state().is_directory());
300 }
301
302 #[fuchsia::test]
303 async fn test_create_directory_on_request() {
304 let lazy_dir = LazyPseudoDirectory::new(MockData);
305 let client = open(lazy_dir.clone(), fio::PERM_READABLE, Path::dot());
306 assert!(lazy_dir.state().is_data());
307 client.get_flags().await.unwrap().unwrap();
308 assert!(lazy_dir.state().is_directory());
309 }
310
311 #[cfg(target_os = "fuchsia")]
312 #[fuchsia::test]
313 fn test_peer_closed_does_not_create_directory() {
314 let mut exec = TestExecutor::new();
315 let lazy_dir = LazyPseudoDirectory::new(MockData);
316 let client = open(lazy_dir.clone(), fio::PERM_READABLE, Path::dot());
317 assert!(lazy_dir.state().is_data());
318
319 std::mem::drop(client);
321 run_ready_tasks(&mut exec);
322
323 assert!(lazy_dir.state().is_data());
324 }
325
326 #[fuchsia::test]
327 async fn test_read_inner_file() {
328 let lazy_dir = LazyPseudoDirectory::new(MockData);
329 let client = open(lazy_dir.clone(), fio::PERM_READABLE, Path::dot());
330 assert_eq!(
331 fuchsia_fs::directory::read_file_to_string(&client, "inner/file")
332 .await
333 .expect("failed to read file"),
334 "1234"
335 );
336 }
337 impl ToPseudoDirectoryAsync for MockData {
338 async fn to_pseudo_directory(self) -> Arc<PseudoDirectory> {
339 let inner = PseudoDirectory::new();
340 inner.add_entry("file", PseudoFile::from_data("1234")).unwrap();
341 let dir = PseudoDirectory::new();
342 dir.add_entry("inner", inner).unwrap();
343 dir
344 }
345 }
346
347 fn open_async_dir(
348 dir: Arc<LazyPseudoDirectoryAsync<MockData>>,
349 flags: fio::Flags,
350 path: Path,
351 ) -> fio::DirectoryProxy {
352 let (client, server) = create_proxy::<fio::DirectoryMarker>();
353 flags
354 .to_object_request(server)
355 .handle(|object_request| {
356 dir.open_entry(OpenRequest::new(
357 ExecutionScope::new(),
358 flags,
359 path,
360 object_request,
361 ))
362 .unwrap();
363 Ok(())
364 })
365 .unwrap();
366 client
367 }
368
369 #[fuchsia::test]
370 async fn test_async_create_directory_on_request() {
371 let lazy_dir = LazyPseudoDirectoryAsync::new(MockData);
372 let client = open_async_dir(lazy_dir.clone(), fio::PERM_READABLE, Path::dot());
373 assert!(lazy_dir.is_data().await);
374 client.get_flags().await.unwrap().unwrap();
375 assert!(lazy_dir.is_directory().await);
376 }
377
378 #[fuchsia::test]
379 async fn test_async_read_inner_file() {
380 let lazy_dir = LazyPseudoDirectoryAsync::new(MockData);
381 let client = open_async_dir(lazy_dir.clone(), fio::PERM_READABLE, Path::dot());
382 assert_eq!(
383 fuchsia_fs::directory::read_file_to_string(&client, "inner/file")
384 .await
385 .expect("failed to read file"),
386 "1234"
387 );
388 }
389}