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 wasi_try_ok!(
81 state
82 .fs
83 .get_inode_at_path(inodes, source_fd, source_path, true)
84 );
85 let _ = state
87 .fs
88 .get_inode_at_path(inodes, target_fd, target_path, true);
89 let (source_parent_inode, source_entry_name) = wasi_try_ok!(state.fs.get_parent_inode_at_path(
90 inodes,
91 source_fd,
92 Path::new(source_path),
93 true
94 ));
95 let (target_parent_inode, target_entry_name) = wasi_try_ok!(state.fs.get_parent_inode_at_path(
96 inodes,
97 target_fd,
98 Path::new(target_path),
99 true
100 ));
101 let mut need_create = true;
102 let host_adjusted_target_path = {
103 let guard = target_parent_inode.read();
104 match guard.deref() {
105 Kind::Dir { entries, path, .. } => {
106 if entries.contains_key(&target_entry_name) {
107 need_create = false;
108 }
109 path.join(&target_entry_name)
110 }
111 Kind::Root { .. } => return Ok(Errno::Notcapable),
112 Kind::Socket { .. }
113 | Kind::PipeTx { .. }
114 | Kind::PipeRx { .. }
115 | Kind::DuplexPipe { .. }
116 | Kind::EventNotifications { .. }
117 | Kind::Epoll { .. } => return Ok(Errno::Inval),
118 Kind::Symlink { .. } | Kind::File { .. } | Kind::Buffer { .. } => {
119 debug!("fatal internal logic error: parent of inode is not a directory");
120 return Ok(Errno::Inval);
121 }
122 }
123 };
124
125 let source_entry = {
126 let mut guard = source_parent_inode.write();
127 match guard.deref_mut() {
128 Kind::Dir { entries, .. } => {
129 wasi_try_ok!(entries.remove(&source_entry_name).ok_or(Errno::Noent))
130 }
131 Kind::Root { .. } => return Ok(Errno::Notcapable),
132 Kind::Socket { .. }
133 | Kind::PipeRx { .. }
134 | Kind::PipeTx { .. }
135 | Kind::DuplexPipe { .. }
136 | Kind::EventNotifications { .. }
137 | Kind::Epoll { .. } => {
138 return Ok(Errno::Inval);
139 }
140 Kind::Symlink { .. } | Kind::File { .. } | Kind::Buffer { .. } => {
141 debug!("fatal internal logic error: parent of inode is not a directory");
142 return Ok(Errno::Inval);
143 }
144 }
145 };
146
147 {
148 let mut guard = source_entry.write();
149 match guard.deref_mut() {
150 Kind::File { path, .. } => {
151 let result = {
152 let path_clone = path.clone();
153 drop(guard);
154 let state = state;
155 let host_adjusted_target_path = host_adjusted_target_path.clone();
156 __asyncify_light(env, None, async move {
157 state
158 .fs_rename(path_clone, &host_adjusted_target_path)
159 .await
160 })?
161 };
162 if let Err(e) = result {
164 let mut guard = source_parent_inode.write();
165 if let Kind::Dir { entries, .. } = guard.deref_mut() {
166 entries.insert(source_entry_name, source_entry);
167 return Ok(e);
168 }
169 } else {
170 let mut guard = source_entry.write();
171 if let Kind::File { path, .. } = guard.deref_mut() {
172 *path = host_adjusted_target_path;
173 } else {
174 unreachable!()
175 }
176 }
177 }
178 Kind::Dir { path, .. } => {
179 let cloned_path = path.clone();
180 let res = {
181 let state = state;
182 let host_adjusted_target_path = host_adjusted_target_path.clone();
183 __asyncify_light(env, None, async move {
184 state
185 .fs_rename(cloned_path, &host_adjusted_target_path)
186 .await
187 })?
188 };
189 if let Err(e) = res {
190 return Ok(e);
191 }
192 {
193 let source_dir_path = path.clone();
194 drop(guard);
195 rename_inode_tree(&source_entry, &source_dir_path, &host_adjusted_target_path);
196 }
197 }
198 Kind::Buffer { .. }
199 | Kind::Symlink { .. }
200 | Kind::Socket { .. }
201 | Kind::PipeTx { .. }
202 | Kind::PipeRx { .. }
203 | Kind::DuplexPipe { .. }
204 | Kind::Epoll { .. }
205 | Kind::EventNotifications { .. } => {}
206 Kind::Root { .. } => unreachable!("The root can not be moved"),
207 }
208 }
209
210 let source_size = source_entry.stat.read().unwrap().st_size;
211
212 if need_create {
213 let mut guard = target_parent_inode.write();
214 if let Kind::Dir { entries, .. } = guard.deref_mut() {
215 let result = entries.insert(target_entry_name.clone(), source_entry);
216 assert!(
217 result.is_none(),
218 "fatal error: race condition on filesystem detected or internal logic error"
219 );
220 }
221 }
222
223 let target_inode = state
225 .fs
226 .get_inode_at_path(inodes, target_fd, target_path, true)
227 .expect("Expected target inode to exist, and it's too late to safely fail");
228 *target_inode.name.write().unwrap() = target_entry_name.into();
229 target_inode.stat.write().unwrap().st_size = source_size;
230
231 Ok(Errno::Success)
232}
233
234fn rename_inode_tree(inode: &InodeGuard, source_dir_path: &Path, target_dir_path: &Path) {
235 let children;
236
237 let mut guard = inode.write();
238 match guard.deref_mut() {
239 Kind::File { path, .. } => {
240 *path = adjust_path(path, source_dir_path, target_dir_path);
241 return;
242 }
243 Kind::Dir { path, entries, .. } => {
244 *path = adjust_path(path, source_dir_path, target_dir_path);
245 children = entries.values().cloned().collect::<Vec<_>>();
246 }
247 _ => return,
248 }
249 drop(guard);
250
251 for child in children {
252 rename_inode_tree(&child, source_dir_path, target_dir_path);
253 }
254}
255
256fn adjust_path(path: &Path, source_dir_path: &Path, target_dir_path: &Path) -> PathBuf {
257 let relative_path = path
258 .strip_prefix(source_dir_path)
259 .with_context(|| format!("Expected path {path:?} to be a subpath of {source_dir_path:?}"))
260 .expect("Fatal filesystem error");
261 target_dir_path.join(relative_path)
262}