wasmer_wasix/syscalls/wasi/
path_link.rs

1use super::*;
2use crate::syscalls::*;
3
4/// ### `path_link()`
5/// Create a hard link
6/// Inputs:
7/// - `Fd old_fd`
8///     The directory relative to which the `old_path` is
9/// - `LookupFlags old_flags`
10///     Flags to control how `old_path` is understood
11/// - `const char *old_path`
12///     String containing the old file path
13/// - `u32 old_path_len`
14///     Length of the `old_path` string
15/// - `Fd new_fd`
16///     The directory relative to which the `new_path` is
17/// - `const char *new_path`
18///     String containing the new file path
19/// - `u32 old_path_len`
20///     Length of the `new_path` string
21#[instrument(level = "trace", skip_all, fields(%old_fd, %new_fd, old_path = field::Empty, new_path = field::Empty, follow_symlinks = false), ret)]
22pub fn path_link<M: MemorySize>(
23    mut ctx: FunctionEnvMut<'_, WasiEnv>,
24    old_fd: WasiFd,
25    old_flags: LookupFlags,
26    old_path: WasmPtr<u8, M>,
27    old_path_len: M::Offset,
28    new_fd: WasiFd,
29    new_path: WasmPtr<u8, M>,
30    new_path_len: M::Offset,
31) -> Result<Errno, WasiError> {
32    WasiEnv::do_pending_operations(&mut ctx)?;
33
34    if old_flags & __WASI_LOOKUP_SYMLINK_FOLLOW != 0 {
35        Span::current().record("follow_symlinks", true);
36    }
37    let env = ctx.data();
38    let (memory, mut state, inodes) = unsafe { env.get_memory_and_wasi_state_and_inodes(&ctx, 0) };
39    let mut old_path_str = unsafe { get_input_str_ok!(&memory, old_path, old_path_len) };
40    Span::current().record("old_path", old_path_str.as_str());
41    let mut new_path_str = unsafe { get_input_str_ok!(&memory, new_path, new_path_len) };
42    Span::current().record("new_path", new_path_str.as_str());
43
44    wasi_try_ok!(path_link_internal(
45        &mut ctx,
46        old_fd,
47        old_flags,
48        &old_path_str,
49        new_fd,
50        &new_path_str
51    ));
52    let env = ctx.data();
53
54    #[cfg(feature = "journal")]
55    if env.enable_journal {
56        JournalEffector::save_path_link(
57            &mut ctx,
58            old_fd,
59            old_flags,
60            old_path_str,
61            new_fd,
62            new_path_str,
63        )
64        .map_err(|err| {
65            tracing::error!("failed to save path hard link event - {}", err);
66            WasiError::Exit(ExitCode::from(Errno::Fault))
67        })?;
68    }
69
70    Ok(Errno::Success)
71}
72
73pub(crate) fn path_link_internal(
74    ctx: &mut FunctionEnvMut<'_, WasiEnv>,
75    old_fd: WasiFd,
76    old_flags: LookupFlags,
77    old_path: &str,
78    new_fd: WasiFd,
79    new_path: &str,
80) -> Result<(), Errno> {
81    let env = ctx.data();
82    let (memory, mut state, inodes) = unsafe { env.get_memory_and_wasi_state_and_inodes(&ctx, 0) };
83    let source_fd = state.fs.get_fd(old_fd)?;
84    let target_fd = state.fs.get_fd(new_fd)?;
85
86    if !source_fd.inner.rights.contains(Rights::PATH_LINK_SOURCE)
87        || !target_fd.inner.rights.contains(Rights::PATH_LINK_TARGET)
88    {
89        return Err(Errno::Access);
90    }
91
92    Span::current().record("old_path", old_path);
93    Span::current().record("new_path", new_path);
94
95    let source_inode = state.fs.get_inode_at_path(
96        inodes,
97        old_fd,
98        old_path,
99        old_flags & __WASI_LOOKUP_SYMLINK_FOLLOW != 0,
100    )?;
101    let target_path_arg = std::path::PathBuf::from(new_path);
102    let (target_parent_inode, new_entry_name) =
103        state
104            .fs
105            .get_parent_inode_at_path(inodes, new_fd, &target_path_arg, true)?;
106
107    if source_inode.stat.write().unwrap().st_nlink == Linkcount::MAX {
108        return Err(Errno::Mlink);
109    }
110
111    let materialized_paths = {
112        let source_guard = source_inode.read();
113        let target_parent_guard = target_parent_inode.read();
114
115        match (source_guard.deref(), target_parent_guard.deref()) {
116            (
117                Kind::File {
118                    path: source_path, ..
119                },
120                Kind::Dir {
121                    path: target_parent_path,
122                    entries,
123                    ..
124                },
125            ) => {
126                if entries.contains_key(&new_entry_name) {
127                    return Err(Errno::Exist);
128                }
129
130                Some((
131                    source_path.clone(),
132                    crate::fs::PosixPath::from_path(target_parent_path)
133                        .join(&crate::fs::PosixPath::new(&new_entry_name))
134                        .into_path_buf(),
135                ))
136            }
137            (_, Kind::Dir { entries, .. }) => {
138                if entries.contains_key(&new_entry_name) {
139                    return Err(Errno::Exist);
140                }
141
142                None
143            }
144            (_, Kind::Root { .. }) => return Err(Errno::Inval),
145            (
146                _,
147                Kind::File { .. }
148                | Kind::Symlink { .. }
149                | Kind::Buffer { .. }
150                | Kind::Socket { .. }
151                | Kind::PipeTx { .. }
152                | Kind::PipeRx { .. }
153                | Kind::DuplexPipe { .. }
154                | Kind::EventNotifications { .. }
155                | Kind::Epoll { .. },
156            ) => return Err(Errno::Notdir),
157        }
158    };
159
160    let target_inode = if let Some((source_path, target_path)) = materialized_paths {
161        match state.fs.root_fs.symlink_metadata(&target_path) {
162            Ok(_) => return Err(Errno::Exist),
163            Err(virtual_fs::FsError::EntryNotFound) => {}
164            Err(err) => return Err(fs_error_into_wasi_err(err)),
165        }
166
167        match state.fs.root_fs.hard_link(&source_path, &target_path) {
168            Ok(()) => {
169                let kind = Kind::File {
170                    handle: None,
171                    path: target_path.clone(),
172                    fd: None,
173                };
174                match state
175                    .fs
176                    .create_inode(inodes, kind, false, new_entry_name.clone())
177                {
178                    Ok(inode) => inode,
179                    Err(err) => {
180                        let _ = state.fs.root_fs.remove_file(&target_path);
181                        return Err(err);
182                    }
183                }
184            }
185            Err(virtual_fs::FsError::Unsupported) => {
186                // Some virtual filesystems cannot materialize hard links in
187                // their backing store. Preserve the old WASIX behavior there
188                // by linking the target directory entry to the same inode.
189                source_inode.clone()
190            }
191            Err(err) => return Err(fs_error_into_wasi_err(err)),
192        }
193    } else {
194        source_inode.clone()
195    };
196
197    {
198        let mut guard = target_parent_inode.write();
199        match guard.deref_mut() {
200            Kind::Dir { entries, .. } => {
201                if entries.contains_key(&new_entry_name) {
202                    return Err(Errno::Exist);
203                }
204                entries.insert(new_entry_name, target_inode.clone());
205            }
206            Kind::Root { .. } => return Err(Errno::Inval),
207            Kind::File { .. }
208            | Kind::Symlink { .. }
209            | Kind::Buffer { .. }
210            | Kind::Socket { .. }
211            | Kind::PipeTx { .. }
212            | Kind::PipeRx { .. }
213            | Kind::DuplexPipe { .. }
214            | Kind::EventNotifications { .. }
215            | Kind::Epoll { .. } => return Err(Errno::Notdir),
216        }
217    }
218    let new_link_count = {
219        let mut stat = source_inode.stat.write().unwrap();
220        stat.st_nlink += 1;
221        stat.st_nlink
222    };
223    target_inode.stat.write().unwrap().st_nlink = new_link_count;
224
225    Ok(())
226}