use std::io;
use std::iter;
use std::mem::{forget, size_of, zeroed};
use std::os::windows::io::{AsRawHandle, FromRawHandle, OwnedHandle};
use std::ptr;
use std::sync::atomic::{AtomicU64, Ordering};
use windows_sys::Win32::Foundation::{
ERROR_ACCESS_DENIED, ERROR_IO_PENDING, ERROR_PIPE_BUSY, ERROR_PIPE_CONNECTED, GENERIC_READ,
GENERIC_WRITE, INVALID_HANDLE_VALUE, WAIT_FAILED, WAIT_OBJECT_0, WAIT_TIMEOUT,
};
use windows_sys::Win32::Security::SECURITY_ATTRIBUTES;
use windows_sys::Win32::Storage::FileSystem::{
CreateFileW, FILE_FLAG_FIRST_PIPE_INSTANCE, FILE_FLAG_OVERLAPPED, OPEN_EXISTING,
PIPE_ACCESS_INBOUND, PIPE_ACCESS_OUTBOUND,
};
use windows_sys::Win32::System::Pipes::{
ConnectNamedPipe, CreateNamedPipeW, PIPE_READMODE_BYTE, PIPE_REJECT_REMOTE_CLIENTS,
PIPE_TYPE_BYTE, PIPE_WAIT,
};
use windows_sys::Win32::System::SystemInformation::GetTickCount64;
use windows_sys::Win32::System::Threading::{CreateEventW, WaitForSingleObject};
use windows_sys::Win32::System::IO::{CancelIo, GetOverlappedResult, OVERLAPPED};
const PIPE_BUFFER_SIZE: u32 = 128 * 1024;
const PIPE_NAME_ATTEMPTS: u32 = 4;
const CONNECT_TIMEOUT_MS: u32 = 10_000;
const CANCEL_DRAIN_TIMEOUT_MS: u32 = 5_000;
#[derive(Debug)]
pub(crate) struct OverlappedPipes {
pub(crate) conout_server: OwnedHandle,
pub(crate) conout_client: OwnedHandle,
pub(crate) conin_server: OwnedHandle,
pub(crate) conin_client: OwnedHandle,
}
pub(crate) fn create_overlapped_pipes() -> io::Result<OverlappedPipes> {
let (conout_server, conout_client) = create_connected_pair(ServerDirection::Inbound)?;
let (conin_server, conin_client) = create_connected_pair(ServerDirection::Outbound)?;
Ok(OverlappedPipes {
conout_server,
conout_client,
conin_server,
conin_client,
})
}
#[derive(Clone, Copy)]
enum ServerDirection {
Inbound,
Outbound,
}
impl ServerDirection {
const fn server_open_mode(self) -> u32 {
let access = match self {
Self::Inbound => PIPE_ACCESS_INBOUND,
Self::Outbound => PIPE_ACCESS_OUTBOUND,
};
access | FILE_FLAG_OVERLAPPED | FILE_FLAG_FIRST_PIPE_INSTANCE
}
const fn client_desired_access(self) -> u32 {
match self {
Self::Inbound => GENERIC_WRITE,
Self::Outbound => GENERIC_READ,
}
}
}
fn create_connected_pair(direction: ServerDirection) -> io::Result<(OwnedHandle, OwnedHandle)> {
let (name, server) = retry_name_collisions(|| {
let name = unique_pipe_name();
create_pipe_server(&name, direction).map(|server| (name, server))
})?;
let client = open_pipe_client(&name, direction)?;
confirm_client_connected(&server)?;
Ok((server, client))
}
fn retry_name_collisions<T>(mut operation: impl FnMut() -> io::Result<T>) -> io::Result<T> {
let mut attempts = 0;
loop {
attempts = next_attempt(attempts);
match operation() {
Ok(value) => return Ok(value),
Err(err) if !should_retry_name_collision(attempts, &err) => return Err(err),
Err(_) => {},
}
}
}
const fn next_attempt(attempts: u32) -> u32 {
attempts + 1
}
fn should_retry_name_collision(attempts: u32, err: &io::Error) -> bool {
attempts < PIPE_NAME_ATTEMPTS && is_pipe_name_collision(err)
}
static NEXT_PIPE_SEQ: AtomicU64 = AtomicU64::new(0);
fn unique_pipe_name() -> String {
let pid = std::process::id();
let seq = NEXT_PIPE_SEQ.fetch_add(1, Ordering::Relaxed);
let tick = unsafe { GetTickCount64() };
format!(r"\\.\pipe\conpty-oxide-{pid}-{seq}-{tick}")
}
fn to_wide_null(s: &str) -> Vec<u16> {
s.encode_utf16().chain(iter::once(0)).collect()
}
fn non_inheritable_attributes() -> SECURITY_ATTRIBUTES {
SECURITY_ATTRIBUTES {
nLength: u32::try_from(size_of::<SECURITY_ATTRIBUTES>()).unwrap_or(u32::MAX),
lpSecurityDescriptor: ptr::null_mut(),
bInheritHandle: 0, }
}
const fn pipe_mode() -> u32 {
PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT | PIPE_REJECT_REMOTE_CLIENTS
}
fn create_pipe_server(name: &str, direction: ServerDirection) -> io::Result<OwnedHandle> {
let wide_name = to_wide_null(name);
let attributes = non_inheritable_attributes();
let handle = unsafe {
CreateNamedPipeW(
wide_name.as_ptr(),
direction.server_open_mode(),
pipe_mode(),
1, PIPE_BUFFER_SIZE,
PIPE_BUFFER_SIZE,
0,
&attributes,
)
};
if handle == INVALID_HANDLE_VALUE {
return Err(io::Error::last_os_error());
}
Ok(unsafe { OwnedHandle::from_raw_handle(handle) })
}
fn is_pipe_name_collision(err: &io::Error) -> bool {
matches!(
err.raw_os_error().and_then(|code| u32::try_from(code).ok()),
Some(ERROR_ACCESS_DENIED | ERROR_PIPE_BUSY)
)
}
fn open_pipe_client(name: &str, direction: ServerDirection) -> io::Result<OwnedHandle> {
let wide_name = to_wide_null(name);
let attributes = non_inheritable_attributes();
let handle = unsafe {
CreateFileW(
wide_name.as_ptr(),
direction.client_desired_access(),
0, &attributes,
OPEN_EXISTING,
0, ptr::null_mut(),
)
};
if handle == INVALID_HANDLE_VALUE {
return Err(io::Error::last_os_error());
}
Ok(unsafe { OwnedHandle::from_raw_handle(handle) })
}
fn create_manual_reset_event() -> io::Result<OwnedHandle> {
let handle = unsafe { CreateEventW(ptr::null(), 1, 0, ptr::null()) };
if handle.is_null() {
return Err(io::Error::last_os_error());
}
Ok(unsafe { OwnedHandle::from_raw_handle(handle) })
}
fn overlapped_with(event: &OwnedHandle) -> OVERLAPPED {
let mut overlapped: OVERLAPPED = unsafe { zeroed() };
overlapped.hEvent = event.as_raw_handle();
overlapped
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
enum ConnectDisposition {
Complete,
Pending,
Error,
}
const fn classify_connect_result(connected: i32, error: Option<u32>) -> ConnectDisposition {
if connected != 0 || matches!(error, Some(ERROR_PIPE_CONNECTED)) {
ConnectDisposition::Complete
} else if matches!(error, Some(ERROR_IO_PENDING)) {
ConnectDisposition::Pending
} else {
ConnectDisposition::Error
}
}
fn confirm_client_connected(server: &OwnedHandle) -> io::Result<()> {
let event = create_manual_reset_event()?;
let mut overlapped = Box::new(overlapped_with(&event));
let connected = unsafe { ConnectNamedPipe(server.as_raw_handle(), &mut *overlapped) };
let err = io::Error::last_os_error();
let error = err.raw_os_error().and_then(|code| u32::try_from(code).ok());
match classify_connect_result(connected, error) {
ConnectDisposition::Complete => Ok(()),
ConnectDisposition::Pending => await_pending_connect(server, overlapped, event),
ConnectDisposition::Error => Err(err),
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
enum PendingWaitDisposition {
Completed,
TimedOut,
Failed,
Unexpected(u32),
}
const fn classify_pending_wait(wait: u32) -> PendingWaitDisposition {
match wait {
WAIT_OBJECT_0 => PendingWaitDisposition::Completed,
WAIT_TIMEOUT => PendingWaitDisposition::TimedOut,
WAIT_FAILED => PendingWaitDisposition::Failed,
other => PendingWaitDisposition::Unexpected(other),
}
}
const fn overlapped_result_succeeded(ok: i32) -> bool {
ok != 0
}
const fn cancellation_is_still_pending(wait: u32) -> bool {
wait != WAIT_OBJECT_0
}
fn await_pending_connect(
server: &OwnedHandle,
overlapped: Box<OVERLAPPED>,
event: OwnedHandle,
) -> io::Result<()> {
let wait = unsafe { WaitForSingleObject(event.as_raw_handle(), CONNECT_TIMEOUT_MS) };
let disposition = classify_pending_wait(wait);
let primary = match disposition {
PendingWaitDisposition::Completed => {
let mut transferred = 0u32;
let ok = unsafe {
GetOverlappedResult(server.as_raw_handle(), &*overlapped, &mut transferred, 0)
};
if overlapped_result_succeeded(ok) {
return Ok(());
}
return Err(io::Error::last_os_error());
},
PendingWaitDisposition::TimedOut => io::Error::new(
io::ErrorKind::TimedOut,
"the named pipe client did not finish connecting in time",
),
PendingWaitDisposition::Failed => io::Error::last_os_error(),
PendingWaitDisposition::Unexpected(other) => io::Error::other(format!(
"unexpected wait result {other:#x} while waiting for the pipe client to connect"
)),
};
unsafe { CancelIo(server.as_raw_handle()) };
let drained = unsafe { WaitForSingleObject(event.as_raw_handle(), CANCEL_DRAIN_TIMEOUT_MS) };
if cancellation_is_still_pending(drained) {
Box::leak(overlapped);
forget(event);
}
Err(primary)
}
#[cfg(test)]
#[path = "overlapped_tests.rs"]
mod tests;