834 lines
25 KiB
Rust
834 lines
25 KiB
Rust
#![forbid(unsafe_op_in_unsafe_fn)]
|
|
|
|
use super::fd::WasiFd;
|
|
use crate::ffi::{CStr, OsStr, OsString};
|
|
use crate::io::{self, BorrowedCursor, IoSlice, IoSliceMut, SeekFrom};
|
|
use crate::mem::{self, ManuallyDrop};
|
|
use crate::os::raw::c_int;
|
|
use crate::os::wasi::ffi::{OsStrExt, OsStringExt};
|
|
use crate::os::wasi::io::{AsFd, AsRawFd, BorrowedFd, FromRawFd, IntoRawFd, RawFd};
|
|
use crate::path::{Path, PathBuf};
|
|
use crate::sync::Arc;
|
|
use crate::sys::common::small_c_string::run_path_with_cstr;
|
|
use crate::sys::time::SystemTime;
|
|
use crate::sys::unsupported;
|
|
pub use crate::sys_common::fs::exists;
|
|
use crate::sys_common::{AsInner, FromInner, IntoInner, ignore_notfound};
|
|
use crate::{fmt, iter, ptr};
|
|
|
|
pub struct File {
|
|
fd: WasiFd,
|
|
}
|
|
|
|
#[derive(Clone)]
|
|
pub struct FileAttr {
|
|
meta: wasi::Filestat,
|
|
}
|
|
|
|
pub struct ReadDir {
|
|
inner: Arc<ReadDirInner>,
|
|
cookie: Option<wasi::Dircookie>,
|
|
buf: Vec<u8>,
|
|
offset: usize,
|
|
cap: usize,
|
|
}
|
|
|
|
struct ReadDirInner {
|
|
root: PathBuf,
|
|
dir: File,
|
|
}
|
|
|
|
pub struct DirEntry {
|
|
meta: wasi::Dirent,
|
|
name: Vec<u8>,
|
|
inner: Arc<ReadDirInner>,
|
|
}
|
|
|
|
#[derive(Clone, Debug, Default)]
|
|
pub struct OpenOptions {
|
|
read: bool,
|
|
write: bool,
|
|
append: bool,
|
|
dirflags: wasi::Lookupflags,
|
|
fdflags: wasi::Fdflags,
|
|
oflags: wasi::Oflags,
|
|
rights_base: Option<wasi::Rights>,
|
|
rights_inheriting: Option<wasi::Rights>,
|
|
}
|
|
|
|
#[derive(Clone, PartialEq, Eq, Debug)]
|
|
pub struct FilePermissions {
|
|
readonly: bool,
|
|
}
|
|
|
|
#[derive(Copy, Clone, Debug, Default)]
|
|
pub struct FileTimes {
|
|
accessed: Option<SystemTime>,
|
|
modified: Option<SystemTime>,
|
|
}
|
|
|
|
#[derive(PartialEq, Eq, Hash, Debug, Copy, Clone)]
|
|
pub struct FileType {
|
|
bits: wasi::Filetype,
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub struct DirBuilder {}
|
|
|
|
impl FileAttr {
|
|
pub fn size(&self) -> u64 {
|
|
self.meta.size
|
|
}
|
|
|
|
pub fn perm(&self) -> FilePermissions {
|
|
// not currently implemented in wasi yet
|
|
FilePermissions { readonly: false }
|
|
}
|
|
|
|
pub fn file_type(&self) -> FileType {
|
|
FileType { bits: self.meta.filetype }
|
|
}
|
|
|
|
pub fn modified(&self) -> io::Result<SystemTime> {
|
|
Ok(SystemTime::from_wasi_timestamp(self.meta.mtim))
|
|
}
|
|
|
|
pub fn accessed(&self) -> io::Result<SystemTime> {
|
|
Ok(SystemTime::from_wasi_timestamp(self.meta.atim))
|
|
}
|
|
|
|
pub fn created(&self) -> io::Result<SystemTime> {
|
|
Ok(SystemTime::from_wasi_timestamp(self.meta.ctim))
|
|
}
|
|
|
|
pub(crate) fn as_wasi(&self) -> &wasi::Filestat {
|
|
&self.meta
|
|
}
|
|
}
|
|
|
|
impl FilePermissions {
|
|
pub fn readonly(&self) -> bool {
|
|
self.readonly
|
|
}
|
|
|
|
pub fn set_readonly(&mut self, readonly: bool) {
|
|
self.readonly = readonly;
|
|
}
|
|
}
|
|
|
|
impl FileTimes {
|
|
pub fn set_accessed(&mut self, t: SystemTime) {
|
|
self.accessed = Some(t);
|
|
}
|
|
|
|
pub fn set_modified(&mut self, t: SystemTime) {
|
|
self.modified = Some(t);
|
|
}
|
|
}
|
|
|
|
impl FileType {
|
|
pub fn is_dir(&self) -> bool {
|
|
self.bits == wasi::FILETYPE_DIRECTORY
|
|
}
|
|
|
|
pub fn is_file(&self) -> bool {
|
|
self.bits == wasi::FILETYPE_REGULAR_FILE
|
|
}
|
|
|
|
pub fn is_symlink(&self) -> bool {
|
|
self.bits == wasi::FILETYPE_SYMBOLIC_LINK
|
|
}
|
|
|
|
pub(crate) fn bits(&self) -> wasi::Filetype {
|
|
self.bits
|
|
}
|
|
}
|
|
|
|
impl ReadDir {
|
|
fn new(dir: File, root: PathBuf) -> ReadDir {
|
|
ReadDir {
|
|
cookie: Some(0),
|
|
buf: vec![0; 128],
|
|
offset: 0,
|
|
cap: 0,
|
|
inner: Arc::new(ReadDirInner { dir, root }),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl fmt::Debug for ReadDir {
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
f.debug_struct("ReadDir").finish_non_exhaustive()
|
|
}
|
|
}
|
|
|
|
impl Iterator for ReadDir {
|
|
type Item = io::Result<DirEntry>;
|
|
|
|
fn next(&mut self) -> Option<io::Result<DirEntry>> {
|
|
loop {
|
|
// If we've reached the capacity of our buffer then we need to read
|
|
// some more from the OS, otherwise we pick up at our old offset.
|
|
let offset = if self.offset == self.cap {
|
|
let cookie = self.cookie.take()?;
|
|
match self.inner.dir.fd.readdir(&mut self.buf, cookie) {
|
|
Ok(bytes) => self.cap = bytes,
|
|
Err(e) => return Some(Err(e)),
|
|
}
|
|
self.offset = 0;
|
|
self.cookie = Some(cookie);
|
|
|
|
// If we didn't actually read anything, this is in theory the
|
|
// end of the directory.
|
|
if self.cap == 0 {
|
|
self.cookie = None;
|
|
return None;
|
|
}
|
|
|
|
0
|
|
} else {
|
|
self.offset
|
|
};
|
|
let data = &self.buf[offset..self.cap];
|
|
|
|
// If we're not able to read a directory entry then that means it
|
|
// must have been truncated at the end of the buffer, so reset our
|
|
// offset so we can go back and reread into the buffer, picking up
|
|
// where we last left off.
|
|
let dirent_size = mem::size_of::<wasi::Dirent>();
|
|
if data.len() < dirent_size {
|
|
assert!(self.cookie.is_some());
|
|
assert!(self.buf.len() >= dirent_size);
|
|
self.offset = self.cap;
|
|
continue;
|
|
}
|
|
let (dirent, data) = data.split_at(dirent_size);
|
|
let dirent = unsafe { ptr::read_unaligned(dirent.as_ptr() as *const wasi::Dirent) };
|
|
|
|
// If the file name was truncated, then we need to reinvoke
|
|
// `readdir` so we truncate our buffer to start over and reread this
|
|
// descriptor. Note that if our offset is 0 that means the file name
|
|
// is massive and we need a bigger buffer.
|
|
if data.len() < dirent.d_namlen as usize {
|
|
if offset == 0 {
|
|
let amt_to_add = self.buf.capacity();
|
|
self.buf.extend(iter::repeat(0).take(amt_to_add));
|
|
}
|
|
assert!(self.cookie.is_some());
|
|
self.offset = self.cap;
|
|
continue;
|
|
}
|
|
self.cookie = Some(dirent.d_next);
|
|
self.offset = offset + dirent_size + dirent.d_namlen as usize;
|
|
|
|
let name = &data[..(dirent.d_namlen as usize)];
|
|
|
|
// These names are skipped on all other platforms, so let's skip
|
|
// them here too
|
|
if name == b"." || name == b".." {
|
|
continue;
|
|
}
|
|
|
|
return Some(Ok(DirEntry {
|
|
meta: dirent,
|
|
name: name.to_vec(),
|
|
inner: self.inner.clone(),
|
|
}));
|
|
}
|
|
}
|
|
}
|
|
|
|
impl DirEntry {
|
|
pub fn path(&self) -> PathBuf {
|
|
let name = OsStr::from_bytes(&self.name);
|
|
self.inner.root.join(name)
|
|
}
|
|
|
|
pub fn file_name(&self) -> OsString {
|
|
OsString::from_vec(self.name.clone())
|
|
}
|
|
|
|
pub fn metadata(&self) -> io::Result<FileAttr> {
|
|
metadata_at(&self.inner.dir.fd, 0, OsStr::from_bytes(&self.name).as_ref())
|
|
}
|
|
|
|
pub fn file_type(&self) -> io::Result<FileType> {
|
|
Ok(FileType { bits: self.meta.d_type })
|
|
}
|
|
|
|
pub fn ino(&self) -> wasi::Inode {
|
|
self.meta.d_ino
|
|
}
|
|
}
|
|
|
|
impl OpenOptions {
|
|
pub fn new() -> OpenOptions {
|
|
let mut base = OpenOptions::default();
|
|
base.dirflags = wasi::LOOKUPFLAGS_SYMLINK_FOLLOW;
|
|
base
|
|
}
|
|
|
|
pub fn read(&mut self, read: bool) {
|
|
self.read = read;
|
|
}
|
|
|
|
pub fn write(&mut self, write: bool) {
|
|
self.write = write;
|
|
}
|
|
|
|
pub fn truncate(&mut self, truncate: bool) {
|
|
self.oflag(wasi::OFLAGS_TRUNC, truncate);
|
|
}
|
|
|
|
pub fn create(&mut self, create: bool) {
|
|
self.oflag(wasi::OFLAGS_CREAT, create);
|
|
}
|
|
|
|
pub fn create_new(&mut self, create_new: bool) {
|
|
self.oflag(wasi::OFLAGS_EXCL, create_new);
|
|
self.oflag(wasi::OFLAGS_CREAT, create_new);
|
|
}
|
|
|
|
pub fn directory(&mut self, directory: bool) {
|
|
self.oflag(wasi::OFLAGS_DIRECTORY, directory);
|
|
}
|
|
|
|
fn oflag(&mut self, bit: wasi::Oflags, set: bool) {
|
|
if set {
|
|
self.oflags |= bit;
|
|
} else {
|
|
self.oflags &= !bit;
|
|
}
|
|
}
|
|
|
|
pub fn append(&mut self, append: bool) {
|
|
self.append = append;
|
|
self.fdflag(wasi::FDFLAGS_APPEND, append);
|
|
}
|
|
|
|
pub fn dsync(&mut self, set: bool) {
|
|
self.fdflag(wasi::FDFLAGS_DSYNC, set);
|
|
}
|
|
|
|
pub fn nonblock(&mut self, set: bool) {
|
|
self.fdflag(wasi::FDFLAGS_NONBLOCK, set);
|
|
}
|
|
|
|
pub fn rsync(&mut self, set: bool) {
|
|
self.fdflag(wasi::FDFLAGS_RSYNC, set);
|
|
}
|
|
|
|
pub fn sync(&mut self, set: bool) {
|
|
self.fdflag(wasi::FDFLAGS_SYNC, set);
|
|
}
|
|
|
|
fn fdflag(&mut self, bit: wasi::Fdflags, set: bool) {
|
|
if set {
|
|
self.fdflags |= bit;
|
|
} else {
|
|
self.fdflags &= !bit;
|
|
}
|
|
}
|
|
|
|
pub fn fs_rights_base(&mut self, rights: wasi::Rights) {
|
|
self.rights_base = Some(rights);
|
|
}
|
|
|
|
pub fn fs_rights_inheriting(&mut self, rights: wasi::Rights) {
|
|
self.rights_inheriting = Some(rights);
|
|
}
|
|
|
|
fn rights_base(&self) -> wasi::Rights {
|
|
if let Some(rights) = self.rights_base {
|
|
return rights;
|
|
}
|
|
|
|
// If rights haven't otherwise been specified try to pick a reasonable
|
|
// set. This can always be overridden by users via extension traits, and
|
|
// implementations may give us fewer rights silently than we ask for. So
|
|
// given that, just look at `read` and `write` and bucket permissions
|
|
// based on that.
|
|
let mut base = 0;
|
|
if self.read {
|
|
base |= wasi::RIGHTS_FD_READ;
|
|
base |= wasi::RIGHTS_FD_READDIR;
|
|
}
|
|
if self.write || self.append {
|
|
base |= wasi::RIGHTS_FD_WRITE;
|
|
base |= wasi::RIGHTS_FD_DATASYNC;
|
|
base |= wasi::RIGHTS_FD_ALLOCATE;
|
|
base |= wasi::RIGHTS_FD_FILESTAT_SET_SIZE;
|
|
}
|
|
|
|
// FIXME: some of these should probably be read-only or write-only...
|
|
base |= wasi::RIGHTS_FD_ADVISE;
|
|
base |= wasi::RIGHTS_FD_FDSTAT_SET_FLAGS;
|
|
base |= wasi::RIGHTS_FD_FILESTAT_GET;
|
|
base |= wasi::RIGHTS_FD_FILESTAT_SET_TIMES;
|
|
base |= wasi::RIGHTS_FD_SEEK;
|
|
base |= wasi::RIGHTS_FD_SYNC;
|
|
base |= wasi::RIGHTS_FD_TELL;
|
|
base |= wasi::RIGHTS_PATH_CREATE_DIRECTORY;
|
|
base |= wasi::RIGHTS_PATH_CREATE_FILE;
|
|
base |= wasi::RIGHTS_PATH_FILESTAT_GET;
|
|
base |= wasi::RIGHTS_PATH_LINK_SOURCE;
|
|
base |= wasi::RIGHTS_PATH_LINK_TARGET;
|
|
base |= wasi::RIGHTS_PATH_OPEN;
|
|
base |= wasi::RIGHTS_PATH_READLINK;
|
|
base |= wasi::RIGHTS_PATH_REMOVE_DIRECTORY;
|
|
base |= wasi::RIGHTS_PATH_RENAME_SOURCE;
|
|
base |= wasi::RIGHTS_PATH_RENAME_TARGET;
|
|
base |= wasi::RIGHTS_PATH_SYMLINK;
|
|
base |= wasi::RIGHTS_PATH_UNLINK_FILE;
|
|
base |= wasi::RIGHTS_POLL_FD_READWRITE;
|
|
|
|
base
|
|
}
|
|
|
|
fn rights_inheriting(&self) -> wasi::Rights {
|
|
self.rights_inheriting.unwrap_or_else(|| self.rights_base())
|
|
}
|
|
|
|
pub fn lookup_flags(&mut self, flags: wasi::Lookupflags) {
|
|
self.dirflags = flags;
|
|
}
|
|
}
|
|
|
|
impl File {
|
|
pub fn open(path: &Path, opts: &OpenOptions) -> io::Result<File> {
|
|
let (dir, file) = open_parent(path)?;
|
|
open_at(&dir, &file, opts)
|
|
}
|
|
|
|
pub fn open_at(&self, path: &Path, opts: &OpenOptions) -> io::Result<File> {
|
|
open_at(&self.fd, path, opts)
|
|
}
|
|
|
|
pub fn file_attr(&self) -> io::Result<FileAttr> {
|
|
self.fd.filestat_get().map(|meta| FileAttr { meta })
|
|
}
|
|
|
|
pub fn metadata_at(&self, flags: wasi::Lookupflags, path: &Path) -> io::Result<FileAttr> {
|
|
metadata_at(&self.fd, flags, path)
|
|
}
|
|
|
|
pub fn fsync(&self) -> io::Result<()> {
|
|
self.fd.sync()
|
|
}
|
|
|
|
pub fn datasync(&self) -> io::Result<()> {
|
|
self.fd.datasync()
|
|
}
|
|
|
|
pub fn lock(&self) -> io::Result<()> {
|
|
unsupported()
|
|
}
|
|
|
|
pub fn lock_shared(&self) -> io::Result<()> {
|
|
unsupported()
|
|
}
|
|
|
|
pub fn try_lock(&self) -> io::Result<bool> {
|
|
unsupported()
|
|
}
|
|
|
|
pub fn try_lock_shared(&self) -> io::Result<bool> {
|
|
unsupported()
|
|
}
|
|
|
|
pub fn unlock(&self) -> io::Result<()> {
|
|
unsupported()
|
|
}
|
|
|
|
pub fn truncate(&self, size: u64) -> io::Result<()> {
|
|
self.fd.filestat_set_size(size)
|
|
}
|
|
|
|
pub fn read(&self, buf: &mut [u8]) -> io::Result<usize> {
|
|
self.read_vectored(&mut [IoSliceMut::new(buf)])
|
|
}
|
|
|
|
pub fn read_vectored(&self, bufs: &mut [IoSliceMut<'_>]) -> io::Result<usize> {
|
|
self.fd.read(bufs)
|
|
}
|
|
|
|
#[inline]
|
|
pub fn is_read_vectored(&self) -> bool {
|
|
true
|
|
}
|
|
|
|
pub fn read_buf(&self, cursor: BorrowedCursor<'_>) -> io::Result<()> {
|
|
self.fd.read_buf(cursor)
|
|
}
|
|
|
|
pub fn write(&self, buf: &[u8]) -> io::Result<usize> {
|
|
self.write_vectored(&[IoSlice::new(buf)])
|
|
}
|
|
|
|
pub fn write_vectored(&self, bufs: &[IoSlice<'_>]) -> io::Result<usize> {
|
|
self.fd.write(bufs)
|
|
}
|
|
|
|
#[inline]
|
|
pub fn is_write_vectored(&self) -> bool {
|
|
true
|
|
}
|
|
|
|
pub fn flush(&self) -> io::Result<()> {
|
|
Ok(())
|
|
}
|
|
|
|
pub fn seek(&self, pos: SeekFrom) -> io::Result<u64> {
|
|
self.fd.seek(pos)
|
|
}
|
|
|
|
pub fn duplicate(&self) -> io::Result<File> {
|
|
// https://github.com/CraneStation/wasmtime/blob/master/docs/WASI-rationale.md#why-no-dup
|
|
unsupported()
|
|
}
|
|
|
|
pub fn set_permissions(&self, _perm: FilePermissions) -> io::Result<()> {
|
|
// Permissions haven't been fully figured out in wasi yet, so this is
|
|
// likely temporary
|
|
unsupported()
|
|
}
|
|
|
|
pub fn set_times(&self, times: FileTimes) -> io::Result<()> {
|
|
let to_timestamp = |time: Option<SystemTime>| match time {
|
|
Some(time) if let Some(ts) = time.to_wasi_timestamp() => Ok(ts),
|
|
Some(_) => Err(io::const_error!(
|
|
io::ErrorKind::InvalidInput,
|
|
"timestamp is too large to set as a file time"
|
|
)),
|
|
None => Ok(0),
|
|
};
|
|
self.fd.filestat_set_times(
|
|
to_timestamp(times.accessed)?,
|
|
to_timestamp(times.modified)?,
|
|
times.accessed.map_or(0, |_| wasi::FSTFLAGS_ATIM)
|
|
| times.modified.map_or(0, |_| wasi::FSTFLAGS_MTIM),
|
|
)
|
|
}
|
|
|
|
pub fn read_link(&self, file: &Path) -> io::Result<PathBuf> {
|
|
read_link(&self.fd, file)
|
|
}
|
|
}
|
|
|
|
impl AsInner<WasiFd> for File {
|
|
#[inline]
|
|
fn as_inner(&self) -> &WasiFd {
|
|
&self.fd
|
|
}
|
|
}
|
|
|
|
impl IntoInner<WasiFd> for File {
|
|
fn into_inner(self) -> WasiFd {
|
|
self.fd
|
|
}
|
|
}
|
|
|
|
impl FromInner<WasiFd> for File {
|
|
fn from_inner(fd: WasiFd) -> File {
|
|
File { fd }
|
|
}
|
|
}
|
|
|
|
impl AsFd for File {
|
|
fn as_fd(&self) -> BorrowedFd<'_> {
|
|
self.fd.as_fd()
|
|
}
|
|
}
|
|
|
|
impl AsRawFd for File {
|
|
#[inline]
|
|
fn as_raw_fd(&self) -> RawFd {
|
|
self.fd.as_raw_fd()
|
|
}
|
|
}
|
|
|
|
impl IntoRawFd for File {
|
|
fn into_raw_fd(self) -> RawFd {
|
|
self.fd.into_raw_fd()
|
|
}
|
|
}
|
|
|
|
impl FromRawFd for File {
|
|
unsafe fn from_raw_fd(raw_fd: RawFd) -> Self {
|
|
unsafe { Self { fd: FromRawFd::from_raw_fd(raw_fd) } }
|
|
}
|
|
}
|
|
|
|
impl DirBuilder {
|
|
pub fn new() -> DirBuilder {
|
|
DirBuilder {}
|
|
}
|
|
|
|
pub fn mkdir(&self, p: &Path) -> io::Result<()> {
|
|
let (dir, file) = open_parent(p)?;
|
|
dir.create_directory(osstr2str(file.as_ref())?)
|
|
}
|
|
}
|
|
|
|
impl fmt::Debug for File {
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
f.debug_struct("File").field("fd", &self.as_raw_fd()).finish()
|
|
}
|
|
}
|
|
|
|
pub fn readdir(p: &Path) -> io::Result<ReadDir> {
|
|
let mut opts = OpenOptions::new();
|
|
opts.directory(true);
|
|
opts.read(true);
|
|
let dir = File::open(p, &opts)?;
|
|
Ok(ReadDir::new(dir, p.to_path_buf()))
|
|
}
|
|
|
|
pub fn unlink(p: &Path) -> io::Result<()> {
|
|
let (dir, file) = open_parent(p)?;
|
|
dir.unlink_file(osstr2str(file.as_ref())?)
|
|
}
|
|
|
|
pub fn rename(old: &Path, new: &Path) -> io::Result<()> {
|
|
let (old, old_file) = open_parent(old)?;
|
|
let (new, new_file) = open_parent(new)?;
|
|
old.rename(osstr2str(old_file.as_ref())?, &new, osstr2str(new_file.as_ref())?)
|
|
}
|
|
|
|
pub fn set_perm(_p: &Path, _perm: FilePermissions) -> io::Result<()> {
|
|
// Permissions haven't been fully figured out in wasi yet, so this is
|
|
// likely temporary
|
|
unsupported()
|
|
}
|
|
|
|
pub fn rmdir(p: &Path) -> io::Result<()> {
|
|
let (dir, file) = open_parent(p)?;
|
|
dir.remove_directory(osstr2str(file.as_ref())?)
|
|
}
|
|
|
|
pub fn readlink(p: &Path) -> io::Result<PathBuf> {
|
|
let (dir, file) = open_parent(p)?;
|
|
read_link(&dir, &file)
|
|
}
|
|
|
|
fn read_link(fd: &WasiFd, file: &Path) -> io::Result<PathBuf> {
|
|
// Try to get a best effort initial capacity for the vector we're going to
|
|
// fill. Note that if it's not a symlink we don't use a file to avoid
|
|
// allocating gigabytes if you read_link a huge movie file by accident.
|
|
// Additionally we add 1 to the initial size so if it doesn't change until
|
|
// when we call `readlink` the returned length will be less than the
|
|
// capacity, guaranteeing that we got all the data.
|
|
let meta = metadata_at(fd, 0, file)?;
|
|
let initial_size = if meta.file_type().is_symlink() {
|
|
(meta.size() as usize).saturating_add(1)
|
|
} else {
|
|
1 // this'll fail in just a moment
|
|
};
|
|
|
|
// Now that we have an initial guess of how big to make our buffer, call
|
|
// `readlink` in a loop until it fails or reports it filled fewer bytes than
|
|
// we asked for, indicating we got everything.
|
|
let file = osstr2str(file.as_ref())?;
|
|
let mut destination = vec![0u8; initial_size];
|
|
loop {
|
|
let len = fd.readlink(file, &mut destination)?;
|
|
if len < destination.len() {
|
|
destination.truncate(len);
|
|
destination.shrink_to_fit();
|
|
return Ok(PathBuf::from(OsString::from_vec(destination)));
|
|
}
|
|
let amt_to_add = destination.len();
|
|
destination.extend(iter::repeat(0).take(amt_to_add));
|
|
}
|
|
}
|
|
|
|
pub fn symlink(original: &Path, link: &Path) -> io::Result<()> {
|
|
let (link, link_file) = open_parent(link)?;
|
|
link.symlink(osstr2str(original.as_ref())?, osstr2str(link_file.as_ref())?)
|
|
}
|
|
|
|
pub fn link(original: &Path, link: &Path) -> io::Result<()> {
|
|
let (original, original_file) = open_parent(original)?;
|
|
let (link, link_file) = open_parent(link)?;
|
|
// Pass 0 as the flags argument, meaning don't follow symlinks.
|
|
original.link(0, osstr2str(original_file.as_ref())?, &link, osstr2str(link_file.as_ref())?)
|
|
}
|
|
|
|
pub fn stat(p: &Path) -> io::Result<FileAttr> {
|
|
let (dir, file) = open_parent(p)?;
|
|
metadata_at(&dir, wasi::LOOKUPFLAGS_SYMLINK_FOLLOW, &file)
|
|
}
|
|
|
|
pub fn lstat(p: &Path) -> io::Result<FileAttr> {
|
|
let (dir, file) = open_parent(p)?;
|
|
metadata_at(&dir, 0, &file)
|
|
}
|
|
|
|
fn metadata_at(fd: &WasiFd, flags: wasi::Lookupflags, path: &Path) -> io::Result<FileAttr> {
|
|
let meta = fd.path_filestat_get(flags, osstr2str(path.as_ref())?)?;
|
|
Ok(FileAttr { meta })
|
|
}
|
|
|
|
pub fn canonicalize(_p: &Path) -> io::Result<PathBuf> {
|
|
// This seems to not be in wasi's API yet, and we may need to end up
|
|
// emulating it ourselves. For now just return an error.
|
|
unsupported()
|
|
}
|
|
|
|
fn open_at(fd: &WasiFd, path: &Path, opts: &OpenOptions) -> io::Result<File> {
|
|
let fd = fd.open(
|
|
opts.dirflags,
|
|
osstr2str(path.as_ref())?,
|
|
opts.oflags,
|
|
opts.rights_base(),
|
|
opts.rights_inheriting(),
|
|
opts.fdflags,
|
|
)?;
|
|
Ok(File { fd })
|
|
}
|
|
|
|
/// Attempts to open a bare path `p`.
|
|
///
|
|
/// WASI has no fundamental capability to do this. All syscalls and operations
|
|
/// are relative to already-open file descriptors. The C library, however,
|
|
/// manages a map of pre-opened file descriptors to their path, and then the C
|
|
/// library provides an API to look at this. In other words, when you want to
|
|
/// open a path `p`, you have to find a previously opened file descriptor in a
|
|
/// global table and then see if `p` is relative to that file descriptor.
|
|
///
|
|
/// This function, if successful, will return two items:
|
|
///
|
|
/// * The first is a `ManuallyDrop<WasiFd>`. This represents a pre-opened file
|
|
/// descriptor which we don't have ownership of, but we can use. You shouldn't
|
|
/// actually drop the `fd`.
|
|
///
|
|
/// * The second is a path that should be a part of `p` and represents a
|
|
/// relative traversal from the file descriptor specified to the desired
|
|
/// location `p`.
|
|
///
|
|
/// If successful you can use the returned file descriptor to perform
|
|
/// file-descriptor-relative operations on the path returned as well. The
|
|
/// `rights` argument indicates what operations are desired on the returned file
|
|
/// descriptor, and if successful the returned file descriptor should have the
|
|
/// appropriate rights for performing `rights` actions.
|
|
///
|
|
/// Note that this can fail if `p` doesn't look like it can be opened relative
|
|
/// to any pre-opened file descriptor.
|
|
fn open_parent(p: &Path) -> io::Result<(ManuallyDrop<WasiFd>, PathBuf)> {
|
|
run_path_with_cstr(p, &|p| {
|
|
let mut buf = Vec::<u8>::with_capacity(512);
|
|
loop {
|
|
unsafe {
|
|
let mut relative_path = buf.as_ptr().cast();
|
|
let mut abs_prefix = ptr::null();
|
|
let fd = __wasilibc_find_relpath(
|
|
p.as_ptr(),
|
|
&mut abs_prefix,
|
|
&mut relative_path,
|
|
buf.capacity(),
|
|
);
|
|
if fd == -1 {
|
|
if io::Error::last_os_error().raw_os_error() == Some(libc::ENOMEM) {
|
|
// Trigger the internal buffer resizing logic of `Vec` by requiring
|
|
// more space than the current capacity.
|
|
let cap = buf.capacity();
|
|
buf.set_len(cap);
|
|
buf.reserve(1);
|
|
continue;
|
|
}
|
|
let msg = format!(
|
|
"failed to find a pre-opened file descriptor \
|
|
through which {:?} could be opened",
|
|
p
|
|
);
|
|
return Err(io::Error::new(io::ErrorKind::Uncategorized, msg));
|
|
}
|
|
let relative = CStr::from_ptr(relative_path).to_bytes().to_vec();
|
|
|
|
return Ok((
|
|
ManuallyDrop::new(WasiFd::from_raw_fd(fd as c_int)),
|
|
PathBuf::from(OsString::from_vec(relative)),
|
|
));
|
|
}
|
|
}
|
|
|
|
extern "C" {
|
|
pub fn __wasilibc_find_relpath(
|
|
path: *const libc::c_char,
|
|
abs_prefix: *mut *const libc::c_char,
|
|
relative_path: *mut *const libc::c_char,
|
|
relative_path_len: libc::size_t,
|
|
) -> libc::c_int;
|
|
}
|
|
})
|
|
}
|
|
|
|
pub fn osstr2str(f: &OsStr) -> io::Result<&str> {
|
|
f.to_str().ok_or_else(|| io::const_error!(io::ErrorKind::Uncategorized, "input must be utf-8"))
|
|
}
|
|
|
|
pub fn copy(from: &Path, to: &Path) -> io::Result<u64> {
|
|
use crate::fs::File;
|
|
|
|
let mut reader = File::open(from)?;
|
|
let mut writer = File::create(to)?;
|
|
|
|
io::copy(&mut reader, &mut writer)
|
|
}
|
|
|
|
pub fn remove_dir_all(path: &Path) -> io::Result<()> {
|
|
let (parent, path) = open_parent(path)?;
|
|
remove_dir_all_recursive(&parent, &path)
|
|
}
|
|
|
|
fn remove_dir_all_recursive(parent: &WasiFd, path: &Path) -> io::Result<()> {
|
|
// Open up a file descriptor for the directory itself. Note that we don't
|
|
// follow symlinks here and we specifically open directories.
|
|
//
|
|
// At the root invocation of this function this will correctly handle
|
|
// symlinks passed to the top-level `remove_dir_all`. At the recursive
|
|
// level this will double-check that after the `readdir` call deduced this
|
|
// was a directory it's still a directory by the time we open it up.
|
|
//
|
|
// If the opened file was actually a symlink then the symlink is deleted,
|
|
// not the directory recursively.
|
|
let mut opts = OpenOptions::new();
|
|
opts.lookup_flags(0);
|
|
opts.directory(true);
|
|
opts.read(true);
|
|
let fd = open_at(parent, path, &opts)?;
|
|
if fd.file_attr()?.file_type().is_symlink() {
|
|
return parent.unlink_file(osstr2str(path.as_ref())?);
|
|
}
|
|
|
|
// this "root" is only used by `DirEntry::path` which we don't use below so
|
|
// it's ok for this to be a bogus value
|
|
let dummy_root = PathBuf::new();
|
|
|
|
// Iterate over all the entries in this directory, and travel recursively if
|
|
// necessary
|
|
for entry in ReadDir::new(fd, dummy_root) {
|
|
let entry = entry?;
|
|
let path = crate::str::from_utf8(&entry.name).map_err(|_| {
|
|
io::const_error!(io::ErrorKind::Uncategorized, "invalid utf-8 file name found")
|
|
})?;
|
|
|
|
let result: io::Result<()> = try {
|
|
if entry.file_type()?.is_dir() {
|
|
remove_dir_all_recursive(&entry.inner.dir.fd, path.as_ref())?;
|
|
} else {
|
|
entry.inner.dir.fd.unlink_file(path)?;
|
|
}
|
|
};
|
|
// ignore internal NotFound errors
|
|
if let Err(err) = &result
|
|
&& err.kind() != io::ErrorKind::NotFound
|
|
{
|
|
return result;
|
|
}
|
|
}
|
|
|
|
// Once all this directory's contents are deleted it should be safe to
|
|
// delete the directory tiself.
|
|
ignore_notfound(parent.remove_directory(osstr2str(path.as_ref())?))
|
|
}
|