From 382f9a7a3d15580c15531428f39bf47f55093d42 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Sun, 31 Mar 2019 14:39:03 -0700 Subject: [PATCH 1/4] wasi: Load arguments via syscalls This commit switches the wasi target to loading CLI arguments via the syscalls provided by wasi rather than through the argc/argv passed to the main function. While serving the same purpose it's hoped that using syscalls will make us a bit more portable (less reliance from libstd on an external C library) as well as avoiding the need for a lock! --- src/libstd/sys/wasi/args.rs | 50 ++++++++++++++++++------------------- 1 file changed, 24 insertions(+), 26 deletions(-) diff --git a/src/libstd/sys/wasi/args.rs b/src/libstd/sys/wasi/args.rs index 20558a8042db4..9c8e59e4fb5e1 100644 --- a/src/libstd/sys/wasi/args.rs +++ b/src/libstd/sys/wasi/args.rs @@ -1,30 +1,15 @@ -use crate::any::Any; use crate::ffi::CStr; +use crate::io; +use crate::sys::cvt_wasi; use crate::ffi::OsString; use crate::marker::PhantomData; use crate::os::wasi::ffi::OsStringExt; -use crate::ptr; use crate::vec; -static mut ARGC: isize = 0; -static mut ARGV: *const *const u8 = ptr::null(); - -#[cfg(not(target_feature = "atomics"))] -pub unsafe fn args_lock() -> impl Any { - // No need for a lock if we're single-threaded, but this function will need - // to get implemented for multi-threaded scenarios -} - -pub unsafe fn init(argc: isize, argv: *const *const u8) { - let _guard = args_lock(); - ARGC = argc; - ARGV = argv; +pub unsafe fn init(_argc: isize, _argv: *const *const u8) { } pub unsafe fn cleanup() { - let _guard = args_lock(); - ARGC = 0; - ARGV = ptr::null(); } pub struct Args { @@ -34,18 +19,31 @@ pub struct Args { /// Returns the command line arguments pub fn args() -> Args { + maybe_args().unwrap_or_else(|_| { + Args { + iter: Vec::new().into_iter(), + _dont_send_or_sync_me: PhantomData + } + }) +} + +fn maybe_args() -> io::Result { unsafe { - let _guard = args_lock(); - let args = (0..ARGC) - .map(|i| { - let cstr = CStr::from_ptr(*ARGV.offset(i) as *const libc::c_char); - OsStringExt::from_vec(cstr.to_bytes().to_vec()) - }) + let (mut argc, mut argv_buf_size) = (0, 0); + cvt_wasi(libc::__wasi_args_sizes_get(&mut argc, &mut argv_buf_size))?; + + let mut argc = vec![0 as *mut libc::c_char; argc]; + let mut argv_buf = vec![0; argv_buf_size]; + cvt_wasi(libc::__wasi_args_get(argc.as_mut_ptr(), argv_buf.as_mut_ptr()))?; + + let args = argc.into_iter() + .map(|ptr| CStr::from_ptr(ptr).to_bytes().to_vec()) + .map(|bytes| OsString::from_vec(bytes)) .collect::>(); - Args { + Ok(Args { iter: args.into_iter(), _dont_send_or_sync_me: PhantomData, - } + }) } } From 60f6cbd0028c61bcca181318f48cdf0c6be61231 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Sun, 31 Mar 2019 14:56:56 -0700 Subject: [PATCH 2/4] wasi: Use raw syscalls for stdio I've since learned that the mapping between libc fds and wasi fds are expected to be one-to-one, so we can use the raw syscalls for writing to stdout/stderr and reading from stdin! This should help ensure that we don't depend on a C library too unnecessarily. --- src/libstd/sys/wasi/stdio.rs | 23 +++++++++-------------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/src/libstd/sys/wasi/stdio.rs b/src/libstd/sys/wasi/stdio.rs index f6a4958897d9b..192947886668f 100644 --- a/src/libstd/sys/wasi/stdio.rs +++ b/src/libstd/sys/wasi/stdio.rs @@ -1,6 +1,7 @@ -use crate::io; +use crate::io::{self, IoVec, IoVecMut}; use crate::libc; -use crate::sys::cvt; +use crate::mem::ManuallyDrop; +use crate::sys::fd::WasiFd; pub struct Stdin; pub struct Stdout; @@ -12,10 +13,8 @@ impl Stdin { } pub fn read(&self, data: &mut [u8]) -> io::Result { - let amt = cvt(unsafe { - libc::read(libc::STDIN_FILENO, data.as_mut_ptr() as *mut _, data.len()) - })?; - Ok(amt as usize) + ManuallyDrop::new(unsafe { WasiFd::from_raw(libc::STDIN_FILENO as u32) }) + .read(&mut [IoVecMut::new(data)]) } } @@ -25,10 +24,8 @@ impl Stdout { } pub fn write(&self, data: &[u8]) -> io::Result { - let amt = cvt(unsafe { - libc::write(libc::STDOUT_FILENO, data.as_ptr() as *const _, data.len()) - })?; - Ok(amt as usize) + ManuallyDrop::new(unsafe { WasiFd::from_raw(libc::STDOUT_FILENO as u32) }) + .write(&[IoVec::new(data)]) } pub fn flush(&self) -> io::Result<()> { @@ -42,10 +39,8 @@ impl Stderr { } pub fn write(&self, data: &[u8]) -> io::Result { - let amt = cvt(unsafe { - libc::write(libc::STDERR_FILENO, data.as_ptr() as *const _, data.len()) - })?; - Ok(amt as usize) + ManuallyDrop::new(unsafe { WasiFd::from_raw(libc::STDERR_FILENO as u32) }) + .write(&[IoVec::new(data)]) } pub fn flush(&self) -> io::Result<()> { From 32a76844c47924a1d230f647db3bafb53c3e20ea Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Mon, 1 Apr 2019 05:29:17 -0700 Subject: [PATCH 3/4] wasi: Implement `error_string` to get readable errors This routes the `error_string` API to `strerror` in libc which should have more human readable descriptions. --- src/libstd/sys/wasi/os.rs | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/libstd/sys/wasi/os.rs b/src/libstd/sys/wasi/os.rs index 6d4d6aae61b9f..822ea02a11b89 100644 --- a/src/libstd/sys/wasi/os.rs +++ b/src/libstd/sys/wasi/os.rs @@ -27,8 +27,21 @@ pub fn errno() -> i32 { unsafe { errno as i32 } } -pub fn error_string(_errno: i32) -> String { - "operation failed".to_string() +pub fn error_string(errno: i32) -> String { + extern { + fn strerror_r(errnum: libc::c_int, buf: *mut libc::c_char, + buflen: libc::size_t) -> libc::c_int; + } + + let mut buf = [0 as libc::c_char; 1024]; + + let p = buf.as_mut_ptr(); + unsafe { + if strerror_r(errno as libc::c_int, p, buf.len()) < 0 { + panic!("strerror_r failure"); + } + str::from_utf8(CStr::from_ptr(p).to_bytes()).unwrap().to_owned() + } } pub fn getcwd() -> io::Result { From 61b487ca8be1d8667a82c1357dc2729cfe56186d Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Fri, 29 Mar 2019 15:57:14 -0700 Subject: [PATCH 4/4] 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. --- src/libstd/fs.rs | 8 + src/libstd/sys/redox/fs.rs | 42 +- src/libstd/sys/unix/fs.rs | 23 +- src/libstd/sys/wasi/ext/ffi.rs | 57 +-- src/libstd/sys/wasi/ext/fs.rs | 412 ++++++++++++++++++++ src/libstd/sys/wasi/ext/io.rs | 87 +++++ src/libstd/sys/wasi/ext/mod.rs | 8 + src/libstd/sys/wasi/fd.rs | 34 +- src/libstd/sys/wasi/fs.rs | 682 ++++++++++++++++++++++++++------- src/libstd/sys/wasi/process.rs | 4 +- src/libstd/sys/wasi/time.rs | 4 + src/libstd/sys_common/fs.rs | 41 ++ src/libstd/sys_common/mod.rs | 1 + 13 files changed, 1141 insertions(+), 262 deletions(-) create mode 100644 src/libstd/sys/wasi/ext/fs.rs create mode 100644 src/libstd/sys/wasi/ext/io.rs create mode 100644 src/libstd/sys_common/fs.rs diff --git a/src/libstd/fs.rs b/src/libstd/fs.rs index 705dc8f40b5a0..29f4c78e27b48 100644 --- a/src/libstd/fs.rs +++ b/src/libstd/fs.rs @@ -881,6 +881,10 @@ impl OpenOptions { } } +impl AsInner for OpenOptions { + fn as_inner(&self) -> &fs_imp::OpenOptions { &self.0 } +} + impl AsInnerMut for OpenOptions { fn as_inner_mut(&mut self) -> &mut fs_imp::OpenOptions { &mut self.0 } } @@ -1104,6 +1108,10 @@ impl AsInner for Metadata { fn as_inner(&self) -> &fs_imp::FileAttr { &self.0 } } +impl FromInner for Metadata { + fn from_inner(attr: fs_imp::FileAttr) -> Metadata { Metadata(attr) } +} + impl Permissions { /// Returns `true` if these permissions describe a readonly (unwritable) file. /// diff --git a/src/libstd/sys/redox/fs.rs b/src/libstd/sys/redox/fs.rs index 3ef9925705fb8..ebefbc942103c 100644 --- a/src/libstd/sys/redox/fs.rs +++ b/src/libstd/sys/redox/fs.rs @@ -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 { 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 { let file = File(FileDesc::new(fd)); file.path() } - -pub fn copy(from: &Path, to: &Path) -> io::Result { - 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) -} diff --git a/src/libstd/sys/unix/fs.rs b/src/libstd/sys/unix/fs.rs index a36dae2f5a19d..dc3dcb5817c05 100644 --- a/src/libstd/sys/unix/fs.rs +++ b/src/libstd/sys/unix/fs.rs @@ -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 { let c_path = cstr(p)?; let p = c_path.as_ptr(); diff --git a/src/libstd/sys/wasi/ext/ffi.rs b/src/libstd/sys/wasi/ext/ffi.rs index 07b93dd143f8f..f71f316d1ba10 100644 --- a/src/libstd/sys/wasi/ext/ffi.rs +++ b/src/libstd/sys/wasi/ext/ffi.rs @@ -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) -> Self; - - /// Yields the underlying byte vector of this `OsString`. - #[stable(feature = "rust1", since = "1.0.0")] - fn into_vec(self) -> Vec; -} - -#[stable(feature = "rust1", since = "1.0.0")] -impl OsStringExt for OsString { - fn from_vec(vec: Vec) -> OsString { - FromInner::from_inner(Buf { inner: vec }) - } - fn into_vec(self) -> Vec { - 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::*; diff --git a/src/libstd/sys/wasi/ext/fs.rs b/src/libstd/sys/wasi/ext/fs.rs new file mode 100644 index 0000000000000..53f415c78219e --- /dev/null +++ b/src/libstd/sys/wasi/ext/fs.rs @@ -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; + + /// 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; + + /// 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; + + /// 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>(&self, dir: P) -> io::Result<()>; + + /// Read the contents of a symbolic link. + /// + /// This corresponds to the `__wasi_path_readlink` syscall. + fn read_link>(&self, path: P) -> io::Result; + + /// Return the attributes of a file or directory. + /// + /// This corresponds to the `__wasi_path_filestat_get` syscall. + fn metadata_at>(&self, lookup_flags: u32, path: P) -> io::Result; + + /// Unlink a file. + /// + /// This corresponds to the `__wasi_path_unlink_file` syscall. + fn remove_file>(&self, path: P) -> io::Result<()>; + + /// Remove a directory. + /// + /// This corresponds to the `__wasi_path_remove_directory` syscall. + fn remove_directory>(&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 { + self.as_inner().fd().pread(bufs, offset) + } + + fn write_at(&self, bufs: &[IoVec<'_>], offset: u64) -> io::Result { + self.as_inner().fd().pwrite(bufs, offset) + } + + fn tell(&self) -> io::Result { + 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>(&self, dir: P) -> io::Result<()> { + self.as_inner() + .fd() + .create_directory(dir.as_ref().as_os_str().as_bytes()) + } + + fn read_link>(&self, path: P) -> io::Result { + self.as_inner().read_link(path.as_ref()) + } + + fn metadata_at>(&self, lookup_flags: u32, path: P) -> io::Result { + let m = self.as_inner().metadata_at(lookup_flags, path.as_ref())?; + Ok(FromInner::from_inner(m)) + } + + fn remove_file>(&self, path: P) -> io::Result<()> { + self.as_inner() + .fd() + .unlink_file(path.as_ref().as_os_str().as_bytes()) + } + + fn remove_directory>(&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>(&self, file: &File, path: P) -> io::Result; +} + +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>(&self, file: &File, path: P) -> io::Result { + 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, U: AsRef>( + 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, U: AsRef>( + 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, U: AsRef>( + 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(), + ) +} diff --git a/src/libstd/sys/wasi/ext/io.rs b/src/libstd/sys/wasi/ext/io.rs new file mode 100644 index 0000000000000..cf75a96d28c1a --- /dev/null +++ b/src/libstd/sys/wasi/ext/io.rs @@ -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 + } +} diff --git a/src/libstd/sys/wasi/ext/mod.rs b/src/libstd/sys/wasi/ext/mod.rs index 877b9ed89d81e..1c24b244b8cd0 100644 --- a/src/libstd/sys/wasi/ext/mod.rs +++ b/src/libstd/sys/wasi/ext/mod.rs @@ -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}; } diff --git a/src/libstd/sys/wasi/fd.rs b/src/libstd/sys/wasi/fd.rs index 29e1880b8f135..0b68b6f4d9c72 100644 --- a/src/libstd/sys/wasi/fd.rs +++ b/src/libstd/sys/wasi/fd.rs @@ -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, diff --git a/src/libstd/sys/wasi/fs.rs b/src/libstd/sys/wasi/fs.rs index 68c8e9356a89a..7b1c2bd79cc16 100644 --- a/src/libstd/sys/wasi/fs.rs +++ b/src/libstd/sys/wasi/fs.rs @@ -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, +} + +#[derive(Clone)] +pub struct FileAttr { + meta: libc::__wasi_filestat_t, +} -pub struct ReadDir(Void); +pub struct ReadDir { + inner: Arc, + cookie: Option, + buf: Vec, + offset: usize, + cap: usize, +} -pub struct DirEntry(Void); +struct ReadDirInner { + root: PathBuf, + dir: File, +} + +pub struct DirEntry { + meta: libc::__wasi_dirent_t, + name: Vec, + inner: Arc, +} -#[derive(Clone, Debug)] -pub struct OpenOptions { } +#[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, + rights_inheriting: Option, +} -pub struct FilePermissions(Void); +#[derive(Clone, PartialEq, Eq, Debug)] +pub struct FilePermissions { + readonly: bool, +} -pub struct FileType(Void); +#[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 { - match self.0 {} + Ok(SystemTime::from_wasi_timestamp(self.meta.st_mtim)) } pub fn accessed(&self) -> io::Result { - match self.0 {} + Ok(SystemTime::from_wasi_timestamp(self.meta.st_atim)) } pub fn created(&self) -> io::Result { - 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 {} - } - - 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 {} + self.readonly } -} - -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 {} - } -} - -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(&self, _h: &mut H) { - match self.0 {} + self.bits == libc::__WASI_FILETYPE_SYMBOLIC_LINK } -} -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; fn next(&mut self) -> Option> { - 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::(); + 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 { - match self.0 {} + metadata_at(&self.inner.dir.fd, 0, OsStr::from_bytes(&self.name).as_ref()) } pub fn file_type(&self) -> io::Result { - 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) { + 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; } - 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) { } + 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 { - unsupported() + pub fn open(path: &Path, opts: &OpenOptions) -> io::Result { + let (dir, file) = open_parent(path)?; + open_at(&dir, file, opts) + } + + pub fn open_at(&self, path: &Path, opts: &OpenOptions) -> io::Result { + open_at(&self.fd, path, opts) } pub fn file_attr(&self) -> io::Result { - 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 { + 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<()> { + self.fd.filestat_set_size(size) } - pub fn truncate(&self, _size: u64) -> io::Result<()> { - match self.0 {} + pub fn read(&self, buf: &mut [u8]) -> io::Result { + self.read_vectored(&mut [IoVecMut::new(buf)]) } - pub fn read(&self, _buf: &mut [u8]) -> io::Result { - match self.0 {} + pub fn read_vectored(&self, bufs: &mut [IoVecMut<'_>]) -> io::Result { + self.fd.read(bufs) } - pub fn write(&self, _buf: &[u8]) -> io::Result { - match self.0 {} + pub fn write(&self, buf: &[u8]) -> io::Result { + self.write_vectored(&[IoVec::new(buf)]) + } + + pub fn write_vectored(&self, bufs: &[IoVec<'_>]) -> io::Result { + self.fd.write(bufs) } pub fn flush(&self) -> io::Result<()> { - match self.0 {} + Ok(()) } - pub fn seek(&self, _pos: SeekFrom) -> io::Result { - match self.0 {} + pub fn seek(&self, pos: SeekFrom) -> io::Result { + self.fd.seek(pos) } pub fn duplicate(&self) -> io::Result { - 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 } -} -impl DirBuilder { - pub fn new() -> DirBuilder { - DirBuilder { } + pub fn into_fd(self) -> WasiFd { + self.fd } - pub fn mkdir(&self, _p: &Path) -> io::Result<()> { - unsupported() + pub fn read_link(&self, file: &Path) -> io::Result { + read_link(&self.fd, file) } } -impl fmt::Debug for File { - fn fmt(&self, _f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self.0 {} +impl FromInner for File { + fn from_inner(fd: u32) -> File { + unsafe { + File { + fd: WasiFd::from_raw(fd), + } + } } } -pub fn readdir(_p: &Path) -> io::Result { - unsupported() -} +impl DirBuilder { + pub fn new() -> DirBuilder { + DirBuilder {} + } -pub fn unlink(_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()) + } } -pub fn rename(_old: &Path, _new: &Path) -> io::Result<()> { +impl fmt::Debug for File { + 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 { + 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 set_perm(_p: &Path, perm: FilePermissions) -> io::Result<()> { - match perm.0 {} +pub fn rmdir(p: &Path) -> io::Result<()> { + let (dir, file) = open_parent(p)?; + dir.remove_directory(file.as_os_str().as_bytes()) } -pub fn rmdir(_p: &Path) -> io::Result<()> { - unsupported() +pub fn readlink(p: &Path) -> io::Result { + let (dir, file) = open_parent(p)?; + read_link(&dir, file) } -pub fn remove_dir_all(_path: &Path) -> io::Result<()> { - unsupported() +fn read_link(fd: &WasiFd, file: &Path) -> io::Result { + // 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 readlink(_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 symlink(_src: &Path, _dst: &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 link(_src: &Path, _dst: &Path) -> io::Result<()> { - unsupported() +pub fn stat(p: &Path) -> io::Result { + let (dir, file) = open_parent(p)?; + metadata_at(&dir, libc::__WASI_LOOKUP_SYMLINK_FOLLOW, file) } -pub fn stat(_p: &Path) -> io::Result { - unsupported() +pub fn lstat(p: &Path) -> io::Result { + let (dir, file) = open_parent(p)?; + metadata_at(&dir, 0, file) } -pub fn lstat(_p: &Path) -> io::Result { - unsupported() +fn metadata_at( + fd: &WasiFd, + flags: libc::__wasi_lookupflags_t, + path: &Path, +) -> io::Result { + 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 { + // 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 { - unsupported() +fn open_at(fd: &WasiFd, path: &Path, opts: &OpenOptions) -> io::Result { + 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>; + fn preopened_map() -> &'static Preopened { + static PTR: AtomicPtr = 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 + } + } + } + } } diff --git a/src/libstd/sys/wasi/process.rs b/src/libstd/sys/wasi/process.rs index a02e009d95356..788b829f4bac9 100644 --- a/src/libstd/sys/wasi/process.rs +++ b/src/libstd/sys/wasi/process.rs @@ -67,8 +67,8 @@ impl From for Stdio { } impl From for Stdio { - fn from(file: File) -> Stdio { - file.diverge() + fn from(_file: File) -> Stdio { + panic!("unsupported") } } diff --git a/src/libstd/sys/wasi/time.rs b/src/libstd/sys/wasi/time.rs index e1b92e7c5a705..3f14c80928c67 100644 --- a/src/libstd/sys/wasi/time.rs +++ b/src/libstd/sys/wasi/time.rs @@ -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 { self.0.checked_sub(other.0).ok_or_else(|| other.0 - self.0) diff --git a/src/libstd/sys_common/fs.rs b/src/libstd/sys_common/fs.rs new file mode 100644 index 0000000000000..7152fcd215c9a --- /dev/null +++ b/src/libstd/sys_common/fs.rs @@ -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 { + 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) +} diff --git a/src/libstd/sys_common/mod.rs b/src/libstd/sys_common/mod.rs index 883ab34f07c58..c911d772bf0fc 100644 --- a/src/libstd/sys_common/mod.rs +++ b/src/libstd/sys_common/mod.rs @@ -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",