wasmer_wasix/syscalls/wasi/
path_symlink.rs

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