diff --git a/Cargo.toml b/Cargo.toml index 0af0847081..c0ccc9a9de 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,7 +21,8 @@ log = "0.4" nix = "0.13" [target.'cfg(windows)'.dependencies] -winapi = { version = "0.3", features=["std", "handleapi", "processthreadsapi", "winbase", "ws2def", "fileapi"] } +winx = { path = "winx" } +winapi = "0.3" [dev-dependencies] wasmtime-runtime = { git = "https://github.com/cranestation/wasmtime" } @@ -45,3 +46,5 @@ name = "wasi_common" crate-type = ["rlib", "staticlib", "cdylib"] [workspace] +members = ["wasi-common-cbindgen"] +exclude = ["winx"] diff --git a/src/sys/windows/fdentry.rs b/src/sys/windows/fdentry.rs index c662cf2898..b3468a2410 100644 --- a/src/sys/windows/fdentry.rs +++ b/src/sys/windows/fdentry.rs @@ -1,12 +1,9 @@ +use super::host_impl; use crate::host; use std::fs::File; use std::os::windows::prelude::{AsRawHandle, FromRawHandle, IntoRawHandle, RawHandle}; use std::path::PathBuf; -use winapi::shared::minwindef::FALSE; -use winapi::um::handleapi::DuplicateHandle; -use winapi::um::processthreadsapi::GetCurrentProcess; -use winapi::um::winnt::DUPLICATE_SAME_ACCESS; #[derive(Clone, Debug)] pub struct FdObject { @@ -27,12 +24,8 @@ pub struct FdEntry { impl Drop for FdObject { fn drop(&mut self) { if self.needs_close { - unsafe { - if winapi::um::handleapi::CloseHandle(self.raw_handle) == 0 { - // TODO: use DWORD WINAPI GetLastError(void) to get error - eprintln!("FdObject::drop(): couldn't close raw Handle"); - } - } + winx::handle::close(self.raw_handle) + .unwrap_or_else(|e| eprintln!("FdObject::drop(): {}", e)) } } } @@ -43,37 +36,30 @@ impl FdEntry { } pub fn duplicate(fd: &F) -> Self { - unsafe { - let source = fd.as_raw_handle(); - let mut dest = 0 as RawHandle; - - let cur_proc = GetCurrentProcess(); - if DuplicateHandle( - cur_proc, - source, - cur_proc, - &mut dest, - 0, // dwDesiredAccess; this flag is ignored if DUPLICATE_SAME_ACCESS is specified - FALSE, - DUPLICATE_SAME_ACCESS, - ) == FALSE - { - panic!("Couldn't duplicate handle"); - } - - Self::from_raw_handle(dest) - } + unsafe { Self::from_raw_handle(winx::handle::dup(fd.as_raw_handle()).unwrap()) } } } impl FromRawHandle for FdEntry { - // TODO: implement unsafe fn from_raw_handle(raw_handle: RawHandle) -> Self { - let (ty, rights_base, rights_inheriting) = ( - host::__WASI_FILETYPE_REGULAR_FILE, - host::RIGHTS_REGULAR_FILE_BASE, - host::RIGHTS_REGULAR_FILE_INHERITING, - ); + use winx::file::{get_file_access_rights, AccessRight}; + + let (ty, mut rights_base, rights_inheriting) = + determine_type_rights(raw_handle).expect("can determine type rights"); + + if ty != host::__WASI_FILETYPE_CHARACTER_DEVICE { + // TODO: is there a way around this? On windows, it seems + // we cannot check access rights for stdout/in handles + let rights = + get_file_access_rights(raw_handle).expect("can determine file access rights"); + let rights = AccessRight::from_bits_truncate(rights); + if rights.contains(AccessRight::FILE_GENERIC_READ) { + rights_base |= host::__WASI_RIGHT_FD_READ; + } + if rights.contains(AccessRight::FILE_GENERIC_WRITE) { + rights_base |= host::__WASI_RIGHT_FD_WRITE; + } + } Self { fd_object: FdObject { @@ -87,3 +73,57 @@ impl FromRawHandle for FdEntry { } } } + +pub unsafe fn determine_type_rights( + raw_handle: RawHandle, +) -> Result< + ( + host::__wasi_filetype_t, + host::__wasi_rights_t, + host::__wasi_rights_t, + ), + host::__wasi_errno_t, +> { + let (ty, rights_base, rights_inheriting) = { + let file_type = winx::file::get_file_type(raw_handle).map_err(host_impl::errno_from_win)?; + if file_type.is_char() { + // character file: LPT device or console + // TODO: rule out LPT device + ( + host::__WASI_FILETYPE_CHARACTER_DEVICE, + host::RIGHTS_TTY_BASE, + host::RIGHTS_TTY_BASE, + ) + } else if file_type.is_disk() { + // disk file: file, dir or disk device + let file = std::mem::ManuallyDrop::new(File::from_raw_handle(raw_handle)); + let meta = file.metadata().map_err(|_| host::__WASI_EINVAL)?; + if meta.is_dir() { + ( + host::__WASI_FILETYPE_DIRECTORY, + host::RIGHTS_DIRECTORY_BASE, + host::RIGHTS_DIRECTORY_INHERITING, + ) + } else if meta.is_file() { + ( + host::__WASI_FILETYPE_REGULAR_FILE, + host::RIGHTS_REGULAR_FILE_BASE, + host::RIGHTS_REGULAR_FILE_INHERITING, + ) + } else { + return Err(host::__WASI_EINVAL); + } + } else if file_type.is_pipe() { + // pipe object: socket, named pipe or anonymous pipe + // TODO: what about pipes, etc? + ( + host::__WASI_FILETYPE_SOCKET_STREAM, + host::RIGHTS_SOCKET_BASE, + host::RIGHTS_SOCKET_INHERITING, + ) + } else { + return Err(host::__WASI_EINVAL); + } + }; + Ok((ty, rights_base, rights_inheriting)) +} diff --git a/src/sys/windows/host_impl.rs b/src/sys/windows/host_impl.rs index 6adba77530..301a5b0a62 100644 --- a/src/sys/windows/host_impl.rs +++ b/src/sys/windows/host_impl.rs @@ -8,81 +8,125 @@ use std::ffi::{OsStr, OsString}; use std::marker::PhantomData; use std::os::windows::prelude::{OsStrExt, OsStringExt}; use std::slice; -use winapi::shared::{ntdef, ws2def}; -// these will be obsolete once https://github.com/rust-lang/rust/pull/60334 -// lands in stable -pub struct IoVec<'a> { - vec: ws2def::WSABUF, - _p: PhantomData<&'a [u8]>, -} - -pub struct IoVecMut<'a> { - vec: ws2def::WSABUF, - _p: PhantomData<&'a mut [u8]>, -} - -impl<'a> IoVec<'a> { - #[inline] - pub fn new(buf: &'a [u8]) -> Self { - assert!(buf.len() <= ntdef::ULONG::max_value() as usize); - Self { - vec: ws2def::WSABUF { - len: buf.len() as ntdef::ULONG, - buf: buf.as_ptr() as *mut u8 as *mut ntdef::CHAR, - }, - _p: PhantomData, - } - } - - #[inline] - pub fn as_slice(&self) -> &[u8] { - unsafe { slice::from_raw_parts(self.vec.buf as *mut u8, self.vec.len as usize) } +pub fn errno_from_win(error: winx::winerror::WinError) -> host::__wasi_errno_t { + // TODO: implement error mapping between Windows and WASI + use winx::winerror::WinError::*; + match error { + ERROR_SUCCESS => host::__WASI_ESUCCESS, + ERROR_BAD_ENVIRONMENT => host::__WASI_E2BIG, + ERROR_FILE_NOT_FOUND | ERROR_PATH_NOT_FOUND => host::__WASI_ENOENT, + ERROR_TOO_MANY_OPEN_FILES => host::__WASI_ENFILE, + ERROR_ACCESS_DENIED | ERROR_SHARING_VIOLATION => host::__WASI_EACCES, + ERROR_INVALID_HANDLE | ERROR_INVALID_NAME => host::__WASI_EBADF, + ERROR_NOT_ENOUGH_MEMORY | ERROR_OUTOFMEMORY => host::__WASI_ENOMEM, + ERROR_DIR_NOT_EMPTY => host::__WASI_ENOTEMPTY, + ERROR_DEV_NOT_EXIST => host::__WASI_ENODEV, + ERROR_NOT_READY | ERROR_BUSY => host::__WASI_EBUSY, + ERROR_NOT_SUPPORTED => host::__WASI_ENOTSUP, + ERROR_FILE_EXISTS => host::__WASI_EEXIST, + ERROR_BROKEN_PIPE => host::__WASI_EPIPE, + ERROR_BUFFER_OVERFLOW => host::__WASI_ENAMETOOLONG, + ERROR_DISK_FULL => host::__WASI_ENOSPC, + ERROR_SHARING_BUFFER_EXCEEDED => host::__WASI_ENFILE, + _ => host::__WASI_ENOTSUP, } } -impl<'a> IoVecMut<'a> { - #[inline] - pub fn new(buf: &'a mut [u8]) -> Self { - assert!(buf.len() <= ntdef::ULONG::max_value() as usize); - Self { - vec: ws2def::WSABUF { - len: buf.len() as ntdef::ULONG, - buf: buf.as_mut_ptr() as *mut u8 as *mut ntdef::CHAR, - }, - _p: PhantomData, - } - } - - #[inline] - pub fn as_slice(&self) -> &[u8] { - unsafe { slice::from_raw_parts(self.vec.buf as *mut u8, self.vec.len as usize) } - } - - #[inline] - pub fn as_mut_slice(&mut self) -> &mut [u8] { - unsafe { slice::from_raw_parts_mut(self.vec.buf as *mut u8, self.vec.len as usize) } - } -} - -pub unsafe fn ciovec_to_win<'a>(ciovec: &'a host::__wasi_ciovec_t) -> IoVec<'a> { +pub unsafe fn ciovec_to_win<'a>(ciovec: &'a host::__wasi_ciovec_t) -> winx::io::IoVec<'a> { let slice = slice::from_raw_parts(ciovec.buf as *const u8, ciovec.buf_len); - IoVec::new(slice) + winx::io::IoVec::new(slice) } -pub unsafe fn ciovec_to_win_mut<'a>(ciovec: &'a mut host::__wasi_ciovec_t) -> IoVecMut<'a> { +pub unsafe fn ciovec_to_win_mut<'a>( + ciovec: &'a mut host::__wasi_ciovec_t, +) -> winx::io::IoVecMut<'a> { let slice = slice::from_raw_parts_mut(ciovec.buf as *mut u8, ciovec.buf_len); - IoVecMut::new(slice) + winx::io::IoVecMut::new(slice) } -pub unsafe fn iovec_to_win<'a>(iovec: &'a host::__wasi_iovec_t) -> IoVec<'a> { +pub unsafe fn iovec_to_win<'a>(iovec: &'a host::__wasi_iovec_t) -> winx::io::IoVec<'a> { let slice = slice::from_raw_parts(iovec.buf as *const u8, iovec.buf_len); - IoVec::new(slice) + winx::io::IoVec::new(slice) } -pub unsafe fn iovec_to_win_mut<'a>(iovec: &'a mut host::__wasi_iovec_t) -> IoVecMut<'a> { +pub unsafe fn iovec_to_win_mut<'a>(iovec: &'a mut host::__wasi_iovec_t) -> winx::io::IoVecMut<'a> { let slice = slice::from_raw_parts_mut(iovec.buf as *mut u8, iovec.buf_len); - IoVecMut::new(slice) + winx::io::IoVecMut::new(slice) +} + +pub fn win_from_fdflags( + fdflags: host::__wasi_fdflags_t, +) -> (winx::file::AccessRight, winx::file::FlagsAndAttributes) { + use winx::file::{AccessRight, FlagsAndAttributes}; + // TODO verify this! + let mut win_rights = AccessRight::empty(); + let mut win_flags_attrs = FlagsAndAttributes::empty(); + + if fdflags & host::__WASI_FDFLAG_NONBLOCK != 0 { + win_flags_attrs.insert(FlagsAndAttributes::FILE_FLAG_OVERLAPPED); + } + if fdflags & host::__WASI_FDFLAG_APPEND != 0 { + win_rights.insert(AccessRight::FILE_APPEND_DATA); + } + if fdflags & host::__WASI_FDFLAG_DSYNC != 0 + || fdflags & host::__WASI_FDFLAG_RSYNC != 0 + || fdflags & host::__WASI_FDFLAG_SYNC != 0 + { + win_rights.insert(AccessRight::SYNCHRONIZE); + } + (win_rights, win_flags_attrs) +} + +pub fn fdflags_from_win(rights: winx::file::AccessRight) -> host::__wasi_fdflags_t { + use winx::file::AccessRight; + let mut fdflags = 0; + // TODO verify this! + if rights.contains(AccessRight::FILE_APPEND_DATA) { + fdflags |= host::__WASI_FDFLAG_APPEND; + } + if rights.contains(AccessRight::SYNCHRONIZE) { + fdflags |= host::__WASI_FDFLAG_DSYNC; + fdflags |= host::__WASI_FDFLAG_RSYNC; + fdflags |= host::__WASI_FDFLAG_SYNC; + } + // The NONBLOCK equivalent is FILE_FLAG_OVERLAPPED + // but it seems winapi doesn't provide a mechanism + // for checking whether the handle supports async IO. + // On the contrary, I've found some dicsussion online + // which suggests that on Windows all handles should + // generally be assumed to be opened with async support + // and then the program should fallback should that **not** + // be the case at the time of the operation. + // TODO: this requires further investigation + fdflags +} + +pub fn win_from_oflags( + oflags: host::__wasi_oflags_t, +) -> ( + winx::file::CreationDisposition, + winx::file::FlagsAndAttributes, +) { + use winx::file::{CreationDisposition, FlagsAndAttributes}; + + let win_flags_attrs = if oflags & host::__WASI_O_DIRECTORY != 0 { + FlagsAndAttributes::FILE_FLAG_BACKUP_SEMANTICS + } else { + FlagsAndAttributes::FILE_ATTRIBUTE_NORMAL + }; + + let win_disp = if oflags & host::__WASI_O_CREAT != 0 && oflags & host::__WASI_O_EXCL != 0 { + CreationDisposition::CREATE_NEW + } else if oflags & host::__WASI_O_CREAT != 0 { + CreationDisposition::CREATE_ALWAYS + } else if oflags & host::__WASI_O_TRUNC != 0 { + CreationDisposition::TRUNCATE_EXISTING + } else { + CreationDisposition::OPEN_EXISTING + }; + + (win_disp, win_flags_attrs) } pub fn path_from_raw(raw_path: &[u8]) -> OsString { diff --git a/src/sys/windows/hostcalls_impl/fs.rs b/src/sys/windows/hostcalls_impl/fs.rs index e2cab0552f..6d3f2f0ad8 100644 --- a/src/sys/windows/hostcalls_impl/fs.rs +++ b/src/sys/windows/hostcalls_impl/fs.rs @@ -1,16 +1,17 @@ #![allow(non_camel_case_types)] -#![allow(unused_unsafe)] #![allow(unused)] -use super::fdentry::FdEntry; +use super::fdentry::{determine_type_rights, FdEntry}; +use super::fs_helpers::*; use super::host_impl; use crate::ctx::WasiCtx; use crate::host; use std::ffi::OsStr; +use std::os::windows::prelude::FromRawHandle; pub(crate) fn fd_close(fd_entry: FdEntry) -> Result<(), host::__wasi_errno_t> { - unimplemented!("fd_close") + winx::handle::close(fd_entry.fd_object.raw_handle).map_err(|e| host_impl::errno_from_win(e)) } pub(crate) fn fd_datasync(fd_entry: &FdEntry) -> Result<(), host::__wasi_errno_t> { @@ -37,7 +38,14 @@ pub(crate) fn fd_read( fd_entry: &FdEntry, iovs: &mut [host::__wasi_iovec_t], ) -> Result { - unimplemented!("fd_pread") + use winx::io::{readv, IoVecMut}; + + let mut iovs: Vec = iovs + .iter_mut() + .map(|iov| unsafe { host_impl::iovec_to_win_mut(iov) }) + .collect(); + + readv(fd_entry.fd_object.raw_handle, &mut iovs).map_err(|e| host_impl::errno_from_win(e)) } pub(crate) fn fd_renumber( @@ -63,7 +71,13 @@ pub(crate) fn fd_tell(fd_entry: &FdEntry) -> Result { pub(crate) fn fd_fdstat_get( fd_entry: &FdEntry, ) -> Result { - unimplemented!("fd_fdstat_get") + use winx::file::AccessRight; + match winx::file::get_file_access_rights(fd_entry.fd_object.raw_handle) + .map(AccessRight::from_bits_truncate) + { + Ok(rights) => Ok(host_impl::fdflags_from_win(rights)), + Err(e) => Err(host_impl::errno_from_win(e)), + } } pub(crate) fn fd_fdstat_set_flags( @@ -81,32 +95,14 @@ pub(crate) fn fd_write( fd_entry: &FdEntry, iovs: &[host::__wasi_iovec_t], ) -> Result { - use winapi::shared::minwindef::{DWORD, LPVOID}; - use winapi::um::fileapi::WriteFile; + use winx::io::{writev, IoVec}; - let iovs: Vec = iovs + let iovs: Vec = iovs .iter() .map(|iov| unsafe { host_impl::iovec_to_win(iov) }) .collect(); - let buf = iovs - .iter() - .find(|b| !b.as_slice().is_empty()) - .map_or(&[][..], |b| b.as_slice()); - - let mut host_nwritten = 0; - let len = std::cmp::min(buf.len(), ::max_value() as usize) as DWORD; - unsafe { - WriteFile( - fd_entry.fd_object.raw_handle, - buf.as_ptr() as LPVOID, - len, - &mut host_nwritten, - std::ptr::null_mut(), - ) - }; - - Ok(host_nwritten as usize) + writev(fd_entry.fd_object.raw_handle, &iovs).map_err(|e| host_impl::errno_from_win(e)) } pub(crate) fn fd_advise( @@ -158,7 +154,70 @@ pub(crate) fn path_open( mut needed_inheriting: host::__wasi_rights_t, fs_flags: host::__wasi_fdflags_t, ) -> Result { - unimplemented!("path_open") + use winx::file::{AccessRight, CreationDisposition, FlagsAndAttributes, ShareMode}; + + let mut win_rights = AccessRight::READ_CONTROL; + if read { + win_rights.insert(AccessRight::FILE_GENERIC_READ); + } + if write { + win_rights.insert(AccessRight::FILE_GENERIC_WRITE); + } + + // convert open flags + let (win_create_disp, mut win_flags_attrs) = host_impl::win_from_oflags(oflags); + if win_create_disp == CreationDisposition::CREATE_NEW { + needed_base |= host::__WASI_RIGHT_PATH_CREATE_FILE; + } else if win_create_disp == CreationDisposition::CREATE_ALWAYS { + needed_base |= host::__WASI_RIGHT_PATH_CREATE_FILE; + } else if win_create_disp == CreationDisposition::TRUNCATE_EXISTING { + needed_base |= host::__WASI_RIGHT_PATH_FILESTAT_SET_SIZE; + } + + // convert file descriptor flags + let win_fdflags_res = host_impl::win_from_fdflags(fs_flags); + win_rights.insert(win_fdflags_res.0); + win_flags_attrs.insert(win_fdflags_res.1); + if win_rights.contains(AccessRight::SYNCHRONIZE) { + needed_inheriting |= host::__WASI_RIGHT_FD_DATASYNC; + needed_inheriting |= host::__WASI_RIGHT_FD_SYNC; + } + + let (dir, path) = match path_get( + ctx, + dirfd, + dirflags, + path, + needed_base, + needed_inheriting, + !win_flags_attrs.contains(FlagsAndAttributes::FILE_FLAG_BACKUP_SEMANTICS), + ) { + Ok((dir, path)) => (dir, path), + Err(e) => return Err(e), + }; + + let new_handle = + match winx::file::openat(dir, &path, win_rights, win_create_disp, win_flags_attrs) { + Ok(handle) => handle, + Err(e) => return Err(host_impl::errno_from_win(e)), + }; + + // Determine the type of the new file descriptor and which rights contradict with this type + match unsafe { determine_type_rights(new_handle) } { + Err(e) => { + // if `close` fails, note it but do not override the underlying errno + winx::handle::close(new_handle).unwrap_or_else(|e| { + dbg!(e); + }); + Err(e) + } + Ok((_ty, max_base, max_inheriting)) => { + let mut fe = unsafe { FdEntry::from_raw_handle(new_handle) }; + fe.rights_base &= max_base; + fe.rights_inheriting &= max_inheriting; + Ok(fe) + } + } } pub(crate) fn fd_readdir( diff --git a/src/sys/windows/hostcalls_impl/fs_helpers.rs b/src/sys/windows/hostcalls_impl/fs_helpers.rs new file mode 100644 index 0000000000..d934959430 --- /dev/null +++ b/src/sys/windows/hostcalls_impl/fs_helpers.rs @@ -0,0 +1,139 @@ +#![allow(non_camel_case_types)] +#![allow(unused_unsafe)] + +use super::host_impl; +use crate::ctx::WasiCtx; +use crate::host; + +use std::ffi::{OsStr, OsString}; +use std::os::windows::prelude::RawHandle; +use std::path::{Component, Path, PathBuf}; + +/// Normalizes a path to ensure that the target path is located under the directory provided. +pub fn path_get>( + wasi_ctx: &WasiCtx, + dirfd: host::__wasi_fd_t, + _dirflags: host::__wasi_lookupflags_t, + path: P, + needed_base: host::__wasi_rights_t, + needed_inheriting: host::__wasi_rights_t, + needs_final_component: bool, +) -> Result<(RawHandle, OsString), host::__wasi_errno_t> { + /// close all the intermediate handles, but make sure not to drop either the original + /// dirfd or the one we return (which may be the same dirfd) + fn ret_dir_success(dir_stack: &mut Vec) -> RawHandle { + let ret_dir = dir_stack.pop().expect("there is always a dirfd to return"); + if let Some(dirfds) = dir_stack.get(1..) { + for dirfd in dirfds { + winx::handle::close(*dirfd).unwrap_or_else(|e| { + dbg!(e); + }); + } + } + ret_dir + } + + /// close all file descriptors other than the base directory, and return the errno for + /// convenience with `return` + fn ret_error( + dir_stack: &mut Vec, + errno: host::__wasi_errno_t, + ) -> Result<(RawHandle, OsString), host::__wasi_errno_t> { + if let Some(dirfds) = dir_stack.get(1..) { + for dirfd in dirfds { + winx::handle::close(*dirfd).unwrap_or_else(|e| { + dbg!(e); + }); + } + } + Err(errno) + } + + let dirfe = wasi_ctx.get_fd_entry(dirfd, needed_base, needed_inheriting)?; + + // Stack of directory handles. Index 0 always corresponds with the directory provided + // to this function. Entering a directory causes a handle to be pushed, while handling + // ".." entries causes an entry to be popped. Index 0 cannot be popped, as this would imply + // escaping the base directory. + let mut dir_stack = vec![dirfe.fd_object.raw_handle]; + + // Stack of paths left to process. This is initially the `path` argument to this function, but + // any symlinks we encounter are processed by pushing them on the stack. + let mut path_stack = vec![PathBuf::from(path.as_ref())]; + + loop { + match path_stack.pop() { + Some(cur_path) => { + // dbg!(&cur_path); + let mut components = cur_path.components(); + let head = match components.next() { + None => return ret_error(&mut dir_stack, host::__WASI_ENOENT), + Some(p) => p, + }; + let tail = components.as_path(); + + if tail.components().next().is_some() { + path_stack.push(PathBuf::from(tail)); + } + + match head { + Component::Prefix(_) | Component::RootDir => { + // path is absolute! + return ret_error(&mut dir_stack, host::__WASI_ENOTCAPABLE); + } + Component::CurDir => { + // "." so skip + continue; + } + Component::ParentDir => { + // ".." so pop a dir + let dirfd = dir_stack.pop().expect("dir_stack is never empty"); + + // we're not allowed to pop past the original directory + if dir_stack.is_empty() { + return ret_error(&mut dir_stack, host::__WASI_ENOTCAPABLE); + } else { + winx::handle::close(dirfd).unwrap_or_else(|e| { + dbg!(e); + }); + } + } + Component::Normal(head) => { + // should the component be a directory? it should if there is more path left to process, or + // if it has a trailing slash and `needs_final_component` is not set + if !path_stack.is_empty() + || (Path::new(head).is_dir() && !needs_final_component) + { + match winx::file::openat( + *dir_stack.last().expect("dir_stack is never empty"), + head, + winx::file::AccessRight::FILE_GENERIC_READ, + winx::file::CreationDisposition::OPEN_EXISTING, + winx::file::FlagsAndAttributes::FILE_FLAG_BACKUP_SEMANTICS, + ) { + Ok(new_dir) => { + dir_stack.push(new_dir); + continue; + } + Err(e) => { + return ret_error(&mut dir_stack, host_impl::errno_from_win(e)); + } + } + } else { + // we're done + return Ok((ret_dir_success(&mut dir_stack), head.to_os_string())); + } + } + } + } + None => { + // no further components to process. means we've hit a case like "." or "a/..", or if the + // input path has trailing slashes and `needs_final_component` is not set + return Ok(( + ret_dir_success(&mut dir_stack), + OsStr::new(".").to_os_string(), + )); + } + } + } +} diff --git a/src/sys/windows/hostcalls_impl/mod.rs b/src/sys/windows/hostcalls_impl/mod.rs index 7e928101c4..161eb36406 100644 --- a/src/sys/windows/hostcalls_impl/mod.rs +++ b/src/sys/windows/hostcalls_impl/mod.rs @@ -1,6 +1,7 @@ //! Windows-specific hostcalls that implement //! [WASI](https://github.com/CraneStation/wasmtime-wasi/blob/wasi/docs/WASI-overview.md). mod fs; +mod fs_helpers; mod misc; use super::fdentry; diff --git a/winx/Cargo.toml b/winx/Cargo.toml new file mode 100644 index 0000000000..20a115eac3 --- /dev/null +++ b/winx/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "winx" +version = "0.1.0" +authors = ["Jakub Konka "] +edition = "2018" + +[dependencies] +winapi = { version = "0.3", features = ["std", "errhandlingapi", "handleapi", "processthreadsapi", "securitybaseapi", "winbase", "winerror", "ws2def", "fileapi", "aclapi" ] } +bitflags = "1.0" diff --git a/winx/src/file.rs b/winx/src/file.rs new file mode 100644 index 0000000000..30339763d8 --- /dev/null +++ b/winx/src/file.rs @@ -0,0 +1,459 @@ +#![allow(non_camel_case_types)] +use crate::{winerror, Result}; +use std::ffi::{OsStr, OsString}; +use std::os::windows::prelude::{OsStrExt, OsStringExt, RawHandle}; +use winapi::shared::minwindef::{self, DWORD}; +use winapi::um::{fileapi, fileapi::GetFileType, winbase, winnt}; + +/// Maximum total path length for Unicode in Windows. +/// [Maximum path length limitation]: https://docs.microsoft.com/en-us/windows/desktop/FileIO/naming-a-file#maximum-path-length-limitation +pub const WIDE_MAX_PATH: DWORD = 0x7fff; + +#[derive(Debug, Copy, Clone)] +pub struct FileType(minwindef::DWORD); + +// possible types are: +// * FILE_TYPE_CHAR +// * FILE_TYPE_DISK +// * FILE_TYPE_PIPE +// * FILE_TYPE_REMOTE +// * FILE_TYPE_UNKNOWN +// +// FILE_TYPE_REMOTE is unused +// https://technet.microsoft.com/en-us/evalcenter/aa364960(v=vs.100) +impl FileType { + /// Returns true if character device such as LPT device or console + pub fn is_char(&self) -> bool { + self.0 == winbase::FILE_TYPE_CHAR + } + + /// Returns true if disk device such as file or dir + pub fn is_disk(&self) -> bool { + self.0 == winbase::FILE_TYPE_DISK + } + + /// Returns true if pipe device such as socket, named pipe or anonymous pipe + pub fn is_pipe(&self) -> bool { + self.0 == winbase::FILE_TYPE_PIPE + } + + /// Returns true if unknown device + pub fn is_unknown(&self) -> bool { + self.0 == winbase::FILE_TYPE_UNKNOWN + } +} + +pub fn get_file_type(handle: RawHandle) -> Result { + let file_type = unsafe { FileType(GetFileType(handle)) }; + let err = winerror::WinError::last(); + if file_type.is_unknown() && err != winerror::WinError::ERROR_SUCCESS { + Err(err) + } else { + Ok(file_type) + } +} + +bitflags! { + pub struct ShareMode: minwindef::DWORD { + /// Prevents other processes from opening a file or device if they request delete, read, or write access. + const NO_SHARE = 0x0; + /// Enables subsequent open operations on a file or device to request read access. + /// Otherwise, other processes cannot open the file or device if they request read access. + /// If this flag is not specified, but the file or device has been opened for read access, the function fails. + const FILE_SHARE_READ = winnt::FILE_SHARE_READ; + /// Enables subsequent open operations on a file or device to request write access. + /// Otherwise, other processes cannot open the file or device if they request write access. + /// If this flag is not specified, but the file or device has been opened for write access or has a file mapping with write access, the function fails. + const FILE_SHARE_WRITE = winnt::FILE_SHARE_WRITE; + /// Enables subsequent open operations on a file or device to request delete access. + /// Otherwise, other processes cannot open the file or device if they request delete access. + /// If this flag is not specified, but the file or device has been opened for delete access, the function fails. + const FILE_SHARE_DELETE = winnt::FILE_SHARE_DELETE; + const ALL = ShareMode::FILE_SHARE_READ.bits + | ShareMode::FILE_SHARE_WRITE.bits + | ShareMode::FILE_SHARE_DELETE.bits; + } +} + +#[derive(Debug, Clone, Copy, Eq, PartialEq)] +#[repr(u32)] +pub enum CreationDisposition { + NO_DISPOSITION = 0, + /// Creates a new file, only if it does not already exist. + /// If the specified file exists, the function fails and the last-error code is + /// set to ERROR_FILE_EXISTS (80). + /// + /// If the specified file does not exist and is a valid path to a writable location, + /// a new file is created. + CREATE_NEW = fileapi::CREATE_NEW, + /// Creates a new file, always. + /// If the specified file exists and is writable, the function overwrites the file, + /// the function succeeds, and last-error code is set to ERROR_ALREADY_EXISTS (183). + /// + /// If the specified file does not exist and is a valid path, a new file is created, + /// the function succeeds, and the last-error code is set to zero. + CREATE_ALWAYS = fileapi::CREATE_ALWAYS, + /// Opens a file or device, only if it exists. + /// If the specified file or device does not exist, the function fails and the + /// last-error code is set to ERROR_FILE_NOT_FOUND (2). + OPEN_EXISTING = fileapi::OPEN_EXISTING, + /// Opens a file, always. + /// If the specified file exists, the function succeeds and the last-error code is + /// set to ERROR_ALREADY_EXISTS (183). + /// + /// If the specified file does not exist and is a valid path to a writable location, + /// the function creates a file and the last-error code is set to zero. + OPEN_ALWAYS = fileapi::OPEN_ALWAYS, + /// Opens a file and truncates it so that its size is zero bytes, only if it exists. + /// If the specified file does not exist, the function fails and the last-error code + /// is set to ERROR_FILE_NOT_FOUND (2). + /// + /// The calling process must open the file with the GENERIC_WRITE bit set as part + /// of the dwDesiredAccess parameter. + TRUNCATE_EXISTING = fileapi::TRUNCATE_EXISTING, +} + +impl CreationDisposition { + pub fn from_u32(disp: u32) -> Self { + use CreationDisposition::*; + match disp { + fileapi::CREATE_NEW => CREATE_NEW, + fileapi::CREATE_ALWAYS => CREATE_ALWAYS, + fileapi::OPEN_EXISTING => OPEN_EXISTING, + fileapi::OPEN_ALWAYS => OPEN_ALWAYS, + fileapi::TRUNCATE_EXISTING => TRUNCATE_EXISTING, + _ => NO_DISPOSITION, + } + } +} + +bitflags! { + pub struct FlagsAndAttributes: minwindef::DWORD { + /// A file or directory that is an archive file or directory. + /// Applications typically use this attribute to mark files for backup or removal. + const FILE_ATTRIBUTE_ARCHIVE = winnt::FILE_ATTRIBUTE_ARCHIVE; + /// A file or directory that is compressed. For a file, all of the data in the file is compressed. + /// For a directory, compression is the default for newly created files and subdirectories. + const FILE_ATTRIBUTE_COMPRESSED = winnt::FILE_ATTRIBUTE_COMPRESSED; + /// This value is reserved for system use. + const FILE_ATTRIBUTE_DEVICE = winnt::FILE_ATTRIBUTE_DEVICE; + /// The handle that identifies a directory. + const FILE_ATTRIBUTE_DIRECTORY = winnt::FILE_ATTRIBUTE_DIRECTORY; + /// A file or directory that is encrypted. For a file, all data streams in the file are encrypted. + /// For a directory, encryption is the default for newly created files and subdirectories. + const FILE_ATTRIBUTE_ENCRYPTED = winnt::FILE_ATTRIBUTE_ENCRYPTED; + /// The file or directory is hidden. It is not included in an ordinary directory listing. + const FILE_ATTRIBUTE_HIDDEN = winnt::FILE_ATTRIBUTE_HIDDEN; + /// The directory or user data stream is configured with integrity (only supported on ReFS volumes). + /// It is not included in an ordinary directory listing. The integrity setting persists with the file if it's renamed. + /// If a file is copied the destination file will have integrity set if either the source file or destination directory have integrity set. + const FILE_ATTRIBUTE_INTEGRITY_STREAM = winnt::FILE_ATTRIBUTE_INTEGRITY_STREAM; + /// A file that does not have other attributes set. This attribute is valid only when used alone. + const FILE_ATTRIBUTE_NORMAL = winnt::FILE_ATTRIBUTE_NORMAL; + /// The file or directory is not to be indexed by the content indexing service. + const FILE_ATTRIBUTE_NOT_CONTENT_INDEXED = winnt::FILE_ATTRIBUTE_NOT_CONTENT_INDEXED; + /// The user data stream not to be read by the background data integrity scanner (AKA scrubber). + /// When set on a directory it only provides inheritance. This flag is only supported on Storage Spaces and ReFS volumes. + /// It is not included in an ordinary directory listing. + const FILE_ATTRIBUTE_NO_SCRUB_DATA = winnt::FILE_ATTRIBUTE_NO_SCRUB_DATA; + /// The data of a file is not available immediately. + /// This attribute indicates that the file data is physically moved to offline storage. + /// This attribute is used by Remote Storage, which is the hierarchical storage management software. + /// Applications should not arbitrarily change this attribute. + const FILE_ATTRIBUTE_OFFLINE = winnt::FILE_ATTRIBUTE_OFFLINE; + /// A file that is read-only. Applications can read the file, but cannot write to it or delete it. + /// This attribute is not honored on directories. + const FILE_ATTRIBUTE_READONLY = winnt::FILE_ATTRIBUTE_READONLY; + /// When this attribute is set, it means that the file or directory is not fully present locally. + /// For a file that means that not all of its data is on local storage (e.g. it may be sparse with some data still in remote storage). + /// For a directory it means that some of the directory contents are being virtualized from another location. + /// Reading the file / enumerating the directory will be more expensive than normal, e.g. it will cause at least some of the + /// file/directory content to be fetched from a remote store. Only kernel-mode callers can set this bit. + const FILE_ATTRIBUTE_RECALL_ON_DATA_ACCESS = winnt::FILE_ATTRIBUTE_RECALL_ON_DATA_ACCESS; + /// This attribute only appears in directory enumeration classes (FILE_DIRECTORY_INFORMATION, FILE_BOTH_DIR_INFORMATION, etc.). + /// When this attribute is set, it means that the file or directory has no physical representation on the local system; the item is virtual. + /// Opening the item will be more expensive than normal, e.g. it will cause at least some of it to be fetched from a remote store. + const FILE_ATTRIBUTE_RECALL_ON_OPEN = winnt::FILE_ATTRIBUTE_RECALL_ON_OPEN; + /// A file or directory that has an associated reparse point, or a file that is a symbolic link. + const FILE_ATTRIBUTE_REPARSE_POINT = winnt::FILE_ATTRIBUTE_REPARSE_POINT; + /// A file that is a sparse file. + const FILE_ATTRIBUTE_SPARSE_FILE = winnt::FILE_ATTRIBUTE_SPARSE_FILE; + /// A file or directory that the operating system uses a part of, or uses exclusively. + const FILE_ATTRIBUTE_SYSTEM = winnt::FILE_ATTRIBUTE_SYSTEM; + /// A file that is being used for temporary storage. + /// File systems avoid writing data back to mass storage if sufficient cache memory is available, because typically, + /// an application deletes a temporary file after the handle is closed. In that scenario, the system can entirely + /// avoid writing the data. Otherwise, the data is written after the handle is closed. + const FILE_ATTRIBUTE_TEMPORARY = winnt::FILE_ATTRIBUTE_TEMPORARY; + /// This value is reserved for system use. + const FILE_ATTRIBUTE_VIRTUAL = winnt::FILE_ATTRIBUTE_VIRTUAL; + /// The file is being opened or created for a backup or restore operation. + /// The system ensures that the calling process overrides file security checks when the process has SE_BACKUP_NAME and SE_RESTORE_NAME privileges. + /// You must set this flag to obtain a handle to a directory. A directory handle can be passed to some functions instead of a file handle. + const FILE_FLAG_BACKUP_SEMANTICS = winbase::FILE_FLAG_BACKUP_SEMANTICS; + /// The file is to be deleted immediately after all of its handles are closed, which includes the specified handle and any other open or duplicated handles. + /// If there are existing open handles to a file, the call fails unless they were all opened with the FILE_SHARE_DELETE share mode. + /// Subsequent open requests for the file fail, unless the FILE_SHARE_DELETE share mode is specified. + const FILE_FLAG_DELETE_ON_CLOSE = winbase::FILE_FLAG_DELETE_ON_CLOSE; + /// The file or device is being opened with no system caching for data reads and writes. + /// This flag does not affect hard disk caching or memory mapped files. + /// There are strict requirements for successfully working with files opened with + /// CreateFile using the FILE_FLAG_NO_BUFFERING flag. + const FILE_FLAG_NO_BUFFERING = winbase::FILE_FLAG_NO_BUFFERING; + /// The file data is requested, but it should continue to be located in remote storage. + /// It should not be transported back to local storage. This flag is for use by remote storage systems. + const FILE_FLAG_OPEN_NO_RECALL = winbase::FILE_FLAG_OPEN_NO_RECALL; + /// Normal reparse point processing will not occur; CreateFile will attempt to open the reparse point. + /// When a file is opened, a file handle is returned, whether or not the filter that controls the reparse point is operational. + /// This flag cannot be used with the CREATE_ALWAYS flag. + /// If the file is not a reparse point, then this flag is ignored. + const FILE_FLAG_OPEN_REPARSE_POINT = winbase::FILE_FLAG_OPEN_REPARSE_POINT; + /// The file or device is being opened or created for asynchronous I/O. + /// When subsequent I/O operations are completed on this handle, the event specified in the OVERLAPPED structure will be set to the signaled state. + /// If this flag is specified, the file can be used for simultaneous read and write operations. + /// If this flag is not specified, then I/O operations are serialized, even if the calls to the read and write functions specify an OVERLAPPED structure. + const FILE_FLAG_OVERLAPPED = winbase::FILE_FLAG_OVERLAPPED; + /// Access will occur according to POSIX rules. This includes allowing multiple files with names, + /// differing only in case, for file systems that support that naming. Use care when using this option, + /// because files created with this flag may not be accessible by applications that are written for MS-DOS or 16-bit Windows. + const FILE_FLAG_POSIX_SEMANTICS = winbase::FILE_FLAG_POSIX_SEMANTICS; + /// Access is intended to be random. The system can use this as a hint to optimize file caching. + /// This flag has no effect if the file system does not support cached I/O and FILE_FLAG_NO_BUFFERING. + const FILE_FLAG_RANDOM_ACCESS = winbase::FILE_FLAG_RANDOM_ACCESS; + /// The file or device is being opened with session awareness. + /// If this flag is not specified, then per-session devices (such as a device using RemoteFX USB Redirection) + /// cannot be opened by processes running in session 0. This flag has no effect for callers not in session 0. + /// This flag is supported only on server editions of Windows. + const FILE_FLAG_SESSION_AWARE = winbase::FILE_FLAG_SESSION_AWARE; + /// Access is intended to be sequential from beginning to end. The system can use this as a hint to optimize file caching. + /// This flag should not be used if read-behind (that is, reverse scans) will be used. + /// This flag has no effect if the file system does not support cached I/O and FILE_FLAG_NO_BUFFERING. + const FILE_FLAG_SEQUENTIAL_SCAN = winbase::FILE_FLAG_SEQUENTIAL_SCAN; + /// Write operations will not go through any intermediate cache, they will go directly to disk. + const FILE_FLAG_WRITE_THROUGH = winbase::FILE_FLAG_WRITE_THROUGH; + } +} + +bitflags! { + /// [Access mask]: https://docs.microsoft.com/en-us/windows/desktop/SecAuthZ/access-mask + pub struct AccessRight: minwindef::DWORD { + /// For a file object, the right to read the corresponding file data. + /// For a directory object, the right to read the corresponding directory data. + const FILE_READ_DATA = winnt::FILE_READ_DATA; + const FILE_LIST_DIRECTORY = winnt::FILE_LIST_DIRECTORY; + /// For a file object, the right to write data to the file. + /// For a directory object, the right to create a file in the directory. + const FILE_WRITE_DATA = winnt::FILE_WRITE_DATA; + const FILE_ADD_FILE = winnt::FILE_ADD_FILE; + /// For a file object, the right to append data to the file. + /// (For local files, write operations will not overwrite existing data + /// if this flag is specified without FILE_WRITE_DATA.) + /// For a directory object, the right to create a subdirectory. + /// For a named pipe, the right to create a pipe. + const FILE_APPEND_DATA = winnt::FILE_APPEND_DATA; + const FILE_ADD_SUBDIRECTORY = winnt::FILE_ADD_SUBDIRECTORY; + const FILE_CREATE_PIPE_INSTANCE = winnt::FILE_CREATE_PIPE_INSTANCE; + /// The right to read extended file attributes. + const FILE_READ_EA = winnt::FILE_READ_EA; + /// The right to write extended file attributes. + const FILE_WRITE_EA = winnt::FILE_WRITE_EA; + /// For a file, the right to execute FILE_EXECUTE. + /// For a directory, the right to traverse the directory. + /// By default, users are assigned the BYPASS_TRAVERSE_CHECKING privilege, + /// which ignores the FILE_TRAVERSE access right. + const FILE_EXECUTE = winnt::FILE_EXECUTE; + const FILE_TRAVERSE = winnt::FILE_TRAVERSE; + /// For a directory, the right to delete a directory and all + /// the files it contains, including read-only files. + const FILE_DELETE_CHILD = winnt::FILE_DELETE_CHILD; + /// The right to read file attributes. + const FILE_READ_ATTRIBUTES = winnt::FILE_READ_ATTRIBUTES; + /// The right to write file attributes. + const FILE_WRITE_ATTRIBUTES = winnt::FILE_WRITE_ATTRIBUTES; + /// The right to delete the object. + const DELETE = winnt::DELETE; + /// The right to read the information in the object's security descriptor, + /// not including the information in the system access control list (SACL). + const READ_CONTROL = winnt::READ_CONTROL; + /// The right to use the object for synchronization. This enables a thread + /// to wait until the object is in the signaled state. Some object types + /// do not support this access right. + const SYNCHRONIZE = winnt::SYNCHRONIZE; + /// The right to modify the discretionary access control list (DACL) in + /// the object's security descriptor. + const WRITE_DAC = winnt::WRITE_DAC; + /// The right to change the owner in the object's security descriptor. + const WRITE_OWNER = winnt::WRITE_OWNER; + /// It is used to indicate access to a system access control list (SACL). + const ACCESS_SYSTEM_SECURITY = winnt::ACCESS_SYSTEM_SECURITY; + /// Maximum allowed. + const MAXIMUM_ALLOWED = winnt::MAXIMUM_ALLOWED; + /// Reserved + const RESERVED1 = 0x4000000; + /// Reserved + const RESERVED2 = 0x8000000; + /// Provides all possible access rights. + /// This is convenience flag which is translated by the OS into actual [`FILE_GENERIC_ALL`] union. + const GENERIC_ALL = winnt::GENERIC_ALL; + /// Provides execute access. + const GENERIC_EXECUTE = winnt::GENERIC_EXECUTE; + /// Provides write access. + /// This is convenience flag which is translated by the OS into actual [`FILE_GENERIC_WRITE`] union. + const GENERIC_WRITE = winnt::GENERIC_WRITE; + /// Provides read access. + /// This is convenience flag which is translated by the OS into actual [`FILE_GENERIC_READ`] union. + const GENERIC_READ = winnt::GENERIC_READ; + /// Provides read access. + /// This flag is a union of: FILE_READ_ATTRIBUTES, FILE_READ_DATA, FILE_READ_EA, READ_CONTROL, SYNCHRONIZE + const FILE_GENERIC_READ = AccessRight::FILE_READ_ATTRIBUTES.bits + | AccessRight::FILE_READ_DATA.bits + | AccessRight::FILE_READ_EA.bits + | AccessRight::READ_CONTROL.bits + | AccessRight::SYNCHRONIZE.bits; + /// Provides write access. + /// This flag is a union of: FILE_WRITE_ATTRIBUTES, FILE_WRITE_DATA, FILE_WRITE_EA, READ_CONTROL, SYNCHRONIZE + const FILE_GENERIC_WRITE = AccessRight::FILE_WRITE_ATTRIBUTES.bits + | AccessRight::FILE_WRITE_DATA.bits + | AccessRight::FILE_WRITE_EA.bits + | AccessRight::READ_CONTROL.bits + | AccessRight::SYNCHRONIZE.bits; + /// Provides execute access. + /// This flag is a union of: FILE_WRITE_ATTRIBUTES, FILE_WRITE_DATA, FILE_WRITE_EA, READ_CONTROL, SYNCHRONIZE + const FILE_GENERIC_EXECUTE = AccessRight::FILE_EXECUTE.bits + | AccessRight::FILE_READ_ATTRIBUTES.bits + | AccessRight::READ_CONTROL.bits + | AccessRight::SYNCHRONIZE.bits; + /// Provides all accesses. + /// This flag is a union of: FILE_GENERIC_READ, FILE_GENERIC_WRITE, FILE_GENERIC_EXECUTE + const FILE_GENERIC_ALL = AccessRight::FILE_GENERIC_READ.bits | AccessRight::FILE_GENERIC_WRITE.bits | AccessRight::FILE_GENERIC_EXECUTE.bits; + } +} + +pub fn get_file_access_rights(handle: RawHandle) -> Result { + use winapi::shared::minwindef::FALSE; + use winapi::um::accctrl; + use winapi::um::aclapi::GetSecurityInfo; + use winapi::um::securitybaseapi::{GetAce, IsValidAcl}; + unsafe { + let mut dacl = 0 as winnt::PACL; + let mut sec_desc = 0 as winnt::PSECURITY_DESCRIPTOR; + + let err = winerror::WinError::from_u32(GetSecurityInfo( + handle, + accctrl::SE_FILE_OBJECT, + winnt::DACL_SECURITY_INFORMATION, + std::ptr::null_mut(), + std::ptr::null_mut(), + &mut dacl, + std::ptr::null_mut(), + &mut sec_desc, + )); + + if err != winerror::WinError::ERROR_SUCCESS { + return Err(err); + } + + if IsValidAcl(dacl) == FALSE { + return Err(winerror::WinError::last()); + } + + // let count = (*dacl).AceCount; + let mut ace = 0 as winnt::PVOID; + + if GetAce(dacl, 0, &mut ace) == FALSE { + return Err(winerror::WinError::last()); + } + + // TODO: check for PACCESS_ALLOWED_ACE in Ace before accessing + // let header = (*(ace as winnt::PACCESS_ALLOWED_ACE)).Header.AceType; + Ok((*(ace as winnt::PACCESS_ALLOWED_ACE)).Mask) + } +} + +/// Converts OS string reference to Windows wide UTF-16 format. +pub fn str_to_wide>(s: S) -> Vec { + let mut win_unicode: Vec = s.as_ref().encode_wide().collect(); + win_unicode.push(0); + win_unicode +} + +fn get_path_by_handle(handle: RawHandle) -> Result { + use winapi::um::fileapi::GetFinalPathNameByHandleW; + + let mut raw_path: Vec = Vec::with_capacity(WIDE_MAX_PATH as usize); + raw_path.resize(WIDE_MAX_PATH as usize, 0); + + let read_len = + unsafe { GetFinalPathNameByHandleW(handle, raw_path.as_mut_ptr(), WIDE_MAX_PATH, 0) }; + + if read_len == 0 { + // failed to read + return Err(winerror::WinError::last()); + } + if read_len > WIDE_MAX_PATH { + // path too long (practically probably impossible) + return Err(winerror::WinError::ERROR_BUFFER_OVERFLOW); + } + + // concatenate paths + raw_path.resize(read_len as usize, 0); + Ok(OsString::from_wide(&raw_path)) +} + +fn strip_extended_prefix>(path: P) -> OsString { + let path = str_to_wide(path); + if &[92, 92, 63, 92] == &path[0..4] { + OsString::from_wide(&path[4..]) + } else { + OsString::from_wide(&path) + } +} + +/// Opens a `path` relative to a directory handle `dir_handle`, and returns a handle to the +/// newly opened file. The newly opened file will have the specified `AccessRight` `rights`. +/// +/// If the `path` is absolute, then the directory handle `dir_handle` is ignored. +pub fn openat>( + dir_handle: RawHandle, + path: S, + rights: AccessRight, + disposition: CreationDisposition, + flags_attrs: FlagsAndAttributes, +) -> Result { + use std::path::PathBuf; + use winapi::um::fileapi::CreateFileW; + use winapi::um::handleapi::INVALID_HANDLE_VALUE; + + // check if specified path is absolute + let path = PathBuf::from(path.as_ref()); + let out_path = if path.is_absolute() { + path + } else { + let dir_path = get_path_by_handle(dir_handle)?; + // concatenate paths + let mut out_path = PathBuf::from(&dir_path); + out_path.push(path); + out_path.into() + }; + + // this is needed so that we can use relative paths + let raw_out_path = strip_extended_prefix(out_path); + let raw_out_path = str_to_wide(raw_out_path); + let handle = unsafe { + CreateFileW( + raw_out_path.as_ptr(), + rights.bits(), + ShareMode::ALL.bits(), + std::ptr::null_mut(), + disposition as minwindef::DWORD, + flags_attrs.bits(), + std::ptr::null_mut(), + ) + }; + + if handle == INVALID_HANDLE_VALUE { + Err(winerror::WinError::last()) + } else { + Ok(handle) + } +} diff --git a/winx/src/handle.rs b/winx/src/handle.rs new file mode 100644 index 0000000000..79efa31b9c --- /dev/null +++ b/winx/src/handle.rs @@ -0,0 +1,37 @@ +#![allow(non_camel_case_types)] +use crate::{winerror, Result}; +use std::os::windows::prelude::RawHandle; +use winapi::shared::minwindef::FALSE; + +pub fn dup(old_handle: RawHandle) -> Result { + use winapi::um::handleapi::DuplicateHandle; + use winapi::um::processthreadsapi::GetCurrentProcess; + use winapi::um::winnt::DUPLICATE_SAME_ACCESS; + unsafe { + let mut new_handle = 0 as RawHandle; + let cur_proc = GetCurrentProcess(); + if DuplicateHandle( + cur_proc, + old_handle, + cur_proc, + &mut new_handle, + 0, // dwDesiredAccess; this flag is ignored if DUPLICATE_SAME_ACCESS is specified + FALSE, + DUPLICATE_SAME_ACCESS, + ) == FALSE + { + Err(winerror::WinError::last()) + } else { + Ok(new_handle) + } + } +} + +pub fn close(handle: RawHandle) -> Result<()> { + use winapi::um::handleapi::CloseHandle; + if unsafe { CloseHandle(handle) } == FALSE { + Err(winerror::WinError::last()) + } else { + Ok(()) + } +} diff --git a/winx/src/io.rs b/winx/src/io.rs new file mode 100644 index 0000000000..3bb950d80c --- /dev/null +++ b/winx/src/io.rs @@ -0,0 +1,112 @@ +use crate::{winerror, Result}; + +use std::marker::PhantomData; +use std::os::windows::prelude::*; +use winapi::shared::{ntdef, ws2def}; + +// these will be obsolete once https://github.com/rust-lang/rust/pull/60334 +// lands in stable +pub struct IoVec<'a> { + vec: ws2def::WSABUF, + _p: PhantomData<&'a [u8]>, +} + +impl<'a> IoVec<'a> { + #[inline] + pub fn new(buf: &'a [u8]) -> Self { + Self { + vec: ws2def::WSABUF { + len: buf.len() as ntdef::ULONG, + buf: buf.as_ptr() as *mut u8 as *mut ntdef::CHAR, + }, + _p: PhantomData, + } + } + + #[inline] + pub fn as_slice(&self) -> &[u8] { + unsafe { std::slice::from_raw_parts(self.vec.buf as *const u8, self.vec.len as usize) } + } +} + +pub struct IoVecMut<'a> { + vec: ws2def::WSABUF, + _p: PhantomData<&'a mut [u8]>, +} + +impl<'a> IoVecMut<'a> { + #[inline] + pub fn new(buf: &'a mut [u8]) -> Self { + Self { + vec: ws2def::WSABUF { + len: buf.len() as ntdef::ULONG, + buf: buf.as_ptr() as *mut u8 as *mut ntdef::CHAR, + }, + _p: PhantomData, + } + } + + #[inline] + pub fn as_slice(&'a self) -> &'a [u8] { + unsafe { std::slice::from_raw_parts(self.vec.buf as *const u8, self.vec.len as usize) } + } + + #[inline] + pub fn as_mut_slice(&'a mut self) -> &'a mut [u8] { + unsafe { std::slice::from_raw_parts_mut(self.vec.buf as *mut u8, self.vec.len as usize) } + } +} + +pub fn writev<'a>(raw_handle: RawHandle, iovecs: &'a [IoVec<'a>]) -> Result { + use winapi::shared::minwindef::{DWORD, FALSE, LPVOID}; + use winapi::um::fileapi::WriteFile; + + let buf = iovecs + .iter() + .find(|b| !b.as_slice().is_empty()) + .map_or(&[][..], |b| b.as_slice()); + + let mut host_nwritten = 0; + let len = std::cmp::min(buf.len(), ::max_value() as usize) as DWORD; + unsafe { + if WriteFile( + raw_handle, + buf.as_ptr() as LPVOID, + len, + &mut host_nwritten, + std::ptr::null_mut(), + ) == FALSE + { + return Err(winerror::WinError::last()); + } + } + + Ok(host_nwritten as usize) +} + +pub fn readv<'a>(raw_handle: RawHandle, iovecs: &'a mut [IoVecMut<'a>]) -> Result { + use winapi::shared::minwindef::{DWORD, FALSE, LPVOID}; + use winapi::um::fileapi::ReadFile; + + let buf = iovecs + .iter_mut() + .find(|b| !b.as_slice().is_empty()) + .map_or(&mut [][..], |b| b.as_mut_slice()); + + let mut host_nread = 0; + let len = std::cmp::min(buf.len(), ::max_value() as usize) as DWORD; + unsafe { + if ReadFile( + raw_handle, + buf.as_mut_ptr() as LPVOID, + len, + &mut host_nread, + std::ptr::null_mut(), + ) == FALSE + { + return Err(winerror::WinError::last()); + } + } + + Ok(host_nread as usize) +} diff --git a/winx/src/lib.rs b/winx/src/lib.rs new file mode 100644 index 0000000000..ae7d7f2218 --- /dev/null +++ b/winx/src/lib.rs @@ -0,0 +1,33 @@ +#![deny( + // missing_docs, + trivial_numeric_casts, + unused_extern_crates, + unstable_features +)] +#![warn(unused_import_braces)] +#![cfg_attr(feature = "clippy", plugin(clippy(conf_file = "../clippy.toml")))] +#![cfg_attr(feature = "cargo-clippy", allow(clippy::new_without_default))] +#![cfg_attr( + feature = "cargo-clippy", + warn( + clippy::float_arithmetic, + clippy::mut_mut, + clippy::nonminimal_bool, + clippy::option_map_unwrap_or, + clippy::option_map_unwrap_or_else, + clippy::unicode_not_nfc, + clippy::use_self + ) +)] + +#[macro_use] +extern crate bitflags; + +pub mod file; +pub mod handle; +pub mod io; +pub mod winerror; + +use winerror::WinError; + +pub type Result = std::result::Result; diff --git a/winx/src/winerror.rs b/winx/src/winerror.rs new file mode 100644 index 0000000000..107e7bf20e --- /dev/null +++ b/winx/src/winerror.rs @@ -0,0 +1,117 @@ +#![allow(non_camel_case_types)] +use winapi::shared::winerror; +use winapi::um::errhandlingapi::GetLastError; + +macro_rules! win_error_expand { + { + $( + #[doc=$doc:literal] + $error:ident, + )* + } => { + /// Wraps WINAPI error code as enum. + #[derive(Debug, Clone, Copy, Eq, PartialEq)] + #[repr(u32)] + pub enum WinError { + /// Unknown error occurred. + UnknownError = std::u32::MAX, + $( + #[doc=$doc] + $error = winerror::$error, + )* + } + + fn desc(err: WinError) -> &'static str { + use WinError::*; + match err { + UnknownError => r" Unknown error occurred.", + $($error => $doc,)* + } + } + + fn from_u32(err: u32) -> WinError { + use WinError::*; + match err { + $(winerror::$error => $error,)* + _ => UnknownError, + } + } + } +} + +win_error_expand! { + /// The operation completed successfully. + ERROR_SUCCESS, + /// The system cannot find the file specified. + ERROR_FILE_NOT_FOUND, + /// The system cannot find the path specified. + ERROR_PATH_NOT_FOUND, + /// The system cannot open the file. + ERROR_TOO_MANY_OPEN_FILES, + /// Access is denied. + ERROR_ACCESS_DENIED, + /// The handle is invalid. + ERROR_INVALID_HANDLE, + /// Not enough storage is available to process this command. + ERROR_NOT_ENOUGH_MEMORY, + /// The environment is incorrect. + ERROR_BAD_ENVIRONMENT, + /// Not enough storage is available to complete this operation. + ERROR_OUTOFMEMORY, + /// The device is not ready. + ERROR_NOT_READY, + /// The request is not supported. + ERROR_NOT_SUPPORTED, + /// The file exists. + ERROR_FILE_EXISTS, + /// The pipe has been ended. + ERROR_BROKEN_PIPE, + /// The file name is too long. + ERROR_BUFFER_OVERFLOW, + /// The directory is not empty. + ERROR_DIR_NOT_EMPTY, + /// The volume label you entered exceeds the label character limit of the destination file system. + ERROR_LABEL_TOO_LONG, + /// The requested resource is in use. + ERROR_BUSY, + /// The file name, directory name, or volume label syntax is incorrect. + ERROR_INVALID_NAME, + /// The process cannot access the file because it is being used by another process. + ERROR_SHARING_VIOLATION, +} + +impl WinError { + /// Returns the last error as WinError. + pub fn last() -> Self { + Self::from_u32(unsafe { GetLastError() }) + } + + /// Constructs WinError from error code. + pub fn from_u32(err: u32) -> Self { + from_u32(err) + } + + /// Returns error's description string. This description matches + /// the docs for the error. + pub fn desc(self) -> &'static str { + desc(self) + } +} + +impl std::error::Error for WinError { + fn description(&self) -> &str { + self.desc() + } +} + +impl std::fmt::Display for WinError { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "{:?}: {}", self, self.desc()) + } +} + +impl From for std::io::Error { + fn from(err: WinError) -> Self { + Self::from_raw_os_error(err as i32) + } +}