wasmer_wasix/syscalls/wasi/
path_rename.rs1use std::path::PathBuf;
2
3use anyhow::Context;
4
5use super::*;
6use crate::syscalls::*;
7
8#[instrument(level = "trace", skip_all, fields(%old_fd, %new_fd, old_path = field::Empty, new_path = field::Empty), ret)]
24pub fn path_rename<M: MemorySize>(
25 mut ctx: FunctionEnvMut<'_, WasiEnv>,
26 old_fd: WasiFd,
27 old_path: WasmPtr<u8, M>,
28 old_path_len: M::Offset,
29 new_fd: WasiFd,
30 new_path: WasmPtr<u8, M>,
31 new_path_len: M::Offset,
32) -> Result<Errno, WasiError> {
33 WasiEnv::do_pending_operations(&mut ctx)?;
34
35 let env = ctx.data();
36 let (memory, mut state, inodes) = unsafe { env.get_memory_and_wasi_state_and_inodes(&ctx, 0) };
37 let source_str = unsafe { get_input_str_ok!(&memory, old_path, old_path_len) };
38 Span::current().record("old_path", source_str.as_str());
39 let target_str = unsafe { get_input_str_ok!(&memory, new_path, new_path_len) };
40 Span::current().record("new_path", target_str.as_str());
41
42 let ret = path_rename_internal(&mut ctx, old_fd, &source_str, new_fd, &target_str)?;
43 let env = ctx.data();
44
45 if ret == Errno::Success {
46 #[cfg(feature = "journal")]
47 if env.enable_journal {
48 JournalEffector::save_path_rename(&mut ctx, old_fd, source_str, new_fd, target_str)
49 .map_err(|err| {
50 tracing::error!("failed to save path rename event - {}", err);
51 WasiError::Exit(ExitCode::from(Errno::Fault))
52 })?;
53 }
54 }
55 Ok(ret)
56}
57
58pub fn path_rename_internal(
59 ctx: &mut FunctionEnvMut<'_, WasiEnv>,
60 source_fd: WasiFd,
61 source_path: &str,
62 target_fd: WasiFd,
63 target_path: &str,
64) -> Result<Errno, WasiError> {
65 let env = ctx.data();
66 let (memory, mut state, inodes) = unsafe { env.get_memory_and_wasi_state_and_inodes(&ctx, 0) };
67
68 let mut moved_ephemeral_symlink = false;
69
70 {
71 let source_fd = wasi_try_ok!(state.fs.get_fd(source_fd));
72 if !source_fd.inner.rights.contains(Rights::PATH_RENAME_SOURCE) {
73 return Ok(Errno::Access);
74 }
75 let target_fd = wasi_try_ok!(state.fs.get_fd(target_fd));
76 if !target_fd.inner.rights.contains(Rights::PATH_RENAME_TARGET) {
77 return Ok(Errno::Access);
78 }
79 }
80
81 let source_inode = wasi_try_ok!(state.fs.get_inode_at_path(
83 inodes,
84 source_fd,
85 source_path,
86 true
87 ));
88 let _ = state
90 .fs
91 .get_inode_at_path(inodes, target_fd, target_path, true);
92 let (source_parent_inode, source_entry_name) = wasi_try_ok!(state.fs.get_parent_inode_at_path(
93 inodes,
94 source_fd,
95 Path::new(source_path),
96 true
97 ));
98 let (target_parent_inode, target_entry_name) = wasi_try_ok!(state.fs.get_parent_inode_at_path(
99 inodes,
100 target_fd,
101 Path::new(target_path),
102 true
103 ));
104 let source_guest_path = {
105 let guard = source_parent_inode.read();
106 match guard.deref() {
107 Kind::Dir { path, .. } => crate::fs::PosixPath::from_path(path)
108 .join(&crate::fs::PosixPath::new(&source_entry_name))
109 .into_path_buf(),
110 Kind::Root { .. } => return Ok(Errno::Notcapable),
111 Kind::Socket { .. }
112 | Kind::PipeTx { .. }
113 | Kind::PipeRx { .. }
114 | Kind::DuplexPipe { .. }
115 | Kind::EventNotifications { .. }
116 | Kind::Epoll { .. } => return Ok(Errno::Inval),
117 Kind::Symlink { .. } | Kind::File { .. } | Kind::Buffer { .. } => {
118 debug!("fatal internal logic error: parent of inode is not a directory");
119 return Ok(Errno::Inval);
120 }
121 }
122 };
123 let mut need_create = true;
124 let target_guest_path = {
125 let guard = target_parent_inode.read();
126 match guard.deref() {
127 Kind::Dir { entries, path, .. } => {
128 if entries.contains_key(&target_entry_name) {
129 need_create = false;
130 }
131 crate::fs::PosixPath::from_path(path)
132 .join(&crate::fs::PosixPath::new(&target_entry_name))
133 .into_path_buf()
134 }
135 Kind::Root { .. } => return Ok(Errno::Notcapable),
136 Kind::Socket { .. }
137 | Kind::PipeTx { .. }
138 | Kind::PipeRx { .. }
139 | Kind::DuplexPipe { .. }
140 | Kind::EventNotifications { .. }
141 | Kind::Epoll { .. } => return Ok(Errno::Inval),
142 Kind::Symlink { .. } | Kind::File { .. } | Kind::Buffer { .. } => {
143 debug!("fatal internal logic error: parent of inode is not a directory");
144 return Ok(Errno::Inval);
145 }
146 }
147 };
148
149 if source_parent_inode.ino() == target_parent_inode.ino()
150 && source_entry_name == target_entry_name
151 {
152 return Ok(Errno::Success);
153 }
154
155 let source_is_dir = {
156 let guard = source_inode.read();
157 matches!(guard.deref(), Kind::Dir { .. })
158 };
159 if source_is_dir
160 && crate::fs::PosixPath::from_path(&target_guest_path)
161 .strip_prefix(&crate::fs::PosixPath::from_path(&source_guest_path))
162 .is_some()
163 {
164 return Ok(Errno::Inval);
165 }
166
167 let source_entry = {
168 let mut guard = source_parent_inode.write();
169 match guard.deref_mut() {
170 Kind::Dir { entries, .. } => {
171 wasi_try_ok!(entries.remove(&source_entry_name).ok_or(Errno::Noent))
172 }
173 Kind::Root { .. } => return Ok(Errno::Notcapable),
174 Kind::Socket { .. }
175 | Kind::PipeRx { .. }
176 | Kind::PipeTx { .. }
177 | Kind::DuplexPipe { .. }
178 | Kind::EventNotifications { .. }
179 | Kind::Epoll { .. } => {
180 return Ok(Errno::Inval);
181 }
182 Kind::Symlink { .. } | Kind::File { .. } | Kind::Buffer { .. } => {
183 debug!("fatal internal logic error: parent of inode is not a directory");
184 return Ok(Errno::Inval);
185 }
186 }
187 };
188
189 {
190 let mut guard = source_entry.write();
191 match guard.deref_mut() {
192 Kind::File { path, .. } => {
193 let result = {
194 let path_clone = path.clone();
195 drop(guard);
196 let state = state;
197 let target_guest_path = target_guest_path.clone();
198 __asyncify_light(env, None, async move {
199 state.fs_rename(path_clone, &target_guest_path).await
200 })?
201 };
202 if let Err(e) = result {
204 let mut guard = source_parent_inode.write();
205 if let Kind::Dir { entries, .. } = guard.deref_mut() {
206 entries.insert(source_entry_name, source_entry);
207 return Ok(e);
208 }
209 } else {
210 let mut guard = source_entry.write();
211 if let Kind::File { path, .. } = guard.deref_mut() {
212 *path = target_guest_path.clone();
213 } else {
214 unreachable!()
215 }
216 }
217 }
218 Kind::Dir { path, .. } => {
219 let cloned_path = path.clone();
220 let res = {
221 let state = state;
222 let target_guest_path = target_guest_path.clone();
223 __asyncify_light(env, None, async move {
224 state.fs_rename(cloned_path, &target_guest_path).await
225 })?
226 };
227 if let Err(e) = res {
228 return Ok(e);
229 }
230 {
231 let source_dir_path = path.clone();
232 drop(guard);
233 rename_inode_tree(&source_entry, &source_dir_path, &target_guest_path);
234 }
235 }
236 Kind::Symlink {
237 path_to_symlink,
238 relative_path,
239 ..
240 } => {
241 let is_ephemeral = state
242 .fs
243 .ephemeral_symlink_at(source_guest_path.as_path())
244 .is_some();
245 let res = {
246 let state = state;
247 let from = source_guest_path.clone();
248 let to = target_guest_path.clone();
249 __asyncify_light(env, None, async move { state.fs_rename(from, to).await })?
250 };
251 match (res, is_ephemeral) {
252 (Ok(()), _) | (Err(Errno::Noent), true) => {}
253 (Err(e), _) => {
254 let mut guard = source_parent_inode.write();
255 if let Kind::Dir { entries, .. } = guard.deref_mut() {
256 entries.insert(source_entry_name, source_entry.clone());
257 return Ok(e);
258 }
259 }
260 }
261
262 let new_path_to_symlink = state
263 .fs
264 .rebase_symlink_location(target_guest_path.as_path());
265 *path_to_symlink = new_path_to_symlink.clone();
266 if is_ephemeral {
267 state.fs.move_ephemeral_symlink(
268 source_guest_path.as_path(),
269 target_guest_path.as_path(),
270 new_path_to_symlink,
271 relative_path.clone(),
272 );
273 moved_ephemeral_symlink = true;
274 }
275 }
276 Kind::Buffer { .. }
277 | Kind::Socket { .. }
278 | Kind::PipeTx { .. }
279 | Kind::PipeRx { .. }
280 | Kind::DuplexPipe { .. }
281 | Kind::Epoll { .. }
282 | Kind::EventNotifications { .. } => {}
283 Kind::Root { .. } => unreachable!("The root can not be moved"),
284 }
285 }
286
287 let source_size = source_entry.stat.read().unwrap().st_size;
288
289 if need_create {
290 let mut guard = target_parent_inode.write();
291 if let Kind::Dir { entries, .. } = guard.deref_mut() {
292 let result = entries.insert(target_entry_name.clone(), source_entry);
293 assert!(
294 result.is_none(),
295 "fatal error: race condition on filesystem detected or internal logic error"
296 );
297 }
298 }
299
300 let target_inode = state
302 .fs
303 .get_inode_at_path(inodes, target_fd, target_path, true)
304 .expect("Expected target inode to exist, and it's too late to safely fail");
305 *target_inode.name.write().unwrap() = target_entry_name.into();
306 target_inode.stat.write().unwrap().st_size = source_size;
307
308 if !moved_ephemeral_symlink {
311 state
312 .fs
313 .unregister_ephemeral_symlink(target_guest_path.as_path());
314 }
315
316 Ok(Errno::Success)
317}
318
319fn rename_inode_tree(inode: &InodeGuard, source_dir_path: &Path, target_dir_path: &Path) {
320 let children;
321
322 let mut guard = inode.write();
323 match guard.deref_mut() {
324 Kind::File { path, .. } => {
325 *path = adjust_path(path, source_dir_path, target_dir_path);
326 return;
327 }
328 Kind::Dir { path, entries, .. } => {
329 *path = adjust_path(path, source_dir_path, target_dir_path);
330 children = entries.values().cloned().collect::<Vec<_>>();
331 }
332 _ => return,
333 }
334 drop(guard);
335
336 for child in children {
337 rename_inode_tree(&child, source_dir_path, target_dir_path);
338 }
339}
340
341fn adjust_path(path: &Path, source_dir_path: &Path, target_dir_path: &Path) -> PathBuf {
342 let path = crate::fs::PosixPath::from_path(path);
343 let source_dir_path = crate::fs::PosixPath::from_path(source_dir_path);
344 let relative_path = path
345 .strip_prefix(&source_dir_path)
346 .with_context(|| format!("Expected path {path:?} to be a subpath of {source_dir_path:?}"))
347 .expect("Fatal filesystem error");
348 crate::fs::PosixPath::from_path(target_dir_path)
349 .join(&relative_path)
350 .into_path_buf()
351}