diff options
| author | bors <bors@rust-lang.org> | 2013-11-19 10:56:42 -0800 |
|---|---|---|
| committer | bors <bors@rust-lang.org> | 2013-11-19 10:56:42 -0800 |
| commit | eef913b290f668b4f131ead5be65a1615616426b (patch) | |
| tree | 9be070be09e2754b700922454567f6e334d24882 /src/libstd/io/native | |
| parent | d57765d8a9d5c1136b914d0d3a073d44882f4ba9 (diff) | |
| parent | 68d5510292ed2f9714568285759ca9ef54d9b48c (diff) | |
| download | rust-eef913b290f668b4f131ead5be65a1615616426b.tar.gz rust-eef913b290f668b4f131ead5be65a1615616426b.zip | |
auto merge of #10495 : alexcrichton/rust/more-native-io, r=brson
This implements a fair amount of the unimpl() functionality in io::native relating to filesystem operations. I've also modified all io::fs tests to run in both a native and uv environment (so everything is actually tested). There are a few bits of remaining functionality which I was unable to get working: * truncate on windows * change_file_times on windows * lstat on windows I think that change_file_times may just need a better interface, but the other two have large implementations in libuv which I didn't want to tackle trying to copy. I found a `chsize` function to work for truncate on windows, but it doesn't quite seem to be working out.
Diffstat (limited to 'src/libstd/io/native')
| -rw-r--r-- | src/libstd/io/native/file.rs | 1090 | ||||
| -rw-r--r-- | src/libstd/io/native/mod.rs | 90 |
2 files changed, 695 insertions, 485 deletions
diff --git a/src/libstd/io/native/file.rs b/src/libstd/io/native/file.rs index c157efa7d38..b2cb8f735cf 100644 --- a/src/libstd/io/native/file.rs +++ b/src/libstd/io/native/file.rs @@ -12,16 +12,26 @@ #[allow(non_camel_case_types)]; +use c_str::CString; use io::IoError; use io; +use libc::c_int; use libc; use ops::Drop; use option::{Some, None, Option}; use os; +use path::{Path, GenericPath}; use ptr::RawPtr; use result::{Result, Ok, Err}; use rt::rtio; +use super::IoResult; +use unstable::intrinsics; use vec::ImmutableVector; +use vec; + +#[cfg(windows)] use os::win32::{as_utf16_p, fill_utf16_buf_and_decode}; +#[cfg(windows)] use ptr; +#[cfg(windows)] use str; fn keep_going(data: &[u8], f: &fn(*u8, uint) -> i64) -> i64 { #[cfg(windows)] static eintr: int = 0; // doesn't matter @@ -122,26 +132,157 @@ impl rtio::RtioFileStream for FileDesc { fn write(&mut self, buf: &[u8]) -> Result<(), IoError> { self.inner_write(buf) } - fn pread(&mut self, _buf: &mut [u8], _offset: u64) -> Result<int, IoError> { - Err(super::unimpl()) + fn pread(&mut self, buf: &mut [u8], offset: u64) -> Result<int, IoError> { + return os_pread(self.fd, vec::raw::to_ptr(buf), buf.len(), offset); + + #[cfg(windows)] + fn os_pread(fd: c_int, buf: *u8, amt: uint, offset: u64) -> IoResult<int> { + unsafe { + let mut overlap: libc::OVERLAPPED = intrinsics::init(); + let handle = libc::get_osfhandle(fd) as libc::HANDLE; + let mut bytes_read = 0; + overlap.Offset = offset as libc::DWORD; + overlap.OffsetHigh = (offset >> 32) as libc::DWORD; + + match libc::ReadFile(handle, buf as libc::LPVOID, + amt as libc::DWORD, + &mut bytes_read, &mut overlap) { + 0 => Err(super::last_error()), + _ => Ok(bytes_read as int) + } + } + } + + #[cfg(unix)] + fn os_pread(fd: c_int, buf: *u8, amt: uint, offset: u64) -> IoResult<int> { + match unsafe { + libc::pread(fd, buf as *libc::c_void, amt 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) -> Result<(), IoError> { - Err(super::unimpl()) + fn pwrite(&mut self, buf: &[u8], offset: u64) -> Result<(), IoError> { + return os_pwrite(self.fd, vec::raw::to_ptr(buf), buf.len(), offset); + + #[cfg(windows)] + fn os_pwrite(fd: c_int, buf: *u8, amt: uint, offset: u64) -> IoResult<()> { + unsafe { + let mut overlap: libc::OVERLAPPED = intrinsics::init(); + let handle = libc::get_osfhandle(fd) as libc::HANDLE; + overlap.Offset = offset as libc::DWORD; + overlap.OffsetHigh = (offset >> 32) as libc::DWORD; + + match libc::WriteFile(handle, buf as libc::LPVOID, + amt as libc::DWORD, + ptr::mut_null(), &mut overlap) { + 0 => Err(super::last_error()), + _ => Ok(()), + } + } + } + + #[cfg(unix)] + fn os_pwrite(fd: c_int, buf: *u8, amt: uint, offset: u64) -> IoResult<()> { + super::mkerr_libc(unsafe { + libc::pwrite(fd, buf as *libc::c_void, amt as libc::size_t, + offset as libc::off_t) + } as c_int) + } } - fn seek(&mut self, _pos: i64, _whence: io::SeekStyle) -> Result<u64, IoError> { - Err(super::unimpl()) + #[cfg(windows)] + fn seek(&mut self, pos: i64, style: io::SeekStyle) -> Result<u64, IoError> { + let whence = match style { + io::SeekSet => libc::FILE_BEGIN, + io::SeekEnd => libc::FILE_END, + io::SeekCur => libc::FILE_CURRENT, + }; + unsafe { + let handle = libc::get_osfhandle(self.fd) as libc::HANDLE; + let mut newpos = 0; + match libc::SetFilePointerEx(handle, pos, &mut newpos, whence) { + 0 => Err(super::last_error()), + _ => Ok(newpos as u64), + } + } + } + #[cfg(unix)] + fn seek(&mut self, pos: i64, whence: io::SeekStyle) -> Result<u64, IoError> { + let whence = match whence { + io::SeekSet => libc::SEEK_SET, + io::SeekEnd => libc::SEEK_END, + io::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) -> Result<u64, IoError> { - Err(super::unimpl()) + 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) -> Result<(), IoError> { - Err(super::unimpl()) + return os_fsync(self.fd); + + #[cfg(windows)] + fn os_fsync(fd: c_int) -> IoResult<()> { + super::mkerr_winbool(unsafe { + let handle = libc::get_osfhandle(fd); + libc::FlushFileBuffers(handle as libc::HANDLE) + }) + } + #[cfg(unix)] + fn os_fsync(fd: c_int) -> IoResult<()> { + super::mkerr_libc(unsafe { libc::fsync(fd) }) + } } + #[cfg(windows)] + fn datasync(&mut self) -> Result<(), IoError> { return self.fsync(); } + + #[cfg(not(windows))] fn datasync(&mut self) -> Result<(), IoError> { - Err(super::unimpl()) + return super::mkerr_libc(os_datasync(self.fd)); + + #[cfg(target_os = "macos")] + 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 { unsafe { libc::fdatasync(fd) } } + #[cfg(not(target_os = "macos"), not(target_os = "linux"))] + fn os_datasync(fd: c_int) -> c_int { unsafe { libc::fsync(fd) } } } - fn truncate(&mut self, _offset: i64) -> Result<(), IoError> { - Err(super::unimpl()) + + #[cfg(windows)] + fn truncate(&mut self, offset: i64) -> Result<(), IoError> { + let orig_pos = match self.tell() { Ok(i) => i, Err(e) => return Err(e) }; + match self.seek(offset, io::SeekSet) { + Ok(_) => {}, Err(e) => return Err(e), + }; + let ret = unsafe { + let handle = libc::get_osfhandle(self.fd) as libc::HANDLE; + match libc::SetEndOfFile(handle) { + 0 => Err(super::last_error()), + _ => Ok(()) + } + }; + self.seek(orig_pos as i64, io::SeekSet); + return ret; + } + #[cfg(unix)] + fn truncate(&mut self, offset: i64) -> Result<(), IoError> { + super::mkerr_libc(unsafe { + libc::ftruncate(self.fd, offset as libc::off_t) + }) } } @@ -180,7 +321,8 @@ impl Drop for FileDesc { } pub struct CFile { - priv file: *libc::FILE + priv file: *libc::FILE, + priv fd: FileDesc, } impl CFile { @@ -188,7 +330,16 @@ impl CFile { /// /// The `CFile` takes ownership of the `FILE` pointer and will close it upon /// destruction. - pub fn new(file: *libc::FILE) -> CFile { CFile { file: file } } + pub fn new(file: *libc::FILE) -> CFile { + CFile { + file: file, + fd: FileDesc::new(unsafe { libc::fileno(file) }, false) + } + } + + pub fn flush(&mut self) -> Result<(), IoError> { + super::mkerr_libc(unsafe { libc::fflush(self.file) }) + } } impl rtio::RtioFileStream for CFile { @@ -222,11 +373,13 @@ impl rtio::RtioFileStream for CFile { } } - fn pread(&mut self, _buf: &mut [u8], _offset: u64) -> Result<int, IoError> { - Err(super::unimpl()) + fn pread(&mut self, buf: &mut [u8], offset: u64) -> Result<int, IoError> { + self.flush(); + self.fd.pread(buf, offset) } - fn pwrite(&mut self, _buf: &[u8], _offset: u64) -> Result<(), IoError> { - Err(super::unimpl()) + fn pwrite(&mut self, buf: &[u8], offset: u64) -> Result<(), IoError> { + self.flush(); + self.fd.pwrite(buf, offset) } fn seek(&mut self, pos: i64, style: io::SeekStyle) -> Result<u64, IoError> { let whence = match style { @@ -250,13 +403,16 @@ impl rtio::RtioFileStream for CFile { } } fn fsync(&mut self) -> Result<(), IoError> { - Err(super::unimpl()) + self.flush(); + self.fd.fsync() } fn datasync(&mut self) -> Result<(), IoError> { - Err(super::unimpl()) + self.flush(); + self.fd.fsync() } - fn truncate(&mut self, _offset: i64) -> Result<(), IoError> { - Err(super::unimpl()) + fn truncate(&mut self, offset: i64) -> Result<(), IoError> { + self.flush(); + self.fd.truncate(offset) } } @@ -266,529 +422,561 @@ impl Drop for CFile { } } -#[cfg(test)] -mod tests { - use libc; - use os; - use io::{io_error, SeekSet, Writer, Reader}; - use result::Ok; - use super::{CFile, FileDesc}; +pub fn open(path: &CString, fm: io::FileMode, fa: io::FileAccess) + -> IoResult<FileDesc> { + let flags = match fm { + io::Open => 0, + io::Append => libc::O_APPEND, + io::Truncate => libc::O_TRUNC, + }; + // Opening with a write permission must silently create the file. + let (flags, mode) = match fa { + io::Read => (flags | libc::O_RDONLY, 0), + io::Write => (flags | libc::O_WRONLY | libc::O_CREAT, + libc::S_IRUSR | libc::S_IWUSR), + io::ReadWrite => (flags | libc::O_RDWR | libc::O_CREAT, + libc::S_IRUSR | libc::S_IWUSR), + }; + + return match os_open(path, flags, mode) { + -1 => Err(super::last_error()), + fd => Ok(FileDesc::new(fd, true)), + }; - #[ignore(cfg(target_os = "freebsd"))] // hmm, maybe pipes have a tiny buffer - fn test_file_desc() { - // Run this test with some pipes so we don't have to mess around with - // opening or closing files. - unsafe { - let os::Pipe { input, out } = os::pipe(); - let mut reader = FileDesc::new(input, true); - let mut writer = FileDesc::new(out, true); - - writer.inner_write(bytes!("test")); - 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 => fail!("invalid read: {:?}", r) - } - - assert!(writer.inner_read(buf).is_err()); - assert!(reader.inner_write(buf).is_err()); + #[cfg(windows)] + fn os_open(path: &CString, flags: c_int, mode: c_int) -> c_int { + do as_utf16_p(path.as_str().unwrap()) |path| { + unsafe { libc::wopen(path, flags, mode) } } } - #[ignore(cfg(windows))] // apparently windows doesn't like tmpfile - fn test_cfile() { - unsafe { - let f = libc::tmpfile(); - assert!(!f.is_null()); - let mut file = CFile::new(f); + #[cfg(unix)] + fn os_open(path: &CString, flags: c_int, mode: c_int) -> c_int { + unsafe { libc::open(path.with_ref(|p| p), flags, mode) } + } +} - file.write(bytes!("test")); - let mut buf = [0u8, ..4]; - file.seek(0, SeekSet); - 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 => fail!("invalid read: {:?}", r) +pub fn mkdir(p: &CString, mode: io::FilePermission) -> IoResult<()> { + return os_mkdir(p, mode as c_int); + + #[cfg(windows)] + fn os_mkdir(p: &CString, _mode: c_int) -> IoResult<()> { + super::mkerr_winbool(unsafe { + // FIXME: turn mode into something useful? #2623 + do as_utf16_p(p.as_str().unwrap()) |buf| { + libc::CreateDirectoryW(buf, ptr::mut_null()) } - } + }) + } + + #[cfg(unix)] + fn os_mkdir(p: &CString, mode: c_int) -> IoResult<()> { + super::mkerr_libc(unsafe { + libc::mkdir(p.with_ref(|p| p), mode as libc::mode_t) + }) } } -// n.b. these functions were all part of the old `std::os` module. There's lots -// of fun little nuances that were taken care of by these functions, but -// they are all thread-blocking versions that are no longer desired (we now -// use a non-blocking event loop implementation backed by libuv). -// -// In theory we will have a thread-blocking version of the event loop (if -// desired), so these functions may just need to get adapted to work in -// those situtations. For now, I'm leaving the code around so it doesn't -// get bitrotted instantaneously. -mod old_os { - use prelude::*; - use libc::{size_t, c_void, c_int}; - use libc; - use vec; +pub fn readdir(p: &CString) -> IoResult<~[Path]> { + fn prune(root: &CString, dirs: ~[Path]) -> ~[Path] { + let root = unsafe { CString::new(root.with_ref(|p| p), false) }; + let root = Path::new(root); - #[cfg(not(windows))] use c_str::CString; - #[cfg(not(windows))] use libc::fclose; - #[cfg(test)] #[cfg(windows)] use os; - #[cfg(test)] use rand; - #[cfg(windows)] use str; - #[cfg(windows)] use ptr; + dirs.move_iter().filter(|path| { + path.as_vec() != bytes!(".") && path.as_vec() != bytes!("..") + }).map(|path| root.join(path)).collect() + } - // On Windows, wide character version of function must be used to support - // unicode, so functions should be split into at least two versions, - // which are for Windows and for non-Windows, if necessary. - // See https://github.com/mozilla/rust/issues/9822 for more information. + unsafe { + #[cfg(not(windows))] + unsafe fn get_list(p: &CString) -> IoResult<~[Path]> { + use libc::{dirent_t}; + use libc::{opendir, readdir, closedir}; + extern { + fn rust_list_dir_val(ptr: *dirent_t) -> *libc::c_char; + } + debug!("os::list_dir -- BEFORE OPENDIR"); - mod rustrt { - use libc::{c_char, c_int}; - use libc; + let dir_ptr = do p.with_ref |buf| { + opendir(buf) + }; - extern { - pub fn rust_path_is_dir(path: *libc::c_char) -> c_int; - pub fn rust_path_exists(path: *libc::c_char) -> c_int; + if (dir_ptr as uint != 0) { + let mut paths = ~[]; + debug!("os::list_dir -- opendir() SUCCESS"); + let mut entry_ptr = readdir(dir_ptr); + while (entry_ptr as uint != 0) { + let cstr = CString::new(rust_list_dir_val(entry_ptr), false); + paths.push(Path::new(cstr)); + entry_ptr = readdir(dir_ptr); + } + closedir(dir_ptr); + Ok(paths) + } else { + Err(super::last_error()) + } } - // Uses _wstat instead of stat. #[cfg(windows)] - extern { - pub fn rust_path_is_dir_u16(path: *u16) -> c_int; - pub fn rust_path_exists_u16(path: *u16) -> c_int; + unsafe fn get_list(p: &CString) -> IoResult<~[Path]> { + use libc::consts::os::extra::INVALID_HANDLE_VALUE; + use libc::{wcslen, free}; + use libc::funcs::extra::kernel32::{ + FindFirstFileW, + FindNextFileW, + FindClose, + }; + use libc::types::os::arch::extra::HANDLE; + use os::win32::{ + as_utf16_p + }; + use rt::global_heap::malloc_raw; + + #[nolink] + extern { + fn rust_list_dir_wfd_size() -> libc::size_t; + fn rust_list_dir_wfd_fp_buf(wfd: *libc::c_void) -> *u16; + } + let p = CString::new(p.with_ref(|p| p), false); + let p = Path::new(p); + let star = p.join("*"); + do as_utf16_p(star.as_str().unwrap()) |path_ptr| { + let wfd_ptr = malloc_raw(rust_list_dir_wfd_size() as uint); + let find_handle = FindFirstFileW(path_ptr, wfd_ptr as HANDLE); + if find_handle as libc::c_int != INVALID_HANDLE_VALUE { + let mut paths = ~[]; + let mut more_files = 1 as libc::c_int; + while more_files != 0 { + let fp_buf = rust_list_dir_wfd_fp_buf(wfd_ptr); + if fp_buf as uint == 0 { + fail!("os::list_dir() failure: got null ptr from wfd"); + } + else { + let fp_vec = vec::from_buf( + fp_buf, wcslen(fp_buf) as uint); + let fp_str = str::from_utf16(fp_vec); + paths.push(Path::new(fp_str)); + } + more_files = FindNextFileW(find_handle, wfd_ptr as HANDLE); + } + FindClose(find_handle); + free(wfd_ptr); + Ok(paths) + } else { + Err(super::last_error()) + } + } } + + get_list(p).map(|paths| prune(p, paths)) } +} + +pub fn unlink(p: &CString) -> IoResult<()> { + return os_unlink(p); - /// Recursively walk a directory structure - pub fn walk_dir(p: &Path, f: &fn(&Path) -> bool) -> bool { - let r = list_dir(p); - r.iter().advance(|q| { - let path = &p.join(q); - f(path) && (!path_is_dir(path) || walk_dir(path, |p| f(p))) + #[cfg(windows)] + fn os_unlink(p: &CString) -> IoResult<()> { + super::mkerr_winbool(unsafe { + do as_utf16_p(p.as_str().unwrap()) |buf| { + libc::DeleteFileW(buf) + } }) } #[cfg(unix)] - /// Indicates whether a path represents a directory - pub fn path_is_dir(p: &Path) -> bool { - unsafe { - do p.with_c_str |buf| { - rustrt::rust_path_is_dir(buf) != 0 as c_int + fn os_unlink(p: &CString) -> IoResult<()> { + super::mkerr_libc(unsafe { libc::unlink(p.with_ref(|p| p)) }) + } +} + +pub fn rename(old: &CString, new: &CString) -> IoResult<()> { + return os_rename(old, new); + + #[cfg(windows)] + fn os_rename(old: &CString, new: &CString) -> IoResult<()> { + super::mkerr_winbool(unsafe { + do as_utf16_p(old.as_str().unwrap()) |old| { + do as_utf16_p(new.as_str().unwrap()) |new| { + libc::MoveFileExW(old, new, libc::MOVEFILE_REPLACE_EXISTING) + } } - } + }) + } + + #[cfg(unix)] + fn os_rename(old: &CString, new: &CString) -> IoResult<()> { + super::mkerr_libc(unsafe { + libc::rename(old.with_ref(|p| p), new.with_ref(|p| p)) + }) } +} +pub fn chmod(p: &CString, mode: io::FilePermission) -> IoResult<()> { + return super::mkerr_libc(os_chmod(p, mode as c_int)); #[cfg(windows)] - pub fn path_is_dir(p: &Path) -> bool { + fn os_chmod(p: &CString, mode: c_int) -> c_int { unsafe { - do os::win32::as_utf16_p(p.as_str().unwrap()) |buf| { - rustrt::rust_path_is_dir_u16(buf) != 0 as c_int + do as_utf16_p(p.as_str().unwrap()) |p| { + libc::wchmod(p, mode) } } } #[cfg(unix)] - /// Indicates whether a path exists - pub fn path_exists(p: &Path) -> bool { + fn os_chmod(p: &CString, mode: c_int) -> c_int { + unsafe { libc::chmod(p.with_ref(|p| p), mode as libc::mode_t) } + } +} + +pub fn rmdir(p: &CString) -> IoResult<()> { + return super::mkerr_libc(os_rmdir(p)); + + #[cfg(windows)] + fn os_rmdir(p: &CString) -> c_int { unsafe { - do p.with_c_str |buf| { - rustrt::rust_path_exists(buf) != 0 as c_int - } + do as_utf16_p(p.as_str().unwrap()) |p| { libc::wrmdir(p) } } } + #[cfg(unix)] + fn os_rmdir(p: &CString) -> c_int { + unsafe { libc::rmdir(p.with_ref(|p| p)) } + } +} + +pub fn chown(p: &CString, uid: int, gid: int) -> IoResult<()> { + return super::mkerr_libc(os_chown(p, uid, gid)); + + // libuv has this as a no-op, so seems like this should as well? #[cfg(windows)] - pub fn path_exists(p: &Path) -> bool { + fn os_chown(_p: &CString, _uid: int, _gid: int) -> c_int { 0 } + + #[cfg(unix)] + fn os_chown(p: &CString, uid: int, gid: int) -> c_int { unsafe { - do os::win32::as_utf16_p(p.as_str().unwrap()) |buf| { - rustrt::rust_path_exists_u16(buf) != 0 as c_int - } + libc::chown(p.with_ref(|p| p), uid as libc::uid_t, + gid as libc::gid_t) } } +} - /// Creates a directory at the specified path - pub fn make_dir(p: &Path, mode: c_int) -> bool { - return mkdir(p, mode); +pub fn readlink(p: &CString) -> IoResult<Path> { + return os_readlink(p); - #[cfg(windows)] - fn mkdir(p: &Path, _mode: c_int) -> bool { + // XXX: I have a feeling that this reads intermediate symlinks as well. + #[cfg(windows)] + fn os_readlink(p: &CString) -> IoResult<Path> { + let handle = unsafe { + do as_utf16_p(p.as_str().unwrap()) |p| { + libc::CreateFileW(p, + libc::GENERIC_READ, + libc::FILE_SHARE_READ, + ptr::mut_null(), + libc::OPEN_EXISTING, + libc::FILE_ATTRIBUTE_NORMAL, + ptr::mut_null()) + } + }; + if handle == ptr::mut_null() { return Err(super::last_error()) } + let ret = do fill_utf16_buf_and_decode |buf, sz| { unsafe { - use os::win32::as_utf16_p; - // FIXME: turn mode into something useful? #2623 - do as_utf16_p(p.as_str().unwrap()) |buf| { - libc::CreateDirectoryW(buf, ptr::mut_null()) - != (0 as libc::BOOL) - } + libc::GetFinalPathNameByHandleW(handle, buf as *u16, sz, + libc::VOLUME_NAME_NT) } - } + }; + let ret = match ret { + Some(s) => Ok(Path::new(s)), + None => Err(super::last_error()), + }; + unsafe { libc::CloseHandle(handle) }; + return ret; - #[cfg(unix)] - fn mkdir(p: &Path, mode: c_int) -> bool { - do p.with_c_str |buf| { - unsafe { - libc::mkdir(buf, mode as libc::mode_t) == (0 as c_int) - } - } - } } - /// Creates a directory with a given mode. - /// Returns true iff creation - /// succeeded. Also creates all intermediate subdirectories - /// if they don't already exist, giving all of them the same mode. - - // tjc: if directory exists but with different permissions, - // should we return false? - pub fn mkdir_recursive(p: &Path, mode: c_int) -> bool { - if path_is_dir(p) { - return true; + #[cfg(unix)] + fn os_readlink(p: &CString) -> IoResult<Path> { + let p = p.with_ref(|p| p); + let mut len = unsafe { libc::pathconf(p, libc::_PC_NAME_MAX) }; + if len == -1 { + len = 1024; // XXX: read PATH_MAX from C ffi? } - if p.filename().is_some() { - let mut p_ = p.clone(); - p_.pop(); - if !mkdir_recursive(&p_, mode) { - return false; + let mut buf = vec::with_capacity::<u8>(len as uint); + match unsafe { + libc::readlink(p, vec::raw::to_ptr(buf) as *mut libc::c_char, + len as libc::size_t) + } { + -1 => Err(super::last_error()), + n => { + assert!(n > 0); + unsafe { vec::raw::set_len(&mut buf, n as uint); } + Ok(Path::new(buf)) } } - return make_dir(p, mode); } +} - /// Lists the contents of a directory - /// - /// Each resulting Path is a relative path with no directory component. - pub fn list_dir(p: &Path) -> ~[Path] { - unsafe { - #[cfg(target_os = "linux")] - #[cfg(target_os = "android")] - #[cfg(target_os = "freebsd")] - #[cfg(target_os = "macos")] - unsafe fn get_list(p: &Path) -> ~[Path] { - use libc::{dirent_t}; - use libc::{opendir, readdir, closedir}; - extern { - fn rust_list_dir_val(ptr: *dirent_t) -> *libc::c_char; - } - let mut paths = ~[]; - debug!("os::list_dir -- BEFORE OPENDIR"); - - let dir_ptr = do p.with_c_str |buf| { - opendir(buf) - }; - - if (dir_ptr as uint != 0) { - debug!("os::list_dir -- opendir() SUCCESS"); - let mut entry_ptr = readdir(dir_ptr); - while (entry_ptr as uint != 0) { - let cstr = CString::new(rust_list_dir_val(entry_ptr), false); - paths.push(Path::new(cstr)); - entry_ptr = readdir(dir_ptr); - } - closedir(dir_ptr); - } - else { - debug!("os::list_dir -- opendir() FAILURE"); - } - debug!("os::list_dir -- AFTER -- \\#: {}", paths.len()); - paths - } - #[cfg(windows)] - unsafe fn get_list(p: &Path) -> ~[Path] { - use libc::consts::os::extra::INVALID_HANDLE_VALUE; - use libc::{wcslen, free}; - use libc::funcs::extra::kernel32::{ - FindFirstFileW, - FindNextFileW, - FindClose, - }; - use libc::types::os::arch::extra::HANDLE; - use os::win32::{ - as_utf16_p - }; - use rt::global_heap::malloc_raw; - - #[nolink] - extern { - fn rust_list_dir_wfd_size() -> libc::size_t; - fn rust_list_dir_wfd_fp_buf(wfd: *libc::c_void) -> *u16; - } - let star = p.join("*"); - do as_utf16_p(star.as_str().unwrap()) |path_ptr| { - let mut paths = ~[]; - let wfd_ptr = malloc_raw(rust_list_dir_wfd_size() as uint); - let find_handle = FindFirstFileW(path_ptr, wfd_ptr as HANDLE); - if find_handle as libc::c_int != INVALID_HANDLE_VALUE { - let mut more_files = 1 as libc::c_int; - while more_files != 0 { - let fp_buf = rust_list_dir_wfd_fp_buf(wfd_ptr); - if fp_buf as uint == 0 { - fail!("os::list_dir() failure: got null ptr from wfd"); - } - else { - let fp_vec = vec::from_buf( - fp_buf, wcslen(fp_buf) as uint); - let fp_str = str::from_utf16(fp_vec); - paths.push(Path::new(fp_str)); - } - more_files = FindNextFileW(find_handle, wfd_ptr as HANDLE); - } - FindClose(find_handle); - free(wfd_ptr) - } - paths - } - } - do get_list(p).move_iter().filter |path| { - path.as_vec() != bytes!(".") && path.as_vec() != bytes!("..") - }.collect() - } - } +pub fn symlink(src: &CString, dst: &CString) -> IoResult<()> { + return os_symlink(src, dst); - /// Removes a directory at the specified path, after removing - /// all its contents. Use carefully! - pub fn remove_dir_recursive(p: &Path) -> bool { - let mut error_happened = false; - do walk_dir(p) |inner| { - if !error_happened { - if path_is_dir(inner) { - if !remove_dir_recursive(inner) { - error_happened = true; - } - } - else { - if !remove_file(inner) { - error_happened = true; - } - } + #[cfg(windows)] + fn os_symlink(src: &CString, dst: &CString) -> IoResult<()> { + super::mkerr_winbool(do as_utf16_p(src.as_str().unwrap()) |src| { + do as_utf16_p(dst.as_str().unwrap()) |dst| { + unsafe { libc::CreateSymbolicLinkW(dst, src, 0) } } - true - }; - // Directory should now be empty - !error_happened && remove_dir(p) + }) } - /// Removes a directory at the specified path - pub fn remove_dir(p: &Path) -> bool { - return rmdir(p); + #[cfg(unix)] + fn os_symlink(src: &CString, dst: &CString) -> IoResult<()> { + super::mkerr_libc(unsafe { + libc::symlink(src.with_ref(|p| p), dst.with_ref(|p| p)) + }) + } +} - #[cfg(windows)] - fn rmdir(p: &Path) -> bool { - unsafe { - use os::win32::as_utf16_p; - return do as_utf16_p(p.as_str().unwrap()) |buf| { - libc::RemoveDirectoryW(buf) != (0 as libc::BOOL) - }; - } - } +pub fn link(src: &CString, dst: &CString) -> IoResult<()> { + return os_link(src, dst); - #[cfg(unix)] - fn rmdir(p: &Path) -> bool { - do p.with_c_str |buf| { - unsafe { - libc::rmdir(buf) == (0 as c_int) - } + #[cfg(windows)] + fn os_link(src: &CString, dst: &CString) -> IoResult<()> { + super::mkerr_winbool(do as_utf16_p(src.as_str().unwrap()) |src| { + do as_utf16_p(dst.as_str().unwrap()) |dst| { + unsafe { libc::CreateHardLinkW(dst, src, ptr::mut_null()) } } - } + }) } - /// Deletes an existing file - pub fn remove_file(p: &Path) -> bool { - return unlink(p); + #[cfg(unix)] + fn os_link(src: &CString, dst: &CString) -> IoResult<()> { + super::mkerr_libc(unsafe { + libc::link(src.with_ref(|p| p), dst.with_ref(|p| p)) + }) + } +} - #[cfg(windows)] - fn unlink(p: &Path) -> bool { - unsafe { - use os::win32::as_utf16_p; - return do as_utf16_p(p.as_str().unwrap()) |buf| { - libc::DeleteFileW(buf) != (0 as libc::BOOL) - }; - } - } - - #[cfg(unix)] - fn unlink(p: &Path) -> bool { - unsafe { - do p.with_c_str |buf| { - libc::unlink(buf) == (0 as c_int) - } - } +#[cfg(windows)] +fn mkstat(stat: &libc::stat, path: &CString) -> io::FileStat { + let path = unsafe { CString::new(path.with_ref(|p| p), false) }; + + // FileStat times are in milliseconds + fn mktime(secs: u64, nsecs: u64) -> u64 { secs * 1000 + nsecs / 1000000 } + + let kind = match (stat.st_mode as 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, + }; + + io::FileStat { + path: Path::new(path), + size: stat.st_size as u64, + kind: kind, + perm: (stat.st_mode) as io::FilePermission & io::AllPermissions, + created: stat.st_ctime as u64, + modified: stat.st_mtime as u64, + accessed: stat.st_atime as u64, + unstable: io::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, } } +} - /// Renames an existing file or directory - pub fn rename_file(old: &Path, new: &Path) -> bool { - unsafe { - do old.with_c_str |old_buf| { - do new.with_c_str |new_buf| { - libc::rename(old_buf, new_buf) == (0 as c_int) - } - } +#[cfg(unix)] +fn mkstat(stat: &libc::stat, path: &CString) -> io::FileStat { + let path = unsafe { CString::new(path.with_ref(|p| p), false) }; + + // FileStat times are in milliseconds + fn mktime(secs: u64, nsecs: u64) -> u64 { secs * 1000 + nsecs / 1000000 } + + let kind = match (stat.st_mode as 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, + }; + + #[cfg(not(target_os = "linux"), not(target_os = "android"))] + fn flags(stat: &libc::stat) -> u64 { stat.st_flags as u64 } + #[cfg(target_os = "linux")] #[cfg(target_os = "android")] + fn flags(_stat: &libc::stat) -> u64 { 0 } + + #[cfg(not(target_os = "linux"), not(target_os = "android"))] + fn gen(stat: &libc::stat) -> u64 { stat.st_gen as u64 } + #[cfg(target_os = "linux")] #[cfg(target_os = "android")] + fn gen(_stat: &libc::stat) -> u64 { 0 } + + io::FileStat { + path: Path::new(path), + size: stat.st_size as u64, + kind: kind, + perm: (stat.st_mode) as io::FilePermission & io::AllPermissions, + 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: io::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), } } +} - /// Copies a file from one location to another - pub fn copy_file(from: &Path, to: &Path) -> bool { - return do_copy_file(from, to); +pub fn stat(p: &CString) -> IoResult<io::FileStat> { + return os_stat(p); - #[cfg(windows)] - fn do_copy_file(from: &Path, to: &Path) -> bool { - unsafe { - use os::win32::as_utf16_p; - return do as_utf16_p(from.as_str().unwrap()) |fromp| { - do as_utf16_p(to.as_str().unwrap()) |top| { - libc::CopyFileW(fromp, top, (0 as libc::BOOL)) != - (0 as libc::BOOL) - } - } + #[cfg(windows)] + fn os_stat(p: &CString) -> IoResult<io::FileStat> { + let mut stat: libc::stat = unsafe { intrinsics::uninit() }; + do as_utf16_p(p.as_str().unwrap()) |up| { + match unsafe { libc::wstat(up, &mut stat) } { + 0 => Ok(mkstat(&stat, p)), + _ => Err(super::last_error()), } } + } - #[cfg(unix)] - fn do_copy_file(from: &Path, to: &Path) -> bool { - unsafe { - let istream = do from.with_c_str |fromp| { - do "rb".with_c_str |modebuf| { - libc::fopen(fromp, modebuf) - } - }; - if istream as uint == 0u { - return false; - } - // Preserve permissions - let from_mode = from.stat().perm; - - let ostream = do to.with_c_str |top| { - do "w+b".with_c_str |modebuf| { - libc::fopen(top, modebuf) - } - }; - if ostream as uint == 0u { - fclose(istream); - return false; - } - let bufsize = 8192u; - let mut buf = vec::with_capacity::<u8>(bufsize); - let mut done = false; - let mut ok = true; - while !done { - do buf.as_mut_buf |b, _sz| { - let nread = libc::fread(b as *mut c_void, 1u as size_t, - bufsize as size_t, - istream); - if nread > 0 as size_t { - if libc::fwrite(b as *c_void, 1u as size_t, nread, - ostream) != nread { - ok = false; - done = true; - } - } else { - done = true; - } - } - } - fclose(istream); - fclose(ostream); - - // Give the new file the old file's permissions - if do to.with_c_str |to_buf| { - libc::chmod(to_buf, from_mode as libc::mode_t) - } != 0 { - return false; // should be a condition... - } - return ok; - } + #[cfg(unix)] + fn os_stat(p: &CString) -> IoResult<io::FileStat> { + let mut stat: libc::stat = unsafe { intrinsics::uninit() }; + match unsafe { libc::stat(p.with_ref(|p| p), &mut stat) } { + 0 => Ok(mkstat(&stat, p)), + _ => Err(super::last_error()), } } +} - #[test] - fn tmpdir() { - let p = os::tmpdir(); - let s = p.as_str(); - assert!(s.is_some() && s.unwrap() != "."); - } +pub fn lstat(p: &CString) -> IoResult<io::FileStat> { + return os_lstat(p); - // Issue #712 - #[test] - fn test_list_dir_no_invalid_memory_access() { - list_dir(&Path::new(".")); + // XXX: windows implementation is missing + #[cfg(windows)] + fn os_lstat(_p: &CString) -> IoResult<io::FileStat> { + Err(super::unimpl()) } - #[test] - fn test_list_dir() { - let dirs = list_dir(&Path::new(".")); - // Just assuming that we've got some contents in the current directory - assert!(dirs.len() > 0u); - - for dir in dirs.iter() { - debug!("{:?}", (*dir).clone()); + #[cfg(unix)] + fn os_lstat(p: &CString) -> IoResult<io::FileStat> { + let mut stat: libc::stat = unsafe { intrinsics::uninit() }; + match unsafe { libc::lstat(p.with_ref(|p| p), &mut stat) } { + 0 => Ok(mkstat(&stat, p)), + _ => Err(super::last_error()), } } +} + +pub fn utime(p: &CString, atime: u64, mtime: u64) -> IoResult<()> { + return super::mkerr_libc(os_utime(p, atime, mtime)); - #[test] - #[cfg(not(windows))] - fn test_list_dir_root() { - let dirs = list_dir(&Path::new("/")); - assert!(dirs.len() > 1); - } - #[test] #[cfg(windows)] - fn test_list_dir_root() { - let dirs = list_dir(&Path::new("C:\\")); - assert!(dirs.len() > 1); + fn os_utime(p: &CString, atime: u64, mtime: u64) -> c_int { + let buf = libc::utimbuf { + actime: (atime / 1000) as libc::time64_t, + modtime: (mtime / 1000) as libc::time64_t, + }; + unsafe { + do as_utf16_p(p.as_str().unwrap()) |p| { + libc::wutime(p, &buf) + } + } } - #[test] - fn test_path_is_dir() { - use io::fs::{mkdir_recursive}; - use io::{File, UserRWX}; - - assert!((path_is_dir(&Path::new(".")))); - assert!((!path_is_dir(&Path::new("test/stdtest/fs.rs")))); - - let mut dirpath = os::tmpdir(); - dirpath.push(format!("rust-test-{}/test-\uac00\u4e00\u30fc\u4f60\u597d", - rand::random::<u32>())); // 가一ー你好 - debug!("path_is_dir dirpath: {}", dirpath.display()); + #[cfg(unix)] + fn os_utime(p: &CString, atime: u64, mtime: u64) -> c_int { + let buf = libc::utimbuf { + actime: (atime / 1000) as libc::time_t, + modtime: (mtime / 1000) as libc::time_t, + }; + unsafe { libc::utime(p.with_ref(|p| p), &buf) } + } +} - mkdir_recursive(&dirpath, UserRWX); +#[cfg(test)] +mod tests { + use io::native::file::{CFile, FileDesc}; + use io::fs; + use io; + use libc; + use os; + use path::Path; + use rand; + use result::Ok; + use rt::rtio::RtioFileStream; - assert!((path_is_dir(&dirpath))); + fn tmpdir() -> Path { + let ret = os::tmpdir().join(format!("rust-{}", rand::random::<u32>())); + fs::mkdir(&ret, io::UserRWX); + ret + } - let mut filepath = dirpath; - filepath.push("unicode-file-\uac00\u4e00\u30fc\u4f60\u597d.rs"); - debug!("path_is_dir filepath: {}", filepath.display()); + #[ignore(cfg(target_os = "freebsd"))] // hmm, maybe pipes have a tiny buffer + fn test_file_desc() { + // Run this test with some pipes so we don't have to mess around with + // opening or closing files. + unsafe { + let os::Pipe { input, out } = os::pipe(); + let mut reader = FileDesc::new(input, true); + let mut writer = FileDesc::new(out, true); - File::create(&filepath); // ignore return; touch only - assert!((!path_is_dir(&filepath))); + writer.inner_write(bytes!("test")); + 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 => fail!("invalid read: {:?}", r) + } - assert!((!path_is_dir(&Path::new( - "test/unicode-bogus-dir-\uac00\u4e00\u30fc\u4f60\u597d")))); + assert!(writer.inner_read(buf).is_err()); + assert!(reader.inner_write(buf).is_err()); + } } - #[test] - fn test_path_exists() { - use io::fs::mkdir_recursive; - use io::UserRWX; - - assert!((path_exists(&Path::new(".")))); - assert!((!path_exists(&Path::new( - "test/nonexistent-bogus-path")))); - - let mut dirpath = os::tmpdir(); - dirpath.push(format!("rust-test-{}/test-\uac01\u4e01\u30fc\u518d\u89c1", - rand::random::<u32>())); // 각丁ー再见 + #[ignore(cfg(windows))] // apparently windows doesn't like tmpfile + fn test_cfile() { + unsafe { + let f = libc::tmpfile(); + assert!(!f.is_null()); + let mut file = CFile::new(f); - mkdir_recursive(&dirpath, UserRWX); - assert!((path_exists(&dirpath))); - assert!((!path_exists(&Path::new( - "test/unicode-bogus-path-\uac01\u4e01\u30fc\u518d\u89c1")))); + file.write(bytes!("test")); + let mut buf = [0u8, ..4]; + file.seek(0, io::SeekSet); + 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 => fail!("invalid read: {:?}", r) + } + } } } diff --git a/src/libstd/io/native/mod.rs b/src/libstd/io/native/mod.rs index d7aa6c14fcd..cec0de00ec2 100644 --- a/src/libstd/io/native/mod.rs +++ b/src/libstd/io/native/mod.rs @@ -90,6 +90,24 @@ fn last_error() -> IoError { } } +// unix has nonzero values as errors +fn mkerr_libc(ret: libc::c_int) -> IoResult<()> { + if ret != 0 { + Err(last_error()) + } else { + Ok(()) + } +} + +// windows has zero values as errors +fn mkerr_winbool(ret: libc::c_int) -> IoResult<()> { + if ret == 0 { + Err(last_error()) + } else { + Ok(()) + } +} + /// Implementation of rt::rtio's IoFactory trait to generate handles to the /// native I/O functionality. pub struct IoFactory; @@ -125,51 +143,51 @@ impl rtio::IoFactory for IoFactory { }; ~file::FileDesc::new(fd, close) as ~RtioFileStream } - fn fs_open(&mut self, _path: &CString, _fm: io::FileMode, _fa: io::FileAccess) + fn fs_open(&mut self, path: &CString, fm: io::FileMode, fa: io::FileAccess) -> IoResult<~RtioFileStream> { - Err(unimpl()) + file::open(path, fm, fa).map(|fd| ~fd as ~RtioFileStream) } - fn fs_unlink(&mut self, _path: &CString) -> IoResult<()> { - Err(unimpl()) + fn fs_unlink(&mut self, path: &CString) -> IoResult<()> { + file::unlink(path) } - fn fs_stat(&mut self, _path: &CString) -> IoResult<io::FileStat> { - Err(unimpl()) + fn fs_stat(&mut self, path: &CString) -> IoResult<io::FileStat> { + file::stat(path) } - fn fs_mkdir(&mut self, _path: &CString, - _mode: io::FilePermission) -> IoResult<()> { - Err(unimpl()) + fn fs_mkdir(&mut self, path: &CString, + mode: io::FilePermission) -> IoResult<()> { + file::mkdir(path, mode) } - fn fs_chmod(&mut self, _path: &CString, - _mode: io::FilePermission) -> IoResult<()> { - Err(unimpl()) + fn fs_chmod(&mut self, path: &CString, + mode: io::FilePermission) -> IoResult<()> { + file::chmod(path, mode) } - fn fs_rmdir(&mut self, _path: &CString) -> IoResult<()> { - Err(unimpl()) + fn fs_rmdir(&mut self, path: &CString) -> IoResult<()> { + file::rmdir(path) } - fn fs_rename(&mut self, _path: &CString, _to: &CString) -> IoResult<()> { - Err(unimpl()) + 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<~[Path]> { - Err(unimpl()) + fn fs_readdir(&mut self, path: &CString, _flags: c_int) -> IoResult<~[Path]> { + file::readdir(path) } - fn fs_lstat(&mut self, _path: &CString) -> IoResult<io::FileStat> { - Err(unimpl()) + fn fs_lstat(&mut self, path: &CString) -> IoResult<io::FileStat> { + file::lstat(path) } - fn fs_chown(&mut self, _path: &CString, _uid: int, _gid: int) -> IoResult<()> { - Err(unimpl()) + 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<Path> { - Err(unimpl()) + fn fs_readlink(&mut self, path: &CString) -> IoResult<Path> { + file::readlink(path) } - fn fs_symlink(&mut self, _src: &CString, _dst: &CString) -> IoResult<()> { - Err(unimpl()) + fn fs_symlink(&mut self, src: &CString, dst: &CString) -> IoResult<()> { + file::symlink(src, dst) } - fn fs_link(&mut self, _src: &CString, _dst: &CString) -> IoResult<()> { - Err(unimpl()) + 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<()> { - Err(unimpl()) + fn fs_utime(&mut self, src: &CString, atime: u64, + mtime: u64) -> IoResult<()> { + file::utime(src, atime, mtime) } // misc @@ -183,14 +201,18 @@ impl rtio::IoFactory for IoFactory { io.move_iter().map(|p| p.map(|p| ~p as ~RtioPipe)).collect()) }) } - fn pipe_open(&mut self, _fd: c_int) -> IoResult<~RtioPipe> { - Err(unimpl()) + fn pipe_open(&mut self, fd: c_int) -> IoResult<~RtioPipe> { + Ok(~file::FileDesc::new(fd, true) as ~RtioPipe) } fn tty_open(&mut self, fd: c_int, _readable: bool) -> IoResult<~RtioTTY> { if unsafe { libc::isatty(fd) } != 0 { Ok(~file::FileDesc::new(fd, true) as ~RtioTTY) } else { - Err(unimpl()) + Err(IoError { + kind: io::MismatchedFileTypeForOperation, + desc: "file descriptor is not a TTY", + detail: None, + }) } } fn signal(&mut self, _signal: Signum, _channel: SharedChan<Signum>) |
