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