use std::io;
use std::str;
use std::fmt;
use std::sync::Arc;
use std::cell::RefCell;
use std::ffi::{CStr, CString};
use std::fs::Metadata;
use std::path::{Path, PathBuf};
use std::convert::AsRef;
use std::mem::forget;
use std::ops::{Deref, Drop};
use std::os::unix::io::{AsRawFd, IntoRawFd, RawFd};
use std::os::unix::ffi::OsStrExt;
use libc;
pub trait Stream : io::Read + io::Write + io::Seek {
fn stream(&self) -> *mut libc::FILE;
fn position(&self) -> io::Result<u64>;
fn eof(&self) -> bool;
fn errno(&self) -> i32;
fn last_error(&self) -> Option<io::Error>;
fn clear_error(&self);
fn file_name(&self) -> io::Result<PathBuf>;
fn metadata(&self) -> io::Result<Metadata>;
}
pub trait ToStream : AsRawFd + Sized {
fn to_stream(&self, mode: &str) -> io::Result<CFile> {
CFile::open_stream(self, mode)
}
}
impl<S: AsRawFd + Sized> ToStream for S {}
macro_rules! cstr {
($s:expr) => (try!(CString::new($s)).as_ptr() as *const i8)
}
pub struct CFileRaw(*mut libc::FILE);
impl Drop for CFileRaw {
fn drop(&mut self) {
unsafe {
libc::fclose(self.0);
}
}
}
impl Deref for CFileRaw {
type Target = *mut libc::FILE;
fn deref(&self) -> &Self::Target {
&self.0
}
}
extern "C" {
fn clearerr(file: *mut libc::FILE);
}
pub struct CFile {
inner: Arc<RefCell<CFileRaw>>,
owned: bool,
}
unsafe impl Sync for CFile {}
unsafe impl Send for CFile {}
impl Deref for CFile {
type Target = Arc<RefCell<CFileRaw>>;
fn deref(&self) -> &Self::Target {
&self.inner
}
}
impl CFile {
pub fn from_raw(f: *mut libc::FILE, owned: bool) -> io::Result<CFile> {
if f.is_null() {
Err(io::Error::last_os_error())
} else {
Ok(CFile {
inner: Arc::new(RefCell::new(CFileRaw(f))),
owned: owned,
})
}
}
pub fn open<P: AsRef<Path>>(path: P, mode: &str) -> io::Result<CFile> {
Self::from_raw(unsafe {
libc::fopen(cstr!(path.as_ref()
.as_os_str()
.as_bytes()),
cstr!(mode))
},
true)
}
fn _open_fd(fd: RawFd, mode: &str, owned: bool) -> io::Result<CFile> {
Self::from_raw(unsafe { libc::fdopen(fd, cstr!(mode)) }, owned)
}
pub fn open_stream<S: AsRawFd>(s: &S, mode: &str) -> io::Result<CFile> {
Self::_open_fd(s.as_raw_fd(), mode, true)
}
pub fn open_stdin() -> io::Result<CFile> {
Self::_open_fd(libc::STDIN_FILENO, "r", false)
}
pub fn open_stdout() -> io::Result<CFile> {
Self::_open_fd(libc::STDOUT_FILENO, "w", false)
}
pub fn open_stderr() -> io::Result<CFile> {
Self::_open_fd(libc::STDERR_FILENO, "w", false)
}
pub fn open_tmpfile() -> io::Result<CFile> {
Self::from_raw(unsafe { libc::tmpfile() }, true)
}
pub fn reopen(&self, filename: &str, mode: &str) -> io::Result<CFile> {
let f = unsafe { libc::freopen(cstr!(filename), cstr!(mode), self.stream()) };
Self::from_raw(f, true)
}
}
impl Stream for CFile {
#[inline]
fn stream(&self) -> *mut libc::FILE {
(*self.inner.borrow()).0
}
fn position(&self) -> io::Result<u64> {
let off = unsafe { libc::ftell(self.stream()) };
if off < 0 {
if let Some(err) = self.last_error() {
return Err(err);
}
}
Ok(off as u64)
}
#[inline]
fn eof(&self) -> bool {
unsafe { libc::feof(self.stream()) != 0 }
}
#[inline]
fn errno(&self) -> i32 {
unsafe { libc::ferror(self.stream()) }
}
fn last_error(&self) -> Option<io::Error> {
let errno = self.errno();
if errno != 0 {
return Some(io::Error::from_raw_os_error(errno));
}
let err = io::Error::last_os_error();
match err.raw_os_error() {
Some(errno) if errno != 0 => Some(err),
_ => None,
}
}
fn clear_error(&self) {
unsafe { clearerr(self.stream()) }
}
#[cfg(target_os = "linux")]
fn file_name(&self) -> io::Result<PathBuf> {
let s = format!("/proc/self/fd/{}", self.as_raw_fd());
let p = Path::new(&s);
if p.exists() {
p.read_link()
} else {
Err(io::Error::new(io::ErrorKind::NotFound, "fd not found"))
}
}
#[cfg(target_os = "macos")]
fn file_name(&self) -> io::Result<PathBuf> {
let mut buf = Vec::with_capacity(libc::PATH_MAX as usize);
let ret = unsafe { libc::fcntl(self.as_raw_fd(), libc::F_GETPATH, buf.as_mut_ptr()) };
let filename = str::from_utf8(unsafe { CStr::from_ptr(buf.as_ptr()).to_bytes() }).unwrap();
println!("{}, {}", filename, ret);
if ret < 0 {
Err(io::Error::last_os_error())
} else {
Ok(PathBuf::from(filename))
}
}
fn metadata(&self) -> io::Result<Metadata> {
try!(self.file_name()).as_path().metadata()
}
}
impl AsRawFd for CFile {
fn as_raw_fd(&self) -> RawFd {
unsafe { libc::fileno(self.stream()) }
}
}
impl IntoRawFd for CFile {
fn into_raw_fd(self) -> RawFd {
let fd = unsafe { libc::fileno(self.stream()) };
forget(self);
fd
}
}
impl io::Read for CFile {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
if buf.is_empty() {
return Ok(0);
}
let read = unsafe {
libc::fread(buf.as_ptr() as *mut libc::c_void,
1,
buf.len(),
self.stream())
};
if let Some(err) = self.last_error() {
if read == 0 {
return Err(err);
}
}
Ok(read)
}
}
impl io::Write for CFile {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
if buf.is_empty() {
return Ok(0);
}
let wrote = unsafe {
libc::fwrite(buf.as_ptr() as *const libc::c_void,
1,
buf.len(),
self.stream())
};
if let Some(err) = self.last_error() {
if wrote == 0 {
return Err(err);
}
}
Ok(wrote)
}
fn flush(&mut self) -> io::Result<()> {
if unsafe { libc::fflush(self.stream()) } != 0 {
if let Some(err) = self.last_error() {
return Err(err);
}
}
Ok(())
}
}
impl io::Seek for CFile {
fn seek(&mut self, pos: io::SeekFrom) -> io::Result<u64> {
let ret = unsafe {
match pos {
io::SeekFrom::Start(off) => libc::fseek(self.stream(), off as i64, libc::SEEK_SET),
io::SeekFrom::End(off) => libc::fseek(self.stream(), off, libc::SEEK_END),
io::SeekFrom::Current(off) => libc::fseek(self.stream(), off, libc::SEEK_CUR),
}
};
if ret != 0 {
if let Some(err) = self.last_error() {
return Err(err);
}
}
self.position()
}
}
impl fmt::Debug for CFile {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "CFile {{f: {:p}, owned: {}}}", self.stream(), self.owned)
}
}
#[cfg(test)]
mod tests {
use std::io::{Read, Write, Seek, SeekFrom};
use std::os::unix::io::AsRawFd;
use super::*;
#[test]
fn test_cfile() {
let mut f = CFile::open_tmpfile().unwrap();
assert!(!f.stream().is_null());
assert!(f.as_raw_fd() > 2);
assert_eq!(f.write(b"test").unwrap(), 4);
assert_eq!(f.seek(SeekFrom::Current(0)).unwrap(), 4);
let mut buf: [u8; 4] = [0; 4];
assert_eq!(f.read(&mut buf[..]).unwrap(), 0);
assert_eq!(f.seek(SeekFrom::Start(0)).unwrap(), 0);
f.flush().unwrap();
assert_eq!(f.read(&mut buf[..]).unwrap(), 4);
assert_eq!(buf, &b"test"[..]);
let filename = f.file_name().unwrap();
assert!(filename.is_absolute());
assert!(filename.parent().unwrap().is_dir());
}
}