]> git.lizzy.rs Git - rust.git/blob - src/libstd/io/fs.rs
ed183cbf3bc2191dac321c7a6f582144080f4d9c
[rust.git] / src / libstd / io / fs.rs
1 // Copyright 2013-2014 The Rust Project Developers. See the COPYRIGHT
2 // file at the top-level directory of this distribution and at
3 // http://rust-lang.org/COPYRIGHT.
4 //
5 // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6 // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7 // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8 // option. This file may not be copied, modified, or distributed
9 // except according to those terms.
10
11 /*! Synchronous File I/O
12
13 This module provides a set of functions and traits for working
14 with regular files & directories on a filesystem.
15
16 At the top-level of the module are a set of freestanding functions, associated
17 with various filesystem operations. They all operate on `Path` objects.
18
19 All operations in this module, including those as part of `File` et al
20 block the task during execution. In the event of failure, all functions/methods
21 will return an `IoResult` type with an `Err` value.
22
23 Also included in this module is an implementation block on the `Path` object
24 defined in `std::path::Path`. The impl adds useful methods about inspecting the
25 metadata of a file. This includes getting the `stat` information, reading off
26 particular bits of it, etc.
27
28 # Example
29
30 ```rust
31 # #![allow(unused_must_use)]
32 use std::io::{File, fs};
33
34 let path = Path::new("foo.txt");
35
36 // create the file, whether it exists or not
37 let mut file = File::create(&path);
38 file.write(b"foobar");
39 # drop(file);
40
41 // open the file in read-only mode
42 let mut file = File::open(&path);
43 file.read_to_end();
44
45 println!("{}", path.stat().unwrap().size);
46 # drop(file);
47 fs::unlink(&path);
48 ```
49
50 */
51
52 use c_str::ToCStr;
53 use clone::Clone;
54 use collections::Collection;
55 use io::standard_error;
56 use io::{FilePermission, Write, UnstableFileStat, Open, FileAccess, FileMode};
57 use io::{IoResult, IoError, FileStat, SeekStyle, Seek, Writer, Reader};
58 use io::{Read, Truncate, SeekCur, SeekSet, ReadWrite, SeekEnd, Append};
59 use io::UpdateIoError;
60 use io;
61 use iter::Iterator;
62 use kinds::Send;
63 use libc;
64 use option::{Some, None, Option};
65 use owned::Box;
66 use path::{Path, GenericPath};
67 use path;
68 use result::{Err, Ok};
69 use rt::rtio::LocalIo;
70 use rt::rtio;
71 use slice::ImmutableVector;
72 use string::String;
73 use vec::Vec;
74
75 /// Unconstrained file access type that exposes read and write operations
76 ///
77 /// Can be constructed via `File::open()`, `File::create()`, and
78 /// `File::open_mode()`.
79 ///
80 /// # Error
81 ///
82 /// This type will return errors as an `IoResult<T>` if operations are
83 /// attempted against it for which its underlying file descriptor was not
84 /// configured at creation time, via the `FileAccess` parameter to
85 /// `File::open_mode()`.
86 pub struct File {
87     fd: Box<rtio::RtioFileStream + Send>,
88     path: Path,
89     last_nread: int,
90 }
91
92 impl File {
93     /// Open a file at `path` in the mode specified by the `mode` and `access`
94     /// arguments
95     ///
96     /// # Example
97     ///
98     /// ```rust,should_fail
99     /// use std::io::{File, Open, ReadWrite};
100     ///
101     /// let p = Path::new("/some/file/path.txt");
102     ///
103     /// let file = match File::open_mode(&p, Open, ReadWrite) {
104     ///     Ok(f) => f,
105     ///     Err(e) => fail!("file error: {}", e),
106     /// };
107     /// // do some stuff with that file
108     ///
109     /// // the file will be closed at the end of this block
110     /// ```
111     ///
112     /// `FileMode` and `FileAccess` provide information about the permissions
113     /// context in which a given stream is created. More information about them
114     /// can be found in `std::io`'s docs. If a file is opened with `Write`
115     /// or `ReadWrite` access, then it will be created it does not already
116     /// exist.
117     ///
118     /// Note that, with this function, a `File` is returned regardless of the
119     /// access-limitations indicated by `FileAccess` (e.g. calling `write` on a
120     /// `File` opened as `Read` will return an error at runtime).
121     ///
122     /// # Error
123     ///
124     /// This function will return an error under a number of different
125     /// circumstances, to include but not limited to:
126     ///
127     /// * Opening a file that does not exist with `Read` access.
128     /// * Attempting to open a file with a `FileAccess` that the user lacks
129     ///   permissions for
130     /// * Filesystem-level errors (full disk, etc)
131     pub fn open_mode(path: &Path,
132                      mode: FileMode,
133                      access: FileAccess) -> IoResult<File> {
134         let rtio_mode = match mode {
135             Open => rtio::Open,
136             Append => rtio::Append,
137             Truncate => rtio::Truncate,
138         };
139         let rtio_access = match access {
140             Read => rtio::Read,
141             Write => rtio::Write,
142             ReadWrite => rtio::ReadWrite,
143         };
144         let err = LocalIo::maybe_raise(|io| {
145             io.fs_open(&path.to_c_str(), rtio_mode, rtio_access).map(|fd| {
146                 File {
147                     path: path.clone(),
148                     fd: fd,
149                     last_nread: -1
150                 }
151             })
152         }).map_err(IoError::from_rtio_error);
153         err.update_err("couldn't open file", |e| {
154             format!("{}; path={}; mode={}; access={}", e, path.display(),
155                 mode_string(mode), access_string(access))
156         })
157     }
158
159     /// Attempts to open a file in read-only mode. This function is equivalent to
160     /// `File::open_mode(path, Open, Read)`, and will raise all of the same
161     /// errors that `File::open_mode` does.
162     ///
163     /// For more information, see the `File::open_mode` function.
164     ///
165     /// # Example
166     ///
167     /// ```rust
168     /// use std::io::File;
169     ///
170     /// let contents = File::open(&Path::new("foo.txt")).read_to_end();
171     /// ```
172     pub fn open(path: &Path) -> IoResult<File> {
173         File::open_mode(path, Open, Read)
174     }
175
176     /// Attempts to create a file in write-only mode. This function is
177     /// equivalent to `File::open_mode(path, Truncate, Write)`, and will
178     /// raise all of the same errors that `File::open_mode` does.
179     ///
180     /// For more information, see the `File::open_mode` function.
181     ///
182     /// # Example
183     ///
184     /// ```rust
185     /// # #![allow(unused_must_use)]
186     /// use std::io::File;
187     ///
188     /// let mut f = File::create(&Path::new("foo.txt"));
189     /// f.write(b"This is a sample file");
190     /// # drop(f);
191     /// # ::std::io::fs::unlink(&Path::new("foo.txt"));
192     /// ```
193     pub fn create(path: &Path) -> IoResult<File> {
194         File::open_mode(path, Truncate, Write)
195             .update_desc("couldn't create file")
196     }
197
198     /// Returns the original path which was used to open this file.
199     pub fn path<'a>(&'a self) -> &'a Path {
200         &self.path
201     }
202
203     /// Synchronizes all modifications to this file to its permanent storage
204     /// device. This will flush any internal buffers necessary to perform this
205     /// operation.
206     pub fn fsync(&mut self) -> IoResult<()> {
207         let err = self.fd.fsync().map_err(IoError::from_rtio_error);
208         err.update_err("couldn't fsync file",
209                        |e| format!("{}; path={}", e, self.path.display()))
210     }
211
212     /// This function is similar to `fsync`, except that it may not synchronize
213     /// file metadata to the filesystem. This is intended for use case which
214     /// must synchronize content, but don't need the metadata on disk. The goal
215     /// of this method is to reduce disk operations.
216     pub fn datasync(&mut self) -> IoResult<()> {
217         let err = self.fd.datasync().map_err(IoError::from_rtio_error);
218         err.update_err("couldn't datasync file",
219                        |e| format!("{}; path={}", e, self.path.display()))
220     }
221
222     /// Either truncates or extends the underlying file, updating the size of
223     /// this file to become `size`. This is equivalent to unix's `truncate`
224     /// function.
225     ///
226     /// If the `size` is less than the current file's size, then the file will
227     /// be shrunk. If it is greater than the current file's size, then the file
228     /// will be extended to `size` and have all of the intermediate data filled
229     /// in with 0s.
230     pub fn truncate(&mut self, size: i64) -> IoResult<()> {
231         let err = self.fd.truncate(size).map_err(IoError::from_rtio_error);
232         err.update_err("couldn't truncate file", |e| {
233             format!("{}; path={}; size={}", e, self.path.display(), size)
234         })
235     }
236
237     /// Tests whether this stream has reached EOF.
238     ///
239     /// If true, then this file will no longer continue to return data via
240     /// `read`.
241     pub fn eof(&self) -> bool {
242         self.last_nread == 0
243     }
244
245     /// Queries information about the underlying file.
246     pub fn stat(&mut self) -> IoResult<FileStat> {
247         let err = match self.fd.fstat() {
248             Ok(s) => Ok(from_rtio(s)),
249             Err(e) => Err(IoError::from_rtio_error(e)),
250         };
251         err.update_err("couldn't fstat file",
252                        |e| format!("{}; path={}", e, self.path.display()))
253     }
254 }
255
256 /// Unlink a file from the underlying filesystem.
257 ///
258 /// # Example
259 ///
260 /// ```rust
261 /// # #![allow(unused_must_use)]
262 /// use std::io::fs;
263 ///
264 /// let p = Path::new("/some/file/path.txt");
265 /// fs::unlink(&p);
266 /// ```
267 ///
268 /// Note that, just because an unlink call was successful, it is not
269 /// guaranteed that a file is immediately deleted (e.g. depending on
270 /// platform, other open file descriptors may prevent immediate removal)
271 ///
272 /// # Error
273 ///
274 /// This function will return an error if the path points to a directory, the
275 /// user lacks permissions to remove the file, or if some other filesystem-level
276 /// error occurs.
277 pub fn unlink(path: &Path) -> IoResult<()> {
278     let err = LocalIo::maybe_raise(|io| {
279         io.fs_unlink(&path.to_c_str())
280     }).map_err(IoError::from_rtio_error);
281     err.update_err("couldn't unlink path",
282                    |e| format!("{}; path={}", e, path.display()))
283 }
284
285 /// Given a path, query the file system to get information about a file,
286 /// directory, etc. This function will traverse symlinks to query
287 /// information about the destination file.
288 ///
289 /// # Example
290 ///
291 /// ```rust
292 /// use std::io::fs;
293 ///
294 /// let p = Path::new("/some/file/path.txt");
295 /// match fs::stat(&p) {
296 ///     Ok(stat) => { /* ... */ }
297 ///     Err(e) => { /* handle error */ }
298 /// }
299 /// ```
300 ///
301 /// # Error
302 ///
303 /// This call will return an error if the user lacks the requisite permissions
304 /// to perform a `stat` call on the given path or if there is no entry in the
305 /// filesystem at the provided path.
306 pub fn stat(path: &Path) -> IoResult<FileStat> {
307     let err = match LocalIo::maybe_raise(|io| io.fs_stat(&path.to_c_str())) {
308         Ok(s) => Ok(from_rtio(s)),
309         Err(e) => Err(IoError::from_rtio_error(e)),
310     };
311     err.update_err("couldn't stat path",
312                    |e| format!("{}; path={}", e, path.display()))
313 }
314
315 /// Perform the same operation as the `stat` function, except that this
316 /// function does not traverse through symlinks. This will return
317 /// information about the symlink file instead of the file that it points
318 /// to.
319 ///
320 /// # Error
321 ///
322 /// See `stat`
323 pub fn lstat(path: &Path) -> IoResult<FileStat> {
324     let err = match LocalIo::maybe_raise(|io| io.fs_lstat(&path.to_c_str())) {
325         Ok(s) => Ok(from_rtio(s)),
326         Err(e) => Err(IoError::from_rtio_error(e)),
327     };
328     err.update_err("couldn't lstat path",
329                    |e| format!("{}; path={}", e, path.display()))
330 }
331
332 fn from_rtio(s: rtio::FileStat) -> FileStat {
333     let rtio::FileStat {
334         size, kind, perm, created, modified,
335         accessed, device, inode, rdev,
336         nlink, uid, gid, blksize, blocks, flags, gen
337     } = s;
338
339     FileStat {
340         size: size,
341         kind: match (kind as libc::c_int) & libc::S_IFMT {
342             libc::S_IFREG => io::TypeFile,
343             libc::S_IFDIR => io::TypeDirectory,
344             libc::S_IFIFO => io::TypeNamedPipe,
345             libc::S_IFBLK => io::TypeBlockSpecial,
346             libc::S_IFLNK => io::TypeSymlink,
347             _ => io::TypeUnknown,
348         },
349         perm: FilePermission::from_bits_truncate(perm as u32),
350         created: created,
351         modified: modified,
352         accessed: accessed,
353         unstable: UnstableFileStat {
354             device: device,
355             inode: inode,
356             rdev: rdev,
357             nlink: nlink,
358             uid: uid,
359             gid: gid,
360             blksize: blksize,
361             blocks: blocks,
362             flags: flags,
363             gen: gen,
364         },
365     }
366 }
367
368 /// Rename a file or directory to a new name.
369 ///
370 /// # Example
371 ///
372 /// ```rust
373 /// # #![allow(unused_must_use)]
374 /// use std::io::fs;
375 ///
376 /// fs::rename(&Path::new("foo"), &Path::new("bar"));
377 /// ```
378 ///
379 /// # Error
380 ///
381 /// Will return an error if the provided `path` doesn't exist, the process lacks
382 /// permissions to view the contents, or if some other intermittent I/O error
383 /// occurs.
384 pub fn rename(from: &Path, to: &Path) -> IoResult<()> {
385     let err = LocalIo::maybe_raise(|io| {
386         io.fs_rename(&from.to_c_str(), &to.to_c_str())
387     }).map_err(IoError::from_rtio_error);
388     err.update_err("couldn't rename path", |e| {
389         format!("{}; from={}; to={}", e, from.display(), to.display())
390     })
391 }
392
393 /// Copies the contents of one file to another. This function will also
394 /// copy the permission bits of the original file to the destination file.
395 ///
396 /// Note that if `from` and `to` both point to the same file, then the file
397 /// will likely get truncated by this operation.
398 ///
399 /// # Example
400 ///
401 /// ```rust
402 /// # #![allow(unused_must_use)]
403 /// use std::io::fs;
404 ///
405 /// fs::copy(&Path::new("foo.txt"), &Path::new("bar.txt"));
406 /// ```
407 ///
408 /// # Error
409 ///
410 /// Will return an error in the following situations, but is not limited to
411 /// just these cases:
412 ///
413 /// * The `from` path is not a file
414 /// * The `from` file does not exist
415 /// * The current process does not have the permission rights to access
416 ///   `from` or write `to`
417 ///
418 /// Note that this copy is not atomic in that once the destination is
419 /// ensured to not exist, there is nothing preventing the destination from
420 /// being created and then destroyed by this operation.
421 pub fn copy(from: &Path, to: &Path) -> IoResult<()> {
422     fn update_err<T>(result: IoResult<T>, from: &Path, to: &Path) -> IoResult<T> {
423         result.update_err("couldn't copy path",
424             |e| format!("{}; from={}; to={}", e, from.display(), to.display()))
425     }
426
427     if !from.is_file() {
428         return update_err(Err(IoError {
429             kind: io::MismatchedFileTypeForOperation,
430             desc: "the source path is not an existing file",
431             detail: None
432         }), from, to)
433     }
434
435     let mut reader = try!(File::open(from));
436     let mut writer = try!(File::create(to));
437     let mut buf = [0, ..io::DEFAULT_BUF_SIZE];
438
439     loop {
440         let amt = match reader.read(buf) {
441             Ok(n) => n,
442             Err(ref e) if e.kind == io::EndOfFile => { break }
443             Err(e) => return update_err(Err(e), from, to)
444         };
445         try!(writer.write(buf.slice_to(amt)));
446     }
447
448     chmod(to, try!(update_err(from.stat(), from, to)).perm)
449 }
450
451 /// Changes the permission mode bits found on a file or a directory. This
452 /// function takes a mask from the `io` module
453 ///
454 /// # Example
455 ///
456 /// ```rust
457 /// # #![allow(unused_must_use)]
458 /// use std::io;
459 /// use std::io::fs;
460 ///
461 /// fs::chmod(&Path::new("file.txt"), io::UserFile);
462 /// fs::chmod(&Path::new("file.txt"), io::UserRead | io::UserWrite);
463 /// fs::chmod(&Path::new("dir"),      io::UserDir);
464 /// fs::chmod(&Path::new("file.exe"), io::UserExec);
465 /// ```
466 ///
467 /// # Error
468 ///
469 /// If this function encounters an I/O error, it will return an `Err` value.
470 /// Some possible error situations are not having the permission to
471 /// change the attributes of a file or the file not existing.
472 pub fn chmod(path: &Path, mode: io::FilePermission) -> IoResult<()> {
473     let err = LocalIo::maybe_raise(|io| {
474         io.fs_chmod(&path.to_c_str(), mode.bits() as uint)
475     }).map_err(IoError::from_rtio_error);
476     err.update_err("couldn't chmod path", |e| {
477         format!("{}; path={}; mode={}", e, path.display(), mode)
478     })
479 }
480
481 /// Change the user and group owners of a file at the specified path.
482 pub fn chown(path: &Path, uid: int, gid: int) -> IoResult<()> {
483     let err = LocalIo::maybe_raise(|io| {
484         io.fs_chown(&path.to_c_str(), uid, gid)
485     }).map_err(IoError::from_rtio_error);
486     err.update_err("couldn't chown path", |e| {
487         format!("{}; path={}; uid={}; gid={}", e, path.display(), uid, gid)
488     })
489 }
490
491 /// Creates a new hard link on the filesystem. The `dst` path will be a
492 /// link pointing to the `src` path. Note that systems often require these
493 /// two paths to both be located on the same filesystem.
494 pub fn link(src: &Path, dst: &Path) -> IoResult<()> {
495     let err = LocalIo::maybe_raise(|io| {
496         io.fs_link(&src.to_c_str(), &dst.to_c_str())
497     }).map_err(IoError::from_rtio_error);
498     err.update_err("couldn't link path", |e| {
499         format!("{}; src={}; dest={}", e, src.display(), dst.display())
500     })
501 }
502
503 /// Creates a new symbolic link on the filesystem. The `dst` path will be a
504 /// symlink pointing to the `src` path.
505 pub fn symlink(src: &Path, dst: &Path) -> IoResult<()> {
506     let err = LocalIo::maybe_raise(|io| {
507         io.fs_symlink(&src.to_c_str(), &dst.to_c_str())
508     }).map_err(IoError::from_rtio_error);
509     err.update_err("couldn't symlink path", |e| {
510         format!("{}; src={}; dest={}", e, src.display(), dst.display())
511     })
512 }
513
514 /// Reads a symlink, returning the file that the symlink points to.
515 ///
516 /// # Error
517 ///
518 /// This function will return an error on failure. Failure conditions include
519 /// reading a file that does not exist or reading a file which is not a symlink.
520 pub fn readlink(path: &Path) -> IoResult<Path> {
521     let err = LocalIo::maybe_raise(|io| {
522         Ok(Path::new(try!(io.fs_readlink(&path.to_c_str()))))
523     }).map_err(IoError::from_rtio_error);
524     err.update_err("couldn't resolve symlink for path",
525                    |e| format!("{}; path={}", e, path.display()))
526 }
527
528 /// Create a new, empty directory at the provided path
529 ///
530 /// # Example
531 ///
532 /// ```rust
533 /// # #![allow(unused_must_use)]
534 /// use std::io;
535 /// use std::io::fs;
536 ///
537 /// let p = Path::new("/some/dir");
538 /// fs::mkdir(&p, io::UserRWX);
539 /// ```
540 ///
541 /// # Error
542 ///
543 /// This call will return an error if the user lacks permissions to make a new
544 /// directory at the provided path, or if the directory already exists.
545 pub fn mkdir(path: &Path, mode: FilePermission) -> IoResult<()> {
546     let err = LocalIo::maybe_raise(|io| {
547         io.fs_mkdir(&path.to_c_str(), mode.bits() as uint)
548     }).map_err(IoError::from_rtio_error);
549     err.update_err("couldn't create directory", |e| {
550         format!("{}; path={}; mode={}", e, path.display(), mode)
551     })
552 }
553
554 /// Remove an existing, empty directory
555 ///
556 /// # Example
557 ///
558 /// ```rust
559 /// # #![allow(unused_must_use)]
560 /// use std::io::fs;
561 ///
562 /// let p = Path::new("/some/dir");
563 /// fs::rmdir(&p);
564 /// ```
565 ///
566 /// # Error
567 ///
568 /// This call will return an error if the user lacks permissions to remove the
569 /// directory at the provided path, or if the directory isn't empty.
570 pub fn rmdir(path: &Path) -> IoResult<()> {
571     let err = LocalIo::maybe_raise(|io| {
572         io.fs_rmdir(&path.to_c_str())
573     }).map_err(IoError::from_rtio_error);
574     err.update_err("couldn't remove directory",
575                    |e| format!("{}; path={}", e, path.display()))
576 }
577
578 /// Retrieve a vector containing all entries within a provided directory
579 ///
580 /// # Example
581 ///
582 /// ```rust
583 /// use std::io;
584 /// use std::io::fs;
585 ///
586 /// // one possible implementation of fs::walk_dir only visiting files
587 /// fn visit_dirs(dir: &Path, cb: |&Path|) -> io::IoResult<()> {
588 ///     if dir.is_dir() {
589 ///         let contents = try!(fs::readdir(dir));
590 ///         for entry in contents.iter() {
591 ///             if entry.is_dir() {
592 ///                 try!(visit_dirs(entry, |p| cb(p)));
593 ///             } else {
594 ///                 cb(entry);
595 ///             }
596 ///         }
597 ///         Ok(())
598 ///     } else {
599 ///         Err(io::standard_error(io::InvalidInput))
600 ///     }
601 /// }
602 /// ```
603 ///
604 /// # Error
605 ///
606 /// Will return an error if the provided `from` doesn't exist, the process lacks
607 /// permissions to view the contents or if the `path` points at a non-directory
608 /// file
609 pub fn readdir(path: &Path) -> IoResult<Vec<Path>> {
610     let err = LocalIo::maybe_raise(|io| {
611         Ok(try!(io.fs_readdir(&path.to_c_str(), 0)).move_iter().map(|a| {
612             Path::new(a)
613         }).collect())
614     }).map_err(IoError::from_rtio_error);
615     err.update_err("couldn't read directory",
616                    |e| format!("{}; path={}", e, path.display()))
617 }
618
619 /// Returns an iterator which will recursively walk the directory structure
620 /// rooted at `path`. The path given will not be iterated over, and this will
621 /// perform iteration in some top-down order.  The contents of unreadable
622 /// subdirectories are ignored.
623 pub fn walk_dir(path: &Path) -> IoResult<Directories> {
624     Ok(Directories {
625         stack: try!(readdir(path).update_err("couldn't walk directory",
626                                              |e| format!("{}; path={}",
627                                                          e, path.display())))
628     })
629 }
630
631 /// An iterator which walks over a directory
632 pub struct Directories {
633     stack: Vec<Path>,
634 }
635
636 impl Iterator<Path> for Directories {
637     fn next(&mut self) -> Option<Path> {
638         match self.stack.pop() {
639             Some(path) => {
640                 if path.is_dir() {
641                     let result = readdir(&path)
642                         .update_err("couldn't advance Directories iterator",
643                                     |e| format!("{}; path={}",
644                                                 e, path.display()));
645
646                     match result {
647                         Ok(dirs) => { self.stack.push_all_move(dirs); }
648                         Err(..) => {}
649                     }
650                 }
651                 Some(path)
652             }
653             None => None
654         }
655     }
656 }
657
658 /// Recursively create a directory and all of its parent components if they
659 /// are missing.
660 ///
661 /// # Error
662 ///
663 /// This function will return an `Err` value if an error happens, see
664 /// `fs::mkdir` for more information about error conditions and performance.
665 pub fn mkdir_recursive(path: &Path, mode: FilePermission) -> IoResult<()> {
666     // tjc: if directory exists but with different permissions,
667     // should we return false?
668     if path.is_dir() {
669         return Ok(())
670     }
671
672     let mut comps = path.components();
673     let mut curpath = path.root_path().unwrap_or(Path::new("."));
674
675     for c in comps {
676         curpath.push(c);
677
678         let result = mkdir(&curpath, mode)
679             .update_err("couldn't recursively mkdir",
680                         |e| format!("{}; path={}", e, path.display()));
681
682         match result {
683             Err(mkdir_err) => {
684                 // already exists ?
685                 if try!(stat(&curpath)).kind != io::TypeDirectory {
686                     return Err(mkdir_err);
687                 }
688             }
689             Ok(()) => ()
690         }
691     }
692
693     Ok(())
694 }
695
696 /// Removes a directory at this path, after removing all its contents. Use
697 /// carefully!
698 ///
699 /// # Error
700 ///
701 /// This function will return an `Err` value if an error happens. See
702 /// `file::unlink` and `fs::readdir` for possible error conditions.
703 pub fn rmdir_recursive(path: &Path) -> IoResult<()> {
704     let mut rm_stack = Vec::new();
705     rm_stack.push(path.clone());
706
707     fn rmdir_failed(err: &IoError, path: &Path) -> String {
708         format!("rmdir_recursive failed; path={}; cause={}",
709                 path.display(), err)
710     }
711
712     fn update_err<T>(err: IoResult<T>, path: &Path) -> IoResult<T> {
713         err.update_err("couldn't recursively rmdir",
714                        |e| rmdir_failed(e, path))
715     }
716
717     while !rm_stack.is_empty() {
718         let children = try!(readdir(rm_stack.last().unwrap())
719             .update_detail(|e| rmdir_failed(e, path)));
720
721         let mut has_child_dir = false;
722
723         // delete all regular files in the way and push subdirs
724         // on the stack
725         for child in children.move_iter() {
726             // FIXME(#12795) we should use lstat in all cases
727             let child_type = match cfg!(windows) {
728                 true => try!(update_err(stat(&child), path)),
729                 false => try!(update_err(lstat(&child), path))
730             };
731
732             if child_type.kind == io::TypeDirectory {
733                 rm_stack.push(child);
734                 has_child_dir = true;
735             } else {
736                 // we can carry on safely if the file is already gone
737                 // (eg: deleted by someone else since readdir)
738                 match update_err(unlink(&child), path) {
739                     Ok(()) => (),
740                     Err(ref e) if e.kind == io::FileNotFound => (),
741                     Err(e) => return Err(e)
742                 }
743             }
744         }
745
746         // if no subdir was found, let's pop and delete
747         if !has_child_dir {
748             let result = update_err(rmdir(&rm_stack.pop().unwrap()), path);
749             match result {
750                 Ok(()) => (),
751                 Err(ref e) if e.kind == io::FileNotFound => (),
752                 Err(e) => return Err(e)
753             }
754         }
755     }
756
757     Ok(())
758 }
759
760 /// Changes the timestamps for a file's last modification and access time.
761 /// The file at the path specified will have its last access time set to
762 /// `atime` and its modification time set to `mtime`. The times specified should
763 /// be in milliseconds.
764 // FIXME(#10301) these arguments should not be u64
765 pub fn change_file_times(path: &Path, atime: u64, mtime: u64) -> IoResult<()> {
766     let err = LocalIo::maybe_raise(|io| {
767         io.fs_utime(&path.to_c_str(), atime, mtime)
768     }).map_err(IoError::from_rtio_error);
769     err.update_err("couldn't change_file_times",
770                    |e| format!("{}; path={}", e, path.display()))
771 }
772
773 impl Reader for File {
774     fn read(&mut self, buf: &mut [u8]) -> IoResult<uint> {
775         fn update_err<T>(result: IoResult<T>, file: &File) -> IoResult<T> {
776             result.update_err("couldn't read file",
777                               |e| format!("{}; path={}",
778                                           e, file.path.display()))
779         }
780
781         let result = update_err(self.fd.read(buf)
782                                     .map_err(IoError::from_rtio_error), self);
783
784         match result {
785             Ok(read) => {
786                 self.last_nread = read;
787                 match read {
788                     0 => update_err(Err(standard_error(io::EndOfFile)), self),
789                     _ => Ok(read as uint)
790                 }
791             },
792             Err(e) => Err(e)
793         }
794     }
795 }
796
797 impl Writer for File {
798     fn write(&mut self, buf: &[u8]) -> IoResult<()> {
799         let err = self.fd.write(buf).map_err(IoError::from_rtio_error);
800         err.update_err("couldn't write to file",
801                        |e| format!("{}; path={}", e, self.path.display()))
802     }
803 }
804
805 impl Seek for File {
806     fn tell(&self) -> IoResult<u64> {
807         let err = self.fd.tell().map_err(IoError::from_rtio_error);
808         err.update_err("couldn't retrieve file cursor (`tell`)",
809                        |e| format!("{}; path={}", e, self.path.display()))
810     }
811
812     fn seek(&mut self, pos: i64, style: SeekStyle) -> IoResult<()> {
813         let style = match style {
814             SeekSet => rtio::SeekSet,
815             SeekCur => rtio::SeekCur,
816             SeekEnd => rtio::SeekEnd,
817         };
818         let err = match self.fd.seek(pos, style) {
819             Ok(_) => {
820                 // successful seek resets EOF indicator
821                 self.last_nread = -1;
822                 Ok(())
823             }
824             Err(e) => Err(IoError::from_rtio_error(e)),
825         };
826         err.update_err("couldn't seek in file",
827                        |e| format!("{}; path={}", e, self.path.display()))
828     }
829 }
830
831 impl path::Path {
832     /// Get information on the file, directory, etc at this path.
833     ///
834     /// Consult the `fs::stat` documentation for more info.
835     ///
836     /// This call preserves identical runtime/error semantics with `file::stat`.
837     pub fn stat(&self) -> IoResult<FileStat> { stat(self) }
838
839     /// Get information on the file, directory, etc at this path, not following
840     /// symlinks.
841     ///
842     /// Consult the `fs::lstat` documentation for more info.
843     ///
844     /// This call preserves identical runtime/error semantics with `file::lstat`.
845     pub fn lstat(&self) -> IoResult<FileStat> { lstat(self) }
846
847     /// Boolean value indicator whether the underlying file exists on the local
848     /// filesystem. Returns false in exactly the cases where `fs::stat` fails.
849     pub fn exists(&self) -> bool {
850         self.stat().is_ok()
851     }
852
853     /// Whether the underlying implementation (be it a file path, or something
854     /// else) points at a "regular file" on the FS. Will return false for paths
855     /// to non-existent locations or directories or other non-regular files
856     /// (named pipes, etc). Follows links when making this determination.
857     pub fn is_file(&self) -> bool {
858         match self.stat() {
859             Ok(s) => s.kind == io::TypeFile,
860             Err(..) => false
861         }
862     }
863
864     /// Whether the underlying implementation (be it a file path, or something
865     /// else) is pointing at a directory in the underlying FS. Will return
866     /// false for paths to non-existent locations or if the item is not a
867     /// directory (eg files, named pipes, etc). Follows links when making this
868     /// determination.
869     pub fn is_dir(&self) -> bool {
870         match self.stat() {
871             Ok(s) => s.kind == io::TypeDirectory,
872             Err(..) => false
873         }
874     }
875 }
876
877 fn mode_string(mode: FileMode) -> &'static str {
878     match mode {
879         super::Open => "open",
880         super::Append => "append",
881         super::Truncate => "truncate"
882     }
883 }
884
885 fn access_string(access: FileAccess) -> &'static str {
886     match access {
887         super::Read => "read",
888         super::Write => "write",
889         super::ReadWrite => "readwrite"
890     }
891 }
892
893 #[cfg(test)]
894 #[allow(unused_imports)]
895 mod test {
896     use prelude::*;
897     use io::{SeekSet, SeekCur, SeekEnd, Read, Open, ReadWrite};
898     use io;
899     use str;
900     use io::fs::{File, rmdir, mkdir, readdir, rmdir_recursive,
901                  mkdir_recursive, copy, unlink, stat, symlink, link,
902                  readlink, chmod, lstat, change_file_times};
903     use path::Path;
904     use io;
905     use ops::Drop;
906     use str::StrSlice;
907
908     macro_rules! check( ($e:expr) => (
909         match $e {
910             Ok(t) => t,
911             Err(e) => fail!("{} failed with: {}", stringify!($e), e),
912         }
913     ) )
914
915     macro_rules! error( ($e:expr, $s:expr) => (
916         match $e {
917             Ok(val) => fail!("Should have been an error, was {:?}", val),
918             Err(ref err) => assert!(err.to_string().as_slice().contains($s.as_slice()),
919                                     format!("`{}` did not contain `{}`", err, $s))
920         }
921     ) )
922
923     struct TempDir(Path);
924
925     impl TempDir {
926         fn join(&self, path: &str) -> Path {
927             let TempDir(ref p) = *self;
928             p.join(path)
929         }
930
931         fn path<'a>(&'a self) -> &'a Path {
932             let TempDir(ref p) = *self;
933             p
934         }
935     }
936
937     impl Drop for TempDir {
938         fn drop(&mut self) {
939             // Gee, seeing how we're testing the fs module I sure hope that we
940             // at least implement this correctly!
941             let TempDir(ref p) = *self;
942             check!(io::fs::rmdir_recursive(p));
943         }
944     }
945
946     pub fn tmpdir() -> TempDir {
947         use os;
948         use rand;
949         let ret = os::tmpdir().join(format!("rust-{}", rand::random::<u32>()));
950         check!(io::fs::mkdir(&ret, io::UserRWX));
951         TempDir(ret)
952     }
953
954     iotest!(fn file_test_io_smoke_test() {
955         let message = "it's alright. have a good time";
956         let tmpdir = tmpdir();
957         let filename = &tmpdir.join("file_rt_io_file_test.txt");
958         {
959             let mut write_stream = File::open_mode(filename, Open, ReadWrite);
960             check!(write_stream.write(message.as_bytes()));
961         }
962         {
963             let mut read_stream = File::open_mode(filename, Open, Read);
964             let mut read_buf = [0, .. 1028];
965             let read_str = match check!(read_stream.read(read_buf)) {
966                 -1|0 => fail!("shouldn't happen"),
967                 n => str::from_utf8(read_buf.slice_to(n)).unwrap().to_owned()
968             };
969             assert_eq!(read_str, message.to_owned());
970         }
971         check!(unlink(filename));
972     })
973
974     iotest!(fn invalid_path_raises() {
975         let tmpdir = tmpdir();
976         let filename = &tmpdir.join("file_that_does_not_exist.txt");
977         let result = File::open_mode(filename, Open, Read);
978
979         error!(result, "couldn't open file");
980         if cfg!(unix) {
981             error!(result, "no such file or directory");
982         }
983         error!(result, format!("path={}; mode=open; access=read", filename.display()));
984     })
985
986     iotest!(fn file_test_iounlinking_invalid_path_should_raise_condition() {
987         let tmpdir = tmpdir();
988         let filename = &tmpdir.join("file_another_file_that_does_not_exist.txt");
989
990         let result = unlink(filename);
991
992         error!(result, "couldn't unlink path");
993         if cfg!(unix) {
994             error!(result, "no such file or directory");
995         }
996         error!(result, format!("path={}", filename.display()));
997     })
998
999     iotest!(fn file_test_io_non_positional_read() {
1000         let message: &str = "ten-four";
1001         let mut read_mem = [0, .. 8];
1002         let tmpdir = tmpdir();
1003         let filename = &tmpdir.join("file_rt_io_file_test_positional.txt");
1004         {
1005             let mut rw_stream = File::open_mode(filename, Open, ReadWrite);
1006             check!(rw_stream.write(message.as_bytes()));
1007         }
1008         {
1009             let mut read_stream = File::open_mode(filename, Open, Read);
1010             {
1011                 let read_buf = read_mem.mut_slice(0, 4);
1012                 check!(read_stream.read(read_buf));
1013             }
1014             {
1015                 let read_buf = read_mem.mut_slice(4, 8);
1016                 check!(read_stream.read(read_buf));
1017             }
1018         }
1019         check!(unlink(filename));
1020         let read_str = str::from_utf8(read_mem).unwrap();
1021         assert_eq!(read_str, message);
1022     })
1023
1024     iotest!(fn file_test_io_seek_and_tell_smoke_test() {
1025         let message = "ten-four";
1026         let mut read_mem = [0, .. 4];
1027         let set_cursor = 4 as u64;
1028         let mut tell_pos_pre_read;
1029         let mut tell_pos_post_read;
1030         let tmpdir = tmpdir();
1031         let filename = &tmpdir.join("file_rt_io_file_test_seeking.txt");
1032         {
1033             let mut rw_stream = File::open_mode(filename, Open, ReadWrite);
1034             check!(rw_stream.write(message.as_bytes()));
1035         }
1036         {
1037             let mut read_stream = File::open_mode(filename, Open, Read);
1038             check!(read_stream.seek(set_cursor as i64, SeekSet));
1039             tell_pos_pre_read = check!(read_stream.tell());
1040             check!(read_stream.read(read_mem));
1041             tell_pos_post_read = check!(read_stream.tell());
1042         }
1043         check!(unlink(filename));
1044         let read_str = str::from_utf8(read_mem).unwrap();
1045         assert_eq!(read_str, message.slice(4, 8));
1046         assert_eq!(tell_pos_pre_read, set_cursor);
1047         assert_eq!(tell_pos_post_read, message.len() as u64);
1048     })
1049
1050     iotest!(fn file_test_io_seek_and_write() {
1051         let initial_msg =   "food-is-yummy";
1052         let overwrite_msg =    "-the-bar!!";
1053         let final_msg =     "foo-the-bar!!";
1054         let seek_idx = 3i;
1055         let mut read_mem = [0, .. 13];
1056         let tmpdir = tmpdir();
1057         let filename = &tmpdir.join("file_rt_io_file_test_seek_and_write.txt");
1058         {
1059             let mut rw_stream = File::open_mode(filename, Open, ReadWrite);
1060             check!(rw_stream.write(initial_msg.as_bytes()));
1061             check!(rw_stream.seek(seek_idx as i64, SeekSet));
1062             check!(rw_stream.write(overwrite_msg.as_bytes()));
1063         }
1064         {
1065             let mut read_stream = File::open_mode(filename, Open, Read);
1066             check!(read_stream.read(read_mem));
1067         }
1068         check!(unlink(filename));
1069         let read_str = str::from_utf8(read_mem).unwrap();
1070         assert!(read_str.as_slice() == final_msg.as_slice());
1071     })
1072
1073     iotest!(fn file_test_io_seek_shakedown() {
1074         use str;          // 01234567890123
1075         let initial_msg =   "qwer-asdf-zxcv";
1076         let chunk_one: &str = "qwer";
1077         let chunk_two: &str = "asdf";
1078         let chunk_three: &str = "zxcv";
1079         let mut read_mem = [0, .. 4];
1080         let tmpdir = tmpdir();
1081         let filename = &tmpdir.join("file_rt_io_file_test_seek_shakedown.txt");
1082         {
1083             let mut rw_stream = File::open_mode(filename, Open, ReadWrite);
1084             check!(rw_stream.write(initial_msg.as_bytes()));
1085         }
1086         {
1087             let mut read_stream = File::open_mode(filename, Open, Read);
1088
1089             check!(read_stream.seek(-4, SeekEnd));
1090             check!(read_stream.read(read_mem));
1091             assert_eq!(str::from_utf8(read_mem).unwrap(), chunk_three);
1092
1093             check!(read_stream.seek(-9, SeekCur));
1094             check!(read_stream.read(read_mem));
1095             assert_eq!(str::from_utf8(read_mem).unwrap(), chunk_two);
1096
1097             check!(read_stream.seek(0, SeekSet));
1098             check!(read_stream.read(read_mem));
1099             assert_eq!(str::from_utf8(read_mem).unwrap(), chunk_one);
1100         }
1101         check!(unlink(filename));
1102     })
1103
1104     iotest!(fn file_test_stat_is_correct_on_is_file() {
1105         let tmpdir = tmpdir();
1106         let filename = &tmpdir.join("file_stat_correct_on_is_file.txt");
1107         {
1108             let mut fs = check!(File::open_mode(filename, Open, ReadWrite));
1109             let msg = "hw";
1110             fs.write(msg.as_bytes()).unwrap();
1111
1112             let fstat_res = check!(fs.stat());
1113             assert_eq!(fstat_res.kind, io::TypeFile);
1114         }
1115         let stat_res_fn = check!(stat(filename));
1116         assert_eq!(stat_res_fn.kind, io::TypeFile);
1117         let stat_res_meth = check!(filename.stat());
1118         assert_eq!(stat_res_meth.kind, io::TypeFile);
1119         check!(unlink(filename));
1120     })
1121
1122     iotest!(fn file_test_stat_is_correct_on_is_dir() {
1123         let tmpdir = tmpdir();
1124         let filename = &tmpdir.join("file_stat_correct_on_is_dir");
1125         check!(mkdir(filename, io::UserRWX));
1126         let stat_res_fn = check!(stat(filename));
1127         assert!(stat_res_fn.kind == io::TypeDirectory);
1128         let stat_res_meth = check!(filename.stat());
1129         assert!(stat_res_meth.kind == io::TypeDirectory);
1130         check!(rmdir(filename));
1131     })
1132
1133     iotest!(fn file_test_fileinfo_false_when_checking_is_file_on_a_directory() {
1134         let tmpdir = tmpdir();
1135         let dir = &tmpdir.join("fileinfo_false_on_dir");
1136         check!(mkdir(dir, io::UserRWX));
1137         assert!(dir.is_file() == false);
1138         check!(rmdir(dir));
1139     })
1140
1141     iotest!(fn file_test_fileinfo_check_exists_before_and_after_file_creation() {
1142         let tmpdir = tmpdir();
1143         let file = &tmpdir.join("fileinfo_check_exists_b_and_a.txt");
1144         check!(File::create(file).write(b"foo"));
1145         assert!(file.exists());
1146         check!(unlink(file));
1147         assert!(!file.exists());
1148     })
1149
1150     iotest!(fn file_test_directoryinfo_check_exists_before_and_after_mkdir() {
1151         let tmpdir = tmpdir();
1152         let dir = &tmpdir.join("before_and_after_dir");
1153         assert!(!dir.exists());
1154         check!(mkdir(dir, io::UserRWX));
1155         assert!(dir.exists());
1156         assert!(dir.is_dir());
1157         check!(rmdir(dir));
1158         assert!(!dir.exists());
1159     })
1160
1161     iotest!(fn file_test_directoryinfo_readdir() {
1162         use str;
1163         let tmpdir = tmpdir();
1164         let dir = &tmpdir.join("di_readdir");
1165         check!(mkdir(dir, io::UserRWX));
1166         let prefix = "foo";
1167         for n in range(0i,3) {
1168             let f = dir.join(format!("{}.txt", n));
1169             let mut w = check!(File::create(&f));
1170             let msg_str = format!("{}{}", prefix, n.to_string());
1171             let msg = msg_str.as_slice().as_bytes();
1172             check!(w.write(msg));
1173         }
1174         let files = check!(readdir(dir));
1175         let mut mem = [0u8, .. 4];
1176         for f in files.iter() {
1177             {
1178                 let n = f.filestem_str();
1179                 check!(File::open(f).read(mem));
1180                 let read_str = str::from_utf8(mem).unwrap();
1181                 let expected = match n {
1182                     None|Some("") => fail!("really shouldn't happen.."),
1183                     Some(n) => format!("{}{}", prefix, n),
1184                 };
1185                 assert_eq!(expected.as_slice(), read_str);
1186             }
1187             check!(unlink(f));
1188         }
1189         check!(rmdir(dir));
1190     })
1191
1192     iotest!(fn file_test_walk_dir() {
1193         let tmpdir = tmpdir();
1194         let dir = &tmpdir.join("walk_dir");
1195         check!(mkdir(dir, io::UserRWX));
1196
1197         let dir1 = &dir.join("01/02/03");
1198         check!(mkdir_recursive(dir1, io::UserRWX));
1199         check!(File::create(&dir1.join("04")));
1200
1201         let dir2 = &dir.join("11/12/13");
1202         check!(mkdir_recursive(dir2, io::UserRWX));
1203         check!(File::create(&dir2.join("14")));
1204
1205         let mut files = check!(walk_dir(dir));
1206         let mut cur = [0u8, .. 2];
1207         for f in files {
1208             let stem = f.filestem_str().unwrap();
1209             let root = stem.as_bytes()[0] - ('0' as u8);
1210             let name = stem.as_bytes()[1] - ('0' as u8);
1211             assert!(cur[root as uint] < name);
1212             cur[root as uint] = name;
1213         }
1214
1215         check!(rmdir_recursive(dir));
1216     })
1217
1218     iotest!(fn recursive_mkdir() {
1219         let tmpdir = tmpdir();
1220         let dir = tmpdir.join("d1/d2");
1221         check!(mkdir_recursive(&dir, io::UserRWX));
1222         assert!(dir.is_dir())
1223     })
1224
1225     iotest!(fn recursive_mkdir_failure() {
1226         let tmpdir = tmpdir();
1227         let dir = tmpdir.join("d1");
1228         let file = dir.join("f1");
1229
1230         check!(mkdir_recursive(&dir, io::UserRWX));
1231         check!(File::create(&file));
1232
1233         let result = mkdir_recursive(&file, io::UserRWX);
1234
1235         error!(result, "couldn't recursively mkdir");
1236         error!(result, "couldn't create directory");
1237         error!(result, "mode=FilePermission { bits: 448 }");
1238         error!(result, format!("path={}", file.display()));
1239     })
1240
1241     iotest!(fn recursive_mkdir_slash() {
1242         check!(mkdir_recursive(&Path::new("/"), io::UserRWX));
1243     })
1244
1245     // FIXME(#12795) depends on lstat to work on windows
1246     #[cfg(not(windows))]
1247     iotest!(fn recursive_rmdir() {
1248         let tmpdir = tmpdir();
1249         let d1 = tmpdir.join("d1");
1250         let dt = d1.join("t");
1251         let dtt = dt.join("t");
1252         let d2 = tmpdir.join("d2");
1253         let canary = d2.join("do_not_delete");
1254         check!(mkdir_recursive(&dtt, io::UserRWX));
1255         check!(mkdir_recursive(&d2, io::UserRWX));
1256         check!(File::create(&canary).write(b"foo"));
1257         check!(symlink(&d2, &dt.join("d2")));
1258         check!(rmdir_recursive(&d1));
1259
1260         assert!(!d1.is_dir());
1261         assert!(canary.exists());
1262     })
1263
1264     iotest!(fn unicode_path_is_dir() {
1265         assert!(Path::new(".").is_dir());
1266         assert!(!Path::new("test/stdtest/fs.rs").is_dir());
1267
1268         let tmpdir = tmpdir();
1269
1270         let mut dirpath = tmpdir.path().clone();
1271         dirpath.push(format!("test-가一ー你好"));
1272         check!(mkdir(&dirpath, io::UserRWX));
1273         assert!(dirpath.is_dir());
1274
1275         let mut filepath = dirpath;
1276         filepath.push("unicode-file-\uac00\u4e00\u30fc\u4f60\u597d.rs");
1277         check!(File::create(&filepath)); // ignore return; touch only
1278         assert!(!filepath.is_dir());
1279         assert!(filepath.exists());
1280     })
1281
1282     iotest!(fn unicode_path_exists() {
1283         assert!(Path::new(".").exists());
1284         assert!(!Path::new("test/nonexistent-bogus-path").exists());
1285
1286         let tmpdir = tmpdir();
1287         let unicode = tmpdir.path();
1288         let unicode = unicode.join(format!("test-각丁ー再见"));
1289         check!(mkdir(&unicode, io::UserRWX));
1290         assert!(unicode.exists());
1291         assert!(!Path::new("test/unicode-bogus-path-각丁ー再见").exists());
1292     })
1293
1294     iotest!(fn copy_file_does_not_exist() {
1295         let from = Path::new("test/nonexistent-bogus-path");
1296         let to = Path::new("test/other-bogus-path");
1297
1298         error!(copy(&from, &to),
1299             format!("couldn't copy path (the source path is not an \
1300                     existing file; from={}; to={})",
1301                     from.display(), to.display()));
1302
1303         match copy(&from, &to) {
1304             Ok(..) => fail!(),
1305             Err(..) => {
1306                 assert!(!from.exists());
1307                 assert!(!to.exists());
1308             }
1309         }
1310     })
1311
1312     iotest!(fn copy_file_ok() {
1313         let tmpdir = tmpdir();
1314         let input = tmpdir.join("in.txt");
1315         let out = tmpdir.join("out.txt");
1316
1317         check!(File::create(&input).write(b"hello"));
1318         check!(copy(&input, &out));
1319         let contents = check!(File::open(&out).read_to_end());
1320         assert_eq!(contents.as_slice(), b"hello");
1321
1322         assert_eq!(check!(input.stat()).perm, check!(out.stat()).perm);
1323     })
1324
1325     iotest!(fn copy_file_dst_dir() {
1326         let tmpdir = tmpdir();
1327         let out = tmpdir.join("out");
1328
1329         check!(File::create(&out));
1330         match copy(&out, tmpdir.path()) {
1331             Ok(..) => fail!(), Err(..) => {}
1332         }
1333     })
1334
1335     iotest!(fn copy_file_dst_exists() {
1336         let tmpdir = tmpdir();
1337         let input = tmpdir.join("in");
1338         let output = tmpdir.join("out");
1339
1340         check!(File::create(&input).write("foo".as_bytes()));
1341         check!(File::create(&output).write("bar".as_bytes()));
1342         check!(copy(&input, &output));
1343
1344         assert_eq!(check!(File::open(&output).read_to_end()),
1345                    (Vec::from_slice(b"foo")));
1346     })
1347
1348     iotest!(fn copy_file_src_dir() {
1349         let tmpdir = tmpdir();
1350         let out = tmpdir.join("out");
1351
1352         match copy(tmpdir.path(), &out) {
1353             Ok(..) => fail!(), Err(..) => {}
1354         }
1355         assert!(!out.exists());
1356     })
1357
1358     iotest!(fn copy_file_preserves_perm_bits() {
1359         let tmpdir = tmpdir();
1360         let input = tmpdir.join("in.txt");
1361         let out = tmpdir.join("out.txt");
1362
1363         check!(File::create(&input));
1364         check!(chmod(&input, io::UserRead));
1365         check!(copy(&input, &out));
1366         assert!(!check!(out.stat()).perm.intersects(io::UserWrite));
1367
1368         check!(chmod(&input, io::UserFile));
1369         check!(chmod(&out, io::UserFile));
1370     })
1371
1372     #[cfg(not(windows))] // FIXME(#10264) operation not permitted?
1373     iotest!(fn symlinks_work() {
1374         let tmpdir = tmpdir();
1375         let input = tmpdir.join("in.txt");
1376         let out = tmpdir.join("out.txt");
1377
1378         check!(File::create(&input).write("foobar".as_bytes()));
1379         check!(symlink(&input, &out));
1380         if cfg!(not(windows)) {
1381             assert_eq!(check!(lstat(&out)).kind, io::TypeSymlink);
1382             assert_eq!(check!(out.lstat()).kind, io::TypeSymlink);
1383         }
1384         assert_eq!(check!(stat(&out)).size, check!(stat(&input)).size);
1385         assert_eq!(check!(File::open(&out).read_to_end()),
1386                    (Vec::from_slice(b"foobar")));
1387     })
1388
1389     #[cfg(not(windows))] // apparently windows doesn't like symlinks
1390     iotest!(fn symlink_noexist() {
1391         let tmpdir = tmpdir();
1392         // symlinks can point to things that don't exist
1393         check!(symlink(&tmpdir.join("foo"), &tmpdir.join("bar")));
1394         assert!(check!(readlink(&tmpdir.join("bar"))) == tmpdir.join("foo"));
1395     })
1396
1397     iotest!(fn readlink_not_symlink() {
1398         let tmpdir = tmpdir();
1399         match readlink(tmpdir.path()) {
1400             Ok(..) => fail!("wanted a failure"),
1401             Err(..) => {}
1402         }
1403     })
1404
1405     iotest!(fn links_work() {
1406         let tmpdir = tmpdir();
1407         let input = tmpdir.join("in.txt");
1408         let out = tmpdir.join("out.txt");
1409
1410         check!(File::create(&input).write("foobar".as_bytes()));
1411         check!(link(&input, &out));
1412         if cfg!(not(windows)) {
1413             assert_eq!(check!(lstat(&out)).kind, io::TypeFile);
1414             assert_eq!(check!(out.lstat()).kind, io::TypeFile);
1415             assert_eq!(check!(stat(&out)).unstable.nlink, 2);
1416             assert_eq!(check!(out.stat()).unstable.nlink, 2);
1417         }
1418         assert_eq!(check!(stat(&out)).size, check!(stat(&input)).size);
1419         assert_eq!(check!(stat(&out)).size, check!(input.stat()).size);
1420         assert_eq!(check!(File::open(&out).read_to_end()),
1421                    (Vec::from_slice(b"foobar")));
1422
1423         // can't link to yourself
1424         match link(&input, &input) {
1425             Ok(..) => fail!("wanted a failure"),
1426             Err(..) => {}
1427         }
1428         // can't link to something that doesn't exist
1429         match link(&tmpdir.join("foo"), &tmpdir.join("bar")) {
1430             Ok(..) => fail!("wanted a failure"),
1431             Err(..) => {}
1432         }
1433     })
1434
1435     iotest!(fn chmod_works() {
1436         let tmpdir = tmpdir();
1437         let file = tmpdir.join("in.txt");
1438
1439         check!(File::create(&file));
1440         assert!(check!(stat(&file)).perm.contains(io::UserWrite));
1441         check!(chmod(&file, io::UserRead));
1442         assert!(!check!(stat(&file)).perm.contains(io::UserWrite));
1443
1444         match chmod(&tmpdir.join("foo"), io::UserRWX) {
1445             Ok(..) => fail!("wanted a failure"),
1446             Err(..) => {}
1447         }
1448
1449         check!(chmod(&file, io::UserFile));
1450     })
1451
1452     iotest!(fn sync_doesnt_kill_anything() {
1453         let tmpdir = tmpdir();
1454         let path = tmpdir.join("in.txt");
1455
1456         let mut file = check!(File::open_mode(&path, io::Open, io::ReadWrite));
1457         check!(file.fsync());
1458         check!(file.datasync());
1459         check!(file.write(b"foo"));
1460         check!(file.fsync());
1461         check!(file.datasync());
1462         drop(file);
1463     })
1464
1465     iotest!(fn truncate_works() {
1466         let tmpdir = tmpdir();
1467         let path = tmpdir.join("in.txt");
1468
1469         let mut file = check!(File::open_mode(&path, io::Open, io::ReadWrite));
1470         check!(file.write(b"foo"));
1471         check!(file.fsync());
1472
1473         // Do some simple things with truncation
1474         assert_eq!(check!(file.stat()).size, 3);
1475         check!(file.truncate(10));
1476         assert_eq!(check!(file.stat()).size, 10);
1477         check!(file.write(b"bar"));
1478         check!(file.fsync());
1479         assert_eq!(check!(file.stat()).size, 10);
1480         assert_eq!(check!(File::open(&path).read_to_end()),
1481                    (Vec::from_slice(b"foobar\0\0\0\0")));
1482
1483         // Truncate to a smaller length, don't seek, and then write something.
1484         // Ensure that the intermediate zeroes are all filled in (we're seeked
1485         // past the end of the file).
1486         check!(file.truncate(2));
1487         assert_eq!(check!(file.stat()).size, 2);
1488         check!(file.write(b"wut"));
1489         check!(file.fsync());
1490         assert_eq!(check!(file.stat()).size, 9);
1491         assert_eq!(check!(File::open(&path).read_to_end()),
1492                    (Vec::from_slice(b"fo\0\0\0\0wut")));
1493         drop(file);
1494     })
1495
1496     iotest!(fn open_flavors() {
1497         let tmpdir = tmpdir();
1498
1499         match File::open_mode(&tmpdir.join("a"), io::Open, io::Read) {
1500             Ok(..) => fail!(), Err(..) => {}
1501         }
1502
1503         // Perform each one twice to make sure that it succeeds the second time
1504         // (where the file exists)
1505         check!(File::open_mode(&tmpdir.join("b"), io::Open, io::Write));
1506         assert!(tmpdir.join("b").exists());
1507         check!(File::open_mode(&tmpdir.join("b"), io::Open, io::Write));
1508
1509         check!(File::open_mode(&tmpdir.join("c"), io::Open, io::ReadWrite));
1510         assert!(tmpdir.join("c").exists());
1511         check!(File::open_mode(&tmpdir.join("c"), io::Open, io::ReadWrite));
1512
1513         check!(File::open_mode(&tmpdir.join("d"), io::Append, io::Write));
1514         assert!(tmpdir.join("d").exists());
1515         check!(File::open_mode(&tmpdir.join("d"), io::Append, io::Write));
1516
1517         check!(File::open_mode(&tmpdir.join("e"), io::Append, io::ReadWrite));
1518         assert!(tmpdir.join("e").exists());
1519         check!(File::open_mode(&tmpdir.join("e"), io::Append, io::ReadWrite));
1520
1521         check!(File::open_mode(&tmpdir.join("f"), io::Truncate, io::Write));
1522         assert!(tmpdir.join("f").exists());
1523         check!(File::open_mode(&tmpdir.join("f"), io::Truncate, io::Write));
1524
1525         check!(File::open_mode(&tmpdir.join("g"), io::Truncate, io::ReadWrite));
1526         assert!(tmpdir.join("g").exists());
1527         check!(File::open_mode(&tmpdir.join("g"), io::Truncate, io::ReadWrite));
1528
1529         check!(File::create(&tmpdir.join("h")).write("foo".as_bytes()));
1530         check!(File::open_mode(&tmpdir.join("h"), io::Open, io::Read));
1531         {
1532             let mut f = check!(File::open_mode(&tmpdir.join("h"), io::Open,
1533                                                io::Read));
1534             match f.write("wut".as_bytes()) {
1535                 Ok(..) => fail!(), Err(..) => {}
1536             }
1537         }
1538         assert!(check!(stat(&tmpdir.join("h"))).size == 3,
1539                 "write/stat failed");
1540         {
1541             let mut f = check!(File::open_mode(&tmpdir.join("h"), io::Append,
1542                                                io::Write));
1543             check!(f.write("bar".as_bytes()));
1544         }
1545         assert!(check!(stat(&tmpdir.join("h"))).size == 6,
1546                 "append didn't append");
1547         {
1548             let mut f = check!(File::open_mode(&tmpdir.join("h"), io::Truncate,
1549                                                io::Write));
1550             check!(f.write("bar".as_bytes()));
1551         }
1552         assert!(check!(stat(&tmpdir.join("h"))).size == 3,
1553                 "truncate didn't truncate");
1554     })
1555
1556     #[test]
1557     fn utime() {
1558         let tmpdir = tmpdir();
1559         let path = tmpdir.join("a");
1560         check!(File::create(&path));
1561
1562         check!(change_file_times(&path, 1000, 2000));
1563         assert_eq!(check!(path.stat()).accessed, 1000);
1564         assert_eq!(check!(path.stat()).modified, 2000);
1565     }
1566
1567     #[test]
1568     fn utime_noexist() {
1569         let tmpdir = tmpdir();
1570
1571         match change_file_times(&tmpdir.join("a"), 100, 200) {
1572             Ok(..) => fail!(),
1573             Err(..) => {}
1574         }
1575     }
1576
1577     iotest!(fn binary_file() {
1578         use rand::{StdRng, Rng};
1579
1580         let mut bytes = [0, ..1024];
1581         StdRng::new().ok().unwrap().fill_bytes(bytes);
1582
1583         let tmpdir = tmpdir();
1584
1585         check!(File::create(&tmpdir.join("test")).write(bytes));
1586         let actual = check!(File::open(&tmpdir.join("test")).read_to_end());
1587         assert!(actual.as_slice() == bytes);
1588     })
1589 }