use std::io;
use std::iter;
use std::mem;
use std::os::windows::ffi::OsStrExt;
use std::path::Path;
use std::ptr;
use windows_sys::core::{BOOL, HRESULT};
#[cfg(any(feature = "blocking", feature = "tokio", test))]
use windows_sys::Win32::Foundation::HANDLE;
use windows_sys::Win32::Foundation::{FreeLibrary, HMODULE};
#[cfg(any(feature = "blocking", feature = "tokio", test))]
use windows_sys::Win32::System::Console::COORD;
use windows_sys::Win32::System::Console::HPCON;
use windows_sys::Win32::System::LibraryLoader::{
GetProcAddress, LoadLibraryExW, LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR, LOAD_LIBRARY_SEARCH_SYSTEM32,
};
const CONPTY_EXPORT_PREFIX: &str = "Conpty";
pub(super) const CREATE_PSEUDO_CONSOLE: &str = "CreatePseudoConsole";
const RESIZE_PSEUDO_CONSOLE: &str = "ResizePseudoConsole";
const CLOSE_PSEUDO_CONSOLE: &str = "ClosePseudoConsole";
const RELEASE_PSEUDO_CONSOLE: &str = "ReleasePseudoConsole";
const CLEAR_PSEUDO_CONSOLE: &str = "ClearPseudoConsole";
pub(super) const fn restricted_search_flags() -> u32 {
LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR | LOAD_LIBRARY_SEARCH_SYSTEM32
}
const CLEAR_ABI_IS_CALLABLE: bool = !cfg!(target_arch = "x86");
#[cfg(any(feature = "blocking", feature = "tokio", test))]
type CreatePseudoConsoleFn =
unsafe extern "system" fn(COORD, HANDLE, HANDLE, u32, *mut HPCON) -> HRESULT;
#[cfg(any(feature = "blocking", feature = "tokio", test))]
type ResizePseudoConsoleFn = unsafe extern "system" fn(HPCON, COORD) -> HRESULT;
#[cfg(any(feature = "blocking", feature = "tokio", test))]
type ClosePseudoConsoleFn = unsafe extern "system" fn(HPCON);
type ReleasePseudoConsoleFn = unsafe extern "system" fn(HPCON) -> HRESULT;
type ClearPseudoConsoleFn = unsafe extern "system" fn(HPCON, BOOL) -> HRESULT;
pub(super) type ProcAddress = unsafe extern "system" fn() -> isize;
pub(super) unsafe fn resolve_export(module: HMODULE, name: &str) -> Option<ProcAddress> {
fn c_name(prefix: &str, name: &str) -> Vec<u8> {
prefix
.bytes()
.chain(name.bytes())
.chain(iter::once(0))
.collect()
}
let prefixed = c_name(CONPTY_EXPORT_PREFIX, name);
let address = unsafe { GetProcAddress(module, prefixed.as_ptr()) };
if address.is_some() {
return address;
}
let bare = c_name("", name);
unsafe { GetProcAddress(module, bare.as_ptr()) }
}
#[derive(Debug)]
pub(super) struct ModuleGuard {
pub(super) module: HMODULE,
}
unsafe impl Send for ModuleGuard {}
unsafe impl Sync for ModuleGuard {}
impl Drop for ModuleGuard {
fn drop(&mut self) {
let _ = unsafe { FreeLibrary(self.module) };
}
}
#[derive(Debug)]
pub(super) struct ConptyApi {
#[cfg(any(feature = "blocking", feature = "tokio", test))]
pub(super) create: CreatePseudoConsoleFn,
#[cfg(any(feature = "blocking", feature = "tokio", test))]
pub(super) resize: ResizePseudoConsoleFn,
#[cfg(any(feature = "blocking", feature = "tokio", test))]
pub(super) close: ClosePseudoConsoleFn,
pub(super) release: Option<ReleasePseudoConsoleFn>,
pub(super) clear: Option<ClearPseudoConsoleFn>,
}
impl ConptyApi {
pub(super) unsafe fn from_module(module: HMODULE) -> Result<Self, &'static str> {
#[cfg(any(feature = "blocking", feature = "tokio", test))]
let create = {
let create = unsafe { resolve_export(module, CREATE_PSEUDO_CONSOLE) }
.ok_or(CREATE_PSEUDO_CONSOLE)?;
unsafe { mem::transmute::<ProcAddress, CreatePseudoConsoleFn>(create) }
};
#[cfg(not(any(feature = "blocking", feature = "tokio", test)))]
if unsafe { resolve_export(module, CREATE_PSEUDO_CONSOLE) }.is_none() {
return Err(CREATE_PSEUDO_CONSOLE);
}
#[cfg(any(feature = "blocking", feature = "tokio", test))]
let resize = {
let resize = unsafe { resolve_export(module, RESIZE_PSEUDO_CONSOLE) }
.ok_or(RESIZE_PSEUDO_CONSOLE)?;
unsafe { mem::transmute::<ProcAddress, ResizePseudoConsoleFn>(resize) }
};
#[cfg(not(any(feature = "blocking", feature = "tokio", test)))]
if unsafe { resolve_export(module, RESIZE_PSEUDO_CONSOLE) }.is_none() {
return Err(RESIZE_PSEUDO_CONSOLE);
}
#[cfg(any(feature = "blocking", feature = "tokio", test))]
let close = {
let close = unsafe { resolve_export(module, CLOSE_PSEUDO_CONSOLE) }
.ok_or(CLOSE_PSEUDO_CONSOLE)?;
unsafe { mem::transmute::<ProcAddress, ClosePseudoConsoleFn>(close) }
};
#[cfg(not(any(feature = "blocking", feature = "tokio", test)))]
if unsafe { resolve_export(module, CLOSE_PSEUDO_CONSOLE) }.is_none() {
return Err(CLOSE_PSEUDO_CONSOLE);
}
let release = unsafe { resolve_export(module, RELEASE_PSEUDO_CONSOLE) };
let release = release.map(|release| {
unsafe { mem::transmute::<ProcAddress, ReleasePseudoConsoleFn>(release) }
});
let clear = if CLEAR_ABI_IS_CALLABLE {
let clear = unsafe { resolve_export(module, CLEAR_PSEUDO_CONSOLE) };
clear.map(|clear| {
unsafe { mem::transmute::<ProcAddress, ClearPseudoConsoleFn>(clear) }
})
} else {
None
};
Ok(Self {
#[cfg(any(feature = "blocking", feature = "tokio", test))]
create,
#[cfg(any(feature = "blocking", feature = "tokio", test))]
resize,
#[cfg(any(feature = "blocking", feature = "tokio", test))]
close,
release,
clear,
})
}
#[cfg(test)]
pub(super) fn without_release(&self) -> Self {
Self {
create: self.create,
resize: self.resize,
close: self.close,
release: None,
clear: self.clear,
}
}
#[cfg(test)]
pub(super) fn with_close(&self, close: unsafe extern "system" fn(HPCON)) -> Self {
Self {
create: self.create,
resize: self.resize,
close,
release: self.release,
clear: self.clear,
}
}
}
pub(super) fn load_module(dll: &Path) -> io::Result<ModuleGuard> {
let path = wide_path(dll)?;
let module =
unsafe { LoadLibraryExW(path.as_ptr(), ptr::null_mut(), restricted_search_flags()) };
if module.is_null() {
return Err(io::Error::last_os_error());
}
Ok(ModuleGuard { module })
}
pub(super) fn wide_path(path: &Path) -> io::Result<Vec<u16>> {
let mut wide: Vec<u16> = path.as_os_str().encode_wide().collect();
if wide.contains(&0) {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"path contains an interior NUL",
));
}
wide.push(0);
Ok(wide)
}