wasmer_wasix/syscalls/wasi/
path_rename.rs

1use std::path::PathBuf;
2
3use anyhow::Context;
4
5use super::*;
6use crate::syscalls::*;
7
8/// ### `path_rename()`
9/// Rename a file or directory
10/// Inputs:
11/// - `Fd old_fd`
12///     The base directory for `old_path`
13/// - `const char* old_path`
14///     Pointer to UTF8 bytes, the file to be renamed
15/// - `u32 old_path_len`
16///     The number of bytes to read from `old_path`
17/// - `Fd new_fd`
18///     The base directory for `new_path`
19/// - `const char* new_path`
20///     Pointer to UTF8 bytes, the new file name
21/// - `u32 new_path_len`
22///     The number of bytes to read from `new_path`
23#[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    // this is to be sure the source file is fetched from the filesystem if needed
80    let source_inode = wasi_try_ok!(state.fs.get_inode_at_path(
81        inodes,
82        source_fd,
83        source_path,
84        true
85    ));
86    // Create the destination inode if the file exists.
87    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 the above operation failed we have to revert the previous change and then fail
195                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    // The target entry is created, one way or the other
297    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    // If the rename replaced an existing destination entry, clear any stale
305    // ephemeral symlink mapping for that path.
306    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}