wasmer_wasix/syscalls/wasi/
path_symlink.rs

1use super::*;
2use crate::fs::WasiFsRoot;
3use crate::syscalls::*;
4use virtual_fs::FsError;
5
6/// ### `path_symlink()`
7/// Create a symlink
8/// Inputs:
9/// - `const char *old_path`
10///     Array of UTF-8 bytes representing the source path
11/// - `u32 old_path_len`
12///     The number of bytes to read from `old_path`
13/// - `Fd fd`
14///     The base directory from which the paths are understood
15/// - `const char *new_path`
16///     Array of UTF-8 bytes representing the target path
17/// - `u32 new_path_len`
18///     The number of bytes to read from `new_path`
19#[instrument(level = "trace", skip_all, fields(%fd, old_path = field::Empty, new_path = field::Empty), ret)]
20pub fn path_symlink<M: MemorySize>(
21    mut ctx: FunctionEnvMut<'_, WasiEnv>,
22    old_path: WasmPtr<u8, M>,
23    old_path_len: M::Offset,
24    fd: WasiFd,
25    new_path: WasmPtr<u8, M>,
26    new_path_len: M::Offset,
27) -> Result<Errno, WasiError> {
28    WasiEnv::do_pending_operations(&mut ctx)?;
29
30    let env = ctx.data();
31    let (memory, mut state, inodes) = unsafe { env.get_memory_and_wasi_state_and_inodes(&ctx, 0) };
32    let old_path_str = unsafe { get_input_str_ok!(&memory, old_path, old_path_len) };
33    Span::current().record("old_path", old_path_str.as_str());
34    let new_path_str = unsafe { get_input_str_ok!(&memory, new_path, new_path_len) };
35    Span::current().record("new_path", new_path_str.as_str());
36
37    wasi_try_ok!(path_symlink_internal(
38        &mut ctx,
39        &old_path_str,
40        fd,
41        &new_path_str
42    ));
43    let env = ctx.data();
44
45    #[cfg(feature = "journal")]
46    if env.enable_journal {
47        JournalEffector::save_path_symlink(&mut ctx, old_path_str, fd, new_path_str).map_err(
48            |err| {
49                tracing::error!("failed to save path symbolic link event - {}", err);
50                WasiError::Exit(ExitCode::from(Errno::Fault))
51            },
52        )?;
53    }
54
55    Ok(Errno::Success)
56}
57
58pub fn path_symlink_internal(
59    ctx: &mut FunctionEnvMut<'_, WasiEnv>,
60    old_path: &str,
61    fd: WasiFd,
62    new_path: &str,
63) -> Result<(), Errno> {
64    let env = ctx.data();
65    let (memory, mut state, inodes) = unsafe { env.get_memory_and_wasi_state_and_inodes(&ctx, 0) };
66
67    let base_fd = state.fs.get_fd(fd)?;
68    if !base_fd.inner.rights.contains(Rights::PATH_SYMLINK) {
69        return Err(Errno::Access);
70    }
71
72    let new_path_path = std::path::Path::new(new_path);
73    let (target_parent_inode, entry_name) =
74        state
75            .fs
76            .get_parent_inode_at_path(inodes, fd, new_path_path, true)?;
77
78    let symlink_path = {
79        let guard = target_parent_inode.read();
80        match guard.deref() {
81            Kind::Dir { path, .. } => {
82                let mut symlink_path = path.clone();
83                symlink_path.push(&entry_name);
84                symlink_path
85            }
86            Kind::Root { .. } => {
87                let mut symlink_path = std::path::PathBuf::from("/");
88                symlink_path.push(&entry_name);
89                symlink_path
90            }
91            _ => unreachable!("parent inode should be a directory"),
92        }
93    };
94
95    // Resolve symlink location to (preopen fd, relative path within that preopen)
96    // so runtime-created symlinks behave the same as symlinks discovered via readlink().
97    let (base_po_dir, path_to_symlink) = state
98        .fs
99        .path_into_pre_open_and_relative_path_owned(&symlink_path)?;
100    let relative_path = std::path::PathBuf::from(old_path);
101
102    // short circuit if anything is wrong, before we create an inode
103    {
104        let guard = target_parent_inode.read();
105        match guard.deref() {
106            Kind::Dir { entries, .. } => {
107                if entries.contains_key(&entry_name) {
108                    return Err(Errno::Exist);
109                }
110            }
111            Kind::Root { .. } => return Err(Errno::Notcapable),
112            Kind::Socket { .. }
113            | Kind::PipeRx { .. }
114            | Kind::PipeTx { .. }
115            | Kind::DuplexPipe { .. }
116            | Kind::EventNotifications { .. }
117            | Kind::Epoll { .. } => return Err(Errno::Inval),
118            Kind::File { .. } | Kind::Symlink { .. } | Kind::Buffer { .. } => {
119                unreachable!("get_parent_inode_at_path returned something other than a Dir or Root")
120            }
121        }
122    }
123
124    let source_path = std::path::Path::new(old_path);
125    let target_path = symlink_path.as_path();
126    let persisted_in_backing_fs = match &state.fs.root_fs {
127        WasiFsRoot::Sandbox(fs) => fs.create_symlink(source_path, target_path),
128        WasiFsRoot::Overlay(overlay) => overlay.primary().create_symlink(source_path, target_path),
129        WasiFsRoot::Backing(_) => Err(FsError::Unsupported),
130    };
131
132    let needs_ephemeral_fallback = match persisted_in_backing_fs {
133        Ok(()) => false,
134        Err(FsError::Unsupported) => true,
135        Err(err) => return Err(fs_error_into_wasi_err(err)),
136    };
137
138    let kind = Kind::Symlink {
139        base_po_dir,
140        path_to_symlink: path_to_symlink.clone(),
141        relative_path: relative_path.clone(),
142    };
143    let new_inode =
144        state
145            .fs
146            .create_inode_with_default_stat(inodes, kind, false, entry_name.clone().into());
147
148    {
149        let mut guard = target_parent_inode.write();
150        if let Kind::Dir { entries, .. } = guard.deref_mut() {
151            entries.insert(entry_name, new_inode);
152        }
153    }
154
155    // Keep transient map in sync with the backing outcome.
156    if needs_ephemeral_fallback {
157        state.fs.register_ephemeral_symlink(
158            symlink_path,
159            base_po_dir,
160            path_to_symlink,
161            relative_path,
162        );
163    } else {
164        state.fs.unregister_ephemeral_symlink(target_path);
165    }
166
167    Ok(())
168}