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, true)?;
106
107 if source_inode.stat.write().unwrap().st_nlink == Linkcount::MAX {
108 return Err(Errno::Mlink);
109 }
110
111 let materialized_paths = {
112 let source_guard = source_inode.read();
113 let target_parent_guard = target_parent_inode.read();
114
115 match (source_guard.deref(), target_parent_guard.deref()) {
116 (
117 Kind::File {
118 path: source_path, ..
119 },
120 Kind::Dir {
121 path: target_parent_path,
122 entries,
123 ..
124 },
125 ) => {
126 if entries.contains_key(&new_entry_name) {
127 return Err(Errno::Exist);
128 }
129
130 Some((
131 source_path.clone(),
132 crate::fs::PosixPath::from_path(target_parent_path)
133 .join(&crate::fs::PosixPath::new(&new_entry_name))
134 .into_path_buf(),
135 ))
136 }
137 (_, Kind::Dir { entries, .. }) => {
138 if entries.contains_key(&new_entry_name) {
139 return Err(Errno::Exist);
140 }
141
142 None
143 }
144 (_, Kind::Root { .. }) => return Err(Errno::Inval),
145 (
146 _,
147 Kind::File { .. }
148 | Kind::Symlink { .. }
149 | Kind::Buffer { .. }
150 | Kind::Socket { .. }
151 | Kind::PipeTx { .. }
152 | Kind::PipeRx { .. }
153 | Kind::DuplexPipe { .. }
154 | Kind::EventNotifications { .. }
155 | Kind::Epoll { .. },
156 ) => return Err(Errno::Notdir),
157 }
158 };
159
160 let target_inode = if let Some((source_path, target_path)) = materialized_paths {
161 match state.fs.root_fs.symlink_metadata(&target_path) {
162 Ok(_) => return Err(Errno::Exist),
163 Err(virtual_fs::FsError::EntryNotFound) => {}
164 Err(err) => return Err(fs_error_into_wasi_err(err)),
165 }
166
167 match state.fs.root_fs.hard_link(&source_path, &target_path) {
168 Ok(()) => {
169 let kind = Kind::File {
170 handle: None,
171 path: target_path.clone(),
172 fd: None,
173 };
174 match state
175 .fs
176 .create_inode(inodes, kind, false, new_entry_name.clone())
177 {
178 Ok(inode) => inode,
179 Err(err) => {
180 let _ = state.fs.root_fs.remove_file(&target_path);
181 return Err(err);
182 }
183 }
184 }
185 Err(virtual_fs::FsError::Unsupported) => {
186 source_inode.clone()
190 }
191 Err(err) => return Err(fs_error_into_wasi_err(err)),
192 }
193 } else {
194 source_inode.clone()
195 };
196
197 {
198 let mut guard = target_parent_inode.write();
199 match guard.deref_mut() {
200 Kind::Dir { entries, .. } => {
201 if entries.contains_key(&new_entry_name) {
202 return Err(Errno::Exist);
203 }
204 entries.insert(new_entry_name, target_inode.clone());
205 }
206 Kind::Root { .. } => return Err(Errno::Inval),
207 Kind::File { .. }
208 | Kind::Symlink { .. }
209 | Kind::Buffer { .. }
210 | Kind::Socket { .. }
211 | Kind::PipeTx { .. }
212 | Kind::PipeRx { .. }
213 | Kind::DuplexPipe { .. }
214 | Kind::EventNotifications { .. }
215 | Kind::Epoll { .. } => return Err(Errno::Notdir),
216 }
217 }
218 let new_link_count = {
219 let mut stat = source_inode.stat.write().unwrap();
220 stat.st_nlink += 1;
221 stat.st_nlink
222 };
223 target_inode.stat.write().unwrap().st_nlink = new_link_count;
224
225 Ok(())
226}