use super::*;
use crate::syscalls::*;
#[instrument(level = "trace", skip_all, fields(%old_fd, %new_fd, old_path = field::Empty, new_path = field::Empty, follow_symlinks = false), ret)]
pub fn path_link<M: MemorySize>(
mut ctx: FunctionEnvMut<'_, WasiEnv>,
old_fd: WasiFd,
old_flags: LookupFlags,
old_path: WasmPtr<u8, M>,
old_path_len: M::Offset,
new_fd: WasiFd,
new_path: WasmPtr<u8, M>,
new_path_len: M::Offset,
) -> Result<Errno, WasiError> {
WasiEnv::do_pending_operations(&mut ctx)?;
if old_flags & __WASI_LOOKUP_SYMLINK_FOLLOW != 0 {
Span::current().record("follow_symlinks", true);
}
let env = ctx.data();
let (memory, mut state, inodes) = unsafe { env.get_memory_and_wasi_state_and_inodes(&ctx, 0) };
let mut old_path_str = unsafe { get_input_str_ok!(&memory, old_path, old_path_len) };
Span::current().record("old_path", old_path_str.as_str());
let mut new_path_str = unsafe { get_input_str_ok!(&memory, new_path, new_path_len) };
Span::current().record("new_path", new_path_str.as_str());
wasi_try_ok!(path_link_internal(
&mut ctx,
old_fd,
old_flags,
&old_path_str,
new_fd,
&new_path_str
));
let env = ctx.data();
#[cfg(feature = "journal")]
if env.enable_journal {
JournalEffector::save_path_link(
&mut ctx,
old_fd,
old_flags,
old_path_str,
new_fd,
new_path_str,
)
.map_err(|err| {
tracing::error!("failed to save path hard link event - {}", err);
WasiError::Exit(ExitCode::from(Errno::Fault))
})?;
}
Ok(Errno::Success)
}
pub(crate) fn path_link_internal(
ctx: &mut FunctionEnvMut<'_, WasiEnv>,
old_fd: WasiFd,
old_flags: LookupFlags,
old_path: &str,
new_fd: WasiFd,
new_path: &str,
) -> Result<(), Errno> {
let env = ctx.data();
let (memory, mut state, inodes) = unsafe { env.get_memory_and_wasi_state_and_inodes(&ctx, 0) };
let source_fd = state.fs.get_fd(old_fd)?;
let target_fd = state.fs.get_fd(new_fd)?;
if !source_fd.inner.rights.contains(Rights::PATH_LINK_SOURCE)
|| !target_fd.inner.rights.contains(Rights::PATH_LINK_TARGET)
{
return Err(Errno::Access);
}
Span::current().record("old_path", old_path);
Span::current().record("new_path", new_path);
let source_inode = state.fs.get_inode_at_path(
inodes,
old_fd,
old_path,
old_flags & __WASI_LOOKUP_SYMLINK_FOLLOW != 0,
)?;
let target_path_arg = std::path::PathBuf::from(new_path);
let (target_parent_inode, new_entry_name) =
state
.fs
.get_parent_inode_at_path(inodes, new_fd, &target_path_arg, true)?;
if source_inode.stat.write().unwrap().st_nlink == Linkcount::MAX {
return Err(Errno::Mlink);
}
let materialized_paths = {
let source_guard = source_inode.read();
let target_parent_guard = target_parent_inode.read();
match (source_guard.deref(), target_parent_guard.deref()) {
(
Kind::File {
path: source_path, ..
},
Kind::Dir {
path: target_parent_path,
entries,
..
},
) => {
if entries.contains_key(&new_entry_name) {
return Err(Errno::Exist);
}
Some((
source_path.clone(),
crate::fs::PosixPath::from_path(target_parent_path)
.join(&crate::fs::PosixPath::new(&new_entry_name))
.into_path_buf(),
))
}
(_, Kind::Dir { entries, .. }) => {
if entries.contains_key(&new_entry_name) {
return Err(Errno::Exist);
}
None
}
(_, Kind::Root { .. }) => return Err(Errno::Inval),
(
_,
Kind::File { .. }
| Kind::Symlink { .. }
| Kind::Buffer { .. }
| Kind::Socket { .. }
| Kind::PipeTx { .. }
| Kind::PipeRx { .. }
| Kind::DuplexPipe { .. }
| Kind::EventNotifications { .. }
| Kind::Epoll { .. },
) => return Err(Errno::Notdir),
}
};
let target_inode = if let Some((source_path, target_path)) = materialized_paths {
match state.fs.root_fs.symlink_metadata(&target_path) {
Ok(_) => return Err(Errno::Exist),
Err(virtual_fs::FsError::EntryNotFound) => {}
Err(err) => return Err(fs_error_into_wasi_err(err)),
}
match state.fs.root_fs.hard_link(&source_path, &target_path) {
Ok(()) => {
let kind = Kind::File {
handle: None,
path: target_path.clone(),
fd: None,
};
match state
.fs
.create_inode(inodes, kind, false, new_entry_name.clone())
{
Ok(inode) => inode,
Err(err) => {
let _ = state.fs.root_fs.remove_file(&target_path);
return Err(err);
}
}
}
Err(virtual_fs::FsError::Unsupported) => {
source_inode.clone()
}
Err(err) => return Err(fs_error_into_wasi_err(err)),
}
} else {
source_inode.clone()
};
{
let mut guard = target_parent_inode.write();
match guard.deref_mut() {
Kind::Dir { entries, .. } => {
if entries.contains_key(&new_entry_name) {
return Err(Errno::Exist);
}
entries.insert(new_entry_name, target_inode.clone());
}
Kind::Root { .. } => return Err(Errno::Inval),
Kind::File { .. }
| Kind::Symlink { .. }
| Kind::Buffer { .. }
| Kind::Socket { .. }
| Kind::PipeTx { .. }
| Kind::PipeRx { .. }
| Kind::DuplexPipe { .. }
| Kind::EventNotifications { .. }
| Kind::Epoll { .. } => return Err(Errno::Notdir),
}
}
let new_link_count = {
let mut stat = source_inode.stat.write().unwrap();
stat.st_nlink += 1;
stat.st_nlink
};
target_inode.stat.write().unwrap().st_nlink = new_link_count;
Ok(())
}