From 68811817f723e57354b0137b1c9bc6b28016ecbc Mon Sep 17 00:00:00 2001 From: Vadim Chugunov Date: Sat, 23 Aug 2014 01:12:59 -0700 Subject: Complete renaming of win32 to windows --- src/libnative/io/c_win32.rs | 207 ----------- src/libnative/io/c_windows.rs | 207 +++++++++++ src/libnative/io/file_win32.rs | 526 ---------------------------- src/libnative/io/file_windows.rs | 526 ++++++++++++++++++++++++++++ src/libnative/io/mod.rs | 11 +- src/libnative/io/pipe_win32.rs | 713 -------------------------------------- src/libnative/io/pipe_windows.rs | 713 ++++++++++++++++++++++++++++++++++++++ src/libnative/io/process.rs | 2 +- src/libnative/io/timer_win32.rs | 210 ----------- src/libnative/io/timer_windows.rs | 210 +++++++++++ src/libnative/io/tty_win32.rs | 158 --------- src/libnative/io/tty_windows.rs | 158 +++++++++ 12 files changed, 1820 insertions(+), 1821 deletions(-) delete mode 100644 src/libnative/io/c_win32.rs create mode 100644 src/libnative/io/c_windows.rs delete mode 100644 src/libnative/io/file_win32.rs create mode 100644 src/libnative/io/file_windows.rs delete mode 100644 src/libnative/io/pipe_win32.rs create mode 100644 src/libnative/io/pipe_windows.rs delete mode 100644 src/libnative/io/timer_win32.rs create mode 100644 src/libnative/io/timer_windows.rs delete mode 100644 src/libnative/io/tty_win32.rs create mode 100644 src/libnative/io/tty_windows.rs (limited to 'src/libnative') diff --git a/src/libnative/io/c_win32.rs b/src/libnative/io/c_win32.rs deleted file mode 100644 index 80c9e91b48f..00000000000 --- a/src/libnative/io/c_win32.rs +++ /dev/null @@ -1,207 +0,0 @@ -// 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. -// -// Licensed under the Apache License, Version 2.0 or the MIT license -// , at your -// option. This file may not be copied, modified, or distributed -// except according to those terms. - -//! C definitions used by libnative that don't belong in liblibc - -#![allow(type_overflow)] - -use libc; - -pub static WSADESCRIPTION_LEN: uint = 256; -pub static WSASYS_STATUS_LEN: uint = 128; -pub static FIONBIO: libc::c_long = 0x8004667e; -static FD_SETSIZE: uint = 64; -pub static MSG_DONTWAIT: libc::c_int = 0; -pub static ERROR_ILLEGAL_CHARACTER: libc::c_int = 582; -pub static ENABLE_ECHO_INPUT: libc::DWORD = 0x4; -pub static ENABLE_EXTENDED_FLAGS: libc::DWORD = 0x80; -pub static ENABLE_INSERT_MODE: libc::DWORD = 0x20; -pub static ENABLE_LINE_INPUT: libc::DWORD = 0x2; -pub static ENABLE_PROCESSED_INPUT: libc::DWORD = 0x1; -pub static ENABLE_QUICK_EDIT_MODE: libc::DWORD = 0x40; - -#[repr(C)] -#[cfg(target_arch = "x86")] -pub struct WSADATA { - pub wVersion: libc::WORD, - pub wHighVersion: libc::WORD, - pub szDescription: [u8, ..WSADESCRIPTION_LEN + 1], - pub szSystemStatus: [u8, ..WSASYS_STATUS_LEN + 1], - pub iMaxSockets: u16, - pub iMaxUdpDg: u16, - pub lpVendorInfo: *mut u8, -} -#[repr(C)] -#[cfg(target_arch = "x86_64")] -pub struct WSADATA { - pub wVersion: libc::WORD, - pub wHighVersion: libc::WORD, - pub iMaxSockets: u16, - pub iMaxUdpDg: u16, - pub lpVendorInfo: *mut u8, - pub szDescription: [u8, ..WSADESCRIPTION_LEN + 1], - pub szSystemStatus: [u8, ..WSASYS_STATUS_LEN + 1], -} - -pub type LPWSADATA = *mut WSADATA; - -#[repr(C)] -pub struct fd_set { - fd_count: libc::c_uint, - fd_array: [libc::SOCKET, ..FD_SETSIZE], -} - -pub fn fd_set(set: &mut fd_set, s: libc::SOCKET) { - set.fd_array[set.fd_count as uint] = s; - set.fd_count += 1; -} - -#[link(name = "ws2_32")] -extern "system" { - pub fn WSAStartup(wVersionRequested: libc::WORD, - lpWSAData: LPWSADATA) -> libc::c_int; - pub fn WSAGetLastError() -> libc::c_int; - - pub fn ioctlsocket(s: libc::SOCKET, cmd: libc::c_long, - argp: *mut libc::c_ulong) -> libc::c_int; - pub fn select(nfds: libc::c_int, - readfds: *mut fd_set, - writefds: *mut fd_set, - exceptfds: *mut fd_set, - timeout: *mut libc::timeval) -> libc::c_int; - pub fn getsockopt(sockfd: libc::SOCKET, - level: libc::c_int, - optname: libc::c_int, - optval: *mut libc::c_char, - optlen: *mut libc::c_int) -> libc::c_int; - - pub fn CancelIo(hFile: libc::HANDLE) -> libc::BOOL; - pub fn CancelIoEx(hFile: libc::HANDLE, - lpOverlapped: libc::LPOVERLAPPED) -> libc::BOOL; -} - -pub mod compat { - use std::intrinsics::{atomic_store_relaxed, transmute}; - use std::iter::Iterator; - use libc::types::os::arch::extra::{LPCWSTR, HMODULE, LPCSTR, LPVOID}; - - extern "system" { - fn GetModuleHandleW(lpModuleName: LPCWSTR) -> HMODULE; - fn GetProcAddress(hModule: HMODULE, lpProcName: LPCSTR) -> LPVOID; - } - - // store_func() is idempotent, so using relaxed ordering for the atomics - // should be enough. This way, calling a function in this compatibility - // layer (after it's loaded) shouldn't be any slower than a regular DLL - // call. - unsafe fn store_func(ptr: *mut uint, module: &str, symbol: &str, fallback: uint) { - let module: Vec = module.utf16_units().collect(); - let module = module.append_one(0); - symbol.with_c_str(|symbol| { - let handle = GetModuleHandleW(module.as_ptr()); - let func: uint = transmute(GetProcAddress(handle, symbol)); - atomic_store_relaxed(ptr, if func == 0 { - fallback - } else { - func - }) - }) - } - - /// Macro for creating a compatibility fallback for a Windows function - /// - /// # Example - /// ``` - /// compat_fn!(adll32::SomeFunctionW(_arg: LPCWSTR) { - /// // Fallback implementation - /// }) - /// ``` - /// - /// Note that arguments unused by the fallback implementation should not be called `_` as - /// they are used to be passed to the real function if available. - macro_rules! compat_fn( - ($module:ident::$symbol:ident($($argname:ident: $argtype:ty),*) - -> $rettype:ty $fallback:block) => ( - #[inline(always)] - pub unsafe fn $symbol($($argname: $argtype),*) -> $rettype { - static mut ptr: extern "system" fn($($argname: $argtype),*) -> $rettype = thunk; - - extern "system" fn thunk($($argname: $argtype),*) -> $rettype { - unsafe { - ::io::c::compat::store_func(&mut ptr as *mut _ as *mut uint, - stringify!($module), - stringify!($symbol), - fallback as uint); - ::std::intrinsics::atomic_load_relaxed(&ptr)($($argname),*) - } - } - - extern "system" fn fallback($($argname: $argtype),*) -> $rettype $fallback - - ::std::intrinsics::atomic_load_relaxed(&ptr)($($argname),*) - } - ); - - ($module:ident::$symbol:ident($($argname:ident: $argtype:ty),*) $fallback:block) => ( - compat_fn!($module::$symbol($($argname: $argtype),*) -> () $fallback) - ) - ) - - /// Compatibility layer for functions in `kernel32.dll` - /// - /// Latest versions of Windows this is needed for: - /// - /// * `CreateSymbolicLinkW`: Windows XP, Windows Server 2003 - /// * `GetFinalPathNameByHandleW`: Windows XP, Windows Server 2003 - pub mod kernel32 { - use libc::types::os::arch::extra::{DWORD, LPCWSTR, BOOLEAN, HANDLE}; - use libc::consts::os::extra::ERROR_CALL_NOT_IMPLEMENTED; - - extern "system" { - fn SetLastError(dwErrCode: DWORD); - } - - compat_fn!(kernel32::CreateSymbolicLinkW(_lpSymlinkFileName: LPCWSTR, - _lpTargetFileName: LPCWSTR, - _dwFlags: DWORD) -> BOOLEAN { - unsafe { SetLastError(ERROR_CALL_NOT_IMPLEMENTED as DWORD); } - 0 - }) - - compat_fn!(kernel32::GetFinalPathNameByHandleW(_hFile: HANDLE, - _lpszFilePath: LPCWSTR, - _cchFilePath: DWORD, - _dwFlags: DWORD) -> DWORD { - unsafe { SetLastError(ERROR_CALL_NOT_IMPLEMENTED as DWORD); } - 0 - }) - } -} - -extern "system" { - // FIXME - pInputControl should be PCONSOLE_READCONSOLE_CONTROL - pub fn ReadConsoleW(hConsoleInput: libc::HANDLE, - lpBuffer: libc::LPVOID, - nNumberOfCharsToRead: libc::DWORD, - lpNumberOfCharsRead: libc::LPDWORD, - pInputControl: libc::LPVOID) -> libc::BOOL; - - pub fn WriteConsoleW(hConsoleOutput: libc::HANDLE, - lpBuffer: libc::types::os::arch::extra::LPCVOID, - nNumberOfCharsToWrite: libc::DWORD, - lpNumberOfCharsWritten: libc::LPDWORD, - lpReserved: libc::LPVOID) -> libc::BOOL; - - pub fn GetConsoleMode(hConsoleHandle: libc::HANDLE, - lpMode: libc::LPDWORD) -> libc::BOOL; - - pub fn SetConsoleMode(hConsoleHandle: libc::HANDLE, - lpMode: libc::DWORD) -> libc::BOOL; -} diff --git a/src/libnative/io/c_windows.rs b/src/libnative/io/c_windows.rs new file mode 100644 index 00000000000..80c9e91b48f --- /dev/null +++ b/src/libnative/io/c_windows.rs @@ -0,0 +1,207 @@ +// 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. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! C definitions used by libnative that don't belong in liblibc + +#![allow(type_overflow)] + +use libc; + +pub static WSADESCRIPTION_LEN: uint = 256; +pub static WSASYS_STATUS_LEN: uint = 128; +pub static FIONBIO: libc::c_long = 0x8004667e; +static FD_SETSIZE: uint = 64; +pub static MSG_DONTWAIT: libc::c_int = 0; +pub static ERROR_ILLEGAL_CHARACTER: libc::c_int = 582; +pub static ENABLE_ECHO_INPUT: libc::DWORD = 0x4; +pub static ENABLE_EXTENDED_FLAGS: libc::DWORD = 0x80; +pub static ENABLE_INSERT_MODE: libc::DWORD = 0x20; +pub static ENABLE_LINE_INPUT: libc::DWORD = 0x2; +pub static ENABLE_PROCESSED_INPUT: libc::DWORD = 0x1; +pub static ENABLE_QUICK_EDIT_MODE: libc::DWORD = 0x40; + +#[repr(C)] +#[cfg(target_arch = "x86")] +pub struct WSADATA { + pub wVersion: libc::WORD, + pub wHighVersion: libc::WORD, + pub szDescription: [u8, ..WSADESCRIPTION_LEN + 1], + pub szSystemStatus: [u8, ..WSASYS_STATUS_LEN + 1], + pub iMaxSockets: u16, + pub iMaxUdpDg: u16, + pub lpVendorInfo: *mut u8, +} +#[repr(C)] +#[cfg(target_arch = "x86_64")] +pub struct WSADATA { + pub wVersion: libc::WORD, + pub wHighVersion: libc::WORD, + pub iMaxSockets: u16, + pub iMaxUdpDg: u16, + pub lpVendorInfo: *mut u8, + pub szDescription: [u8, ..WSADESCRIPTION_LEN + 1], + pub szSystemStatus: [u8, ..WSASYS_STATUS_LEN + 1], +} + +pub type LPWSADATA = *mut WSADATA; + +#[repr(C)] +pub struct fd_set { + fd_count: libc::c_uint, + fd_array: [libc::SOCKET, ..FD_SETSIZE], +} + +pub fn fd_set(set: &mut fd_set, s: libc::SOCKET) { + set.fd_array[set.fd_count as uint] = s; + set.fd_count += 1; +} + +#[link(name = "ws2_32")] +extern "system" { + pub fn WSAStartup(wVersionRequested: libc::WORD, + lpWSAData: LPWSADATA) -> libc::c_int; + pub fn WSAGetLastError() -> libc::c_int; + + pub fn ioctlsocket(s: libc::SOCKET, cmd: libc::c_long, + argp: *mut libc::c_ulong) -> libc::c_int; + pub fn select(nfds: libc::c_int, + readfds: *mut fd_set, + writefds: *mut fd_set, + exceptfds: *mut fd_set, + timeout: *mut libc::timeval) -> libc::c_int; + pub fn getsockopt(sockfd: libc::SOCKET, + level: libc::c_int, + optname: libc::c_int, + optval: *mut libc::c_char, + optlen: *mut libc::c_int) -> libc::c_int; + + pub fn CancelIo(hFile: libc::HANDLE) -> libc::BOOL; + pub fn CancelIoEx(hFile: libc::HANDLE, + lpOverlapped: libc::LPOVERLAPPED) -> libc::BOOL; +} + +pub mod compat { + use std::intrinsics::{atomic_store_relaxed, transmute}; + use std::iter::Iterator; + use libc::types::os::arch::extra::{LPCWSTR, HMODULE, LPCSTR, LPVOID}; + + extern "system" { + fn GetModuleHandleW(lpModuleName: LPCWSTR) -> HMODULE; + fn GetProcAddress(hModule: HMODULE, lpProcName: LPCSTR) -> LPVOID; + } + + // store_func() is idempotent, so using relaxed ordering for the atomics + // should be enough. This way, calling a function in this compatibility + // layer (after it's loaded) shouldn't be any slower than a regular DLL + // call. + unsafe fn store_func(ptr: *mut uint, module: &str, symbol: &str, fallback: uint) { + let module: Vec = module.utf16_units().collect(); + let module = module.append_one(0); + symbol.with_c_str(|symbol| { + let handle = GetModuleHandleW(module.as_ptr()); + let func: uint = transmute(GetProcAddress(handle, symbol)); + atomic_store_relaxed(ptr, if func == 0 { + fallback + } else { + func + }) + }) + } + + /// Macro for creating a compatibility fallback for a Windows function + /// + /// # Example + /// ``` + /// compat_fn!(adll32::SomeFunctionW(_arg: LPCWSTR) { + /// // Fallback implementation + /// }) + /// ``` + /// + /// Note that arguments unused by the fallback implementation should not be called `_` as + /// they are used to be passed to the real function if available. + macro_rules! compat_fn( + ($module:ident::$symbol:ident($($argname:ident: $argtype:ty),*) + -> $rettype:ty $fallback:block) => ( + #[inline(always)] + pub unsafe fn $symbol($($argname: $argtype),*) -> $rettype { + static mut ptr: extern "system" fn($($argname: $argtype),*) -> $rettype = thunk; + + extern "system" fn thunk($($argname: $argtype),*) -> $rettype { + unsafe { + ::io::c::compat::store_func(&mut ptr as *mut _ as *mut uint, + stringify!($module), + stringify!($symbol), + fallback as uint); + ::std::intrinsics::atomic_load_relaxed(&ptr)($($argname),*) + } + } + + extern "system" fn fallback($($argname: $argtype),*) -> $rettype $fallback + + ::std::intrinsics::atomic_load_relaxed(&ptr)($($argname),*) + } + ); + + ($module:ident::$symbol:ident($($argname:ident: $argtype:ty),*) $fallback:block) => ( + compat_fn!($module::$symbol($($argname: $argtype),*) -> () $fallback) + ) + ) + + /// Compatibility layer for functions in `kernel32.dll` + /// + /// Latest versions of Windows this is needed for: + /// + /// * `CreateSymbolicLinkW`: Windows XP, Windows Server 2003 + /// * `GetFinalPathNameByHandleW`: Windows XP, Windows Server 2003 + pub mod kernel32 { + use libc::types::os::arch::extra::{DWORD, LPCWSTR, BOOLEAN, HANDLE}; + use libc::consts::os::extra::ERROR_CALL_NOT_IMPLEMENTED; + + extern "system" { + fn SetLastError(dwErrCode: DWORD); + } + + compat_fn!(kernel32::CreateSymbolicLinkW(_lpSymlinkFileName: LPCWSTR, + _lpTargetFileName: LPCWSTR, + _dwFlags: DWORD) -> BOOLEAN { + unsafe { SetLastError(ERROR_CALL_NOT_IMPLEMENTED as DWORD); } + 0 + }) + + compat_fn!(kernel32::GetFinalPathNameByHandleW(_hFile: HANDLE, + _lpszFilePath: LPCWSTR, + _cchFilePath: DWORD, + _dwFlags: DWORD) -> DWORD { + unsafe { SetLastError(ERROR_CALL_NOT_IMPLEMENTED as DWORD); } + 0 + }) + } +} + +extern "system" { + // FIXME - pInputControl should be PCONSOLE_READCONSOLE_CONTROL + pub fn ReadConsoleW(hConsoleInput: libc::HANDLE, + lpBuffer: libc::LPVOID, + nNumberOfCharsToRead: libc::DWORD, + lpNumberOfCharsRead: libc::LPDWORD, + pInputControl: libc::LPVOID) -> libc::BOOL; + + pub fn WriteConsoleW(hConsoleOutput: libc::HANDLE, + lpBuffer: libc::types::os::arch::extra::LPCVOID, + nNumberOfCharsToWrite: libc::DWORD, + lpNumberOfCharsWritten: libc::LPDWORD, + lpReserved: libc::LPVOID) -> libc::BOOL; + + pub fn GetConsoleMode(hConsoleHandle: libc::HANDLE, + lpMode: libc::LPDWORD) -> libc::BOOL; + + pub fn SetConsoleMode(hConsoleHandle: libc::HANDLE, + lpMode: libc::DWORD) -> libc::BOOL; +} diff --git a/src/libnative/io/file_win32.rs b/src/libnative/io/file_win32.rs deleted file mode 100644 index fe29c024529..00000000000 --- a/src/libnative/io/file_win32.rs +++ /dev/null @@ -1,526 +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 or the MIT license -// , at your -// option. This file may not be copied, modified, or distributed -// except according to those terms. - -//! Blocking win32-based file I/O - -use alloc::arc::Arc; -use libc::{c_int, c_void}; -use libc; -use std::c_str::CString; -use std::mem; -use std::os::win32::fill_utf16_buf_and_decode; -use std::ptr; -use std::rt::rtio; -use std::rt::rtio::{IoResult, IoError}; -use std::str; -use std::vec; - -pub type fd_t = libc::c_int; - -struct Inner { - fd: fd_t, - close_on_drop: bool, -} - -pub struct FileDesc { - inner: Arc -} - -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 - }) } - } - - pub fn inner_read(&mut self, buf: &mut [u8]) -> IoResult { - let mut read = 0; - let ret = unsafe { - libc::ReadFile(self.handle(), buf.as_ptr() as libc::LPVOID, - buf.len() as libc::DWORD, &mut read, - ptr::mut_null()) - }; - if ret != 0 { - Ok(read as uint) - } else { - Err(super::last_error()) - } - } - pub fn inner_write(&mut self, buf: &[u8]) -> IoResult<()> { - let mut cur = buf.as_ptr(); - let mut remaining = buf.len(); - while remaining > 0 { - let mut amt = 0; - let ret = unsafe { - libc::WriteFile(self.handle(), cur as libc::LPVOID, - remaining as libc::DWORD, &mut amt, - ptr::mut_null()) - }; - if ret != 0 { - remaining -= amt as uint; - cur = unsafe { cur.offset(amt as int) }; - } else { - return Err(super::last_error()) - } - } - Ok(()) - } - - pub fn fd(&self) -> fd_t { self.inner.fd } - - pub fn handle(&self) -> libc::HANDLE { - unsafe { libc::get_osfhandle(self.fd()) as libc::HANDLE } - } - - // 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 { - let whence = match style { - rtio::SeekSet => libc::FILE_BEGIN, - rtio::SeekEnd => libc::FILE_END, - rtio::SeekCur => libc::FILE_CURRENT, - }; - unsafe { - let mut newpos = 0; - 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 { - 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 { - 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 { - self.seek_common(pos, style) - } - - fn tell(&self) -> IoResult { - self.seek_common(0, rtio::SeekCur) - } - - fn fsync(&mut self) -> IoResult<()> { - super::mkerr_winbool(unsafe { - libc::FlushFileBuffers(self.handle()) - }) - } - - fn datasync(&mut self) -> IoResult<()> { return self.fsync(); } - - fn truncate(&mut self, offset: i64) -> IoResult<()> { - let orig_pos = try!(self.tell()); - let _ = try!(self.seek(offset, rtio::SeekSet)); - let ret = unsafe { - match libc::SetEndOfFile(self.handle()) { - 0 => Err(super::last_error()), - _ => Ok(()) - } - }; - let _ = self.seek(orig_pos as i64, rtio::SeekSet); - return ret; - } - - fn fstat(&mut self) -> IoResult { - 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 { - self.inner_read(buf) - } - fn write(&mut self, buf: &[u8]) -> IoResult<()> { - self.inner_write(buf) - } - fn clone(&self) -> Box { - box FileDesc { inner: self.inner.clone() } as Box - } - - // 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) {} - fn set_read_timeout(&mut self, _t: Option) {} - fn set_write_timeout(&mut self, _t: Option) {} -} - -impl rtio::RtioTTY for FileDesc { - fn read(&mut self, buf: &mut [u8]) -> IoResult { - 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 fn to_utf16(s: &CString) -> IoResult> { - match s.as_str() { - Some(s) => Ok(s.utf16_units().collect::>().append_one(0)), - None => Err(IoError { - code: libc::ERROR_INVALID_NAME as uint, - extra: 0, - detail: Some("valid unicode input required".to_string()), - }) - } -} - -pub fn open(path: &CString, fm: rtio::FileMode, fa: rtio::FileAccess) - -> IoResult { - // Flags passed to open_osfhandle - let flags = match fm { - rtio::Open => 0, - rtio::Append => libc::O_APPEND, - rtio::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, - }; - - 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 - }; - - // libuv has a good comment about this, but the basic idea is what we try to - // emulate unix semantics by enabling all sharing by allowing things such as - // deleting a file while it's still open. - let dwShareMode = libc::FILE_SHARE_READ | libc::FILE_SHARE_WRITE | - 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) => { - dwDesiredAccess |= libc::FILE_APPEND_DATA; - libc::OPEN_EXISTING - } - (rtio::Append, _) => { - dwDesiredAccess &= !libc::FILE_WRITE_DATA; - dwDesiredAccess |= libc::FILE_APPEND_DATA; - libc::OPEN_ALWAYS - } - }; - - let mut dwFlagsAndAttributes = libc::FILE_ATTRIBUTE_NORMAL; - // Compat with unix, this allows opening directories (see libuv) - dwFlagsAndAttributes |= libc::FILE_FLAG_BACKUP_SEMANTICS; - - let path = try!(to_utf16(path)); - let handle = unsafe { - libc::CreateFileW(path.as_ptr(), - dwDesiredAccess, - dwShareMode, - ptr::mut_null(), - dwCreationDisposition, - dwFlagsAndAttributes, - ptr::mut_null()) - }; - if handle == libc::INVALID_HANDLE_VALUE { - Err(super::last_error()) - } else { - let fd = unsafe { - libc::open_osfhandle(handle as libc::intptr_t, flags) - }; - if fd < 0 { - let _ = unsafe { libc::CloseHandle(handle) }; - Err(super::last_error()) - } else { - Ok(FileDesc::new(fd, true)) - } - } -} - -pub fn mkdir(p: &CString, _mode: uint) -> IoResult<()> { - let p = try!(to_utf16(p)); - super::mkerr_winbool(unsafe { - // FIXME: turn mode into something useful? #2623 - libc::CreateDirectoryW(p.as_ptr(), ptr::mut_null()) - }) -} - -pub fn readdir(p: &CString) -> IoResult> { - use std::rt::libc_heap::malloc_raw; - - fn prune(root: &CString, dirs: Vec) -> Vec { - let root = unsafe { CString::new(root.as_ptr(), false) }; - let root = Path::new(root); - - dirs.move_iter().filter(|path| { - path.as_vec() != b"." && path.as_vec() != b".." - }).map(|path| root.join(path).to_c_str()).collect() - } - - extern { - fn rust_list_dir_wfd_size() -> libc::size_t; - fn rust_list_dir_wfd_fp_buf(wfd: *mut libc::c_void) -> *const u16; - } - let star = Path::new(unsafe { - CString::new(p.as_ptr(), false) - }).join("*"); - let path = try!(to_utf16(&star.to_c_str())); - - unsafe { - let wfd_ptr = malloc_raw(rust_list_dir_wfd_size() as uint); - let find_handle = libc::FindFirstFileW(path.as_ptr(), - wfd_ptr as libc::HANDLE); - if find_handle != libc::INVALID_HANDLE_VALUE { - let mut paths = vec!(); - let mut more_files = 1 as libc::c_int; - while more_files != 0 { - let fp_buf = rust_list_dir_wfd_fp_buf(wfd_ptr as *mut c_void); - if fp_buf as uint == 0 { - fail!("os::list_dir() failure: got null ptr from wfd"); - } else { - let fp_vec = vec::raw::from_buf(fp_buf, libc::wcslen(fp_buf) as uint); - let fp_trimmed = str::truncate_utf16_at_nul(fp_vec.as_slice()); - let fp_str = String::from_utf16(fp_trimmed) - .expect("rust_list_dir_wfd_fp_buf returned invalid UTF-16"); - paths.push(Path::new(fp_str)); - } - more_files = libc::FindNextFileW(find_handle, - wfd_ptr as libc::HANDLE); - } - assert!(libc::FindClose(find_handle) != 0); - libc::free(wfd_ptr as *mut c_void); - Ok(prune(p, paths)) - } else { - Err(super::last_error()) - } - } -} - -pub fn unlink(p: &CString) -> IoResult<()> { - let p = try!(to_utf16(p)); - super::mkerr_winbool(unsafe { - libc::DeleteFileW(p.as_ptr()) - }) -} - -pub fn rename(old: &CString, new: &CString) -> 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) - }) -} - -pub fn chmod(p: &CString, mode: uint) -> IoResult<()> { - let p = try!(to_utf16(p)); - super::mkerr_libc(unsafe { - libc::wchmod(p.as_ptr(), mode as libc::c_int) - }) -} - -pub fn rmdir(p: &CString) -> IoResult<()> { - let p = try!(to_utf16(p)); - super::mkerr_libc(unsafe { libc::wrmdir(p.as_ptr()) }) -} - -pub fn chown(_p: &CString, _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 { - // FIXME: I have a feeling that this reads intermediate symlinks as well. - use io::c::compat::kernel32::GetFinalPathNameByHandleW; - let p = try!(to_utf16(p)); - let handle = unsafe { - libc::CreateFileW(p.as_ptr(), - libc::GENERIC_READ, - libc::FILE_SHARE_READ, - ptr::mut_null(), - libc::OPEN_EXISTING, - libc::FILE_ATTRIBUTE_NORMAL, - ptr::mut_null()) - }; - if handle == libc::INVALID_HANDLE_VALUE { - return Err(super::last_error()) - } - // Specify (sz - 1) because the documentation states that it's the size - // without the null pointer - let ret = fill_utf16_buf_and_decode(|buf, sz| unsafe { - GetFinalPathNameByHandleW(handle, - buf as *const u16, - sz - 1, - 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(s) => Ok(Path::new(s).to_c_str()), - 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; - let src = try!(to_utf16(src)); - let dst = try!(to_utf16(dst)); - super::mkerr_winbool(unsafe { - CreateSymbolicLinkW(dst.as_ptr(), src.as_ptr(), 0) as libc::BOOL - }) -} - -pub fn link(src: &CString, dst: &CString) -> IoResult<()> { - let src = try!(to_utf16(src)); - let dst = try!(to_utf16(dst)); - super::mkerr_winbool(unsafe { - libc::CreateHardLinkW(dst.as_ptr(), src.as_ptr(), ptr::mut_null()) - }) -} - -fn mkstat(stat: &libc::stat) -> rtio::FileStat { - rtio::FileStat { - size: stat.st_size as u64, - kind: stat.st_mode as u64, - perm: stat.st_mode as u64, - 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, - } -} - -pub fn stat(p: &CString) -> IoResult { - let mut stat: libc::stat = unsafe { mem::zeroed() }; - let p = try!(to_utf16(p)); - match unsafe { libc::wstat(p.as_ptr(), &mut stat) } { - 0 => Ok(mkstat(&stat)), - _ => Err(super::last_error()), - } -} - -pub fn lstat(_p: &CString) -> IoResult { - // FIXME: implementation is missing - Err(super::unimpl()) -} - -pub fn utime(p: &CString, 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 { - libc::wutime(p.as_ptr(), &mut buf) - }) -} diff --git a/src/libnative/io/file_windows.rs b/src/libnative/io/file_windows.rs new file mode 100644 index 00000000000..cb1d79b8397 --- /dev/null +++ b/src/libnative/io/file_windows.rs @@ -0,0 +1,526 @@ +// 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 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! Blocking Windows-based file I/O + +use alloc::arc::Arc; +use libc::{c_int, c_void}; +use libc; +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 std::vec; + +pub type fd_t = libc::c_int; + +struct Inner { + fd: fd_t, + close_on_drop: bool, +} + +pub struct FileDesc { + inner: Arc +} + +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 + }) } + } + + pub fn inner_read(&mut self, buf: &mut [u8]) -> IoResult { + let mut read = 0; + let ret = unsafe { + libc::ReadFile(self.handle(), buf.as_ptr() as libc::LPVOID, + buf.len() as libc::DWORD, &mut read, + ptr::mut_null()) + }; + if ret != 0 { + Ok(read as uint) + } else { + Err(super::last_error()) + } + } + pub fn inner_write(&mut self, buf: &[u8]) -> IoResult<()> { + let mut cur = buf.as_ptr(); + let mut remaining = buf.len(); + while remaining > 0 { + let mut amt = 0; + let ret = unsafe { + libc::WriteFile(self.handle(), cur as libc::LPVOID, + remaining as libc::DWORD, &mut amt, + ptr::mut_null()) + }; + if ret != 0 { + remaining -= amt as uint; + cur = unsafe { cur.offset(amt as int) }; + } else { + return Err(super::last_error()) + } + } + Ok(()) + } + + pub fn fd(&self) -> fd_t { self.inner.fd } + + pub fn handle(&self) -> libc::HANDLE { + unsafe { libc::get_osfhandle(self.fd()) as libc::HANDLE } + } + + // 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 { + let whence = match style { + rtio::SeekSet => libc::FILE_BEGIN, + rtio::SeekEnd => libc::FILE_END, + rtio::SeekCur => libc::FILE_CURRENT, + }; + unsafe { + let mut newpos = 0; + 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 { + 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 { + 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 { + self.seek_common(pos, style) + } + + fn tell(&self) -> IoResult { + self.seek_common(0, rtio::SeekCur) + } + + fn fsync(&mut self) -> IoResult<()> { + super::mkerr_winbool(unsafe { + libc::FlushFileBuffers(self.handle()) + }) + } + + fn datasync(&mut self) -> IoResult<()> { return self.fsync(); } + + fn truncate(&mut self, offset: i64) -> IoResult<()> { + let orig_pos = try!(self.tell()); + let _ = try!(self.seek(offset, rtio::SeekSet)); + let ret = unsafe { + match libc::SetEndOfFile(self.handle()) { + 0 => Err(super::last_error()), + _ => Ok(()) + } + }; + let _ = self.seek(orig_pos as i64, rtio::SeekSet); + return ret; + } + + fn fstat(&mut self) -> IoResult { + 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 { + self.inner_read(buf) + } + fn write(&mut self, buf: &[u8]) -> IoResult<()> { + self.inner_write(buf) + } + fn clone(&self) -> Box { + box FileDesc { inner: self.inner.clone() } as Box + } + + // 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) {} + fn set_read_timeout(&mut self, _t: Option) {} + fn set_write_timeout(&mut self, _t: Option) {} +} + +impl rtio::RtioTTY for FileDesc { + fn read(&mut self, buf: &mut [u8]) -> IoResult { + 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 fn to_utf16(s: &CString) -> IoResult> { + match s.as_str() { + Some(s) => Ok(s.utf16_units().collect::>().append_one(0)), + None => Err(IoError { + code: libc::ERROR_INVALID_NAME as uint, + extra: 0, + detail: Some("valid unicode input required".to_string()), + }) + } +} + +pub fn open(path: &CString, fm: rtio::FileMode, fa: rtio::FileAccess) + -> IoResult { + // Flags passed to open_osfhandle + let flags = match fm { + rtio::Open => 0, + rtio::Append => libc::O_APPEND, + rtio::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, + }; + + 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 + }; + + // libuv has a good comment about this, but the basic idea is what we try to + // emulate unix semantics by enabling all sharing by allowing things such as + // deleting a file while it's still open. + let dwShareMode = libc::FILE_SHARE_READ | libc::FILE_SHARE_WRITE | + 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) => { + dwDesiredAccess |= libc::FILE_APPEND_DATA; + libc::OPEN_EXISTING + } + (rtio::Append, _) => { + dwDesiredAccess &= !libc::FILE_WRITE_DATA; + dwDesiredAccess |= libc::FILE_APPEND_DATA; + libc::OPEN_ALWAYS + } + }; + + let mut dwFlagsAndAttributes = libc::FILE_ATTRIBUTE_NORMAL; + // Compat with unix, this allows opening directories (see libuv) + dwFlagsAndAttributes |= libc::FILE_FLAG_BACKUP_SEMANTICS; + + let path = try!(to_utf16(path)); + let handle = unsafe { + libc::CreateFileW(path.as_ptr(), + dwDesiredAccess, + dwShareMode, + ptr::mut_null(), + dwCreationDisposition, + dwFlagsAndAttributes, + ptr::mut_null()) + }; + if handle == libc::INVALID_HANDLE_VALUE { + Err(super::last_error()) + } else { + let fd = unsafe { + libc::open_osfhandle(handle as libc::intptr_t, flags) + }; + if fd < 0 { + let _ = unsafe { libc::CloseHandle(handle) }; + Err(super::last_error()) + } else { + Ok(FileDesc::new(fd, true)) + } + } +} + +pub fn mkdir(p: &CString, _mode: uint) -> IoResult<()> { + let p = try!(to_utf16(p)); + super::mkerr_winbool(unsafe { + // FIXME: turn mode into something useful? #2623 + libc::CreateDirectoryW(p.as_ptr(), ptr::mut_null()) + }) +} + +pub fn readdir(p: &CString) -> IoResult> { + use std::rt::libc_heap::malloc_raw; + + fn prune(root: &CString, dirs: Vec) -> Vec { + let root = unsafe { CString::new(root.as_ptr(), false) }; + let root = Path::new(root); + + dirs.move_iter().filter(|path| { + path.as_vec() != b"." && path.as_vec() != b".." + }).map(|path| root.join(path).to_c_str()).collect() + } + + extern { + fn rust_list_dir_wfd_size() -> libc::size_t; + fn rust_list_dir_wfd_fp_buf(wfd: *mut libc::c_void) -> *const u16; + } + let star = Path::new(unsafe { + CString::new(p.as_ptr(), false) + }).join("*"); + let path = try!(to_utf16(&star.to_c_str())); + + unsafe { + let wfd_ptr = malloc_raw(rust_list_dir_wfd_size() as uint); + let find_handle = libc::FindFirstFileW(path.as_ptr(), + wfd_ptr as libc::HANDLE); + if find_handle != libc::INVALID_HANDLE_VALUE { + let mut paths = vec!(); + let mut more_files = 1 as libc::c_int; + while more_files != 0 { + let fp_buf = rust_list_dir_wfd_fp_buf(wfd_ptr as *mut c_void); + if fp_buf as uint == 0 { + fail!("os::list_dir() failure: got null ptr from wfd"); + } else { + let fp_vec = vec::raw::from_buf(fp_buf, libc::wcslen(fp_buf) as uint); + let fp_trimmed = str::truncate_utf16_at_nul(fp_vec.as_slice()); + let fp_str = String::from_utf16(fp_trimmed) + .expect("rust_list_dir_wfd_fp_buf returned invalid UTF-16"); + paths.push(Path::new(fp_str)); + } + more_files = libc::FindNextFileW(find_handle, + wfd_ptr as libc::HANDLE); + } + assert!(libc::FindClose(find_handle) != 0); + libc::free(wfd_ptr as *mut c_void); + Ok(prune(p, paths)) + } else { + Err(super::last_error()) + } + } +} + +pub fn unlink(p: &CString) -> IoResult<()> { + let p = try!(to_utf16(p)); + super::mkerr_winbool(unsafe { + libc::DeleteFileW(p.as_ptr()) + }) +} + +pub fn rename(old: &CString, new: &CString) -> 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) + }) +} + +pub fn chmod(p: &CString, mode: uint) -> IoResult<()> { + let p = try!(to_utf16(p)); + super::mkerr_libc(unsafe { + libc::wchmod(p.as_ptr(), mode as libc::c_int) + }) +} + +pub fn rmdir(p: &CString) -> IoResult<()> { + let p = try!(to_utf16(p)); + super::mkerr_libc(unsafe { libc::wrmdir(p.as_ptr()) }) +} + +pub fn chown(_p: &CString, _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 { + // FIXME: I have a feeling that this reads intermediate symlinks as well. + use io::c::compat::kernel32::GetFinalPathNameByHandleW; + let p = try!(to_utf16(p)); + let handle = unsafe { + libc::CreateFileW(p.as_ptr(), + libc::GENERIC_READ, + libc::FILE_SHARE_READ, + ptr::mut_null(), + libc::OPEN_EXISTING, + libc::FILE_ATTRIBUTE_NORMAL, + ptr::mut_null()) + }; + if handle == libc::INVALID_HANDLE_VALUE { + return Err(super::last_error()) + } + // Specify (sz - 1) because the documentation states that it's the size + // without the null pointer + let ret = fill_utf16_buf_and_decode(|buf, sz| unsafe { + GetFinalPathNameByHandleW(handle, + buf as *const u16, + sz - 1, + 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(s) => Ok(Path::new(s).to_c_str()), + 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; + let src = try!(to_utf16(src)); + let dst = try!(to_utf16(dst)); + super::mkerr_winbool(unsafe { + CreateSymbolicLinkW(dst.as_ptr(), src.as_ptr(), 0) as libc::BOOL + }) +} + +pub fn link(src: &CString, dst: &CString) -> IoResult<()> { + let src = try!(to_utf16(src)); + let dst = try!(to_utf16(dst)); + super::mkerr_winbool(unsafe { + libc::CreateHardLinkW(dst.as_ptr(), src.as_ptr(), ptr::mut_null()) + }) +} + +fn mkstat(stat: &libc::stat) -> rtio::FileStat { + rtio::FileStat { + size: stat.st_size as u64, + kind: stat.st_mode as u64, + perm: stat.st_mode as u64, + 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, + } +} + +pub fn stat(p: &CString) -> IoResult { + let mut stat: libc::stat = unsafe { mem::zeroed() }; + let p = try!(to_utf16(p)); + match unsafe { libc::wstat(p.as_ptr(), &mut stat) } { + 0 => Ok(mkstat(&stat)), + _ => Err(super::last_error()), + } +} + +pub fn lstat(_p: &CString) -> IoResult { + // FIXME: implementation is missing + Err(super::unimpl()) +} + +pub fn utime(p: &CString, 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 { + libc::wutime(p.as_ptr(), &mut buf) + }) +} diff --git a/src/libnative/io/mod.rs b/src/libnative/io/mod.rs index 5d661007329..2dc6539b178 100644 --- a/src/libnative/io/mod.rs +++ b/src/libnative/io/mod.rs @@ -46,7 +46,7 @@ mod util; #[path = "file_unix.rs"] pub mod file; #[cfg(windows)] -#[path = "file_win32.rs"] +#[path = "file_windows.rs"] pub mod file; #[cfg(target_os = "macos")] @@ -59,8 +59,7 @@ pub mod file; pub mod timer; #[cfg(target_os = "windows")] -#[cfg(stage0, target_os = "win32")] // NOTE: Remove after snapshot -#[path = "timer_win32.rs"] +#[path = "timer_windows.rs"] pub mod timer; #[cfg(unix)] @@ -68,15 +67,15 @@ pub mod timer; pub mod pipe; #[cfg(windows)] -#[path = "pipe_win32.rs"] +#[path = "pipe_windows.rs"] pub mod pipe; #[cfg(windows)] -#[path = "tty_win32.rs"] +#[path = "tty_windows.rs"] mod tty; #[cfg(unix)] #[path = "c_unix.rs"] mod c; -#[cfg(windows)] #[path = "c_win32.rs"] mod c; +#[cfg(windows)] #[path = "c_windows.rs"] mod c; fn unimpl() -> IoError { #[cfg(unix)] use libc::ENOSYS as ERROR; diff --git a/src/libnative/io/pipe_win32.rs b/src/libnative/io/pipe_win32.rs deleted file mode 100644 index 717915e5d23..00000000000 --- a/src/libnative/io/pipe_win32.rs +++ /dev/null @@ -1,713 +0,0 @@ -// 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. -// -// Licensed under the Apache License, Version 2.0 or the MIT license -// , at your -// option. This file may not be copied, modified, or distributed -// except according to those terms. - -//! Named pipes implementation for windows -//! -//! If are unfortunate enough to be reading this code, I would like to first -//! apologize. This was my first encounter with windows named pipes, and it -//! didn't exactly turn out very cleanly. If you, too, are new to named pipes, -//! read on as I'll try to explain some fun things that I ran into. -//! -//! # Unix pipes vs Named pipes -//! -//! As with everything else, named pipes on windows are pretty different from -//! unix pipes on unix. On unix, you use one "server pipe" to accept new client -//! pipes. So long as this server pipe is active, new children pipes can -//! connect. On windows, you instead have a number of "server pipes", and each -//! of these server pipes can throughout their lifetime be attached to a client -//! or not. Once attached to a client, a server pipe may then disconnect at a -//! later date. -//! -//! # Accepting clients -//! -//! As with most other I/O interfaces, our Listener/Acceptor/Stream interfaces -//! are built around the unix flavors. This means that we have one "server -//! pipe" to which many clients can connect. In order to make this compatible -//! with the windows model, each connected client consumes ownership of a server -//! pipe, and then a new server pipe is created for the next client. -//! -//! Note that the server pipes attached to clients are never given back to the -//! listener for recycling. This could possibly be implemented with a channel so -//! the listener half can re-use server pipes, but for now I err'd on the simple -//! side of things. Each stream accepted by a listener will destroy the server -//! pipe after the stream is dropped. -//! -//! This model ends up having a small race or two, and you can find more details -//! on the `native_accept` method. -//! -//! # Simultaneous reads and writes -//! -//! In testing, I found that two simultaneous writes and two simultaneous reads -//! on a pipe ended up working out just fine, but problems were encountered when -//! a read was executed simultaneously with a write. After some googling around, -//! it sounded like named pipes just weren't built for this kind of interaction, -//! and the suggested solution was to use overlapped I/O. -//! -//! I don't really know what overlapped I/O is, but my basic understanding after -//! reading about it is that you have an external Event which is used to signal -//! I/O completion, passed around in some OVERLAPPED structures. As to what this -//! is, I'm not exactly sure. -//! -//! This problem implies that all named pipes are created with the -//! FILE_FLAG_OVERLAPPED option. This means that all of their I/O is -//! asynchronous. Each I/O operation has an associated OVERLAPPED structure, and -//! inside of this structure is a HANDLE from CreateEvent. After the I/O is -//! determined to be pending (may complete in the future), the -//! GetOverlappedResult function is used to block on the event, waiting for the -//! I/O to finish. -//! -//! This scheme ended up working well enough. There were two snags that I ran -//! into, however: -//! -//! * Each UnixStream instance needs its own read/write events to wait on. These -//! can't be shared among clones of the same stream because the documentation -//! states that it unsets the event when the I/O is started (would possibly -//! corrupt other events simultaneously waiting). For convenience's sake, -//! these events are lazily initialized. -//! -//! * Each server pipe needs to be created with FILE_FLAG_OVERLAPPED in addition -//! to all pipes created through `connect`. Notably this means that the -//! ConnectNamedPipe function is nonblocking, implying that the Listener needs -//! to have yet another event to do the actual blocking. -//! -//! # Conclusion -//! -//! The conclusion here is that I probably don't know the best way to work with -//! windows named pipes, but the solution here seems to work well enough to get -//! the test suite passing (the suite is in libstd), and that's good enough for -//! me! - -use alloc::arc::Arc; -use libc; -use std::c_str::CString; -use std::mem; -use std::os; -use std::ptr; -use std::rt::rtio; -use std::rt::rtio::{IoResult, IoError}; -use std::sync::atomic; -use std::rt::mutex; - -use super::c; -use super::util; -use super::file::to_utf16; - -struct Event(libc::HANDLE); - -impl Event { - fn new(manual_reset: bool, initial_state: bool) -> IoResult { - let event = unsafe { - libc::CreateEventW(ptr::mut_null(), - manual_reset as libc::BOOL, - initial_state as libc::BOOL, - ptr::null()) - }; - if event as uint == 0 { - Err(super::last_error()) - } else { - Ok(Event(event)) - } - } - - fn handle(&self) -> libc::HANDLE { let Event(handle) = *self; handle } -} - -impl Drop for Event { - fn drop(&mut self) { - unsafe { let _ = libc::CloseHandle(self.handle()); } - } -} - -struct Inner { - handle: libc::HANDLE, - lock: mutex::NativeMutex, - read_closed: atomic::AtomicBool, - write_closed: atomic::AtomicBool, -} - -impl Inner { - fn new(handle: libc::HANDLE) -> Inner { - Inner { - handle: handle, - lock: unsafe { mutex::NativeMutex::new() }, - read_closed: atomic::AtomicBool::new(false), - write_closed: atomic::AtomicBool::new(false), - } - } -} - -impl Drop for Inner { - fn drop(&mut self) { - unsafe { - let _ = libc::FlushFileBuffers(self.handle); - let _ = libc::CloseHandle(self.handle); - } - } -} - -unsafe fn pipe(name: *const u16, init: bool) -> libc::HANDLE { - libc::CreateNamedPipeW( - name, - libc::PIPE_ACCESS_DUPLEX | - if init {libc::FILE_FLAG_FIRST_PIPE_INSTANCE} else {0} | - libc::FILE_FLAG_OVERLAPPED, - libc::PIPE_TYPE_BYTE | libc::PIPE_READMODE_BYTE | - libc::PIPE_WAIT, - libc::PIPE_UNLIMITED_INSTANCES, - 65536, - 65536, - 0, - ptr::mut_null() - ) -} - -pub fn await(handle: libc::HANDLE, deadline: u64, - overlapped: &mut libc::OVERLAPPED) -> bool { - if deadline == 0 { return true } - - // If we've got a timeout, use WaitForSingleObject in tandem with CancelIo - // to figure out if we should indeed get the result. - let now = ::io::timer::now(); - let timeout = deadline < now || unsafe { - let ms = (deadline - now) as libc::DWORD; - let r = libc::WaitForSingleObject(overlapped.hEvent, - ms); - r != libc::WAIT_OBJECT_0 - }; - if timeout { - unsafe { let _ = c::CancelIo(handle); } - false - } else { - true - } -} - -fn epipe() -> IoError { - IoError { - code: libc::ERROR_BROKEN_PIPE as uint, - extra: 0, - detail: None, - } -} - -//////////////////////////////////////////////////////////////////////////////// -// Unix Streams -//////////////////////////////////////////////////////////////////////////////// - -pub struct UnixStream { - inner: Arc, - write: Option, - read: Option, - read_deadline: u64, - write_deadline: u64, -} - -impl UnixStream { - fn try_connect(p: *const u16) -> Option { - // Note that most of this is lifted from the libuv implementation. - // The idea is that if we fail to open a pipe in read/write mode - // that we try afterwards in just read or just write - let mut result = unsafe { - libc::CreateFileW(p, - libc::GENERIC_READ | libc::GENERIC_WRITE, - 0, - ptr::mut_null(), - libc::OPEN_EXISTING, - libc::FILE_FLAG_OVERLAPPED, - ptr::mut_null()) - }; - if result != libc::INVALID_HANDLE_VALUE { - return Some(result) - } - - let err = unsafe { libc::GetLastError() }; - if err == libc::ERROR_ACCESS_DENIED as libc::DWORD { - result = unsafe { - libc::CreateFileW(p, - libc::GENERIC_READ | libc::FILE_WRITE_ATTRIBUTES, - 0, - ptr::mut_null(), - libc::OPEN_EXISTING, - libc::FILE_FLAG_OVERLAPPED, - ptr::mut_null()) - }; - if result != libc::INVALID_HANDLE_VALUE { - return Some(result) - } - } - let err = unsafe { libc::GetLastError() }; - if err == libc::ERROR_ACCESS_DENIED as libc::DWORD { - result = unsafe { - libc::CreateFileW(p, - libc::GENERIC_WRITE | libc::FILE_READ_ATTRIBUTES, - 0, - ptr::mut_null(), - libc::OPEN_EXISTING, - libc::FILE_FLAG_OVERLAPPED, - ptr::mut_null()) - }; - if result != libc::INVALID_HANDLE_VALUE { - return Some(result) - } - } - None - } - - pub fn connect(addr: &CString, timeout: Option) -> IoResult { - let addr = try!(to_utf16(addr)); - let start = ::io::timer::now(); - loop { - match UnixStream::try_connect(addr.as_ptr()) { - Some(handle) => { - let inner = Inner::new(handle); - let mut mode = libc::PIPE_TYPE_BYTE | - libc::PIPE_READMODE_BYTE | - libc::PIPE_WAIT; - let ret = unsafe { - libc::SetNamedPipeHandleState(inner.handle, - &mut mode, - ptr::mut_null(), - ptr::mut_null()) - }; - return if ret == 0 { - Err(super::last_error()) - } else { - Ok(UnixStream { - inner: Arc::new(inner), - read: None, - write: None, - read_deadline: 0, - write_deadline: 0, - }) - } - } - None => {} - } - - // On windows, if you fail to connect, you may need to call the - // `WaitNamedPipe` function, and this is indicated with an error - // code of ERROR_PIPE_BUSY. - let code = unsafe { libc::GetLastError() }; - if code as int != libc::ERROR_PIPE_BUSY as int { - return Err(super::last_error()) - } - - match timeout { - Some(timeout) => { - let now = ::io::timer::now(); - let timed_out = (now - start) >= timeout || unsafe { - let ms = (timeout - (now - start)) as libc::DWORD; - libc::WaitNamedPipeW(addr.as_ptr(), ms) == 0 - }; - if timed_out { - return Err(util::timeout("connect timed out")) - } - } - - // An example I found on Microsoft's website used 20 - // seconds, libuv uses 30 seconds, hence we make the - // obvious choice of waiting for 25 seconds. - None => { - if unsafe { libc::WaitNamedPipeW(addr.as_ptr(), 25000) } == 0 { - return Err(super::last_error()) - } - } - } - } - } - - fn handle(&self) -> libc::HANDLE { self.inner.handle } - - fn read_closed(&self) -> bool { - self.inner.read_closed.load(atomic::SeqCst) - } - - fn write_closed(&self) -> bool { - self.inner.write_closed.load(atomic::SeqCst) - } - - fn cancel_io(&self) -> IoResult<()> { - match unsafe { c::CancelIoEx(self.handle(), ptr::mut_null()) } { - 0 if os::errno() == libc::ERROR_NOT_FOUND as uint => { - Ok(()) - } - 0 => Err(super::last_error()), - _ => Ok(()) - } - } -} - -impl rtio::RtioPipe for UnixStream { - fn read(&mut self, buf: &mut [u8]) -> IoResult { - if self.read.is_none() { - self.read = Some(try!(Event::new(true, false))); - } - - let mut bytes_read = 0; - let mut overlapped: libc::OVERLAPPED = unsafe { mem::zeroed() }; - overlapped.hEvent = self.read.get_ref().handle(); - - // Pre-flight check to see if the reading half has been closed. This - // must be done before issuing the ReadFile request, but after we - // acquire the lock. - // - // See comments in close_read() about why this lock is necessary. - let guard = unsafe { self.inner.lock.lock() }; - if self.read_closed() { - return Err(util::eof()) - } - - // Issue a nonblocking requests, succeeding quickly if it happened to - // succeed. - let ret = unsafe { - libc::ReadFile(self.handle(), - buf.as_ptr() as libc::LPVOID, - buf.len() as libc::DWORD, - &mut bytes_read, - &mut overlapped) - }; - if ret != 0 { return Ok(bytes_read as uint) } - - // If our errno doesn't say that the I/O is pending, then we hit some - // legitimate error and return immediately. - if os::errno() != libc::ERROR_IO_PENDING as uint { - return Err(super::last_error()) - } - - // Now that we've issued a successful nonblocking request, we need to - // wait for it to finish. This can all be done outside the lock because - // we'll see any invocation of CancelIoEx. We also call this in a loop - // because we're woken up if the writing half is closed, we just need to - // realize that the reading half wasn't closed and we go right back to - // sleep. - drop(guard); - loop { - // Process a timeout if one is pending - let succeeded = await(self.handle(), self.read_deadline, - &mut overlapped); - - let ret = unsafe { - libc::GetOverlappedResult(self.handle(), - &mut overlapped, - &mut bytes_read, - libc::TRUE) - }; - // If we succeeded, or we failed for some reason other than - // CancelIoEx, return immediately - if ret != 0 { return Ok(bytes_read as uint) } - if os::errno() != libc::ERROR_OPERATION_ABORTED as uint { - return Err(super::last_error()) - } - - // If the reading half is now closed, then we're done. If we woke up - // because the writing half was closed, keep trying. - if !succeeded { - return Err(util::timeout("read timed out")) - } - if self.read_closed() { - return Err(util::eof()) - } - } - } - - fn write(&mut self, buf: &[u8]) -> IoResult<()> { - if self.write.is_none() { - self.write = Some(try!(Event::new(true, false))); - } - - let mut offset = 0; - let mut overlapped: libc::OVERLAPPED = unsafe { mem::zeroed() }; - overlapped.hEvent = self.write.get_ref().handle(); - - while offset < buf.len() { - let mut bytes_written = 0; - - // This sequence below is quite similar to the one found in read(). - // Some careful looping is done to ensure that if close_write() is - // invoked we bail out early, and if close_read() is invoked we keep - // going after we woke up. - // - // See comments in close_read() about why this lock is necessary. - let guard = unsafe { self.inner.lock.lock() }; - if self.write_closed() { - return Err(epipe()) - } - let ret = unsafe { - libc::WriteFile(self.handle(), - buf.slice_from(offset).as_ptr() as libc::LPVOID, - (buf.len() - offset) as libc::DWORD, - &mut bytes_written, - &mut overlapped) - }; - let err = os::errno(); - drop(guard); - - if ret == 0 { - if err != libc::ERROR_IO_PENDING as uint { - return Err(IoError { - code: err as uint, - extra: 0, - detail: Some(os::error_string(err as uint)), - }) - } - // Process a timeout if one is pending - let succeeded = await(self.handle(), self.write_deadline, - &mut overlapped); - let ret = unsafe { - libc::GetOverlappedResult(self.handle(), - &mut overlapped, - &mut bytes_written, - libc::TRUE) - }; - // If we weren't aborted, this was a legit error, if we were - // aborted, then check to see if the write half was actually - // closed or whether we woke up from the read half closing. - if ret == 0 { - if os::errno() != libc::ERROR_OPERATION_ABORTED as uint { - return Err(super::last_error()) - } - if !succeeded { - let amt = offset + bytes_written as uint; - return if amt > 0 { - Err(IoError { - code: libc::ERROR_OPERATION_ABORTED as uint, - extra: amt, - detail: Some("short write during write".to_string()), - }) - } else { - Err(util::timeout("write timed out")) - } - } - if self.write_closed() { - return Err(epipe()) - } - continue // retry - } - } - offset += bytes_written as uint; - } - Ok(()) - } - - fn clone(&self) -> Box { - box UnixStream { - inner: self.inner.clone(), - read: None, - write: None, - read_deadline: 0, - write_deadline: 0, - } as Box - } - - fn close_read(&mut self) -> IoResult<()> { - // On windows, there's no actual shutdown() method for pipes, so we're - // forced to emulate the behavior manually at the application level. To - // do this, we need to both cancel any pending requests, as well as - // prevent all future requests from succeeding. These two operations are - // not atomic with respect to one another, so we must use a lock to do - // so. - // - // The read() code looks like: - // - // 1. Make sure the pipe is still open - // 2. Submit a read request - // 3. Wait for the read request to finish - // - // The race this lock is preventing is if another thread invokes - // close_read() between steps 1 and 2. By atomically executing steps 1 - // and 2 with a lock with respect to close_read(), we're guaranteed that - // no thread will erroneously sit in a read forever. - let _guard = unsafe { self.inner.lock.lock() }; - self.inner.read_closed.store(true, atomic::SeqCst); - self.cancel_io() - } - - fn close_write(&mut self) -> IoResult<()> { - // see comments in close_read() for why this lock is necessary - let _guard = unsafe { self.inner.lock.lock() }; - self.inner.write_closed.store(true, atomic::SeqCst); - self.cancel_io() - } - - fn set_timeout(&mut self, timeout: Option) { - let deadline = timeout.map(|a| ::io::timer::now() + a).unwrap_or(0); - self.read_deadline = deadline; - self.write_deadline = deadline; - } - fn set_read_timeout(&mut self, timeout: Option) { - self.read_deadline = timeout.map(|a| ::io::timer::now() + a).unwrap_or(0); - } - fn set_write_timeout(&mut self, timeout: Option) { - self.write_deadline = timeout.map(|a| ::io::timer::now() + a).unwrap_or(0); - } -} - -//////////////////////////////////////////////////////////////////////////////// -// Unix Listener -//////////////////////////////////////////////////////////////////////////////// - -pub struct UnixListener { - handle: libc::HANDLE, - name: CString, -} - -impl UnixListener { - pub fn bind(addr: &CString) -> IoResult { - // Although we technically don't need the pipe until much later, we - // create the initial handle up front to test the validity of the name - // and such. - let addr_v = try!(to_utf16(addr)); - let ret = unsafe { pipe(addr_v.as_ptr(), true) }; - if ret == libc::INVALID_HANDLE_VALUE { - Err(super::last_error()) - } else { - Ok(UnixListener { handle: ret, name: addr.clone() }) - } - } - - pub fn native_listen(self) -> IoResult { - Ok(UnixAcceptor { - listener: self, - event: try!(Event::new(true, false)), - deadline: 0, - }) - } -} - -impl Drop for UnixListener { - fn drop(&mut self) { - unsafe { let _ = libc::CloseHandle(self.handle); } - } -} - -impl rtio::RtioUnixListener for UnixListener { - fn listen(self: Box) - -> IoResult> { - self.native_listen().map(|a| { - box a as Box - }) - } -} - -pub struct UnixAcceptor { - listener: UnixListener, - event: Event, - deadline: u64, -} - -impl UnixAcceptor { - pub fn native_accept(&mut self) -> IoResult { - // This function has some funky implementation details when working with - // unix pipes. On windows, each server named pipe handle can be - // connected to a one or zero clients. To the best of my knowledge, a - // named server is considered active and present if there exists at - // least one server named pipe for it. - // - // The model of this function is to take the current known server - // handle, connect a client to it, and then transfer ownership to the - // UnixStream instance. The next time accept() is invoked, it'll need a - // different server handle to connect a client to. - // - // Note that there is a possible race here. Once our server pipe is - // handed off to a `UnixStream` object, the stream could be closed, - // meaning that there would be no active server pipes, hence even though - // we have a valid `UnixAcceptor`, no one can connect to it. For this - // reason, we generate the next accept call's server pipe at the end of - // this function call. - // - // This provides us an invariant that we always have at least one server - // connection open at a time, meaning that all connects to this acceptor - // should succeed while this is active. - // - // The actual implementation of doing this is a little tricky. Once a - // server pipe is created, a client can connect to it at any time. I - // assume that which server a client connects to is nondeterministic, so - // we also need to guarantee that the only server able to be connected - // to is the one that we're calling ConnectNamedPipe on. This means that - // we have to create the second server pipe *after* we've already - // accepted a connection. In order to at least somewhat gracefully - // handle errors, this means that if the second server pipe creation - // fails that we disconnect the connected client and then just keep - // using the original server pipe. - let handle = self.listener.handle; - - let name = try!(to_utf16(&self.listener.name)); - - // Once we've got a "server handle", we need to wait for a client to - // connect. The ConnectNamedPipe function will block this thread until - // someone on the other end connects. This function can "fail" if a - // client connects after we created the pipe but before we got down - // here. Thanks windows. - let mut overlapped: libc::OVERLAPPED = unsafe { mem::zeroed() }; - overlapped.hEvent = self.event.handle(); - if unsafe { libc::ConnectNamedPipe(handle, &mut overlapped) == 0 } { - let mut err = unsafe { libc::GetLastError() }; - - if err == libc::ERROR_IO_PENDING as libc::DWORD { - // Process a timeout if one is pending - let _ = await(handle, self.deadline, &mut overlapped); - - // This will block until the overlapped I/O is completed. The - // timeout was previously handled, so this will either block in - // the normal case or succeed very quickly in the timeout case. - let ret = unsafe { - let mut transfer = 0; - libc::GetOverlappedResult(handle, - &mut overlapped, - &mut transfer, - libc::TRUE) - }; - if ret == 0 { - err = unsafe { libc::GetLastError() }; - } else { - // we succeeded, bypass the check below - err = libc::ERROR_PIPE_CONNECTED as libc::DWORD; - } - } - if err != libc::ERROR_PIPE_CONNECTED as libc::DWORD { - return Err(super::last_error()) - } - } - - // Now that we've got a connected client to our handle, we need to - // create a second server pipe. If this fails, we disconnect the - // connected client and return an error (see comments above). - let new_handle = unsafe { pipe(name.as_ptr(), false) }; - if new_handle == libc::INVALID_HANDLE_VALUE { - let ret = Err(super::last_error()); - // If our disconnection fails, then there's not really a whole lot - // that we can do, so fail the task. - let err = unsafe { libc::DisconnectNamedPipe(handle) }; - assert!(err != 0); - return ret; - } else { - self.listener.handle = new_handle; - } - - // Transfer ownership of our handle into this stream - Ok(UnixStream { - inner: Arc::new(Inner::new(handle)), - read: None, - write: None, - read_deadline: 0, - write_deadline: 0, - }) - } -} - -impl rtio::RtioUnixAcceptor for UnixAcceptor { - fn accept(&mut self) -> IoResult> { - self.native_accept().map(|s| box s as Box) - } - fn set_timeout(&mut self, timeout: Option) { - self.deadline = timeout.map(|i| i + ::io::timer::now()).unwrap_or(0); - } -} - diff --git a/src/libnative/io/pipe_windows.rs b/src/libnative/io/pipe_windows.rs new file mode 100644 index 00000000000..717915e5d23 --- /dev/null +++ b/src/libnative/io/pipe_windows.rs @@ -0,0 +1,713 @@ +// 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. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! Named pipes implementation for windows +//! +//! If are unfortunate enough to be reading this code, I would like to first +//! apologize. This was my first encounter with windows named pipes, and it +//! didn't exactly turn out very cleanly. If you, too, are new to named pipes, +//! read on as I'll try to explain some fun things that I ran into. +//! +//! # Unix pipes vs Named pipes +//! +//! As with everything else, named pipes on windows are pretty different from +//! unix pipes on unix. On unix, you use one "server pipe" to accept new client +//! pipes. So long as this server pipe is active, new children pipes can +//! connect. On windows, you instead have a number of "server pipes", and each +//! of these server pipes can throughout their lifetime be attached to a client +//! or not. Once attached to a client, a server pipe may then disconnect at a +//! later date. +//! +//! # Accepting clients +//! +//! As with most other I/O interfaces, our Listener/Acceptor/Stream interfaces +//! are built around the unix flavors. This means that we have one "server +//! pipe" to which many clients can connect. In order to make this compatible +//! with the windows model, each connected client consumes ownership of a server +//! pipe, and then a new server pipe is created for the next client. +//! +//! Note that the server pipes attached to clients are never given back to the +//! listener for recycling. This could possibly be implemented with a channel so +//! the listener half can re-use server pipes, but for now I err'd on the simple +//! side of things. Each stream accepted by a listener will destroy the server +//! pipe after the stream is dropped. +//! +//! This model ends up having a small race or two, and you can find more details +//! on the `native_accept` method. +//! +//! # Simultaneous reads and writes +//! +//! In testing, I found that two simultaneous writes and two simultaneous reads +//! on a pipe ended up working out just fine, but problems were encountered when +//! a read was executed simultaneously with a write. After some googling around, +//! it sounded like named pipes just weren't built for this kind of interaction, +//! and the suggested solution was to use overlapped I/O. +//! +//! I don't really know what overlapped I/O is, but my basic understanding after +//! reading about it is that you have an external Event which is used to signal +//! I/O completion, passed around in some OVERLAPPED structures. As to what this +//! is, I'm not exactly sure. +//! +//! This problem implies that all named pipes are created with the +//! FILE_FLAG_OVERLAPPED option. This means that all of their I/O is +//! asynchronous. Each I/O operation has an associated OVERLAPPED structure, and +//! inside of this structure is a HANDLE from CreateEvent. After the I/O is +//! determined to be pending (may complete in the future), the +//! GetOverlappedResult function is used to block on the event, waiting for the +//! I/O to finish. +//! +//! This scheme ended up working well enough. There were two snags that I ran +//! into, however: +//! +//! * Each UnixStream instance needs its own read/write events to wait on. These +//! can't be shared among clones of the same stream because the documentation +//! states that it unsets the event when the I/O is started (would possibly +//! corrupt other events simultaneously waiting). For convenience's sake, +//! these events are lazily initialized. +//! +//! * Each server pipe needs to be created with FILE_FLAG_OVERLAPPED in addition +//! to all pipes created through `connect`. Notably this means that the +//! ConnectNamedPipe function is nonblocking, implying that the Listener needs +//! to have yet another event to do the actual blocking. +//! +//! # Conclusion +//! +//! The conclusion here is that I probably don't know the best way to work with +//! windows named pipes, but the solution here seems to work well enough to get +//! the test suite passing (the suite is in libstd), and that's good enough for +//! me! + +use alloc::arc::Arc; +use libc; +use std::c_str::CString; +use std::mem; +use std::os; +use std::ptr; +use std::rt::rtio; +use std::rt::rtio::{IoResult, IoError}; +use std::sync::atomic; +use std::rt::mutex; + +use super::c; +use super::util; +use super::file::to_utf16; + +struct Event(libc::HANDLE); + +impl Event { + fn new(manual_reset: bool, initial_state: bool) -> IoResult { + let event = unsafe { + libc::CreateEventW(ptr::mut_null(), + manual_reset as libc::BOOL, + initial_state as libc::BOOL, + ptr::null()) + }; + if event as uint == 0 { + Err(super::last_error()) + } else { + Ok(Event(event)) + } + } + + fn handle(&self) -> libc::HANDLE { let Event(handle) = *self; handle } +} + +impl Drop for Event { + fn drop(&mut self) { + unsafe { let _ = libc::CloseHandle(self.handle()); } + } +} + +struct Inner { + handle: libc::HANDLE, + lock: mutex::NativeMutex, + read_closed: atomic::AtomicBool, + write_closed: atomic::AtomicBool, +} + +impl Inner { + fn new(handle: libc::HANDLE) -> Inner { + Inner { + handle: handle, + lock: unsafe { mutex::NativeMutex::new() }, + read_closed: atomic::AtomicBool::new(false), + write_closed: atomic::AtomicBool::new(false), + } + } +} + +impl Drop for Inner { + fn drop(&mut self) { + unsafe { + let _ = libc::FlushFileBuffers(self.handle); + let _ = libc::CloseHandle(self.handle); + } + } +} + +unsafe fn pipe(name: *const u16, init: bool) -> libc::HANDLE { + libc::CreateNamedPipeW( + name, + libc::PIPE_ACCESS_DUPLEX | + if init {libc::FILE_FLAG_FIRST_PIPE_INSTANCE} else {0} | + libc::FILE_FLAG_OVERLAPPED, + libc::PIPE_TYPE_BYTE | libc::PIPE_READMODE_BYTE | + libc::PIPE_WAIT, + libc::PIPE_UNLIMITED_INSTANCES, + 65536, + 65536, + 0, + ptr::mut_null() + ) +} + +pub fn await(handle: libc::HANDLE, deadline: u64, + overlapped: &mut libc::OVERLAPPED) -> bool { + if deadline == 0 { return true } + + // If we've got a timeout, use WaitForSingleObject in tandem with CancelIo + // to figure out if we should indeed get the result. + let now = ::io::timer::now(); + let timeout = deadline < now || unsafe { + let ms = (deadline - now) as libc::DWORD; + let r = libc::WaitForSingleObject(overlapped.hEvent, + ms); + r != libc::WAIT_OBJECT_0 + }; + if timeout { + unsafe { let _ = c::CancelIo(handle); } + false + } else { + true + } +} + +fn epipe() -> IoError { + IoError { + code: libc::ERROR_BROKEN_PIPE as uint, + extra: 0, + detail: None, + } +} + +//////////////////////////////////////////////////////////////////////////////// +// Unix Streams +//////////////////////////////////////////////////////////////////////////////// + +pub struct UnixStream { + inner: Arc, + write: Option, + read: Option, + read_deadline: u64, + write_deadline: u64, +} + +impl UnixStream { + fn try_connect(p: *const u16) -> Option { + // Note that most of this is lifted from the libuv implementation. + // The idea is that if we fail to open a pipe in read/write mode + // that we try afterwards in just read or just write + let mut result = unsafe { + libc::CreateFileW(p, + libc::GENERIC_READ | libc::GENERIC_WRITE, + 0, + ptr::mut_null(), + libc::OPEN_EXISTING, + libc::FILE_FLAG_OVERLAPPED, + ptr::mut_null()) + }; + if result != libc::INVALID_HANDLE_VALUE { + return Some(result) + } + + let err = unsafe { libc::GetLastError() }; + if err == libc::ERROR_ACCESS_DENIED as libc::DWORD { + result = unsafe { + libc::CreateFileW(p, + libc::GENERIC_READ | libc::FILE_WRITE_ATTRIBUTES, + 0, + ptr::mut_null(), + libc::OPEN_EXISTING, + libc::FILE_FLAG_OVERLAPPED, + ptr::mut_null()) + }; + if result != libc::INVALID_HANDLE_VALUE { + return Some(result) + } + } + let err = unsafe { libc::GetLastError() }; + if err == libc::ERROR_ACCESS_DENIED as libc::DWORD { + result = unsafe { + libc::CreateFileW(p, + libc::GENERIC_WRITE | libc::FILE_READ_ATTRIBUTES, + 0, + ptr::mut_null(), + libc::OPEN_EXISTING, + libc::FILE_FLAG_OVERLAPPED, + ptr::mut_null()) + }; + if result != libc::INVALID_HANDLE_VALUE { + return Some(result) + } + } + None + } + + pub fn connect(addr: &CString, timeout: Option) -> IoResult { + let addr = try!(to_utf16(addr)); + let start = ::io::timer::now(); + loop { + match UnixStream::try_connect(addr.as_ptr()) { + Some(handle) => { + let inner = Inner::new(handle); + let mut mode = libc::PIPE_TYPE_BYTE | + libc::PIPE_READMODE_BYTE | + libc::PIPE_WAIT; + let ret = unsafe { + libc::SetNamedPipeHandleState(inner.handle, + &mut mode, + ptr::mut_null(), + ptr::mut_null()) + }; + return if ret == 0 { + Err(super::last_error()) + } else { + Ok(UnixStream { + inner: Arc::new(inner), + read: None, + write: None, + read_deadline: 0, + write_deadline: 0, + }) + } + } + None => {} + } + + // On windows, if you fail to connect, you may need to call the + // `WaitNamedPipe` function, and this is indicated with an error + // code of ERROR_PIPE_BUSY. + let code = unsafe { libc::GetLastError() }; + if code as int != libc::ERROR_PIPE_BUSY as int { + return Err(super::last_error()) + } + + match timeout { + Some(timeout) => { + let now = ::io::timer::now(); + let timed_out = (now - start) >= timeout || unsafe { + let ms = (timeout - (now - start)) as libc::DWORD; + libc::WaitNamedPipeW(addr.as_ptr(), ms) == 0 + }; + if timed_out { + return Err(util::timeout("connect timed out")) + } + } + + // An example I found on Microsoft's website used 20 + // seconds, libuv uses 30 seconds, hence we make the + // obvious choice of waiting for 25 seconds. + None => { + if unsafe { libc::WaitNamedPipeW(addr.as_ptr(), 25000) } == 0 { + return Err(super::last_error()) + } + } + } + } + } + + fn handle(&self) -> libc::HANDLE { self.inner.handle } + + fn read_closed(&self) -> bool { + self.inner.read_closed.load(atomic::SeqCst) + } + + fn write_closed(&self) -> bool { + self.inner.write_closed.load(atomic::SeqCst) + } + + fn cancel_io(&self) -> IoResult<()> { + match unsafe { c::CancelIoEx(self.handle(), ptr::mut_null()) } { + 0 if os::errno() == libc::ERROR_NOT_FOUND as uint => { + Ok(()) + } + 0 => Err(super::last_error()), + _ => Ok(()) + } + } +} + +impl rtio::RtioPipe for UnixStream { + fn read(&mut self, buf: &mut [u8]) -> IoResult { + if self.read.is_none() { + self.read = Some(try!(Event::new(true, false))); + } + + let mut bytes_read = 0; + let mut overlapped: libc::OVERLAPPED = unsafe { mem::zeroed() }; + overlapped.hEvent = self.read.get_ref().handle(); + + // Pre-flight check to see if the reading half has been closed. This + // must be done before issuing the ReadFile request, but after we + // acquire the lock. + // + // See comments in close_read() about why this lock is necessary. + let guard = unsafe { self.inner.lock.lock() }; + if self.read_closed() { + return Err(util::eof()) + } + + // Issue a nonblocking requests, succeeding quickly if it happened to + // succeed. + let ret = unsafe { + libc::ReadFile(self.handle(), + buf.as_ptr() as libc::LPVOID, + buf.len() as libc::DWORD, + &mut bytes_read, + &mut overlapped) + }; + if ret != 0 { return Ok(bytes_read as uint) } + + // If our errno doesn't say that the I/O is pending, then we hit some + // legitimate error and return immediately. + if os::errno() != libc::ERROR_IO_PENDING as uint { + return Err(super::last_error()) + } + + // Now that we've issued a successful nonblocking request, we need to + // wait for it to finish. This can all be done outside the lock because + // we'll see any invocation of CancelIoEx. We also call this in a loop + // because we're woken up if the writing half is closed, we just need to + // realize that the reading half wasn't closed and we go right back to + // sleep. + drop(guard); + loop { + // Process a timeout if one is pending + let succeeded = await(self.handle(), self.read_deadline, + &mut overlapped); + + let ret = unsafe { + libc::GetOverlappedResult(self.handle(), + &mut overlapped, + &mut bytes_read, + libc::TRUE) + }; + // If we succeeded, or we failed for some reason other than + // CancelIoEx, return immediately + if ret != 0 { return Ok(bytes_read as uint) } + if os::errno() != libc::ERROR_OPERATION_ABORTED as uint { + return Err(super::last_error()) + } + + // If the reading half is now closed, then we're done. If we woke up + // because the writing half was closed, keep trying. + if !succeeded { + return Err(util::timeout("read timed out")) + } + if self.read_closed() { + return Err(util::eof()) + } + } + } + + fn write(&mut self, buf: &[u8]) -> IoResult<()> { + if self.write.is_none() { + self.write = Some(try!(Event::new(true, false))); + } + + let mut offset = 0; + let mut overlapped: libc::OVERLAPPED = unsafe { mem::zeroed() }; + overlapped.hEvent = self.write.get_ref().handle(); + + while offset < buf.len() { + let mut bytes_written = 0; + + // This sequence below is quite similar to the one found in read(). + // Some careful looping is done to ensure that if close_write() is + // invoked we bail out early, and if close_read() is invoked we keep + // going after we woke up. + // + // See comments in close_read() about why this lock is necessary. + let guard = unsafe { self.inner.lock.lock() }; + if self.write_closed() { + return Err(epipe()) + } + let ret = unsafe { + libc::WriteFile(self.handle(), + buf.slice_from(offset).as_ptr() as libc::LPVOID, + (buf.len() - offset) as libc::DWORD, + &mut bytes_written, + &mut overlapped) + }; + let err = os::errno(); + drop(guard); + + if ret == 0 { + if err != libc::ERROR_IO_PENDING as uint { + return Err(IoError { + code: err as uint, + extra: 0, + detail: Some(os::error_string(err as uint)), + }) + } + // Process a timeout if one is pending + let succeeded = await(self.handle(), self.write_deadline, + &mut overlapped); + let ret = unsafe { + libc::GetOverlappedResult(self.handle(), + &mut overlapped, + &mut bytes_written, + libc::TRUE) + }; + // If we weren't aborted, this was a legit error, if we were + // aborted, then check to see if the write half was actually + // closed or whether we woke up from the read half closing. + if ret == 0 { + if os::errno() != libc::ERROR_OPERATION_ABORTED as uint { + return Err(super::last_error()) + } + if !succeeded { + let amt = offset + bytes_written as uint; + return if amt > 0 { + Err(IoError { + code: libc::ERROR_OPERATION_ABORTED as uint, + extra: amt, + detail: Some("short write during write".to_string()), + }) + } else { + Err(util::timeout("write timed out")) + } + } + if self.write_closed() { + return Err(epipe()) + } + continue // retry + } + } + offset += bytes_written as uint; + } + Ok(()) + } + + fn clone(&self) -> Box { + box UnixStream { + inner: self.inner.clone(), + read: None, + write: None, + read_deadline: 0, + write_deadline: 0, + } as Box + } + + fn close_read(&mut self) -> IoResult<()> { + // On windows, there's no actual shutdown() method for pipes, so we're + // forced to emulate the behavior manually at the application level. To + // do this, we need to both cancel any pending requests, as well as + // prevent all future requests from succeeding. These two operations are + // not atomic with respect to one another, so we must use a lock to do + // so. + // + // The read() code looks like: + // + // 1. Make sure the pipe is still open + // 2. Submit a read request + // 3. Wait for the read request to finish + // + // The race this lock is preventing is if another thread invokes + // close_read() between steps 1 and 2. By atomically executing steps 1 + // and 2 with a lock with respect to close_read(), we're guaranteed that + // no thread will erroneously sit in a read forever. + let _guard = unsafe { self.inner.lock.lock() }; + self.inner.read_closed.store(true, atomic::SeqCst); + self.cancel_io() + } + + fn close_write(&mut self) -> IoResult<()> { + // see comments in close_read() for why this lock is necessary + let _guard = unsafe { self.inner.lock.lock() }; + self.inner.write_closed.store(true, atomic::SeqCst); + self.cancel_io() + } + + fn set_timeout(&mut self, timeout: Option) { + let deadline = timeout.map(|a| ::io::timer::now() + a).unwrap_or(0); + self.read_deadline = deadline; + self.write_deadline = deadline; + } + fn set_read_timeout(&mut self, timeout: Option) { + self.read_deadline = timeout.map(|a| ::io::timer::now() + a).unwrap_or(0); + } + fn set_write_timeout(&mut self, timeout: Option) { + self.write_deadline = timeout.map(|a| ::io::timer::now() + a).unwrap_or(0); + } +} + +//////////////////////////////////////////////////////////////////////////////// +// Unix Listener +//////////////////////////////////////////////////////////////////////////////// + +pub struct UnixListener { + handle: libc::HANDLE, + name: CString, +} + +impl UnixListener { + pub fn bind(addr: &CString) -> IoResult { + // Although we technically don't need the pipe until much later, we + // create the initial handle up front to test the validity of the name + // and such. + let addr_v = try!(to_utf16(addr)); + let ret = unsafe { pipe(addr_v.as_ptr(), true) }; + if ret == libc::INVALID_HANDLE_VALUE { + Err(super::last_error()) + } else { + Ok(UnixListener { handle: ret, name: addr.clone() }) + } + } + + pub fn native_listen(self) -> IoResult { + Ok(UnixAcceptor { + listener: self, + event: try!(Event::new(true, false)), + deadline: 0, + }) + } +} + +impl Drop for UnixListener { + fn drop(&mut self) { + unsafe { let _ = libc::CloseHandle(self.handle); } + } +} + +impl rtio::RtioUnixListener for UnixListener { + fn listen(self: Box) + -> IoResult> { + self.native_listen().map(|a| { + box a as Box + }) + } +} + +pub struct UnixAcceptor { + listener: UnixListener, + event: Event, + deadline: u64, +} + +impl UnixAcceptor { + pub fn native_accept(&mut self) -> IoResult { + // This function has some funky implementation details when working with + // unix pipes. On windows, each server named pipe handle can be + // connected to a one or zero clients. To the best of my knowledge, a + // named server is considered active and present if there exists at + // least one server named pipe for it. + // + // The model of this function is to take the current known server + // handle, connect a client to it, and then transfer ownership to the + // UnixStream instance. The next time accept() is invoked, it'll need a + // different server handle to connect a client to. + // + // Note that there is a possible race here. Once our server pipe is + // handed off to a `UnixStream` object, the stream could be closed, + // meaning that there would be no active server pipes, hence even though + // we have a valid `UnixAcceptor`, no one can connect to it. For this + // reason, we generate the next accept call's server pipe at the end of + // this function call. + // + // This provides us an invariant that we always have at least one server + // connection open at a time, meaning that all connects to this acceptor + // should succeed while this is active. + // + // The actual implementation of doing this is a little tricky. Once a + // server pipe is created, a client can connect to it at any time. I + // assume that which server a client connects to is nondeterministic, so + // we also need to guarantee that the only server able to be connected + // to is the one that we're calling ConnectNamedPipe on. This means that + // we have to create the second server pipe *after* we've already + // accepted a connection. In order to at least somewhat gracefully + // handle errors, this means that if the second server pipe creation + // fails that we disconnect the connected client and then just keep + // using the original server pipe. + let handle = self.listener.handle; + + let name = try!(to_utf16(&self.listener.name)); + + // Once we've got a "server handle", we need to wait for a client to + // connect. The ConnectNamedPipe function will block this thread until + // someone on the other end connects. This function can "fail" if a + // client connects after we created the pipe but before we got down + // here. Thanks windows. + let mut overlapped: libc::OVERLAPPED = unsafe { mem::zeroed() }; + overlapped.hEvent = self.event.handle(); + if unsafe { libc::ConnectNamedPipe(handle, &mut overlapped) == 0 } { + let mut err = unsafe { libc::GetLastError() }; + + if err == libc::ERROR_IO_PENDING as libc::DWORD { + // Process a timeout if one is pending + let _ = await(handle, self.deadline, &mut overlapped); + + // This will block until the overlapped I/O is completed. The + // timeout was previously handled, so this will either block in + // the normal case or succeed very quickly in the timeout case. + let ret = unsafe { + let mut transfer = 0; + libc::GetOverlappedResult(handle, + &mut overlapped, + &mut transfer, + libc::TRUE) + }; + if ret == 0 { + err = unsafe { libc::GetLastError() }; + } else { + // we succeeded, bypass the check below + err = libc::ERROR_PIPE_CONNECTED as libc::DWORD; + } + } + if err != libc::ERROR_PIPE_CONNECTED as libc::DWORD { + return Err(super::last_error()) + } + } + + // Now that we've got a connected client to our handle, we need to + // create a second server pipe. If this fails, we disconnect the + // connected client and return an error (see comments above). + let new_handle = unsafe { pipe(name.as_ptr(), false) }; + if new_handle == libc::INVALID_HANDLE_VALUE { + let ret = Err(super::last_error()); + // If our disconnection fails, then there's not really a whole lot + // that we can do, so fail the task. + let err = unsafe { libc::DisconnectNamedPipe(handle) }; + assert!(err != 0); + return ret; + } else { + self.listener.handle = new_handle; + } + + // Transfer ownership of our handle into this stream + Ok(UnixStream { + inner: Arc::new(Inner::new(handle)), + read: None, + write: None, + read_deadline: 0, + write_deadline: 0, + }) + } +} + +impl rtio::RtioUnixAcceptor for UnixAcceptor { + fn accept(&mut self) -> IoResult> { + self.native_accept().map(|s| box s as Box) + } + fn set_timeout(&mut self, timeout: Option) { + self.deadline = timeout.map(|i| i + ::io::timer::now()).unwrap_or(0); + } +} + diff --git a/src/libnative/io/process.rs b/src/libnative/io/process.rs index 443d7645388..d1b28854157 100644 --- a/src/libnative/io/process.rs +++ b/src/libnative/io/process.rs @@ -786,7 +786,7 @@ fn with_envp(env: Option<&[(&CString, &CString)]>, #[cfg(windows)] fn with_envp(env: Option<&[(&CString, &CString)]>, cb: |*mut c_void| -> T) -> T { - // On win32 we pass an "environment block" which is not a char**, but + // On Windows we pass an "environment block" which is not a char**, but // rather a concatenation of null-terminated k=v\0 sequences, with a final // \0 to terminate. match env { diff --git a/src/libnative/io/timer_win32.rs b/src/libnative/io/timer_win32.rs deleted file mode 100644 index 1e5594126b1..00000000000 --- a/src/libnative/io/timer_win32.rs +++ /dev/null @@ -1,210 +0,0 @@ -// Copyright 2013 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 or the MIT license -// , at your -// option. This file may not be copied, modified, or distributed -// except according to those terms. - -//! Timers based on win32 WaitableTimers -//! -//! This implementation is meant to be used solely on windows. As with other -//! implementations, there is a worker thread which is doing all the waiting on -//! a large number of timers for all active timers in the system. This worker -//! thread uses the select() equivalent, WaitForMultipleObjects. One of the -//! objects being waited on is a signal into the worker thread to notify that -//! the incoming channel should be looked at. -//! -//! Other than that, the implementation is pretty straightforward in terms of -//! the other two implementations of timers with nothing *that* new showing up. - -use libc; -use std::ptr; -use std::rt::rtio; -use std::rt::rtio::{IoResult, Callback}; -use std::comm; - -use io::helper_thread::Helper; - -helper_init!(static mut HELPER: Helper) - -pub struct Timer { - obj: libc::HANDLE, - on_worker: bool, -} - -pub enum Req { - NewTimer(libc::HANDLE, Box, bool), - RemoveTimer(libc::HANDLE, Sender<()>), -} - -fn helper(input: libc::HANDLE, messages: Receiver, _: ()) { - let mut objs = vec![input]; - let mut chans = vec![]; - - 'outer: loop { - let idx = unsafe { - imp::WaitForMultipleObjects(objs.len() as libc::DWORD, - objs.as_ptr(), - 0 as libc::BOOL, - libc::INFINITE) - }; - - if idx == 0 { - loop { - match messages.try_recv() { - Ok(NewTimer(obj, c, one)) => { - objs.push(obj); - chans.push((c, one)); - } - Ok(RemoveTimer(obj, c)) => { - c.send(()); - match objs.iter().position(|&o| o == obj) { - Some(i) => { - drop(objs.remove(i)); - drop(chans.remove(i - 1)); - } - None => {} - } - } - Err(comm::Disconnected) => { - assert_eq!(objs.len(), 1); - assert_eq!(chans.len(), 0); - break 'outer; - } - Err(..) => break - } - } - } else { - let remove = { - match chans.get_mut(idx as uint - 1) { - &(ref mut c, oneshot) => { c.call(); oneshot } - } - }; - if remove { - drop(objs.remove(idx as uint)); - drop(chans.remove(idx as uint - 1)); - } - } - } -} - -// returns the current time (in milliseconds) -pub fn now() -> u64 { - let mut ticks_per_s = 0; - assert_eq!(unsafe { libc::QueryPerformanceFrequency(&mut ticks_per_s) }, 1); - let ticks_per_s = if ticks_per_s == 0 {1} else {ticks_per_s}; - let mut ticks = 0; - assert_eq!(unsafe { libc::QueryPerformanceCounter(&mut ticks) }, 1); - - return (ticks as u64 * 1000) / (ticks_per_s as u64); -} - -impl Timer { - pub fn new() -> IoResult { - unsafe { HELPER.boot(|| {}, helper) } - - let obj = unsafe { - imp::CreateWaitableTimerA(ptr::mut_null(), 0, ptr::null()) - }; - if obj.is_null() { - Err(super::last_error()) - } else { - Ok(Timer { obj: obj, on_worker: false, }) - } - } - - pub fn sleep(ms: u64) { - use std::rt::rtio::RtioTimer; - let mut t = Timer::new().ok().expect("must allocate a timer!"); - t.sleep(ms); - } - - fn remove(&mut self) { - if !self.on_worker { return } - - let (tx, rx) = channel(); - unsafe { HELPER.send(RemoveTimer(self.obj, tx)) } - rx.recv(); - - self.on_worker = false; - } -} - -impl rtio::RtioTimer for Timer { - fn sleep(&mut self, msecs: u64) { - self.remove(); - - // there are 10^6 nanoseconds in a millisecond, and the parameter is in - // 100ns intervals, so we multiply by 10^4. - let due = -(msecs as i64 * 10000) as libc::LARGE_INTEGER; - assert_eq!(unsafe { - imp::SetWaitableTimer(self.obj, &due, 0, ptr::mut_null(), - ptr::mut_null(), 0) - }, 1); - - let _ = unsafe { imp::WaitForSingleObject(self.obj, libc::INFINITE) }; - } - - fn oneshot(&mut self, msecs: u64, cb: Box) { - self.remove(); - - // see above for the calculation - let due = -(msecs as i64 * 10000) as libc::LARGE_INTEGER; - assert_eq!(unsafe { - imp::SetWaitableTimer(self.obj, &due, 0, ptr::mut_null(), - ptr::mut_null(), 0) - }, 1); - - unsafe { HELPER.send(NewTimer(self.obj, cb, true)) } - self.on_worker = true; - } - - fn period(&mut self, msecs: u64, cb: Box) { - self.remove(); - - // see above for the calculation - let due = -(msecs as i64 * 10000) as libc::LARGE_INTEGER; - assert_eq!(unsafe { - imp::SetWaitableTimer(self.obj, &due, msecs as libc::LONG, - ptr::mut_null(), ptr::mut_null(), 0) - }, 1); - - unsafe { HELPER.send(NewTimer(self.obj, cb, false)) } - self.on_worker = true; - } -} - -impl Drop for Timer { - fn drop(&mut self) { - self.remove(); - assert!(unsafe { libc::CloseHandle(self.obj) != 0 }); - } -} - -mod imp { - use libc::{LPSECURITY_ATTRIBUTES, BOOL, LPCSTR, HANDLE, LARGE_INTEGER, - LONG, LPVOID, DWORD, c_void}; - - pub type PTIMERAPCROUTINE = *mut c_void; - - extern "system" { - pub fn CreateWaitableTimerA(lpTimerAttributes: LPSECURITY_ATTRIBUTES, - bManualReset: BOOL, - lpTimerName: LPCSTR) -> HANDLE; - pub fn SetWaitableTimer(hTimer: HANDLE, - pDueTime: *const LARGE_INTEGER, - lPeriod: LONG, - pfnCompletionRoutine: PTIMERAPCROUTINE, - lpArgToCompletionRoutine: LPVOID, - fResume: BOOL) -> BOOL; - pub fn WaitForMultipleObjects(nCount: DWORD, - lpHandles: *const HANDLE, - bWaitAll: BOOL, - dwMilliseconds: DWORD) -> DWORD; - pub fn WaitForSingleObject(hHandle: HANDLE, - dwMilliseconds: DWORD) -> DWORD; - } -} diff --git a/src/libnative/io/timer_windows.rs b/src/libnative/io/timer_windows.rs new file mode 100644 index 00000000000..8d781f50d35 --- /dev/null +++ b/src/libnative/io/timer_windows.rs @@ -0,0 +1,210 @@ +// Copyright 2013 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 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! Timers based on Windows WaitableTimers +//! +//! This implementation is meant to be used solely on windows. As with other +//! implementations, there is a worker thread which is doing all the waiting on +//! a large number of timers for all active timers in the system. This worker +//! thread uses the select() equivalent, WaitForMultipleObjects. One of the +//! objects being waited on is a signal into the worker thread to notify that +//! the incoming channel should be looked at. +//! +//! Other than that, the implementation is pretty straightforward in terms of +//! the other two implementations of timers with nothing *that* new showing up. + +use libc; +use std::ptr; +use std::rt::rtio; +use std::rt::rtio::{IoResult, Callback}; +use std::comm; + +use io::helper_thread::Helper; + +helper_init!(static mut HELPER: Helper) + +pub struct Timer { + obj: libc::HANDLE, + on_worker: bool, +} + +pub enum Req { + NewTimer(libc::HANDLE, Box, bool), + RemoveTimer(libc::HANDLE, Sender<()>), +} + +fn helper(input: libc::HANDLE, messages: Receiver, _: ()) { + let mut objs = vec![input]; + let mut chans = vec![]; + + 'outer: loop { + let idx = unsafe { + imp::WaitForMultipleObjects(objs.len() as libc::DWORD, + objs.as_ptr(), + 0 as libc::BOOL, + libc::INFINITE) + }; + + if idx == 0 { + loop { + match messages.try_recv() { + Ok(NewTimer(obj, c, one)) => { + objs.push(obj); + chans.push((c, one)); + } + Ok(RemoveTimer(obj, c)) => { + c.send(()); + match objs.iter().position(|&o| o == obj) { + Some(i) => { + drop(objs.remove(i)); + drop(chans.remove(i - 1)); + } + None => {} + } + } + Err(comm::Disconnected) => { + assert_eq!(objs.len(), 1); + assert_eq!(chans.len(), 0); + break 'outer; + } + Err(..) => break + } + } + } else { + let remove = { + match chans.get_mut(idx as uint - 1) { + &(ref mut c, oneshot) => { c.call(); oneshot } + } + }; + if remove { + drop(objs.remove(idx as uint)); + drop(chans.remove(idx as uint - 1)); + } + } + } +} + +// returns the current time (in milliseconds) +pub fn now() -> u64 { + let mut ticks_per_s = 0; + assert_eq!(unsafe { libc::QueryPerformanceFrequency(&mut ticks_per_s) }, 1); + let ticks_per_s = if ticks_per_s == 0 {1} else {ticks_per_s}; + let mut ticks = 0; + assert_eq!(unsafe { libc::QueryPerformanceCounter(&mut ticks) }, 1); + + return (ticks as u64 * 1000) / (ticks_per_s as u64); +} + +impl Timer { + pub fn new() -> IoResult { + unsafe { HELPER.boot(|| {}, helper) } + + let obj = unsafe { + imp::CreateWaitableTimerA(ptr::mut_null(), 0, ptr::null()) + }; + if obj.is_null() { + Err(super::last_error()) + } else { + Ok(Timer { obj: obj, on_worker: false, }) + } + } + + pub fn sleep(ms: u64) { + use std::rt::rtio::RtioTimer; + let mut t = Timer::new().ok().expect("must allocate a timer!"); + t.sleep(ms); + } + + fn remove(&mut self) { + if !self.on_worker { return } + + let (tx, rx) = channel(); + unsafe { HELPER.send(RemoveTimer(self.obj, tx)) } + rx.recv(); + + self.on_worker = false; + } +} + +impl rtio::RtioTimer for Timer { + fn sleep(&mut self, msecs: u64) { + self.remove(); + + // there are 10^6 nanoseconds in a millisecond, and the parameter is in + // 100ns intervals, so we multiply by 10^4. + let due = -(msecs as i64 * 10000) as libc::LARGE_INTEGER; + assert_eq!(unsafe { + imp::SetWaitableTimer(self.obj, &due, 0, ptr::mut_null(), + ptr::mut_null(), 0) + }, 1); + + let _ = unsafe { imp::WaitForSingleObject(self.obj, libc::INFINITE) }; + } + + fn oneshot(&mut self, msecs: u64, cb: Box) { + self.remove(); + + // see above for the calculation + let due = -(msecs as i64 * 10000) as libc::LARGE_INTEGER; + assert_eq!(unsafe { + imp::SetWaitableTimer(self.obj, &due, 0, ptr::mut_null(), + ptr::mut_null(), 0) + }, 1); + + unsafe { HELPER.send(NewTimer(self.obj, cb, true)) } + self.on_worker = true; + } + + fn period(&mut self, msecs: u64, cb: Box) { + self.remove(); + + // see above for the calculation + let due = -(msecs as i64 * 10000) as libc::LARGE_INTEGER; + assert_eq!(unsafe { + imp::SetWaitableTimer(self.obj, &due, msecs as libc::LONG, + ptr::mut_null(), ptr::mut_null(), 0) + }, 1); + + unsafe { HELPER.send(NewTimer(self.obj, cb, false)) } + self.on_worker = true; + } +} + +impl Drop for Timer { + fn drop(&mut self) { + self.remove(); + assert!(unsafe { libc::CloseHandle(self.obj) != 0 }); + } +} + +mod imp { + use libc::{LPSECURITY_ATTRIBUTES, BOOL, LPCSTR, HANDLE, LARGE_INTEGER, + LONG, LPVOID, DWORD, c_void}; + + pub type PTIMERAPCROUTINE = *mut c_void; + + extern "system" { + pub fn CreateWaitableTimerA(lpTimerAttributes: LPSECURITY_ATTRIBUTES, + bManualReset: BOOL, + lpTimerName: LPCSTR) -> HANDLE; + pub fn SetWaitableTimer(hTimer: HANDLE, + pDueTime: *const LARGE_INTEGER, + lPeriod: LONG, + pfnCompletionRoutine: PTIMERAPCROUTINE, + lpArgToCompletionRoutine: LPVOID, + fResume: BOOL) -> BOOL; + pub fn WaitForMultipleObjects(nCount: DWORD, + lpHandles: *const HANDLE, + bWaitAll: BOOL, + dwMilliseconds: DWORD) -> DWORD; + pub fn WaitForSingleObject(hHandle: HANDLE, + dwMilliseconds: DWORD) -> DWORD; + } +} diff --git a/src/libnative/io/tty_win32.rs b/src/libnative/io/tty_win32.rs deleted file mode 100644 index e98fe1e20b1..00000000000 --- a/src/libnative/io/tty_win32.rs +++ /dev/null @@ -1,158 +0,0 @@ -// 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. -// -// Licensed under the Apache License, Version 2.0 or the MIT license -// , at your -// option. This file may not be copied, modified, or distributed -// except according to those terms. - -// ignore-lexer-test FIXME #15877 - -//! Windows specific console TTY implementation -//! -//! This module contains the implementation of a Windows specific console TTY. -//! Also converts between UTF-16 and UTF-8. Windows has very poor support for -//! UTF-8 and some functions will fail. In particular ReadFile and ReadConsole -//! will fail when the codepage is set to UTF-8 and a unicode character is -//! entered. -//! -//! FIXME -//! This implementation does not account for codepoints that are split across -//! multiple reads and writes. Also, this implementation does not expose a way -//! to read/write UTF-16 directly. When/if Rust receives a Reader/Writer -//! wrapper that performs encoding/decoding, this implementation should switch -//! to working in raw UTF-16, with such a wrapper around it. - -use super::c::{ReadConsoleW, WriteConsoleW, GetConsoleMode, SetConsoleMode}; -use super::c::{ERROR_ILLEGAL_CHARACTER}; -use super::c::{ENABLE_ECHO_INPUT, ENABLE_EXTENDED_FLAGS}; -use super::c::{ENABLE_INSERT_MODE, ENABLE_LINE_INPUT}; -use super::c::{ENABLE_PROCESSED_INPUT, ENABLE_QUICK_EDIT_MODE}; -use libc::{c_int, HANDLE, LPDWORD, DWORD, LPVOID}; -use libc::{get_osfhandle, CloseHandle}; -use libc::types::os::arch::extra::LPCVOID; -use std::io::MemReader; -use std::ptr; -use std::rt::rtio::{IoResult, IoError, RtioTTY}; -use std::str::{from_utf16, from_utf8}; - -fn invalid_encoding() -> IoError { - IoError { - code: ERROR_ILLEGAL_CHARACTER as uint, - extra: 0, - detail: Some("text was not valid unicode".to_string()), - } -} - -pub fn is_tty(fd: c_int) -> bool { - let mut out: DWORD = 0; - // If this function doesn't fail then fd is a TTY - match unsafe { GetConsoleMode(get_osfhandle(fd) as HANDLE, - &mut out as LPDWORD) } { - 0 => false, - _ => true, - } -} - -pub struct WindowsTTY { - closeme: bool, - handle: HANDLE, - utf8: MemReader, -} - -impl WindowsTTY { - pub fn new(fd: c_int) -> WindowsTTY { - // If the file descriptor is one of stdin, stderr, or stdout - // then it should not be closed by us - let closeme = match fd { - 0..2 => false, - _ => true, - }; - let handle = unsafe { get_osfhandle(fd) as HANDLE }; - WindowsTTY { - handle: handle, - utf8: MemReader::new(Vec::new()), - closeme: closeme, - } - } -} - -impl Drop for WindowsTTY { - fn drop(&mut self) { - if self.closeme { - // Nobody cares about the return value - let _ = unsafe { CloseHandle(self.handle) }; - } - } -} - -impl RtioTTY for WindowsTTY { - fn read(&mut self, buf: &mut [u8]) -> IoResult { - // Read more if the buffer is empty - if self.utf8.eof() { - let mut utf16 = Vec::from_elem(0x1000, 0u16); - let mut num: DWORD = 0; - match unsafe { ReadConsoleW(self.handle, - utf16.as_mut_ptr() as LPVOID, - utf16.len() as u32, - &mut num as LPDWORD, - ptr::mut_null()) } { - 0 => return Err(super::last_error()), - _ => (), - }; - utf16.truncate(num as uint); - let utf8 = match from_utf16(utf16.as_slice()) { - Some(utf8) => utf8.into_bytes(), - None => return Err(invalid_encoding()), - }; - self.utf8 = MemReader::new(utf8); - } - // MemReader shouldn't error here since we just filled it - Ok(self.utf8.read(buf).unwrap()) - } - - fn write(&mut self, buf: &[u8]) -> IoResult<()> { - let utf16 = match from_utf8(buf) { - Some(utf8) => utf8.to_utf16(), - None => return Err(invalid_encoding()), - }; - let mut num: DWORD = 0; - match unsafe { WriteConsoleW(self.handle, - utf16.as_ptr() as LPCVOID, - utf16.len() as u32, - &mut num as LPDWORD, - ptr::mut_null()) } { - 0 => Err(super::last_error()), - _ => Ok(()), - } - } - - fn set_raw(&mut self, raw: bool) -> IoResult<()> { - // FIXME - // Somebody needs to decide on which of these flags we want - match unsafe { SetConsoleMode(self.handle, - match raw { - true => 0, - false => ENABLE_ECHO_INPUT | ENABLE_EXTENDED_FLAGS | - ENABLE_INSERT_MODE | ENABLE_LINE_INPUT | - ENABLE_PROCESSED_INPUT | ENABLE_QUICK_EDIT_MODE, - }) } { - 0 => Err(super::last_error()), - _ => Ok(()), - } - } - - fn get_winsize(&mut self) -> IoResult<(int, int)> { - // FIXME - // Get console buffer via CreateFile with CONOUT$ - // Make a CONSOLE_SCREEN_BUFFER_INFO - // Call GetConsoleScreenBufferInfo - // Maybe call GetLargestConsoleWindowSize instead? - Err(super::unimpl()) - } - - // Let us magically declare this as a TTY - fn isatty(&self) -> bool { true } -} diff --git a/src/libnative/io/tty_windows.rs b/src/libnative/io/tty_windows.rs new file mode 100644 index 00000000000..e98fe1e20b1 --- /dev/null +++ b/src/libnative/io/tty_windows.rs @@ -0,0 +1,158 @@ +// 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. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +// ignore-lexer-test FIXME #15877 + +//! Windows specific console TTY implementation +//! +//! This module contains the implementation of a Windows specific console TTY. +//! Also converts between UTF-16 and UTF-8. Windows has very poor support for +//! UTF-8 and some functions will fail. In particular ReadFile and ReadConsole +//! will fail when the codepage is set to UTF-8 and a unicode character is +//! entered. +//! +//! FIXME +//! This implementation does not account for codepoints that are split across +//! multiple reads and writes. Also, this implementation does not expose a way +//! to read/write UTF-16 directly. When/if Rust receives a Reader/Writer +//! wrapper that performs encoding/decoding, this implementation should switch +//! to working in raw UTF-16, with such a wrapper around it. + +use super::c::{ReadConsoleW, WriteConsoleW, GetConsoleMode, SetConsoleMode}; +use super::c::{ERROR_ILLEGAL_CHARACTER}; +use super::c::{ENABLE_ECHO_INPUT, ENABLE_EXTENDED_FLAGS}; +use super::c::{ENABLE_INSERT_MODE, ENABLE_LINE_INPUT}; +use super::c::{ENABLE_PROCESSED_INPUT, ENABLE_QUICK_EDIT_MODE}; +use libc::{c_int, HANDLE, LPDWORD, DWORD, LPVOID}; +use libc::{get_osfhandle, CloseHandle}; +use libc::types::os::arch::extra::LPCVOID; +use std::io::MemReader; +use std::ptr; +use std::rt::rtio::{IoResult, IoError, RtioTTY}; +use std::str::{from_utf16, from_utf8}; + +fn invalid_encoding() -> IoError { + IoError { + code: ERROR_ILLEGAL_CHARACTER as uint, + extra: 0, + detail: Some("text was not valid unicode".to_string()), + } +} + +pub fn is_tty(fd: c_int) -> bool { + let mut out: DWORD = 0; + // If this function doesn't fail then fd is a TTY + match unsafe { GetConsoleMode(get_osfhandle(fd) as HANDLE, + &mut out as LPDWORD) } { + 0 => false, + _ => true, + } +} + +pub struct WindowsTTY { + closeme: bool, + handle: HANDLE, + utf8: MemReader, +} + +impl WindowsTTY { + pub fn new(fd: c_int) -> WindowsTTY { + // If the file descriptor is one of stdin, stderr, or stdout + // then it should not be closed by us + let closeme = match fd { + 0..2 => false, + _ => true, + }; + let handle = unsafe { get_osfhandle(fd) as HANDLE }; + WindowsTTY { + handle: handle, + utf8: MemReader::new(Vec::new()), + closeme: closeme, + } + } +} + +impl Drop for WindowsTTY { + fn drop(&mut self) { + if self.closeme { + // Nobody cares about the return value + let _ = unsafe { CloseHandle(self.handle) }; + } + } +} + +impl RtioTTY for WindowsTTY { + fn read(&mut self, buf: &mut [u8]) -> IoResult { + // Read more if the buffer is empty + if self.utf8.eof() { + let mut utf16 = Vec::from_elem(0x1000, 0u16); + let mut num: DWORD = 0; + match unsafe { ReadConsoleW(self.handle, + utf16.as_mut_ptr() as LPVOID, + utf16.len() as u32, + &mut num as LPDWORD, + ptr::mut_null()) } { + 0 => return Err(super::last_error()), + _ => (), + }; + utf16.truncate(num as uint); + let utf8 = match from_utf16(utf16.as_slice()) { + Some(utf8) => utf8.into_bytes(), + None => return Err(invalid_encoding()), + }; + self.utf8 = MemReader::new(utf8); + } + // MemReader shouldn't error here since we just filled it + Ok(self.utf8.read(buf).unwrap()) + } + + fn write(&mut self, buf: &[u8]) -> IoResult<()> { + let utf16 = match from_utf8(buf) { + Some(utf8) => utf8.to_utf16(), + None => return Err(invalid_encoding()), + }; + let mut num: DWORD = 0; + match unsafe { WriteConsoleW(self.handle, + utf16.as_ptr() as LPCVOID, + utf16.len() as u32, + &mut num as LPDWORD, + ptr::mut_null()) } { + 0 => Err(super::last_error()), + _ => Ok(()), + } + } + + fn set_raw(&mut self, raw: bool) -> IoResult<()> { + // FIXME + // Somebody needs to decide on which of these flags we want + match unsafe { SetConsoleMode(self.handle, + match raw { + true => 0, + false => ENABLE_ECHO_INPUT | ENABLE_EXTENDED_FLAGS | + ENABLE_INSERT_MODE | ENABLE_LINE_INPUT | + ENABLE_PROCESSED_INPUT | ENABLE_QUICK_EDIT_MODE, + }) } { + 0 => Err(super::last_error()), + _ => Ok(()), + } + } + + fn get_winsize(&mut self) -> IoResult<(int, int)> { + // FIXME + // Get console buffer via CreateFile with CONOUT$ + // Make a CONSOLE_SCREEN_BUFFER_INFO + // Call GetConsoleScreenBufferInfo + // Maybe call GetLargestConsoleWindowSize instead? + Err(super::unimpl()) + } + + // Let us magically declare this as a TTY + fn isatty(&self) -> bool { true } +} -- cgit 1.4.1-3-g733a5