wasmer_wasix/syscalls/wasi/
path_link.rs1use super::*;
2use crate::syscalls::*;
3
4#[instrument(level = "trace", skip_all, fields(%old_fd, %new_fd, old_path = field::Empty, new_path = field::Empty, follow_symlinks = false), ret)]
22pub fn path_link<M: MemorySize>(
23 mut ctx: FunctionEnvMut<'_, WasiEnv>,
24 old_fd: WasiFd,
25 old_flags: LookupFlags,
26 old_path: WasmPtr<u8, M>,
27 old_path_len: M::Offset,
28 new_fd: WasiFd,
29 new_path: WasmPtr<u8, M>,
30 new_path_len: M::Offset,
31) -> Result<Errno, WasiError> {
32 WasiEnv::do_pending_operations(&mut ctx)?;
33
34 if old_flags & __WASI_LOOKUP_SYMLINK_FOLLOW != 0 {
35 Span::current().record("follow_symlinks", true);
36 }
37 let env = ctx.data();
38 let (memory, mut state, inodes) = unsafe { env.get_memory_and_wasi_state_and_inodes(&ctx, 0) };
39 let mut old_path_str = unsafe { get_input_str_ok!(&memory, old_path, old_path_len) };
40 Span::current().record("old_path", old_path_str.as_str());
41 let mut new_path_str = unsafe { get_input_str_ok!(&memory, new_path, new_path_len) };
42 Span::current().record("new_path", new_path_str.as_str());
43
44 wasi_try_ok!(path_link_internal(
45 &mut ctx,
46 old_fd,
47 old_flags,
48 &old_path_str,
49 new_fd,
50 &new_path_str
51 ));
52 let env = ctx.data();
53
54 #[cfg(feature = "journal")]
55 if env.enable_journal {
56 JournalEffector::save_path_link(
57 &mut ctx,
58 old_fd,
59 old_flags,
60 old_path_str,
61 new_fd,
62 new_path_str,
63 )
64 .map_err(|err| {
65 tracing::error!("failed to save path hard link event - {}", err);
66 WasiError::Exit(ExitCode::from(Errno::Fault))
67 })?;
68 }
69
70 Ok(Errno::Success)
71}
72
73pub(crate) fn path_link_internal(
74 ctx: &mut FunctionEnvMut<'_, WasiEnv>,
75 old_fd: WasiFd,
76 old_flags: LookupFlags,
77 old_path: &str,
78 new_fd: WasiFd,
79 new_path: &str,
80) -> Result<(), Errno> {
81 let env = ctx.data();
82 let (memory, mut state, inodes) = unsafe { env.get_memory_and_wasi_state_and_inodes(&ctx, 0) };
83 let source_fd = state.fs.get_fd(old_fd)?;
84 let target_fd = state.fs.get_fd(new_fd)?;
85
86 if !source_fd.inner.rights.contains(Rights::PATH_LINK_SOURCE)
87 || !target_fd.inner.rights.contains(Rights::PATH_LINK_TARGET)
88 {
89 return Err(Errno::Access);
90 }
91
92 Span::current().record("old_path", old_path);
93 Span::current().record("new_path", new_path);
94
95 let source_inode = state.fs.get_inode_at_path(
96 inodes,
97 old_fd,
98 old_path,
99 old_flags & __WASI_LOOKUP_SYMLINK_FOLLOW != 0,
100 )?;
101 let target_path_arg = std::path::PathBuf::from(new_path);
102 let (target_parent_inode, new_entry_name) =
103 state
104 .fs
105 .get_parent_inode_at_path(inodes, new_fd, &target_path_arg, false)?;
106
107 if source_inode.stat.write().unwrap().st_nlink == Linkcount::MAX {
108 return Err(Errno::Mlink);
109 }
110 {
111 let mut guard = target_parent_inode.write();
112 match guard.deref_mut() {
113 Kind::Dir { entries, .. } => {
114 if entries.contains_key(&new_entry_name) {
115 return Err(Errno::Exist);
116 }
117 entries.insert(new_entry_name, source_inode.clone());
118 }
119 Kind::Root { .. } => return Err(Errno::Inval),
120 Kind::File { .. }
121 | Kind::Symlink { .. }
122 | Kind::Buffer { .. }
123 | Kind::Socket { .. }
124 | Kind::PipeTx { .. }
125 | Kind::PipeRx { .. }
126 | Kind::DuplexPipe { .. }
127 | Kind::EventNotifications { .. }
128 | Kind::Epoll { .. } => return Err(Errno::Notdir),
129 }
130 }
131 source_inode.stat.write().unwrap().st_nlink += 1;
132
133 Ok(())
134}