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 {
69 let source_fd = wasi_try_ok!(state.fs.get_fd(source_fd));
70 if !source_fd.inner.rights.contains(Rights::PATH_RENAME_SOURCE) {
71 return Ok(Errno::Access);
72 }
73 let target_fd = wasi_try_ok!(state.fs.get_fd(target_fd));
74 if !target_fd.inner.rights.contains(Rights::PATH_RENAME_TARGET) {
75 return Ok(Errno::Access);
76 }
77 }
78
79 let source_inode = wasi_try_ok!(state.fs.get_inode_at_path(
81 inodes,
82 source_fd,
83 source_path,
84 true
85 ));
86 let _ = state
88 .fs
89 .get_inode_at_path(inodes, target_fd, target_path, true);
90 let (source_parent_inode, source_entry_name) = wasi_try_ok!(state.fs.get_parent_inode_at_path(
91 inodes,
92 source_fd,
93 Path::new(source_path),
94 true
95 ));
96 let (target_parent_inode, target_entry_name) = wasi_try_ok!(state.fs.get_parent_inode_at_path(
97 inodes,
98 target_fd,
99 Path::new(target_path),
100 true
101 ));
102 let host_adjusted_source_path = {
103 let guard = source_parent_inode.read();
104 match guard.deref() {
105 Kind::Dir { path, .. } => path.join(&source_entry_name),
106 Kind::Root { .. } => return Ok(Errno::Notcapable),
107 Kind::Socket { .. }
108 | Kind::PipeTx { .. }
109 | Kind::PipeRx { .. }
110 | Kind::DuplexPipe { .. }
111 | Kind::EventNotifications { .. }
112 | Kind::Epoll { .. } => return Ok(Errno::Inval),
113 Kind::Symlink { .. } | Kind::File { .. } | Kind::Buffer { .. } => {
114 debug!("fatal internal logic error: parent of inode is not a directory");
115 return Ok(Errno::Inval);
116 }
117 }
118 };
119 let mut need_create = true;
120 let host_adjusted_target_path = {
121 let guard = target_parent_inode.read();
122 match guard.deref() {
123 Kind::Dir { entries, path, .. } => {
124 if entries.contains_key(&target_entry_name) {
125 need_create = false;
126 }
127 path.join(&target_entry_name)
128 }
129 Kind::Root { .. } => return Ok(Errno::Notcapable),
130 Kind::Socket { .. }
131 | Kind::PipeTx { .. }
132 | Kind::PipeRx { .. }
133 | Kind::DuplexPipe { .. }
134 | Kind::EventNotifications { .. }
135 | Kind::Epoll { .. } => return Ok(Errno::Inval),
136 Kind::Symlink { .. } | Kind::File { .. } | Kind::Buffer { .. } => {
137 debug!("fatal internal logic error: parent of inode is not a directory");
138 return Ok(Errno::Inval);
139 }
140 }
141 };
142
143 if source_parent_inode.ino() == target_parent_inode.ino()
144 && source_entry_name == target_entry_name
145 {
146 return Ok(Errno::Success);
147 }
148
149 let source_is_dir = {
150 let guard = source_inode.read();
151 matches!(guard.deref(), Kind::Dir { .. })
152 };
153 if source_is_dir && host_adjusted_target_path.starts_with(&host_adjusted_source_path) {
154 return Ok(Errno::Inval);
155 }
156
157 let source_entry = {
158 let mut guard = source_parent_inode.write();
159 match guard.deref_mut() {
160 Kind::Dir { entries, .. } => {
161 wasi_try_ok!(entries.remove(&source_entry_name).ok_or(Errno::Noent))
162 }
163 Kind::Root { .. } => return Ok(Errno::Notcapable),
164 Kind::Socket { .. }
165 | Kind::PipeRx { .. }
166 | Kind::PipeTx { .. }
167 | Kind::DuplexPipe { .. }
168 | Kind::EventNotifications { .. }
169 | Kind::Epoll { .. } => {
170 return Ok(Errno::Inval);
171 }
172 Kind::Symlink { .. } | Kind::File { .. } | Kind::Buffer { .. } => {
173 debug!("fatal internal logic error: parent of inode is not a directory");
174 return Ok(Errno::Inval);
175 }
176 }
177 };
178
179 {
180 let mut guard = source_entry.write();
181 match guard.deref_mut() {
182 Kind::File { path, .. } => {
183 let result = {
184 let path_clone = path.clone();
185 drop(guard);
186 let state = state;
187 let host_adjusted_target_path = host_adjusted_target_path.clone();
188 __asyncify_light(env, None, async move {
189 state
190 .fs_rename(path_clone, &host_adjusted_target_path)
191 .await
192 })?
193 };
194 if let Err(e) = result {
196 let mut guard = source_parent_inode.write();
197 if let Kind::Dir { entries, .. } = guard.deref_mut() {
198 entries.insert(source_entry_name, source_entry);
199 return Ok(e);
200 }
201 } else {
202 let mut guard = source_entry.write();
203 if let Kind::File { path, .. } = guard.deref_mut() {
204 *path = host_adjusted_target_path.clone();
205 } else {
206 unreachable!()
207 }
208 }
209 }
210 Kind::Dir { path, .. } => {
211 let cloned_path = path.clone();
212 let res = {
213 let state = state;
214 let host_adjusted_target_path = host_adjusted_target_path.clone();
215 __asyncify_light(env, None, async move {
216 state
217 .fs_rename(cloned_path, &host_adjusted_target_path)
218 .await
219 })?
220 };
221 if let Err(e) = res {
222 return Ok(e);
223 }
224 {
225 let source_dir_path = path.clone();
226 drop(guard);
227 rename_inode_tree(&source_entry, &source_dir_path, &host_adjusted_target_path);
228 }
229 }
230 Kind::Symlink {
231 base_po_dir,
232 path_to_symlink,
233 relative_path,
234 } => {
235 let is_ephemeral = state
236 .fs
237 .ephemeral_symlink_at(host_adjusted_source_path.as_path())
238 .is_some();
239 let res = {
240 let state = state;
241 let from = host_adjusted_source_path.clone();
242 let to = host_adjusted_target_path.clone();
243 __asyncify_light(env, None, async move { state.fs_rename(from, to).await })?
244 };
245 match (res, is_ephemeral) {
246 (Ok(()), _) | (Err(Errno::Noent), true) => {}
247 (Err(e), _) => {
248 let mut guard = source_parent_inode.write();
249 if let Kind::Dir { entries, .. } = guard.deref_mut() {
250 entries.insert(source_entry_name, source_entry.clone());
251 return Ok(e);
252 }
253 }
254 }
255
256 let (new_base_po_dir, new_path_to_symlink) =
257 wasi_try_ok!(state.fs.path_into_pre_open_and_relative_path_owned(
258 host_adjusted_target_path.as_path()
259 ));
260 *base_po_dir = new_base_po_dir;
261 *path_to_symlink = new_path_to_symlink.clone();
262 if is_ephemeral {
263 state.fs.move_ephemeral_symlink(
264 host_adjusted_source_path.as_path(),
265 host_adjusted_target_path.as_path(),
266 new_base_po_dir,
267 new_path_to_symlink,
268 relative_path.clone(),
269 );
270 }
271 }
272 Kind::Buffer { .. }
273 | Kind::Socket { .. }
274 | Kind::PipeTx { .. }
275 | Kind::PipeRx { .. }
276 | Kind::DuplexPipe { .. }
277 | Kind::Epoll { .. }
278 | Kind::EventNotifications { .. } => {}
279 Kind::Root { .. } => unreachable!("The root can not be moved"),
280 }
281 }
282
283 let source_size = source_entry.stat.read().unwrap().st_size;
284
285 if need_create {
286 let mut guard = target_parent_inode.write();
287 if let Kind::Dir { entries, .. } = guard.deref_mut() {
288 let result = entries.insert(target_entry_name.clone(), source_entry);
289 assert!(
290 result.is_none(),
291 "fatal error: race condition on filesystem detected or internal logic error"
292 );
293 }
294 }
295
296 let target_inode = state
298 .fs
299 .get_inode_at_path(inodes, target_fd, target_path, true)
300 .expect("Expected target inode to exist, and it's too late to safely fail");
301 *target_inode.name.write().unwrap() = target_entry_name.into();
302 target_inode.stat.write().unwrap().st_size = source_size;
303
304 state
307 .fs
308 .unregister_ephemeral_symlink(host_adjusted_target_path.as_path());
309
310 Ok(Errno::Success)
311}
312
313fn rename_inode_tree(inode: &InodeGuard, source_dir_path: &Path, target_dir_path: &Path) {
314 let children;
315
316 let mut guard = inode.write();
317 match guard.deref_mut() {
318 Kind::File { path, .. } => {
319 *path = adjust_path(path, source_dir_path, target_dir_path);
320 return;
321 }
322 Kind::Dir { path, entries, .. } => {
323 *path = adjust_path(path, source_dir_path, target_dir_path);
324 children = entries.values().cloned().collect::<Vec<_>>();
325 }
326 _ => return,
327 }
328 drop(guard);
329
330 for child in children {
331 rename_inode_tree(&child, source_dir_path, target_dir_path);
332 }
333}
334
335fn adjust_path(path: &Path, source_dir_path: &Path, target_dir_path: &Path) -> PathBuf {
336 let relative_path = path
337 .strip_prefix(source_dir_path)
338 .with_context(|| format!("Expected path {path:?} to be a subpath of {source_dir_path:?}"))
339 .expect("Fatal filesystem error");
340 target_dir_path.join(relative_path)
341}