wasi: Fill out `std::fs` module for WASI

This commit fills out the `std::fs` module and implementation for WASI.
Not all APIs are implemented, such as permissions-related ones and
`canonicalize`, but all others APIs have been implemented and very
lightly tested so far. We'll eventually want to run a more exhaustive
test suite!

For now the highlights of this commit are:

* The `std::fs::File` type is now backed by `WasiFd`, a raw WASI file
  descriptor.
* All APIs in `std::fs` (except permissions/canonicalize) have
  implementations for the WASI target.
* A suite of unstable extension traits were added to
  `std::os::wasi::fs`. These traits expose the raw filesystem
  functionality of WASI, namely `*at` syscalls (opening a file relative
  to an already opened one, for example). Additionally metadata only
  available on wasi is exposed through these traits.

Perhaps one of the most notable parts is the implementation of
path-taking APIs. WASI actually has no fundamental API that just takes a
path, but rather everything is relative to a previously opened file
descriptor. To allow existing APIs to work (that only take a path) WASI
has a few syscalls to learn about "pre opened" file descriptors by the
runtime. We use these to build a map of existing directory names to file
descriptors, and then when using a path we try to anchor it at an
already-opened file.

This support is very rudimentary though and is intended to be shared
with C since it's likely to be so tricky. For now though the C library
doesn't expose quite an API for us to use, so we implement it for now
and will swap it out as soon as one is available.
This commit is contained in:
Alex Crichton 2019-03-29 15:57:14 -07:00
parent 32a76844c4
commit 61b487ca8b
13 changed files with 1140 additions and 261 deletions

View File

@ -881,6 +881,10 @@ impl OpenOptions {
}
}
impl AsInner<fs_imp::OpenOptions> for OpenOptions {
fn as_inner(&self) -> &fs_imp::OpenOptions { &self.0 }
}
impl AsInnerMut<fs_imp::OpenOptions> for OpenOptions {
fn as_inner_mut(&mut self) -> &mut fs_imp::OpenOptions { &mut self.0 }
}
@ -1104,6 +1108,10 @@ impl AsInner<fs_imp::FileAttr> for Metadata {
fn as_inner(&self) -> &fs_imp::FileAttr { &self.0 }
}
impl FromInner<fs_imp::FileAttr> for Metadata {
fn from_inner(attr: fs_imp::FileAttr) -> Metadata { Metadata(attr) }
}
impl Permissions {
/// Returns `true` if these permissions describe a readonly (unwritable) file.
///

View File

@ -2,7 +2,7 @@ use crate::os::unix::prelude::*;
use crate::ffi::{OsString, OsStr};
use crate::fmt;
use crate::io::{self, Error, ErrorKind, SeekFrom};
use crate::io::{self, Error, SeekFrom};
use crate::path::{Path, PathBuf};
use crate::sync::Arc;
use crate::sys::fd::FileDesc;
@ -10,6 +10,9 @@ use crate::sys::time::SystemTime;
use crate::sys::{cvt, syscall};
use crate::sys_common::{AsInner, FromInner};
pub use crate::sys_common::fs::copy;
pub use crate::sys_common::fs::remove_dir_all;
pub struct File(FileDesc);
#[derive(Clone)]
@ -392,27 +395,6 @@ pub fn rmdir(p: &Path) -> io::Result<()> {
Ok(())
}
pub fn remove_dir_all(path: &Path) -> io::Result<()> {
let filetype = lstat(path)?.file_type();
if filetype.is_symlink() {
unlink(path)
} else {
remove_dir_all_recursive(path)
}
}
fn remove_dir_all_recursive(path: &Path) -> io::Result<()> {
for child in readdir(path)? {
let child = child?;
if child.file_type()?.is_dir() {
remove_dir_all_recursive(&child.path())?;
} else {
unlink(&child.path())?;
}
}
rmdir(path)
}
pub fn readlink(p: &Path) -> io::Result<PathBuf> {
let fd = cvt(syscall::open(p.to_str().unwrap(),
syscall::O_CLOEXEC | syscall::O_SYMLINK | syscall::O_RDONLY))?;
@ -455,19 +437,3 @@ pub fn canonicalize(p: &Path) -> io::Result<PathBuf> {
let file = File(FileDesc::new(fd));
file.path()
}
pub fn copy(from: &Path, to: &Path) -> io::Result<u64> {
use crate::fs::{File, set_permissions};
if !from.is_file() {
return Err(Error::new(ErrorKind::InvalidInput,
"the source path is not an existing regular file"))
}
let mut reader = File::open(from)?;
let mut writer = File::create(to)?;
let perm = reader.metadata()?.permissions();
let ret = io::copy(&mut reader, &mut writer)?;
set_permissions(to, perm)?;
Ok(ret)
}

View File

@ -36,6 +36,8 @@ use libc::{stat as stat64, fstat as fstat64, lstat as lstat64, off_t as off64_t,
target_os = "fuchsia")))]
use libc::{readdir_r as readdir64_r};
pub use crate::sys_common::fs::remove_dir_all;
pub struct File(FileDesc);
#[derive(Clone)]
@ -734,27 +736,6 @@ pub fn rmdir(p: &Path) -> io::Result<()> {
Ok(())
}
pub fn remove_dir_all(path: &Path) -> io::Result<()> {
let filetype = lstat(path)?.file_type();
if filetype.is_symlink() {
unlink(path)
} else {
remove_dir_all_recursive(path)
}
}
fn remove_dir_all_recursive(path: &Path) -> io::Result<()> {
for child in readdir(path)? {
let child = child?;
if child.file_type()?.is_dir() {
remove_dir_all_recursive(&child.path())?;
} else {
unlink(&child.path())?;
}
}
rmdir(path)
}
pub fn readlink(p: &Path) -> io::Result<PathBuf> {
let c_path = cstr(p)?;
let p = c_path.as_ptr();

View File

@ -2,60 +2,5 @@
#![stable(feature = "rust1", since = "1.0.0")]
use crate::ffi::{OsStr, OsString};
use crate::mem;
use crate::sys::os_str::Buf;
use crate::sys_common::{FromInner, IntoInner, AsInner};
/// WASI-specific extensions to [`OsString`].
///
/// [`OsString`]: ../../../../std/ffi/struct.OsString.html
#[stable(feature = "rust1", since = "1.0.0")]
pub trait OsStringExt {
/// Creates an `OsString` from a byte vector.
#[stable(feature = "rust1", since = "1.0.0")]
fn from_vec(vec: Vec<u8>) -> Self;
/// Yields the underlying byte vector of this `OsString`.
#[stable(feature = "rust1", since = "1.0.0")]
fn into_vec(self) -> Vec<u8>;
}
#[stable(feature = "rust1", since = "1.0.0")]
impl OsStringExt for OsString {
fn from_vec(vec: Vec<u8>) -> OsString {
FromInner::from_inner(Buf { inner: vec })
}
fn into_vec(self) -> Vec<u8> {
self.into_inner().inner
}
}
/// WASI-specific extensions to [`OsStr`].
///
/// [`OsStr`]: ../../../../std/ffi/struct.OsStr.html
#[stable(feature = "rust1", since = "1.0.0")]
pub trait OsStrExt {
#[stable(feature = "rust1", since = "1.0.0")]
/// Creates an [`OsStr`] from a byte slice.
///
/// [`OsStr`]: ../../../ffi/struct.OsStr.html
fn from_bytes(slice: &[u8]) -> &Self;
/// Gets the underlying byte view of the [`OsStr`] slice.
///
/// [`OsStr`]: ../../../ffi/struct.OsStr.html
#[stable(feature = "rust1", since = "1.0.0")]
fn as_bytes(&self) -> &[u8];
}
#[stable(feature = "rust1", since = "1.0.0")]
impl OsStrExt for OsStr {
fn from_bytes(slice: &[u8]) -> &OsStr {
unsafe { mem::transmute(slice) }
}
fn as_bytes(&self) -> &[u8] {
&self.as_inner().inner
}
}
pub use crate::sys_common::os_str_bytes::*;

View File

@ -0,0 +1,412 @@
//! WASI-specific extensions to primitives in the `std::fs` module.
#![unstable(feature = "wasi_ext", issue = "0")]
use crate::fs::{self, File, Metadata, OpenOptions};
use crate::io::{self, IoVec, IoVecMut};
use crate::os::wasi::ffi::OsStrExt;
use crate::path::{Path, PathBuf};
use crate::sys_common::{AsInner, AsInnerMut, FromInner};
/// WASI-specific extensions to [`File`].
///
/// [`File`]: ../../../../std/fs/struct.File.html
pub trait FileExt {
/// Reads a number of bytes starting from a given offset.
///
/// Returns the number of bytes read.
///
/// The offset is relative to the start of the file and thus independent
/// from the current cursor.
///
/// The current file cursor is not affected by this function.
///
/// Note that similar to [`File::read_vectored`], it is not an error to
/// return with a short read.
///
/// [`File::read`]: ../../../../std/fs/struct.File.html#method.read_vectored
fn read_at(&self, bufs: &mut [IoVecMut<'_>], offset: u64) -> io::Result<usize>;
/// Writes a number of bytes starting from a given offset.
///
/// Returns the number of bytes written.
///
/// The offset is relative to the start of the file and thus independent
/// from the current cursor.
///
/// The current file cursor is not affected by this function.
///
/// When writing beyond the end of the file, the file is appropriately
/// extended and the intermediate bytes are initialized with the value 0.
///
/// Note that similar to [`File::write_vectored`], it is not an error to return a
/// short write.
///
/// [`File::write`]: ../../../../std/fs/struct.File.html#method.write_vectored
fn write_at(&self, bufs: &[IoVec<'_>], offset: u64) -> io::Result<usize>;
/// Returns the current position within the file.
///
/// This corresponds to the `__wasi_fd_tell` syscall and is similar to
/// `seek` where you offset 0 bytes from the current position.
fn tell(&self) -> io::Result<u64>;
/// Adjust the flags associated with this file.
///
/// This corresponds to the `__wasi_fd_fdstat_set_flags` syscall.
fn fdstat_set_flags(&self, flags: u16) -> io::Result<()>;
/// Adjust the rights associated with this file.
///
/// This corresponds to the `__wasi_fd_fdstat_set_rights` syscall.
fn fdstat_set_rights(&self, rights: u64, inheriting: u64) -> io::Result<()>;
/// Provide file advisory information on a file descriptor.
///
/// This corresponds to the `__wasi_fd_advise` syscall.
fn advise(&self, offset: u64, len: u64, advice: u8) -> io::Result<()>;
/// Force the allocation of space in a file.
///
/// This corresponds to the `__wasi_fd_allocate` syscall.
fn allocate(&self, offset: u64, len: u64) -> io::Result<()>;
/// Create a directory.
///
/// This corresponds to the `__wasi_path_create_directory` syscall.
fn create_directory<P: AsRef<Path>>(&self, dir: P) -> io::Result<()>;
/// Read the contents of a symbolic link.
///
/// This corresponds to the `__wasi_path_readlink` syscall.
fn read_link<P: AsRef<Path>>(&self, path: P) -> io::Result<PathBuf>;
/// Return the attributes of a file or directory.
///
/// This corresponds to the `__wasi_path_filestat_get` syscall.
fn metadata_at<P: AsRef<Path>>(&self, lookup_flags: u32, path: P) -> io::Result<Metadata>;
/// Unlink a file.
///
/// This corresponds to the `__wasi_path_unlink_file` syscall.
fn remove_file<P: AsRef<Path>>(&self, path: P) -> io::Result<()>;
/// Remove a directory.
///
/// This corresponds to the `__wasi_path_remove_directory` syscall.
fn remove_directory<P: AsRef<Path>>(&self, path: P) -> io::Result<()>;
}
// FIXME: bind __wasi_fd_fdstat_get - need to define a custom return type
// FIXME: bind __wasi_fd_readdir - can't return `ReadDir` since we only have entry name
// FIXME: bind __wasi_fd_filestat_set_times maybe? - on crates.io for unix
// FIXME: bind __wasi_path_filestat_set_times maybe? - on crates.io for unix
// FIXME: bind __wasi_poll_oneoff maybe? - probably should wait for I/O to settle
// FIXME: bind __wasi_random_get maybe? - on crates.io for unix
impl FileExt for fs::File {
fn read_at(&self, bufs: &mut [IoVecMut<'_>], offset: u64) -> io::Result<usize> {
self.as_inner().fd().pread(bufs, offset)
}
fn write_at(&self, bufs: &[IoVec<'_>], offset: u64) -> io::Result<usize> {
self.as_inner().fd().pwrite(bufs, offset)
}
fn tell(&self) -> io::Result<u64> {
self.as_inner().fd().tell()
}
fn fdstat_set_flags(&self, flags: u16) -> io::Result<()> {
self.as_inner().fd().set_flags(flags)
}
fn fdstat_set_rights(&self, rights: u64, inheriting: u64) -> io::Result<()> {
self.as_inner().fd().set_rights(rights, inheriting)
}
fn advise(&self, offset: u64, len: u64, advice: u8) -> io::Result<()> {
self.as_inner().fd().advise(offset, len, advice)
}
fn allocate(&self, offset: u64, len: u64) -> io::Result<()> {
self.as_inner().fd().allocate(offset, len)
}
fn create_directory<P: AsRef<Path>>(&self, dir: P) -> io::Result<()> {
self.as_inner()
.fd()
.create_directory(dir.as_ref().as_os_str().as_bytes())
}
fn read_link<P: AsRef<Path>>(&self, path: P) -> io::Result<PathBuf> {
self.as_inner().read_link(path.as_ref())
}
fn metadata_at<P: AsRef<Path>>(&self, lookup_flags: u32, path: P) -> io::Result<Metadata> {
let m = self.as_inner().metadata_at(lookup_flags, path.as_ref())?;
Ok(FromInner::from_inner(m))
}
fn remove_file<P: AsRef<Path>>(&self, path: P) -> io::Result<()> {
self.as_inner()
.fd()
.unlink_file(path.as_ref().as_os_str().as_bytes())
}
fn remove_directory<P: AsRef<Path>>(&self, path: P) -> io::Result<()> {
self.as_inner()
.fd()
.remove_directory(path.as_ref().as_os_str().as_bytes())
}
}
/// WASI-specific extensions to [`fs::OpenOptions`].
///
/// [`fs::OpenOptions`]: ../../../../std/fs/struct.OpenOptions.html
pub trait OpenOptionsExt {
/// Pass custom `dirflags` argument to `__wasi_path_open`.
///
/// This option configures the `dirflags` argument to the
/// `__wasi_path_open` syscall which `OpenOptions` will eventually call. The
/// `dirflags` argument configures how the file is looked up, currently
/// primarily affecting whether symlinks are followed or not.
///
/// By default this value is `__WASI_LOOKUP_SYMLINK_FOLLOW`, or symlinks are
/// followed. You can call this method with 0 to disable following symlinks
fn lookup_flags(&mut self, flags: u32) -> &mut Self;
/// Indicates whether `OpenOptions` must open a directory or not.
///
/// This method will configure whether the `__WASI_O_DIRECTORY` flag is
/// passed when opening a file. When passed it will require that the opened
/// path is a directory.
///
/// This option is by default `false`
fn directory(&mut self, dir: bool) -> &mut Self;
/// Indicates whether `__WASI_FDFLAG_DSYNC` is passed in the `fs_flags`
/// field of `__wasi_path_open`.
///
/// This option is by default `false`
fn dsync(&mut self, dsync: bool) -> &mut Self;
/// Indicates whether `__WASI_FDFLAG_NONBLOCK` is passed in the `fs_flags`
/// field of `__wasi_path_open`.
///
/// This option is by default `false`
fn nonblock(&mut self, nonblock: bool) -> &mut Self;
/// Indicates whether `__WASI_FDFLAG_RSYNC` is passed in the `fs_flags`
/// field of `__wasi_path_open`.
///
/// This option is by default `false`
fn rsync(&mut self, rsync: bool) -> &mut Self;
/// Indicates whether `__WASI_FDFLAG_SYNC` is passed in the `fs_flags`
/// field of `__wasi_path_open`.
///
/// This option is by default `false`
fn sync(&mut self, sync: bool) -> &mut Self;
/// Indicates the value that should be passed in for the `fs_rights_base`
/// parameter of `__wasi_path_open`.
///
/// This option defaults based on the `read` and `write` configuration of
/// this `OpenOptions` builder. If this method is called, however, the
/// exact mask passed in will be used instead.
fn fs_rights_base(&mut self, rights: u64) -> &mut Self;
/// Indicates the value that should be passed in for the
/// `fs_rights_inheriting` parameter of `__wasi_path_open`.
///
/// The default for this option is the same value as what will be passed
/// for the `fs_rights_base` parameter but if this method is called then
/// the specified value will be used instead.
fn fs_rights_inheriting(&mut self, rights: u64) -> &mut Self;
/// Open a file or directory.
///
/// This corresponds to the `__wasi_path_open` syscall.
fn open_at<P: AsRef<Path>>(&self, file: &File, path: P) -> io::Result<File>;
}
impl OpenOptionsExt for OpenOptions {
fn lookup_flags(&mut self, flags: u32) -> &mut OpenOptions {
self.as_inner_mut().lookup_flags(flags);
self
}
fn directory(&mut self, dir: bool) -> &mut OpenOptions {
self.as_inner_mut().directory(dir);
self
}
fn dsync(&mut self, enabled: bool) -> &mut OpenOptions {
self.as_inner_mut().dsync(enabled);
self
}
fn nonblock(&mut self, enabled: bool) -> &mut OpenOptions {
self.as_inner_mut().nonblock(enabled);
self
}
fn rsync(&mut self, enabled: bool) -> &mut OpenOptions {
self.as_inner_mut().rsync(enabled);
self
}
fn sync(&mut self, enabled: bool) -> &mut OpenOptions {
self.as_inner_mut().sync(enabled);
self
}
fn fs_rights_base(&mut self, rights: u64) -> &mut OpenOptions {
self.as_inner_mut().fs_rights_base(rights);
self
}
fn fs_rights_inheriting(&mut self, rights: u64) -> &mut OpenOptions {
self.as_inner_mut().fs_rights_inheriting(rights);
self
}
fn open_at<P: AsRef<Path>>(&self, file: &File, path: P) -> io::Result<File> {
let inner = file.as_inner().open_at(path.as_ref(), self.as_inner())?;
Ok(File::from_inner(inner))
}
}
/// WASI-specific extensions to [`fs::Metadata`].
///
/// [`fs::Metadata`]: ../../../../std/fs/struct.Metadata.html
pub trait MetadataExt {
/// Returns the `st_dev` field of the internal `__wasi_filestat_t`
fn dev(&self) -> u64;
/// Returns the `st_ino` field of the internal `__wasi_filestat_t`
fn ino(&self) -> u64;
/// Returns the `st_nlink` field of the internal `__wasi_filestat_t`
fn nlink(&self) -> u32;
/// Returns the `st_atim` field of the internal `__wasi_filestat_t`
fn atim(&self) -> u64;
/// Returns the `st_mtim` field of the internal `__wasi_filestat_t`
fn mtim(&self) -> u64;
/// Returns the `st_ctim` field of the internal `__wasi_filestat_t`
fn ctim(&self) -> u64;
}
impl MetadataExt for fs::Metadata {
fn dev(&self) -> u64 {
self.as_inner().as_wasi().st_dev
}
fn ino(&self) -> u64 {
self.as_inner().as_wasi().st_ino
}
fn nlink(&self) -> u32 {
self.as_inner().as_wasi().st_nlink
}
fn atim(&self) -> u64 {
self.as_inner().as_wasi().st_atim
}
fn mtim(&self) -> u64 {
self.as_inner().as_wasi().st_mtim
}
fn ctim(&self) -> u64 {
self.as_inner().as_wasi().st_ctim
}
}
/// WASI-specific extensions for [`FileType`].
///
/// Adds support for special WASI file types such as block/character devices,
/// pipes, and sockets.
///
/// [`FileType`]: ../../../../std/fs/struct.FileType.html
pub trait FileTypeExt {
/// Returns `true` if this file type is a block device.
fn is_block_device(&self) -> bool;
/// Returns `true` if this file type is a character device.
fn is_character_device(&self) -> bool;
/// Returns `true` if this file type is a socket datagram.
fn is_socket_dgram(&self) -> bool;
/// Returns `true` if this file type is a socket stream.
fn is_socket_stream(&self) -> bool;
}
impl FileTypeExt for fs::FileType {
fn is_block_device(&self) -> bool {
self.as_inner().bits() == libc::__WASI_FILETYPE_BLOCK_DEVICE
}
fn is_character_device(&self) -> bool {
self.as_inner().bits() == libc::__WASI_FILETYPE_CHARACTER_DEVICE
}
fn is_socket_dgram(&self) -> bool {
self.as_inner().bits() == libc::__WASI_FILETYPE_SOCKET_DGRAM
}
fn is_socket_stream(&self) -> bool {
self.as_inner().bits() == libc::__WASI_FILETYPE_SOCKET_STREAM
}
}
/// WASI-specific extension methods for [`fs::DirEntry`].
///
/// [`fs::DirEntry`]: ../../../../std/fs/struct.DirEntry.html
pub trait DirEntryExt {
/// Returns the underlying `d_ino` field of the `__wasi_dirent_t`
fn ino(&self) -> u64;
}
impl DirEntryExt for fs::DirEntry {
fn ino(&self) -> u64 {
self.as_inner().ino()
}
}
/// Create a hard link.
///
/// This corresponds to the `__wasi_path_link` syscall.
pub fn link<P: AsRef<Path>, U: AsRef<Path>>(
old_fd: &File,
old_flags: u32,
old_path: P,
new_fd: &File,
new_path: U,
) -> io::Result<()> {
old_fd.as_inner().fd().link(
old_flags,
old_path.as_ref().as_os_str().as_bytes(),
new_fd.as_inner().fd(),
new_path.as_ref().as_os_str().as_bytes(),
)
}
/// Rename a file or directory.
///
/// This corresponds to the `__wasi_path_rename` syscall.
pub fn rename<P: AsRef<Path>, U: AsRef<Path>>(
old_fd: &File,
old_path: P,
new_fd: &File,
new_path: U,
) -> io::Result<()> {
old_fd.as_inner().fd().rename(
old_path.as_ref().as_os_str().as_bytes(),
new_fd.as_inner().fd(),
new_path.as_ref().as_os_str().as_bytes(),
)
}
/// Create a symbolic link.
///
/// This corresponds to the `__wasi_path_symlink` syscall.
pub fn symlink<P: AsRef<Path>, U: AsRef<Path>>(
old_path: P,
fd: &File,
new_path: U,
) -> io::Result<()> {
fd.as_inner().fd().symlink(
old_path.as_ref().as_os_str().as_bytes(),
new_path.as_ref().as_os_str().as_bytes(),
)
}

View File

@ -0,0 +1,87 @@
//! WASI-specific extensions to general I/O primitives
#![unstable(feature = "wasi_ext", issue = "0")]
use crate::fs;
use crate::io;
use crate::sys;
use crate::sys_common::{AsInner, FromInner, IntoInner};
/// Raw file descriptors.
pub type RawFd = u32;
/// A trait to extract the raw WASI file descriptor from an underlying
/// object.
pub trait AsRawFd {
/// Extracts the raw file descriptor.
///
/// This method does **not** pass ownership of the raw file descriptor
/// to the caller. The descriptor is only guaranteed to be valid while
/// the original object has not yet been destroyed.
fn as_raw_fd(&self) -> RawFd;
}
/// A trait to express the ability to construct an object from a raw file
/// descriptor.
pub trait FromRawFd {
/// Constructs a new instance of `Self` from the given raw file
/// descriptor.
///
/// This function **consumes ownership** of the specified file
/// descriptor. The returned object will take responsibility for closing
/// it when the object goes out of scope.
///
/// This function is also unsafe as the primitives currently returned
/// have the contract that they are the sole owner of the file
/// descriptor they are wrapping. Usage of this function could
/// accidentally allow violating this contract which can cause memory
/// unsafety in code that relies on it being true.
unsafe fn from_raw_fd(fd: RawFd) -> Self;
}
/// A trait to express the ability to consume an object and acquire ownership of
/// its raw file descriptor.
pub trait IntoRawFd {
/// Consumes this object, returning the raw underlying file descriptor.
///
/// This function **transfers ownership** of the underlying file descriptor
/// to the caller. Callers are then the unique owners of the file descriptor
/// and must close the descriptor once it's no longer needed.
fn into_raw_fd(self) -> RawFd;
}
impl AsRawFd for fs::File {
fn as_raw_fd(&self) -> RawFd {
self.as_inner().fd().as_raw()
}
}
impl FromRawFd for fs::File {
unsafe fn from_raw_fd(fd: RawFd) -> fs::File {
fs::File::from_inner(sys::fs::File::from_inner(fd))
}
}
impl IntoRawFd for fs::File {
fn into_raw_fd(self) -> RawFd {
self.into_inner().into_fd().into_raw()
}
}
impl AsRawFd for io::Stdin {
fn as_raw_fd(&self) -> RawFd {
libc::STDIN_FILENO as u32
}
}
impl AsRawFd for io::Stdout {
fn as_raw_fd(&self) -> RawFd {
libc::STDOUT_FILENO as u32
}
}
impl AsRawFd for io::Stderr {
fn as_raw_fd(&self) -> RawFd {
libc::STDERR_FILENO as u32
}
}

View File

@ -1,4 +1,6 @@
pub mod ffi;
pub mod fs;
pub mod io;
/// A prelude for conveniently writing platform-specific code.
///
@ -7,4 +9,10 @@ pub mod ffi;
pub mod prelude {
#[doc(no_inline)] #[stable(feature = "rust1", since = "1.0.0")]
pub use crate::sys::ext::ffi::{OsStringExt, OsStrExt};
#[doc(no_inline)] #[stable(feature = "rust1", since = "1.0.0")]
pub use crate::sys::ext::fs::{FileExt, DirEntryExt, MetadataExt, OpenOptionsExt};
#[doc(no_inline)] #[stable(feature = "rust1", since = "1.0.0")]
pub use crate::sys::ext::fs::FileTypeExt;
#[doc(no_inline)] #[stable(feature = "rust1", since = "1.0.0")]
pub use crate::sys::ext::io::{AsRawFd, IntoRawFd, FromRawFd};
}

View File

@ -6,6 +6,7 @@ use crate::net::Shutdown;
use crate::sys::cvt_wasi;
use libc::{self, c_char, c_void};
#[derive(Debug)]
pub struct WasiFd {
fd: libc::__wasi_fd_t,
}
@ -52,6 +53,16 @@ impl WasiFd {
WasiFd { fd }
}
pub fn into_raw(self) -> libc::__wasi_fd_t {
let ret = self.fd;
mem::forget(self);
ret
}
pub fn as_raw(&self) -> libc::__wasi_fd_t {
self.fd
}
pub fn datasync(&self) -> io::Result<()> {
cvt_wasi(unsafe { libc::__wasi_fd_datasync(self.fd) })
}
@ -123,7 +134,7 @@ impl WasiFd {
cvt_wasi(unsafe { libc::__wasi_fd_allocate(self.fd, offset, len) })
}
pub fn crate_directory(&self, path: &[u8]) -> io::Result<()> {
pub fn create_directory(&self, path: &[u8]) -> io::Result<()> {
cvt_wasi(unsafe {
libc::__wasi_path_create_directory(self.fd, path.as_ptr() as *const c_char, path.len())
})
@ -217,7 +228,9 @@ impl WasiFd {
})
}
// FIXME: __wasi_fd_filestat_get
pub fn filestat_get(&self, buf: *mut libc::__wasi_filestat_t) -> io::Result<()> {
cvt_wasi(unsafe { libc::__wasi_fd_filestat_get(self.fd, buf) })
}
pub fn filestat_set_times(
&self,
@ -232,7 +245,22 @@ impl WasiFd {
cvt_wasi(unsafe { libc::__wasi_fd_filestat_set_size(self.fd, size) })
}
// FIXME: __wasi_path_filestat_get
pub fn path_filestat_get(
&self,
flags: LookupFlags,
path: &[u8],
buf: *mut libc::__wasi_filestat_t,
) -> io::Result<()> {
cvt_wasi(unsafe {
libc::__wasi_path_filestat_get(
self.fd,
flags,
path.as_ptr() as *const c_char,
path.len(),
buf,
)
})
}
pub fn path_filestat_set_times(
&self,

View File

@ -1,138 +1,144 @@
use crate::ffi::OsString;
use crate::collections::HashMap;
use crate::ffi::{OsStr, OsString};
use crate::fmt;
use crate::hash::{Hash, Hasher};
use crate::io::{self, SeekFrom};
use crate::io::{self, IoVec, IoVecMut, SeekFrom};
use crate::iter;
use crate::mem::{self, ManuallyDrop};
use crate::os::wasi::ffi::{OsStrExt, OsStringExt};
use crate::path::{Path, PathBuf};
use crate::ptr;
use crate::sync::atomic::{AtomicPtr, Ordering::SeqCst};
use crate::sync::Arc;
use crate::sys::fd::{DirCookie, WasiFd};
use crate::sys::time::SystemTime;
use crate::sys::{unsupported, Void};
use crate::sys::{cvt_wasi, unsupported};
use crate::sys_common::FromInner;
pub struct File(Void);
pub use crate::sys_common::fs::copy;
pub use crate::sys_common::fs::remove_dir_all;
pub struct FileAttr(Void);
pub struct File {
fd: WasiFd,
}
pub struct ReadDir(Void);
#[derive(Clone)]
pub struct FileAttr {
meta: libc::__wasi_filestat_t,
}
pub struct DirEntry(Void);
pub struct ReadDir {
inner: Arc<ReadDirInner>,
cookie: Option<DirCookie>,
buf: Vec<u8>,
offset: usize,
cap: usize,
}
#[derive(Clone, Debug)]
pub struct OpenOptions { }
struct ReadDirInner {
root: PathBuf,
dir: File,
}
pub struct FilePermissions(Void);
pub struct DirEntry {
meta: libc::__wasi_dirent_t,
name: Vec<u8>,
inner: Arc<ReadDirInner>,
}
pub struct FileType(Void);
#[derive(Clone, Debug, Default)]
pub struct OpenOptions {
read: bool,
write: bool,
dirflags: libc::__wasi_lookupflags_t,
fdflags: libc::__wasi_fdflags_t,
oflags: libc::__wasi_oflags_t,
rights_base: Option<libc::__wasi_rights_t>,
rights_inheriting: Option<libc::__wasi_rights_t>,
}
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct FilePermissions {
readonly: bool,
}
#[derive(PartialEq, Eq, Hash, Debug, Copy, Clone)]
pub struct FileType {
bits: libc::__wasi_filetype_t,
}
#[derive(Debug)]
pub struct DirBuilder { }
pub struct DirBuilder {}
impl FileAttr {
fn zero() -> FileAttr {
FileAttr {
meta: unsafe { mem::zeroed() },
}
}
pub fn size(&self) -> u64 {
match self.0 {}
self.meta.st_size
}
pub fn perm(&self) -> FilePermissions {
match self.0 {}
// not currently implemented in wasi yet
FilePermissions { readonly: false }
}
pub fn file_type(&self) -> FileType {
match self.0 {}
FileType {
bits: self.meta.st_filetype,
}
}
pub fn modified(&self) -> io::Result<SystemTime> {
match self.0 {}
Ok(SystemTime::from_wasi_timestamp(self.meta.st_mtim))
}
pub fn accessed(&self) -> io::Result<SystemTime> {
match self.0 {}
Ok(SystemTime::from_wasi_timestamp(self.meta.st_atim))
}
pub fn created(&self) -> io::Result<SystemTime> {
match self.0 {}
Ok(SystemTime::from_wasi_timestamp(self.meta.st_ctim))
}
}
impl Clone for FileAttr {
fn clone(&self) -> FileAttr {
match self.0 {}
pub fn as_wasi(&self) -> &libc::__wasi_filestat_t {
&self.meta
}
}
impl FilePermissions {
pub fn readonly(&self) -> bool {
match self.0 {}
self.readonly
}
pub fn set_readonly(&mut self, _readonly: bool) {
match self.0 {}
}
}
impl Clone for FilePermissions {
fn clone(&self) -> FilePermissions {
match self.0 {}
}
}
impl PartialEq for FilePermissions {
fn eq(&self, _other: &FilePermissions) -> bool {
match self.0 {}
}
}
impl Eq for FilePermissions {
}
impl fmt::Debug for FilePermissions {
fn fmt(&self, _f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.0 {}
pub fn set_readonly(&mut self, readonly: bool) {
self.readonly = readonly;
}
}
impl FileType {
pub fn is_dir(&self) -> bool {
match self.0 {}
self.bits == libc::__WASI_FILETYPE_DIRECTORY
}
pub fn is_file(&self) -> bool {
match self.0 {}
self.bits == libc::__WASI_FILETYPE_REGULAR_FILE
}
pub fn is_symlink(&self) -> bool {
match self.0 {}
self.bits == libc::__WASI_FILETYPE_SYMBOLIC_LINK
}
}
impl Clone for FileType {
fn clone(&self) -> FileType {
match self.0 {}
}
}
impl Copy for FileType {}
impl PartialEq for FileType {
fn eq(&self, _other: &FileType) -> bool {
match self.0 {}
}
}
impl Eq for FileType {
}
impl Hash for FileType {
fn hash<H: Hasher>(&self, _h: &mut H) {
match self.0 {}
}
}
impl fmt::Debug for FileType {
fn fmt(&self, _f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.0 {}
pub fn bits(&self) -> libc::__wasi_filetype_t {
self.bits
}
}
impl fmt::Debug for ReadDir {
fn fmt(&self, _f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.0 {}
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("ReadDir").finish()
}
}
@ -140,155 +146,547 @@ impl Iterator for ReadDir {
type Item = io::Result<DirEntry>;
fn next(&mut self) -> Option<io::Result<DirEntry>> {
match self.0 {}
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::<libc::__wasi_dirent_t>();
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 libc::__wasi_dirent_t) };
// 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 {
match self.0 {}
let name = OsStr::from_bytes(&self.name);
self.inner.root.join(name)
}
pub fn file_name(&self) -> OsString {
match self.0 {}
OsString::from_vec(self.name.clone())
}
pub fn metadata(&self) -> io::Result<FileAttr> {
match self.0 {}
metadata_at(&self.inner.dir.fd, 0, OsStr::from_bytes(&self.name).as_ref())
}
pub fn file_type(&self) -> io::Result<FileType> {
match self.0 {}
Ok(FileType {
bits: self.meta.d_type,
})
}
pub fn ino(&self) -> libc::__wasi_inode_t {
self.meta.d_ino
}
}
impl OpenOptions {
pub fn new() -> OpenOptions {
OpenOptions { }
let mut base = OpenOptions::default();
base.dirflags = libc::__WASI_LOOKUP_SYMLINK_FOLLOW;
return base;
}
pub fn read(&mut self, _read: bool) { }
pub fn write(&mut self, _write: bool) { }
pub fn append(&mut self, _append: bool) { }
pub fn truncate(&mut self, _truncate: bool) { }
pub fn create(&mut self, _create: bool) { }
pub fn create_new(&mut self, _create_new: bool) { }
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(libc::__WASI_O_TRUNC, truncate);
}
pub fn create(&mut self, create: bool) {
self.oflag(libc::__WASI_O_CREAT, create);
}
pub fn create_new(&mut self, create_new: bool) {
self.oflag(libc::__WASI_O_EXCL, create_new);
self.oflag(libc::__WASI_O_CREAT, create_new);
}
pub fn directory(&mut self, directory: bool) {
self.oflag(libc::__WASI_O_DIRECTORY, directory);
}
fn oflag(&mut self, bit: libc::__wasi_oflags_t, set: bool) {
if set {
self.oflags |= bit;
} else {
self.oflags &= !bit;
}
}
pub fn append(&mut self, set: bool) {
self.fdflag(libc::__WASI_FDFLAG_APPEND, set);
}
pub fn dsync(&mut self, set: bool) {
self.fdflag(libc::__WASI_FDFLAG_DSYNC, set);
}
pub fn nonblock(&mut self, set: bool) {
self.fdflag(libc::__WASI_FDFLAG_NONBLOCK, set);
}
pub fn rsync(&mut self, set: bool) {
self.fdflag(libc::__WASI_FDFLAG_RSYNC, set);
}
pub fn sync(&mut self, set: bool) {
self.fdflag(libc::__WASI_FDFLAG_SYNC, set);
}
fn fdflag(&mut self, bit: libc::__wasi_fdflags_t, set: bool) {
if set {
self.fdflags |= bit;
} else {
self.fdflags &= !bit;
}
}
pub fn fs_rights_base(&mut self, rights: libc::__wasi_rights_t) {
self.rights_base = Some(rights);
}
pub fn fs_rights_inheriting(&mut self, rights: libc::__wasi_rights_t) {
self.rights_inheriting = Some(rights);
}
fn rights_base(&self) -> libc::__wasi_rights_t {
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 |= libc::__WASI_RIGHT_FD_READ;
base |= libc::__WASI_RIGHT_FD_READDIR;
}
if self.write {
base |= libc::__WASI_RIGHT_FD_WRITE;
base |= libc::__WASI_RIGHT_FD_DATASYNC;
base |= libc::__WASI_RIGHT_FD_ALLOCATE;
base |= libc::__WASI_RIGHT_FD_FILESTAT_SET_SIZE;
}
// FIXME: some of these should probably be read-only or write-only...
base |= libc::__WASI_RIGHT_FD_ADVISE;
base |= libc::__WASI_RIGHT_FD_FDSTAT_SET_FLAGS;
base |= libc::__WASI_RIGHT_FD_FILESTAT_SET_TIMES;
base |= libc::__WASI_RIGHT_FD_SEEK;
base |= libc::__WASI_RIGHT_FD_SYNC;
base |= libc::__WASI_RIGHT_FD_TELL;
base |= libc::__WASI_RIGHT_PATH_CREATE_DIRECTORY;
base |= libc::__WASI_RIGHT_PATH_CREATE_FILE;
base |= libc::__WASI_RIGHT_PATH_FILESTAT_GET;
base |= libc::__WASI_RIGHT_PATH_LINK_SOURCE;
base |= libc::__WASI_RIGHT_PATH_LINK_TARGET;
base |= libc::__WASI_RIGHT_PATH_OPEN;
base |= libc::__WASI_RIGHT_PATH_READLINK;
base |= libc::__WASI_RIGHT_PATH_REMOVE_DIRECTORY;
base |= libc::__WASI_RIGHT_PATH_RENAME_SOURCE;
base |= libc::__WASI_RIGHT_PATH_RENAME_TARGET;
base |= libc::__WASI_RIGHT_PATH_SYMLINK;
base |= libc::__WASI_RIGHT_PATH_UNLINK_FILE;
base |= libc::__WASI_RIGHT_POLL_FD_READWRITE;
return base;
}
fn rights_inheriting(&self) -> libc::__wasi_rights_t {
self.rights_inheriting.unwrap_or_else(|| self.rights_base())
}
pub fn lookup_flags(&mut self, flags: libc::__wasi_lookupflags_t) {
self.dirflags = flags;
}
}
impl File {
pub fn open(_path: &Path, _opts: &OpenOptions) -> io::Result<File> {
unsupported()
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> {
match self.0 {}
let mut ret = FileAttr::zero();
self.fd.filestat_get(&mut ret.meta)?;
Ok(ret)
}
pub fn metadata_at(
&self,
flags: libc::__wasi_lookupflags_t,
path: &Path,
) -> io::Result<FileAttr> {
metadata_at(&self.fd, flags, path)
}
pub fn fsync(&self) -> io::Result<()> {
match self.0 {}
self.fd.sync()
}
pub fn datasync(&self) -> io::Result<()> {
match self.0 {}
self.fd.datasync()
}
pub fn truncate(&self, _size: u64) -> io::Result<()> {
match self.0 {}
pub fn truncate(&self, size: u64) -> io::Result<()> {
self.fd.filestat_set_size(size)
}
pub fn read(&self, _buf: &mut [u8]) -> io::Result<usize> {
match self.0 {}
pub fn read(&self, buf: &mut [u8]) -> io::Result<usize> {
self.read_vectored(&mut [IoVecMut::new(buf)])
}
pub fn write(&self, _buf: &[u8]) -> io::Result<usize> {
match self.0 {}
pub fn read_vectored(&self, bufs: &mut [IoVecMut<'_>]) -> io::Result<usize> {
self.fd.read(bufs)
}
pub fn write(&self, buf: &[u8]) -> io::Result<usize> {
self.write_vectored(&[IoVec::new(buf)])
}
pub fn write_vectored(&self, bufs: &[IoVec<'_>]) -> io::Result<usize> {
self.fd.write(bufs)
}
pub fn flush(&self) -> io::Result<()> {
match self.0 {}
Ok(())
}
pub fn seek(&self, _pos: SeekFrom) -> io::Result<u64> {
match self.0 {}
pub fn seek(&self, pos: SeekFrom) -> io::Result<u64> {
self.fd.seek(pos)
}
pub fn duplicate(&self) -> io::Result<File> {
match self.0 {}
// https://github.com/CraneStation/wasmtime/blob/master/docs/WASI-rationale.md#why-no-dup
unsupported()
}
pub fn set_permissions(&self, _perm: FilePermissions) -> io::Result<()> {
match self.0 {}
// Permissions haven't been fully figured out in wasi yet, so this is
// likely temporary
unsupported()
}
pub fn diverge(&self) -> ! {
match self.0 {}
pub fn fd(&self) -> &WasiFd {
&self.fd
}
pub fn into_fd(self) -> WasiFd {
self.fd
}
pub fn read_link(&self, file: &Path) -> io::Result<PathBuf> {
read_link(&self.fd, file)
}
}
impl FromInner<u32> for File {
fn from_inner(fd: u32) -> File {
unsafe {
File {
fd: WasiFd::from_raw(fd),
}
}
}
}
impl DirBuilder {
pub fn new() -> DirBuilder {
DirBuilder { }
DirBuilder {}
}
pub fn mkdir(&self, _p: &Path) -> io::Result<()> {
unsupported()
pub fn mkdir(&self, p: &Path) -> io::Result<()> {
let (dir, file) = open_parent(p)?;
dir.create_directory(file.as_os_str().as_bytes())
}
}
impl fmt::Debug for File {
fn fmt(&self, _f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.0 {}
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("File")
.field("fd", &self.fd.as_raw())
.finish()
}
}
pub fn readdir(_p: &Path) -> io::Result<ReadDir> {
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 {
cookie: Some(0),
buf: vec![0; 128],
offset: 0,
cap: 0,
inner: Arc::new(ReadDirInner {
dir,
root: p.to_path_buf(),
}),
})
}
pub fn unlink(p: &Path) -> io::Result<()> {
let (dir, file) = open_parent(p)?;
dir.unlink_file(file.as_os_str().as_bytes())
}
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(
old_file.as_os_str().as_bytes(),
&new,
new_file.as_os_str().as_bytes(),
)
}
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 unlink(_p: &Path) -> io::Result<()> {
unsupported()
pub fn rmdir(p: &Path) -> io::Result<()> {
let (dir, file) = open_parent(p)?;
dir.remove_directory(file.as_os_str().as_bytes())
}
pub fn rename(_old: &Path, _new: &Path) -> io::Result<()> {
unsupported()
pub fn readlink(p: &Path) -> io::Result<PathBuf> {
let (dir, file) = open_parent(p)?;
read_link(&dir, file)
}
pub fn set_perm(_p: &Path, perm: FilePermissions) -> io::Result<()> {
match perm.0 {}
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 = file.as_os_str().as_bytes();
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 rmdir(_p: &Path) -> io::Result<()> {
unsupported()
pub fn symlink(src: &Path, dst: &Path) -> io::Result<()> {
let (dst, dst_file) = open_parent(dst)?;
dst.symlink(src.as_os_str().as_bytes(), dst_file.as_os_str().as_bytes())
}
pub fn remove_dir_all(_path: &Path) -> io::Result<()> {
unsupported()
pub fn link(src: &Path, dst: &Path) -> io::Result<()> {
let (src, src_file) = open_parent(src)?;
let (dst, dst_file) = open_parent(dst)?;
src.link(
libc::__WASI_LOOKUP_SYMLINK_FOLLOW,
src_file.as_os_str().as_bytes(),
&dst,
dst_file.as_os_str().as_bytes(),
)
}
pub fn readlink(_p: &Path) -> io::Result<PathBuf> {
unsupported()
pub fn stat(p: &Path) -> io::Result<FileAttr> {
let (dir, file) = open_parent(p)?;
metadata_at(&dir, libc::__WASI_LOOKUP_SYMLINK_FOLLOW, file)
}
pub fn symlink(_src: &Path, _dst: &Path) -> io::Result<()> {
unsupported()
pub fn lstat(p: &Path) -> io::Result<FileAttr> {
let (dir, file) = open_parent(p)?;
metadata_at(&dir, 0, file)
}
pub fn link(_src: &Path, _dst: &Path) -> io::Result<()> {
unsupported()
}
pub fn stat(_p: &Path) -> io::Result<FileAttr> {
unsupported()
}
pub fn lstat(_p: &Path) -> io::Result<FileAttr> {
unsupported()
fn metadata_at(
fd: &WasiFd,
flags: libc::__wasi_lookupflags_t,
path: &Path,
) -> io::Result<FileAttr> {
let mut ret = FileAttr::zero();
fd.path_filestat_get(flags, path.as_os_str().as_bytes(), &mut ret.meta)?;
Ok(ret)
}
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()
}
pub fn copy(_from: &Path, _to: &Path) -> io::Result<u64> {
unsupported()
fn open_at(fd: &WasiFd, path: &Path, opts: &OpenOptions) -> io::Result<File> {
let fd = fd.open(
opts.dirflags,
path.as_os_str().as_bytes(),
opts.oflags,
opts.rights_base(),
opts.rights_inheriting(),
opts.fdflags,
)?;
Ok(File { fd })
}
// FIXME: we shouldn't implement this. It'd be much better to share this between
// libc (the wasi-sysroot) and Rust as the logic here is likely far more tricky
// than what we're executing below. For now this is a stopgap to enable this
// module, but we should add an official API in upstream wasi-libc which looks
// like this.
//
// In the meantime this is highly unlikely to be correct. It allows some basic
// testing but is not at all robust.
fn open_parent(p: &Path) -> io::Result<(&'static WasiFd, &Path)> {
let map = preopened_map();
for ancestor in p.ancestors() {
if let Some(fd) = map.get(ancestor) {
let tail = p.strip_prefix(ancestor).unwrap();
let tail = if tail == Path::new("") {
".".as_ref()
} else {
tail
};
return Ok((fd, tail))
}
}
let msg = format!("failed to find a preopened file descriptor to open {:?}", p);
return Err(io::Error::new(io::ErrorKind::Other, msg));
type Preopened = HashMap<PathBuf, ManuallyDrop<WasiFd>>;
fn preopened_map() -> &'static Preopened {
static PTR: AtomicPtr<Preopened> = AtomicPtr::new(ptr::null_mut());
unsafe {
let ptr = PTR.load(SeqCst);
if !ptr.is_null() {
return &*ptr;
}
let mut map = Box::new(HashMap::new());
for fd in 3.. {
let mut buf = mem::zeroed();
if cvt_wasi(libc::__wasi_fd_prestat_get(fd, &mut buf)).is_err() {
break;
}
if buf.pr_type != libc::__WASI_PREOPENTYPE_DIR {
continue;
}
let len = buf.u.dir.pr_name_len;
let mut v = vec![0u8; len];
let res = cvt_wasi(libc::__wasi_fd_prestat_dir_name(
fd,
v.as_mut_ptr() as *mut i8,
v.len(),
));
if res.is_err() {
continue;
}
let path = PathBuf::from(OsString::from_vec(v));
map.insert(path, ManuallyDrop::new(WasiFd::from_raw(fd)));
}
let ptr = Box::into_raw(map);
match PTR.compare_exchange(ptr::null_mut(), ptr, SeqCst, SeqCst) {
Ok(_) => &*ptr,
// If we lost the race for initialization clean up the map we
// made and just use the one that's already there
Err(other) => {
drop(Box::from_raw(ptr));
&*other
}
}
}
}
}

View File

@ -67,8 +67,8 @@ impl From<AnonPipe> for Stdio {
}
impl From<File> for Stdio {
fn from(file: File) -> Stdio {
file.diverge()
fn from(_file: File) -> Stdio {
panic!("unsupported")
}
}

View File

@ -57,6 +57,10 @@ impl SystemTime {
SystemTime(current_time(libc::__WASI_CLOCK_REALTIME))
}
pub fn from_wasi_timestamp(ts: libc::__wasi_timestamp_t) -> SystemTime {
SystemTime(Duration::from_nanos(ts))
}
pub fn sub_time(&self, other: &SystemTime)
-> Result<Duration, Duration> {
self.0.checked_sub(other.0).ok_or_else(|| other.0 - self.0)

View File

@ -0,0 +1,41 @@
#![allow(dead_code)] // not used on all platforms
use crate::path::Path;
use crate::fs;
use crate::io::{self, Error, ErrorKind};
pub fn copy(from: &Path, to: &Path) -> io::Result<u64> {
if !from.is_file() {
return Err(Error::new(ErrorKind::InvalidInput,
"the source path is not an existing regular file"))
}
let mut reader = fs::File::open(from)?;
let mut writer = fs::File::create(to)?;
let perm = reader.metadata()?.permissions();
let ret = io::copy(&mut reader, &mut writer)?;
fs::set_permissions(to, perm)?;
Ok(ret)
}
pub fn remove_dir_all(path: &Path) -> io::Result<()> {
let filetype = fs::symlink_metadata(path)?.file_type();
if filetype.is_symlink() {
fs::remove_file(path)
} else {
remove_dir_all_recursive(path)
}
}
fn remove_dir_all_recursive(path: &Path) -> io::Result<()> {
for child in fs::read_dir(path)? {
let child = child?;
if child.file_type()?.is_dir() {
remove_dir_all_recursive(&child.path())?;
} else {
fs::remove_file(&child.path())?;
}
}
fs::remove_dir(path)
}

View File

@ -52,6 +52,7 @@ pub mod util;
pub mod wtf8;
pub mod bytestring;
pub mod process;
pub mod fs;
cfg_if! {
if #[cfg(any(target_os = "cloudabi",