Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Create windows/api.rs for safer FFI #116816

Merged
merged 1 commit into from
Oct 28, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
157 changes: 157 additions & 0 deletions library/std/src/sys/windows/api.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
//! # Safe(r) wrappers around Windows API functions.
//!
//! This module contains fairly thin wrappers around Windows API functions,
//! aimed at centralising safety instead of having unsafe blocks spread
//! throughout higher level code. This makes it much easier to audit FFI safety.
//!
//! Not all functions can be made completely safe without more context but in
//! such cases we should still endeavour to reduce the caller's burden of safety
//! as much as possible.
//!
//! ## Guidelines for wrappers
//!
//! Items here should be named similarly to their raw Windows API name, except
//! that they follow Rust's case conventions. E.g. function names are
//! lower_snake_case. The idea here is that it should be easy for a Windows
//! C/C++ programmer to identify the underlying function that's being wrapped
//! while not looking too out of place in Rust code.
//!
//! Every use of an `unsafe` block must have a related SAFETY comment, even if
//! it's trivially safe (for example, see `get_last_error`). Public unsafe
//! functions must document what the caller has to do to call them safely.
//!
//! Avoid unchecked `as` casts. For integers, either assert that the integer
//! is in range or use `try_into` instead. For pointers, prefer to use
//! `ptr.cast::<Type>()` when possible.
//!
//! This module must only depend on core and not on std types as the eventual
//! hope is to have std depend on sys and not the other way around.
//! However, some amount of glue code may currently be necessary so such code
//! should go in sys/windows/mod.rs rather than here. See `IoResult` as an example.

use core::ffi::c_void;
use core::ptr::addr_of;

use super::c;

/// Helper method for getting the size of `T` as a u32.
/// Errors at compile time if the size would overflow.
///
/// While a type larger than u32::MAX is unlikely, it is possible if only because of a bug.
/// However, one key motivation for this function is to avoid the temptation to
/// use frequent `as` casts. This is risky because they are too powerful.
/// For example, the following will compile today:
///
/// `std::mem::size_of::<u64> as u32`
///
/// Note that `size_of` is never actually called, instead a function pointer is
/// converted to a `u32`. Clippy would warn about this but, alas, it's not run
/// on the standard library.
const fn win32_size_of<T: Sized>() -> u32 {
// Const assert that the size is less than u32::MAX.
// Uses a trait to workaround restriction on using generic types in inner items.
trait Win32SizeOf: Sized {
const WIN32_SIZE_OF: u32 = {
let size = core::mem::size_of::<Self>();
assert!(size <= u32::MAX as usize);
Comment on lines +51 to +56
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment says about less than u32::MAX, but actual code checks assert!(size <= u32::MAX as usize);

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I assume this is because the size should not exceed u32::MAX, right @ChrisDenton? Happy to r+ a patch changing the comment to that effect.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I probably meant to write "less than or equal" but "not exceed" reads better.

size as u32
};
}
impl<T: Sized> Win32SizeOf for T {}

T::WIN32_SIZE_OF
}

/// The `SetFileInformationByHandle` function takes a generic parameter by
/// making the user specify the type (class), a pointer to the data and its
/// size. This trait allows attaching that information to a Rust type so that
/// [`set_file_information_by_handle`] can be called safely.
///
/// This trait is designed so that it can support variable sized types.
/// However, currently Rust's std only uses fixed sized structures.
///
/// # Safety
///
/// * `as_ptr` must return a pointer to memory that is readable up to `size` bytes.
/// * `CLASS` must accurately reflect the type pointed to by `as_ptr`. E.g.
/// the `FILE_BASIC_INFO` structure has the class `FileBasicInfo`.
pub unsafe trait SetFileInformation {
/// The type of information to set.
const CLASS: i32;
/// A pointer to the file information to set.
fn as_ptr(&self) -> *const c_void;
/// The size of the type pointed to by `as_ptr`.
fn size(&self) -> u32;
}
/// Helper trait for implementing `SetFileInformation` for statically sized types.
unsafe trait SizedSetFileInformation: Sized {
const CLASS: i32;
}
unsafe impl<T: SizedSetFileInformation> SetFileInformation for T {
const CLASS: i32 = T::CLASS;
fn as_ptr(&self) -> *const c_void {
addr_of!(*self).cast::<c_void>()
}
fn size(&self) -> u32 {
win32_size_of::<Self>()
}
}

// SAFETY: FILE_BASIC_INFO, FILE_END_OF_FILE_INFO, FILE_ALLOCATION_INFO,
// FILE_DISPOSITION_INFO, FILE_DISPOSITION_INFO_EX and FILE_IO_PRIORITY_HINT_INFO
// are all plain `repr(C)` structs that only contain primitive types.
// The given information classes correctly match with the struct.
unsafe impl SizedSetFileInformation for c::FILE_BASIC_INFO {
const CLASS: i32 = c::FileBasicInfo;
}
unsafe impl SizedSetFileInformation for c::FILE_END_OF_FILE_INFO {
const CLASS: i32 = c::FileEndOfFileInfo;
}
unsafe impl SizedSetFileInformation for c::FILE_ALLOCATION_INFO {
const CLASS: i32 = c::FileAllocationInfo;
}
unsafe impl SizedSetFileInformation for c::FILE_DISPOSITION_INFO {
const CLASS: i32 = c::FileDispositionInfo;
}
unsafe impl SizedSetFileInformation for c::FILE_DISPOSITION_INFO_EX {
const CLASS: i32 = c::FileDispositionInfoEx;
}
unsafe impl SizedSetFileInformation for c::FILE_IO_PRIORITY_HINT_INFO {
const CLASS: i32 = c::FileIoPriorityHintInfo;
}

#[inline]
pub fn set_file_information_by_handle<T: SetFileInformation>(
handle: c::HANDLE,
info: &T,
) -> Result<(), WinError> {
unsafe fn set_info(
handle: c::HANDLE,
class: i32,
info: *const c_void,
size: u32,
) -> Result<(), WinError> {
let result = c::SetFileInformationByHandle(handle, class, info, size);
(result != 0).then_some(()).ok_or_else(|| get_last_error())
}
// SAFETY: The `SetFileInformation` trait ensures that this is safe.
unsafe { set_info(handle, T::CLASS, info.as_ptr(), info.size()) }
}

/// Gets the error from the last function.
/// This must be called immediately after the function that sets the error to
/// avoid the risk of another function overwriting it.
pub fn get_last_error() -> WinError {
// SAFETY: This just returns a thread-local u32 and has no other effects.
unsafe { WinError { code: c::GetLastError() } }
}

/// An error code as returned by [`get_last_error`].
///
/// This is usually a 16-bit Win32 error code but may be a 32-bit HRESULT or NTSTATUS.
/// Check the documentation of the Windows API function being called for expected errors.
#[derive(Clone, Copy, PartialEq, Eq)]
#[repr(transparent)]
pub struct WinError {
pub code: u32,
}
2 changes: 2 additions & 0 deletions library/std/src/sys/windows/c/windows_sys.lst
Original file line number Diff line number Diff line change
Expand Up @@ -2224,6 +2224,7 @@ Windows.Win32.Storage.FileSystem.FILE_ACCESS_RIGHTS
Windows.Win32.Storage.FileSystem.FILE_ADD_FILE
Windows.Win32.Storage.FileSystem.FILE_ADD_SUBDIRECTORY
Windows.Win32.Storage.FileSystem.FILE_ALL_ACCESS
Windows.Win32.Storage.FileSystem.FILE_ALLOCATION_INFO
Windows.Win32.Storage.FileSystem.FILE_APPEND_DATA
Windows.Win32.Storage.FileSystem.FILE_ATTRIBUTE_ARCHIVE
Windows.Win32.Storage.FileSystem.FILE_ATTRIBUTE_COMPRESSED
Expand Down Expand Up @@ -2284,6 +2285,7 @@ Windows.Win32.Storage.FileSystem.FILE_GENERIC_READ
Windows.Win32.Storage.FileSystem.FILE_GENERIC_WRITE
Windows.Win32.Storage.FileSystem.FILE_ID_BOTH_DIR_INFO
Windows.Win32.Storage.FileSystem.FILE_INFO_BY_HANDLE_CLASS
Windows.Win32.Storage.FileSystem.FILE_IO_PRIORITY_HINT_INFO
Windows.Win32.Storage.FileSystem.FILE_LIST_DIRECTORY
Windows.Win32.Storage.FileSystem.FILE_NAME_NORMALIZED
Windows.Win32.Storage.FileSystem.FILE_NAME_OPENED
Expand Down
21 changes: 21 additions & 0 deletions library/std/src/sys/windows/c/windows_sys.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3107,6 +3107,16 @@ impl ::core::clone::Clone for FILETIME {
pub type FILE_ACCESS_RIGHTS = u32;
pub const FILE_ADD_FILE: FILE_ACCESS_RIGHTS = 2u32;
pub const FILE_ADD_SUBDIRECTORY: FILE_ACCESS_RIGHTS = 4u32;
#[repr(C)]
pub struct FILE_ALLOCATION_INFO {
pub AllocationSize: i64,
}
impl ::core::marker::Copy for FILE_ALLOCATION_INFO {}
impl ::core::clone::Clone for FILE_ALLOCATION_INFO {
fn clone(&self) -> Self {
*self
}
}
pub const FILE_ALL_ACCESS: FILE_ACCESS_RIGHTS = 2032127u32;
pub const FILE_APPEND_DATA: FILE_ACCESS_RIGHTS = 4u32;
pub const FILE_ATTRIBUTE_ARCHIVE: FILE_FLAGS_AND_ATTRIBUTES = 32u32;
Expand Down Expand Up @@ -3248,6 +3258,16 @@ impl ::core::clone::Clone for FILE_ID_BOTH_DIR_INFO {
}
}
pub type FILE_INFO_BY_HANDLE_CLASS = i32;
#[repr(C)]
pub struct FILE_IO_PRIORITY_HINT_INFO {
pub PriorityHint: PRIORITY_HINT,
}
impl ::core::marker::Copy for FILE_IO_PRIORITY_HINT_INFO {}
impl ::core::clone::Clone for FILE_IO_PRIORITY_HINT_INFO {
fn clone(&self) -> Self {
*self
}
}
pub const FILE_LIST_DIRECTORY: FILE_ACCESS_RIGHTS = 1u32;
pub const FILE_NAME_NORMALIZED: GETFINALPATHNAMEBYHANDLE_FLAGS = 0u32;
pub const FILE_NAME_OPENED: GETFINALPATHNAMEBYHANDLE_FLAGS = 8u32;
Expand Down Expand Up @@ -3753,6 +3773,7 @@ pub const PIPE_SERVER_END: NAMED_PIPE_MODE = 1u32;
pub const PIPE_TYPE_BYTE: NAMED_PIPE_MODE = 0u32;
pub const PIPE_TYPE_MESSAGE: NAMED_PIPE_MODE = 4u32;
pub const PIPE_WAIT: NAMED_PIPE_MODE = 0u32;
pub type PRIORITY_HINT = i32;
pub type PROCESSOR_ARCHITECTURE = u16;
pub type PROCESS_CREATION_FLAGS = u32;
#[repr(C)]
Expand Down
56 changes: 10 additions & 46 deletions library/std/src/sys/windows/fs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ use crate::thread;
use core::ffi::c_void;

use super::path::maybe_verbatim;
use super::to_u16s;
use super::{api, to_u16s, IoResult};

pub struct File {
handle: Handle,
Expand Down Expand Up @@ -123,7 +123,7 @@ impl Iterator for ReadDir {
let mut wfd = mem::zeroed();
loop {
if c::FindNextFileW(self.handle.0, &mut wfd) == 0 {
if c::GetLastError() == c::ERROR_NO_MORE_FILES {
if api::get_last_error().code == c::ERROR_NO_MORE_FILES {
return None;
} else {
return Some(Err(Error::last_os_error()));
Expand Down Expand Up @@ -318,17 +318,8 @@ impl File {
}

pub fn truncate(&self, size: u64) -> io::Result<()> {
let mut info = c::FILE_END_OF_FILE_INFO { EndOfFile: size as c::LARGE_INTEGER };
let size = mem::size_of_val(&info);
cvt(unsafe {
c::SetFileInformationByHandle(
self.handle.as_raw_handle(),
c::FileEndOfFileInfo,
&mut info as *mut _ as *mut _,
size as c::DWORD,
)
})?;
Ok(())
let info = c::FILE_END_OF_FILE_INFO { EndOfFile: size as i64 };
api::set_file_information_by_handle(self.handle.as_raw_handle(), &info).io_result()
}

#[cfg(not(target_vendor = "uwp"))]
Expand Down Expand Up @@ -565,23 +556,14 @@ impl File {
}

pub fn set_permissions(&self, perm: FilePermissions) -> io::Result<()> {
let mut info = c::FILE_BASIC_INFO {
let info = c::FILE_BASIC_INFO {
CreationTime: 0,
LastAccessTime: 0,
LastWriteTime: 0,
ChangeTime: 0,
FileAttributes: perm.attrs,
};
let size = mem::size_of_val(&info);
cvt(unsafe {
c::SetFileInformationByHandle(
self.handle.as_raw_handle(),
c::FileBasicInfo,
&mut info as *mut _ as *mut _,
size as c::DWORD,
)
})?;
Ok(())
api::set_file_information_by_handle(self.handle.as_raw_handle(), &info).io_result()
}

pub fn set_times(&self, times: FileTimes) -> io::Result<()> {
Expand Down Expand Up @@ -641,38 +623,20 @@ impl File {
/// If the operation is not supported for this filesystem or OS version
/// then errors will be `ERROR_NOT_SUPPORTED` or `ERROR_INVALID_PARAMETER`.
fn posix_delete(&self) -> io::Result<()> {
let mut info = c::FILE_DISPOSITION_INFO_EX {
let info = c::FILE_DISPOSITION_INFO_EX {
Flags: c::FILE_DISPOSITION_FLAG_DELETE
| c::FILE_DISPOSITION_FLAG_POSIX_SEMANTICS
| c::FILE_DISPOSITION_FLAG_IGNORE_READONLY_ATTRIBUTE,
};
let size = mem::size_of_val(&info);
cvt(unsafe {
c::SetFileInformationByHandle(
self.handle.as_raw_handle(),
c::FileDispositionInfoEx,
&mut info as *mut _ as *mut _,
size as c::DWORD,
)
})?;
Ok(())
api::set_file_information_by_handle(self.handle.as_raw_handle(), &info).io_result()
}

/// Delete a file using win32 semantics. The file won't actually be deleted
/// until all file handles are closed. However, marking a file for deletion
/// will prevent anyone from opening a new handle to the file.
fn win32_delete(&self) -> io::Result<()> {
let mut info = c::FILE_DISPOSITION_INFO { DeleteFile: c::TRUE as _ };
let size = mem::size_of_val(&info);
cvt(unsafe {
c::SetFileInformationByHandle(
self.handle.as_raw_handle(),
c::FileDispositionInfo,
&mut info as *mut _ as *mut _,
size as c::DWORD,
)
})?;
Ok(())
let info = c::FILE_DISPOSITION_INFO { DeleteFile: c::TRUE as _ };
api::set_file_information_by_handle(self.handle.as_raw_handle(), &info).io_result()
}

/// Fill the given buffer with as many directory entries as will fit.
Expand Down
16 changes: 14 additions & 2 deletions library/std/src/sys/windows/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,18 @@ cfg_if::cfg_if! {
}
}

mod api;

/// Map a Result<T, WinError> to io::Result<T>.
trait IoResult<T> {
fn io_result(self) -> crate::io::Result<T>;
}
impl<T> IoResult<T> for Result<T, api::WinError> {
fn io_result(self) -> crate::io::Result<T> {
self.map_err(|e| crate::io::Error::from_raw_os_error(e.code as i32))
}
}

// SAFETY: must be called only once during runtime initialization.
// NOTE: this is not guaranteed to run, for example when Rust code is called externally.
pub unsafe fn init(_argc: isize, _argv: *const *const u8, _sigpipe: u8) {
Expand Down Expand Up @@ -241,11 +253,11 @@ where
// not an actual error.
c::SetLastError(0);
let k = match f1(buf.as_mut_ptr().cast::<u16>(), n as c::DWORD) {
0 if c::GetLastError() == 0 => 0,
0 if api::get_last_error().code == 0 => 0,
0 => return Err(crate::io::Error::last_os_error()),
n => n,
} as usize;
if k == n && c::GetLastError() == c::ERROR_INSUFFICIENT_BUFFER {
if k == n && api::get_last_error().code == c::ERROR_INSUFFICIENT_BUFFER {
n = n.saturating_mul(2).min(c::DWORD::MAX as usize);
} else if k > n {
n = k;
Expand Down
6 changes: 3 additions & 3 deletions library/std/src/sys/windows/os.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,10 @@ use crate::ptr;
use crate::slice;
use crate::sys::{c, cvt};

use super::to_u16s;
use super::{api, to_u16s};

pub fn errno() -> i32 {
unsafe { c::GetLastError() as i32 }
api::get_last_error().code as i32
}

/// Gets a detailed string description for the given error number.
Expand Down Expand Up @@ -336,7 +336,7 @@ fn home_dir_crt() -> Option<PathBuf> {
super::fill_utf16_buf(
|buf, mut sz| {
match c::GetUserProfileDirectoryW(token, buf, &mut sz) {
0 if c::GetLastError() != c::ERROR_INSUFFICIENT_BUFFER => 0,
0 if api::get_last_error().code != c::ERROR_INSUFFICIENT_BUFFER => 0,
0 => sz,
_ => sz - 1, // sz includes the null terminator
}
Expand Down
4 changes: 3 additions & 1 deletion library/std/src/sys/windows/stack_overflow.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,16 @@
use crate::sys::c;
use crate::thread;

use super::api;

pub struct Handler;

impl Handler {
pub unsafe fn new() -> Handler {
// This API isn't available on XP, so don't panic in that case and just
// pray it works out ok.
if c::SetThreadStackGuarantee(&mut 0x5000) == 0
&& c::GetLastError() as u32 != c::ERROR_CALL_NOT_IMPLEMENTED as u32
&& api::get_last_error().code != c::ERROR_CALL_NOT_IMPLEMENTED
{
panic!("failed to reserve stack space for exception handling");
}
Expand Down
Loading
Loading