Runtime removal: refactor fs

This moves the filesystem implementation from libnative into the new
`sys` modules, refactoring along the way and hooking into `std::io::fs`.

Because this eliminates APIs in `libnative` and `librustrt`, it is a:

[breaking-change]

This functionality is likely to be available publicly, in some form,
from `std` in the future.
This commit is contained in:
Aaron Turon 2014-09-30 17:34:14 -07:00
parent 16470cf01b
commit 0c1e1ff1e3
9 changed files with 664 additions and 1168 deletions

View File

@ -1,554 +0,0 @@
// Copyright 2013-2014 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
//! Blocking posix-based file I/O
use alloc::arc::Arc;
use libc::{mod, c_int, c_void};
use std::c_str::CString;
use std::mem;
use std::rt::rtio::{mod, IoResult};
use io::{retry, keep_going};
use io::util;
pub type fd_t = libc::c_int;
struct Inner {
fd: fd_t,
close_on_drop: bool,
}
pub struct FileDesc {
inner: Arc<Inner>
}
impl FileDesc {
/// Create a `FileDesc` from an open C file descriptor.
///
/// The `FileDesc` will take ownership of the specified file descriptor and
/// close it upon destruction if the `close_on_drop` flag is true, otherwise
/// it will not close the file descriptor when this `FileDesc` is dropped.
///
/// Note that all I/O operations done on this object will be *blocking*, but
/// they do not require the runtime to be active.
pub fn new(fd: fd_t, close_on_drop: bool) -> FileDesc {
FileDesc { inner: Arc::new(Inner {
fd: fd,
close_on_drop: close_on_drop
}) }
}
// FIXME(#10465) these functions should not be public, but anything in
// native::io wanting to use them is forced to have all the
// rtio traits in scope
pub fn inner_read(&mut self, buf: &mut [u8]) -> IoResult<uint> {
let ret = retry(|| unsafe {
libc::read(self.fd(),
buf.as_mut_ptr() as *mut libc::c_void,
buf.len() as libc::size_t)
});
if ret == 0 {
Err(util::eof())
} else if ret < 0 {
Err(super::last_error())
} else {
Ok(ret as uint)
}
}
pub fn inner_write(&mut self, buf: &[u8]) -> IoResult<()> {
let ret = keep_going(buf, |buf, len| {
unsafe {
libc::write(self.fd(), buf as *const libc::c_void,
len as libc::size_t) as i64
}
});
if ret < 0 {
Err(super::last_error())
} else {
Ok(())
}
}
pub fn fd(&self) -> fd_t { self.inner.fd }
}
impl rtio::RtioFileStream for FileDesc {
fn read(&mut self, buf: &mut [u8]) -> IoResult<int> {
self.inner_read(buf).map(|i| i as int)
}
fn write(&mut self, buf: &[u8]) -> IoResult<()> {
self.inner_write(buf)
}
fn pread(&mut self, buf: &mut [u8], offset: u64) -> IoResult<int> {
match retry(|| unsafe {
libc::pread(self.fd(), buf.as_ptr() as *mut _,
buf.len() as libc::size_t,
offset as libc::off_t)
}) {
-1 => Err(super::last_error()),
n => Ok(n as int)
}
}
fn pwrite(&mut self, buf: &[u8], offset: u64) -> IoResult<()> {
super::mkerr_libc(retry(|| unsafe {
libc::pwrite(self.fd(), buf.as_ptr() as *const _,
buf.len() as libc::size_t, offset as libc::off_t)
}))
}
fn seek(&mut self, pos: i64, whence: rtio::SeekStyle) -> IoResult<u64> {
let whence = match whence {
rtio::SeekSet => libc::SEEK_SET,
rtio::SeekEnd => libc::SEEK_END,
rtio::SeekCur => libc::SEEK_CUR,
};
let n = unsafe { libc::lseek(self.fd(), pos as libc::off_t, whence) };
if n < 0 {
Err(super::last_error())
} else {
Ok(n as u64)
}
}
fn tell(&self) -> IoResult<u64> {
let n = unsafe { libc::lseek(self.fd(), 0, libc::SEEK_CUR) };
if n < 0 {
Err(super::last_error())
} else {
Ok(n as u64)
}
}
fn fsync(&mut self) -> IoResult<()> {
super::mkerr_libc(retry(|| unsafe { libc::fsync(self.fd()) }))
}
fn datasync(&mut self) -> IoResult<()> {
return super::mkerr_libc(os_datasync(self.fd()));
#[cfg(any(target_os = "macos", target_os = "ios"))]
fn os_datasync(fd: c_int) -> c_int {
unsafe { libc::fcntl(fd, libc::F_FULLFSYNC) }
}
#[cfg(target_os = "linux")]
fn os_datasync(fd: c_int) -> c_int {
retry(|| unsafe { libc::fdatasync(fd) })
}
#[cfg(not(any(target_os = "macos", target_os = "ios", target_os = "linux")))]
fn os_datasync(fd: c_int) -> c_int {
retry(|| unsafe { libc::fsync(fd) })
}
}
fn truncate(&mut self, offset: i64) -> IoResult<()> {
super::mkerr_libc(retry(|| unsafe {
libc::ftruncate(self.fd(), offset as libc::off_t)
}))
}
fn fstat(&mut self) -> IoResult<rtio::FileStat> {
let mut stat: libc::stat = unsafe { mem::zeroed() };
match unsafe { libc::fstat(self.fd(), &mut stat) } {
0 => Ok(mkstat(&stat)),
_ => Err(super::last_error()),
}
}
}
impl rtio::RtioPipe for FileDesc {
fn read(&mut self, buf: &mut [u8]) -> IoResult<uint> {
self.inner_read(buf)
}
fn write(&mut self, buf: &[u8]) -> IoResult<()> {
self.inner_write(buf)
}
fn clone(&self) -> Box<rtio::RtioPipe + Send> {
box FileDesc { inner: self.inner.clone() } as Box<rtio::RtioPipe + Send>
}
// Only supported on named pipes currently. Note that this doesn't have an
// impact on the std::io primitives, this is never called via
// std::io::PipeStream. If the functionality is exposed in the future, then
// these methods will need to be implemented.
fn close_read(&mut self) -> IoResult<()> {
Err(super::unimpl())
}
fn close_write(&mut self) -> IoResult<()> {
Err(super::unimpl())
}
fn set_timeout(&mut self, _t: Option<u64>) {}
fn set_read_timeout(&mut self, _t: Option<u64>) {}
fn set_write_timeout(&mut self, _t: Option<u64>) {}
}
impl rtio::RtioTTY for FileDesc {
fn read(&mut self, buf: &mut [u8]) -> IoResult<uint> {
self.inner_read(buf)
}
fn write(&mut self, buf: &[u8]) -> IoResult<()> {
self.inner_write(buf)
}
fn set_raw(&mut self, _raw: bool) -> IoResult<()> {
Err(super::unimpl())
}
fn get_winsize(&mut self) -> IoResult<(int, int)> {
Err(super::unimpl())
}
fn isatty(&self) -> bool { false }
}
impl Drop for Inner {
fn drop(&mut self) {
// closing stdio file handles makes no sense, so never do it. Also, note
// that errors are ignored when closing a file descriptor. The reason
// for this is that if an error occurs we don't actually know if the
// file descriptor was closed or not, and if we retried (for something
// like EINTR), we might close another valid file descriptor (opened
// after we closed ours.
if self.close_on_drop && self.fd > libc::STDERR_FILENO {
let n = unsafe { libc::close(self.fd) };
if n != 0 {
println!("error {} when closing file descriptor {}", n,
self.fd);
}
}
}
}
pub struct CFile {
file: *mut libc::FILE,
fd: FileDesc,
}
impl CFile {
/// Create a `CFile` from an open `FILE` pointer.
///
/// The `CFile` takes ownership of the `FILE` pointer and will close it upon
/// destruction.
pub fn new(file: *mut libc::FILE) -> CFile {
CFile {
file: file,
fd: FileDesc::new(unsafe { libc::fileno(file) }, false)
}
}
pub fn flush(&mut self) -> IoResult<()> {
super::mkerr_libc(retry(|| unsafe { libc::fflush(self.file) }))
}
}
impl rtio::RtioFileStream for CFile {
fn read(&mut self, buf: &mut [u8]) -> IoResult<int> {
let ret = keep_going(buf, |buf, len| {
unsafe {
libc::fread(buf as *mut libc::c_void, 1, len as libc::size_t,
self.file) as i64
}
});
if ret == 0 {
Err(util::eof())
} else if ret < 0 {
Err(super::last_error())
} else {
Ok(ret as int)
}
}
fn write(&mut self, buf: &[u8]) -> IoResult<()> {
let ret = keep_going(buf, |buf, len| {
unsafe {
libc::fwrite(buf as *const libc::c_void, 1, len as libc::size_t,
self.file) as i64
}
});
if ret < 0 {
Err(super::last_error())
} else {
Ok(())
}
}
fn pread(&mut self, buf: &mut [u8], offset: u64) -> IoResult<int> {
self.flush().and_then(|()| self.fd.pread(buf, offset))
}
fn pwrite(&mut self, buf: &[u8], offset: u64) -> IoResult<()> {
self.flush().and_then(|()| self.fd.pwrite(buf, offset))
}
fn seek(&mut self, pos: i64, style: rtio::SeekStyle) -> IoResult<u64> {
let whence = match style {
rtio::SeekSet => libc::SEEK_SET,
rtio::SeekEnd => libc::SEEK_END,
rtio::SeekCur => libc::SEEK_CUR,
};
let n = unsafe { libc::fseek(self.file, pos as libc::c_long, whence) };
if n < 0 {
Err(super::last_error())
} else {
Ok(n as u64)
}
}
fn tell(&self) -> IoResult<u64> {
let ret = unsafe { libc::ftell(self.file) };
if ret < 0 {
Err(super::last_error())
} else {
Ok(ret as u64)
}
}
fn fsync(&mut self) -> IoResult<()> {
self.flush().and_then(|()| self.fd.fsync())
}
fn datasync(&mut self) -> IoResult<()> {
self.flush().and_then(|()| self.fd.datasync())
}
fn truncate(&mut self, offset: i64) -> IoResult<()> {
self.flush().and_then(|()| self.fd.truncate(offset))
}
fn fstat(&mut self) -> IoResult<rtio::FileStat> {
self.flush().and_then(|()| self.fd.fstat())
}
}
impl Drop for CFile {
fn drop(&mut self) {
unsafe { let _ = libc::fclose(self.file); }
}
}
pub fn open(path: &CString, fm: rtio::FileMode, fa: rtio::FileAccess)
-> IoResult<FileDesc>
{
let flags = match fm {
rtio::Open => 0,
rtio::Append => libc::O_APPEND,
rtio::Truncate => libc::O_TRUNC,
};
// Opening with a write permission must silently create the file.
let (flags, mode) = match fa {
rtio::Read => (flags | libc::O_RDONLY, 0),
rtio::Write => (flags | libc::O_WRONLY | libc::O_CREAT,
libc::S_IRUSR | libc::S_IWUSR),
rtio::ReadWrite => (flags | libc::O_RDWR | libc::O_CREAT,
libc::S_IRUSR | libc::S_IWUSR),
};
match retry(|| unsafe { libc::open(path.as_ptr(), flags, mode) }) {
-1 => Err(super::last_error()),
fd => Ok(FileDesc::new(fd, true)),
}
}
pub fn mkdir(p: &CString, mode: uint) -> IoResult<()> {
super::mkerr_libc(unsafe { libc::mkdir(p.as_ptr(), mode as libc::mode_t) })
}
pub fn readdir(p: &CString) -> IoResult<Vec<CString>> {
use libc::{dirent_t};
use libc::{opendir, readdir_r, closedir};
fn prune(root: &CString, dirs: Vec<Path>) -> Vec<CString> {
let root = unsafe { CString::new(root.as_ptr(), false) };
let root = Path::new(root);
dirs.into_iter().filter(|path| {
path.as_vec() != b"." && path.as_vec() != b".."
}).map(|path| root.join(path).to_c_str()).collect()
}
extern {
fn rust_dirent_t_size() -> libc::c_int;
fn rust_list_dir_val(ptr: *mut dirent_t) -> *const libc::c_char;
}
let size = unsafe { rust_dirent_t_size() };
let mut buf = Vec::<u8>::with_capacity(size as uint);
let ptr = buf.as_mut_slice().as_mut_ptr() as *mut dirent_t;
let dir_ptr = unsafe {opendir(p.as_ptr())};
if dir_ptr as uint != 0 {
let mut paths = vec!();
let mut entry_ptr = 0 as *mut dirent_t;
while unsafe { readdir_r(dir_ptr, ptr, &mut entry_ptr) == 0 } {
if entry_ptr.is_null() { break }
let cstr = unsafe {
CString::new(rust_list_dir_val(entry_ptr), false)
};
paths.push(Path::new(cstr));
}
assert_eq!(unsafe { closedir(dir_ptr) }, 0);
Ok(prune(p, paths))
} else {
Err(super::last_error())
}
}
pub fn unlink(p: &CString) -> IoResult<()> {
super::mkerr_libc(unsafe { libc::unlink(p.as_ptr()) })
}
pub fn rename(old: &CString, new: &CString) -> IoResult<()> {
super::mkerr_libc(unsafe { libc::rename(old.as_ptr(), new.as_ptr()) })
}
pub fn chmod(p: &CString, mode: uint) -> IoResult<()> {
super::mkerr_libc(retry(|| unsafe {
libc::chmod(p.as_ptr(), mode as libc::mode_t)
}))
}
pub fn rmdir(p: &CString) -> IoResult<()> {
super::mkerr_libc(unsafe { libc::rmdir(p.as_ptr()) })
}
pub fn chown(p: &CString, uid: int, gid: int) -> IoResult<()> {
super::mkerr_libc(retry(|| unsafe {
libc::chown(p.as_ptr(), uid as libc::uid_t,
gid as libc::gid_t)
}))
}
pub fn readlink(p: &CString) -> IoResult<CString> {
let p = p.as_ptr();
let mut len = unsafe { libc::pathconf(p as *mut _, libc::_PC_NAME_MAX) };
if len == -1 {
len = 1024; // FIXME: read PATH_MAX from C ffi?
}
let mut buf: Vec<u8> = Vec::with_capacity(len as uint);
match unsafe {
libc::readlink(p, buf.as_ptr() as *mut libc::c_char,
len as libc::size_t) as libc::c_int
} {
-1 => Err(super::last_error()),
n => {
assert!(n > 0);
unsafe { buf.set_len(n as uint); }
Ok(buf.as_slice().to_c_str())
}
}
}
pub fn symlink(src: &CString, dst: &CString) -> IoResult<()> {
super::mkerr_libc(unsafe { libc::symlink(src.as_ptr(), dst.as_ptr()) })
}
pub fn link(src: &CString, dst: &CString) -> IoResult<()> {
super::mkerr_libc(unsafe { libc::link(src.as_ptr(), dst.as_ptr()) })
}
fn mkstat(stat: &libc::stat) -> rtio::FileStat {
// FileStat times are in milliseconds
fn mktime(secs: u64, nsecs: u64) -> u64 { secs * 1000 + nsecs / 1000000 }
#[cfg(not(any(target_os = "linux", target_os = "android")))]
fn flags(stat: &libc::stat) -> u64 { stat.st_flags as u64 }
#[cfg(any(target_os = "linux", target_os = "android"))]
fn flags(_stat: &libc::stat) -> u64 { 0 }
#[cfg(not(any(target_os = "linux", target_os = "android")))]
fn gen(stat: &libc::stat) -> u64 { stat.st_gen as u64 }
#[cfg(any(target_os = "linux", target_os = "android"))]
fn gen(_stat: &libc::stat) -> u64 { 0 }
rtio::FileStat {
size: stat.st_size as u64,
kind: stat.st_mode as u64,
perm: stat.st_mode as u64,
created: mktime(stat.st_ctime as u64, stat.st_ctime_nsec as u64),
modified: mktime(stat.st_mtime as u64, stat.st_mtime_nsec as u64),
accessed: mktime(stat.st_atime as u64, stat.st_atime_nsec as u64),
device: stat.st_dev as u64,
inode: stat.st_ino as u64,
rdev: stat.st_rdev as u64,
nlink: stat.st_nlink as u64,
uid: stat.st_uid as u64,
gid: stat.st_gid as u64,
blksize: stat.st_blksize as u64,
blocks: stat.st_blocks as u64,
flags: flags(stat),
gen: gen(stat),
}
}
pub fn stat(p: &CString) -> IoResult<rtio::FileStat> {
let mut stat: libc::stat = unsafe { mem::zeroed() };
match unsafe { libc::stat(p.as_ptr(), &mut stat) } {
0 => Ok(mkstat(&stat)),
_ => Err(super::last_error()),
}
}
pub fn lstat(p: &CString) -> IoResult<rtio::FileStat> {
let mut stat: libc::stat = unsafe { mem::zeroed() };
match unsafe { libc::lstat(p.as_ptr(), &mut stat) } {
0 => Ok(mkstat(&stat)),
_ => Err(super::last_error()),
}
}
pub fn utime(p: &CString, atime: u64, mtime: u64) -> IoResult<()> {
let buf = libc::utimbuf {
actime: (atime / 1000) as libc::time_t,
modtime: (mtime / 1000) as libc::time_t,
};
super::mkerr_libc(unsafe { libc::utime(p.as_ptr(), &buf) })
}
#[cfg(test)]
mod tests {
use super::{CFile, FileDesc};
use libc;
use std::os;
use std::rt::rtio::{RtioFileStream, SeekSet};
#[cfg_attr(target_os = "freebsd", ignore)] // hmm, maybe pipes have a tiny buffer
#[test]
fn test_file_desc() {
// Run this test with some pipes so we don't have to mess around with
// opening or closing files.
let os::Pipe { reader, writer } = unsafe { os::pipe().unwrap() };
let mut reader = FileDesc::new(reader, true);
let mut writer = FileDesc::new(writer, true);
writer.inner_write(b"test").ok().unwrap();
let mut buf = [0u8, ..4];
match reader.inner_read(buf) {
Ok(4) => {
assert_eq!(buf[0], 't' as u8);
assert_eq!(buf[1], 'e' as u8);
assert_eq!(buf[2], 's' as u8);
assert_eq!(buf[3], 't' as u8);
}
r => panic!("invalid read: {}", r),
}
assert!(writer.inner_read(buf).is_err());
assert!(reader.inner_write(buf).is_err());
}
#[test]
fn test_cfile() {
unsafe {
let f = libc::tmpfile();
assert!(!f.is_null());
let mut file = CFile::new(f);
file.write(b"test").ok().unwrap();
let mut buf = [0u8, ..4];
let _ = file.seek(0, SeekSet).ok().unwrap();
match file.read(buf) {
Ok(4) => {
assert_eq!(buf[0], 't' as u8);
assert_eq!(buf[1], 'e' as u8);
assert_eq!(buf[2], 's' as u8);
assert_eq!(buf[3], 't' as u8);
}
r => panic!("invalid read: {}", r)
}
}
}
}

View File

@ -30,7 +30,6 @@ use std::rt::rtio::{mod, IoResult, IoError};
use std::num;
// Local re-exports
pub use self::file::FileDesc;
pub use self::process::Process;
mod helper_thread;
@ -41,13 +40,6 @@ pub mod net;
pub mod process;
mod util;
#[cfg(unix)]
#[path = "file_unix.rs"]
pub mod file;
#[cfg(windows)]
#[path = "file_windows.rs"]
pub mod file;
#[cfg(any(target_os = "macos",
target_os = "ios",
target_os = "freebsd",
@ -92,25 +84,6 @@ fn last_error() -> IoError {
}
}
// unix has nonzero values as errors
fn mkerr_libc <Int: num::Zero>(ret: Int) -> IoResult<()> {
if !ret.is_zero() {
Err(last_error())
} else {
Ok(())
}
}
// windows has zero values as errors
#[cfg(windows)]
fn mkerr_winbool(ret: libc::c_int) -> IoResult<()> {
if ret == 0 {
Err(last_error())
} else {
Ok(())
}
}
#[cfg(windows)]
#[inline]
fn retry<I> (f: || -> I) -> I { f() } // PR rust-lang/rust/#17020
@ -199,62 +172,6 @@ impl rtio::IoFactory for IoFactory {
addrinfo::GetAddrInfoRequest::run(host, servname, hint)
}
// filesystem operations
fn fs_from_raw_fd(&mut self, fd: c_int, close: rtio::CloseBehavior)
-> Box<rtio::RtioFileStream + Send> {
let close = match close {
rtio::CloseSynchronously | rtio::CloseAsynchronously => true,
rtio::DontClose => false
};
box file::FileDesc::new(fd, close) as Box<rtio::RtioFileStream + Send>
}
fn fs_open(&mut self, path: &CString, fm: rtio::FileMode,
fa: rtio::FileAccess)
-> IoResult<Box<rtio::RtioFileStream + Send>>
{
file::open(path, fm, fa).map(|fd| box fd as Box<rtio::RtioFileStream + Send>)
}
fn fs_unlink(&mut self, path: &CString) -> IoResult<()> {
file::unlink(path)
}
fn fs_stat(&mut self, path: &CString) -> IoResult<rtio::FileStat> {
file::stat(path)
}
fn fs_mkdir(&mut self, path: &CString, mode: uint) -> IoResult<()> {
file::mkdir(path, mode)
}
fn fs_chmod(&mut self, path: &CString, mode: uint) -> IoResult<()> {
file::chmod(path, mode)
}
fn fs_rmdir(&mut self, path: &CString) -> IoResult<()> {
file::rmdir(path)
}
fn fs_rename(&mut self, path: &CString, to: &CString) -> IoResult<()> {
file::rename(path, to)
}
fn fs_readdir(&mut self, path: &CString, _flags: c_int) -> IoResult<Vec<CString>> {
file::readdir(path)
}
fn fs_lstat(&mut self, path: &CString) -> IoResult<rtio::FileStat> {
file::lstat(path)
}
fn fs_chown(&mut self, path: &CString, uid: int, gid: int) -> IoResult<()> {
file::chown(path, uid, gid)
}
fn fs_readlink(&mut self, path: &CString) -> IoResult<CString> {
file::readlink(path)
}
fn fs_symlink(&mut self, src: &CString, dst: &CString) -> IoResult<()> {
file::symlink(src, dst)
}
fn fs_link(&mut self, src: &CString, dst: &CString) -> IoResult<()> {
file::link(src, dst)
}
fn fs_utime(&mut self, src: &CString, atime: u64,
mtime: u64) -> IoResult<()> {
file::utime(src, atime, mtime)
}
// misc
fn timer_init(&mut self) -> IoResult<Box<rtio::RtioTimer + Send>> {
timer::Timer::new().map(|t| box t as Box<rtio::RtioTimer + Send>)

View File

@ -56,7 +56,7 @@ use std::sync::atomic;
use std::comm;
use io::c;
use io::file::FileDesc;
use platform_imp::fs::FileDesc;
use io::helper_thread::Helper;
helper_init!(static HELPER: Helper<Req>)

View File

@ -50,20 +50,6 @@ pub trait RemoteCallback {
fn fire(&mut self);
}
/// Description of what to do when a file handle is closed
pub enum CloseBehavior {
/// Do not close this handle when the object is destroyed
DontClose,
/// Synchronously close the handle, meaning that the task will block when
/// the handle is destroyed until it has been fully closed.
CloseSynchronously,
/// Asynchronously closes a handle, meaning that the task will *not* block
/// when the handle is destroyed, but the handle will still get deallocated
/// and cleaned up (but this will happen asynchronously on the local event
/// loop).
CloseAsynchronously,
}
/// Data needed to spawn a process. Serializes the `std::io::process::Command`
/// builder.
pub struct ProcessConfig<'a> {
@ -202,28 +188,6 @@ pub trait IoFactory {
hint: Option<AddrinfoHint>)
-> IoResult<Vec<AddrinfoInfo>>;
// filesystem operations
fn fs_from_raw_fd(&mut self, fd: c_int, close: CloseBehavior)
-> Box<RtioFileStream + Send>;
fn fs_open(&mut self, path: &CString, fm: FileMode, fa: FileAccess)
-> IoResult<Box<RtioFileStream + Send>>;
fn fs_unlink(&mut self, path: &CString) -> IoResult<()>;
fn fs_stat(&mut self, path: &CString) -> IoResult<FileStat>;
fn fs_mkdir(&mut self, path: &CString, mode: uint) -> IoResult<()>;
fn fs_chmod(&mut self, path: &CString, mode: uint) -> IoResult<()>;
fn fs_rmdir(&mut self, path: &CString) -> IoResult<()>;
fn fs_rename(&mut self, path: &CString, to: &CString) -> IoResult<()>;
fn fs_readdir(&mut self, path: &CString, flags: c_int) ->
IoResult<Vec<CString>>;
fn fs_lstat(&mut self, path: &CString) -> IoResult<FileStat>;
fn fs_chown(&mut self, path: &CString, uid: int, gid: int) ->
IoResult<()>;
fn fs_readlink(&mut self, path: &CString) -> IoResult<CString>;
fn fs_symlink(&mut self, src: &CString, dst: &CString) -> IoResult<()>;
fn fs_link(&mut self, src: &CString, dst: &CString) -> IoResult<()>;
fn fs_utime(&mut self, src: &CString, atime: u64, mtime: u64) ->
IoResult<()>;
// misc
fn timer_init(&mut self) -> IoResult<Box<RtioTimer + Send>>;
fn spawn(&mut self, cfg: ProcessConfig)
@ -296,19 +260,6 @@ pub trait RtioTimer {
fn period(&mut self, msecs: u64, cb: Box<Callback + Send>);
}
pub trait RtioFileStream {
fn read(&mut self, buf: &mut [u8]) -> IoResult<int>;
fn write(&mut self, buf: &[u8]) -> IoResult<()>;
fn pread(&mut self, buf: &mut [u8], offset: u64) -> IoResult<int>;
fn pwrite(&mut self, buf: &[u8], offset: u64) -> IoResult<()>;
fn seek(&mut self, pos: i64, whence: SeekStyle) -> IoResult<u64>;
fn tell(&self) -> IoResult<u64>;
fn fsync(&mut self) -> IoResult<()>;
fn datasync(&mut self) -> IoResult<()>;
fn truncate(&mut self, offset: i64) -> IoResult<()>;
fn fstat(&mut self) -> IoResult<FileStat>;
}
pub trait RtioProcess {
fn id(&self) -> libc::pid_t;
fn kill(&mut self, signal: int) -> IoResult<()>;
@ -399,43 +350,6 @@ pub enum ProcessExit {
ExitSignal(int),
}
pub enum FileMode {
Open,
Append,
Truncate,
}
pub enum FileAccess {
Read,
Write,
ReadWrite,
}
pub struct FileStat {
pub size: u64,
pub kind: u64,
pub perm: u64,
pub created: u64,
pub modified: u64,
pub accessed: u64,
pub device: u64,
pub inode: u64,
pub rdev: u64,
pub nlink: u64,
pub uid: u64,
pub gid: u64,
pub blksize: u64,
pub blocks: u64,
pub flags: u64,
pub gen: u64,
}
pub enum SeekStyle {
SeekSet,
SeekEnd,
SeekCur,
}
pub struct AddrinfoHint {
pub family: uint,
pub socktype: uint,

View File

@ -52,28 +52,25 @@ fs::unlink(&path);
*/
use c_str::ToCStr;
use clone::Clone;
use io::standard_error;
use io::{FilePermission, Write, UnstableFileStat, Open, FileAccess, FileMode};
use io::{FilePermission, Write, Open, FileAccess, FileMode};
use io::{IoResult, IoError, FileStat, SeekStyle, Seek, Writer, Reader};
use io::{Read, Truncate, SeekCur, SeekSet, ReadWrite, SeekEnd, Append};
use io::{Read, Truncate, ReadWrite, Append};
use io::UpdateIoError;
use io;
use iter::{Iterator, Extend};
use kinds::Send;
use libc;
use option::{Some, None, Option};
use boxed::Box;
use path::{Path, GenericPath};
use path;
use result::{Err, Ok};
use rt::rtio::LocalIo;
use rt::rtio;
use slice::SlicePrelude;
use string::String;
use vec::Vec;
use sys::fs as fs_imp;
use sys_common;
/// Unconstrained file access type that exposes read and write operations
///
/// Can be constructed via `File::open()`, `File::create()`, and
@ -86,11 +83,17 @@ use vec::Vec;
/// configured at creation time, via the `FileAccess` parameter to
/// `File::open_mode()`.
pub struct File {
fd: Box<rtio::RtioFileStream + Send>,
fd: fs_imp::FileDesc,
path: Path,
last_nread: int,
}
impl sys_common::AsFileDesc for File {
fn as_fd(&self) -> &fs_imp::FileDesc {
&self.fd
}
}
impl File {
/// Open a file at `path` in the mode specified by the `mode` and `access`
/// arguments
@ -133,26 +136,13 @@ impl File {
pub fn open_mode(path: &Path,
mode: FileMode,
access: FileAccess) -> IoResult<File> {
let rtio_mode = match mode {
Open => rtio::Open,
Append => rtio::Append,
Truncate => rtio::Truncate,
};
let rtio_access = match access {
Read => rtio::Read,
Write => rtio::Write,
ReadWrite => rtio::ReadWrite,
};
let err = LocalIo::maybe_raise(|io| {
io.fs_open(&path.to_c_str(), rtio_mode, rtio_access).map(|fd| {
File {
path: path.clone(),
fd: fd,
last_nread: -1
}
})
}).map_err(IoError::from_rtio_error);
err.update_err("couldn't open file", |e| {
fs_imp::open(path, mode, access).map(|fd| {
File {
path: path.clone(),
fd: fd,
last_nread: -1
}
}).update_err("couldn't open file", |e| {
format!("{}; path={}; mode={}; access={}", e, path.display(),
mode_string(mode), access_string(access))
})
@ -194,7 +184,7 @@ impl File {
/// ```
pub fn create(path: &Path) -> IoResult<File> {
File::open_mode(path, Truncate, Write)
.update_desc("couldn't create file")
.update_desc("couldn't create file")
}
/// Returns the original path which was used to open this file.
@ -206,9 +196,9 @@ impl File {
/// device. This will flush any internal buffers necessary to perform this
/// operation.
pub fn fsync(&mut self) -> IoResult<()> {
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()))
self.fd.fsync()
.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
@ -216,9 +206,9 @@ impl File {
/// 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<()> {
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()))
self.fd.datasync()
.update_err("couldn't datasync file",
|e| format!("{}; path={}", e, self.path.display()))
}
/// Either truncates or extends the underlying file, updating the size of
@ -230,10 +220,9 @@ impl File {
/// will be extended to `size` and have all of the intermediate data filled
/// in with 0s.
pub fn truncate(&mut self, size: i64) -> IoResult<()> {
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)
})
self.fd.truncate(size)
.update_err("couldn't truncate file", |e|
format!("{}; path={}; size={}", e, self.path.display(), size))
}
/// Returns true if the stream has reached the end of the file.
@ -251,12 +240,9 @@ impl File {
/// Queries information about the underlying file.
pub fn stat(&mut self) -> IoResult<FileStat> {
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()))
self.fd.fstat()
.update_err("couldn't fstat file", |e|
format!("{}; path={}", e, self.path.display()))
}
}
@ -282,41 +268,9 @@ impl File {
/// user lacks permissions to remove the file, or if some other filesystem-level
/// error occurs.
pub fn unlink(path: &Path) -> IoResult<()> {
return match do_unlink(path) {
Ok(()) => Ok(()),
Err(e) => {
// On unix, a readonly file can be successfully removed. On windows,
// however, it cannot. To keep the two platforms in line with
// respect to their behavior, catch this case on windows, attempt to
// change it to read-write, and then remove the file.
if cfg!(windows) && e.kind == io::PermissionDenied {
let stat = match stat(path) {
Ok(stat) => stat,
Err(..) => return Err(e),
};
if stat.perm.intersects(io::USER_WRITE) { return Err(e) }
match chmod(path, stat.perm | io::USER_WRITE) {
Ok(()) => do_unlink(path),
Err(..) => {
// Try to put it back as we found it
let _ = chmod(path, stat.perm);
Err(e)
}
}
} else {
Err(e)
}
}
};
fn do_unlink(path: &Path) -> IoResult<()> {
let err = LocalIo::maybe_raise(|io| {
io.fs_unlink(&path.to_c_str())
}).map_err(IoError::from_rtio_error);
err.update_err("couldn't unlink path",
|e| format!("{}; path={}", e, path.display()))
}
fs_imp::unlink(path)
.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,
@ -341,12 +295,9 @@ 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> {
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()))
fs_imp::stat(path)
.update_err("couldn't stat path", |e|
format!("{}; path={}", e, path.display()))
}
/// Perform the same operation as the `stat` function, except that this
@ -358,53 +309,9 @@ pub fn stat(path: &Path) -> IoResult<FileStat> {
///
/// See `stat`
pub fn lstat(path: &Path) -> IoResult<FileStat> {
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 {
#[cfg(windows)]
type Mode = libc::c_int;
#[cfg(unix)]
type Mode = libc::mode_t;
let rtio::FileStat {
size, kind, perm, created, modified,
accessed, device, inode, rdev,
nlink, uid, gid, blksize, blocks, flags, gen
} = s;
FileStat {
size: size,
kind: match (kind as Mode) & libc::S_IFMT {
libc::S_IFREG => io::TypeFile,
libc::S_IFDIR => io::TypeDirectory,
libc::S_IFIFO => io::TypeNamedPipe,
libc::S_IFBLK => io::TypeBlockSpecial,
libc::S_IFLNK => io::TypeSymlink,
_ => io::TypeUnknown,
},
perm: FilePermission::from_bits_truncate(perm as u32),
created: created,
modified: modified,
accessed: accessed,
unstable: UnstableFileStat {
device: device,
inode: inode,
rdev: rdev,
nlink: nlink,
uid: uid,
gid: gid,
blksize: blksize,
blocks: blocks,
flags: flags,
gen: gen,
},
}
fs_imp::lstat(path)
.update_err("couldn't lstat path", |e|
format!("{}; path={}", e, path.display()))
}
/// Rename a file or directory to a new name.
@ -424,12 +331,9 @@ fn from_rtio(s: rtio::FileStat) -> FileStat {
/// the process lacks permissions to view the contents, or if some other
/// intermittent I/O error occurs.
pub fn rename(from: &Path, to: &Path) -> IoResult<()> {
let err = LocalIo::maybe_raise(|io| {
io.fs_rename(&from.to_c_str(), &to.to_c_str())
}).map_err(IoError::from_rtio_error);
err.update_err("couldn't rename path", |e| {
format!("{}; from={}; to={}", e, from.display(), to.display())
})
fs_imp::rename(from, to)
.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
@ -462,8 +366,9 @@ pub fn rename(from: &Path, to: &Path) -> IoResult<()> {
/// 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()))
result.update_err("couldn't copy path", |e| {
format!("{}; from={}; to={}", e, from.display(), to.display())
})
}
if !from.is_file() {
@ -512,45 +417,33 @@ pub fn copy(from: &Path, to: &Path) -> IoResult<()> {
/// the process lacks permissions to change the attributes of the file, or if
/// some other I/O error is encountered.
pub fn chmod(path: &Path, mode: io::FilePermission) -> IoResult<()> {
let err = LocalIo::maybe_raise(|io| {
io.fs_chmod(&path.to_c_str(), mode.bits() as uint)
}).map_err(IoError::from_rtio_error);
err.update_err("couldn't chmod path", |e| {
format!("{}; path={}; mode={}", e, path.display(), mode)
})
fs_imp::chmod(path, mode.bits() as uint)
.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<()> {
let err = LocalIo::maybe_raise(|io| {
io.fs_chown(&path.to_c_str(), uid, gid)
}).map_err(IoError::from_rtio_error);
err.update_err("couldn't chown path", |e| {
format!("{}; path={}; uid={}; gid={}", e, path.display(), uid, gid)
})
fs_imp::chown(path, uid, gid)
.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<()> {
let err = LocalIo::maybe_raise(|io| {
io.fs_link(&src.to_c_str(), &dst.to_c_str())
}).map_err(IoError::from_rtio_error);
err.update_err("couldn't link path", |e| {
format!("{}; src={}; dest={}", e, src.display(), dst.display())
})
fs_imp::link(src, dst)
.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<()> {
let err = LocalIo::maybe_raise(|io| {
io.fs_symlink(&src.to_c_str(), &dst.to_c_str())
}).map_err(IoError::from_rtio_error);
err.update_err("couldn't symlink path", |e| {
format!("{}; src={}; dest={}", e, src.display(), dst.display())
})
fs_imp::symlink(src, dst)
.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.
@ -560,11 +453,9 @@ 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> {
let err = LocalIo::maybe_raise(|io| {
Ok(Path::new(try!(io.fs_readlink(&path.to_c_str()))))
}).map_err(IoError::from_rtio_error);
err.update_err("couldn't resolve symlink for path",
|e| format!("{}; path={}", e, path.display()))
fs_imp::readlink(path)
.update_err("couldn't resolve symlink for path", |e|
format!("{}; path={}", e, path.display()))
}
/// Create a new, empty directory at the provided path
@ -585,12 +476,9 @@ pub fn readlink(path: &Path) -> IoResult<Path> {
/// This function 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<()> {
let err = LocalIo::maybe_raise(|io| {
io.fs_mkdir(&path.to_c_str(), mode.bits() as uint)
}).map_err(IoError::from_rtio_error);
err.update_err("couldn't create directory", |e| {
format!("{}; path={}; mode={}", e, path.display(), mode)
})
fs_imp::mkdir(path, mode.bits() as uint)
.update_err("couldn't create directory", |e|
format!("{}; path={}; mode={}", e, path.display(), mode))
}
/// Remove an existing, empty directory
@ -610,11 +498,9 @@ pub fn mkdir(path: &Path, mode: FilePermission) -> IoResult<()> {
/// This function 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<()> {
let err = LocalIo::maybe_raise(|io| {
io.fs_rmdir(&path.to_c_str())
}).map_err(IoError::from_rtio_error);
err.update_err("couldn't remove directory",
|e| format!("{}; path={}", e, path.display()))
fs_imp::rmdir(path)
.update_err("couldn't remove directory", |e|
format!("{}; path={}", e, path.display()))
}
/// Retrieve a vector containing all entries within a provided directory
@ -650,13 +536,9 @@ pub fn rmdir(path: &Path) -> IoResult<()> {
/// the process lacks permissions to view the contents or if the `path` points
/// at a non-directory file
pub fn readdir(path: &Path) -> IoResult<Vec<Path>> {
let err = LocalIo::maybe_raise(|io| {
Ok(try!(io.fs_readdir(&path.to_c_str(), 0)).into_iter().map(|a| {
Path::new(a)
}).collect())
}).map_err(IoError::from_rtio_error);
err.update_err("couldn't read directory",
|e| format!("{}; path={}", e, path.display()))
fs_imp::readdir(path)
.update_err("couldn't read directory",
|e| format!("{}; path={}", e, path.display()))
}
/// Returns an iterator which will recursively walk the directory structure
@ -666,8 +548,7 @@ pub fn readdir(path: &Path) -> IoResult<Vec<Path>> {
pub fn walk_dir(path: &Path) -> IoResult<Directories> {
Ok(Directories {
stack: try!(readdir(path).update_err("couldn't walk directory",
|e| format!("{}; path={}",
e, path.display())))
|e| format!("{}; path={}", e, path.display())))
})
}
@ -681,12 +562,7 @@ impl Iterator<Path> for Directories {
match self.stack.pop() {
Some(path) => {
if path.is_dir() {
let result = readdir(&path)
.update_err("couldn't advance Directories iterator",
|e| format!("{}; path={}",
e, path.display()));
match result {
match readdir(&path) {
Ok(dirs) => { self.stack.extend(dirs.into_iter()); }
Err(..) => {}
}
@ -804,11 +680,9 @@ 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<()> {
let err = LocalIo::maybe_raise(|io| {
io.fs_utime(&path.to_c_str(), atime, mtime)
}).map_err(IoError::from_rtio_error);
err.update_err("couldn't change_file_times",
|e| format!("{}; path={}", e, path.display()))
fs_imp::utime(path, atime, mtime)
.update_err("couldn't change_file_times", |e|
format!("{}; path={}", e, path.display()))
}
impl Reader for File {
@ -819,12 +693,11 @@ impl Reader for File {
e, file.path.display()))
}
let result = update_err(self.fd.read(buf)
.map_err(IoError::from_rtio_error), self);
let result = update_err(self.fd.read(buf), self);
match result {
Ok(read) => {
self.last_nread = read;
self.last_nread = read as int;
match read {
0 => update_err(Err(standard_error(io::EndOfFile)), self),
_ => Ok(read as uint)
@ -837,32 +710,27 @@ impl Reader for File {
impl Writer for File {
fn write(&mut self, buf: &[u8]) -> IoResult<()> {
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()))
self.fd.write(buf)
.update_err("couldn't write to file",
|e| format!("{}; path={}", e, self.path.display()))
}
}
impl Seek for File {
fn tell(&self) -> IoResult<u64> {
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()))
self.fd.tell()
.update_err("couldn't retrieve file cursor (`tell`)",
|e| format!("{}; path={}", e, self.path.display()))
}
fn seek(&mut self, pos: i64, style: SeekStyle) -> IoResult<()> {
let style = match style {
SeekSet => rtio::SeekSet,
SeekCur => rtio::SeekCur,
SeekEnd => rtio::SeekEnd,
};
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(e) => Err(e),
};
err.update_err("couldn't seek in file",
|e| format!("{}; path={}", e, self.path.display()))
@ -942,6 +810,8 @@ fn access_string(access: FileAccess) -> &'static str {
#[cfg(test)]
#[allow(unused_imports)]
#[allow(unused_variables)]
#[allow(unused_mut)]
mod test {
use prelude::*;
use io::{SeekSet, SeekCur, SeekEnd, Read, Open, ReadWrite};

View File

@ -316,6 +316,7 @@ impl IoError {
err.detail = Some(os::error_string(errno).as_slice().chars()
.map(|c| c.to_lowercase()).collect())
}
err
}
/// Retrieve the last error to occur as a (detailed) IoError.

View File

@ -36,11 +36,11 @@ use kinds::Send;
use libc;
use option::{Option, Some, None};
use boxed::Box;
use sys::fs::FileDesc;
use result::{Ok, Err};
use rt;
use rt::local::Local;
use rt::task::Task;
use rt::rtio::{DontClose, IoFactory, LocalIo, RtioFileStream, RtioTTY};
use slice::SlicePrelude;
use str::StrPrelude;
use uint;
@ -75,14 +75,14 @@ use uint;
// case pipe also doesn't work, but magically file does!
enum StdSource {
TTY(Box<RtioTTY + Send>),
File(Box<RtioFileStream + Send>),
File(FileDesc),
}
fn src<T>(fd: libc::c_int, readable: bool, f: |StdSource| -> T) -> T {
LocalIo::maybe_raise(|io| {
Ok(match io.tty_open(fd, readable) {
Ok(tty) => f(TTY(tty)),
Err(_) => f(File(io.fs_from_raw_fd(fd, DontClose))),
Err(_) => f(File(FileDesc::new(fd, false))),
})
}).map_err(IoError::from_rtio_error).unwrap()
}
@ -278,10 +278,10 @@ impl Reader for StdReader {
// print!'d prompt not being shown until after the user hits
// enter.
flush();
tty.read(buf)
tty.read(buf).map_err(IoError::from_rtio_error)
},
File(ref mut file) => file.read(buf).map(|i| i as uint),
}.map_err(IoError::from_rtio_error);
};
match ret {
// When reading a piped stdin, libuv will return 0-length reads when
// stdin reaches EOF. For pretty much all other streams it will
@ -372,9 +372,9 @@ impl Writer for StdWriter {
let max_size = if cfg!(windows) {8192} else {uint::MAX};
for chunk in buf.chunks(max_size) {
try!(match self.inner {
TTY(ref mut tty) => tty.write(chunk),
TTY(ref mut tty) => tty.write(chunk).map_err(IoError::from_rtio_error),
File(ref mut file) => file.write(chunk),
}.map_err(IoError::from_rtio_error))
})
}
Ok(())
}

View File

@ -0,0 +1,411 @@
// Copyright 2013-2014 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
//! Blocking posix-based file I/O
use libc::{mod, c_int, c_void};
use c_str::CString;
use mem;
use io;
use prelude::*;
use io::{FilePermission, Write, UnstableFileStat, Open, FileAccess, FileMode};
use io::{IoResult, FileStat, SeekStyle, Reader};
use io::{Read, Truncate, SeekCur, SeekSet, ReadWrite, SeekEnd, Append};
use result::{Ok, Err};
use sys::retry;
use sys_common::{keep_going, eof, mkerr_libc};
pub use path::PosixPath as Path;
pub type fd_t = libc::c_int;
pub struct FileDesc {
/// The underlying C file descriptor.
fd: fd_t,
/// Whether to close the file descriptor on drop.
close_on_drop: bool,
}
impl FileDesc {
pub fn new(fd: fd_t, close_on_drop: bool) -> FileDesc {
FileDesc { fd: fd, close_on_drop: close_on_drop }
}
pub fn read(&self, buf: &mut [u8]) -> IoResult<uint> {
let ret = retry(|| unsafe {
libc::read(self.fd(),
buf.as_mut_ptr() as *mut libc::c_void,
buf.len() as libc::size_t)
});
if ret == 0 {
Err(eof())
} else if ret < 0 {
Err(super::last_error())
} else {
Ok(ret as uint)
}
}
pub fn write(&self, buf: &[u8]) -> IoResult<()> {
let ret = keep_going(buf, |buf, len| {
unsafe {
libc::write(self.fd(), buf as *const libc::c_void,
len as libc::size_t) as i64
}
});
if ret < 0 {
Err(super::last_error())
} else {
Ok(())
}
}
pub fn fd(&self) -> fd_t { self.fd }
pub fn seek(&self, pos: i64, whence: SeekStyle) -> IoResult<u64> {
let whence = match whence {
SeekSet => libc::SEEK_SET,
SeekEnd => libc::SEEK_END,
SeekCur => libc::SEEK_CUR,
};
let n = unsafe { libc::lseek(self.fd(), pos as libc::off_t, whence) };
if n < 0 {
Err(super::last_error())
} else {
Ok(n as u64)
}
}
pub fn tell(&self) -> IoResult<u64> {
let n = unsafe { libc::lseek(self.fd(), 0, libc::SEEK_CUR) };
if n < 0 {
Err(super::last_error())
} else {
Ok(n as u64)
}
}
pub fn fsync(&self) -> IoResult<()> {
mkerr_libc(retry(|| unsafe { libc::fsync(self.fd()) }))
}
pub fn datasync(&self) -> IoResult<()> {
return mkerr_libc(os_datasync(self.fd()));
#[cfg(any(target_os = "macos", target_os = "ios"))]
fn os_datasync(fd: c_int) -> c_int {
unsafe { libc::fcntl(fd, libc::F_FULLFSYNC) }
}
#[cfg(target_os = "linux")]
fn os_datasync(fd: c_int) -> c_int {
retry(|| unsafe { libc::fdatasync(fd) })
}
#[cfg(not(any(target_os = "macos", target_os = "ios", target_os = "linux")))]
fn os_datasync(fd: c_int) -> c_int {
retry(|| unsafe { libc::fsync(fd) })
}
}
pub fn truncate(&self, offset: i64) -> IoResult<()> {
mkerr_libc(retry(|| unsafe {
libc::ftruncate(self.fd(), offset as libc::off_t)
}))
}
pub fn fstat(&self) -> IoResult<FileStat> {
let mut stat: libc::stat = unsafe { mem::zeroed() };
match unsafe { libc::fstat(self.fd(), &mut stat) } {
0 => Ok(mkstat(&stat)),
_ => Err(super::last_error()),
}
}
/// Extract the actual filedescriptor without closing it.
pub fn unwrap(self) -> fd_t {
let fd = self.fd;
unsafe { mem::forget(self) };
fd
}
}
/*
impl RtioTTY for FileDesc {
fn read(&mut self, buf: &mut [u8]) -> IoResult<uint> {
self.inner_read(buf)
}
fn write(&mut self, buf: &[u8]) -> IoResult<()> {
self.inner_write(buf)
}
fn set_raw(&mut self, _raw: bool) -> IoResult<()> {
Err(super::unimpl())
}
fn get_winsize(&mut self) -> IoResult<(int, int)> {
Err(super::unimpl())
}
fn isatty(&self) -> bool { false }
}
*/
impl Drop for FileDesc {
fn drop(&mut self) {
// closing stdio file handles makes no sense, so never do it. Also, note
// that errors are ignored when closing a file descriptor. The reason
// for this is that if an error occurs we don't actually know if the
// file descriptor was closed or not, and if we retried (for something
// like EINTR), we might close another valid file descriptor (opened
// after we closed ours.
if self.close_on_drop && self.fd > libc::STDERR_FILENO {
let n = unsafe { libc::close(self.fd) };
if n != 0 {
println!("error {} when closing file descriptor {}", n, self.fd);
}
}
}
}
pub fn open(path: &Path, fm: FileMode, fa: FileAccess) -> IoResult<FileDesc> {
let flags = match fm {
Open => 0,
Append => libc::O_APPEND,
Truncate => libc::O_TRUNC,
};
// Opening with a write permission must silently create the file.
let (flags, mode) = match fa {
Read => (flags | libc::O_RDONLY, 0),
Write => (flags | libc::O_WRONLY | libc::O_CREAT,
libc::S_IRUSR | libc::S_IWUSR),
ReadWrite => (flags | libc::O_RDWR | libc::O_CREAT,
libc::S_IRUSR | libc::S_IWUSR),
};
let path = path.to_c_str();
match retry(|| unsafe { libc::open(path.as_ptr(), flags, mode) }) {
-1 => Err(super::last_error()),
fd => Ok(FileDesc::new(fd, true)),
}
}
pub fn mkdir(p: &Path, mode: uint) -> IoResult<()> {
let p = p.to_c_str();
mkerr_libc(unsafe { libc::mkdir(p.as_ptr(), mode as libc::mode_t) })
}
pub fn readdir(p: &Path) -> IoResult<Vec<Path>> {
use libc::{dirent_t};
use libc::{opendir, readdir_r, closedir};
fn prune(root: &CString, dirs: Vec<Path>) -> Vec<Path> {
let root = unsafe { CString::new(root.as_ptr(), false) };
let root = Path::new(root);
dirs.into_iter().filter(|path| {
path.as_vec() != b"." && path.as_vec() != b".."
}).map(|path| root.join(path)).collect()
}
extern {
fn rust_dirent_t_size() -> libc::c_int;
fn rust_list_dir_val(ptr: *mut dirent_t) -> *const libc::c_char;
}
let size = unsafe { rust_dirent_t_size() };
let mut buf = Vec::<u8>::with_capacity(size as uint);
let ptr = buf.as_mut_slice().as_mut_ptr() as *mut dirent_t;
let p = p.to_c_str();
let dir_ptr = unsafe {opendir(p.as_ptr())};
if dir_ptr as uint != 0 {
let mut paths = vec!();
let mut entry_ptr = 0 as *mut dirent_t;
while unsafe { readdir_r(dir_ptr, ptr, &mut entry_ptr) == 0 } {
if entry_ptr.is_null() { break }
let cstr = unsafe {
CString::new(rust_list_dir_val(entry_ptr), false)
};
paths.push(Path::new(cstr));
}
assert_eq!(unsafe { closedir(dir_ptr) }, 0);
Ok(prune(&p, paths))
} else {
Err(super::last_error())
}
}
pub fn unlink(p: &Path) -> IoResult<()> {
let p = p.to_c_str();
mkerr_libc(unsafe { libc::unlink(p.as_ptr()) })
}
pub fn rename(old: &Path, new: &Path) -> IoResult<()> {
let old = old.to_c_str();
let new = new.to_c_str();
mkerr_libc(unsafe {
libc::rename(old.as_ptr(), new.as_ptr())
})
}
pub fn chmod(p: &Path, mode: uint) -> IoResult<()> {
let p = p.to_c_str();
mkerr_libc(retry(|| unsafe {
libc::chmod(p.as_ptr(), mode as libc::mode_t)
}))
}
pub fn rmdir(p: &Path) -> IoResult<()> {
let p = p.to_c_str();
mkerr_libc(unsafe { libc::rmdir(p.as_ptr()) })
}
pub fn chown(p: &Path, uid: int, gid: int) -> IoResult<()> {
let p = p.to_c_str();
mkerr_libc(retry(|| unsafe {
libc::chown(p.as_ptr(), uid as libc::uid_t, gid as libc::gid_t)
}))
}
pub fn readlink(p: &Path) -> IoResult<Path> {
let c_path = p.to_c_str();
let p = c_path.as_ptr();
let mut len = unsafe { libc::pathconf(p as *mut _, libc::_PC_NAME_MAX) };
if len == -1 {
len = 1024; // FIXME: read PATH_MAX from C ffi?
}
let mut buf: Vec<u8> = Vec::with_capacity(len as uint);
match unsafe {
libc::readlink(p, buf.as_ptr() as *mut libc::c_char,
len as libc::size_t) as libc::c_int
} {
-1 => Err(super::last_error()),
n => {
assert!(n > 0);
unsafe { buf.set_len(n as uint); }
Ok(Path::new(buf))
}
}
}
pub fn symlink(src: &Path, dst: &Path) -> IoResult<()> {
let src = src.to_c_str();
let dst = dst.to_c_str();
mkerr_libc(unsafe { libc::symlink(src.as_ptr(), dst.as_ptr()) })
}
pub fn link(src: &Path, dst: &Path) -> IoResult<()> {
let src = src.to_c_str();
let dst = dst.to_c_str();
mkerr_libc(unsafe { libc::link(src.as_ptr(), dst.as_ptr()) })
}
fn mkstat(stat: &libc::stat) -> FileStat {
// FileStat times are in milliseconds
fn mktime(secs: u64, nsecs: u64) -> u64 { secs * 1000 + nsecs / 1000000 }
#[cfg(not(any(target_os = "linux", target_os = "android")))]
fn flags(stat: &libc::stat) -> u64 { stat.st_flags as u64 }
#[cfg(any(target_os = "linux", target_os = "android"))]
fn flags(_stat: &libc::stat) -> u64 { 0 }
#[cfg(not(any(target_os = "linux", target_os = "android")))]
fn gen(stat: &libc::stat) -> u64 { stat.st_gen as u64 }
#[cfg(any(target_os = "linux", target_os = "android"))]
fn gen(_stat: &libc::stat) -> u64 { 0 }
FileStat {
size: stat.st_size as u64,
kind: match (stat.st_mode as libc::mode_t) & libc::S_IFMT {
libc::S_IFREG => io::TypeFile,
libc::S_IFDIR => io::TypeDirectory,
libc::S_IFIFO => io::TypeNamedPipe,
libc::S_IFBLK => io::TypeBlockSpecial,
libc::S_IFLNK => io::TypeSymlink,
_ => io::TypeUnknown,
},
perm: FilePermission::from_bits_truncate(stat.st_mode as u32),
created: mktime(stat.st_ctime as u64, stat.st_ctime_nsec as u64),
modified: mktime(stat.st_mtime as u64, stat.st_mtime_nsec as u64),
accessed: mktime(stat.st_atime as u64, stat.st_atime_nsec as u64),
unstable: UnstableFileStat {
device: stat.st_dev as u64,
inode: stat.st_ino as u64,
rdev: stat.st_rdev as u64,
nlink: stat.st_nlink as u64,
uid: stat.st_uid as u64,
gid: stat.st_gid as u64,
blksize: stat.st_blksize as u64,
blocks: stat.st_blocks as u64,
flags: flags(stat),
gen: gen(stat),
},
}
}
pub fn stat(p: &Path) -> IoResult<FileStat> {
let p = p.to_c_str();
let mut stat: libc::stat = unsafe { mem::zeroed() };
match unsafe { libc::stat(p.as_ptr(), &mut stat) } {
0 => Ok(mkstat(&stat)),
_ => Err(super::last_error()),
}
}
pub fn lstat(p: &Path) -> IoResult<FileStat> {
let p = p.to_c_str();
let mut stat: libc::stat = unsafe { mem::zeroed() };
match unsafe { libc::lstat(p.as_ptr(), &mut stat) } {
0 => Ok(mkstat(&stat)),
_ => Err(super::last_error()),
}
}
pub fn utime(p: &Path, atime: u64, mtime: u64) -> IoResult<()> {
let p = p.to_c_str();
let buf = libc::utimbuf {
actime: (atime / 1000) as libc::time_t,
modtime: (mtime / 1000) as libc::time_t,
};
mkerr_libc(unsafe { libc::utime(p.as_ptr(), &buf) })
}
#[cfg(test)]
mod tests {
use super::FileDesc;
use libc;
use os;
use prelude::*;
#[cfg_attr(target_os = "freebsd", ignore)] // hmm, maybe pipes have a tiny buffer
#[test]
fn test_file_desc() {
// Run this test with some pipes so we don't have to mess around with
// opening or closing files.
let os::Pipe { reader, writer } = unsafe { os::pipe().unwrap() };
let mut reader = FileDesc::new(reader, true);
let mut writer = FileDesc::new(writer, true);
writer.write(b"test").ok().unwrap();
let mut buf = [0u8, ..4];
match reader.read(buf) {
Ok(4) => {
assert_eq!(buf[0], 't' as u8);
assert_eq!(buf[1], 'e' as u8);
assert_eq!(buf[2], 's' as u8);
assert_eq!(buf[3], 't' as u8);
}
r => panic!("invalid read: {}", r),
}
assert!(writer.read(buf).is_err());
assert!(reader.write(buf).is_err());
}
}

View File

@ -1,4 +1,4 @@
// Copyright 2013-2014 The Rust Project Developers. See the COPYRIGHT
// Copyright 2014 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
@ -12,42 +12,40 @@
use alloc::arc::Arc;
use libc::{mod, c_int};
use std::c_str::CString;
use std::mem;
use std::os::windows::fill_utf16_buf_and_decode;
use std::ptr;
use std::rt::rtio;
use std::rt::rtio::{IoResult, IoError};
use std::str;
use c_str::CString;
use mem;
use os::windows::fill_utf16_buf_and_decode;
use path;
use ptr;
use str;
use io;
use prelude::*;
use sys;
use sys_common::{keep_going, eof, mkerr_libc};
use io::{FilePermission, Write, UnstableFileStat, Open, FileAccess, FileMode};
use io::{IoResult, IoError, FileStat, SeekStyle, Seek, Writer, Reader};
use io::{Read, Truncate, SeekCur, SeekSet, ReadWrite, SeekEnd, Append};
pub use path::WindowsPath as Path;
pub type fd_t = libc::c_int;
struct Inner {
fd: fd_t,
pub struct FileDesc {
/// The underlying C file descriptor.
pub fd: fd_t,
/// Whether to close the file descriptor on drop.
close_on_drop: bool,
}
pub struct FileDesc {
inner: Arc<Inner>
}
impl FileDesc {
/// Create a `FileDesc` from an open C file descriptor.
///
/// The `FileDesc` will take ownership of the specified file descriptor and
/// close it upon destruction if the `close_on_drop` flag is true, otherwise
/// it will not close the file descriptor when this `FileDesc` is dropped.
///
/// Note that all I/O operations done on this object will be *blocking*, but
/// they do not require the runtime to be active.
pub fn new(fd: fd_t, close_on_drop: bool) -> FileDesc {
FileDesc { inner: Arc::new(Inner {
fd: fd,
close_on_drop: close_on_drop
}) }
FileDesc { fd: fd, close_on_drop: close_on_drop }
}
pub fn inner_read(&mut self, buf: &mut [u8]) -> IoResult<uint> {
pub fn read(&self, buf: &mut [u8]) -> IoResult<uint> {
let mut read = 0;
let ret = unsafe {
libc::ReadFile(self.handle(), buf.as_ptr() as libc::LPVOID,
@ -60,7 +58,8 @@ impl FileDesc {
Err(super::last_error())
}
}
pub fn inner_write(&mut self, buf: &[u8]) -> IoResult<()> {
pub fn write(&self, buf: &[u8]) -> IoResult<()> {
let mut cur = buf.as_ptr();
let mut remaining = buf.len();
while remaining > 0 {
@ -80,7 +79,7 @@ impl FileDesc {
Ok(())
}
pub fn fd(&self) -> fd_t { self.inner.fd }
pub fn fd(&self) -> fd_t { self.fd }
pub fn handle(&self) -> libc::HANDLE {
unsafe { libc::get_osfhandle(self.fd()) as libc::HANDLE }
@ -88,153 +87,67 @@ impl FileDesc {
// A version of seek that takes &self so that tell can call it
// - the private seek should of course take &mut self.
fn seek_common(&self, pos: i64, style: rtio::SeekStyle) -> IoResult<u64> {
fn seek_common(&self, pos: i64, style: SeekStyle) -> IoResult<u64> {
let whence = match style {
rtio::SeekSet => libc::FILE_BEGIN,
rtio::SeekEnd => libc::FILE_END,
rtio::SeekCur => libc::FILE_CURRENT,
SeekSet => libc::FILE_BEGIN,
SeekEnd => libc::FILE_END,
SeekCur => libc::FILE_CURRENT,
};
unsafe {
let mut newpos = 0;
match libc::SetFilePointerEx(self.handle(), pos, &mut newpos,
whence) {
match libc::SetFilePointerEx(self.handle(), pos, &mut newpos, whence) {
0 => Err(super::last_error()),
_ => Ok(newpos as u64),
}
}
}
}
impl rtio::RtioFileStream for FileDesc {
fn read(&mut self, buf: &mut [u8]) -> IoResult<int> {
self.inner_read(buf).map(|i| i as int)
}
fn write(&mut self, buf: &[u8]) -> IoResult<()> {
self.inner_write(buf)
}
fn pread(&mut self, buf: &mut [u8], offset: u64) -> IoResult<int> {
let mut read = 0;
let mut overlap: libc::OVERLAPPED = unsafe { mem::zeroed() };
overlap.Offset = offset as libc::DWORD;
overlap.OffsetHigh = (offset >> 32) as libc::DWORD;
let ret = unsafe {
libc::ReadFile(self.handle(), buf.as_ptr() as libc::LPVOID,
buf.len() as libc::DWORD, &mut read,
&mut overlap)
};
if ret != 0 {
Ok(read as int)
} else {
Err(super::last_error())
}
}
fn pwrite(&mut self, buf: &[u8], mut offset: u64) -> IoResult<()> {
let mut cur = buf.as_ptr();
let mut remaining = buf.len();
let mut overlap: libc::OVERLAPPED = unsafe { mem::zeroed() };
while remaining > 0 {
overlap.Offset = offset as libc::DWORD;
overlap.OffsetHigh = (offset >> 32) as libc::DWORD;
let mut amt = 0;
let ret = unsafe {
libc::WriteFile(self.handle(), cur as libc::LPVOID,
remaining as libc::DWORD, &mut amt,
&mut overlap)
};
if ret != 0 {
remaining -= amt as uint;
cur = unsafe { cur.offset(amt as int) };
offset += amt as u64;
} else {
return Err(super::last_error())
}
}
Ok(())
}
fn seek(&mut self, pos: i64, style: rtio::SeekStyle) -> IoResult<u64> {
pub fn seek(&mut self, pos: i64, style: SeekStyle) -> IoResult<u64> {
self.seek_common(pos, style)
}
fn tell(&self) -> IoResult<u64> {
self.seek_common(0, rtio::SeekCur)
pub fn tell(&self) -> IoResult<u64> {
self.seek_common(0, SeekCur)
}
fn fsync(&mut self) -> IoResult<()> {
pub fn fsync(&mut self) -> IoResult<()> {
super::mkerr_winbool(unsafe {
libc::FlushFileBuffers(self.handle())
})
}
fn datasync(&mut self) -> IoResult<()> { return self.fsync(); }
pub fn datasync(&mut self) -> IoResult<()> { return self.fsync(); }
fn truncate(&mut self, offset: i64) -> IoResult<()> {
pub fn truncate(&mut self, offset: i64) -> IoResult<()> {
let orig_pos = try!(self.tell());
let _ = try!(self.seek(offset, rtio::SeekSet));
let _ = try!(self.seek(offset, SeekSet));
let ret = unsafe {
match libc::SetEndOfFile(self.handle()) {
0 => Err(super::last_error()),
_ => Ok(())
}
};
let _ = self.seek(orig_pos as i64, rtio::SeekSet);
let _ = self.seek(orig_pos as i64, SeekSet);
return ret;
}
fn fstat(&mut self) -> IoResult<rtio::FileStat> {
pub fn fstat(&mut self) -> IoResult<io::FileStat> {
let mut stat: libc::stat = unsafe { mem::zeroed() };
match unsafe { libc::fstat(self.fd(), &mut stat) } {
0 => Ok(mkstat(&stat)),
_ => Err(super::last_error()),
}
}
/// Extract the actual filedescriptor without closing it.
pub fn unwrap(self) -> fd_t {
let fd = self.fd;
unsafe { mem::forget(self) };
fd
}
}
impl rtio::RtioPipe for FileDesc {
fn read(&mut self, buf: &mut [u8]) -> IoResult<uint> {
self.inner_read(buf)
}
fn write(&mut self, buf: &[u8]) -> IoResult<()> {
self.inner_write(buf)
}
fn clone(&self) -> Box<rtio::RtioPipe + Send> {
box FileDesc { inner: self.inner.clone() } as Box<rtio::RtioPipe + Send>
}
// Only supported on named pipes currently. Note that this doesn't have an
// impact on the std::io primitives, this is never called via
// std::io::PipeStream. If the functionality is exposed in the future, then
// these methods will need to be implemented.
fn close_read(&mut self) -> IoResult<()> {
Err(super::unimpl())
}
fn close_write(&mut self) -> IoResult<()> {
Err(super::unimpl())
}
fn set_timeout(&mut self, _t: Option<u64>) {}
fn set_read_timeout(&mut self, _t: Option<u64>) {}
fn set_write_timeout(&mut self, _t: Option<u64>) {}
}
impl rtio::RtioTTY for FileDesc {
fn read(&mut self, buf: &mut [u8]) -> IoResult<uint> {
self.inner_read(buf)
}
fn write(&mut self, buf: &[u8]) -> IoResult<()> {
self.inner_write(buf)
}
fn set_raw(&mut self, _raw: bool) -> IoResult<()> {
Err(super::unimpl())
}
fn get_winsize(&mut self) -> IoResult<(int, int)> {
Err(super::unimpl())
}
fn isatty(&self) -> bool { false }
}
impl Drop for Inner {
impl Drop for FileDesc {
fn drop(&mut self) {
// closing stdio file handles makes no sense, so never do it. Also, note
// that errors are ignored when closing a file descriptor. The reason
@ -251,39 +164,26 @@ impl Drop for Inner {
}
}
pub fn to_utf16(s: &CString) -> IoResult<Vec<u16>> {
match s.as_str() {
Some(s) => Ok({
let mut s = s.utf16_units().collect::<Vec<u16>>();
s.push(0);
s
}),
None => Err(IoError {
code: libc::ERROR_INVALID_NAME as uint,
extra: 0,
detail: Some("valid unicode input required".to_string()),
})
}
pub fn to_utf16(s: &Path) -> IoResult<Vec<u16>> {
sys::to_utf16(s.as_str())
}
pub fn open(path: &CString, fm: rtio::FileMode, fa: rtio::FileAccess)
-> IoResult<FileDesc> {
pub fn open(path: &Path, fm: FileMode, fa: FileAccess) -> IoResult<FileDesc> {
// Flags passed to open_osfhandle
let flags = match fm {
rtio::Open => 0,
rtio::Append => libc::O_APPEND,
rtio::Truncate => libc::O_TRUNC,
Open => 0,
Append => libc::O_APPEND,
Truncate => libc::O_TRUNC,
};
let flags = match fa {
rtio::Read => flags | libc::O_RDONLY,
rtio::Write => flags | libc::O_WRONLY | libc::O_CREAT,
rtio::ReadWrite => flags | libc::O_RDWR | libc::O_CREAT,
Read => flags | libc::O_RDONLY,
Write => flags | libc::O_WRONLY | libc::O_CREAT,
ReadWrite => flags | libc::O_RDWR | libc::O_CREAT,
};
let mut dwDesiredAccess = match fa {
rtio::Read => libc::FILE_GENERIC_READ,
rtio::Write => libc::FILE_GENERIC_WRITE,
rtio::ReadWrite => libc::FILE_GENERIC_READ | libc::FILE_GENERIC_WRITE
Read => libc::FILE_GENERIC_READ,
Write => libc::FILE_GENERIC_WRITE,
ReadWrite => libc::FILE_GENERIC_READ | libc::FILE_GENERIC_WRITE
};
// libuv has a good comment about this, but the basic idea is what we try to
@ -293,15 +193,15 @@ pub fn open(path: &CString, fm: rtio::FileMode, fa: rtio::FileAccess)
libc::FILE_SHARE_DELETE;
let dwCreationDisposition = match (fm, fa) {
(rtio::Truncate, rtio::Read) => libc::TRUNCATE_EXISTING,
(rtio::Truncate, _) => libc::CREATE_ALWAYS,
(rtio::Open, rtio::Read) => libc::OPEN_EXISTING,
(rtio::Open, _) => libc::OPEN_ALWAYS,
(rtio::Append, rtio::Read) => {
(Truncate, Read) => libc::TRUNCATE_EXISTING,
(Truncate, _) => libc::CREATE_ALWAYS,
(Open, Read) => libc::OPEN_EXISTING,
(Open, _) => libc::OPEN_ALWAYS,
(Append, Read) => {
dwDesiredAccess |= libc::FILE_APPEND_DATA;
libc::OPEN_EXISTING
}
(rtio::Append, _) => {
(Append, _) => {
dwDesiredAccess &= !libc::FILE_WRITE_DATA;
dwDesiredAccess |= libc::FILE_APPEND_DATA;
libc::OPEN_ALWAYS
@ -337,7 +237,7 @@ pub fn open(path: &CString, fm: rtio::FileMode, fa: rtio::FileAccess)
}
}
pub fn mkdir(p: &CString, _mode: uint) -> IoResult<()> {
pub fn mkdir(p: &Path, _mode: uint) -> IoResult<()> {
let p = try!(to_utf16(p));
super::mkerr_winbool(unsafe {
// FIXME: turn mode into something useful? #2623
@ -345,20 +245,15 @@ pub fn mkdir(p: &CString, _mode: uint) -> IoResult<()> {
})
}
pub fn readdir(p: &CString) -> IoResult<Vec<CString>> {
fn prune(root: &CString, dirs: Vec<Path>) -> Vec<CString> {
let root = unsafe { CString::new(root.as_ptr(), false) };
let root = Path::new(root);
pub fn readdir(p: &Path) -> IoResult<Vec<Path>> {
fn prune(root: &Path, dirs: Vec<Path>) -> Vec<Path> {
dirs.into_iter().filter(|path| {
path.as_vec() != b"." && path.as_vec() != b".."
}).map(|path| root.join(path).to_c_str()).collect()
}).map(|path| root.join(path)).collect()
}
let star = Path::new(unsafe {
CString::new(p.as_ptr(), false)
}).join("*");
let path = try!(to_utf16(&star.to_c_str()));
let star = p.join("*");
let path = try!(to_utf16(&star));
unsafe {
let mut wfd = mem::zeroed();
@ -374,8 +269,8 @@ pub fn readdir(p: &CString) -> IoResult<Vec<CString>> {
None => {
assert!(libc::FindClose(find_handle) != 0);
return Err(IoError {
code: super::c::ERROR_ILLEGAL_CHARACTER as uint,
extra: 0,
kind: io::InvalidInput,
desc: "path was not valid UTF-16",
detail: Some(format!("path was not valid UTF-16: {}", filename)),
})
}, // FIXME #12056: Convert the UCS-2 to invalid utf-8 instead of erroring
@ -391,42 +286,74 @@ pub fn readdir(p: &CString) -> IoResult<Vec<CString>> {
}
}
pub fn unlink(p: &CString) -> IoResult<()> {
let p = try!(to_utf16(p));
super::mkerr_winbool(unsafe {
libc::DeleteFileW(p.as_ptr())
})
pub fn unlink(p: &Path) -> IoResult<()> {
fn do_unlink(p_utf16: &Vec<u16>) -> IoResult<()> {
super::mkerr_winbool(unsafe { libc::DeleteFileW(p_utf16.as_ptr()) })
}
let p_utf16 = try!(to_utf16(p));
let res = do_unlink(&p_utf16);
match res {
Ok(()) => Ok(()),
Err(e) => {
// FIXME: change the code below to use more direct calls
// than `stat` and `chmod`, to avoid re-conversion to
// utf16 etc.
// On unix, a readonly file can be successfully removed. On windows,
// however, it cannot. To keep the two platforms in line with
// respect to their behavior, catch this case on windows, attempt to
// change it to read-write, and then remove the file.
if e.kind == io::PermissionDenied {
let stat = match stat(p) {
Ok(stat) => stat,
Err(..) => return Err(e),
};
if stat.perm.intersects(io::USER_WRITE) { return Err(e) }
match chmod(p, (stat.perm | io::USER_WRITE).bits() as uint) {
Ok(()) => do_unlink(&p_utf16),
Err(..) => {
// Try to put it back as we found it
let _ = chmod(p, stat.perm.bits() as uint);
Err(e)
}
}
} else {
Err(e)
}
}
}
}
pub fn rename(old: &CString, new: &CString) -> IoResult<()> {
pub fn rename(old: &Path, new: &Path) -> IoResult<()> {
let old = try!(to_utf16(old));
let new = try!(to_utf16(new));
super::mkerr_winbool(unsafe {
libc::MoveFileExW(old.as_ptr(), new.as_ptr(),
libc::MOVEFILE_REPLACE_EXISTING)
libc::MoveFileExW(old.as_ptr(), new.as_ptr(), libc::MOVEFILE_REPLACE_EXISTING)
})
}
pub fn chmod(p: &CString, mode: uint) -> IoResult<()> {
pub fn chmod(p: &Path, mode: uint) -> IoResult<()> {
let p = try!(to_utf16(p));
super::mkerr_libc(unsafe {
mkerr_libc(unsafe {
libc::wchmod(p.as_ptr(), mode as libc::c_int)
})
}
pub fn rmdir(p: &CString) -> IoResult<()> {
pub fn rmdir(p: &Path) -> IoResult<()> {
let p = try!(to_utf16(p));
super::mkerr_libc(unsafe { libc::wrmdir(p.as_ptr()) })
mkerr_libc(unsafe { libc::wrmdir(p.as_ptr()) })
}
pub fn chown(_p: &CString, _uid: int, _gid: int) -> IoResult<()> {
pub fn chown(_p: &Path, _uid: int, _gid: int) -> IoResult<()> {
// libuv has this as a no-op, so seems like this should as well?
Ok(())
}
pub fn readlink(p: &CString) -> IoResult<CString> {
pub fn readlink(p: &Path) -> IoResult<Path> {
// FIXME: I have a feeling that this reads intermediate symlinks as well.
use io::c::compat::kernel32::GetFinalPathNameByHandleW;
use sys::c::compat::kernel32::GetFinalPathNameByHandleW;
let p = try!(to_utf16(p));
let handle = unsafe {
libc::CreateFileW(p.as_ptr(),
@ -449,18 +376,18 @@ pub fn readlink(p: &CString) -> IoResult<CString> {
libc::VOLUME_NAME_DOS)
});
let ret = match ret {
Some(ref s) if s.as_slice().starts_with(r"\\?\") => {
Ok(Path::new(s.as_slice().slice_from(4)).to_c_str())
Some(ref s) if s.as_slice().starts_with(r"\\?\") => { // "
Ok(Path::new(s.as_slice().slice_from(4)))
}
Some(s) => Ok(Path::new(s).to_c_str()),
Some(s) => Ok(Path::new(s)),
None => Err(super::last_error()),
};
assert!(unsafe { libc::CloseHandle(handle) } != 0);
return ret;
}
pub fn symlink(src: &CString, dst: &CString) -> IoResult<()> {
use io::c::compat::kernel32::CreateSymbolicLinkW;
pub fn symlink(src: &Path, dst: &Path) -> IoResult<()> {
use sys::c::compat::kernel32::CreateSymbolicLinkW;
let src = try!(to_utf16(src));
let dst = try!(to_utf16(dst));
super::mkerr_winbool(unsafe {
@ -468,7 +395,7 @@ pub fn symlink(src: &CString, dst: &CString) -> IoResult<()> {
})
}
pub fn link(src: &CString, dst: &CString) -> IoResult<()> {
pub fn link(src: &Path, dst: &Path) -> IoResult<()> {
let src = try!(to_utf16(src));
let dst = try!(to_utf16(dst));
super::mkerr_winbool(unsafe {
@ -476,28 +403,37 @@ pub fn link(src: &CString, dst: &CString) -> IoResult<()> {
})
}
fn mkstat(stat: &libc::stat) -> rtio::FileStat {
rtio::FileStat {
fn mkstat(stat: &libc::stat) -> FileStat {
FileStat {
size: stat.st_size as u64,
kind: stat.st_mode as u64,
perm: stat.st_mode as u64,
kind: match (stat.st_mode as libc::c_int) & libc::S_IFMT {
libc::S_IFREG => io::TypeFile,
libc::S_IFDIR => io::TypeDirectory,
libc::S_IFIFO => io::TypeNamedPipe,
libc::S_IFBLK => io::TypeBlockSpecial,
libc::S_IFLNK => io::TypeSymlink,
_ => io::TypeUnknown,
},
perm: FilePermission::from_bits_truncate(stat.st_mode as u32),
created: stat.st_ctime as u64,
modified: stat.st_mtime as u64,
accessed: stat.st_atime as u64,
device: stat.st_dev as u64,
inode: stat.st_ino as u64,
rdev: stat.st_rdev as u64,
nlink: stat.st_nlink as u64,
uid: stat.st_uid as u64,
gid: stat.st_gid as u64,
blksize: 0,
blocks: 0,
flags: 0,
gen: 0,
unstable: UnstableFileStat {
device: stat.st_dev as u64,
inode: stat.st_ino as u64,
rdev: stat.st_rdev as u64,
nlink: stat.st_nlink as u64,
uid: stat.st_uid as u64,
gid: stat.st_gid as u64,
blksize:0,
blocks: 0,
flags: 0,
gen: 0,
},
}
}
pub fn stat(p: &CString) -> IoResult<rtio::FileStat> {
pub fn stat(p: &Path) -> IoResult<FileStat> {
let mut stat: libc::stat = unsafe { mem::zeroed() };
let p = try!(to_utf16(p));
match unsafe { libc::wstat(p.as_ptr(), &mut stat) } {
@ -506,18 +442,19 @@ pub fn stat(p: &CString) -> IoResult<rtio::FileStat> {
}
}
pub fn lstat(_p: &CString) -> IoResult<rtio::FileStat> {
// FIXME: move this to platform-specific modules (for now)?
pub fn lstat(_p: &Path) -> IoResult<FileStat> {
// FIXME: implementation is missing
Err(super::unimpl())
}
pub fn utime(p: &CString, atime: u64, mtime: u64) -> IoResult<()> {
pub fn utime(p: &Path, atime: u64, mtime: u64) -> IoResult<()> {
let mut buf = libc::utimbuf {
actime: atime as libc::time64_t,
modtime: mtime as libc::time64_t,
};
let p = try!(to_utf16(p));
super::mkerr_libc(unsafe {
mkerr_libc(unsafe {
libc::wutime(p.as_ptr(), &mut buf)
})
}