]> git.lizzy.rs Git - rust.git/commitdiff
Improve error messages for io::fs
authorYehuda Katz <wycats@gmail.com>
Tue, 3 Jun 2014 19:33:18 +0000 (12:33 -0700)
committerAlex Crichton <alex@alexcrichton.com>
Fri, 13 Jun 2014 20:53:34 +0000 (13:53 -0700)
src/libstd/io/fs.rs
src/libstd/io/mod.rs

index c29c82ab2e9b443085be829f7fa7e378d081db9f..1ac6fdc5ab122800b9d4a565bf7f9442064752fa 100644 (file)
@@ -56,9 +56,9 @@
 use io::{IoResult, IoError, FileStat, SeekStyle, Seek, Writer, Reader};
 use io::{Read, Truncate, SeekCur, SeekSet, ReadWrite, SeekEnd, Append};
 use io;
+use io;
 use iter::Iterator;
 use kinds::Send;
-use libc;
 use option::{Some, None, Option};
 use owned::Box;
 use path::{Path, GenericPath};
@@ -138,7 +138,7 @@ pub fn open_mode(path: &Path,
             Write => rtio::Write,
             ReadWrite => rtio::ReadWrite,
         };
-        LocalIo::maybe_raise(|io| {
+        let err = LocalIo::maybe_raise(|io| {
             io.fs_open(&path.to_c_str(), mode, access).map(|fd| {
                 File {
                     path: path.clone(),
@@ -146,7 +146,11 @@ pub fn open_mode(path: &Path,
                     last_nread: -1
                 }
             })
-        }).map_err(IoError::from_rtio_error)
+        }).map_err(IoError::from_rtio_error);
+        err.update_err("couldn't open file", |e| {
+            format!("{}; path={}; mode={}; access={}", e, path.display(),
+                mode_string(mode), access_string(access))
+        })
     }
 
     /// Attempts to open a file in read-only mode. This function is equivalent to
@@ -185,6 +189,7 @@ pub fn open(path: &Path) -> IoResult<File> {
     /// ```
     pub fn create(path: &Path) -> IoResult<File> {
         File::open_mode(path, Truncate, Write)
+            .update_desc("couldn't create file")
     }
 
     /// Returns the original path which was used to open this file.
@@ -196,7 +201,9 @@ pub fn path<'a>(&'a self) -> &'a Path {
     /// device. This will flush any internal buffers necessary to perform this
     /// operation.
     pub fn fsync(&mut self) -> IoResult<()> {
-        self.fd.fsync().map_err(IoError::from_rtio_error)
+        let err = self.fd.fsync().map_err(IoError::from_rtio_error);
+        err.update_err("couldn't fsync file",
+                       |e| format!("{}; path={}", e, self.path.display()))
     }
 
     /// This function is similar to `fsync`, except that it may not synchronize
@@ -204,7 +211,9 @@ pub fn fsync(&mut self) -> IoResult<()> {
     /// must synchronize content, but don't need the metadata on disk. The goal
     /// of this method is to reduce disk operations.
     pub fn datasync(&mut self) -> IoResult<()> {
-        self.fd.datasync().map_err(IoError::from_rtio_error)
+        let err = self.fd.datasync().map_err(IoError::from_rtio_error);
+        err.update_err("couldn't datasync file",
+                       |e| format!("{}; path={}", e, self.path.display()))
     }
 
     /// Either truncates or extends the underlying file, updating the size of
@@ -216,7 +225,10 @@ pub fn datasync(&mut self) -> IoResult<()> {
     /// will be extended to `size` and have all of the intermediate data filled
     /// in with 0s.
     pub fn truncate(&mut self, size: i64) -> IoResult<()> {
-        self.fd.truncate(size).map_err(IoError::from_rtio_error)
+        let err = self.fd.truncate(size).map_err(IoError::from_rtio_error);
+        err.update_err("couldn't truncate file", |e| {
+            format!("{}; path={}; size={}", e, self.path.display(), size)
+        })
     }
 
     /// Tests whether this stream has reached EOF.
@@ -229,10 +241,12 @@ pub fn eof(&self) -> bool {
 
     /// Queries information about the underlying file.
     pub fn stat(&mut self) -> IoResult<FileStat> {
-        match self.fd.fstat() {
+        let err = match self.fd.fstat() {
             Ok(s) => Ok(from_rtio(s)),
             Err(e) => Err(IoError::from_rtio_error(e)),
-        }
+        };
+        err.update_err("couldn't fstat file",
+                       |e| format!("{}; path={}", e, self.path.display()))
     }
 }
 
@@ -258,9 +272,11 @@ pub fn stat(&mut self) -> IoResult<FileStat> {
 /// user lacks permissions to remove the file, or if some other filesystem-level
 /// error occurs.
 pub fn unlink(path: &Path) -> IoResult<()> {
-    LocalIo::maybe_raise(|io| {
+    let err = LocalIo::maybe_raise(|io| {
         io.fs_unlink(&path.to_c_str())
-    }).map_err(IoError::from_rtio_error)
+    }).map_err(IoError::from_rtio_error);
+    err.update_err("couldn't unlink path",
+                   |e| format!("{}; path={}", e, path.display()))
 }
 
 /// Given a path, query the file system to get information about a file,
@@ -285,10 +301,12 @@ pub fn unlink(path: &Path) -> IoResult<()> {
 /// 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) -> IoResult<FileStat> {
-    match LocalIo::maybe_raise(|io| io.fs_stat(&path.to_c_str())) {
+    let err = match LocalIo::maybe_raise(|io| io.fs_stat(&path.to_c_str())) {
         Ok(s) => Ok(from_rtio(s)),
         Err(e) => Err(IoError::from_rtio_error(e)),
-    }
+    };
+    err.update_err("couldn't stat path",
+                   |e| format!("{}; path={}", e, path.display()))
 }
 
 /// Perform the same operation as the `stat` function, except that this
@@ -300,10 +318,12 @@ pub fn stat(path: &Path) -> IoResult<FileStat> {
 ///
 /// See `stat`
 pub fn lstat(path: &Path) -> IoResult<FileStat> {
-    match LocalIo::maybe_raise(|io| io.fs_lstat(&path.to_c_str())) {
+    let err = match LocalIo::maybe_raise(|io| io.fs_lstat(&path.to_c_str())) {
         Ok(s) => Ok(from_rtio(s)),
         Err(e) => Err(IoError::from_rtio_error(e)),
-    }
+    };
+    err.update_err("couldn't lstat path",
+                   |e| format!("{}; path={}", e, path.display()))
 }
 
 fn from_rtio(s: rtio::FileStat) -> FileStat {
@@ -359,9 +379,12 @@ fn from_rtio(s: rtio::FileStat) -> FileStat {
 /// permissions to view the contents, or if some other intermittent I/O error
 /// occurs.
 pub fn rename(from: &Path, to: &Path) -> IoResult<()> {
-    LocalIo::maybe_raise(|io| {
+    let err = LocalIo::maybe_raise(|io| {
         io.fs_rename(&from.to_c_str(), &to.to_c_str())
-    }).map_err(IoError::from_rtio_error)
+    }).map_err(IoError::from_rtio_error);
+    err.update_err("couldn't rename path", |e| {
+        format!("{}; from={}; to={}", e, from.display(), to.display())
+    })
 }
 
 /// Copies the contents of one file to another. This function will also
@@ -393,12 +416,17 @@ pub fn rename(from: &Path, to: &Path) -> IoResult<()> {
 /// ensured to not exist, there is nothing preventing the destination from
 /// being created and then destroyed by this operation.
 pub fn copy(from: &Path, to: &Path) -> IoResult<()> {
+    fn update_err<T>(result: IoResult<T>, from: &Path, to: &Path) -> IoResult<T> {
+        result.update_err("couldn't copy path",
+            |e| format!("{}; from={}; to={}", e, from.display(), to.display()))
+    }
+
     if !from.is_file() {
-        return Err(IoError {
+        return update_err(Err(IoError {
             kind: io::MismatchedFileTypeForOperation,
             desc: "the source path is not an existing file",
-            detail: None,
-        })
+            detail: None
+        }), from, to)
     }
 
     let mut reader = try!(File::open(from));
@@ -409,12 +437,12 @@ pub fn copy(from: &Path, to: &Path) -> IoResult<()> {
         let amt = match reader.read(buf) {
             Ok(n) => n,
             Err(ref e) if e.kind == io::EndOfFile => { break }
-            Err(e) => return Err(e)
+            Err(e) => return update_err(Err(e), from, to)
         };
         try!(writer.write(buf.slice_to(amt)));
     }
 
-    chmod(to, try!(from.stat()).perm)
+    chmod(to, try!(update_err(from.stat(), from, to)).perm)
 }
 
 /// Changes the permission mode bits found on a file or a directory. This
@@ -439,33 +467,45 @@ pub fn copy(from: &Path, to: &Path) -> IoResult<()> {
 /// 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) -> IoResult<()> {
-    LocalIo::maybe_raise(|io| {
+    let err = LocalIo::maybe_raise(|io| {
         io.fs_chmod(&path.to_c_str(), mode.bits() as uint)
-    }).map_err(IoError::from_rtio_error)
+    }).map_err(IoError::from_rtio_error);
+    err.update_err("couldn't chmod path", |e| {
+        format!("{}; path={}; mode={}", e, path.display(), mode)
+    })
 }
 
 /// Change the user and group owners of a file at the specified path.
 pub fn chown(path: &Path, uid: int, gid: int) -> IoResult<()> {
-    LocalIo::maybe_raise(|io| {
+    let err = LocalIo::maybe_raise(|io| {
         io.fs_chown(&path.to_c_str(), uid, gid)
-    }).map_err(IoError::from_rtio_error)
+    }).map_err(IoError::from_rtio_error);
+    err.update_err("couldn't chown path", |e| {
+        format!("{}; path={}; uid={}; gid={}", e, path.display(), 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.
 pub fn link(src: &Path, dst: &Path) -> IoResult<()> {
-    LocalIo::maybe_raise(|io| {
+    let err = LocalIo::maybe_raise(|io| {
         io.fs_link(&src.to_c_str(), &dst.to_c_str())
-    }).map_err(IoError::from_rtio_error)
+    }).map_err(IoError::from_rtio_error);
+    err.update_err("couldn't link path", |e| {
+        format!("{}; src={}; dest={}", e, src.display(), dst.display())
+    })
 }
 
 /// Creates a new symbolic link on the filesystem. The `dst` path will be a
 /// symlink pointing to the `src` path.
 pub fn symlink(src: &Path, dst: &Path) -> IoResult<()> {
-    LocalIo::maybe_raise(|io| {
+    let err = LocalIo::maybe_raise(|io| {
         io.fs_symlink(&src.to_c_str(), &dst.to_c_str())
-    }).map_err(IoError::from_rtio_error)
+    }).map_err(IoError::from_rtio_error);
+    err.update_err("couldn't symlink path", |e| {
+        format!("{}; src={}; dest={}", e, src.display(), dst.display())
+    })
 }
 
 /// Reads a symlink, returning the file that the symlink points to.
@@ -475,9 +515,11 @@ pub fn symlink(src: &Path, dst: &Path) -> IoResult<()> {
 /// This function will return an error on failure. Failure conditions include
 /// reading a file that does not exist or reading a file which is not a symlink.
 pub fn readlink(path: &Path) -> IoResult<Path> {
-    LocalIo::maybe_raise(|io| {
+    let err = LocalIo::maybe_raise(|io| {
         Ok(Path::new(try!(io.fs_readlink(&path.to_c_str()))))
-    }).map_err(IoError::from_rtio_error)
+    }).map_err(IoError::from_rtio_error);
+    err.update_err("couldn't resolve symlink for path",
+                   |e| format!("{}; path={}", e, path.display()))
 }
 
 /// Create a new, empty directory at the provided path
@@ -498,9 +540,12 @@ pub fn readlink(path: &Path) -> IoResult<Path> {
 /// This call will return an error if the user lacks permissions to make a new
 /// directory at the provided path, or if the directory already exists.
 pub fn mkdir(path: &Path, mode: FilePermission) -> IoResult<()> {
-    LocalIo::maybe_raise(|io| {
+    let err = LocalIo::maybe_raise(|io| {
         io.fs_mkdir(&path.to_c_str(), mode.bits() as uint)
-    }).map_err(IoError::from_rtio_error)
+    }).map_err(IoError::from_rtio_error);
+    err.update_err("couldn't create directory", |e| {
+        format!("{}; path={}; mode={}", e, path.display(), mode)
+    })
 }
 
 /// Remove an existing, empty directory
@@ -520,9 +565,11 @@ pub fn mkdir(path: &Path, mode: FilePermission) -> IoResult<()> {
 /// This call will return an error if the user lacks permissions to remove the
 /// directory at the provided path, or if the directory isn't empty.
 pub fn rmdir(path: &Path) -> IoResult<()> {
-    LocalIo::maybe_raise(|io| {
+    let err = LocalIo::maybe_raise(|io| {
         io.fs_rmdir(&path.to_c_str())
-    }).map_err(IoError::from_rtio_error)
+    }).map_err(IoError::from_rtio_error);
+    err.update_err("couldn't remove directory",
+                   |e| format!("{}; path={}", e, path.display()))
 }
 
 /// Retrieve a vector containing all entries within a provided directory
@@ -557,11 +604,13 @@ pub fn rmdir(path: &Path) -> IoResult<()> {
 /// permissions to view the contents or if the `path` points at a non-directory
 /// file
 pub fn readdir(path: &Path) -> IoResult<Vec<Path>> {
-    LocalIo::maybe_raise(|io| {
+    let err = LocalIo::maybe_raise(|io| {
         Ok(try!(io.fs_readdir(&path.to_c_str(), 0)).move_iter().map(|a| {
             Path::new(a)
         }).collect())
-    }).map_err(IoError::from_rtio_error)
+    }).map_err(IoError::from_rtio_error);
+    err.update_err("couldn't read directory",
+                   |e| format!("{}; path={}", e, path.display()))
 }
 
 /// Returns an iterator which will recursively walk the directory structure
@@ -569,7 +618,11 @@ pub fn readdir(path: &Path) -> IoResult<Vec<Path>> {
 /// perform iteration in some top-down order.  The contents of unreadable
 /// subdirectories are ignored.
 pub fn walk_dir(path: &Path) -> IoResult<Directories> {
-    Ok(Directories { stack: try!(readdir(path)) })
+    Ok(Directories {
+        stack: try!(readdir(path).update_err("couldn't walk directory",
+                                             |e| format!("{}; path={}",
+                                                         e, path.display())))
+    })
 }
 
 /// An iterator which walks over a directory
@@ -582,7 +635,12 @@ fn next(&mut self) -> Option<Path> {
         match self.stack.pop() {
             Some(path) => {
                 if path.is_dir() {
-                    match readdir(&path) {
+                    let result = readdir(&path)
+                        .update_err("couldn't advance Directories iterator",
+                                    |e| format!("{}; path={}",
+                                                e, path.display()));
+
+                    match result {
                         Ok(dirs) => { self.stack.push_all_move(dirs); }
                         Err(..) => {}
                     }
@@ -614,7 +672,11 @@ pub fn mkdir_recursive(path: &Path, mode: FilePermission) -> IoResult<()> {
     for c in comps {
         curpath.push(c);
 
-        match mkdir(&curpath, mode) {
+        let result = mkdir(&curpath, mode)
+            .update_err("couldn't recursively mkdir",
+                        |e| format!("{}; path={}", e, path.display()));
+
+        match result {
             Err(mkdir_err) => {
                 // already exists ?
                 if try!(stat(&curpath)).kind != io::TypeDirectory {
@@ -639,8 +701,20 @@ pub fn rmdir_recursive(path: &Path) -> IoResult<()> {
     let mut rm_stack = Vec::new();
     rm_stack.push(path.clone());
 
+    fn rmdir_failed(err: &IoError, path: &Path) -> String {
+        format!("rmdir_recursive failed; path={}; cause={}",
+                path.display(), err)
+    }
+
+    fn update_err<T>(err: IoResult<T>, path: &Path) -> IoResult<T> {
+        err.update_err("couldn't recursively rmdir",
+                       |e| rmdir_failed(e, path))
+    }
+
     while !rm_stack.is_empty() {
-        let children = try!(readdir(rm_stack.last().unwrap()));
+        let children = try!(readdir(rm_stack.last().unwrap())
+            .update_detail(|e| rmdir_failed(e, path)));
+
         let mut has_child_dir = false;
 
         // delete all regular files in the way and push subdirs
@@ -648,17 +722,17 @@ pub fn rmdir_recursive(path: &Path) -> IoResult<()> {
         for child in children.move_iter() {
             // FIXME(#12795) we should use lstat in all cases
             let child_type = match cfg!(windows) {
-                true => try!(stat(&child)).kind,
-                false => try!(lstat(&child)).kind
+                true => try!(update_err(stat(&child), path)),
+                false => try!(update_err(lstat(&child), path))
             };
 
-            if child_type == io::TypeDirectory {
+            if child_type.kind == io::TypeDirectory {
                 rm_stack.push(child);
                 has_child_dir = true;
             } else {
                 // we can carry on safely if the file is already gone
                 // (eg: deleted by someone else since readdir)
-                match unlink(&child) {
+                match update_err(unlink(&child), path) {
                     Ok(()) => (),
                     Err(ref e) if e.kind == io::FileNotFound => (),
                     Err(e) => return Err(e)
@@ -668,7 +742,8 @@ pub fn rmdir_recursive(path: &Path) -> IoResult<()> {
 
         // if no subdir was found, let's pop and delete
         if !has_child_dir {
-            match rmdir(&rm_stack.pop().unwrap()) {
+            let result = update_err(rmdir(&rm_stack.pop().unwrap()), path);
+            match result {
                 Ok(()) => (),
                 Err(ref e) if e.kind == io::FileNotFound => (),
                 Err(e) => return Err(e)
@@ -685,18 +760,28 @@ pub fn rmdir_recursive(path: &Path) -> IoResult<()> {
 /// be in milliseconds.
 // FIXME(#10301) these arguments should not be u64
 pub fn change_file_times(path: &Path, atime: u64, mtime: u64) -> IoResult<()> {
-    LocalIo::maybe_raise(|io| {
+    let err = LocalIo::maybe_raise(|io| {
         io.fs_utime(&path.to_c_str(), atime, mtime)
-    }).map_err(IoError::from_rtio_error)
+    }).map_err(IoError::from_rtio_error);
+    err.update_err("couldn't change_file_times",
+                   |e| format!("{}; path={}", e, path.display()))
 }
 
 impl Reader for File {
     fn read(&mut self, buf: &mut [u8]) -> IoResult<uint> {
-        match self.fd.read(buf) {
+        fn update_err<T>(result: IoResult<T>, file: &File) -> IoResult<T> {
+            result.update_err("couldn't read file",
+                              |e| format!("{}; path={}",
+                                          e, file.path.display()))
+        }
+
+        let result: IoResult<int> = update_err(self.fd.read(buf), self);
+
+        match result {
             Ok(read) => {
                 self.last_nread = read;
                 match read {
-                    0 => Err(io::standard_error(io::EndOfFile)),
+                    0 => update_err(Err(standard_error(io::EndOfFile)), self),
                     _ => Ok(read as uint)
                 }
             },
@@ -707,13 +792,17 @@ fn read(&mut self, buf: &mut [u8]) -> IoResult<uint> {
 
 impl Writer for File {
     fn write(&mut self, buf: &[u8]) -> IoResult<()> {
-        self.fd.write(buf).map_err(IoError::from_rtio_error)
+        let err = self.fd.write(buf).map_err(IoError::from_rtio_error)
+        err.update_err("couldn't write to file",
+                       |e| format!("{}; path={}", e, self.path.display()))
     }
 }
 
 impl Seek for File {
     fn tell(&self) -> IoResult<u64> {
-        self.fd.tell().map_err(IoError::from_rtio_error)
+        let err = self.fd.tell().map_err(IoError::from_rtio_error);
+        err.update_err("couldn't retrieve file cursor (`tell`)",
+                       |e| format!("{}; path={}", e, self.path.display()))
     }
 
     fn seek(&mut self, pos: i64, style: SeekStyle) -> IoResult<()> {
@@ -722,14 +811,16 @@ fn seek(&mut self, pos: i64, style: SeekStyle) -> IoResult<()> {
             SeekCur => rtio::SeekCur,
             SeekEnd => rtio::SeekEnd,
         };
-        match self.fd.seek(pos, style) {
+        let err = match self.fd.seek(pos, style) {
             Ok(_) => {
                 // successful seek resets EOF indicator
                 self.last_nread = -1;
                 Ok(())
             }
             Err(e) => Err(IoError::from_rtio_error(e)),
-        }
+        };
+        err.update_err("couldn't seek in file",
+                       |e| format!("{}; path={}", e, self.path.display()))
     }
 }
 
@@ -779,6 +870,22 @@ pub fn is_dir(&self) -> bool {
     }
 }
 
+fn mode_string(mode: FileMode) -> &'static str {
+    match mode {
+        super::Open => "open",
+        super::Append => "append",
+        super::Truncate => "truncate"
+    }
+}
+
+fn access_string(access: FileAccess) -> &'static str {
+    match access {
+        super::Read => "read",
+        super::Write => "write",
+        super::ReadWrite => "readwrite"
+    }
+}
+
 #[cfg(test)]
 #[allow(unused_imports)]
 mod test {
@@ -801,6 +908,14 @@ macro_rules! check( ($e:expr) => (
         }
     ) )
 
+    macro_rules! error( ($e:expr, $s:expr) => (
+        match $e {
+            Ok(val) => fail!("Should have been an error, was {:?}", val),
+            Err(ref err) => assert!(err.to_str().as_slice().contains($s.as_slice()),
+                                    format!("`{}` did not contain `{}`", err, $s))
+        }
+    ) )
+
     struct TempDir(Path);
 
     impl TempDir {
@@ -856,13 +971,21 @@ pub fn tmpdir() -> TempDir {
         let tmpdir = tmpdir();
         let filename = &tmpdir.join("file_that_does_not_exist.txt");
         let result = File::open_mode(filename, Open, Read);
-        assert!(result.is_err());
+
+        error!(result, "couldn't open file");
+        error!(result, "no such file or directory");
+        error!(result, format!("path={}; mode=open; access=read", filename.display()));
     })
 
     iotest!(fn file_test_iounlinking_invalid_path_should_raise_condition() {
         let tmpdir = tmpdir();
         let filename = &tmpdir.join("file_another_file_that_does_not_exist.txt");
-        assert!(unlink(filename).is_err());
+
+        let result = unlink(filename);
+
+        error!(result, "couldn't unlink path");
+        error!(result, "no such file or directory");
+        error!(result, format!("path={}", filename.display()));
     })
 
     iotest!(fn file_test_io_non_positional_read() {
@@ -1091,6 +1214,22 @@ pub fn tmpdir() -> TempDir {
         assert!(dir.is_dir())
     })
 
+    iotest!(fn recursive_mkdir_failure() {
+        let tmpdir = tmpdir();
+        let dir = tmpdir.join("d1");
+        let file = dir.join("f1");
+
+        check!(mkdir_recursive(&dir, io::UserRWX));
+        check!(File::create(&file));
+
+        let result = mkdir_recursive(&file, io::UserRWX);
+
+        error!(result, "couldn't recursively mkdir");
+        error!(result, "couldn't create directory");
+        error!(result, "mode=FilePermission { bits: 448 }");
+        error!(result, format!("path={}", file.display()));
+    })
+
     iotest!(fn recursive_mkdir_slash() {
         check!(mkdir_recursive(&Path::new("/"), io::UserRWX));
     })
@@ -1147,6 +1286,12 @@ pub fn tmpdir() -> TempDir {
     iotest!(fn copy_file_does_not_exist() {
         let from = Path::new("test/nonexistent-bogus-path");
         let to = Path::new("test/other-bogus-path");
+
+        error!(copy(&from, &to),
+            format!("couldn't copy path (the source path is not an \
+                    existing file; from={}; to={})",
+                    from.display(), to.display()));
+
         match copy(&from, &to) {
             Ok(..) => fail!(),
             Err(..) => {
index a1e0fa889789f39b3b59a1f3a50c6cdd781d1641..a7f84899a622e1703858bab178a2fe3059a6bdbc 100644 (file)
@@ -232,7 +232,7 @@ fn file_product(p: &Path) -> IoResult<u32> {
 use result::{Ok, Err, Result};
 use rt::rtio;
 use slice::{Vector, MutableVector, ImmutableVector};
-use str::{StrSlice, StrAllocating};
+use str::{Str, StrSlice, StrAllocating};
 use str;
 use string::String;
 use uint;
@@ -309,6 +309,7 @@ impl IoError {
     /// struct is filled with an allocated string describing the error
     /// in more detail, retrieved from the operating system.
     pub fn from_errno(errno: uint, detail: bool) -> IoError {
+
         #[cfg(windows)]
         fn get_err(errno: i32) -> (IoErrorKind, &'static str) {
             match errno {
@@ -388,8 +389,8 @@ fn get_err(errno: i32) -> (IoErrorKind, &'static str) {
         IoError {
             kind: kind,
             desc: desc,
-            detail: if detail {
-                Some(os::error_string(errno))
+            detail: if detail && kind == OtherIoError {
+                Some(os::error_string(errno).as_slice().chars().map(|c| c.to_lowercase()).collect())
             } else {
                 None
             },
@@ -420,10 +421,13 @@ fn from_rtio_error(err: rtio::IoError) -> IoError {
 
 impl fmt::Show for IoError {
     fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
-        try!(write!(fmt, "{}", self.desc));
-        match self.detail {
-            Some(ref s) => write!(fmt, " ({})", *s),
-            None => Ok(())
+        match *self {
+            IoError { kind: OtherIoError, desc: "unknown error", detail: Some(ref detail) } =>
+                write!(fmt, "{}", detail),
+            IoError { detail: None, desc, .. } =>
+                write!(fmt, "{}", desc),
+            IoError { detail: Some(ref detail), desc, .. } =>
+                write!(fmt, "{} ({})", desc, detail)
         }
     }
 }
@@ -484,6 +488,37 @@ pub enum IoErrorKind {
     NoProgress,
 }
 
+/// A trait that lets you add a `detail` to an IoError easily
+trait UpdateIoError<T> {
+    /// Returns an IoError with updated description and detail
+    fn update_err(self, desc: &'static str, detail: |&IoError| -> String) -> Self;
+
+    /// Returns an IoError with updated detail
+    fn update_detail(self, detail: |&IoError| -> String) -> Self;
+
+    /// Returns an IoError with update description
+    fn update_desc(self, desc: &'static str) -> Self;
+}
+
+impl<T> UpdateIoError<T> for IoResult<T> {
+    fn update_err(self, desc: &'static str, detail: |&IoError| -> String) -> IoResult<T> {
+        self.map_err(|mut e| {
+            let detail = detail(&e);
+            e.desc = desc;
+            e.detail = Some(detail);
+            e
+        })
+    }
+
+    fn update_detail(self, detail: |&IoError| -> String) -> IoResult<T> {
+        self.map_err(|mut e| { e.detail = Some(detail(&e)); e })
+    }
+
+    fn update_desc(self, desc: &'static str) -> IoResult<T> {
+        self.map_err(|mut e| { e.desc = desc; e })
+    }
+}
+
 static NO_PROGRESS_LIMIT: uint = 1000;
 
 /// A trait for objects which are byte-oriented streams. Readers are defined by
@@ -1577,7 +1612,7 @@ pub fn standard_error(kind: IoErrorKind) -> IoError {
         ConnectionAborted => "connection aborted",
         NotConnected => "not connected",
         BrokenPipe => "broken pipe",
-        PathAlreadyExists => "file exists",
+        PathAlreadyExists => "file already exists",
         PathDoesntExist => "no such file",
         MismatchedFileTypeForOperation => "mismatched file type",
         ResourceUnavailable => "resource unavailable",