Virtual file support (#701)
* Add support for virtual files (eg, not backed by an OS file). Virtual files are implemented through trait objects, with a default implementation that tries to behave like on-disk files, but entirely backed by in-memory structures. Co-authored-by: Dan Gohman <sunfish@mozilla.com>
This commit is contained in:
@@ -3,6 +3,7 @@ use super::fs_helpers::path_get;
|
||||
use crate::ctx::WasiCtx;
|
||||
use crate::fdentry::{Descriptor, FdEntry};
|
||||
use crate::helpers::*;
|
||||
use crate::host::Dirent;
|
||||
use crate::memory::*;
|
||||
use crate::sandboxed_tty_writer::SandboxedTTYWriter;
|
||||
use crate::sys::hostcalls_impl::fs_helpers::path_open_rights;
|
||||
@@ -10,7 +11,7 @@ use crate::sys::{host_impl, hostcalls_impl};
|
||||
use crate::{helpers, host, wasi, wasi32, Error, Result};
|
||||
use filetime::{set_file_handle_times, FileTime};
|
||||
use log::trace;
|
||||
use std::fs::File;
|
||||
use std::convert::TryInto;
|
||||
use std::io::{self, Read, Seek, SeekFrom, Write};
|
||||
use std::ops::DerefMut;
|
||||
use std::time::{Duration, SystemTime, UNIX_EPOCH};
|
||||
@@ -40,12 +41,15 @@ pub(crate) unsafe fn fd_datasync(
|
||||
) -> Result<()> {
|
||||
trace!("fd_datasync(fd={:?})", fd);
|
||||
|
||||
let fd = wasi_ctx
|
||||
let file = wasi_ctx
|
||||
.get_fd_entry(fd)?
|
||||
.as_descriptor(wasi::__WASI_RIGHTS_FD_DATASYNC, 0)?
|
||||
.as_file()?;
|
||||
.as_descriptor(wasi::__WASI_RIGHTS_FD_DATASYNC, 0)?;
|
||||
|
||||
fd.sync_data().map_err(Into::into)
|
||||
match file {
|
||||
Descriptor::OsHandle(fd) => fd.sync_data().map_err(Into::into),
|
||||
Descriptor::VirtualFile(virt) => virt.datasync(),
|
||||
other => other.as_os_handle().sync_data().map_err(Into::into),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) unsafe fn fd_pread(
|
||||
@@ -66,7 +70,7 @@ pub(crate) unsafe fn fd_pread(
|
||||
nread
|
||||
);
|
||||
|
||||
let fd = wasi_ctx
|
||||
let file = wasi_ctx
|
||||
.get_fd_entry(fd)?
|
||||
.as_descriptor(wasi::__WASI_RIGHTS_FD_READ | wasi::__WASI_RIGHTS_FD_SEEK, 0)?
|
||||
.as_file()?;
|
||||
@@ -76,9 +80,28 @@ pub(crate) unsafe fn fd_pread(
|
||||
if offset > i64::max_value() as u64 {
|
||||
return Err(Error::EIO);
|
||||
}
|
||||
let buf_size = iovs.iter().map(|v| v.buf_len).sum();
|
||||
let mut buf = vec![0; buf_size];
|
||||
let host_nread = hostcalls_impl::fd_pread(fd, &mut buf, offset)?;
|
||||
let buf_size = iovs
|
||||
.iter()
|
||||
.map(|iov| {
|
||||
let cast_iovlen: wasi32::size_t = iov
|
||||
.buf_len
|
||||
.try_into()
|
||||
.expect("iovec are bounded by wasi max sizes");
|
||||
cast_iovlen
|
||||
})
|
||||
.fold(Some(0u32), |len, iov| len.and_then(|x| x.checked_add(iov)))
|
||||
.ok_or(Error::EINVAL)?;
|
||||
let mut buf = vec![0; buf_size as usize];
|
||||
let host_nread = match file {
|
||||
Descriptor::OsHandle(fd) => hostcalls_impl::fd_pread(&fd, &mut buf, offset)?,
|
||||
Descriptor::VirtualFile(virt) => virt.pread(&mut buf, offset)?,
|
||||
_ => {
|
||||
unreachable!(
|
||||
"implementation error: fd should have been checked to not be a stream already"
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
let mut buf_offset = 0;
|
||||
let mut left = host_nread;
|
||||
for iov in &iovs {
|
||||
@@ -115,7 +138,7 @@ pub(crate) unsafe fn fd_pwrite(
|
||||
nwritten
|
||||
);
|
||||
|
||||
let fd = wasi_ctx
|
||||
let file = wasi_ctx
|
||||
.get_fd_entry(fd)?
|
||||
.as_descriptor(
|
||||
wasi::__WASI_RIGHTS_FD_WRITE | wasi::__WASI_RIGHTS_FD_SEEK,
|
||||
@@ -127,15 +150,33 @@ pub(crate) unsafe fn fd_pwrite(
|
||||
if offset > i64::max_value() as u64 {
|
||||
return Err(Error::EIO);
|
||||
}
|
||||
let buf_size = iovs.iter().map(|v| v.buf_len).sum();
|
||||
let mut buf = Vec::with_capacity(buf_size);
|
||||
let buf_size = iovs
|
||||
.iter()
|
||||
.map(|iov| {
|
||||
let cast_iovlen: wasi32::size_t = iov
|
||||
.buf_len
|
||||
.try_into()
|
||||
.expect("iovec are bounded by wasi max sizes");
|
||||
cast_iovlen
|
||||
})
|
||||
.fold(Some(0u32), |len, iov| len.and_then(|x| x.checked_add(iov)))
|
||||
.ok_or(Error::EINVAL)?;
|
||||
let mut buf = Vec::with_capacity(buf_size as usize);
|
||||
for iov in &iovs {
|
||||
buf.extend_from_slice(std::slice::from_raw_parts(
|
||||
iov.buf as *const u8,
|
||||
iov.buf_len,
|
||||
));
|
||||
}
|
||||
let host_nwritten = hostcalls_impl::fd_pwrite(fd, &buf, offset)?;
|
||||
let host_nwritten = match file {
|
||||
Descriptor::OsHandle(fd) => hostcalls_impl::fd_pwrite(&fd, &buf, offset)?,
|
||||
Descriptor::VirtualFile(virt) => virt.pwrite(buf.as_mut(), offset)?,
|
||||
_ => {
|
||||
unreachable!(
|
||||
"implementation error: fd should have been checked to not be a stream already"
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
trace!(" | *nwritten={:?}", host_nwritten);
|
||||
|
||||
@@ -168,8 +209,9 @@ pub(crate) unsafe fn fd_read(
|
||||
.get_fd_entry_mut(fd)?
|
||||
.as_descriptor_mut(wasi::__WASI_RIGHTS_FD_READ, 0)?
|
||||
{
|
||||
Descriptor::OsHandle(file) => file.read_vectored(&mut iovs),
|
||||
Descriptor::Stdin => io::stdin().read_vectored(&mut iovs),
|
||||
Descriptor::OsHandle(file) => file.read_vectored(&mut iovs).map_err(Into::into),
|
||||
Descriptor::VirtualFile(virt) => virt.read_vectored(&mut iovs),
|
||||
Descriptor::Stdin => io::stdin().read_vectored(&mut iovs).map_err(Into::into),
|
||||
_ => return Err(Error::EBADF),
|
||||
};
|
||||
|
||||
@@ -232,7 +274,7 @@ pub(crate) unsafe fn fd_seek(
|
||||
} else {
|
||||
wasi::__WASI_RIGHTS_FD_SEEK | wasi::__WASI_RIGHTS_FD_TELL
|
||||
};
|
||||
let fd = wasi_ctx
|
||||
let file = wasi_ctx
|
||||
.get_fd_entry_mut(fd)?
|
||||
.as_descriptor_mut(rights, 0)?
|
||||
.as_file_mut()?;
|
||||
@@ -243,7 +285,15 @@ pub(crate) unsafe fn fd_seek(
|
||||
wasi::__WASI_WHENCE_SET => SeekFrom::Start(offset as u64),
|
||||
_ => return Err(Error::EINVAL),
|
||||
};
|
||||
let host_newoffset = fd.seek(pos)?;
|
||||
let host_newoffset = match file {
|
||||
Descriptor::OsHandle(fd) => fd.seek(pos)?,
|
||||
Descriptor::VirtualFile(virt) => virt.seek(pos)?,
|
||||
_ => {
|
||||
unreachable!(
|
||||
"implementation error: fd should have been checked to not be a stream already"
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
trace!(" | *newoffset={:?}", host_newoffset);
|
||||
|
||||
@@ -258,12 +308,20 @@ pub(crate) unsafe fn fd_tell(
|
||||
) -> Result<()> {
|
||||
trace!("fd_tell(fd={:?}, newoffset={:#x?})", fd, newoffset);
|
||||
|
||||
let fd = wasi_ctx
|
||||
let file = wasi_ctx
|
||||
.get_fd_entry_mut(fd)?
|
||||
.as_descriptor_mut(wasi::__WASI_RIGHTS_FD_TELL, 0)?
|
||||
.as_file_mut()?;
|
||||
|
||||
let host_offset = fd.seek(SeekFrom::Current(0))?;
|
||||
let host_offset = match file {
|
||||
Descriptor::OsHandle(fd) => fd.seek(SeekFrom::Current(0))?,
|
||||
Descriptor::VirtualFile(virt) => virt.seek(SeekFrom::Current(0))?,
|
||||
_ => {
|
||||
unreachable!(
|
||||
"implementation error: fd should have been checked to not be a stream already"
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
trace!(" | *newoffset={:?}", host_offset);
|
||||
|
||||
@@ -279,12 +337,13 @@ pub(crate) unsafe fn fd_fdstat_get(
|
||||
trace!("fd_fdstat_get(fd={:?}, fdstat_ptr={:#x?})", fd, fdstat_ptr);
|
||||
|
||||
let mut fdstat = dec_fdstat_byref(memory, fdstat_ptr)?;
|
||||
let host_fd = wasi_ctx
|
||||
.get_fd_entry(fd)?
|
||||
.as_descriptor(0, 0)?
|
||||
.as_os_handle();
|
||||
let wasi_file = wasi_ctx.get_fd_entry(fd)?.as_descriptor(0, 0)?;
|
||||
|
||||
let fs_flags = hostcalls_impl::fd_fdstat_get(&host_fd)?;
|
||||
let fs_flags = match wasi_file {
|
||||
Descriptor::OsHandle(wasi_fd) => hostcalls_impl::fd_fdstat_get(&wasi_fd)?,
|
||||
Descriptor::VirtualFile(virt) => virt.fdstat_get(),
|
||||
other => hostcalls_impl::fd_fdstat_get(&other.as_os_handle())?,
|
||||
};
|
||||
|
||||
let fe = wasi_ctx.get_fd_entry(fd)?;
|
||||
fdstat.fs_filetype = fe.file_type;
|
||||
@@ -309,11 +368,28 @@ pub(crate) unsafe fn fd_fdstat_set_flags(
|
||||
.get_fd_entry_mut(fd)?
|
||||
.as_descriptor_mut(wasi::__WASI_RIGHTS_FD_FDSTAT_SET_FLAGS, 0)?;
|
||||
|
||||
if let Some(new_handle) =
|
||||
hostcalls_impl::fd_fdstat_set_flags(&descriptor.as_os_handle(), fdflags)?
|
||||
{
|
||||
*descriptor = Descriptor::OsHandle(new_handle);
|
||||
}
|
||||
match descriptor {
|
||||
Descriptor::OsHandle(handle) => {
|
||||
let set_result =
|
||||
hostcalls_impl::fd_fdstat_set_flags(&handle, fdflags)?.map(Descriptor::OsHandle);
|
||||
|
||||
if let Some(new_descriptor) = set_result {
|
||||
*descriptor = new_descriptor;
|
||||
}
|
||||
}
|
||||
Descriptor::VirtualFile(handle) => {
|
||||
handle.fdstat_set_flags(fdflags)?;
|
||||
}
|
||||
_ => {
|
||||
let set_result =
|
||||
hostcalls_impl::fd_fdstat_set_flags(&descriptor.as_os_handle(), fdflags)?
|
||||
.map(Descriptor::OsHandle);
|
||||
|
||||
if let Some(new_descriptor) = set_result {
|
||||
*descriptor = new_descriptor;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -351,11 +427,19 @@ pub(crate) unsafe fn fd_sync(
|
||||
) -> Result<()> {
|
||||
trace!("fd_sync(fd={:?})", fd);
|
||||
|
||||
let fd = wasi_ctx
|
||||
let file = wasi_ctx
|
||||
.get_fd_entry(fd)?
|
||||
.as_descriptor(wasi::__WASI_RIGHTS_FD_SYNC, 0)?
|
||||
.as_file()?;
|
||||
fd.sync_all().map_err(Into::into)
|
||||
match file {
|
||||
Descriptor::OsHandle(fd) => fd.sync_all().map_err(Into::into),
|
||||
Descriptor::VirtualFile(virt) => virt.sync(),
|
||||
_ => {
|
||||
unreachable!(
|
||||
"implementation error: fd should have been checked to not be a stream already"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) unsafe fn fd_write(
|
||||
@@ -389,6 +473,13 @@ pub(crate) unsafe fn fd_write(
|
||||
file.write_vectored(&iovs)?
|
||||
}
|
||||
}
|
||||
Descriptor::VirtualFile(virt) => {
|
||||
if isatty {
|
||||
unimplemented!("writes to virtual tty");
|
||||
} else {
|
||||
virt.write_vectored(&iovs)?
|
||||
}
|
||||
}
|
||||
Descriptor::Stdin => return Err(Error::EBADF),
|
||||
Descriptor::Stdout => {
|
||||
// lock for the duration of the scope
|
||||
@@ -415,7 +506,7 @@ pub(crate) unsafe fn fd_write(
|
||||
}
|
||||
|
||||
pub(crate) unsafe fn fd_advise(
|
||||
wasi_ctx: &WasiCtx,
|
||||
wasi_ctx: &mut WasiCtx,
|
||||
_memory: &mut [u8],
|
||||
fd: wasi::__wasi_fd_t,
|
||||
offset: wasi::__wasi_filesize_t,
|
||||
@@ -430,12 +521,20 @@ pub(crate) unsafe fn fd_advise(
|
||||
advice
|
||||
);
|
||||
|
||||
let fd = wasi_ctx
|
||||
.get_fd_entry(fd)?
|
||||
.as_descriptor(wasi::__WASI_RIGHTS_FD_ADVISE, 0)?
|
||||
.as_file()?;
|
||||
let file = wasi_ctx
|
||||
.get_fd_entry_mut(fd)?
|
||||
.as_descriptor_mut(wasi::__WASI_RIGHTS_FD_ADVISE, 0)?
|
||||
.as_file_mut()?;
|
||||
|
||||
hostcalls_impl::fd_advise(fd, advice, offset, len)
|
||||
match file {
|
||||
Descriptor::OsHandle(fd) => hostcalls_impl::fd_advise(&fd, advice, offset, len),
|
||||
Descriptor::VirtualFile(virt) => virt.advise(advice, offset, len),
|
||||
_ => {
|
||||
unreachable!(
|
||||
"implementation error: fd should have been checked to not be a stream already"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) unsafe fn fd_allocate(
|
||||
@@ -447,24 +546,34 @@ pub(crate) unsafe fn fd_allocate(
|
||||
) -> Result<()> {
|
||||
trace!("fd_allocate(fd={:?}, offset={}, len={})", fd, offset, len);
|
||||
|
||||
let fd = wasi_ctx
|
||||
let file = wasi_ctx
|
||||
.get_fd_entry(fd)?
|
||||
.as_descriptor(wasi::__WASI_RIGHTS_FD_ALLOCATE, 0)?
|
||||
.as_file()?;
|
||||
|
||||
let metadata = fd.metadata()?;
|
||||
match file {
|
||||
Descriptor::OsHandle(fd) => {
|
||||
let metadata = fd.metadata()?;
|
||||
|
||||
let current_size = metadata.len();
|
||||
let wanted_size = offset.checked_add(len).ok_or(Error::E2BIG)?;
|
||||
// This check will be unnecessary when rust-lang/rust#63326 is fixed
|
||||
if wanted_size > i64::max_value() as u64 {
|
||||
return Err(Error::E2BIG);
|
||||
}
|
||||
let current_size = metadata.len();
|
||||
let wanted_size = offset.checked_add(len).ok_or(Error::E2BIG)?;
|
||||
// This check will be unnecessary when rust-lang/rust#63326 is fixed
|
||||
if wanted_size > i64::max_value() as u64 {
|
||||
return Err(Error::E2BIG);
|
||||
}
|
||||
|
||||
if wanted_size > current_size {
|
||||
fd.set_len(wanted_size).map_err(Into::into)
|
||||
} else {
|
||||
Ok(())
|
||||
if wanted_size > current_size {
|
||||
fd.set_len(wanted_size).map_err(Into::into)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
Descriptor::VirtualFile(virt) => virt.allocate(offset, len),
|
||||
_ => {
|
||||
unreachable!(
|
||||
"implementation error: fd should have been checked to not be a stream already"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -490,7 +599,7 @@ pub(crate) unsafe fn path_create_directory(
|
||||
let fe = wasi_ctx.get_fd_entry(dirfd)?;
|
||||
let resolved = path_get(fe, rights, 0, 0, path, false)?;
|
||||
|
||||
hostcalls_impl::path_create_directory(resolved)
|
||||
resolved.path_create_directory()
|
||||
}
|
||||
|
||||
pub(crate) unsafe fn path_link(
|
||||
@@ -607,7 +716,7 @@ pub(crate) unsafe fn path_open(
|
||||
read,
|
||||
write
|
||||
);
|
||||
let fd = hostcalls_impl::path_open(resolved, read, write, oflags, fs_flags)?;
|
||||
let fd = resolved.open_with(read, write, oflags, fs_flags)?;
|
||||
|
||||
let mut fe = FdEntry::from(fd)?;
|
||||
// We need to manually deny the rights which are not explicitly requested
|
||||
@@ -652,7 +761,12 @@ pub(crate) unsafe fn path_readlink(
|
||||
|
||||
let mut buf = dec_slice_of_mut_u8(memory, buf_ptr, buf_len)?;
|
||||
|
||||
let host_bufused = hostcalls_impl::path_readlink(resolved, &mut buf)?;
|
||||
let host_bufused = match resolved.dirfd() {
|
||||
Descriptor::VirtualFile(_virt) => {
|
||||
unimplemented!("virtual readlink");
|
||||
}
|
||||
_ => hostcalls_impl::path_readlink(resolved, &mut buf)?,
|
||||
};
|
||||
|
||||
trace!(" | (buf_ptr,*buf_used)={:?}", buf);
|
||||
trace!(" | *buf_used={:?}", host_bufused);
|
||||
@@ -708,7 +822,15 @@ pub(crate) unsafe fn path_rename(
|
||||
log::debug!("path_rename resolved_old={:?}", resolved_old);
|
||||
log::debug!("path_rename resolved_new={:?}", resolved_new);
|
||||
|
||||
hostcalls_impl::path_rename(resolved_old, resolved_new)
|
||||
if let (Descriptor::OsHandle(_), Descriptor::OsHandle(_)) =
|
||||
(resolved_old.dirfd(), resolved_new.dirfd())
|
||||
{
|
||||
hostcalls_impl::path_rename(resolved_old, resolved_new)
|
||||
} else {
|
||||
// Virtual files do not support rename, at the moment, and streams don't have paths to
|
||||
// rename, so any combination of Descriptor that gets here is an error in the making.
|
||||
panic!("path_rename with one or more non-OS files");
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) unsafe fn fd_filestat_get(
|
||||
@@ -727,7 +849,15 @@ pub(crate) unsafe fn fd_filestat_get(
|
||||
.get_fd_entry(fd)?
|
||||
.as_descriptor(wasi::__WASI_RIGHTS_FD_FILESTAT_GET, 0)?
|
||||
.as_file()?;
|
||||
let host_filestat = hostcalls_impl::fd_filestat_get(fd)?;
|
||||
let host_filestat = match fd {
|
||||
Descriptor::OsHandle(fd) => hostcalls_impl::fd_filestat_get(&fd)?,
|
||||
Descriptor::VirtualFile(virt) => virt.filestat_get()?,
|
||||
_ => {
|
||||
unreachable!(
|
||||
"implementation error: fd should have been checked to not be a stream already"
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
trace!(" | *filestat_ptr={:?}", host_filestat);
|
||||
|
||||
@@ -755,11 +885,11 @@ pub(crate) unsafe fn fd_filestat_set_times(
|
||||
.as_descriptor(wasi::__WASI_RIGHTS_FD_FILESTAT_SET_TIMES, 0)?
|
||||
.as_file()?;
|
||||
|
||||
fd_filestat_set_times_impl(fd, st_atim, st_mtim, fst_flags)
|
||||
fd_filestat_set_times_impl(&fd, st_atim, st_mtim, fst_flags)
|
||||
}
|
||||
|
||||
pub(crate) fn fd_filestat_set_times_impl(
|
||||
fd: &File,
|
||||
file: &Descriptor,
|
||||
st_atim: wasi::__wasi_timestamp_t,
|
||||
st_mtim: wasi::__wasi_timestamp_t,
|
||||
fst_flags: wasi::__wasi_fstflags_t,
|
||||
@@ -791,7 +921,15 @@ pub(crate) fn fd_filestat_set_times_impl(
|
||||
} else {
|
||||
None
|
||||
};
|
||||
set_file_handle_times(fd, atim, mtim).map_err(Into::into)
|
||||
match file {
|
||||
Descriptor::OsHandle(fd) => set_file_handle_times(fd, atim, mtim).map_err(Into::into),
|
||||
Descriptor::VirtualFile(virt) => virt.filestat_set_times(atim, mtim),
|
||||
_ => {
|
||||
unreachable!(
|
||||
"implementation error: fd should have been checked to not be a stream already"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) unsafe fn fd_filestat_set_size(
|
||||
@@ -802,7 +940,7 @@ pub(crate) unsafe fn fd_filestat_set_size(
|
||||
) -> Result<()> {
|
||||
trace!("fd_filestat_set_size(fd={:?}, st_size={})", fd, st_size);
|
||||
|
||||
let fd = wasi_ctx
|
||||
let file = wasi_ctx
|
||||
.get_fd_entry(fd)?
|
||||
.as_descriptor(wasi::__WASI_RIGHTS_FD_FILESTAT_SET_SIZE, 0)?
|
||||
.as_file()?;
|
||||
@@ -811,7 +949,15 @@ pub(crate) unsafe fn fd_filestat_set_size(
|
||||
if st_size > i64::max_value() as u64 {
|
||||
return Err(Error::E2BIG);
|
||||
}
|
||||
fd.set_len(st_size).map_err(Into::into)
|
||||
match file {
|
||||
Descriptor::OsHandle(fd) => fd.set_len(st_size).map_err(Into::into),
|
||||
Descriptor::VirtualFile(virt) => virt.filestat_set_size(st_size),
|
||||
_ => {
|
||||
unreachable!(
|
||||
"implementation error: fd should have been checked to not be a stream already"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) unsafe fn path_filestat_get(
|
||||
@@ -845,7 +991,12 @@ pub(crate) unsafe fn path_filestat_get(
|
||||
path,
|
||||
false,
|
||||
)?;
|
||||
let host_filestat = hostcalls_impl::path_filestat_get(resolved, dirflags)?;
|
||||
let host_filestat = match resolved.dirfd() {
|
||||
Descriptor::VirtualFile(virt) => virt
|
||||
.openat(std::path::Path::new(resolved.path()), false, false, 0, 0)?
|
||||
.filestat_get()?,
|
||||
_ => hostcalls_impl::path_filestat_get(resolved, dirflags)?,
|
||||
};
|
||||
|
||||
trace!(" | *filestat_ptr={:?}", host_filestat);
|
||||
|
||||
@@ -887,7 +1038,14 @@ pub(crate) unsafe fn path_filestat_set_times(
|
||||
false,
|
||||
)?;
|
||||
|
||||
hostcalls_impl::path_filestat_set_times(resolved, dirflags, st_atim, st_mtim, fst_flags)
|
||||
match resolved.dirfd() {
|
||||
Descriptor::VirtualFile(_virt) => {
|
||||
unimplemented!("virtual filestat_set_times");
|
||||
}
|
||||
_ => {
|
||||
hostcalls_impl::path_filestat_set_times(resolved, dirflags, st_atim, st_mtim, fst_flags)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) unsafe fn path_symlink(
|
||||
@@ -917,7 +1075,12 @@ pub(crate) unsafe fn path_symlink(
|
||||
let fe = wasi_ctx.get_fd_entry(dirfd)?;
|
||||
let resolved_new = path_get(fe, wasi::__WASI_RIGHTS_PATH_SYMLINK, 0, 0, new_path, true)?;
|
||||
|
||||
hostcalls_impl::path_symlink(old_path, resolved_new)
|
||||
match resolved_new.dirfd() {
|
||||
Descriptor::VirtualFile(_virt) => {
|
||||
unimplemented!("virtual path_symlink");
|
||||
}
|
||||
_non_virtual => hostcalls_impl::path_symlink(old_path, resolved_new),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) unsafe fn path_unlink_file(
|
||||
@@ -941,7 +1104,10 @@ pub(crate) unsafe fn path_unlink_file(
|
||||
let fe = wasi_ctx.get_fd_entry(dirfd)?;
|
||||
let resolved = path_get(fe, wasi::__WASI_RIGHTS_PATH_UNLINK_FILE, 0, 0, path, false)?;
|
||||
|
||||
hostcalls_impl::path_unlink_file(resolved)
|
||||
match resolved.dirfd() {
|
||||
Descriptor::VirtualFile(virt) => virt.unlink_file(resolved.path()),
|
||||
_ => hostcalls_impl::path_unlink_file(resolved),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) unsafe fn path_remove_directory(
|
||||
@@ -974,7 +1140,10 @@ pub(crate) unsafe fn path_remove_directory(
|
||||
|
||||
log::debug!("path_remove_directory resolved={:?}", resolved);
|
||||
|
||||
hostcalls_impl::path_remove_directory(resolved)
|
||||
match resolved.dirfd() {
|
||||
Descriptor::VirtualFile(virt) => virt.remove_directory(resolved.path()),
|
||||
_ => hostcalls_impl::path_remove_directory(resolved),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) unsafe fn fd_prestat_get(
|
||||
@@ -1068,24 +1237,41 @@ pub(crate) unsafe fn fd_readdir(
|
||||
.get_fd_entry_mut(fd)?
|
||||
.as_descriptor_mut(wasi::__WASI_RIGHTS_FD_READDIR, 0)?
|
||||
.as_file_mut()?;
|
||||
let mut host_buf = dec_slice_of_mut_u8(memory, buf, buf_len)?;
|
||||
let host_buf = dec_slice_of_mut_u8(memory, buf, buf_len)?;
|
||||
|
||||
trace!(" | (buf,buf_len)={:?}", host_buf);
|
||||
|
||||
let iter = hostcalls_impl::fd_readdir(file, cookie)?;
|
||||
let mut host_bufused = 0;
|
||||
for dirent in iter {
|
||||
let dirent_raw = dirent?.to_wasi_raw()?;
|
||||
let offset = dirent_raw.len();
|
||||
if host_buf.len() < offset {
|
||||
break;
|
||||
} else {
|
||||
host_buf[0..offset].copy_from_slice(&dirent_raw);
|
||||
host_bufused += offset;
|
||||
host_buf = &mut host_buf[offset..];
|
||||
fn copy_entities<T: Iterator<Item = Result<Dirent>>>(
|
||||
iter: T,
|
||||
mut host_buf: &mut [u8],
|
||||
) -> Result<usize> {
|
||||
let mut host_bufused = 0;
|
||||
for dirent in iter {
|
||||
let dirent_raw = dirent?.to_wasi_raw()?;
|
||||
let offset = dirent_raw.len();
|
||||
if host_buf.len() < offset {
|
||||
break;
|
||||
} else {
|
||||
host_buf[0..offset].copy_from_slice(&dirent_raw);
|
||||
host_bufused += offset;
|
||||
host_buf = &mut host_buf[offset..];
|
||||
}
|
||||
}
|
||||
Ok(host_bufused)
|
||||
}
|
||||
|
||||
let host_bufused = match file {
|
||||
Descriptor::OsHandle(file) => {
|
||||
copy_entities(hostcalls_impl::fd_readdir(file, cookie)?, host_buf)?
|
||||
}
|
||||
Descriptor::VirtualFile(virt) => copy_entities(virt.readdir(cookie)?, host_buf)?,
|
||||
_ => {
|
||||
unreachable!(
|
||||
"implementation error: fd should have been checked to not be a stream already"
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
trace!(" | *buf_used={:?}", host_bufused);
|
||||
|
||||
enc_usize_byref(memory, buf_used, host_bufused)
|
||||
|
||||
@@ -1,24 +1,100 @@
|
||||
#![allow(non_camel_case_types)]
|
||||
use crate::sys::fdentry_impl::OsHandle;
|
||||
use crate::sys::host_impl;
|
||||
use crate::sys::hostcalls_impl::fs_helpers::*;
|
||||
use crate::{error::WasiError, fdentry::FdEntry, wasi, Error, Result};
|
||||
use std::fs::File;
|
||||
use crate::{error::WasiError, fdentry::Descriptor, fdentry::FdEntry, wasi, Error, Result};
|
||||
use std::path::{Component, Path};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct PathGet {
|
||||
dirfd: File,
|
||||
dirfd: Descriptor,
|
||||
path: String,
|
||||
}
|
||||
|
||||
impl PathGet {
|
||||
pub(crate) fn dirfd(&self) -> &File {
|
||||
pub(crate) fn dirfd(&self) -> &Descriptor {
|
||||
&self.dirfd
|
||||
}
|
||||
|
||||
pub(crate) fn path(&self) -> &str {
|
||||
&self.path
|
||||
}
|
||||
|
||||
pub(crate) fn path_create_directory(self) -> Result<()> {
|
||||
match &self.dirfd {
|
||||
Descriptor::OsHandle(file) => {
|
||||
crate::sys::hostcalls_impl::path_create_directory(&file, &self.path)
|
||||
}
|
||||
Descriptor::VirtualFile(virt) => virt.create_directory(&Path::new(&self.path)),
|
||||
other => {
|
||||
panic!("invalid descriptor to create directory: {:?}", other);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn open_with(
|
||||
self,
|
||||
read: bool,
|
||||
write: bool,
|
||||
oflags: u16,
|
||||
fs_flags: u16,
|
||||
) -> Result<Descriptor> {
|
||||
match &self.dirfd {
|
||||
Descriptor::OsHandle(_) => {
|
||||
crate::sys::hostcalls_impl::path_open(self, read, write, oflags, fs_flags)
|
||||
.map_err(Into::into)
|
||||
}
|
||||
Descriptor::VirtualFile(virt) => virt
|
||||
.openat(Path::new(&self.path), read, write, oflags, fs_flags)
|
||||
.map(|file| Descriptor::VirtualFile(file)),
|
||||
other => {
|
||||
panic!("invalid descriptor to path_open: {:?}", other);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct PathRef<'a, 'b> {
|
||||
dirfd: &'a Descriptor,
|
||||
path: &'b str,
|
||||
}
|
||||
|
||||
impl<'a, 'b> PathRef<'a, 'b> {
|
||||
fn new(dirfd: &'a Descriptor, path: &'b str) -> Self {
|
||||
PathRef { dirfd, path }
|
||||
}
|
||||
|
||||
fn open(&self) -> Result<Descriptor> {
|
||||
match self.dirfd {
|
||||
Descriptor::OsHandle(file) => Ok(Descriptor::OsHandle(OsHandle::from(openat(
|
||||
&file, &self.path,
|
||||
)?))),
|
||||
Descriptor::VirtualFile(virt) => virt
|
||||
.openat(
|
||||
Path::new(&self.path),
|
||||
false,
|
||||
false,
|
||||
wasi::__WASI_OFLAGS_DIRECTORY,
|
||||
0,
|
||||
)
|
||||
.map(|file| Descriptor::VirtualFile(file)),
|
||||
other => {
|
||||
panic!("invalid descriptor for open: {:?}", other);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn readlink(&self) -> Result<String> {
|
||||
match self.dirfd {
|
||||
Descriptor::OsHandle(file) => readlinkat(file, self.path),
|
||||
Descriptor::VirtualFile(virt) => {
|
||||
virt.readlinkat(Path::new(self.path)).map_err(Into::into)
|
||||
}
|
||||
other => {
|
||||
panic!("invalid descriptor for readlink: {:?}", other);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Normalizes a path to ensure that the target path is located under the directory provided.
|
||||
@@ -112,7 +188,9 @@ pub(crate) fn path_get(
|
||||
}
|
||||
|
||||
if !path_stack.is_empty() || (ends_with_slash && !needs_final_component) {
|
||||
match openat(dir_stack.last().ok_or(Error::ENOTCAPABLE)?, &head) {
|
||||
match PathRef::new(dir_stack.last().ok_or(Error::ENOTCAPABLE)?, &head)
|
||||
.open()
|
||||
{
|
||||
Ok(new_dir) => {
|
||||
dir_stack.push(new_dir);
|
||||
}
|
||||
@@ -125,10 +203,11 @@ pub(crate) fn path_get(
|
||||
// this with ENOTDIR because of the O_DIRECTORY flag.
|
||||
{
|
||||
// attempt symlink expansion
|
||||
let mut link_path = readlinkat(
|
||||
let mut link_path = PathRef::new(
|
||||
dir_stack.last().ok_or(Error::ENOTCAPABLE)?,
|
||||
&head,
|
||||
)?;
|
||||
)
|
||||
.readlink()?;
|
||||
|
||||
symlink_expansions += 1;
|
||||
if symlink_expansions > MAX_SYMLINK_EXPANSIONS {
|
||||
@@ -159,7 +238,9 @@ pub(crate) fn path_get(
|
||||
{
|
||||
// if there's a trailing slash, or if `LOOKUP_SYMLINK_FOLLOW` is set, attempt
|
||||
// symlink expansion
|
||||
match readlinkat(dir_stack.last().ok_or(Error::ENOTCAPABLE)?, &head) {
|
||||
match PathRef::new(dir_stack.last().ok_or(Error::ENOTCAPABLE)?, &head)
|
||||
.readlink()
|
||||
{
|
||||
Ok(mut link_path) => {
|
||||
symlink_expansions += 1;
|
||||
if symlink_expansions > MAX_SYMLINK_EXPANSIONS {
|
||||
|
||||
Reference in New Issue
Block a user