1use crate::mm::PAGE_SIZE;
6use crate::security;
7use crate::task::{CurrentTask, Kernel};
8use crate::vfs::memory_directory::MemoryDirectoryFile;
9use crate::vfs::{
10 CacheMode, DirEntry, DirEntryHandle, FileOps, FileSystem, FileSystemHandle, FileSystemOps,
11 FileSystemOptions, FsNode, FsNodeHandle, FsNodeInfo, FsNodeOps, FsStr, FsString,
12 MemoryRegularNode, MemoryXattrStorage, SymlinkNode, XattrStorage as _, fs_args,
13 fs_node_impl_not_dir, fs_node_impl_xattr_delegate,
14};
15use starnix_logging::{log_warn, track_stub};
16use starnix_sync::{FileOpsCore, LockEqualOrBefore, Locked, Unlocked};
17use starnix_types::vfs::default_statfs;
18use starnix_uapi::auth::FsCred;
19use starnix_uapi::device_type::DeviceType;
20use starnix_uapi::errors::Errno;
21use starnix_uapi::file_mode::{FileMode, mode};
22use starnix_uapi::open_flags::OpenFlags;
23use starnix_uapi::seal_flags::SealFlags;
24use starnix_uapi::{TMPFS_MAGIC, error, gid_t, statfs, uid_t};
25use std::collections::BTreeMap;
26use std::sync::Arc;
27use std::sync::atomic::{AtomicU32, Ordering};
28
29pub struct TmpFs(&'static FsStr);
30
31impl FileSystemOps for Arc<TmpFs> {
32 fn statfs(
33 &self,
34 _locked: &mut Locked<FileOpsCore>,
35 _fs: &FileSystem,
36 _current_task: &CurrentTask,
37 ) -> Result<statfs, Errno> {
38 Ok(statfs {
39 f_blocks: 0x100000000,
41 f_bavail: 0x100000000,
42 f_bfree: 0x100000000,
43 ..default_statfs(TMPFS_MAGIC)
44 })
45 }
46 fn name(&self) -> &'static FsStr {
47 self.0
48 }
49
50 fn rename(
51 &self,
52 _locked: &mut Locked<FileOpsCore>,
53 _fs: &FileSystem,
54 _current_task: &CurrentTask,
55 old_parent: &FsNodeHandle,
56 _old_name: &FsStr,
57 new_parent: &FsNodeHandle,
58 _new_name: &FsStr,
59 renamed: &FsNodeHandle,
60 replaced: Option<&FsNodeHandle>,
61 ) -> Result<(), Errno> {
62 fn child_count(node: &FsNodeHandle) -> &AtomicU32 {
63 &node.downcast_ops::<TmpFsDirectory>().unwrap().child_count
68 }
69 if let Some(replaced) = replaced {
70 if replaced.is_dir() {
71 if child_count(replaced).load(Ordering::Acquire) != 0 {
73 return error!(ENOTEMPTY);
74 }
75 }
76 }
77 child_count(old_parent).fetch_sub(1, Ordering::Release);
78 child_count(new_parent).fetch_add(1, Ordering::Release);
79 if renamed.is_dir() {
80 old_parent.update_info(|info| {
81 info.link_count -= 1;
82 });
83 new_parent.update_info(|info| {
84 info.link_count += 1;
85 });
86 }
87 if let Some(replaced) = replaced {
90 if replaced.is_dir() {
91 new_parent.update_info(|info| {
92 info.link_count -= 1;
93 });
94 }
95 child_count(new_parent).fetch_sub(1, Ordering::Release);
96 }
97 Ok(())
98 }
99
100 fn exchange(
101 &self,
102 _fs: &FileSystem,
103 _current_task: &CurrentTask,
104 node1: &FsNodeHandle,
105 parent1: &FsNodeHandle,
106 _name1: &FsStr,
107 node2: &FsNodeHandle,
108 parent2: &FsNodeHandle,
109 _name2: &FsStr,
110 ) -> Result<(), Errno> {
111 if node1.is_dir() {
112 parent1.update_info(|info| {
113 info.link_count -= 1;
114 });
115 parent2.update_info(|info| {
116 info.link_count += 1;
117 });
118 }
119
120 if node2.is_dir() {
121 parent1.update_info(|info| {
122 info.link_count += 1;
123 });
124 parent2.update_info(|info| {
125 info.link_count -= 1;
126 });
127 }
128
129 Ok(())
130 }
131}
132
133pub fn tmp_fs(
134 locked: &mut Locked<Unlocked>,
135 current_task: &CurrentTask,
136 options: FileSystemOptions,
137) -> Result<FileSystemHandle, Errno> {
138 TmpFs::new_fs_with_options(locked, ¤t_task.kernel(), options)
139}
140
141impl TmpFs {
142 pub fn new_fs<L>(locked: &mut Locked<L>, kernel: &Kernel) -> FileSystemHandle
143 where
144 L: LockEqualOrBefore<FileOpsCore>,
145 {
146 Self::new_fs_with_options(locked, kernel, Default::default())
147 .expect("empty options cannot fail")
148 }
149
150 pub fn new_fs_with_name<L>(
151 locked: &mut Locked<L>,
152 kernel: &Kernel,
153 name: &'static FsStr,
154 ) -> FileSystemHandle
155 where
156 L: LockEqualOrBefore<FileOpsCore>,
157 {
158 Self::new_fs_with_options_and_name(locked, kernel, Default::default(), name)
159 .expect("empty options cannot fail")
160 }
161
162 pub fn new_fs_with_options<L>(
163 locked: &mut Locked<L>,
164 kernel: &Kernel,
165 options: FileSystemOptions,
166 ) -> Result<FileSystemHandle, Errno>
167 where
168 L: LockEqualOrBefore<FileOpsCore>,
169 {
170 Self::new_fs_with_options_and_name(locked, kernel, options, "tmpfs".into())
171 }
172
173 fn new_fs_with_options_and_name<L>(
174 locked: &mut Locked<L>,
175 kernel: &Kernel,
176 options: FileSystemOptions,
177 name: &'static FsStr,
178 ) -> Result<FileSystemHandle, Errno>
179 where
180 L: LockEqualOrBefore<FileOpsCore>,
181 {
182 let fs =
183 FileSystem::new(locked, kernel, CacheMode::Permanent, Arc::new(TmpFs(name)), options)?;
184 let mut mount_options = fs.options.params.clone();
185 let mode = if let Some(mode) = mount_options.remove(b"mode") {
186 FileMode::from_string(mode.as_ref())?
187 } else {
188 mode!(IFDIR, 0o777)
189 };
190 let uid = if let Some(uid) = mount_options.remove(b"uid") {
191 fs_args::parse::<uid_t>(uid.as_ref())?
192 } else {
193 0
194 };
195 let gid = if let Some(gid) = mount_options.remove(b"gid") {
196 fs_args::parse::<gid_t>(gid.as_ref())?
197 } else {
198 0
199 };
200 let root_ino = fs.allocate_ino();
201 let mut info = FsNodeInfo::new(mode!(IFDIR, 0o777), FsCred { uid, gid });
202 info.chmod(mode);
203 fs.create_root_with_info(root_ino, TmpFsDirectory::new(), info);
204
205 if !mount_options.is_empty() {
206 track_stub!(
207 TODO("https://fxbug.dev/322873419"),
208 "unknown tmpfs options, see logs for strings"
209 );
210 log_warn!("Unknown tmpfs options: {}", mount_options);
211 }
212
213 Ok(fs)
214 }
215
216 pub fn set_initial_content(kernel: &Kernel, fs: &FileSystemHandle, data: TmpFsData) {
217 fn create_dir_entry_from_data(
218 kernel: &Kernel,
219 fs: &FileSystemHandle,
220 data: TmpFsData,
221 this: Option<DirEntryHandle>,
222 name: FsString,
223 ) -> DirEntryHandle {
224 let new_direntry = |node, parent, name| {
227 let dir_entry = DirEntry::new(node, parent, name);
228 security::fs_node_init_with_dentry_deferred(kernel, &dir_entry);
229 dir_entry
230 };
231
232 match data.node_type {
233 TmpFsNodeType::Link(target) => {
234 assert!(this.is_none());
235 let node = TmpFsDirectory::new_symlink(fs, target.as_ref(), data.owner);
236 new_direntry(node, None, name)
237 }
238 TmpFsNodeType::Directory(children) => {
239 let this = this.unwrap_or_else(|| {
240 let info = FsNodeInfo::new(mode!(IFDIR, data.perm), data.owner);
241 let node = fs.create_node_and_allocate_node_id(TmpFsDirectory::new(), info);
242 new_direntry(node, None, name.clone())
243 });
244 this.node
245 .downcast_ops::<TmpFsDirectory>()
246 .expect("directory must be from tmpfs")
247 .child_count
248 .fetch_add(children.len() as u32, Ordering::Release);
249 let children = children
250 .into_iter()
251 .map(|(name, data)| {
252 let child =
253 create_dir_entry_from_data(kernel, fs, data, None, name.clone());
254 (name, child)
255 })
256 .collect::<BTreeMap<_, _>>();
257 this.set_children(children);
258 this
259 }
260 }
261 }
262
263 create_dir_entry_from_data(kernel, fs, data, Some(Arc::clone(fs.root())), "".into());
264 }
265}
266
267pub enum TmpFsNodeType {
268 Link(FsString),
269 Directory(BTreeMap<FsString, TmpFsData>),
270}
271
272pub struct TmpFsData {
273 pub owner: FsCred,
274 pub perm: u32,
275 pub node_type: TmpFsNodeType,
276}
277
278pub struct TmpFsDirectory {
279 xattrs: MemoryXattrStorage,
280 child_count: AtomicU32,
281}
282
283impl TmpFsDirectory {
284 pub fn new() -> Self {
285 Self { xattrs: MemoryXattrStorage::default(), child_count: AtomicU32::new(0) }
286 }
287
288 fn new_symlink(fs: &Arc<FileSystem>, target: &FsStr, owner: FsCred) -> FsNodeHandle {
289 let (link, info) = SymlinkNode::new(target, owner);
290 fs.create_node_and_allocate_node_id(link, info)
291 }
292}
293
294fn create_child_node(
295 parent: &FsNode,
296 mode: FileMode,
297 dev: DeviceType,
298 owner: FsCred,
299) -> Result<FsNodeHandle, Errno> {
300 let ops: Box<dyn FsNodeOps> = match mode.fmt() {
301 FileMode::IFREG => Box::new(MemoryRegularNode::new()?),
302 FileMode::IFIFO | FileMode::IFBLK | FileMode::IFCHR | FileMode::IFSOCK => {
303 Box::new(TmpFsSpecialNode::new())
304 }
305 _ => return error!(EACCES),
306 };
307 let mut info = FsNodeInfo::new(mode, owner);
308 info.rdev = dev;
309 info.blksize = *PAGE_SIZE as usize;
311 let child = parent.fs().create_node_and_allocate_node_id(ops, info);
312 if mode.fmt() == FileMode::IFREG {
313 child.write_guard_state.lock().enable_sealing(SealFlags::SEAL);
315 }
316 Ok(child)
317}
318
319impl FsNodeOps for TmpFsDirectory {
320 fs_node_impl_xattr_delegate!(self, self.xattrs);
321
322 fn create_file_ops(
323 &self,
324 _locked: &mut Locked<FileOpsCore>,
325 _node: &FsNode,
326 _current_task: &CurrentTask,
327 _flags: OpenFlags,
328 ) -> Result<Box<dyn FileOps>, Errno> {
329 Ok(Box::new(MemoryDirectoryFile::new()))
330 }
331
332 fn mkdir(
333 &self,
334 _locked: &mut Locked<FileOpsCore>,
335 node: &FsNode,
336 _current_task: &CurrentTask,
337 _name: &FsStr,
338 mode: FileMode,
339 owner: FsCred,
340 ) -> Result<FsNodeHandle, Errno> {
341 node.update_info(|info| {
342 info.link_count += 1;
343 });
344 self.child_count.fetch_add(1, Ordering::Release);
345 Ok(node
346 .fs()
347 .create_node_and_allocate_node_id(TmpFsDirectory::new(), FsNodeInfo::new(mode, owner)))
348 }
349
350 fn mknod(
351 &self,
352 _locked: &mut Locked<FileOpsCore>,
353 node: &FsNode,
354 _current_task: &CurrentTask,
355 _name: &FsStr,
356 mode: FileMode,
357 dev: DeviceType,
358 owner: FsCred,
359 ) -> Result<FsNodeHandle, Errno> {
360 let child = create_child_node(node, mode, dev, owner)?;
361 self.child_count.fetch_add(1, Ordering::Release);
362 Ok(child)
363 }
364
365 fn create_symlink(
366 &self,
367 _locked: &mut Locked<FileOpsCore>,
368 node: &FsNode,
369 _current_task: &CurrentTask,
370 _name: &FsStr,
371 target: &FsStr,
372 owner: FsCred,
373 ) -> Result<FsNodeHandle, Errno> {
374 self.child_count.fetch_add(1, Ordering::Release);
375 Ok(Self::new_symlink(&node.fs(), target, owner))
376 }
377
378 fn create_tmpfile(
379 &self,
380 node: &FsNode,
381 _current_task: &CurrentTask,
382 mode: FileMode,
383 owner: FsCred,
384 ) -> Result<FsNodeHandle, Errno> {
385 assert!(mode.is_reg());
386 create_child_node(node, mode, DeviceType::NONE, owner)
387 }
388
389 fn link(
390 &self,
391 _locked: &mut Locked<FileOpsCore>,
392 _node: &FsNode,
393 _current_task: &CurrentTask,
394 _name: &FsStr,
395 child: &FsNodeHandle,
396 ) -> Result<(), Errno> {
397 child.update_info(|info| {
398 info.link_count += 1;
399 });
400 self.child_count.fetch_add(1, Ordering::Release);
401 Ok(())
402 }
403
404 fn unlink(
405 &self,
406 _locked: &mut Locked<FileOpsCore>,
407 node: &FsNode,
408 _current_task: &CurrentTask,
409 _name: &FsStr,
410 child_to_unlink: &FsNodeHandle,
411 ) -> Result<(), Errno> {
412 if child_to_unlink.is_dir() {
413 let child_count =
418 &child_to_unlink.downcast_ops::<TmpFsDirectory>().unwrap().child_count;
419 if child_count.load(Ordering::Relaxed) != 0 {
420 return error!(ENOTEMPTY);
421 }
422
423 node.update_info(|info| {
424 info.link_count -= 1;
425 });
426 }
427 child_to_unlink.update_info(|info| {
428 info.link_count -= 1;
429 });
430 self.child_count.fetch_sub(1, Ordering::Release);
431 Ok(())
432 }
433}
434
435struct TmpFsSpecialNode {
436 xattrs: MemoryXattrStorage,
437}
438
439impl TmpFsSpecialNode {
440 pub fn new() -> Self {
441 Self { xattrs: MemoryXattrStorage::default() }
442 }
443}
444
445impl FsNodeOps for TmpFsSpecialNode {
446 fs_node_impl_not_dir!();
447 fs_node_impl_xattr_delegate!(self, self.xattrs);
448
449 fn create_file_ops(
450 &self,
451 _locked: &mut Locked<FileOpsCore>,
452 _node: &FsNode,
453 _current_task: &CurrentTask,
454 _flags: OpenFlags,
455 ) -> Result<Box<dyn FileOps>, Errno> {
456 unreachable!("Special nodes cannot be opened.");
457 }
458}
459
460#[cfg(test)]
461mod test {
462 use super::*;
463 use crate::testing::spawn_kernel_and_run;
464 use crate::vfs::buffers::{VecInputBuffer, VecOutputBuffer};
465 use crate::vfs::fs_args::MountParams;
466 use crate::vfs::{FdNumber, UnlinkKind};
467 use starnix_uapi::errno;
468 use starnix_uapi::file_mode::AccessCheck;
469 use starnix_uapi::mount_flags::MountFlags;
470 use starnix_uapi::vfs::ResolveFlags;
471 use zerocopy::IntoBytes;
472
473 #[::fuchsia::test]
474 async fn test_tmpfs() {
475 spawn_kernel_and_run(async |locked, current_task| {
476 let kernel = current_task.kernel();
477 let fs = TmpFs::new_fs(locked, &kernel);
478 let root = fs.root();
479 let usr = root.create_dir(locked, ¤t_task, "usr".into()).unwrap();
480 let _etc = root.create_dir(locked, ¤t_task, "etc".into()).unwrap();
481 let _usr_bin = usr.create_dir(locked, ¤t_task, "bin".into()).unwrap();
482 let mut names = root.copy_child_names();
483 names.sort();
484 assert!(names.iter().eq(["etc", "usr"].iter()));
485 })
486 .await;
487 }
488
489 #[::fuchsia::test]
490 async fn test_write_read() {
491 spawn_kernel_and_run(async |locked, current_task| {
492 let path = "test.bin";
493 let _file = current_task
494 .fs()
495 .root()
496 .create_node(
497 locked,
498 ¤t_task,
499 path.into(),
500 mode!(IFREG, 0o777),
501 DeviceType::NONE,
502 )
503 .unwrap();
504
505 let wr_file = current_task.open_file(locked, path.into(), OpenFlags::RDWR).unwrap();
506
507 let test_seq = 0..10000u16;
508 let test_vec = test_seq.collect::<Vec<_>>();
509 let test_bytes = test_vec.as_slice().as_bytes();
510
511 let written =
512 wr_file.write(locked, ¤t_task, &mut VecInputBuffer::new(test_bytes)).unwrap();
513 assert_eq!(written, test_bytes.len());
514
515 let mut read_buffer = VecOutputBuffer::new(test_bytes.len() + 1);
516 let read = wr_file.read_at(locked, ¤t_task, 0, &mut read_buffer).unwrap();
517 assert_eq!(read, test_bytes.len());
518 assert_eq!(test_bytes, read_buffer.data());
519 })
520 .await;
521 }
522
523 #[::fuchsia::test]
524 async fn test_read_past_eof() {
525 spawn_kernel_and_run(async |locked, current_task| {
526 let path = "test.bin";
528 let _file = current_task
529 .fs()
530 .root()
531 .create_node(
532 locked,
533 ¤t_task,
534 path.into(),
535 mode!(IFREG, 0o777),
536 DeviceType::NONE,
537 )
538 .unwrap();
539 let rd_file = current_task.open_file(locked, path.into(), OpenFlags::RDONLY).unwrap();
540
541 let buffer_size = 0x10000;
543 let mut output_buffer = VecOutputBuffer::new(buffer_size);
544 let test_offset = 100;
545 let result =
546 rd_file.read_at(locked, ¤t_task, test_offset, &mut output_buffer).unwrap();
547 assert_eq!(result, 0);
548 })
549 .await;
550 }
551
552 #[::fuchsia::test]
553 async fn test_permissions() {
554 spawn_kernel_and_run(async |locked, current_task| {
555 let path = "test.bin";
556 let file = current_task
557 .open_file_at(
558 locked,
559 FdNumber::AT_FDCWD,
560 path.into(),
561 OpenFlags::CREAT | OpenFlags::RDONLY,
562 FileMode::from_bits(0o777),
563 ResolveFlags::empty(),
564 AccessCheck::default(),
565 )
566 .expect("failed to create file");
567 assert_eq!(
568 0,
569 file.read(locked, ¤t_task, &mut VecOutputBuffer::new(0))
570 .expect("failed to read")
571 );
572
573 assert!(file.write(locked, ¤t_task, &mut VecInputBuffer::new(&[])).is_err());
574
575 let file = current_task
576 .open_file_at(
577 locked,
578 FdNumber::AT_FDCWD,
579 path.into(),
580 OpenFlags::WRONLY,
581 FileMode::EMPTY,
582 ResolveFlags::empty(),
583 AccessCheck::default(),
584 )
585 .expect("failed to open file WRONLY");
586
587 assert!(file.read(locked, ¤t_task, &mut VecOutputBuffer::new(0)).is_err());
588
589 assert_eq!(
590 0,
591 file.write(locked, ¤t_task, &mut VecInputBuffer::new(&[]))
592 .expect("failed to write")
593 );
594
595 let file = current_task
596 .open_file_at(
597 locked,
598 FdNumber::AT_FDCWD,
599 path.into(),
600 OpenFlags::RDWR,
601 FileMode::EMPTY,
602 ResolveFlags::empty(),
603 AccessCheck::default(),
604 )
605 .expect("failed to open file RDWR");
606
607 assert_eq!(
608 0,
609 file.read(locked, ¤t_task, &mut VecOutputBuffer::new(0))
610 .expect("failed to read")
611 );
612
613 assert_eq!(
614 0,
615 file.write(locked, ¤t_task, &mut VecInputBuffer::new(&[]))
616 .expect("failed to write")
617 );
618 })
619 .await;
620 }
621
622 #[::fuchsia::test]
623 async fn test_persistence() {
624 spawn_kernel_and_run(async |locked, current_task| {
625 {
626 let root = ¤t_task.fs().root().entry;
627 let usr = root
628 .create_dir(locked, ¤t_task, "usr".into())
629 .expect("failed to create usr");
630 root.create_dir(locked, ¤t_task, "etc".into())
631 .expect("failed to create usr/etc");
632 usr.create_dir(locked, ¤t_task, "bin".into())
633 .expect("failed to create usr/bin");
634 }
635
636 current_task
639 .open_file(locked, "/usr/bin".into(), OpenFlags::RDONLY | OpenFlags::DIRECTORY)
640 .expect("failed to open /usr/bin");
641 assert_eq!(
642 errno!(ENOENT),
643 current_task
644 .open_file(locked, "/usr/bin/test.txt".into(), OpenFlags::RDWR)
645 .unwrap_err()
646 );
647 current_task
648 .open_file_at(
649 locked,
650 FdNumber::AT_FDCWD,
651 "/usr/bin/test.txt".into(),
652 OpenFlags::RDWR | OpenFlags::CREAT,
653 FileMode::from_bits(0o777),
654 ResolveFlags::empty(),
655 AccessCheck::default(),
656 )
657 .expect("failed to create test.txt");
658 let txt = current_task
659 .open_file(locked, "/usr/bin/test.txt".into(), OpenFlags::RDWR)
660 .expect("failed to open test.txt");
661
662 let usr_bin = current_task
663 .open_file(locked, "/usr/bin".into(), OpenFlags::RDONLY)
664 .expect("failed to open /usr/bin");
665 usr_bin
666 .name
667 .unlink(locked, ¤t_task, "test.txt".into(), UnlinkKind::NonDirectory, false)
668 .expect("failed to unlink test.text");
669 assert_eq!(
670 errno!(ENOENT),
671 current_task
672 .open_file(locked, "/usr/bin/test.txt".into(), OpenFlags::RDWR)
673 .unwrap_err()
674 );
675 assert_eq!(
676 errno!(ENOENT),
677 usr_bin
678 .name
679 .unlink(
680 locked,
681 ¤t_task,
682 "test.txt".into(),
683 UnlinkKind::NonDirectory,
684 false
685 )
686 .unwrap_err()
687 );
688
689 assert_eq!(
690 0,
691 txt.read(locked, ¤t_task, &mut VecOutputBuffer::new(0))
692 .expect("failed to read")
693 );
694 std::mem::drop(txt);
695 assert_eq!(
696 errno!(ENOENT),
697 current_task
698 .open_file(locked, "/usr/bin/test.txt".into(), OpenFlags::RDWR)
699 .unwrap_err()
700 );
701 std::mem::drop(usr_bin);
702
703 let usr = current_task
704 .open_file(locked, "/usr".into(), OpenFlags::RDONLY)
705 .expect("failed to open /usr");
706 assert_eq!(
707 errno!(ENOENT),
708 current_task.open_file(locked, "/usr/foo".into(), OpenFlags::RDONLY).unwrap_err()
709 );
710 usr.name
711 .unlink(locked, ¤t_task, "bin".into(), UnlinkKind::Directory, false)
712 .expect("failed to unlink /usr/bin");
713 })
714 .await;
715 }
716
717 #[::fuchsia::test]
718 async fn test_data() {
719 spawn_kernel_and_run(async |locked, current_task| {
720 let kernel = current_task.kernel();
721 let fs = TmpFs::new_fs_with_options(
722 locked,
723 &kernel,
724 FileSystemOptions {
725 source: Default::default(),
726 flags: MountFlags::empty(),
727 params: MountParams::parse(b"mode=0123,uid=42,gid=84".into())
728 .expect("parsed correctly"),
729 },
730 )
731 .expect("new_fs");
732 let info = fs.root().node.info();
733 assert_eq!(info.mode, mode!(IFDIR, 0o123));
734 assert_eq!(info.uid, 42);
735 assert_eq!(info.gid, 84);
736 })
737 .await;
738 }
739}