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