about summary refs log tree commit diff
path: root/src/libstd/rt
diff options
context:
space:
mode:
authorAlex Crichton <alex@alexcrichton.com>2013-10-29 23:31:07 -0700
committerAlex Crichton <alex@alexcrichton.com>2013-11-03 15:15:42 -0800
commitf19d0833625c382c5d0a8868924cd4620335e659 (patch)
tree9d4d67c3f845aa4f2596c6db94f9c883ab1dd696 /src/libstd/rt
parent9c1851019f1ef9511fa8731b8f1acb0796d1e97f (diff)
downloadrust-f19d0833625c382c5d0a8868924cd4620335e659.tar.gz
rust-f19d0833625c382c5d0a8868924cd4620335e659.zip
Fill out the remaining functionality in io::file
This adds bindings to the remaining functions provided by libuv, all of which
are useful operations on files which need to get exposed somehow.

Some highlights:

* Dropped `FileReader` and `FileWriter` and `FileStream` for one `File` type
* Moved all file-related methods to be static methods under `File`
* All directory related methods are still top-level functions
* Created `io::FilePermission` types (backed by u32) that are what you'd expect
* Created `io::FileType` and refactored `FileStat` to use FileType and
  FilePermission
* Removed the expanding matrix of `FileMode` operations. The mode of reading a
  file will not have the O_CREAT flag, but a write mode will always have the
  O_CREAT flag.

Closes #10130
Closes #10131
Closes #10121
Diffstat (limited to 'src/libstd/rt')
-rw-r--r--src/libstd/rt/io/file.rs1334
-rw-r--r--src/libstd/rt/io/mod.rs119
-rw-r--r--src/libstd/rt/io/native/file.rs16
-rw-r--r--src/libstd/rt/io/option.rs2
-rw-r--r--src/libstd/rt/rtio.rs23
5 files changed, 919 insertions, 575 deletions
diff --git a/src/libstd/rt/io/file.rs b/src/libstd/rt/io/file.rs
index b6c0b58434f..c3f5f387211 100644
--- a/src/libstd/rt/io/file.rs
+++ b/src/libstd/rt/io/file.rs
@@ -16,7 +16,7 @@ with regular files & directories on a filesystem.
 At the top-level of the module are a set of freestanding functions, associated
 with various filesystem operations. They all operate on a `Path` object.
 
-All operations in this module, including those as part of `FileStream` et al
+All operations in this module, including those as part of `File` et al
 block the task during execution. Most will raise `std::rt::io::io_error`
 conditions in the event of failure.
 
@@ -28,66 +28,40 @@ particular bits of it, etc.
 */
 
 use c_str::ToCStr;
+use iter::Iterator;
 use super::{Reader, Writer, Seek};
-use super::{SeekStyle, Read, Write, Open, CreateOrTruncate,
+use super::{SeekStyle, Read, Write, Open, IoError, Truncate,
             FileMode, FileAccess, FileStat, io_error, FilePermission};
 use rt::rtio::{RtioFileStream, IoFactory, with_local_io};
 use rt::io;
 use option::{Some, None, Option};
-use result::{Ok, Err};
+use result::{Ok, Err, Result};
 use path;
 use path::{Path, GenericPath};
+use vec::OwnedVector;
 
-/// Open a file for reading/writing, as indicated by `path`.
-///
-/// # Example
-///
-///     use std::rt::{io, file, io_error};
-///
-///     let p = Path::new("/some/file/path.txt");
-///
-///     do io_error::cond.trap(|_| {
-///         // hoo-boy...
-///     }).inside {
-///         let stream = match file::open_stream(&p, io::Create, io::ReadWrite) {
-///             Some(s) => s,
-///             None => fail!("whoops! I'm sure this raised, anyways..")
-///         };
-///         // do some stuff with that stream
-///
-///         // the file stream will be closed at the end of this block
-///     }
-///     // ..
-///
-/// `FileMode` and `FileAccess` provide information about the permissions
-/// context in which a given stream is created. More information about them
-/// can be found in `std::rt::io`'s docs.
+/// Unconstrained file access type that exposes read and write operations
 ///
-/// Note that, with this function, a `FileStream` is returned regardless of
-/// the access-limitations indicated by `FileAccess` (e.g. calling `write` on a
-/// `FileStream` opened as `ReadOnly` will raise an `io_error` condition at
-/// runtime). If you desire a more-correctly-constrained interface to files,
-/// use the `{open_stream, open, create}` methods that are a part of `Path`.
+/// Can be retreived via `File::open()` and `Path.File::open_mode()`.
 ///
 /// # Errors
 ///
-/// This function will raise an `io_error` condition under a number of different
-/// circumstances, to include but not limited to:
+/// This type will raise an io_error condition if operations are attempted against
+/// it for which its underlying file descriptor was not configured at creation
+/// time, via the `FileAccess` parameter to `file::open()`.
 ///
-/// * Opening a file that already exists with `FileMode` of `Create` or vice
-///   versa (e.g.  opening a non-existant file with `FileMode` or `Open`)
-/// * Attempting to open a file with a `FileAccess` that the user lacks
-///   permissions for
-/// * Filesystem-level errors (full disk, etc)
-pub fn open_stream(path: &Path,
-                   mode: FileMode,
-                   access: FileAccess) -> Option<FileStream> {
+/// For this reason, it is best to use the access-constrained wrappers that are
+/// exposed via `Path.open()` and `Path.create()`.
+pub struct File {
+    priv fd: ~RtioFileStream,
+    priv path: Path,
+    priv last_nread: int,
+}
+
+fn io_raise<T>(f: &fn(io: &mut IoFactory) -> Result<T, IoError>) -> Option<T> {
     do with_local_io |io| {
-        match io.fs_open(&path.to_c_str(), mode, access) {
-            Ok(fd) => Some(FileStream {
-                fd: fd,
-                last_nread: -1
-            }),
+        match f(io) {
+            Ok(t) => Some(t),
             Err(ioerr) => {
                 io_error::cond.raise(ioerr);
                 None
@@ -96,54 +70,367 @@ pub fn open_stream(path: &Path,
     }
 }
 
-/// Attempts to open a file in read-only mode. This function is equivalent to
-/// `open_stream(path, Open, Read)`, and will raise all of the same errors that
-/// `open_stream` does.
-///
-/// For more information, see the `open_stream` function.
-pub fn open(path: &Path) -> Option<FileReader> {
-    open_stream(path, Open, Read).map(|s| FileReader { stream: s })
-}
+impl File {
+    /// Open a file at `path` in the mode specified by the `mode` and `access`
+    /// arguments
+    ///
+    /// # Example
+    ///
+    ///     use std::rt::io::{File, io_error, Open, ReadWrite};
+    ///
+    ///     let p = Path::new("/some/file/path.txt");
+    ///
+    ///     do io_error::cond.trap(|_| {
+    ///         // hoo-boy...
+    ///     }).inside {
+    ///         let file = match File::open_mode(&p, Open, ReadWrite) {
+    ///             Some(s) => s,
+    ///             None => fail!("whoops! I'm sure this raised, anyways..")
+    ///         };
+    ///         // do some stuff with that file
+    ///
+    ///         // the file will be closed at the end of this block
+    ///     }
+    ///     // ..
+    ///
+    /// `FileMode` and `FileAccess` provide information about the permissions
+    /// context in which a given stream is created. More information about them
+    /// can be found in `std::rt::io`'s docs. If a file is opened with `Write`
+    /// or `ReadWrite` access, then it will be created it it does not already
+    /// exist.
+    ///
+    /// Note that, with this function, a `File` is returned regardless of the
+    /// access-limitations indicated by `FileAccess` (e.g. calling `write` on a
+    /// `File` opened as `Read` will raise an `io_error` condition at runtime).
+    ///
+    /// # Errors
+    ///
+    /// This function will raise an `io_error` condition under a number of
+    /// different circumstances, to include but not limited to:
+    ///
+    /// * Opening a file that does not exist with `Read` access.
+    /// * Attempting to open a file with a `FileAccess` that the user lacks
+    ///   permissions for
+    /// * Filesystem-level errors (full disk, etc)
+    pub fn open_mode(path: &Path,
+                     mode: FileMode,
+                     access: FileAccess) -> Option<File> {
+        do with_local_io |io| {
+            match io.fs_open(&path.to_c_str(), mode, access) {
+                Ok(fd) => Some(File {
+                    path: path.clone(),
+                    fd: fd,
+                    last_nread: -1
+                }),
+                Err(ioerr) => {
+                    io_error::cond.raise(ioerr);
+                    None
+                }
+            }
+        }
+    }
 
-/// Attempts to create a file in write-only mode. This function is equivalent to
-/// `open_stream(path, CreateOrTruncate, Write)`, and will raise all of the
-/// same errors that `open_stream` does.
-///
-/// For more information, see the `open_stream` function.
-pub fn create(path: &Path) -> Option<FileWriter> {
-    open_stream(path, CreateOrTruncate, Write).map(|s| FileWriter { stream: s })
-}
+    /// Attempts to open a file in read-only mode. This function is equivalent to
+    /// `File::open_mode(path, Open, Read)`, and will raise all of the same
+    /// errors that `File::open_mode` does.
+    ///
+    /// For more information, see the `File::open_mode` function.
+    ///
+    /// # Example
+    ///
+    ///     use std::rt::io::File;
+    ///
+    ///     let contents = File::open("foo.txt").read_to_end();
+    pub fn open(path: &Path) -> Option<File> {
+        File::open_mode(path, Open, Read)
+    }
 
-/// Unlink a file from the underlying filesystem.
-///
-/// # Example
-///
-///     use std::rt::io::file;
-///
-///     let p = Path::new("/some/file/path.txt");
-///     file::unlink(&p);
-///     // if we made it here without failing, then the
-///     // unlink operation was successful
-///
-/// Note that, just because an unlink call was successful, it is not
-/// guaranteed that a file is immediately deleted (e.g. depending on
-/// platform, other open file descriptors may prevent immediate removal)
-///
-/// # Errors
-///
-/// This function will raise an `io_error` condition if the path points to a
-/// directory, the user lacks permissions to remove the file, or if some other
-/// filesystem-level error occurs.
-pub fn unlink(path: &Path) {
-    do with_local_io |io| {
-        match io.fs_unlink(&path.to_c_str()) {
-            Ok(()) => Some(()),
-            Err(ioerr) => {
-                io_error::cond.raise(ioerr);
-                None
+    /// Attempts to create a file in write-only mode. This function is
+    /// equivalent to `File::open_mode(path, Truncate, Write)`, and will
+    /// raise all of the same errors that `File::open_mode` does.
+    ///
+    /// For more information, see the `File::open_mode` function.
+    ///
+    /// # Example
+    ///
+    ///     use std::rt::io::File;
+    ///
+    ///     File::create("foo.txt").write(bytes!("This is a sample file"));
+    pub fn create(path: &Path) -> Option<File> {
+        File::open_mode(path, Truncate, Write)
+    }
+
+    /// Unlink a file from the underlying filesystem.
+    ///
+    /// # Example
+    ///
+    ///     use std::rt::io::File;
+    ///
+    ///     let p = Path::new("/some/file/path.txt");
+    ///     File::unlink(&p);
+    ///     // if we made it here without failing, then the
+    ///     // unlink operation was successful
+    ///
+    /// Note that, just because an unlink call was successful, it is not
+    /// guaranteed that a file is immediately deleted (e.g. depending on
+    /// platform, other open file descriptors may prevent immediate removal)
+    ///
+    /// # Errors
+    ///
+    /// This function will raise an `io_error` condition if the path points to a
+    /// directory, the user lacks permissions to remove the file, or if some
+    /// other filesystem-level error occurs.
+    pub fn unlink(path: &Path) {
+        do io_raise |io| { io.fs_unlink(&path.to_c_str()) };
+    }
+
+    /// Given a path, query the file system to get information about a file,
+    /// directory, etc. This function will traverse symlinks to query
+    /// information about the destination file.
+    ///
+    /// Returns a fully-filled out stat structure on succes, and on failure it
+    /// will return a dummy stat structure (it is expected that the condition
+    /// raised is handled as well).
+    ///
+    /// # Example
+    ///
+    ///     use std::rt::io::{File, io_error};
+    ///
+    ///     let p = Path::new("/some/file/path.txt");
+    ///
+    ///     do io_error::cond.trap(|_| {
+    ///         // hoo-boy...
+    ///     }).inside {
+    ///         let info = File::stat(p);
+    ///         if info.is_file {
+    ///             // just imagine the possibilities ...
+    ///         }
+    ///     }
+    ///
+    /// # Errors
+    ///
+    /// This call will raise an `io_error` condition if the user lacks the
+    /// requisite permissions to perform a `stat` call on the given path or if
+    /// there is no entry in the filesystem at the provided path.
+    pub fn stat(path: &Path) -> FileStat {
+        do io_raise |io| {
+            io.fs_stat(&path.to_c_str())
+        }.unwrap_or_else(File::dummystat)
+    }
+
+    fn dummystat() -> FileStat {
+        FileStat {
+            path: Path::new(""),
+            size: 0,
+            kind: io::TypeFile,
+            perm: 0,
+            created: 0,
+            modified: 0,
+            accessed: 0,
+            device: 0,
+            inode: 0,
+            rdev: 0,
+            nlink: 0,
+            uid: 0,
+            gid: 0,
+            blksize: 0,
+            blocks: 0,
+            flags: 0,
+            gen: 0,
+        }
+    }
+
+    /// Perform the same operation as the `stat` function, except that this
+    /// function does not traverse through symlinks. This will return
+    /// information about the symlink file instead of the file that it points
+    /// to.
+    ///
+    /// # Errors
+    ///
+    /// See `stat`
+    pub fn lstat(path: &Path) -> FileStat {
+        do io_raise |io| {
+            io.fs_lstat(&path.to_c_str())
+        }.unwrap_or_else(File::dummystat)
+    }
+
+    /// Rename a file or directory to a new name.
+    ///
+    /// # Example
+    ///
+    ///     use std::rt::io::File;
+    ///
+    ///     File::rename(Path::new("foo"), Path::new("bar"));
+    ///     // Oh boy, nothing was raised!
+    ///
+    /// # Errors
+    ///
+    /// Will raise an `io_error` condition if the provided `path` doesn't exist,
+    /// the process lacks permissions to view the contents, or if some other
+    /// intermittent I/O error occurs.
+    pub fn rename(from: &Path, to: &Path) {
+        do io_raise |io| {
+            io.fs_rename(&from.to_c_str(), &to.to_c_str())
+        };
+    }
+
+    /// Copies the contents of one file to another. This function will also
+    /// copy the permission bits of the original file to the destination file.
+    ///
+    /// Note that if `from` and `to` both point to the same file, then the file
+    /// will likely get truncated by this operation.
+    ///
+    /// # Example
+    ///
+    ///     use std::rt::io::File;
+    ///
+    ///     File::copy(Path::new("foo.txt"), Path::new("bar.txt"));
+    ///     // Oh boy, nothing was raised!
+    ///
+    /// # Errors
+    ///
+    /// Will raise an `io_error` condition is the following situtations, but is
+    /// not limited to just these cases:
+    ///
+    /// * The `from` path is not a file
+    /// * The `from` file does not exist
+    /// * The current process does not have the permission rights to access
+    ///   `from` or write `to`
+    ///
+    /// Note that this copy is not atomic in that once the destination is
+    /// ensured to not exist, the is nothing preventing the destination from
+    /// being created and then destroyed by this operation.
+    pub fn copy(from: &Path, to: &Path) {
+        if !from.is_file() {
+            return io_error::cond.raise(IoError {
+                kind: io::MismatchedFileTypeForOperation,
+                desc: "the source path is not an existing file",
+                detail: None,
+            });
+        }
+
+        let mut reader = match File::open(from) { Some(f) => f, None => return };
+        let mut writer = match File::create(to) { Some(f) => f, None => return };
+        let mut buf = [0, ..io::DEFAULT_BUF_SIZE];
+
+        loop {
+            match reader.read(buf) {
+                Some(amt) => writer.write(buf.slice_to(amt)),
+                None => break
             }
         }
-    };
+
+        File::chmod(to, from.stat().perm)
+    }
+
+    /// Changes the permission mode bits found on a file or a directory. This
+    /// function takes a mask from the `io` module
+    ///
+    /// # Example
+    ///
+    ///     use std::rt::io;
+    ///     use std::rt::io::File;
+    ///
+    ///     File::chmod(&Path::new("file.txt"), io::UserFile);
+    ///     File::chmod(&Path::new("file.txt"), io::UserRead | io::UserWrite);
+    ///     File::chmod(&Path::new("dir"),      io::UserDir);
+    ///     File::chmod(&Path::new("file.exe"), io::UserExec);
+    ///
+    /// # Errors
+    ///
+    /// If this funciton encounters an I/O error, it will raise on the `io_error`
+    /// condition. Some possible error situations are not having the permission to
+    /// change the attributes of a file or the file not existing.
+    pub fn chmod(path: &Path, mode: io::FilePermission) {
+        do io_raise |io| {
+            io.fs_chmod(&path.to_c_str(), mode)
+        };
+    }
+
+    /// Change the user and group owners of a file at the specified path.
+    ///
+    /// # Errors
+    ///
+    /// This funtion will raise on the `io_error` condition on failure.
+    pub fn chown(path: &Path, uid: int, gid: int) {
+        do io_raise |io| { io.fs_chown(&path.to_c_str(), uid, gid) };
+    }
+
+    /// Creates a new hard link on the filesystem. The `dst` path will be a
+    /// link pointing to the `src` path. Note that systems often require these
+    /// two paths to both be located on the same filesystem.
+    ///
+    /// # Errors
+    ///
+    /// This function will raise on the `io_error` condition on failure.
+    pub fn link(src: &Path, dst: &Path) {
+        do io_raise |io| { io.fs_link(&src.to_c_str(), &dst.to_c_str()) };
+    }
+
+    /// Creates a new symbolic link on the filesystem. The `dst` path will be a
+    /// symlink pointing to the `src` path.
+    ///
+    /// # Errors
+    ///
+    /// This function will raise on the `io_error` condition on failure.
+    pub fn symlink(src: &Path, dst: &Path) {
+        do io_raise |io| { io.fs_symlink(&src.to_c_str(), &dst.to_c_str()) };
+    }
+
+    /// Reads a symlink, returning the file that the symlink points to.
+    ///
+    /// # Errors
+    ///
+    /// This function will raise on the `io_error` condition on failure.
+    ///
+    /// XXX: does this fail if called on files.
+    pub fn readlink(path: &Path) -> Option<Path> {
+        do io_raise |io| { io.fs_readlink(&path.to_c_str()) }
+    }
+
+    /// Returns the original path which was used to open this file.
+    pub fn path<'a>(&'a self) -> &'a Path {
+        &self.path
+    }
+
+    /// Synchronizes all modifications to this file to its permanent storage
+    /// device. This will flush any internal buffers necessary to perform this
+    /// operation.
+    ///
+    /// # Errors
+    ///
+    /// This function will raise on the `io_error` condition on failure.
+    pub fn fsync(&mut self) {
+        self.fd.fsync();
+    }
+
+    /// This function is similar to `fsync`, except that it may not synchronize
+    /// file metadata to the filesystem. This is intended for use case which
+    /// must synchronize content, but don't need the metadata on disk. The goal
+    /// of this method is to reduce disk operations.
+    ///
+    /// # Errors
+    ///
+    /// This function will raise on the `io_error` condition on failure.
+    pub fn datasync(&mut self) {
+        self.fd.datasync();
+    }
+
+    /// Either truncates or extends the underlying file, as extended from the
+    /// file's current position. This is equivalent to the unix `truncate`
+    /// function.
+    ///
+    /// The offset given is added to the file's current position and the result
+    /// is the new size of the file. If the new size is less than the current
+    /// size, then the file is truncated. If the new size is greater than the
+    /// current size, then the file is expanded to be filled with 0s.
+    ///
+    /// # Errors
+    ///
+    /// On error, this function will raise on the `io_error` condition.
+    pub fn truncate(&mut self, offset: i64) {
+        self.fd.truncate(offset);
+    }
 }
 
 /// Create a new, empty directory at the provided path
@@ -163,14 +450,8 @@ pub fn unlink(path: &Path) {
 /// to make a new directory at the provided path, or if the directory already
 /// exists.
 pub fn mkdir(path: &Path, mode: FilePermission) {
-    do with_local_io |io| {
-        match io.fs_mkdir(&path.to_c_str(), mode) {
-            Ok(_) => Some(()),
-            Err(ioerr) => {
-                io_error::cond.raise(ioerr);
-                None
-            }
-        }
+    do io_raise |io| {
+        io.fs_mkdir(&path.to_c_str(), mode)
     };
 }
 
@@ -190,71 +471,11 @@ pub fn mkdir(path: &Path, mode: FilePermission) {
 /// to remove the directory at the provided path, or if the directory isn't
 /// empty.
 pub fn rmdir(path: &Path) {
-    do with_local_io |io| {
-        match io.fs_rmdir(&path.to_c_str()) {
-            Ok(_) => Some(()),
-            Err(ioerr) => {
-                io_error::cond.raise(ioerr);
-                None
-            }
-        }
+    do io_raise |io| {
+        io.fs_rmdir(&path.to_c_str())
     };
 }
 
-/// Get information on the file, directory, etc at the provided path
-///
-/// Given a path, query the file system to get information about a file,
-/// directory, etc.
-///
-/// Returns a fully-filled out stat structure on succes, and on failure it will
-/// return a dummy stat structure (it is expected that the condition raised is
-/// handled as well).
-///
-/// # Example
-///
-///     use std::rt::io::{file, io_error};
-///
-///     let p = Path::new("/some/file/path.txt");
-///
-///     do io_error::cond.trap(|_| {
-///         // hoo-boy...
-///     }).inside {
-///         let info = file::stat(p);
-///         if info.is_file {
-///             // just imagine the possibilities ...
-///         }
-///     }
-///
-/// # Errors
-///
-/// This call will raise an `io_error` condition if the user lacks the requisite
-/// permissions to perform a `stat` call on the given path or if there is no
-/// entry in the filesystem at the provided path.
-pub fn stat(path: &Path) -> FileStat {
-    do with_local_io |io| {
-        match io.fs_stat(&path.to_c_str()) {
-            Ok(p) => Some(p),
-            Err(ioerr) => {
-                io_error::cond.raise(ioerr);
-                None
-            }
-        }
-    }.unwrap_or_else(|| {
-        FileStat {
-            path: Path::new(path.to_c_str()),
-            is_file: false,
-            is_dir: false,
-            device: 0,
-            mode: 0,
-            inode: 0,
-            size: 0,
-            created: 0,
-            modified: 0,
-            accessed: 0,
-        }
-    })
-}
-
 /// Retrieve a vector containing all entries within a provided directory
 ///
 /// # Example
@@ -278,105 +499,41 @@ pub fn stat(path: &Path) -> FileStat {
 /// the process lacks permissions to view the contents or if the `path` points
 /// at a non-directory file
 pub fn readdir(path: &Path) -> ~[Path] {
-    do with_local_io |io| {
-        match io.fs_readdir(&path.to_c_str(), 0) {
-            Ok(p) => Some(p),
-            Err(ioerr) => {
-                io_error::cond.raise(ioerr);
-                None
-            }
-        }
+    do io_raise |io| {
+        io.fs_readdir(&path.to_c_str(), 0)
     }.unwrap_or_else(|| ~[])
 }
 
-/// Rename a file or directory to a new name.
-///
-/// # Example
-///
-///     use std::rt::io::file;
-///
-///     file::rename(Path::new("foo"), Path::new("bar"));
-///     // Oh boy, nothing was raised!
-///
-/// # Errors
-///
-/// Will raise an `io_error` condition if the provided `path` doesn't exist,
-/// the process lacks permissions to view the contents, or if some other
-/// intermittent I/O error occurs.
-pub fn rename(from: &Path, to: &Path) {
-    do with_local_io |io| {
-        match io.fs_rename(&from.to_c_str(), &to.to_c_str()) {
-            Ok(()) => Some(()),
-            Err(ioerr) => {
-                io_error::cond.raise(ioerr);
-                None
-            }
-        }
-    };
+/// Returns an iterator which will recursively walk the directory structure
+/// rooted at `path`. The path given will not be iterated over, and this will
+/// perform iteration in a top-down order.
+pub fn walk_dir(path: &Path) -> WalkIterator {
+    WalkIterator { stack: readdir(path) }
 }
 
-/// Copies the contents of one file to another.
-///
-/// # Example
-///
-///     use std::rt::io::file;
-///
-///     file::copy(Path::new("foo.txt"), Path::new("bar.txt"));
-///     // Oh boy, nothing was raised!
-///
-/// # Errors
-///
-/// Will raise an `io_error` condition if the provided `from` doesn't exist,
-/// the process lacks permissions to view the contents, or if some other
-/// intermittent I/O error occurs (such as `to` couldn't be created).
-pub fn copy(from: &Path, to: &Path) {
-    let mut reader = match open(from) { Some(f) => f, None => return };
-    let mut writer = match create(to) { Some(f) => f, None => return };
-    let mut buf = [0, ..io::DEFAULT_BUF_SIZE];
-
-    loop {
-        match reader.read(buf) {
-            Some(amt) => writer.write(buf.slice_to(amt)),
-            None => break
-        }
-    }
-
-    // FIXME(#10131) this is an awful way to pull out the permission bits.
-    //               If this comment is removed, then there should be a test
-    //               asserting that permission bits are maintained using the
-    //               permission interface created.
-    chmod(to, (from.stat().mode & 0xfff) as u32);
+/// An iterator which walks over a directory
+pub struct WalkIterator {
+    priv stack: ~[Path],
 }
 
-// This function is not public because it's got a terrible interface for `mode`
-// FIXME(#10131)
-fn chmod(path: &Path, mode: u32) {
-    do with_local_io |io| {
-        match io.fs_chmod(&path.to_c_str(), mode) {
-            Ok(()) => Some(()),
-            Err(ioerr) => {
-                io_error::cond.raise(ioerr);
-                None
+impl Iterator<Path> for WalkIterator {
+    fn next(&mut self) -> Option<Path> {
+        match self.stack.shift_opt() {
+            Some(path) => {
+                if path.is_dir() {
+                    self.stack.push_all_move(readdir(&path));
+                }
+                Some(path)
             }
+            None => None
         }
-    };
-}
-
-/// Recursively walk a directory structure. This function will call the
-/// provided closure on all directories and files found inside the path
-/// pointed to by `self`. If the closure returns `false`, then the iteration
-/// will be short-circuited.
-pub fn walk_dir(path: &Path, f: &fn(&Path) -> bool) -> bool {
-    let files = readdir(path);
-    files.iter().advance(|q| {
-        f(q) && (!q.is_dir() || walk_dir(q, |p| f(p)))
-    })
+    }
 }
 
 /// Recursively create a directory and all of its parent components if they
 /// are missing.
 ///
-/// # Failure
+/// # Errors
 ///
 /// This function will raise on the `io_error` condition if an error
 /// happens, see `file::mkdir` for more information about error conditions
@@ -396,74 +553,25 @@ pub fn mkdir_recursive(path: &Path, mode: FilePermission) {
 /// Removes a directory at this path, after removing all its contents. Use
 /// carefully!
 ///
-/// # Failure
+/// # Errors
 ///
 /// This function will raise on the `io_error` condition if an error
 /// happens. See `file::unlink` and `file::readdir` for possible error
 /// conditions.
 pub fn rmdir_recursive(path: &Path) {
-    do walk_dir(path) |inner| {
-        if inner.is_dir() {
-            rmdir_recursive(inner);
+    let children = readdir(path);
+    for child in children.iter() {
+        if child.is_dir() {
+            rmdir_recursive(child);
         } else {
-            unlink(inner);
+            File::unlink(child);
         }
-        true
-    };
+    }
     // Directory should now be empty
     rmdir(path);
 }
 
-/// Constrained version of `FileStream` that only exposes read-specific
-/// operations.
-///
-/// Can be retreived via `Path.open()` or `file::open`.
-pub struct FileReader { priv stream: FileStream }
-
-impl Reader for FileReader {
-    fn read(&mut self, buf: &mut [u8]) -> Option<uint> { self.stream.read(buf) }
-    fn eof(&mut self) -> bool { self.stream.eof() }
-}
-
-impl Seek for FileReader {
-    fn tell(&self) -> u64 { self.stream.tell() }
-    fn seek(&mut self, p: i64, s: SeekStyle) { self.stream.seek(p, s) }
-}
-
-/// Constrained version of `FileStream` that only exposes write-specific
-/// operations.
-///
-/// Can be retreived via `Path.create()` or `file::create`.
-pub struct FileWriter { priv stream: FileStream }
-
-impl Writer for FileWriter {
-    fn write(&mut self, buf: &[u8]) { self.stream.write(buf); }
-    fn flush(&mut self) { self.stream.flush(); }
-}
-
-impl Seek for FileWriter {
-    fn tell(&self) -> u64 { self.stream.tell() }
-    fn seek(&mut self, p: i64, s: SeekStyle) { self.stream.seek(p, s); }
-}
-
-/// Unconstrained file access type that exposes read and write operations
-///
-/// Can be retreived via `file::open()` and `Path.open_stream()`.
-///
-/// # Errors
-///
-/// This type will raise an io_error condition if operations are attempted against
-/// it for which its underlying file descriptor was not configured at creation
-/// time, via the `FileAccess` parameter to `file::open()`.
-///
-/// For this reason, it is best to use the access-constrained wrappers that are
-/// exposed via `Path.open()` and `Path.create()`.
-pub struct FileStream {
-    priv fd: ~RtioFileStream,
-    priv last_nread: int,
-}
-
-impl Reader for FileStream {
+impl Reader for File {
     fn read(&mut self, buf: &mut [u8]) -> Option<uint> {
         match self.fd.read(buf) {
             Ok(read) => {
@@ -486,10 +594,10 @@ impl Reader for FileStream {
     fn eof(&mut self) -> bool { self.last_nread == 0 }
 }
 
-impl Writer for FileStream {
+impl Writer for File {
     fn write(&mut self, buf: &[u8]) {
         match self.fd.write(buf) {
-            Ok(_) => (),
+            Ok(()) => (),
             Err(ioerr) => {
                 io_error::cond.raise(ioerr);
             }
@@ -497,7 +605,7 @@ impl Writer for FileStream {
     }
 }
 
-impl Seek for FileStream {
+impl Seek for File {
     fn tell(&self) -> u64 {
         let res = self.fd.tell();
         match res {
@@ -529,7 +637,7 @@ impl path::Path {
     /// Consult the `file::stat` documentation for more info.
     ///
     /// This call preserves identical runtime/error semantics with `file::stat`.
-    pub fn stat(&self) -> FileStat { stat(self) }
+    pub fn stat(&self) -> FileStat { File::stat(self) }
 
     /// Boolean value indicator whether the underlying file exists on the local
     /// filesystem. This will return true if the path points to either a
@@ -552,7 +660,7 @@ impl path::Path {
     /// Will not raise a condition
     pub fn is_file(&self) -> bool {
         match io::result(|| self.stat()) {
-            Ok(s) => s.is_file,
+            Ok(s) => s.kind == io::TypeFile,
             Err(*) => false
         }
     }
@@ -567,7 +675,7 @@ impl path::Path {
     /// Will not raise a condition
     pub fn is_dir(&self) -> bool {
         match io::result(|| self.stat()) {
-            Ok(s) => s.is_dir,
+            Ok(s) => s.kind == io::TypeDirectory,
             Err(*) => false
         }
     }
@@ -575,19 +683,11 @@ impl path::Path {
 
 #[cfg(test)]
 mod test {
-    use path::{Path, GenericPath};
-    use result::{Ok, Err};
-    use option::{Some, None};
-    use iter::range;
-    use rt::test::run_in_mt_newsched_task;
-    use super::{open_stream, unlink, stat, copy, rmdir, mkdir, readdir,
-                open, create, rmdir_recursive, mkdir_recursive};
-
+    use prelude::*;
+    use rt::io::{SeekSet, SeekCur, SeekEnd, io_error, Read, Open, ReadWrite};
     use rt::io;
-    use rt::io::Reader;
-    use super::super::{SeekSet, SeekCur, SeekEnd,
-                       io_error, Read, Create, Open, ReadWrite};
-    use vec::Vector;
+    use str;
+    use super::{File, rmdir, mkdir, readdir, rmdir_recursive, mkdir_recursive};
 
     fn tmpdir() -> Path {
         use os;
@@ -599,266 +699,236 @@ mod test {
 
     #[test]
     fn file_test_io_smoke_test() {
-        do run_in_mt_newsched_task {
-            let message = "it's alright. have a good time";
-            let filename = &Path::new("./tmp/file_rt_io_file_test.txt");
-            {
-                let mut write_stream = open_stream(filename, Create, ReadWrite);
-                write_stream.write(message.as_bytes());
-            }
-            {
-                use str;
-                let mut read_stream = open_stream(filename, Open, Read);
-                let mut read_buf = [0, .. 1028];
-                let read_str = match read_stream.read(read_buf).unwrap() {
-                    -1|0 => fail!("shouldn't happen"),
-                    n => str::from_utf8(read_buf.slice_to(n))
-                };
-                assert!(read_str == message.to_owned());
-            }
-            unlink(filename);
+        let message = "it's alright. have a good time";
+        let filename = &Path::new("./tmp/file_rt_io_file_test.txt");
+        {
+            let mut write_stream = File::open_mode(filename, Open, ReadWrite);
+            write_stream.write(message.as_bytes());
         }
+        {
+            let mut read_stream = File::open_mode(filename, Open, Read);
+            let mut read_buf = [0, .. 1028];
+            let read_str = match read_stream.read(read_buf).unwrap() {
+                -1|0 => fail!("shouldn't happen"),
+                n => str::from_utf8(read_buf.slice_to(n))
+            };
+            assert!(read_str == message.to_owned());
+        }
+        File::unlink(filename);
     }
 
     #[test]
     fn file_test_io_invalid_path_opened_without_create_should_raise_condition() {
-        do run_in_mt_newsched_task {
-            let filename = &Path::new("./tmp/file_that_does_not_exist.txt");
-            let mut called = false;
-            do io_error::cond.trap(|_| {
-                called = true;
-            }).inside {
-                let result = open_stream(filename, Open, Read);
-                assert!(result.is_none());
-            }
-            assert!(called);
+        let filename = &Path::new("./tmp/file_that_does_not_exist.txt");
+        let mut called = false;
+        do io_error::cond.trap(|_| {
+            called = true;
+        }).inside {
+            let result = File::open_mode(filename, Open, Read);
+            assert!(result.is_none());
         }
+        assert!(called);
     }
 
     #[test]
     fn file_test_iounlinking_invalid_path_should_raise_condition() {
-        do run_in_mt_newsched_task {
-            let filename = &Path::new("./tmp/file_another_file_that_does_not_exist.txt");
-            let mut called = false;
-            do io_error::cond.trap(|_| {
-                called = true;
-            }).inside {
-                unlink(filename);
-            }
-            assert!(called);
+        let filename = &Path::new("./tmp/file_another_file_that_does_not_exist.txt");
+        let mut called = false;
+        do io_error::cond.trap(|_| {
+            called = true;
+        }).inside {
+            File::unlink(filename);
         }
+        assert!(called);
     }
 
     #[test]
     fn file_test_io_non_positional_read() {
-        do run_in_mt_newsched_task {
-            use str;
-            let message = "ten-four";
-            let mut read_mem = [0, .. 8];
-            let filename = &Path::new("./tmp/file_rt_io_file_test_positional.txt");
+        let message = "ten-four";
+        let mut read_mem = [0, .. 8];
+        let filename = &Path::new("./tmp/file_rt_io_file_test_positional.txt");
+        {
+            let mut rw_stream = File::open_mode(filename, Open, ReadWrite);
+            rw_stream.write(message.as_bytes());
+        }
+        {
+            let mut read_stream = File::open_mode(filename, Open, Read);
             {
-                let mut rw_stream = open_stream(filename, Create, ReadWrite);
-                rw_stream.write(message.as_bytes());
+                let read_buf = read_mem.mut_slice(0, 4);
+                read_stream.read(read_buf);
             }
             {
-                let mut read_stream = open_stream(filename, Open, Read);
-                {
-                    let read_buf = read_mem.mut_slice(0, 4);
-                    read_stream.read(read_buf);
-                }
-                {
-                    let read_buf = read_mem.mut_slice(4, 8);
-                    read_stream.read(read_buf);
-                }
+                let read_buf = read_mem.mut_slice(4, 8);
+                read_stream.read(read_buf);
             }
-            unlink(filename);
-            let read_str = str::from_utf8(read_mem);
-            assert!(read_str == message.to_owned());
         }
+        File::unlink(filename);
+        let read_str = str::from_utf8(read_mem);
+        assert!(read_str == message.to_owned());
     }
 
     #[test]
     fn file_test_io_seek_and_tell_smoke_test() {
-        do run_in_mt_newsched_task {
-            use str;
-            let message = "ten-four";
-            let mut read_mem = [0, .. 4];
-            let set_cursor = 4 as u64;
-            let mut tell_pos_pre_read;
-            let mut tell_pos_post_read;
-            let filename = &Path::new("./tmp/file_rt_io_file_test_seeking.txt");
-            {
-                let mut rw_stream = open_stream(filename, Create, ReadWrite);
-                rw_stream.write(message.as_bytes());
-            }
-            {
-                let mut read_stream = open_stream(filename, Open, Read);
-                read_stream.seek(set_cursor as i64, SeekSet);
-                tell_pos_pre_read = read_stream.tell();
-                read_stream.read(read_mem);
-                tell_pos_post_read = read_stream.tell();
-            }
-            unlink(filename);
-            let read_str = str::from_utf8(read_mem);
-            assert!(read_str == message.slice(4, 8).to_owned());
-            assert!(tell_pos_pre_read == set_cursor);
-            assert!(tell_pos_post_read == message.len() as u64);
+        let message = "ten-four";
+        let mut read_mem = [0, .. 4];
+        let set_cursor = 4 as u64;
+        let mut tell_pos_pre_read;
+        let mut tell_pos_post_read;
+        let filename = &Path::new("./tmp/file_rt_io_file_test_seeking.txt");
+        {
+            let mut rw_stream = File::open_mode(filename, Open, ReadWrite);
+            rw_stream.write(message.as_bytes());
+        }
+        {
+            let mut read_stream = File::open_mode(filename, Open, Read);
+            read_stream.seek(set_cursor as i64, SeekSet);
+            tell_pos_pre_read = read_stream.tell();
+            read_stream.read(read_mem);
+            tell_pos_post_read = read_stream.tell();
         }
+        File::unlink(filename);
+        let read_str = str::from_utf8(read_mem);
+        assert!(read_str == message.slice(4, 8).to_owned());
+        assert!(tell_pos_pre_read == set_cursor);
+        assert!(tell_pos_post_read == message.len() as u64);
     }
 
     #[test]
     fn file_test_io_seek_and_write() {
-        do run_in_mt_newsched_task {
-            use str;
-            let initial_msg =   "food-is-yummy";
-            let overwrite_msg =    "-the-bar!!";
-            let final_msg =     "foo-the-bar!!";
-            let seek_idx = 3;
-            let mut read_mem = [0, .. 13];
-            let filename = &Path::new("./tmp/file_rt_io_file_test_seek_and_write.txt");
-            {
-                let mut rw_stream = open_stream(filename, Create, ReadWrite);
-                rw_stream.write(initial_msg.as_bytes());
-                rw_stream.seek(seek_idx as i64, SeekSet);
-                rw_stream.write(overwrite_msg.as_bytes());
-            }
-            {
-                let mut read_stream = open_stream(filename, Open, Read);
-                read_stream.read(read_mem);
-            }
-            unlink(filename);
-            let read_str = str::from_utf8(read_mem);
-            assert!(read_str == final_msg.to_owned());
+        let initial_msg =   "food-is-yummy";
+        let overwrite_msg =    "-the-bar!!";
+        let final_msg =     "foo-the-bar!!";
+        let seek_idx = 3;
+        let mut read_mem = [0, .. 13];
+        let filename = &Path::new("./tmp/file_rt_io_file_test_seek_and_write.txt");
+        {
+            let mut rw_stream = File::open_mode(filename, Open, ReadWrite);
+            rw_stream.write(initial_msg.as_bytes());
+            rw_stream.seek(seek_idx as i64, SeekSet);
+            rw_stream.write(overwrite_msg.as_bytes());
         }
+        {
+            let mut read_stream = File::open_mode(filename, Open, Read);
+            read_stream.read(read_mem);
+        }
+        File::unlink(filename);
+        let read_str = str::from_utf8(read_mem);
+        assert!(read_str == final_msg.to_owned());
     }
 
     #[test]
     fn file_test_io_seek_shakedown() {
-        do run_in_mt_newsched_task {
-            use str;          // 01234567890123
-            let initial_msg =   "qwer-asdf-zxcv";
-            let chunk_one = "qwer";
-            let chunk_two = "asdf";
-            let chunk_three = "zxcv";
-            let mut read_mem = [0, .. 4];
-            let filename = &Path::new("./tmp/file_rt_io_file_test_seek_shakedown.txt");
-            {
-                let mut rw_stream = open_stream(filename, Create, ReadWrite);
-                rw_stream.write(initial_msg.as_bytes());
-            }
-            {
-                let mut read_stream = open_stream(filename, Open, Read);
-
-                read_stream.seek(-4, SeekEnd);
-                read_stream.read(read_mem);
-                let read_str = str::from_utf8(read_mem);
-                assert!(read_str == chunk_three.to_owned());
-
-                read_stream.seek(-9, SeekCur);
-                read_stream.read(read_mem);
-                let read_str = str::from_utf8(read_mem);
-                assert!(read_str == chunk_two.to_owned());
-
-                read_stream.seek(0, SeekSet);
-                read_stream.read(read_mem);
-                let read_str = str::from_utf8(read_mem);
-                assert!(read_str == chunk_one.to_owned());
-            }
-            unlink(filename);
+        use std::str;          // 01234567890123
+        let initial_msg =   "qwer-asdf-zxcv";
+        let chunk_one = "qwer";
+        let chunk_two = "asdf";
+        let chunk_three = "zxcv";
+        let mut read_mem = [0, .. 4];
+        let filename = &Path::new("./tmp/file_rt_io_file_test_seek_shakedown.txt");
+        {
+            let mut rw_stream = File::open_mode(filename, Open, ReadWrite);
+            rw_stream.write(initial_msg.as_bytes());
         }
+        {
+            let mut read_stream = File::open_mode(filename, Open, Read);
+
+            read_stream.seek(-4, SeekEnd);
+            read_stream.read(read_mem);
+            let read_str = str::from_utf8(read_mem);
+            assert!(read_str == chunk_three.to_owned());
+
+            read_stream.seek(-9, SeekCur);
+            read_stream.read(read_mem);
+            let read_str = str::from_utf8(read_mem);
+            assert!(read_str == chunk_two.to_owned());
+
+            read_stream.seek(0, SeekSet);
+            read_stream.read(read_mem);
+            let read_str = str::from_utf8(read_mem);
+            assert!(read_str == chunk_one.to_owned());
+        }
+        File::unlink(filename);
     }
 
     #[test]
     fn file_test_stat_is_correct_on_is_file() {
-        do run_in_mt_newsched_task {
-            let filename = &Path::new("./tmp/file_stat_correct_on_is_file.txt");
-            {
-                let mut fs = open_stream(filename, Create, ReadWrite);
-                let msg = "hw";
-                fs.write(msg.as_bytes());
-            }
-            let stat_res = stat(filename);
-            assert!(stat_res.is_file);
-            unlink(filename);
+        let filename = &Path::new("./tmp/file_stat_correct_on_is_file.txt");
+        {
+            let mut fs = File::open_mode(filename, Open, ReadWrite);
+            let msg = "hw";
+            fs.write(msg.as_bytes());
         }
+        let stat_res = File::stat(filename);
+        assert_eq!(stat_res.kind, io::TypeFile);
+        File::unlink(filename);
     }
 
     #[test]
     fn file_test_stat_is_correct_on_is_dir() {
-        do run_in_mt_newsched_task {
-            let filename = &Path::new("./tmp/file_stat_correct_on_is_dir");
-            mkdir(filename, io::UserRWX);
-            let stat_res = filename.stat();
-            assert!(stat_res.is_dir);
-            rmdir(filename);
-        }
+        let filename = &Path::new("./tmp/file_stat_correct_on_is_dir");
+        mkdir(filename, io::UserRWX);
+        let stat_res = filename.stat();
+        assert!(stat_res.kind == io::TypeDirectory);
+        rmdir(filename);
     }
 
     #[test]
     fn file_test_fileinfo_false_when_checking_is_file_on_a_directory() {
-        do run_in_mt_newsched_task {
-            let dir = &Path::new("./tmp/fileinfo_false_on_dir");
-            mkdir(dir, io::UserRWX);
-            assert!(dir.is_file() == false);
-            rmdir(dir);
-        }
+        let dir = &Path::new("./tmp/fileinfo_false_on_dir");
+        mkdir(dir, io::UserRWX);
+        assert!(dir.is_file() == false);
+        rmdir(dir);
     }
 
     #[test]
     fn file_test_fileinfo_check_exists_before_and_after_file_creation() {
-        do run_in_mt_newsched_task {
-            let file = &Path::new("./tmp/fileinfo_check_exists_b_and_a.txt");
-            create(file).write(bytes!("foo"));
-            assert!(file.exists());
-            unlink(file);
-            assert!(!file.exists());
-        }
+        let file = &Path::new("./tmp/fileinfo_check_exists_b_and_a.txt");
+        File::create(file).write(bytes!("foo"));
+        assert!(file.exists());
+        File::unlink(file);
+        assert!(!file.exists());
     }
 
     #[test]
     fn file_test_directoryinfo_check_exists_before_and_after_mkdir() {
-        do run_in_mt_newsched_task {
-            let dir = &Path::new("./tmp/before_and_after_dir");
-            assert!(!dir.exists());
-            mkdir(dir, io::UserRWX);
-            assert!(dir.exists());
-            assert!(dir.is_dir());
-            rmdir(dir);
-            assert!(!dir.exists());
-        }
+        let dir = &Path::new("./tmp/before_and_after_dir");
+        assert!(!dir.exists());
+        mkdir(dir, io::UserRWX);
+        assert!(dir.exists());
+        assert!(dir.is_dir());
+        rmdir(dir);
+        assert!(!dir.exists());
     }
 
     #[test]
     fn file_test_directoryinfo_readdir() {
-        use str;
-        do run_in_mt_newsched_task {
-            let dir = &Path::new("./tmp/di_readdir");
-            mkdir(dir, io::UserRWX);
-            let prefix = "foo";
-            for n in range(0,3) {
-                let f = dir.join(format!("{}.txt", n));
-                let mut w = create(&f);
-                let msg_str = (prefix + n.to_str().to_owned()).to_owned();
-                let msg = msg_str.as_bytes();
-                w.write(msg);
-            }
-            let files = readdir(dir);
-            let mut mem = [0u8, .. 4];
-            for f in files.iter() {
-                {
-                    let n = f.filestem_str();
-                    open(f).read(mem);
-                    let read_str = str::from_utf8(mem);
-                    let expected = match n {
-                        None|Some("") => fail!("really shouldn't happen.."),
-                        Some(n) => prefix+n
-                    };
-                    assert!(expected == read_str);
-                }
-                unlink(f);
+        use std::str;
+        let dir = &Path::new("./tmp/di_readdir");
+        mkdir(dir, io::UserRWX);
+        let prefix = "foo";
+        for n in range(0,3) {
+            let f = dir.join(format!("{}.txt", n));
+            let mut w = File::create(&f);
+            let msg_str = (prefix + n.to_str().to_owned()).to_owned();
+            let msg = msg_str.as_bytes();
+            w.write(msg);
+        }
+        let files = readdir(dir);
+        let mut mem = [0u8, .. 4];
+        for f in files.iter() {
+            {
+                let n = f.filestem_str();
+                File::open(f).read(mem);
+                let read_str = str::from_utf8(mem);
+                let expected = match n {
+                    None|Some("") => fail!("really shouldn't happen.."),
+                    Some(n) => prefix+n
+                };
+                assert!(expected == read_str);
             }
-            rmdir(dir);
+            File::unlink(f);
         }
+        rmdir(dir);
     }
 
     #[test]
@@ -880,7 +950,7 @@ mod test {
 
         let mut filepath = dirpath;
         filepath.push("unicode-file-\uac00\u4e00\u30fc\u4f60\u597d.rs");
-        create(&filepath); // ignore return; touch only
+        File::create(&filepath); // ignore return; touch only
         assert!(!filepath.is_dir());
         assert!(filepath.exists());
 
@@ -905,7 +975,7 @@ mod test {
     fn copy_file_does_not_exist() {
         let from = Path::new("test/nonexistent-bogus-path");
         let to = Path::new("test/other-bogus-path");
-        match io::result(|| copy(&from, &to)) {
+        match io::result(|| File::copy(&from, &to)) {
             Ok(*) => fail!(),
             Err(*) => {
                 assert!(!from.exists());
@@ -920,12 +990,226 @@ mod test {
         let input = tmpdir.join("in.txt");
         let out = tmpdir.join("out.txt");
 
-        create(&input).write(bytes!("hello"));
-        copy(&input, &out);
-        let contents = open(&out).read_to_end();
+        File::create(&input).write(bytes!("hello"));
+        File::copy(&input, &out);
+        let contents = File::open(&out).read_to_end();
         assert_eq!(contents.as_slice(), bytes!("hello"));
 
-        assert_eq!(input.stat().mode, out.stat().mode);
+        assert_eq!(input.stat().perm, out.stat().perm);
+        rmdir_recursive(&tmpdir);
+    }
+
+    #[test]
+    fn copy_file_dst_dir() {
+        let tmpdir = tmpdir();
+        let out = tmpdir.join("out");
+
+        File::create(&out);
+        match io::result(|| File::copy(&out, &tmpdir)) {
+            Ok(*) => fail!(), Err(*) => {}
+        }
+        rmdir_recursive(&tmpdir);
+    }
+
+    #[test]
+    fn copy_file_dst_exists() {
+        let tmpdir = tmpdir();
+        let input = tmpdir.join("in");
+        let output = tmpdir.join("out");
+
+        File::create(&input).write("foo".as_bytes());
+        File::create(&output).write("bar".as_bytes());
+        File::copy(&input, &output);
+
+        assert_eq!(File::open(&output).read_to_end(),
+                   (bytes!("foo")).to_owned());
+
+        rmdir_recursive(&tmpdir);
+    }
+
+    #[test]
+    fn copy_file_src_dir() {
+        let tmpdir = tmpdir();
+        let out = tmpdir.join("out");
+
+        match io::result(|| File::copy(&tmpdir, &out)) {
+            Ok(*) => fail!(), Err(*) => {}
+        }
+        assert!(!out.exists());
+        rmdir_recursive(&tmpdir);
+    }
+
+    #[test]
+    fn copy_file_preserves_perm_bits() {
+        let tmpdir = tmpdir();
+        let input = tmpdir.join("in.txt");
+        let out = tmpdir.join("out.txt");
+
+        File::create(&input);
+        File::chmod(&input, io::UserExec);
+        File::copy(&input, &out);
+        assert_eq!(out.stat().perm, io::UserExec);
+
+        rmdir_recursive(&tmpdir);
+    }
+
+    #[test]
+    fn symlinks_work() {
+        let tmpdir = tmpdir();
+        let input = tmpdir.join("in.txt");
+        let out = tmpdir.join("out.txt");
+
+        File::create(&input).write("foobar".as_bytes());
+        File::symlink(&input, &out);
+        assert_eq!(File::lstat(&out).kind, io::TypeSymlink);
+        assert_eq!(File::stat(&out).size, File::stat(&input).size);
+        assert_eq!(File::open(&out).read_to_end(), (bytes!("foobar")).to_owned());
+
+        // can't link to yourself
+        match io::result(|| File::symlink(&input, &input)) {
+            Ok(*) => fail!("wanted a failure"),
+            Err(*) => {}
+        }
+        // symlinks can point to things that don't exist
+        File::symlink(&tmpdir.join("foo"), &tmpdir.join("bar"));
+
+        assert!(File::readlink(&tmpdir.join("bar")).unwrap() == tmpdir.join("bar"));
+
+        match io::result(|| File::readlink(&tmpdir)) {
+            Ok(*) => fail!("wanted a failure"),
+            Err(*) => {}
+        }
+
+        rmdir_recursive(&tmpdir);
+    }
+
+    #[test]
+    fn links_work() {
+        let tmpdir = tmpdir();
+        let input = tmpdir.join("in.txt");
+        let out = tmpdir.join("out.txt");
+
+        File::create(&input).write("foobar".as_bytes());
+        File::link(&input, &out);
+        assert_eq!(File::lstat(&out).kind, io::TypeFile);
+        assert_eq!(File::stat(&out).size, File::stat(&input).size);
+        assert_eq!(File::stat(&out).nlink, 2);
+        assert_eq!(File::open(&out).read_to_end(), (bytes!("foobar")).to_owned());
+
+        // can't link to yourself
+        match io::result(|| File::link(&input, &input)) {
+            Ok(*) => fail!("wanted a failure"),
+            Err(*) => {}
+        }
+        // can't link to something that doesn't exist
+        match io::result(|| File::link(&tmpdir.join("foo"), &tmpdir.join("bar"))) {
+            Ok(*) => fail!("wanted a failure"),
+            Err(*) => {}
+        }
+
+        rmdir_recursive(&tmpdir);
+    }
+
+    #[test]
+    fn chmod_works() {
+        let tmpdir = tmpdir();
+        let file = tmpdir.join("in.txt");
+
+        File::create(&file);
+        File::chmod(&file, io::UserRWX);
+        assert_eq!(File::stat(&file).perm, io::UserRWX);
+
+        match io::result(|| File::chmod(&tmpdir.join("foo"), io::UserRWX)) {
+            Ok(*) => fail!("wanted a failure"),
+            Err(*) => {}
+        }
+
+        rmdir_recursive(&tmpdir);
+    }
+
+    #[test]
+    fn sync_doesnt_kill_anything() {
+        let tmpdir = tmpdir();
+        let path = tmpdir.join("in.txt");
+
+        let mut file = File::open_mode(&path, io::Open, io::ReadWrite).unwrap();
+        file.fsync();
+        file.datasync();
+        file.write(bytes!("foo"));
+        file.fsync();
+        file.datasync();
+
+        rmdir_recursive(&tmpdir);
+    }
+
+    #[test]
+    fn truncate_works() {
+        let tmpdir = tmpdir();
+        let path = tmpdir.join("in.txt");
+
+        let mut file = File::open_mode(&path, io::Open, io::ReadWrite).unwrap();
+        file.write(bytes!("foo"));
+
+        // Do some simple things with truncation
+        assert_eq!(File::stat(&path).size, 3);
+        file.truncate(10);
+        assert_eq!(File::stat(&path).size, 10);
+        file.write(bytes!("bar"));
+        assert_eq!(File::stat(&path).size, 10);
+        assert_eq!(File::open(&path).read_to_end(),
+                   (bytes!("foobar", 0, 0, 0, 0)).to_owned());
+
+        // Truncate to a smaller length, don't seek, and then write something.
+        // Ensure that the intermediate zeroes are all filled in (we're seeked
+        // past the end of the file).
+        file.truncate(2);
+        assert_eq!(File::stat(&path).size, 2);
+        file.write(bytes!("wut"));
+        assert_eq!(File::stat(&path).size, 9);
+        assert_eq!(File::open(&path).read_to_end(),
+                   (bytes!("fo", 0, 0, 0, 0, "wut")).to_owned());
+
+        rmdir_recursive(&tmpdir);
+    }
+
+    #[test]
+    fn open_flavors() {
+        let tmpdir = tmpdir();
+
+        match io::result(|| File::open_mode(&tmpdir.join("a"), io::Open,
+                                            io::Read)) {
+            Ok(*) => fail!(), Err(*) => {}
+        }
+        File::open_mode(&tmpdir.join("b"), io::Open, io::Write).unwrap();
+        File::open_mode(&tmpdir.join("c"), io::Open, io::ReadWrite).unwrap();
+        File::open_mode(&tmpdir.join("d"), io::Append, io::Write).unwrap();
+        File::open_mode(&tmpdir.join("e"), io::Append, io::ReadWrite).unwrap();
+        File::open_mode(&tmpdir.join("f"), io::Truncate, io::Write).unwrap();
+        File::open_mode(&tmpdir.join("g"), io::Truncate, io::ReadWrite).unwrap();
+
+        File::create(&tmpdir.join("h")).write("foo".as_bytes());
+        File::open_mode(&tmpdir.join("h"), io::Open, io::Read).unwrap();
+        {
+            let mut f = File::open_mode(&tmpdir.join("h"), io::Open,
+                                        io::Read).unwrap();
+            match io::result(|| f.write("wut".as_bytes())) {
+                Ok(*) => fail!(), Err(*) => {}
+            }
+        }
+        assert_eq!(File::stat(&tmpdir.join("h")).size, 3);
+        {
+            let mut f = File::open_mode(&tmpdir.join("h"), io::Append,
+                                        io::Write).unwrap();
+            f.write("bar".as_bytes());
+        }
+        assert_eq!(File::stat(&tmpdir.join("h")).size, 6);
+        {
+            let mut f = File::open_mode(&tmpdir.join("h"), io::Truncate,
+                                        io::Write).unwrap();
+            f.write("bar".as_bytes());
+        }
+        assert_eq!(File::stat(&tmpdir.join("h")).size, 3);
+
         rmdir_recursive(&tmpdir);
     }
 }
diff --git a/src/libstd/rt/io/mod.rs b/src/libstd/rt/io/mod.rs
index 51204ca73b9..510c3470d06 100644
--- a/src/libstd/rt/io/mod.rs
+++ b/src/libstd/rt/io/mod.rs
@@ -231,8 +231,6 @@ Out of scope
 * Trait for things that are both readers and writers, Stream?
 * How to handle newline conversion
 * String conversion
-* File vs. FileStream? File is shorter but could also be used for getting file info
-  - maybe File is for general file querying and *also* has a static `open` method
 * open vs. connect for generic stream opening
 * Do we need `close` at all? dtors might be good enough
 * How does I/O relate to the Iterator trait?
@@ -244,7 +242,6 @@ Out of scope
 
 use cast;
 use int;
-use libc;
 use path::Path;
 use str::{StrSlice, OwnedStr};
 use option::{Option, Some, None};
@@ -262,7 +259,7 @@ pub use self::stdio::stderr;
 pub use self::stdio::print;
 pub use self::stdio::println;
 
-pub use self::file::FileStream;
+pub use self::file::File;
 pub use self::timer::Timer;
 pub use self::net::ip::IpAddr;
 pub use self::net::tcp::TcpListener;
@@ -465,7 +462,7 @@ pub trait Reader {
     ///
     /// # Example
     ///
-    ///     let reader = FileStream::new()
+    ///     let reader = File::open(&Path::new("foo.txt"))
     ///     while !reader.eof() {
     ///         println(reader.read_line());
     ///     }
@@ -1104,55 +1101,103 @@ pub fn placeholder_error() -> IoError {
     }
 }
 
-/// Instructions on how to open a file and return a `FileStream`.
+/// A mode specifies how a file should be opened or created. These modes are
+/// passed to `File::open_mode` and are used to control where the file is
+/// positioned when it is initially opened.
 pub enum FileMode {
-    /// Opens an existing file. IoError if file does not exist.
+    /// Opens a file positioned at the beginning.
     Open,
-    /// Creates a file. IoError if file exists.
-    Create,
-    /// Opens an existing file or creates a new one.
-    OpenOrCreate,
-    /// Opens an existing file or creates a new one, positioned at EOF.
+    /// Opens a file positioned at EOF.
     Append,
-    /// Opens an existing file, truncating it to 0 bytes.
+    /// Opens a file, truncating it if it already exists.
     Truncate,
-    /// Opens an existing file or creates a new one, truncating it to 0 bytes.
-    CreateOrTruncate,
 }
 
-/// Access permissions with which the file should be opened.
-/// `FileStream`s opened with `Read` will raise an `io_error` condition if written to.
+/// Access permissions with which the file should be opened. `File`s
+/// opened with `Read` will raise an `io_error` condition if written to.
 pub enum FileAccess {
     Read,
     Write,
-    ReadWrite
+    ReadWrite,
+}
+
+/// Different kinds of files which can be identified by a call to stat
+#[deriving(Eq)]
+pub enum FileType {
+    TypeFile,
+    TypeDirectory,
+    TypeNamedPipe,
+    TypeBlockSpecial,
+    TypeSymlink,
+    TypeUnknown,
 }
 
 pub struct FileStat {
-    /// A `Path` object containing information about the `PathInfo`'s location
+    /// The path that this stat structure is describing
     path: Path,
-    /// `true` if the file pointed at by the `PathInfo` is a regular file
-    is_file: bool,
-    /// `true` if the file pointed at by the `PathInfo` is a directory
-    is_dir: bool,
-    /// The file pointed at by the `PathInfo`'s device
-    device: u64,
-    /// The file pointed at by the `PathInfo`'s mode
-    mode: u64,
-    /// The file pointed at by the `PathInfo`'s inode
-    inode: u64,
-    /// The file pointed at by the `PathInfo`'s size in bytes
+    /// The size of the file, in bytes
     size: u64,
-    /// The file pointed at by the `PathInfo`'s creation time
+    /// The kind of file this path points to (directory, file, pipe, etc.)
+    kind: FileType,
+    /// The file permissions currently on the file
+    perm: FilePermission,
+
+    // XXX: These time fields are pretty useless without an actual time
+    //      representation, what are the milliseconds relative to?
+
+    /// The time that the file was created at, in platform-dependent
+    /// milliseconds
     created: u64,
-    /// The file pointed at by the `PathInfo`'s last-modification time in
-    /// platform-dependent msecs
+    /// The time that this file was last modified, in platform-dependent
+    /// milliseconds
     modified: u64,
-    /// The file pointed at by the `PathInfo`'s last-accessd time (e.g. read) in
-    /// platform-dependent msecs
+    /// The time that this file was last accessed, in platform-dependent
+    /// milliseconds
     accessed: u64,
+
+    // Various filesytem info
+    device: u64,
+    inode: u64,
+    rdev: u64,
+    nlink: u64,
+    uid: u64,
+    gid: u64,
+    blksize: u64,
+    blocks: u64,
+    flags: u64,
+    gen: u64,
 }
 
-// FIXME(#10131): this needs to get designed for real
+/// A set of permissions for a file or directory is represented by a set of
+/// flags which are or'd together.
 pub type FilePermission = u32;
-pub static UserRWX: FilePermission = libc::S_IRWXU as FilePermission;
+
+// Each permission bit
+pub static UserRead: FilePermission     = 0x100;
+pub static UserWrite: FilePermission    = 0x080;
+pub static UserExecute: FilePermission  = 0x040;
+pub static GroupRead: FilePermission    = 0x020;
+pub static GroupWrite: FilePermission   = 0x010;
+pub static GroupExecute: FilePermission = 0x008;
+pub static OtherRead: FilePermission    = 0x004;
+pub static OtherWrite: FilePermission   = 0x002;
+pub static OtherExecute: FilePermission = 0x001;
+
+// Common combinations of these bits
+pub static UserRWX: FilePermission  = UserRead | UserWrite | UserExecute;
+pub static GroupRWX: FilePermission = GroupRead | GroupWrite | GroupExecute;
+pub static OtherRWX: FilePermission = OtherRead | OtherWrite | OtherExecute;
+
+/// A set of permissions for user owned files, this is equivalent to 0644 on
+/// unix-like systems.
+pub static UserFile: FilePermission = UserRead | UserWrite | GroupRead | OtherRead;
+/// A set of permissions for user owned directories, this is equivalent to 0755
+/// on unix-like systems.
+pub static UserDir: FilePermission = UserRWX | GroupRead | GroupExecute |
+                                     OtherRead | OtherExecute;
+/// A set of permissions for user owned executables, this is equivalent to 0755
+/// on unix-like systems.
+pub static UserExec: FilePermission = UserDir;
+
+/// A mask for all possible permission bits
+pub static AllPermissions: FilePermission = 0x1ff;
diff --git a/src/libstd/rt/io/native/file.rs b/src/libstd/rt/io/native/file.rs
index e26cc166c8d..4eb473a73a6 100644
--- a/src/libstd/rt/io/native/file.rs
+++ b/src/libstd/rt/io/native/file.rs
@@ -309,14 +309,16 @@ mod tests {
 //      get bitrotted instantaneously.
 mod old_os {
     use prelude::*;
-    use c_str::CString;
-    use libc::fclose;
     use libc::{size_t, c_void, c_int};
     use libc;
     use vec;
 
-    #[cfg(test)] use os;
+    #[cfg(not(windows))] use c_str::CString;
+    #[cfg(not(windows))] use libc::fclose;
+    #[cfg(test)] #[cfg(windows)] use os;
     #[cfg(test)] use rand;
+    #[cfg(windows)] use str;
+    #[cfg(windows)] use ptr;
 
     // On Windows, wide character version of function must be used to support
     // unicode, so functions should be split into at least two versions,
@@ -651,7 +653,7 @@ mod old_os {
                     return false;
                 }
                 // Preserve permissions
-                let from_mode = from.stat().mode;
+                let from_mode = from.stat().perm;
 
                 let ostream = do to.with_c_str |top| {
                     do "w+b".with_c_str |modebuf| {
@@ -735,8 +737,8 @@ mod old_os {
 
     #[test]
     fn test_path_is_dir() {
-        use rt::io::file::{open_stream, mkdir_recursive};
-        use rt::io::{OpenOrCreate, Read, UserRWX};
+        use rt::io::file::{mkdir_recursive};
+        use rt::io::{File, UserRWX};
 
         assert!((path_is_dir(&Path::new("."))));
         assert!((!path_is_dir(&Path::new("test/stdtest/fs.rs"))));
@@ -754,7 +756,7 @@ mod old_os {
         filepath.push("unicode-file-\uac00\u4e00\u30fc\u4f60\u597d.rs");
         debug!("path_is_dir filepath: {}", filepath.display());
 
-        open_stream(&filepath, OpenOrCreate, Read); // ignore return; touch only
+        File::create(&filepath); // ignore return; touch only
         assert!((!path_is_dir(&filepath)));
 
         assert!((!path_is_dir(&Path::new(
diff --git a/src/libstd/rt/io/option.rs b/src/libstd/rt/io/option.rs
index 234b46458b4..5938252571f 100644
--- a/src/libstd/rt/io/option.rs
+++ b/src/libstd/rt/io/option.rs
@@ -11,7 +11,7 @@
 //! Implementations of I/O traits for the Option type
 //!
 //! I/O constructors return option types to allow errors to be handled.
-//! These implementations allow e.g. `Option<FileStream>` to be used
+//! These implementations allow e.g. `Option<File>` to be used
 //! as a `Reader` without unwrapping the option first.
 
 use option::*;
diff --git a/src/libstd/rt/rtio.rs b/src/libstd/rt/rtio.rs
index a0db1f1df00..d24de7cbfee 100644
--- a/src/libstd/rt/rtio.rs
+++ b/src/libstd/rt/rtio.rs
@@ -91,12 +91,17 @@ pub fn with_local_io<T>(f: &fn(&mut IoFactory) -> Option<T>) -> Option<T> {
 }
 
 pub trait IoFactory {
+    // networking
     fn tcp_connect(&mut self, addr: SocketAddr) -> Result<~RtioTcpStream, IoError>;
     fn tcp_bind(&mut self, addr: SocketAddr) -> Result<~RtioTcpListener, IoError>;
     fn udp_bind(&mut self, addr: SocketAddr) -> Result<~RtioUdpSocket, IoError>;
+    fn unix_bind(&mut self, path: &CString) ->
+        Result<~RtioUnixListener, IoError>;
+    fn unix_connect(&mut self, path: &CString) -> Result<~RtioPipe, IoError>;
     fn get_host_addresses(&mut self, host: Option<&str>, servname: Option<&str>,
                           hint: Option<ai::Hint>) -> Result<~[ai::Info], IoError>;
-    fn timer_init(&mut self) -> Result<~RtioTimer, IoError>;
+
+    // filesystem operations
     fn fs_from_raw_fd(&mut self, fd: c_int, close: CloseBehavior) -> ~RtioFileStream;
     fn fs_open(&mut self, path: &CString, fm: FileMode, fa: FileAccess)
         -> Result<~RtioFileStream, IoError>;
@@ -110,13 +115,18 @@ pub trait IoFactory {
     fn fs_rename(&mut self, path: &CString, to: &CString) -> Result<(), IoError>;
     fn fs_readdir(&mut self, path: &CString, flags: c_int) ->
         Result<~[Path], IoError>;
+    fn fs_lstat(&mut self, path: &CString) -> Result<FileStat, IoError>;
+    fn fs_chown(&mut self, path: &CString, uid: int, gid: int) ->
+        Result<(), IoError>;
+    fn fs_readlink(&mut self, path: &CString) -> Result<Path, IoError>;
+    fn fs_symlink(&mut self, src: &CString, dst: &CString) -> Result<(), IoError>;
+    fn fs_link(&mut self, src: &CString, dst: &CString) -> Result<(), IoError>;
+
+    // misc
+    fn timer_init(&mut self) -> Result<~RtioTimer, IoError>;
     fn spawn(&mut self, config: ProcessConfig)
             -> Result<(~RtioProcess, ~[Option<~RtioPipe>]), IoError>;
-
     fn pipe_open(&mut self, fd: c_int) -> Result<~RtioPipe, IoError>;
-    fn unix_bind(&mut self, path: &CString) ->
-        Result<~RtioUnixListener, IoError>;
-    fn unix_connect(&mut self, path: &CString) -> Result<~RtioPipe, IoError>;
     fn tty_open(&mut self, fd: c_int, readable: bool)
             -> Result<~RtioTTY, IoError>;
     fn signal(&mut self, signal: Signum, channel: SharedChan<Signum>)
@@ -177,6 +187,9 @@ pub trait RtioFileStream {
     fn pwrite(&mut self, buf: &[u8], offset: u64) -> Result<(), IoError>;
     fn seek(&mut self, pos: i64, whence: SeekStyle) -> Result<u64, IoError>;
     fn tell(&self) -> Result<u64, IoError>;
+    fn fsync(&mut self) -> Result<(), IoError>;
+    fn datasync(&mut self) -> Result<(), IoError>;
+    fn truncate(&mut self, offset: i64) -> Result<(), IoError>;
 }
 
 pub trait RtioProcess {