1use alloc::{collections::BTreeMap, string::String, vec::Vec};
22use core::sync::atomic::{AtomicU64, Ordering};
23
24use crate::{
25 process::current_task_clone,
26 sync::SpinLock,
27 syscall::error::SyscallError,
28 vfs::scheme::{
29 DirEntry, FileFlags, FileStat, OpenFlags, OpenResult, Scheme, DEV_RAMFS, DT_DIR, DT_LNK,
30 DT_REG,
31 },
32};
33
34const INO_ROOT: u64 = 1;
37const INO_FIRST: u64 = 2;
38
39enum RamKind {
42 File { data: Vec<u8> },
43 Dir { children: BTreeMap<String, u64> },
44 Symlink { target: String },
45}
46
47struct RamInode {
48 #[allow(dead_code)]
49 ino: u64,
50 kind: RamKind,
51 mode: u32,
53 uid: u32,
54 gid: u32,
55 atime_ns: u64,
56 mtime_ns: u64,
57 ctime_ns: u64,
58 rdev: u64,
59}
60
61impl RamInode {
62 fn is_dir(&self) -> bool {
64 matches!(self.kind, RamKind::Dir { .. })
65 }
66
67 fn byte_size(&self) -> u64 {
69 match &self.kind {
70 RamKind::File { data } => data.len() as u64,
71 RamKind::Dir { .. } => 0,
72 RamKind::Symlink { target } => target.len() as u64,
73 }
74 }
75}
76
77struct RamState {
80 inodes: BTreeMap<u64, RamInode>,
81}
82
83impl RamState {
84 fn now_ns() -> u64 {
86 crate::syscall::time::current_time_ns()
87 }
88
89 fn current_ids() -> (u32, u32) {
91 if let Some(task) = current_task_clone() {
92 (
93 task.uid.load(Ordering::Relaxed),
94 task.gid.load(Ordering::Relaxed),
95 )
96 } else {
97 (0, 0)
98 }
99 }
100
101 fn new() -> Self {
103 let mut inodes = BTreeMap::new();
104 let now = Self::now_ns();
105 inodes.insert(
106 INO_ROOT,
107 RamInode {
108 ino: INO_ROOT,
109 kind: RamKind::Dir {
110 children: BTreeMap::new(),
111 },
112 mode: 0o040_755, uid: 0,
114 gid: 0,
115 atime_ns: now,
116 mtime_ns: now,
117 ctime_ns: now,
118 rdev: 0,
119 },
120 );
121 RamState { inodes }
122 }
123
124 fn lookup(&self, path: &str) -> Option<u64> {
128 let path = path.trim_matches('/');
129 if path.is_empty() {
130 return Some(INO_ROOT);
131 }
132 let mut cur = INO_ROOT;
133 for part in path.split('/') {
134 if part.is_empty() {
135 continue;
136 }
137 let inode = self.inodes.get(&cur)?;
138 match &inode.kind {
139 RamKind::Dir { children } => {
140 cur = *children.get(part)?;
141 }
142 _ => return None,
143 }
144 }
145 Some(cur)
146 }
147
148 fn lookup_parent<'a>(&self, path: &'a str) -> Option<(u64, &'a str)> {
152 let path = path.trim_matches('/');
153 let (parent_path, name) = match path.rfind('/') {
154 Some(pos) => (&path[..pos], &path[pos + 1..]),
155 None => ("", path),
156 };
157 let parent_ino = self.lookup(parent_path)?;
158 Some((parent_ino, name))
159 }
160
161 fn has_any_name_ref(&self, ino: u64) -> bool {
163 self.inodes.values().any(|inode| {
164 if let RamKind::Dir { children } = &inode.kind {
165 children.values().any(|&child| child == ino)
166 } else {
167 false
168 }
169 })
170 }
171
172 fn link_count(&self, ino: u64) -> u32 {
174 let mut count = 0u32;
175 for inode in self.inodes.values() {
176 if let RamKind::Dir { children } = &inode.kind {
177 for &child in children.values() {
178 if child == ino {
179 count = count.saturating_add(1);
180 }
181 }
182 }
183 }
184 count
185 }
186
187 fn dir_contains_ino(&self, ancestor_ino: u64, candidate_ino: u64) -> bool {
189 if ancestor_ino == candidate_ino {
190 return true;
191 }
192 let mut stack = Vec::new();
193 stack.push(ancestor_ino);
194 while let Some(cur) = stack.pop() {
195 let Some(inode) = self.inodes.get(&cur) else {
196 continue;
197 };
198 let RamKind::Dir { children } = &inode.kind else {
199 continue;
200 };
201 for &child in children.values() {
202 if child == candidate_ino {
203 return true;
204 }
205 if self
206 .inodes
207 .get(&child)
208 .map(|n| matches!(n.kind, RamKind::Dir { .. }))
209 .unwrap_or(false)
210 {
211 stack.push(child);
212 }
213 }
214 }
215 false
216 }
217}
218
219pub struct RamfsScheme {
223 state: SpinLock<RamState>,
224 next_ino: AtomicU64,
225}
226
227impl RamfsScheme {
228 pub fn new() -> Self {
230 RamfsScheme {
231 state: SpinLock::new(RamState::new()),
232 next_ino: AtomicU64::new(INO_FIRST),
233 }
234 }
235
236 fn alloc_ino(&self) -> u64 {
238 self.next_ino.fetch_add(1, Ordering::Relaxed)
239 }
240
241 pub fn ensure_dir(&self, path: &str) {
245 let _ = self.create_directory(path.trim_matches('/'), 0o755);
246 }
247
248 pub fn insert_file(&self, name: &str, content: &[u8]) {
252 let ino = self.alloc_ino();
253 let mut st = self.state.lock();
254 let now = RamState::now_ns();
255 st.inodes.insert(
256 ino,
257 RamInode {
258 ino,
259 kind: RamKind::File {
260 data: content.to_vec(),
261 },
262 mode: 0o100_444, uid: 0,
264 gid: 0,
265 atime_ns: now,
266 mtime_ns: now,
267 ctime_ns: now,
268 rdev: 0,
269 },
270 );
271 if let Some(root) = st.inodes.get_mut(&INO_ROOT) {
272 if let RamKind::Dir { ref mut children } = root.kind {
273 children.insert(String::from(name), ino);
274 root.mtime_ns = now;
275 root.ctime_ns = now;
276 }
277 }
278 }
279}
280
281impl Scheme for RamfsScheme {
282 fn open(&self, path: &str, flags: OpenFlags) -> Result<OpenResult, SyscallError> {
286 let path = path.trim_matches('/');
287
288 if flags.contains(OpenFlags::CREATE) {
289 let mut st = self.state.lock();
290 let now = RamState::now_ns();
291
292 if let Some(ino) = st.lookup(path) {
293 let inode = st.inodes.get_mut(&ino).unwrap();
295 if inode.is_dir() {
296 inode.atime_ns = now;
297 return Ok(OpenResult {
298 file_id: ino,
299 size: Some(0),
300 flags: FileFlags::DIRECTORY,
301 });
302 }
303 if flags.contains(OpenFlags::TRUNCATE) {
304 if let RamKind::File { ref mut data } = inode.kind {
305 data.clear();
306 }
307 inode.mtime_ns = now;
308 inode.ctime_ns = now;
309 }
310 inode.atime_ns = now;
311 let size = inode.byte_size();
312 return Ok(OpenResult {
313 file_id: ino,
314 size: Some(size),
315 flags: FileFlags::empty(),
316 });
317 }
318
319 let (parent_ino, name) = st.lookup_parent(path).ok_or(SyscallError::NotFound)?;
321 if name.is_empty() {
322 return Err(SyscallError::InvalidArgument);
323 }
324 let new_ino = self.next_ino.fetch_add(1, Ordering::Relaxed);
325 let (uid, gid) = RamState::current_ids();
326 st.inodes.insert(
327 new_ino,
328 RamInode {
329 ino: new_ino,
330 kind: RamKind::File { data: Vec::new() },
331 mode: 0o100_644,
332 uid,
333 gid,
334 atime_ns: now,
335 mtime_ns: now,
336 ctime_ns: now,
337 rdev: 0,
338 },
339 );
340 if let Some(parent) = st.inodes.get_mut(&parent_ino) {
341 if let RamKind::Dir { ref mut children } = parent.kind {
342 children.insert(String::from(name), new_ino);
343 parent.mtime_ns = now;
344 parent.ctime_ns = now;
345 }
346 }
347 return Ok(OpenResult {
348 file_id: new_ino,
349 size: Some(0),
350 flags: FileFlags::empty(),
351 });
352 }
353
354 let mut st = self.state.lock();
356 let now = RamState::now_ns();
357 let ino = st.lookup(path).ok_or(SyscallError::NotFound)?;
358 let inode = st.inodes.get_mut(&ino).unwrap();
359 inode.atime_ns = now;
360
361 if inode.is_dir() {
362 Ok(OpenResult {
363 file_id: ino,
364 size: Some(0),
365 flags: FileFlags::DIRECTORY,
366 })
367 } else {
368 Ok(OpenResult {
369 file_id: ino,
370 size: Some(inode.byte_size()),
371 flags: FileFlags::empty(),
372 })
373 }
374 }
375
376 fn read(&self, file_id: u64, offset: u64, buf: &mut [u8]) -> Result<usize, SyscallError> {
380 let mut st = self.state.lock();
381 let now = RamState::now_ns();
382 let inode = st.inodes.get_mut(&file_id).ok_or(SyscallError::BadHandle)?;
383 match &inode.kind {
384 RamKind::File { data } => {
385 let start = offset as usize;
386 if start >= data.len() {
387 inode.atime_ns = now;
388 return Ok(0);
389 }
390 let n = (data.len() - start).min(buf.len());
391 buf[..n].copy_from_slice(&data[start..start + n]);
392 inode.atime_ns = now;
393 Ok(n)
394 }
395 RamKind::Dir { .. } | RamKind::Symlink { .. } => Err(SyscallError::InvalidArgument),
396 }
397 }
398
399 fn write(&self, file_id: u64, offset: u64, buf: &[u8]) -> Result<usize, SyscallError> {
403 let mut st = self.state.lock();
404 let now = RamState::now_ns();
405 let inode = st.inodes.get_mut(&file_id).ok_or(SyscallError::BadHandle)?;
406 match &mut inode.kind {
407 RamKind::File { ref mut data } => {
408 let start = offset as usize;
409 let end = start + buf.len();
410 if end > data.len() {
411 data.resize(end, 0);
412 }
413 data[start..end].copy_from_slice(buf);
414 inode.mtime_ns = now;
415 inode.ctime_ns = now;
416 Ok(buf.len())
417 }
418 RamKind::Dir { .. } | RamKind::Symlink { .. } => Err(SyscallError::InvalidArgument),
419 }
420 }
421
422 fn close(&self, _file_id: u64) -> Result<(), SyscallError> {
426 Ok(()) }
428
429 fn size(&self, file_id: u64) -> Result<u64, SyscallError> {
433 let st = self.state.lock();
434 let inode = st.inodes.get(&file_id).ok_or(SyscallError::BadHandle)?;
435 Ok(inode.byte_size())
436 }
437
438 fn truncate(&self, file_id: u64, new_size: u64) -> Result<(), SyscallError> {
442 let mut st = self.state.lock();
443 let now = RamState::now_ns();
444 let inode = st.inodes.get_mut(&file_id).ok_or(SyscallError::BadHandle)?;
445 match &mut inode.kind {
446 RamKind::File { ref mut data } => {
447 data.resize(new_size as usize, 0);
448 inode.mtime_ns = now;
449 inode.ctime_ns = now;
450 Ok(())
451 }
452 RamKind::Dir { .. } | RamKind::Symlink { .. } => Err(SyscallError::InvalidArgument),
453 }
454 }
455
456 fn create_file(&self, path: &str, mode: u32) -> Result<OpenResult, SyscallError> {
460 let path = path.trim_matches('/');
461 let mut st = self.state.lock();
462 let now = RamState::now_ns();
463 let (uid, gid) = RamState::current_ids();
464
465 if st.lookup(path).is_some() {
466 return Err(SyscallError::AlreadyExists);
467 }
468
469 let (parent_ino, name) = st.lookup_parent(path).ok_or(SyscallError::NotFound)?;
470 if name.is_empty() {
471 return Err(SyscallError::InvalidArgument);
472 }
473
474 let new_ino = self.next_ino.fetch_add(1, Ordering::Relaxed);
475 let file_mode = (mode & 0o7777) | 0o100_000;
477 st.inodes.insert(
478 new_ino,
479 RamInode {
480 ino: new_ino,
481 kind: RamKind::File { data: Vec::new() },
482 mode: file_mode,
483 uid,
484 gid,
485 atime_ns: now,
486 mtime_ns: now,
487 ctime_ns: now,
488 rdev: 0,
489 },
490 );
491 if let Some(parent) = st.inodes.get_mut(&parent_ino) {
492 if let RamKind::Dir { ref mut children } = parent.kind {
493 children.insert(String::from(name), new_ino);
494 parent.mtime_ns = now;
495 parent.ctime_ns = now;
496 }
497 }
498
499 Ok(OpenResult {
500 file_id: new_ino,
501 size: Some(0),
502 flags: FileFlags::empty(),
503 })
504 }
505
506 fn create_directory(&self, path: &str, mode: u32) -> Result<OpenResult, SyscallError> {
510 let path = path.trim_matches('/');
511 let mut st = self.state.lock();
512 let now = RamState::now_ns();
513 let (uid, gid) = RamState::current_ids();
514
515 if let Some(ino) = st.lookup(path) {
517 let inode = st.inodes.get(&ino).unwrap();
518 if inode.is_dir() {
519 return Ok(OpenResult {
520 file_id: ino,
521 size: Some(0),
522 flags: FileFlags::DIRECTORY,
523 });
524 }
525 return Err(SyscallError::AlreadyExists);
526 }
527
528 let (parent_ino, name) = st.lookup_parent(path).ok_or(SyscallError::NotFound)?;
529 if name.is_empty() {
530 return Err(SyscallError::InvalidArgument);
531 }
532
533 let new_ino = self.next_ino.fetch_add(1, Ordering::Relaxed);
534 let dir_mode = (mode & 0o7777) | 0o040_000;
535 st.inodes.insert(
536 new_ino,
537 RamInode {
538 ino: new_ino,
539 kind: RamKind::Dir {
540 children: BTreeMap::new(),
541 },
542 mode: dir_mode,
543 uid,
544 gid,
545 atime_ns: now,
546 mtime_ns: now,
547 ctime_ns: now,
548 rdev: 0,
549 },
550 );
551 if let Some(parent) = st.inodes.get_mut(&parent_ino) {
552 if let RamKind::Dir { ref mut children } = parent.kind {
553 children.insert(String::from(name), new_ino);
554 parent.mtime_ns = now;
555 parent.ctime_ns = now;
556 }
557 }
558
559 Ok(OpenResult {
560 file_id: new_ino,
561 size: Some(0),
562 flags: FileFlags::DIRECTORY,
563 })
564 }
565
566 fn unlink(&self, path: &str) -> Result<(), SyscallError> {
570 let path = path.trim_matches('/');
571 if path.is_empty() {
572 return Err(SyscallError::PermissionDenied); }
574
575 let mut st = self.state.lock();
576 let now = RamState::now_ns();
577 let ino = st.lookup(path).ok_or(SyscallError::NotFound)?;
578
579 if let Some(inode) = st.inodes.get(&ino) {
581 if let RamKind::Dir { children } = &inode.kind {
582 if !children.is_empty() {
583 return Err(SyscallError::NotEmpty);
584 }
585 }
586 }
587
588 let (parent_ino, name) = st.lookup_parent(path).ok_or(SyscallError::NotFound)?;
589 if let Some(parent) = st.inodes.get_mut(&parent_ino) {
590 if let RamKind::Dir { ref mut children } = parent.kind {
591 children.remove(name);
592 parent.mtime_ns = now;
593 parent.ctime_ns = now;
594 }
595 }
596 if st
597 .inodes
598 .get(&ino)
599 .map(|inode| inode.is_dir())
600 .unwrap_or(false)
601 || !st.has_any_name_ref(ino)
602 {
603 st.inodes.remove(&ino);
604 }
605
606 Ok(())
607 }
608
609 fn stat(&self, file_id: u64) -> Result<FileStat, SyscallError> {
613 let st = self.state.lock();
614 let inode = st.inodes.get(&file_id).ok_or(SyscallError::BadHandle)?;
615
616 let (st_mode, st_size, st_nlink) = match &inode.kind {
617 RamKind::Dir { children } => (inode.mode, 0u64, 2 + children.len() as u32),
618 RamKind::File { data } => (inode.mode, data.len() as u64, st.link_count(file_id)),
619 RamKind::Symlink { target } => {
620 (inode.mode, target.len() as u64, st.link_count(file_id))
621 }
622 };
623
624 Ok(FileStat {
625 st_dev: DEV_RAMFS,
626 st_ino: file_id,
627 st_mode,
628 st_nlink,
629 st_uid: inode.uid,
630 st_gid: inode.gid,
631 st_rdev: inode.rdev,
632 st_size,
633 st_blksize: 4096,
634 st_blocks: (st_size + 511) / 512,
635 st_atime: strat9_abi::data::TimeSpec::from_nanos(inode.atime_ns),
636 st_mtime: strat9_abi::data::TimeSpec::from_nanos(inode.mtime_ns),
637 st_ctime: strat9_abi::data::TimeSpec::from_nanos(inode.ctime_ns),
638 ..FileStat::zeroed()
639 })
640 }
641
642 fn readdir(&self, file_id: u64) -> Result<Vec<DirEntry>, SyscallError> {
646 let mut st = self.state.lock();
647 let now = RamState::now_ns();
648 let children_snapshot: Vec<(String, u64)> = {
649 let inode = st.inodes.get(&file_id).ok_or(SyscallError::BadHandle)?;
650 match &inode.kind {
651 RamKind::Dir { children } => children
652 .iter()
653 .map(|(name, &child_ino)| (name.clone(), child_ino))
654 .collect(),
655 RamKind::File { .. } | RamKind::Symlink { .. } => {
656 return Err(SyscallError::InvalidArgument)
657 }
658 }
659 };
660
661 let mut entries = Vec::with_capacity(children_snapshot.len());
662 for (name, child_ino) in children_snapshot {
663 let file_type = match st.inodes.get(&child_ino).map(|c| &c.kind) {
664 Some(RamKind::Dir { .. }) => DT_DIR,
665 Some(RamKind::Symlink { .. }) => DT_LNK,
666 _ => DT_REG,
667 };
668 entries.push(DirEntry {
669 ino: child_ino,
670 file_type,
671 name,
672 });
673 }
674
675 if let Some(inode) = st.inodes.get_mut(&file_id) {
676 inode.atime_ns = now;
677 }
678 Ok(entries)
679 }
680
681 fn rename(&self, old_path: &str, new_path: &str) -> Result<(), SyscallError> {
685 let old_path = old_path.trim_matches('/');
686 let new_path = new_path.trim_matches('/');
687 if old_path.is_empty() || new_path.is_empty() {
688 return Err(SyscallError::InvalidArgument);
689 }
690
691 let mut st = self.state.lock();
692 let now = RamState::now_ns();
693 let ino = st.lookup(old_path).ok_or(SyscallError::NotFound)?;
694
695 let (old_parent, old_name) = st.lookup_parent(old_path).ok_or(SyscallError::NotFound)?;
696 let old_name = String::from(old_name);
697
698 let (new_parent, new_name) = st.lookup_parent(new_path).ok_or(SyscallError::NotFound)?;
699 let new_name = String::from(new_name);
700 if st
701 .inodes
702 .get(&ino)
703 .map(|n| n.is_dir() && st.dir_contains_ino(ino, new_parent))
704 .unwrap_or(false)
705 {
706 return Err(SyscallError::InvalidArgument);
707 }
708 let existing = st.lookup(new_path);
709
710 if let Some(existing) = existing {
711 if existing == ino {
712 return Ok(());
713 }
714 if let Some(inode) = st.inodes.get(&existing) {
715 if let RamKind::Dir { children } = &inode.kind {
716 if !children.is_empty() {
717 return Err(SyscallError::NotSupported);
718 }
719 }
720 }
721 }
722
723 if let Some(parent) = st.inodes.get_mut(&old_parent) {
724 if let RamKind::Dir { ref mut children } = parent.kind {
725 children.remove(&old_name);
726 parent.mtime_ns = now;
727 parent.ctime_ns = now;
728 }
729 }
730 if let Some(parent) = st.inodes.get_mut(&new_parent) {
731 if let RamKind::Dir { ref mut children } = parent.kind {
732 children.insert(new_name, ino);
733 parent.mtime_ns = now;
734 parent.ctime_ns = now;
735 }
736 }
737 if let Some(inode) = st.inodes.get_mut(&ino) {
738 inode.ctime_ns = now;
739 }
740 if let Some(existing) = existing {
741 if !st.has_any_name_ref(existing) {
742 st.inodes.remove(&existing);
743 }
744 }
745 Ok(())
746 }
747
748 fn chmod(&self, path: &str, mode: u32) -> Result<(), SyscallError> {
752 let path = path.trim_matches('/');
753 let mut st = self.state.lock();
754 let now = RamState::now_ns();
755 let ino = st.lookup(path).ok_or(SyscallError::NotFound)?;
756 let inode = st.inodes.get_mut(&ino).ok_or(SyscallError::NotFound)?;
757 let type_bits = inode.mode & 0o170_000;
758 inode.mode = type_bits | (mode & 0o7777);
759 inode.ctime_ns = now;
760 Ok(())
761 }
762
763 fn fchmod(&self, file_id: u64, mode: u32) -> Result<(), SyscallError> {
767 let mut st = self.state.lock();
768 let now = RamState::now_ns();
769 let inode = st.inodes.get_mut(&file_id).ok_or(SyscallError::BadHandle)?;
770 let type_bits = inode.mode & 0o170_000;
771 inode.mode = type_bits | (mode & 0o7777);
772 inode.ctime_ns = now;
773 Ok(())
774 }
775
776 fn link(&self, old_path: &str, new_path: &str) -> Result<(), SyscallError> {
780 let old_path = old_path.trim_matches('/');
781 let new_path = new_path.trim_matches('/');
782
783 let mut st = self.state.lock();
784 let now = RamState::now_ns();
785 let ino = st.lookup(old_path).ok_or(SyscallError::NotFound)?;
786
787 if st.inodes.get(&ino).map(|i| i.is_dir()).unwrap_or(false) {
788 return Err(SyscallError::PermissionDenied);
789 }
790 if st.lookup(new_path).is_some() {
791 return Err(SyscallError::AlreadyExists);
792 }
793
794 let (parent_ino, name) = st.lookup_parent(new_path).ok_or(SyscallError::NotFound)?;
795 if let Some(parent) = st.inodes.get_mut(&parent_ino) {
796 if let RamKind::Dir { ref mut children } = parent.kind {
797 children.insert(String::from(name), ino);
798 parent.mtime_ns = now;
799 parent.ctime_ns = now;
800 }
801 }
802 if let Some(inode) = st.inodes.get_mut(&ino) {
803 inode.ctime_ns = now;
804 }
805 Ok(())
806 }
807
808 fn symlink(&self, target: &str, link_path: &str) -> Result<(), SyscallError> {
812 let link_path = link_path.trim_matches('/');
813 if link_path.is_empty() {
814 return Err(SyscallError::InvalidArgument);
815 }
816
817 let mut st = self.state.lock();
818 let now = RamState::now_ns();
819 let (uid, gid) = RamState::current_ids();
820 if st.lookup(link_path).is_some() {
821 return Err(SyscallError::AlreadyExists);
822 }
823
824 let (parent_ino, name) = st.lookup_parent(link_path).ok_or(SyscallError::NotFound)?;
825 let new_ino = self.next_ino.fetch_add(1, Ordering::Relaxed);
826 st.inodes.insert(
827 new_ino,
828 RamInode {
829 ino: new_ino,
830 kind: RamKind::Symlink {
831 target: String::from(target),
832 },
833 mode: 0o120_777,
834 uid,
835 gid,
836 atime_ns: now,
837 mtime_ns: now,
838 ctime_ns: now,
839 rdev: 0,
840 },
841 );
842 if let Some(parent) = st.inodes.get_mut(&parent_ino) {
843 if let RamKind::Dir { ref mut children } = parent.kind {
844 children.insert(String::from(name), new_ino);
845 parent.mtime_ns = now;
846 parent.ctime_ns = now;
847 }
848 }
849 Ok(())
850 }
851
852 fn readlink(&self, path: &str) -> Result<String, SyscallError> {
856 let path = path.trim_matches('/');
857 let mut st = self.state.lock();
858 let now = RamState::now_ns();
859 let ino = st.lookup(path).ok_or(SyscallError::NotFound)?;
860 let inode = st.inodes.get_mut(&ino).ok_or(SyscallError::NotFound)?;
861 match &inode.kind {
862 RamKind::Symlink { target } => {
863 inode.atime_ns = now;
864 Ok(target.clone())
865 }
866 _ => Err(SyscallError::InvalidArgument),
867 }
868 }
869}