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/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, - } + }) } } 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/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 { 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/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<()> { 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 4c64e9f3afba7..6260c3b77ff81 100644 --- a/src/libstd/sys_common/mod.rs +++ b/src/libstd/sys_common/mod.rs @@ -61,6 +61,7 @@ pub mod util; pub mod wtf8; pub mod bytestring; pub mod process; +pub mod fs; cfg_if! { if #[cfg(any(target_os = "cloudabi",