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:
iximeow
2020-03-06 11:08:13 -08:00
committed by GitHub
parent 7f7196a655
commit 7e0d9decbf
19 changed files with 1568 additions and 188 deletions

View File

@@ -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)

View File

@@ -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 {