1#![recursion_limit = "512"]
6
7use starnix_core::task::{CurrentTask, Kernel};
8use starnix_core::vfs::{
9 CacheMode, DirectoryEntryType, DirentSink, FileHandle, FileObject, FileOps, FileSystem,
10 FileSystemHandle, FileSystemOps, FsNode, FsNodeHandle, FsNodeOps, FsStr, FsString, MountInfo,
11 SeekTarget, ValueOrSize, WhatToMount, XattrOp, fileops_impl_directory, fileops_impl_noop_sync,
12 fs_node_impl_dir_readonly, unbounded_seek,
13};
14use starnix_sync::{FileOpsCore, LockEqualOrBefore, Locked, Unlocked};
15use starnix_uapi::errors::Errno;
16use starnix_uapi::mount_flags::{FileSystemFlags, MountpointFlags};
17use starnix_uapi::open_flags::OpenFlags;
18use starnix_uapi::{errno, ino_t, off_t, statfs};
19use std::collections::BTreeMap;
20use std::sync::Arc;
21use std::sync::atomic::Ordering;
22
23struct LayeredMountAction {
24 path: FsString,
25 fs: FileSystemHandle,
26}
27
28pub type LayeredFsMounts =
34 Box<dyn FnOnce(&mut Locked<Unlocked>, &CurrentTask) -> Result<(), Errno>>;
35
36pub struct LayeredFsBuilder {
43 fs: FileSystemHandle,
44 subdirs: BTreeMap<FsString, LayeredFsBuilder>,
45}
46
47fn split_path(path: &FsStr) -> Vec<&FsStr> {
48 path.split(|c| *c == b'/').map(<&FsStr>::from).collect()
49}
50
51impl LayeredFsBuilder {
52 pub fn new(root_fs: FileSystemHandle) -> Self {
54 Self { fs: root_fs, subdirs: Default::default() }
55 }
56
57 pub fn add(&mut self, path: &str, fs: FileSystemHandle) {
64 let path = FsStr::new(path);
65 assert_eq!(path[0], b'/');
66 let parts = split_path(&path[1..]);
67 assert!(!parts.is_empty());
68 let final_part = parts.len() - 1;
69
70 let mut parent = self;
71 for i in 0..final_part {
72 parent = parent.subdirs.get_mut(parts[i]).unwrap();
73 }
74
75 parent.subdirs.insert(parts[parts.len() - 1].into(), Self::new(fs));
76 }
77
78 pub fn build<L>(
85 self,
86 locked: &mut Locked<L>,
87 kernel: &Kernel,
88 ) -> (FileSystemHandle, LayeredFsMounts)
89 where
90 L: LockEqualOrBefore<FileOpsCore>,
91 {
92 let (fs, actions) = self.build_internal(locked, kernel, Default::default());
93 let cb = Box::new(move |locked: &mut Locked<Unlocked>, current_task: &CurrentTask| {
94 for action in actions {
95 let mount_point = current_task
96 .lookup_path_from_root(locked, action.path.as_ref())
97 .map_err(|e| {
98 Errno::with_context(
99 e.code,
100 format!("lookup path from root: {}", action.path),
101 )
102 })?;
103 mount_point.mount(WhatToMount::Fs(action.fs), MountpointFlags::empty()).map_err(
104 |e| {
105 Errno::with_context(e.code, format!("mount layered fs at: {}", action.path))
106 },
107 )?;
108 }
109 Ok(())
110 });
111 (fs, cb)
112 }
113
114 fn build_internal<L>(
115 self,
116 locked: &mut Locked<L>,
117 kernel: &Kernel,
118 prefix: &FsStr,
119 ) -> (FileSystemHandle, Vec<LayeredMountAction>)
120 where
121 L: LockEqualOrBefore<FileOpsCore>,
122 {
123 if self.subdirs.is_empty() {
124 return (self.fs, Vec::new());
125 }
126
127 let names =
128 self.subdirs.iter().map(|(name, entry)| (name.clone(), entry.fs.clone())).collect();
129 let fs = LayeredFs::new_fs(locked, kernel, self.fs, names);
130
131 let mut mount_actions = Vec::new();
132 for (subpath, builder) in self.subdirs {
133 let path = FsString::from(format!("{}/{}", prefix, subpath));
134 let (fs, subdir_actions) = builder.build_internal(locked, kernel, path.as_ref());
135 mount_actions.push(LayeredMountAction { path, fs });
136 mount_actions.extend(subdir_actions.into_iter());
137 }
138
139 (fs, mount_actions)
140 }
141}
142
143struct LayeredFs {
146 base_fs: FileSystemHandle,
147 mappings: BTreeMap<FsString, FileSystemHandle>,
148}
149
150impl LayeredFs {
151 fn new_fs<L>(
157 locked: &mut Locked<L>,
158 kernel: &Kernel,
159 base_fs: FileSystemHandle,
160 mappings: BTreeMap<FsString, FileSystemHandle>,
161 ) -> FileSystemHandle
162 where
163 L: LockEqualOrBefore<FileOpsCore>,
164 {
165 let options = base_fs.options.clone();
166 let layered_fs = Arc::new(LayeredFs { base_fs, mappings });
167 let fs = FileSystem::new(
168 locked,
169 kernel,
170 CacheMode::Uncached,
171 LayeredFileSystemOps { fs: layered_fs.clone() },
172 options,
173 )
174 .expect("layeredfs constructed with valid options");
175 let root_ino = fs.allocate_ino();
176 fs.create_root(root_ino, LayeredNodeOps { fs: layered_fs });
177 fs
178 }
179}
180
181struct LayeredFileSystemOps {
182 fs: Arc<LayeredFs>,
183}
184
185impl FileSystemOps for LayeredFileSystemOps {
186 fn statfs(
187 &self,
188 locked: &mut Locked<FileOpsCore>,
189 _fs: &FileSystem,
190 current_task: &CurrentTask,
191 ) -> Result<statfs, Errno> {
192 self.fs.base_fs.statfs(locked, current_task)
193 }
194 fn name(&self) -> &'static FsStr {
195 self.fs.base_fs.name()
196 }
197 fn update_flags(
198 &self,
199 fs: &FileSystem,
200 current_task: &CurrentTask,
201 new_flags: FileSystemFlags,
202 ) -> Result<(), Errno> {
203 self.fs.base_fs.update_flags(current_task, new_flags)?;
204 let flags = self.fs.base_fs.options.flags.load(Ordering::Relaxed);
205 fs.options.flags.store(flags, Ordering::Relaxed);
206 Ok(())
207 }
208}
209
210struct LayeredNodeOps {
211 fs: Arc<LayeredFs>,
212}
213
214impl FsNodeOps for LayeredNodeOps {
215 fs_node_impl_dir_readonly!();
216
217 fn create_file_ops(
218 &self,
219 locked: &mut Locked<FileOpsCore>,
220 _node: &FsNode,
221 current_task: &CurrentTask,
222 flags: OpenFlags,
223 ) -> Result<Box<dyn FileOps>, Errno> {
224 Ok(Box::new(LayeredFileOps {
225 fs: self.fs.clone(),
226 root_file: self.fs.base_fs.root().open_anonymous(locked, current_task, flags)?,
227 }))
228 }
229
230 fn lookup(
231 &self,
232 locked: &mut Locked<FileOpsCore>,
233 _node: &FsNode,
234 current_task: &CurrentTask,
235 name: &FsStr,
236 ) -> Result<FsNodeHandle, Errno> {
237 if let Some(fs) = self.fs.mappings.get(name) {
238 Ok(fs.root().node.clone())
239 } else {
240 self.fs.base_fs.root().node.lookup(locked, current_task, &MountInfo::detached(), name)
241 }
242 }
243
244 fn get_xattr(
245 &self,
246 locked: &mut Locked<FileOpsCore>,
247 _node: &FsNode,
248 current_task: &CurrentTask,
249 name: &FsStr,
250 max_size: usize,
251 ) -> Result<ValueOrSize<FsString>, Errno> {
252 self.fs.base_fs.root().node.ops().get_xattr(
253 locked,
254 &*self.fs.base_fs.root().node,
255 current_task,
256 name,
257 max_size,
258 )
259 }
260
261 fn set_xattr(
263 &self,
264 locked: &mut Locked<FileOpsCore>,
265 _node: &FsNode,
266 current_task: &CurrentTask,
267 name: &FsStr,
268 value: &FsStr,
269 op: XattrOp,
270 ) -> Result<(), Errno> {
271 self.fs.base_fs.root().node.set_xattr(
272 locked,
273 current_task,
274 &MountInfo::detached(),
275 name,
276 value,
277 op,
278 )
279 }
280
281 fn remove_xattr(
282 &self,
283 locked: &mut Locked<FileOpsCore>,
284 _node: &FsNode,
285 current_task: &CurrentTask,
286 name: &FsStr,
287 ) -> Result<(), Errno> {
288 self.fs.base_fs.root().node.remove_xattr(locked, current_task, &MountInfo::detached(), name)
289 }
290
291 fn list_xattrs(
292 &self,
293 locked: &mut Locked<FileOpsCore>,
294 _node: &FsNode,
295 current_task: &CurrentTask,
296 max_size: usize,
297 ) -> Result<ValueOrSize<Vec<FsString>>, Errno> {
298 self.fs.base_fs.root().node.list_xattrs(locked, current_task, max_size)
299 }
300}
301
302struct LayeredFileOps {
303 fs: Arc<LayeredFs>,
304 root_file: FileHandle,
305}
306
307impl FileOps for LayeredFileOps {
308 fileops_impl_directory!();
309 fileops_impl_noop_sync!();
310
311 fn seek(
312 &self,
313 locked: &mut Locked<FileOpsCore>,
314 _file: &FileObject,
315 current_task: &CurrentTask,
316 current_offset: off_t,
317 target: SeekTarget,
318 ) -> Result<off_t, Errno> {
319 let mut new_offset = unbounded_seek(current_offset, target)?;
320 if new_offset >= self.fs.mappings.len() as off_t {
321 new_offset = self
322 .root_file
323 .seek(
324 locked,
325 current_task,
326 SeekTarget::Set(new_offset - self.fs.mappings.len() as off_t),
327 )?
328 .checked_add(self.fs.mappings.len() as off_t)
329 .ok_or_else(|| errno!(EINVAL))?;
330 }
331 Ok(new_offset)
332 }
333
334 fn readdir(
335 &self,
336 locked: &mut Locked<FileOpsCore>,
337 _file: &FileObject,
338 current_task: &CurrentTask,
339 sink: &mut dyn DirentSink,
340 ) -> Result<(), Errno> {
341 for (key, fs) in self.fs.mappings.iter().skip(sink.offset() as usize) {
342 sink.add(fs.root().node.ino, sink.offset() + 1, DirectoryEntryType::DIR, key.as_ref())?;
343 }
344
345 struct DirentSinkWrapper<'a> {
346 sink: &'a mut dyn DirentSink,
347 mappings: &'a BTreeMap<FsString, FileSystemHandle>,
348 offset: &'a mut off_t,
349 }
350
351 impl<'a> DirentSink for DirentSinkWrapper<'a> {
352 fn add(
353 &mut self,
354 inode_num: ino_t,
355 offset: off_t,
356 entry_type: DirectoryEntryType,
357 name: &FsStr,
358 ) -> Result<(), Errno> {
359 if !self.mappings.contains_key(name) {
360 self.sink.add(
361 inode_num,
362 offset + (self.mappings.len() as off_t),
363 entry_type,
364 name,
365 )?;
366 }
367 *self.offset = offset;
368 Ok(())
369 }
370 fn offset(&self) -> off_t {
371 *self.offset
372 }
373 }
374
375 let _token = starnix_sync::allow_subclass();
381 let mut root_file_offset = self.root_file.offset.copy();
382 let mut wrapper =
383 DirentSinkWrapper { sink, mappings: &self.fs.mappings, offset: &mut *root_file_offset };
384
385 self.root_file.readdir(locked, current_task, &mut wrapper)?;
386 root_file_offset.update();
387 Ok(())
388 }
389}
390
391#[cfg(test)]
392mod test {
393 use super::*;
394 use starnix_core::fs::tmpfs::TmpFs;
395 use starnix_core::testing::*;
396 use starnix_sync::Unlocked;
397
398 fn get_root_entry_names(
399 locked: &mut Locked<Unlocked>,
400 current_task: &CurrentTask,
401 fs: &FileSystem,
402 ) -> Vec<Vec<u8>> {
403 struct DirentNameCapturer {
404 pub names: Vec<Vec<u8>>,
405 offset: off_t,
406 }
407 impl DirentSink for DirentNameCapturer {
408 fn add(
409 &mut self,
410 _inode_num: ino_t,
411 offset: off_t,
412 _entry_type: DirectoryEntryType,
413 name: &FsStr,
414 ) -> Result<(), Errno> {
415 self.names.push(name.to_vec());
416 self.offset = offset;
417 Ok(())
418 }
419 fn offset(&self) -> off_t {
420 self.offset
421 }
422 }
423 let mut sink = DirentNameCapturer { names: vec![], offset: 0 };
424 fs.root()
425 .open_anonymous(locked, current_task, OpenFlags::RDONLY)
426 .expect("open")
427 .readdir(locked, current_task, &mut sink)
428 .expect("readdir");
429 std::mem::take(&mut sink.names)
430 }
431
432 #[::fuchsia::test]
433 async fn test_remove_duplicates() {
434 #[allow(deprecated, reason = "pre-existing usage")]
435 let (kernel, current_task, locked) = create_kernel_task_and_unlocked();
436 let base = TmpFs::new_fs(locked, &kernel);
437 base.root().create_dir_for_testing(locked, ¤t_task, "d1".into()).expect("create_dir");
438 base.root().create_dir_for_testing(locked, ¤t_task, "d2".into()).expect("create_dir");
439 let base_entries = get_root_entry_names(locked, ¤t_task, &base);
440 assert_eq!(base_entries.len(), 4);
441 assert!(base_entries.contains(&b".".to_vec()));
442 assert!(base_entries.contains(&b"..".to_vec()));
443 assert!(base_entries.contains(&b"d1".to_vec()));
444 assert!(base_entries.contains(&b"d2".to_vec()));
445
446 let tmpfs1 = TmpFs::new_fs(locked, &kernel);
447 let tmpfs2 = TmpFs::new_fs(locked, &kernel);
448 let layered_fs = LayeredFs::new_fs(
449 locked,
450 &kernel,
451 base,
452 BTreeMap::from([("d1".into(), tmpfs1), ("d3".into(), tmpfs2)]),
453 );
454 let layered_fs_entries = get_root_entry_names(locked, ¤t_task, &layered_fs);
455 assert_eq!(layered_fs_entries.len(), 5);
456 assert!(layered_fs_entries.contains(&b".".to_vec()));
457 assert!(layered_fs_entries.contains(&b"..".to_vec()));
458 assert!(layered_fs_entries.contains(&b"d1".to_vec()));
459 assert!(layered_fs_entries.contains(&b"d2".to_vec()));
460 assert!(layered_fs_entries.contains(&b"d3".to_vec()));
461 }
462}