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    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    // this is to be sure the source file is fetched from the filesystem if needed
82    let source_inode = wasi_try_ok!(state.fs.get_inode_at_path(
83        inodes,
84        source_fd,
85        source_path,
86        true
87    ));
88    // Create the destination inode if the file exists.
89    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 the above operation failed we have to revert the previous change and then fail
203                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    // The target entry is created, one way or the other
301    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 the rename replaced an existing destination entry, clear any stale
309    // ephemeral symlink mapping for that path.
310    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}