pseudo_fs/
lazy_pseudo_directory.rs1use crate::PseudoDirectory;
6use fidl_fuchsia_io as fio;
7use fuchsia_sync::{MappedMutexGuard, Mutex, MutexGuard};
8use std::sync::Arc;
9use vfs::directory::entry::{
10 DirectoryEntry, DirectoryEntryAsync, EntryInfo, GetEntryInfo, OpenRequest,
11};
12use zx_status::Status;
13
14pub trait ToPseudoDirectory: Send + 'static {
15 fn to_pseudo_directory(self) -> Arc<PseudoDirectory>;
19}
20
21pub struct LazyPseudoDirectory<T>(Mutex<Inner<T>>);
27
28impl<T: ToPseudoDirectory> LazyPseudoDirectory<T> {
29 pub fn new(data: T) -> Arc<Self> {
30 Arc::new(Self(Mutex::new(Inner::Data(data))))
31 }
32
33 pub fn state(&self) -> LazyPseudoDirectoryState<'_, T> {
36 let inner = self.0.lock();
37 match &*inner {
38 Inner::Data(_) => LazyPseudoDirectoryState::Data(MutexGuard::map(inner, |inner| {
39 let Inner::Data(data) = inner else { unreachable!() };
40 data
41 })),
42 Inner::Directory(dir) => LazyPseudoDirectoryState::Directory(dir.clone()),
43 Inner::Intermediate => unreachable!(),
44 }
45 }
46}
47
48pub enum LazyPseudoDirectoryState<'a, T> {
49 Data(MappedMutexGuard<'a, T>),
50 Directory(Arc<PseudoDirectory>),
51}
52
53impl<T> LazyPseudoDirectoryState<'_, T> {
54 pub fn is_data(&self) -> bool {
55 match self {
56 Self::Data(_) => true,
57 _ => false,
58 }
59 }
60
61 pub fn is_directory(&self) -> bool {
62 match self {
63 Self::Directory(_) => true,
64 _ => false,
65 }
66 }
67}
68
69enum Inner<T> {
70 Data(T),
71 Directory(Arc<PseudoDirectory>),
72
73 Intermediate,
76}
77
78impl<T: ToPseudoDirectory> Inner<T> {
79 fn get_or_init_directory(&mut self) -> Arc<PseudoDirectory> {
80 if let Self::Directory(dir) = self {
81 return dir.clone();
82 }
83
84 let Self::Data(data) = std::mem::replace(self, Self::Intermediate) else {
85 unreachable!();
86 };
87 let dir = data.to_pseudo_directory();
88 *self = Self::Directory(dir.clone());
89
90 debug_assert!(
93 dir.entry_info().inode() == fio::INO_UNKNOWN,
94 "The directory must not have an inode number"
95 );
96 dir
97 }
98}
99
100impl<T> GetEntryInfo for LazyPseudoDirectory<T> {
101 fn entry_info(&self) -> EntryInfo {
102 EntryInfo::new(fio::INO_UNKNOWN, fio::DirentType::Directory)
103 }
104}
105
106impl<T: ToPseudoDirectory> DirectoryEntry for LazyPseudoDirectory<T> {
107 fn open_entry(self: Arc<Self>, request: OpenRequest<'_>) -> Result<(), Status> {
108 let mut this = self.0.lock();
109 if let Inner::Directory(dir) = &*this {
110 return dir.clone().open_entry(request);
111 }
112 if request.requires_event() || !request.path().is_empty() {
113 this.get_or_init_directory().open_entry(request)
114 } else {
115 std::mem::drop(this);
116 request.spawn(self);
117 Ok(())
118 }
119 }
120}
121
122impl<T: ToPseudoDirectory> DirectoryEntryAsync for LazyPseudoDirectory<T> {
123 async fn open_entry_async(self: Arc<Self>, request: OpenRequest<'_>) -> Result<(), Status> {
124 if !request.wait_till_ready().await {
125 return Ok(());
127 }
128 let mut this = self.0.lock();
129 this.get_or_init_directory().open_entry(request)
130 }
131}
132
133#[cfg(all(test))]
134mod tests {
135 use super::*;
136 use crate::PseudoFile;
137 use fidl::endpoints::create_proxy;
138 use vfs::directory::helper::DirectlyMutable;
139 use vfs::{ExecutionScope, Path, ToObjectRequest};
140
141 #[cfg(target_os = "fuchsia")]
142 use fuchsia_async::TestExecutor;
143
144 struct MockData;
145
146 fn open(
147 dir: Arc<LazyPseudoDirectory<MockData>>,
148 flags: fio::Flags,
149 path: Path,
150 ) -> fio::DirectoryProxy {
151 let (client, server) = create_proxy::<fio::DirectoryMarker>();
152 flags
153 .to_object_request(server)
154 .handle(|object_request| {
155 dir.open_entry(OpenRequest::new(
156 ExecutionScope::new(),
157 flags,
158 path,
159 object_request,
160 ))
161 .unwrap();
162 Ok(())
163 })
164 .unwrap();
165 client
166 }
167
168 #[cfg(target_os = "fuchsia")]
169 fn run_ready_tasks(executor: &mut TestExecutor) {
170 let _ = executor.run_until_stalled(&mut std::future::pending::<()>());
171 }
172
173 impl ToPseudoDirectory for MockData {
174 fn to_pseudo_directory(self) -> Arc<PseudoDirectory> {
175 let inner = PseudoDirectory::new();
176 inner.add_entry("file", PseudoFile::from_data("1234")).unwrap();
177 let dir = PseudoDirectory::new();
178 dir.add_entry("inner", inner).unwrap();
179 dir
180 }
181 }
182
183 #[cfg(target_os = "fuchsia")]
184 #[fuchsia::test]
185 fn test_open_entry_with_no_request_does_not_create_directory() {
186 let mut exec = TestExecutor::new();
187 let lazy_dir = LazyPseudoDirectory::new(MockData);
188 let _client = open(lazy_dir.clone(), fio::PERM_READABLE, Path::dot());
189 run_ready_tasks(&mut exec);
190 assert!(lazy_dir.state().is_data());
191 }
192
193 #[cfg(target_os = "fuchsia")]
194 #[fuchsia::test]
195 fn test_open_entry_with_representation_creates_directory() {
196 let mut exec = TestExecutor::new();
197 let lazy_dir = LazyPseudoDirectory::new(MockData);
198 let _client = open(
199 lazy_dir.clone(),
200 fio::PERM_READABLE | fio::Flags::FLAG_SEND_REPRESENTATION,
201 Path::dot(),
202 );
203 run_ready_tasks(&mut exec);
204 assert!(lazy_dir.state().is_directory());
205 }
206
207 #[cfg(target_os = "fuchsia")]
208 #[fuchsia::test]
209 fn test_open_entry_with_path_creates_directory() {
210 let mut exec = TestExecutor::new();
211 let lazy_dir = LazyPseudoDirectory::new(MockData);
212 let _client = open(lazy_dir.clone(), fio::PERM_READABLE, "inner".try_into().unwrap());
213 run_ready_tasks(&mut exec);
214 assert!(lazy_dir.state().is_directory());
215 }
216
217 #[fuchsia::test]
218 async fn test_create_directory_on_request() {
219 let lazy_dir = LazyPseudoDirectory::new(MockData);
220 let client = open(lazy_dir.clone(), fio::PERM_READABLE, Path::dot());
221 assert!(lazy_dir.state().is_data());
222 client.get_flags().await.unwrap().unwrap();
223 assert!(lazy_dir.state().is_directory());
224 }
225
226 #[cfg(target_os = "fuchsia")]
227 #[fuchsia::test]
228 fn test_peer_closed_does_not_create_directory() {
229 let mut exec = TestExecutor::new();
230 let lazy_dir = LazyPseudoDirectory::new(MockData);
231 let client = open(lazy_dir.clone(), fio::PERM_READABLE, Path::dot());
232 assert!(lazy_dir.state().is_data());
233
234 std::mem::drop(client);
236 run_ready_tasks(&mut exec);
237
238 assert!(lazy_dir.state().is_data());
239 }
240
241 #[fuchsia::test]
242 async fn test_read_inner_file() {
243 let lazy_dir = LazyPseudoDirectory::new(MockData);
244 let client = open(lazy_dir.clone(), fio::PERM_READABLE, Path::dot());
245 assert_eq!(
246 fuchsia_fs::directory::read_file_to_string(&client, "inner/file")
247 .await
248 .expect("failed to read file"),
249 "1234"
250 );
251 }
252}