auto merge of #13932 : MrAlert/rust/win-compat, r=brson

This addresses #12842 by offering fallback implementations for functions that aren't available.

In this case, as Windows XP simply doesn't support symbolic links at all, the fallbacks simply return an error code indicating that the function hasn't been implemented. This should allow programs written in Rust to run under XP while still offering full support for symbolic links under newer versions of Windows with the same binary, but due to LLVM using stderror_s(), which isn't available in msvcrt.dll in XP, rustc itself will not.

The fallback implementation is as follows:

Calling the function instead calls to a mutable function pointer. This in and of itself would not constitute a performance hit because DLL calls are implemented in a similar manner (see Import Address Table). The function pointer initially points to a thunk which tries to get the address of the associated function and write it back to the function pointer. If it fails to find the function, it instead writes the address to a fallback. As this operation is idempotent, reading and writing the pointer simply needs to be atomic. Subsequent calls to the function should be as fast as any other DLL call, as the pointer will then point directly to either the correct function or a fallback.
This commit is contained in:
bors 2014-05-12 09:12:04 -07:00
commit e8053b9a7f
4 changed files with 105 additions and 16 deletions

View File

@ -252,7 +252,6 @@ pub use funcs::bsd43::{shutdown};
#[cfg(windows)] pub use funcs::extra::kernel32::{FlushFileBuffers, SetEndOfFile, CreateFileW};
#[cfg(windows)] pub use funcs::extra::kernel32::{CreateDirectoryW, FindFirstFileW};
#[cfg(windows)] pub use funcs::extra::kernel32::{FindNextFileW, FindClose, DeleteFileW};
#[cfg(windows)] pub use funcs::extra::kernel32::{GetFinalPathNameByHandleW, CreateSymbolicLinkW};
#[cfg(windows)] pub use funcs::extra::kernel32::{CreateHardLinkW, CreateEventW};
#[cfg(windows)] pub use funcs::extra::kernel32::{FlushFileBuffers, CreateNamedPipeW};
#[cfg(windows)] pub use funcs::extra::kernel32::{SetNamedPipeHandleState, WaitNamedPipeW};
@ -1735,6 +1734,7 @@ pub mod consts {
pub static ERROR_INVALID_HANDLE : c_int = 6;
pub static ERROR_BROKEN_PIPE: c_int = 109;
pub static ERROR_DISK_FULL : c_int = 112;
pub static ERROR_CALL_NOT_IMPLEMENTED : c_int = 120;
pub static ERROR_INSUFFICIENT_BUFFER : c_int = 122;
pub static ERROR_INVALID_NAME : c_int = 123;
pub static ERROR_ALREADY_EXISTS : c_int = 183;
@ -4189,9 +4189,9 @@ pub mod funcs {
LPSTARTUPINFO,
LPPROCESS_INFORMATION,
LPMEMORY_BASIC_INFORMATION,
LPSYSTEM_INFO, BOOLEAN,
HANDLE, LPHANDLE, LARGE_INTEGER,
PLARGE_INTEGER, LPFILETIME};
LPSYSTEM_INFO, HANDLE, LPHANDLE,
LARGE_INTEGER, PLARGE_INTEGER,
LPFILETIME};
extern "system" {
pub fn GetEnvironmentVariableW(n: LPCWSTR,
@ -4301,9 +4301,6 @@ pub mod funcs {
pub fn MoveFileExW(lpExistingFileName: LPCWSTR,
lpNewFileName: LPCWSTR,
dwFlags: DWORD) -> BOOL;
pub fn CreateSymbolicLinkW(lpSymlinkFileName: LPCWSTR,
lpTargetFileName: LPCWSTR,
dwFlags: DWORD) -> BOOLEAN;
pub fn CreateHardLinkW(lpSymlinkFileName: LPCWSTR,
lpTargetFileName: LPCWSTR,
lpSecurityAttributes: LPSECURITY_ATTRIBUTES)
@ -4316,10 +4313,6 @@ pub mod funcs {
dwCreationDisposition: DWORD,
dwFlagsAndAttributes: DWORD,
hTemplateFile: HANDLE) -> HANDLE;
pub fn GetFinalPathNameByHandleW(hFile: HANDLE,
lpszFilePath: LPCWSTR,
cchFilePath: DWORD,
dwFlags: DWORD) -> DWORD;
pub fn ReadFile(hFile: HANDLE,
lpBuffer: LPVOID,
nNumberOfBytesToRead: DWORD,

View File

@ -65,3 +65,96 @@ extern "system" {
pub fn CancelIoEx(hFile: libc::HANDLE,
lpOverlapped: libc::LPOVERLAPPED) -> libc::BOOL;
}
pub mod compat {
use std::intrinsics::{atomic_store_relaxed, transmute};
use libc::types::os::arch::extra::{LPCWSTR, HMODULE, LPCSTR, LPVOID};
use std::os::win32::as_utf16_p;
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<T: Copy>(ptr: *mut T, module: &str, symbol: &str, fallback: T) {
as_utf16_p(module, |module| {
symbol.with_c_str(|symbol| {
let handle = GetModuleHandleW(module);
let func: Option<T> = transmute(GetProcAddress(handle, symbol));
atomic_store_relaxed(ptr, func.unwrap_or(fallback))
})
})
}
/// 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,
stringify!($module),
stringify!($symbol),
fallback);
::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
})
}
}

View File

@ -422,6 +422,7 @@ pub fn chown(_p: &CString, _uid: int, _gid: int) -> IoResult<()> {
pub fn readlink(p: &CString) -> IoResult<Path> {
// FIXME: I have a feeling that this reads intermediate symlinks as well.
use io::c::compat::kernel32::GetFinalPathNameByHandleW;
let handle = unsafe {
as_utf16_p(p.as_str().unwrap(), |p| {
libc::CreateFileW(p,
@ -439,10 +440,10 @@ pub fn readlink(p: &CString) -> IoResult<Path> {
// 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 {
libc::GetFinalPathNameByHandleW(handle,
buf as *u16,
sz - 1,
libc::VOLUME_NAME_DOS)
GetFinalPathNameByHandleW(handle,
buf as *u16,
sz - 1,
libc::VOLUME_NAME_DOS)
});
let ret = match ret {
Some(ref s) if s.starts_with(r"\\?\") => Ok(Path::new(s.slice_from(4))),
@ -454,9 +455,10 @@ pub fn readlink(p: &CString) -> IoResult<Path> {
}
pub fn symlink(src: &CString, dst: &CString) -> IoResult<()> {
use io::c::compat::kernel32::CreateSymbolicLinkW;
super::mkerr_winbool(as_utf16_p(src.as_str().unwrap(), |src| {
as_utf16_p(dst.as_str().unwrap(), |dst| {
unsafe { libc::CreateSymbolicLinkW(dst, src, 0) }
unsafe { CreateSymbolicLinkW(dst, src, 0) }
}) as libc::BOOL
}))
}

View File

@ -50,6 +50,7 @@
html_root_url = "http://static.rust-lang.org/doc/master")]
#![deny(unused_result, unused_must_use)]
#![allow(non_camel_case_types)]
#![feature(macro_rules)]
// NB this crate explicitly does *not* allow glob imports, please seriously
// consider whether they're needed before adding that feature here (the