path_get refactor and implementation of missing path_ hostcalls on Windows (#41)

* Move path_get outside of sys module

* Add implementation of readlinkat

* Clean up path_open; use OpenOptions as much as possible

* Enable close_preopen test

* Implement path_create_directory; fix path_open

* Refactor path concatenation onto a descriptor

* Implement path_remove_directory

* Implement path_unlink_file

* Rewrite path_open using specific access mask

* Fix error mapping when unlinking file

* Fix readlinkat to pass nofollow_errors testcase

* Clean up winerror to WASI conversion

* Spoof creating dangling symlinks on windows (hacky!)

* Add positive testcase for readlink

* Implement path_readlink (for nonzero buffers for now)

* Clean up

* Add Symlink struct immitating *nix symlink

* Fix path_readlink

* Augment interesting_paths testcase with trailing slashes example

* Encapsulate path_get return value as PathGet struct

* Remove dangling symlink emulation

* Extract dangling symlinks into its own testcase

This way, we can re-enable nofollow_errors testcase
on Windows also.

* Return __WASI_ENOTCAPABLE if user lacks perms to symlink
This commit is contained in:
Jakub Konka
2019-08-08 17:06:01 +02:00
committed by GitHub
parent 8ea7a983d8
commit e18175c556
19 changed files with 757 additions and 690 deletions

View File

@@ -1,9 +1,9 @@
#![allow(non_camel_case_types)]
use crate::{winerror, Result};
use std::ffi::{c_void, OsStr, OsString};
use std::ffi::{c_void, OsString};
use std::fs::File;
use std::io;
use std::os::windows::prelude::{AsRawHandle, OsStrExt, OsStringExt, RawHandle};
use std::os::windows::prelude::{AsRawHandle, OsStringExt, RawHandle};
use winapi::shared::minwindef::{self, DWORD};
use winapi::um::{fileapi, fileapi::GetFileType, minwinbase, winbase, winnt};
@@ -55,28 +55,6 @@ pub fn get_file_type(handle: RawHandle) -> Result<FileType> {
}
}
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 {
@@ -130,7 +108,7 @@ impl CreationDisposition {
}
bitflags! {
pub struct FlagsAndAttributes: minwindef::DWORD {
pub struct Attributes: 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;
@@ -189,6 +167,11 @@ bitflags! {
const FILE_ATTRIBUTE_TEMPORARY = winnt::FILE_ATTRIBUTE_TEMPORARY;
/// This value is reserved for system use.
const FILE_ATTRIBUTE_VIRTUAL = winnt::FILE_ATTRIBUTE_VIRTUAL;
}
}
bitflags! {
pub struct Flags: minwindef::DWORD {
/// 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.
@@ -238,7 +221,7 @@ bitflags! {
bitflags! {
/// [Access mask]: https://docs.microsoft.com/en-us/windows/desktop/SecAuthZ/access-mask
pub struct AccessRight: minwindef::DWORD {
pub struct AccessMode: 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;
@@ -307,31 +290,31 @@ bitflags! {
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;
const FILE_GENERIC_READ = AccessMode::FILE_READ_ATTRIBUTES.bits
| AccessMode::FILE_READ_DATA.bits
| AccessMode::FILE_READ_EA.bits
| AccessMode::READ_CONTROL.bits
| AccessMode::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;
const FILE_GENERIC_WRITE = AccessMode::FILE_WRITE_ATTRIBUTES.bits
| AccessMode::FILE_WRITE_DATA.bits
| AccessMode::FILE_WRITE_EA.bits
| AccessMode::READ_CONTROL.bits
| AccessMode::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;
const FILE_GENERIC_EXECUTE = AccessMode::FILE_EXECUTE.bits
| AccessMode::FILE_READ_ATTRIBUTES.bits
| AccessMode::READ_CONTROL.bits
| AccessMode::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;
const FILE_GENERIC_ALL = AccessMode::FILE_GENERIC_READ.bits | AccessMode::FILE_GENERIC_WRITE.bits | AccessMode::FILE_GENERIC_EXECUTE.bits;
}
}
pub fn get_file_access_rights(handle: RawHandle) -> Result<minwindef::DWORD> {
pub fn get_file_access_mode(handle: RawHandle) -> Result<AccessMode> {
use winapi::shared::minwindef::FALSE;
use winapi::um::accctrl;
use winapi::um::aclapi::GetSecurityInfo;
@@ -368,18 +351,13 @@ pub fn get_file_access_rights(handle: RawHandle) -> Result<minwindef::DWORD> {
// 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)
Ok(AccessMode::from_bits_truncate(
(*(ace as winnt::PACCESS_ALLOWED_ACE)).Mask,
))
}
}
/// Converts OS string reference to Windows wide UTF-16 format.
pub fn str_to_wide<S: AsRef<OsStr>>(s: S) -> Vec<u16> {
let mut win_unicode: Vec<u16> = s.as_ref().encode_wide().collect();
win_unicode.push(0);
win_unicode
}
fn get_path_by_handle(handle: RawHandle) -> Result<OsString> {
pub fn get_path_by_handle(handle: RawHandle) -> Result<OsString> {
use winapi::um::fileapi::GetFinalPathNameByHandleW;
let mut raw_path: Vec<u16> = Vec::with_capacity(WIDE_MAX_PATH as usize);
@@ -402,64 +380,6 @@ fn get_path_by_handle(handle: RawHandle) -> Result<OsString> {
Ok(OsString::from_wide(&raw_path))
}
fn strip_extended_prefix<P: AsRef<OsStr>>(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<S: AsRef<OsStr>>(
dir_handle: RawHandle,
path: S,
rights: AccessRight,
disposition: CreationDisposition,
flags_attrs: FlagsAndAttributes,
) -> Result<RawHandle> {
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)
}
}
// Taken from Rust libstd, file libstd/sys/windows/fs.rs
fn cvt(i: winapi::shared::minwindef::BOOL) -> io::Result<()> {
if i == 0 {

View File

@@ -78,6 +78,12 @@ win_error_expand! {
ERROR_INVALID_NAME,
/// The process cannot access the file because it is being used by another process.
ERROR_SHARING_VIOLATION,
/// A required privilege is not held by the client.
ERROR_PRIVILEGE_NOT_HELD,
/// The file or directory is not a reparse point.
ERROR_NOT_A_REPARSE_POINT,
/// An attempt was made to move the file pointer before the beginning of the file.
ERROR_NEGATIVE_SEEK,
}
impl WinError {