use std::ffi::{c_void, OsStr};
use std::os::windows::ffi::OsStrExt;
use std::path::{Path, PathBuf};
use std::sync::atomic::{AtomicPtr, Ordering};
use anyhow::{Context, Result};
use super::{acl, locks};
use crate::enforce::Plan;
pub const AVAILABLE: bool = true;
const INFINITE: u32 = 0xFFFF_FFFF;
const EVENT_MODIFY_STATE: u32 = 0x0002;
const SYNCHRONIZE: u32 = 0x0010_0000;
const ERROR_ALREADY_EXISTS: u32 = 183;
const TRUE: i32 = 1;
const FALSE: i32 = 0;
const DETACHED_PROCESS: u32 = 0x0000_0008;
const CREATE_NEW_PROCESS_GROUP: u32 = 0x0000_0200;
type Handle = *mut c_void;
#[repr(C)]
struct StartupInfoW {
cb: u32,
reserved: *mut u16,
desktop: *mut u16,
title: *mut u16,
x: u32,
y: u32,
x_size: u32,
y_size: u32,
x_count_chars: u32,
y_count_chars: u32,
fill_attribute: u32,
flags: u32,
show_window: u16,
reserved2_length: u16,
reserved2: *mut u8,
std_input: Handle,
std_output: Handle,
std_error: Handle,
}
#[repr(C)]
struct ProcessInformation {
process: Handle,
thread: Handle,
process_id: u32,
thread_id: u32,
}
#[link(name = "kernel32")]
extern "system" {
fn CreateEventW(
attributes: *mut c_void,
manual_reset: i32,
initial_state: i32,
name: *const u16,
) -> Handle;
fn OpenEventW(desired_access: u32, inherit_handle: i32, name: *const u16) -> Handle;
fn SetEvent(event: Handle) -> i32;
fn WaitForSingleObject(handle: Handle, milliseconds: u32) -> u32;
fn CloseHandle(handle: Handle) -> i32;
fn GetLastError() -> u32;
fn SetConsoleCtrlHandler(
handler: Option<unsafe extern "system" fn(u32) -> i32>,
add: i32,
) -> i32;
#[allow(clippy::too_many_arguments)]
fn CreateProcessW(
application_name: *const u16,
command_line: *mut u16,
process_attributes: *mut c_void,
thread_attributes: *mut c_void,
inherit_handles: i32,
creation_flags: u32,
environment: *mut c_void,
current_directory: *const u16,
startup_information: *const StartupInfoW,
process_information: *mut ProcessInformation,
) -> i32;
fn CreateFileW(
file_name: *const u16,
desired_access: u32,
share_mode: u32,
security_attributes: *mut c_void,
creation_disposition: u32,
flags_and_attributes: u32,
template: *mut c_void,
) -> Handle;
fn SetStdHandle(std_handle: u32, handle: Handle) -> i32;
}
static PARKED_ON: AtomicPtr<c_void> = AtomicPtr::new(std::ptr::null_mut());
unsafe extern "system" fn on_console_signal(_kind: u32) -> i32 {
let event = PARKED_ON.load(Ordering::SeqCst);
if event.is_null() {
return FALSE;
}
unsafe { SetEvent(event) };
TRUE
}
pub struct Session {
event: Handle,
locks: locks::Locks,
narrowing: acl::Narrowing,
pub warnings: Vec<String>,
}
impl Session {
pub fn files(&self) -> usize {
self.locks.files
}
pub fn directories(&self) -> usize {
self.locks.directories
}
pub fn refused_directories(&self) -> usize {
self.narrowing.directories()
}
pub fn park(self) -> Result<()> {
PARKED_ON.store(self.event, Ordering::SeqCst);
unsafe { SetConsoleCtrlHandler(Some(on_console_signal), TRUE) };
unsafe { WaitForSingleObject(self.event, INFINITE) };
PARKED_ON.store(std::ptr::null_mut(), Ordering::SeqCst);
Ok(())
}
}
impl Drop for Session {
fn drop(&mut self) {
unsafe { CloseHandle(self.event) };
}
}
pub fn start(root: &Path, plan: &Plan) -> Result<Session> {
let name = event_name(root);
let event = unsafe { CreateEventW(std::ptr::null_mut(), TRUE, FALSE, name.as_ptr()) };
if event.is_null() {
anyhow::bail!("could not claim this project (Windows error {})", unsafe {
GetLastError()
});
}
if unsafe { GetLastError() } == ERROR_ALREADY_EXISTS {
unsafe { CloseHandle(event) };
anyhow::bail!("a guard is already protecting this project — `ralon guard --stop` first");
}
let locks = match locks::acquire(&plan.pinned, &plan.protected) {
Ok(locks) => locks,
Err(error) => {
unsafe { CloseHandle(event) };
return Err(error);
}
};
let (narrowing, warnings) = acl::refuse_new_entries(&directories(&plan.protected));
Ok(Session {
event,
locks,
narrowing,
warnings,
})
}
pub fn stop(root: &Path) -> Result<bool> {
let name = event_name(root);
let event = unsafe { OpenEventW(EVENT_MODIFY_STATE, FALSE, name.as_ptr()) };
if event.is_null() {
return Ok(false);
}
let signalled = unsafe { SetEvent(event) } != 0;
unsafe { CloseHandle(event) };
if !signalled {
anyhow::bail!("found a guard but could not ask it to stop");
}
for _ in 0..100 {
if !running(root) {
break;
}
std::thread::sleep(std::time::Duration::from_millis(20));
}
Ok(true)
}
pub fn running(root: &Path) -> bool {
let name = event_name(root);
let event = unsafe { OpenEventW(SYNCHRONIZE, FALSE, name.as_ptr()) };
if event.is_null() {
return false;
}
unsafe { CloseHandle(event) };
true
}
pub fn detach(root: &Path) -> Result<()> {
let executable = std::env::current_exe().context("could not find the ralon executable")?;
let mut command_line = wide(format!(
"\"{}\" --dir \"{}\" guard --detached",
executable.display(),
root.display()
));
let mut startup: StartupInfoW = unsafe { std::mem::zeroed() };
startup.cb = std::mem::size_of::<StartupInfoW>() as u32;
let mut information: ProcessInformation = unsafe { std::mem::zeroed() };
let started = unsafe {
CreateProcessW(
std::ptr::null(),
command_line.as_mut_ptr(),
std::ptr::null_mut(),
std::ptr::null_mut(),
FALSE, DETACHED_PROCESS | CREATE_NEW_PROCESS_GROUP,
std::ptr::null_mut(),
std::ptr::null(),
&startup,
&mut information,
)
};
if started == 0 {
anyhow::bail!(
"could not start a background guard (Windows error {})",
unsafe { GetLastError() }
);
}
unsafe {
CloseHandle(information.thread);
CloseHandle(information.process);
}
for _ in 0..60 {
if running(root) {
return Ok(());
}
std::thread::sleep(std::time::Duration::from_millis(50));
}
anyhow::bail!(
"a background guard was started but never claimed the project — \
run `ralon guard` in this terminal to see why"
)
}
pub fn silence_standard_handles() {
const GENERIC_WRITE: u32 = 0x4000_0000;
const FILE_SHARE_WRITE: u32 = 0x0000_0002;
const OPEN_EXISTING: u32 = 3;
const STD_INPUT: u32 = -10i32 as u32;
const STD_OUTPUT: u32 = -11i32 as u32;
const STD_ERROR: u32 = -12i32 as u32;
let null = unsafe {
CreateFileW(
wide("NUL").as_ptr(),
GENERIC_WRITE,
FILE_SHARE_WRITE,
std::ptr::null_mut(),
OPEN_EXISTING,
0,
std::ptr::null_mut(),
)
};
if null.is_null() || null == (-1isize as Handle) {
return;
}
for handle in [STD_INPUT, STD_OUTPUT, STD_ERROR] {
unsafe { SetStdHandle(handle, null) };
}
}
fn directories(protected: &[PathBuf]) -> Vec<PathBuf> {
protected
.iter()
.filter(|path| path.is_dir())
.cloned()
.collect()
}
pub fn leftovers(protected: &[PathBuf]) -> Vec<PathBuf> {
acl::leftovers(&directories(protected))
}
pub fn clear_leftovers(protected: &[PathBuf]) -> Vec<PathBuf> {
acl::clear(&directories(protected))
}
fn event_name(root: &Path) -> Vec<u16> {
let mut hash: u64 = 0xcbf2_9ce4_8422_2325;
for byte in root.to_string_lossy().to_lowercase().bytes() {
hash ^= u64::from(byte);
hash = hash.wrapping_mul(0x1000_0000_01b3);
}
wide(format!("Local\\ralon-guard-{hash:016x}"))
}
fn wide(text: impl AsRef<OsStr>) -> Vec<u16> {
text.as_ref()
.encode_wide()
.chain(std::iter::once(0))
.collect()
}