about summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/libstd/fs.rs8
-rw-r--r--src/libstd/sys/redox/fs.rs42
-rw-r--r--src/libstd/sys/unix/fs.rs23
-rw-r--r--src/libstd/sys/wasi/args.rs50
-rw-r--r--src/libstd/sys/wasi/ext/ffi.rs57
-rw-r--r--src/libstd/sys/wasi/ext/fs.rs412
-rw-r--r--src/libstd/sys/wasi/ext/io.rs87
-rw-r--r--src/libstd/sys/wasi/ext/mod.rs8
-rw-r--r--src/libstd/sys/wasi/fd.rs34
-rw-r--r--src/libstd/sys/wasi/fs.rs682
-rw-r--r--src/libstd/sys/wasi/os.rs17
-rw-r--r--src/libstd/sys/wasi/process.rs4
-rw-r--r--src/libstd/sys/wasi/stdio.rs23
-rw-r--r--src/libstd/sys/wasi/time.rs4
-rw-r--r--src/libstd/sys_common/fs.rs41
-rw-r--r--src/libstd/sys_common/mod.rs1
16 files changed, 1189 insertions, 304 deletions
diff --git a/src/libstd/fs.rs b/src/libstd/fs.rs
index 705dc8f40b5..29f4c78e27b 100644
--- a/src/libstd/fs.rs
+++ b/src/libstd/fs.rs
@@ -881,6 +881,10 @@ impl OpenOptions {
     }
 }
 
+impl AsInner<fs_imp::OpenOptions> for OpenOptions {
+    fn as_inner(&self) -> &fs_imp::OpenOptions { &self.0 }
+}
+
 impl AsInnerMut<fs_imp::OpenOptions> for OpenOptions {
     fn as_inner_mut(&mut self) -> &mut fs_imp::OpenOptions { &mut self.0 }
 }
@@ -1104,6 +1108,10 @@ impl AsInner<fs_imp::FileAttr> for Metadata {
     fn as_inner(&self) -> &fs_imp::FileAttr { &self.0 }
 }
 
+impl FromInner<fs_imp::FileAttr> for Metadata {
+    fn from_inner(attr: fs_imp::FileAttr) -> Metadata { Metadata(attr) }
+}
+
 impl Permissions {
     /// Returns `true` if these permissions describe a readonly (unwritable) file.
     ///
diff --git a/src/libstd/sys/redox/fs.rs b/src/libstd/sys/redox/fs.rs
index 3ef9925705f..ebefbc94210 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<PathBuf> {
     let fd = cvt(syscall::open(p.to_str().unwrap(),
                                syscall::O_CLOEXEC | syscall::O_SYMLINK | syscall::O_RDONLY))?;
@@ -455,19 +437,3 @@ pub fn canonicalize(p: &Path) -> io::Result<PathBuf> {
     let file = File(FileDesc::new(fd));
     file.path()
 }
-
-pub fn copy(from: &Path, to: &Path) -> io::Result<u64> {
-    use crate::fs::{File, set_permissions};
-    if !from.is_file() {
-        return Err(Error::new(ErrorKind::InvalidInput,
-                              "the source path is not an existing regular file"))
-    }
-
-    let mut reader = File::open(from)?;
-    let mut writer = File::create(to)?;
-    let perm = reader.metadata()?.permissions();
-
-    let ret = io::copy(&mut reader, &mut writer)?;
-    set_permissions(to, perm)?;
-    Ok(ret)
-}
diff --git a/src/libstd/sys/unix/fs.rs b/src/libstd/sys/unix/fs.rs
index a36dae2f5a1..dc3dcb5817c 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<PathBuf> {
     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 20558a8042d..9c8e59e4fb5 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<Args> {
     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::<Vec<_>>();
-        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 07b93dd143f..f71f316d1ba 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<u8>) -> Self;
-
-    /// Yields the underlying byte vector of this `OsString`.
-    #[stable(feature = "rust1", since = "1.0.0")]
-    fn into_vec(self) -> Vec<u8>;
-}
-
-#[stable(feature = "rust1", since = "1.0.0")]
-impl OsStringExt for OsString {
-    fn from_vec(vec: Vec<u8>) -> OsString {
-        FromInner::from_inner(Buf { inner: vec })
-    }
-    fn into_vec(self) -> Vec<u8> {
-        self.into_inner().inner
-    }
-}
-
-/// WASI-specific extensions to [`OsStr`].
-///
-/// [`OsStr`]: ../../../../std/ffi/struct.OsStr.html
 #[stable(feature = "rust1", since = "1.0.0")]
-pub trait OsStrExt {
-    #[stable(feature = "rust1", since = "1.0.0")]
-    /// Creates an [`OsStr`] from a byte slice.
-    ///
-    /// [`OsStr`]: ../../../ffi/struct.OsStr.html
-    fn from_bytes(slice: &[u8]) -> &Self;
-
-    /// Gets the underlying byte view of the [`OsStr`] slice.
-    ///
-    /// [`OsStr`]: ../../../ffi/struct.OsStr.html
-    #[stable(feature = "rust1", since = "1.0.0")]
-    fn as_bytes(&self) -> &[u8];
-}
-
-#[stable(feature = "rust1", since = "1.0.0")]
-impl OsStrExt for OsStr {
-    fn from_bytes(slice: &[u8]) -> &OsStr {
-        unsafe { mem::transmute(slice) }
-    }
-    fn as_bytes(&self) -> &[u8] {
-        &self.as_inner().inner
-    }
-}
-
+pub use crate::sys_common::os_str_bytes::*;
diff --git a/src/libstd/sys/wasi/ext/fs.rs b/src/libstd/sys/wasi/ext/fs.rs
new file mode 100644
index 00000000000..53f415c7821
--- /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<usize>;
+
+    /// Writes a number of bytes starting from a given offset.
+    ///
+    /// Returns the number of bytes written.
+    ///
+    /// The offset is relative to the start of the file and thus independent
+    /// from the current cursor.
+    ///
+    /// The current file cursor is not affected by this function.
+    ///
+    /// When writing beyond the end of the file, the file is appropriately
+    /// extended and the intermediate bytes are initialized with the value 0.
+    ///
+    /// Note that similar to [`File::write_vectored`], it is not an error to return a
+    /// short write.
+    ///
+    /// [`File::write`]: ../../../../std/fs/struct.File.html#method.write_vectored
+    fn write_at(&self, bufs: &[IoVec<'_>], offset: u64) -> io::Result<usize>;
+
+    /// Returns the current position within the file.
+    ///
+    /// This corresponds to the `__wasi_fd_tell` syscall and is similar to
+    /// `seek` where you offset 0 bytes from the current position.
+    fn tell(&self) -> io::Result<u64>;
+
+    /// Adjust the flags associated with this file.
+    ///
+    /// This corresponds to the `__wasi_fd_fdstat_set_flags` syscall.
+    fn fdstat_set_flags(&self, flags: u16) -> io::Result<()>;
+
+    /// Adjust the rights associated with this file.
+    ///
+    /// This corresponds to the `__wasi_fd_fdstat_set_rights` syscall.
+    fn fdstat_set_rights(&self, rights: u64, inheriting: u64) -> io::Result<()>;
+
+    /// Provide file advisory information on a file descriptor.
+    ///
+    /// This corresponds to the `__wasi_fd_advise` syscall.
+    fn advise(&self, offset: u64, len: u64, advice: u8) -> io::Result<()>;
+
+    /// Force the allocation of space in a file.
+    ///
+    /// This corresponds to the `__wasi_fd_allocate` syscall.
+    fn allocate(&self, offset: u64, len: u64) -> io::Result<()>;
+
+    /// Create a directory.
+    ///
+    /// This corresponds to the `__wasi_path_create_directory` syscall.
+    fn create_directory<P: AsRef<Path>>(&self, dir: P) -> io::Result<()>;
+
+    /// Read the contents of a symbolic link.
+    ///
+    /// This corresponds to the `__wasi_path_readlink` syscall.
+    fn read_link<P: AsRef<Path>>(&self, path: P) -> io::Result<PathBuf>;
+
+    /// Return the attributes of a file or directory.
+    ///
+    /// This corresponds to the `__wasi_path_filestat_get` syscall.
+    fn metadata_at<P: AsRef<Path>>(&self, lookup_flags: u32, path: P) -> io::Result<Metadata>;
+
+    /// Unlink a file.
+    ///
+    /// This corresponds to the `__wasi_path_unlink_file` syscall.
+    fn remove_file<P: AsRef<Path>>(&self, path: P) -> io::Result<()>;
+
+    /// Remove a directory.
+    ///
+    /// This corresponds to the `__wasi_path_remove_directory` syscall.
+    fn remove_directory<P: AsRef<Path>>(&self, path: P) -> io::Result<()>;
+}
+
+// FIXME: bind __wasi_fd_fdstat_get - need to define a custom return type
+// FIXME: bind __wasi_fd_readdir - can't return `ReadDir` since we only have entry name
+// FIXME: bind __wasi_fd_filestat_set_times maybe? - on crates.io for unix
+// FIXME: bind __wasi_path_filestat_set_times maybe? - on crates.io for unix
+// FIXME: bind __wasi_poll_oneoff maybe? - probably should wait for I/O to settle
+// FIXME: bind __wasi_random_get maybe? - on crates.io for unix
+
+impl FileExt for fs::File {
+    fn read_at(&self, bufs: &mut [IoVecMut<'_>], offset: u64) -> io::Result<usize> {
+        self.as_inner().fd().pread(bufs, offset)
+    }
+
+    fn write_at(&self, bufs: &[IoVec<'_>], offset: u64) -> io::Result<usize> {
+        self.as_inner().fd().pwrite(bufs, offset)
+    }
+
+    fn tell(&self) -> io::Result<u64> {
+        self.as_inner().fd().tell()
+    }
+
+    fn fdstat_set_flags(&self, flags: u16) -> io::Result<()> {
+        self.as_inner().fd().set_flags(flags)
+    }
+
+    fn fdstat_set_rights(&self, rights: u64, inheriting: u64) -> io::Result<()> {
+        self.as_inner().fd().set_rights(rights, inheriting)
+    }
+
+    fn advise(&self, offset: u64, len: u64, advice: u8) -> io::Result<()> {
+        self.as_inner().fd().advise(offset, len, advice)
+    }
+
+    fn allocate(&self, offset: u64, len: u64) -> io::Result<()> {
+        self.as_inner().fd().allocate(offset, len)
+    }
+
+    fn create_directory<P: AsRef<Path>>(&self, dir: P) -> io::Result<()> {
+        self.as_inner()
+            .fd()
+            .create_directory(dir.as_ref().as_os_str().as_bytes())
+    }
+
+    fn read_link<P: AsRef<Path>>(&self, path: P) -> io::Result<PathBuf> {
+        self.as_inner().read_link(path.as_ref())
+    }
+
+    fn metadata_at<P: AsRef<Path>>(&self, lookup_flags: u32, path: P) -> io::Result<Metadata> {
+        let m = self.as_inner().metadata_at(lookup_flags, path.as_ref())?;
+        Ok(FromInner::from_inner(m))
+    }
+
+    fn remove_file<P: AsRef<Path>>(&self, path: P) -> io::Result<()> {
+        self.as_inner()
+            .fd()
+            .unlink_file(path.as_ref().as_os_str().as_bytes())
+    }
+
+    fn remove_directory<P: AsRef<Path>>(&self, path: P) -> io::Result<()> {
+        self.as_inner()
+            .fd()
+            .remove_directory(path.as_ref().as_os_str().as_bytes())
+    }
+}
+
+/// WASI-specific extensions to [`fs::OpenOptions`].
+///
+/// [`fs::OpenOptions`]: ../../../../std/fs/struct.OpenOptions.html
+pub trait OpenOptionsExt {
+    /// Pass custom `dirflags` argument to `__wasi_path_open`.
+    ///
+    /// This option configures the `dirflags` argument to the
+    /// `__wasi_path_open` syscall which `OpenOptions` will eventually call. The
+    /// `dirflags` argument configures how the file is looked up, currently
+    /// primarily affecting whether symlinks are followed or not.
+    ///
+    /// By default this value is `__WASI_LOOKUP_SYMLINK_FOLLOW`, or symlinks are
+    /// followed. You can call this method with 0 to disable following symlinks
+    fn lookup_flags(&mut self, flags: u32) -> &mut Self;
+
+    /// Indicates whether `OpenOptions` must open a directory or not.
+    ///
+    /// This method will configure whether the `__WASI_O_DIRECTORY` flag is
+    /// passed when opening a file. When passed it will require that the opened
+    /// path is a directory.
+    ///
+    /// This option is by default `false`
+    fn directory(&mut self, dir: bool) -> &mut Self;
+
+    /// Indicates whether `__WASI_FDFLAG_DSYNC` is passed in the `fs_flags`
+    /// field of `__wasi_path_open`.
+    ///
+    /// This option is by default `false`
+    fn dsync(&mut self, dsync: bool) -> &mut Self;
+
+    /// Indicates whether `__WASI_FDFLAG_NONBLOCK` is passed in the `fs_flags`
+    /// field of `__wasi_path_open`.
+    ///
+    /// This option is by default `false`
+    fn nonblock(&mut self, nonblock: bool) -> &mut Self;
+
+    /// Indicates whether `__WASI_FDFLAG_RSYNC` is passed in the `fs_flags`
+    /// field of `__wasi_path_open`.
+    ///
+    /// This option is by default `false`
+    fn rsync(&mut self, rsync: bool) -> &mut Self;
+
+    /// Indicates whether `__WASI_FDFLAG_SYNC` is passed in the `fs_flags`
+    /// field of `__wasi_path_open`.
+    ///
+    /// This option is by default `false`
+    fn sync(&mut self, sync: bool) -> &mut Self;
+
+    /// Indicates the value that should be passed in for the `fs_rights_base`
+    /// parameter of `__wasi_path_open`.
+    ///
+    /// This option defaults based on the `read` and `write` configuration of
+    /// this `OpenOptions` builder. If this method is called, however, the
+    /// exact mask passed in will be used instead.
+    fn fs_rights_base(&mut self, rights: u64) -> &mut Self;
+
+    /// Indicates the value that should be passed in for the
+    /// `fs_rights_inheriting` parameter of `__wasi_path_open`.
+    ///
+    /// The default for this option is the same value as what will be passed
+    /// for the `fs_rights_base` parameter but if this method is called then
+    /// the specified value will be used instead.
+    fn fs_rights_inheriting(&mut self, rights: u64) -> &mut Self;
+
+    /// Open a file or directory.
+    ///
+    /// This corresponds to the `__wasi_path_open` syscall.
+    fn open_at<P: AsRef<Path>>(&self, file: &File, path: P) -> io::Result<File>;
+}
+
+impl OpenOptionsExt for OpenOptions {
+    fn lookup_flags(&mut self, flags: u32) -> &mut OpenOptions {
+        self.as_inner_mut().lookup_flags(flags);
+        self
+    }
+
+    fn directory(&mut self, dir: bool) -> &mut OpenOptions {
+        self.as_inner_mut().directory(dir);
+        self
+    }
+
+    fn dsync(&mut self, enabled: bool) -> &mut OpenOptions {
+        self.as_inner_mut().dsync(enabled);
+        self
+    }
+
+    fn nonblock(&mut self, enabled: bool) -> &mut OpenOptions {
+        self.as_inner_mut().nonblock(enabled);
+        self
+    }
+
+    fn rsync(&mut self, enabled: bool) -> &mut OpenOptions {
+        self.as_inner_mut().rsync(enabled);
+        self
+    }
+
+    fn sync(&mut self, enabled: bool) -> &mut OpenOptions {
+        self.as_inner_mut().sync(enabled);
+        self
+    }
+
+    fn fs_rights_base(&mut self, rights: u64) -> &mut OpenOptions {
+        self.as_inner_mut().fs_rights_base(rights);
+        self
+    }
+
+    fn fs_rights_inheriting(&mut self, rights: u64) -> &mut OpenOptions {
+        self.as_inner_mut().fs_rights_inheriting(rights);
+        self
+    }
+
+    fn open_at<P: AsRef<Path>>(&self, file: &File, path: P) -> io::Result<File> {
+        let inner = file.as_inner().open_at(path.as_ref(), self.as_inner())?;
+        Ok(File::from_inner(inner))
+    }
+}
+
+/// WASI-specific extensions to [`fs::Metadata`].
+///
+/// [`fs::Metadata`]: ../../../../std/fs/struct.Metadata.html
+pub trait MetadataExt {
+    /// Returns the `st_dev` field of the internal `__wasi_filestat_t`
+    fn dev(&self) -> u64;
+    /// Returns the `st_ino` field of the internal `__wasi_filestat_t`
+    fn ino(&self) -> u64;
+    /// Returns the `st_nlink` field of the internal `__wasi_filestat_t`
+    fn nlink(&self) -> u32;
+    /// Returns the `st_atim` field of the internal `__wasi_filestat_t`
+    fn atim(&self) -> u64;
+    /// Returns the `st_mtim` field of the internal `__wasi_filestat_t`
+    fn mtim(&self) -> u64;
+    /// Returns the `st_ctim` field of the internal `__wasi_filestat_t`
+    fn ctim(&self) -> u64;
+}
+
+impl MetadataExt for fs::Metadata {
+    fn dev(&self) -> u64 {
+        self.as_inner().as_wasi().st_dev
+    }
+    fn ino(&self) -> u64 {
+        self.as_inner().as_wasi().st_ino
+    }
+    fn nlink(&self) -> u32 {
+        self.as_inner().as_wasi().st_nlink
+    }
+    fn atim(&self) -> u64 {
+        self.as_inner().as_wasi().st_atim
+    }
+    fn mtim(&self) -> u64 {
+        self.as_inner().as_wasi().st_mtim
+    }
+    fn ctim(&self) -> u64 {
+        self.as_inner().as_wasi().st_ctim
+    }
+}
+
+/// WASI-specific extensions for [`FileType`].
+///
+/// Adds support for special WASI file types such as block/character devices,
+/// pipes, and sockets.
+///
+/// [`FileType`]: ../../../../std/fs/struct.FileType.html
+pub trait FileTypeExt {
+    /// Returns `true` if this file type is a block device.
+    fn is_block_device(&self) -> bool;
+    /// Returns `true` if this file type is a character device.
+    fn is_character_device(&self) -> bool;
+    /// Returns `true` if this file type is a socket datagram.
+    fn is_socket_dgram(&self) -> bool;
+    /// Returns `true` if this file type is a socket stream.
+    fn is_socket_stream(&self) -> bool;
+}
+
+impl FileTypeExt for fs::FileType {
+    fn is_block_device(&self) -> bool {
+        self.as_inner().bits() == libc::__WASI_FILETYPE_BLOCK_DEVICE
+    }
+    fn is_character_device(&self) -> bool {
+        self.as_inner().bits() == libc::__WASI_FILETYPE_CHARACTER_DEVICE
+    }
+    fn is_socket_dgram(&self) -> bool {
+        self.as_inner().bits() == libc::__WASI_FILETYPE_SOCKET_DGRAM
+    }
+    fn is_socket_stream(&self) -> bool {
+        self.as_inner().bits() == libc::__WASI_FILETYPE_SOCKET_STREAM
+    }
+}
+
+/// WASI-specific extension methods for [`fs::DirEntry`].
+///
+/// [`fs::DirEntry`]: ../../../../std/fs/struct.DirEntry.html
+pub trait DirEntryExt {
+    /// Returns the underlying `d_ino` field of the `__wasi_dirent_t`
+    fn ino(&self) -> u64;
+}
+
+impl DirEntryExt for fs::DirEntry {
+    fn ino(&self) -> u64 {
+        self.as_inner().ino()
+    }
+}
+
+/// Create a hard link.
+///
+/// This corresponds to the `__wasi_path_link` syscall.
+pub fn link<P: AsRef<Path>, U: AsRef<Path>>(
+    old_fd: &File,
+    old_flags: u32,
+    old_path: P,
+    new_fd: &File,
+    new_path: U,
+) -> io::Result<()> {
+    old_fd.as_inner().fd().link(
+        old_flags,
+        old_path.as_ref().as_os_str().as_bytes(),
+        new_fd.as_inner().fd(),
+        new_path.as_ref().as_os_str().as_bytes(),
+    )
+}
+
+/// Rename a file or directory.
+///
+/// This corresponds to the `__wasi_path_rename` syscall.
+pub fn rename<P: AsRef<Path>, U: AsRef<Path>>(
+    old_fd: &File,
+    old_path: P,
+    new_fd: &File,
+    new_path: U,
+) -> io::Result<()> {
+    old_fd.as_inner().fd().rename(
+        old_path.as_ref().as_os_str().as_bytes(),
+        new_fd.as_inner().fd(),
+        new_path.as_ref().as_os_str().as_bytes(),
+    )
+}
+
+/// Create a symbolic link.
+///
+/// This corresponds to the `__wasi_path_symlink` syscall.
+pub fn symlink<P: AsRef<Path>, U: AsRef<Path>>(
+    old_path: P,
+    fd: &File,
+    new_path: U,
+) -> io::Result<()> {
+    fd.as_inner().fd().symlink(
+        old_path.as_ref().as_os_str().as_bytes(),
+        new_path.as_ref().as_os_str().as_bytes(),
+    )
+}
diff --git a/src/libstd/sys/wasi/ext/io.rs b/src/libstd/sys/wasi/ext/io.rs
new file mode 100644
index 00000000000..cf75a96d28c
--- /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 877b9ed89d8..1c24b244b8c 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 29e1880b8f1..0b68b6f4d9c 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 68c8e9356a8..7b1c2bd79cc 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<ReadDirInner>,
+    cookie: Option<DirCookie>,
+    buf: Vec<u8>,
+    offset: usize,
+    cap: usize,
+}
 
-pub struct DirEntry(Void);
+struct ReadDirInner {
+    root: PathBuf,
+    dir: File,
+}
+
+pub struct DirEntry {
+    meta: libc::__wasi_dirent_t,
+    name: Vec<u8>,
+    inner: Arc<ReadDirInner>,
+}
 
-#[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<libc::__wasi_rights_t>,
+    rights_inheriting: Option<libc::__wasi_rights_t>,
+}
 
-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<SystemTime> {
-        match self.0 {}
+        Ok(SystemTime::from_wasi_timestamp(self.meta.st_mtim))
     }
 
     pub fn accessed(&self) -> io::Result<SystemTime> {
-        match self.0 {}
+        Ok(SystemTime::from_wasi_timestamp(self.meta.st_atim))
     }
 
     pub fn created(&self) -> io::Result<SystemTime> {
-        match self.0 {}
+        Ok(SystemTime::from_wasi_timestamp(self.meta.st_ctim))
     }
-}
 
-impl Clone for FileAttr {
-    fn clone(&self) -> FileAttr {
-        match self.0 {}
+    pub fn as_wasi(&self) -> &libc::__wasi_filestat_t {
+        &self.meta
     }
 }
 
 impl FilePermissions {
     pub fn readonly(&self) -> bool {
-        match self.0 {}
-    }
-
-    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<H: Hasher>(&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<DirEntry>;
 
     fn next(&mut self) -> Option<io::Result<DirEntry>> {
-        match self.0 {}
+        loop {
+            // If we've reached the capacity of our buffer then we need to read
+            // some more from the OS, otherwise we pick up at our old offset.
+            let offset = if self.offset == self.cap {
+                let cookie = self.cookie.take()?;
+                match self.inner.dir.fd.readdir(&mut self.buf, cookie) {
+                    Ok(bytes) => self.cap = bytes,
+                    Err(e) => return Some(Err(e)),
+                }
+                self.offset = 0;
+                self.cookie = Some(cookie);
+
+                // If we didn't actually read anything, this is in theory the
+                // end of the directory.
+                if self.cap == 0 {
+                    self.cookie = None;
+                    return None;
+                }
+
+                0
+            } else {
+                self.offset
+            };
+            let data = &self.buf[offset..self.cap];
+
+            // If we're not able to read a directory entry then that means it
+            // must have been truncated at the end of the buffer, so reset our
+            // offset so we can go back and reread into the buffer, picking up
+            // where we last left off.
+            let dirent_size = mem::size_of::<libc::__wasi_dirent_t>();
+            if data.len() < dirent_size {
+                assert!(self.cookie.is_some());
+                assert!(self.buf.len() >= dirent_size);
+                self.offset = self.cap;
+                continue;
+            }
+            let (dirent, data) = data.split_at(dirent_size);
+            let dirent =
+                unsafe { ptr::read_unaligned(dirent.as_ptr() as *const libc::__wasi_dirent_t) };
+
+            // If the file name was truncated, then we need to reinvoke
+            // `readdir` so we truncate our buffer to start over and reread this
+            // descriptor. Note that if our offset is 0 that means the file name
+            // is massive and we need a bigger buffer.
+            if data.len() < dirent.d_namlen as usize {
+                if offset == 0 {
+                    let amt_to_add = self.buf.capacity();
+                    self.buf.extend(iter::repeat(0).take(amt_to_add));
+                }
+                assert!(self.cookie.is_some());
+                self.offset = self.cap;
+                continue;
+            }
+            self.cookie = Some(dirent.d_next);
+            self.offset = offset + dirent_size + dirent.d_namlen as usize;
+
+            let name = &data[..(dirent.d_namlen as usize)];
+
+            // These names are skipped on all other platforms, so let's skip
+            // them here too
+            if name == b"." || name == b".." {
+                continue;
+            }
+
+            return Some(Ok(DirEntry {
+                meta: dirent,
+                name: name.to_vec(),
+                inner: self.inner.clone(),
+            }));
+        }
     }
 }
 
 impl DirEntry {
     pub fn path(&self) -> PathBuf {
-        match self.0 {}
+        let name = OsStr::from_bytes(&self.name);
+        self.inner.root.join(name)
     }
 
     pub fn file_name(&self) -> OsString {
-        match self.0 {}
+        OsString::from_vec(self.name.clone())
     }
 
     pub fn metadata(&self) -> io::Result<FileAttr> {
-        match self.0 {}
+        metadata_at(&self.inner.dir.fd, 0, OsStr::from_bytes(&self.name).as_ref())
     }
 
     pub fn file_type(&self) -> io::Result<FileType> {
-        match self.0 {}
+        Ok(FileType {
+            bits: self.meta.d_type,
+        })
+    }
+
+    pub fn ino(&self) -> libc::__wasi_inode_t {
+        self.meta.d_ino
     }
 }
 
 impl OpenOptions {
     pub fn new() -> OpenOptions {
-        OpenOptions { }
+        let mut base = OpenOptions::default();
+        base.dirflags = libc::__WASI_LOOKUP_SYMLINK_FOLLOW;
+        return base;
+    }
+
+    pub fn read(&mut self, read: bool) {
+        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<File> {
-        unsupported()
+    pub fn open(path: &Path, opts: &OpenOptions) -> io::Result<File> {
+        let (dir, file) = open_parent(path)?;
+        open_at(&dir, file, opts)
+    }
+
+    pub fn open_at(&self, path: &Path, opts: &OpenOptions) -> io::Result<File> {
+        open_at(&self.fd, path, opts)
     }
 
     pub fn file_attr(&self) -> io::Result<FileAttr> {
-        match self.0 {}
+        let mut ret = FileAttr::zero();
+        self.fd.filestat_get(&mut ret.meta)?;
+        Ok(ret)
+    }
+
+    pub fn metadata_at(
+        &self,
+        flags: libc::__wasi_lookupflags_t,
+        path: &Path,
+    ) -> io::Result<FileAttr> {
+        metadata_at(&self.fd, flags, path)
     }
 
     pub fn fsync(&self) -> io::Result<()> {
-        match self.0 {}
+        self.fd.sync()
     }
 
     pub fn datasync(&self) -> io::Result<()> {
-        match self.0 {}
+        self.fd.datasync()
+    }
+
+    pub fn truncate(&self, size: u64) -> io::Result<()> {
+        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<usize> {
+        self.read_vectored(&mut [IoVecMut::new(buf)])
     }
 
-    pub fn read(&self, _buf: &mut [u8]) -> io::Result<usize> {
-        match self.0 {}
+    pub fn read_vectored(&self, bufs: &mut [IoVecMut<'_>]) -> io::Result<usize> {
+        self.fd.read(bufs)
     }
 
-    pub fn write(&self, _buf: &[u8]) -> io::Result<usize> {
-        match self.0 {}
+    pub fn write(&self, buf: &[u8]) -> io::Result<usize> {
+        self.write_vectored(&[IoVec::new(buf)])
+    }
+
+    pub fn write_vectored(&self, bufs: &[IoVec<'_>]) -> io::Result<usize> {
+        self.fd.write(bufs)
     }
 
     pub fn flush(&self) -> io::Result<()> {
-        match self.0 {}
+        Ok(())
     }
 
-    pub fn seek(&self, _pos: SeekFrom) -> io::Result<u64> {
-        match self.0 {}
+    pub fn seek(&self, pos: SeekFrom) -> io::Result<u64> {
+        self.fd.seek(pos)
     }
 
     pub fn duplicate(&self) -> io::Result<File> {
-        match self.0 {}
+        // https://github.com/CraneStation/wasmtime/blob/master/docs/WASI-rationale.md#why-no-dup
+        unsupported()
     }
 
     pub fn set_permissions(&self, _perm: FilePermissions) -> io::Result<()> {
-        match self.0 {}
+        // Permissions haven't been fully figured out in wasi yet, so this is
+        // likely temporary
+        unsupported()
     }
 
-    pub fn diverge(&self) -> ! {
-        match self.0 {}
+    pub fn fd(&self) -> &WasiFd {
+        &self.fd
     }
-}
 
-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<PathBuf> {
+        read_link(&self.fd, file)
     }
 }
 
-impl fmt::Debug for File {
-    fn fmt(&self, _f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        match self.0 {}
+impl FromInner<u32> for File {
+    fn from_inner(fd: u32) -> File {
+        unsafe {
+            File {
+                fd: WasiFd::from_raw(fd),
+            }
+        }
     }
 }
 
-pub fn readdir(_p: &Path) -> io::Result<ReadDir> {
-    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<ReadDir> {
+    let mut opts = OpenOptions::new();
+    opts.directory(true);
+    opts.read(true);
+    let dir = File::open(p, &opts)?;
+    Ok(ReadDir {
+        cookie: Some(0),
+        buf: vec![0; 128],
+        offset: 0,
+        cap: 0,
+        inner: Arc::new(ReadDirInner {
+            dir,
+            root: p.to_path_buf(),
+        }),
+    })
+}
+
+pub fn unlink(p: &Path) -> io::Result<()> {
+    let (dir, file) = open_parent(p)?;
+    dir.unlink_file(file.as_os_str().as_bytes())
+}
+
+pub fn rename(old: &Path, new: &Path) -> io::Result<()> {
+    let (old, old_file) = open_parent(old)?;
+    let (new, new_file) = open_parent(new)?;
+    old.rename(
+        old_file.as_os_str().as_bytes(),
+        &new,
+        new_file.as_os_str().as_bytes(),
+    )
+}
+
+pub fn set_perm(_p: &Path, _perm: FilePermissions) -> io::Result<()> {
+    // Permissions haven't been fully figured out in wasi yet, so this is
+    // likely temporary
     unsupported()
 }
 
-pub fn 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<PathBuf> {
+    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<PathBuf> {
+    // Try to get a best effort initial capacity for the vector we're going to
+    // fill. Note that if it's not a symlink we don't use a file to avoid
+    // allocating gigabytes if you read_link a huge movie file by accident.
+    // Additionally we add 1 to the initial size so if it doesn't change until
+    // when we call `readlink` the returned length will be less than the
+    // capacity, guaranteeing that we got all the data.
+    let meta = metadata_at(fd, 0, file)?;
+    let initial_size = if meta.file_type().is_symlink() {
+        (meta.size() as usize).saturating_add(1)
+    } else {
+        1 // this'll fail in just a moment
+    };
+
+    // Now that we have an initial guess of how big to make our buffer, call
+    // `readlink` in a loop until it fails or reports it filled fewer bytes than
+    // we asked for, indicating we got everything.
+    let file = file.as_os_str().as_bytes();
+    let mut destination = vec![0u8; initial_size];
+    loop {
+        let len = fd.readlink(file, &mut destination)?;
+        if len < destination.len() {
+            destination.truncate(len);
+            destination.shrink_to_fit();
+            return Ok(PathBuf::from(OsString::from_vec(destination)));
+        }
+        let amt_to_add = destination.len();
+        destination.extend(iter::repeat(0).take(amt_to_add));
+    }
 }
 
-pub fn readlink(_p: &Path) -> io::Result<PathBuf> {
-    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<FileAttr> {
+    let (dir, file) = open_parent(p)?;
+    metadata_at(&dir, libc::__WASI_LOOKUP_SYMLINK_FOLLOW, file)
 }
 
-pub fn stat(_p: &Path) -> io::Result<FileAttr> {
-    unsupported()
+pub fn lstat(p: &Path) -> io::Result<FileAttr> {
+    let (dir, file) = open_parent(p)?;
+    metadata_at(&dir, 0, file)
 }
 
-pub fn lstat(_p: &Path) -> io::Result<FileAttr> {
-    unsupported()
+fn metadata_at(
+    fd: &WasiFd,
+    flags: libc::__wasi_lookupflags_t,
+    path: &Path,
+) -> io::Result<FileAttr> {
+    let mut ret = FileAttr::zero();
+    fd.path_filestat_get(flags, path.as_os_str().as_bytes(), &mut ret.meta)?;
+    Ok(ret)
 }
 
 pub fn canonicalize(_p: &Path) -> io::Result<PathBuf> {
+    // This seems to not be in wasi's API yet, and we may need to end up
+    // emulating it ourselves. For now just return an error.
     unsupported()
 }
 
-pub fn copy(_from: &Path, _to: &Path) -> io::Result<u64> {
-    unsupported()
+fn open_at(fd: &WasiFd, path: &Path, opts: &OpenOptions) -> io::Result<File> {
+    let fd = fd.open(
+        opts.dirflags,
+        path.as_os_str().as_bytes(),
+        opts.oflags,
+        opts.rights_base(),
+        opts.rights_inheriting(),
+        opts.fdflags,
+    )?;
+    Ok(File { fd })
+}
+
+// FIXME: we shouldn't implement this. It'd be much better to share this between
+// libc (the wasi-sysroot) and Rust as the logic here is likely far more tricky
+// than what we're executing below. For now this is a stopgap to enable this
+// module, but we should add an official API in upstream wasi-libc which looks
+// like this.
+//
+// In the meantime this is highly unlikely to be correct. It allows some basic
+// testing but is not at all robust.
+fn open_parent(p: &Path) -> io::Result<(&'static WasiFd, &Path)> {
+    let map = preopened_map();
+    for ancestor in p.ancestors() {
+        if let Some(fd) = map.get(ancestor) {
+            let tail = p.strip_prefix(ancestor).unwrap();
+            let tail = if tail == Path::new("") {
+                ".".as_ref()
+            } else {
+                tail
+            };
+            return Ok((fd, tail))
+        }
+    }
+    let msg = format!("failed to find a preopened file descriptor to open {:?}", p);
+    return Err(io::Error::new(io::ErrorKind::Other, msg));
+
+    type Preopened = HashMap<PathBuf, ManuallyDrop<WasiFd>>;
+    fn preopened_map() -> &'static Preopened {
+        static PTR: AtomicPtr<Preopened> = AtomicPtr::new(ptr::null_mut());
+        unsafe {
+            let ptr = PTR.load(SeqCst);
+            if !ptr.is_null() {
+                return &*ptr;
+            }
+
+            let mut map = Box::new(HashMap::new());
+            for fd in 3.. {
+                let mut buf = mem::zeroed();
+                if cvt_wasi(libc::__wasi_fd_prestat_get(fd, &mut buf)).is_err() {
+                    break;
+                }
+                if buf.pr_type != libc::__WASI_PREOPENTYPE_DIR {
+                    continue;
+                }
+                let len = buf.u.dir.pr_name_len;
+                let mut v = vec![0u8; len];
+                let res = cvt_wasi(libc::__wasi_fd_prestat_dir_name(
+                    fd,
+                    v.as_mut_ptr() as *mut i8,
+                    v.len(),
+                ));
+                if res.is_err() {
+                    continue;
+                }
+                let path = PathBuf::from(OsString::from_vec(v));
+                map.insert(path, ManuallyDrop::new(WasiFd::from_raw(fd)));
+            }
+            let ptr = Box::into_raw(map);
+            match PTR.compare_exchange(ptr::null_mut(), ptr, SeqCst, SeqCst) {
+                Ok(_) => &*ptr,
+
+                // If we lost the race for initialization clean up the map we
+                // made and just use the one that's already there
+                Err(other) => {
+                    drop(Box::from_raw(ptr));
+                    &*other
+                }
+            }
+        }
+    }
 }
diff --git a/src/libstd/sys/wasi/os.rs b/src/libstd/sys/wasi/os.rs
index 6d4d6aae61b..822ea02a11b 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<PathBuf> {
diff --git a/src/libstd/sys/wasi/process.rs b/src/libstd/sys/wasi/process.rs
index a02e009d953..788b829f4ba 100644
--- a/src/libstd/sys/wasi/process.rs
+++ b/src/libstd/sys/wasi/process.rs
@@ -67,8 +67,8 @@ impl From<AnonPipe> for Stdio {
 }
 
 impl From<File> for Stdio {
-    fn from(file: File) -> Stdio {
-        file.diverge()
+    fn from(_file: File) -> Stdio {
+        panic!("unsupported")
     }
 }
 
diff --git a/src/libstd/sys/wasi/stdio.rs b/src/libstd/sys/wasi/stdio.rs
index f6a4958897d..19294788666 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<usize> {
-        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<usize> {
-        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<usize> {
-        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 e1b92e7c5a7..3f14c80928c 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<Duration, Duration> {
         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 00000000000..7152fcd215c
--- /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<u64> {
+    if !from.is_file() {
+        return Err(Error::new(ErrorKind::InvalidInput,
+                              "the source path is not an existing regular file"))
+    }
+
+    let mut reader = fs::File::open(from)?;
+    let mut writer = fs::File::create(to)?;
+    let perm = reader.metadata()?.permissions();
+
+    let ret = io::copy(&mut reader, &mut writer)?;
+    fs::set_permissions(to, perm)?;
+    Ok(ret)
+}
+
+pub fn remove_dir_all(path: &Path) -> io::Result<()> {
+    let filetype = fs::symlink_metadata(path)?.file_type();
+    if filetype.is_symlink() {
+        fs::remove_file(path)
+    } else {
+        remove_dir_all_recursive(path)
+    }
+}
+
+fn remove_dir_all_recursive(path: &Path) -> io::Result<()> {
+    for child in fs::read_dir(path)? {
+        let child = child?;
+        if child.file_type()?.is_dir() {
+            remove_dir_all_recursive(&child.path())?;
+        } else {
+            fs::remove_file(&child.path())?;
+        }
+    }
+    fs::remove_dir(path)
+}
diff --git a/src/libstd/sys_common/mod.rs b/src/libstd/sys_common/mod.rs
index 4c64e9f3afb..6260c3b77ff 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",