use crate::suite::{valid_service_name, Suite};
use std::io;
use std::time::Duration;
fn invalid_name(name: &str) -> io::Error {
io::Error::new(
io::ErrorKind::InvalidInput,
format!("{name:?} is not a valid wake name"),
)
}
fn hash_hex(text: &str) -> String {
let mut h: u64 = 0xcbf2_9ce4_8422_2325;
for b in text.bytes() {
h ^= u64::from(b);
h = h.wrapping_mul(0x0000_0100_0000_01b3);
}
format!("{h:016x}")
}
pub fn post(suite: &Suite, name: &str) -> io::Result<usize> {
if !valid_service_name(name) {
return Err(invalid_name(name));
}
imp::post(suite, name)
}
pub fn listen(suite: &Suite, name: &str) -> io::Result<WakeListener> {
if !valid_service_name(name) {
return Err(invalid_name(name));
}
imp::listen(suite, name).map(|inner| WakeListener { inner })
}
pub struct WakeListener {
inner: imp::Inner,
}
impl WakeListener {
pub fn wait(&self, timeout: Duration) -> io::Result<bool> {
self.inner.wait(timeout)
}
}
#[cfg(unix)]
mod imp {
use super::*;
use std::os::unix::fs::{DirBuilderExt, MetadataExt, PermissionsExt};
use std::os::unix::net::UnixDatagram;
use std::path::{Path, PathBuf};
use std::sync::atomic::{AtomicU64, Ordering};
static NEXT: AtomicU64 = AtomicU64::new(1);
fn dir_for(suite: &Suite, name: &str) -> PathBuf {
let key = format!("{}|{name}", suite.root().display());
std::env::temp_dir().join(format!(
"rkwake-{}-{}",
crate::transport::current_uid(),
hash_hex(&key)
))
}
fn ensure_dir(dir: &Path) -> io::Result<()> {
match std::fs::DirBuilder::new().mode(0o700).create(dir) {
Ok(()) => {}
Err(e) if e.kind() == io::ErrorKind::AlreadyExists => {}
Err(e) => return Err(e),
}
let md = std::fs::symlink_metadata(dir)?;
if !md.is_dir()
|| md.uid() != crate::transport::current_uid()
|| md.permissions().mode() & 0o022 != 0
{
return Err(io::Error::new(
io::ErrorKind::PermissionDenied,
"the wake directory is not a private directory of this user",
));
}
Ok(())
}
pub(super) struct Inner {
sock: UnixDatagram,
path: PathBuf,
}
pub(super) fn listen(suite: &Suite, name: &str) -> io::Result<Inner> {
let dir = dir_for(suite, name);
ensure_dir(&dir)?;
let path = dir.join(format!(
"{}-{}.sock",
std::process::id(),
NEXT.fetch_add(1, Ordering::SeqCst)
));
let _ = std::fs::remove_file(&path);
let sock = UnixDatagram::bind(&path)?;
std::fs::set_permissions(&path, std::fs::Permissions::from_mode(0o600))?;
Ok(Inner { sock, path })
}
impl Inner {
pub(super) fn wait(&self, timeout: Duration) -> io::Result<bool> {
self.sock
.set_read_timeout(Some(timeout.max(Duration::from_millis(1))))?;
let mut buf = [0u8; 16];
match self.sock.recv(&mut buf) {
Ok(_) => {}
Err(e)
if matches!(
e.kind(),
io::ErrorKind::WouldBlock | io::ErrorKind::TimedOut
) =>
{
return Ok(false)
}
Err(e) => return Err(e),
}
self.sock.set_nonblocking(true)?;
while self.sock.recv(&mut buf).is_ok() {}
self.sock.set_nonblocking(false)?;
Ok(true)
}
}
impl Drop for Inner {
fn drop(&mut self) {
let _ = std::fs::remove_file(&self.path);
}
}
pub(super) fn post(suite: &Suite, name: &str) -> io::Result<usize> {
let dir = dir_for(suite, name);
let entries = match std::fs::read_dir(&dir) {
Ok(entries) => entries,
Err(e) if e.kind() == io::ErrorKind::NotFound => return Ok(0),
Err(e) => return Err(e),
};
ensure_dir(&dir)?;
let sender = UnixDatagram::unbound()?;
sender.set_nonblocking(true)?;
let mut woken = 0;
for entry in entries.flatten() {
let path = entry.path();
if path.extension().and_then(|e| e.to_str()) != Some("sock") {
continue;
}
match sender.send_to(&[1], &path) {
Ok(_) => woken += 1,
Err(e) if e.kind() == io::ErrorKind::WouldBlock => woken += 1,
Err(e)
if matches!(
e.kind(),
io::ErrorKind::ConnectionRefused | io::ErrorKind::NotFound
) =>
{
let _ = std::fs::remove_file(&path);
}
Err(_) => {}
}
}
Ok(woken)
}
}
#[cfg(windows)]
mod imp {
use super::*;
use std::path::PathBuf;
use std::sync::atomic::{AtomicU64, Ordering};
use windows::core::PCWSTR;
use windows::Win32::Foundation::{CloseHandle, HANDLE, WAIT_OBJECT_0};
use windows::Win32::System::Threading::{
CreateEventW, OpenEventW, SetEvent, WaitForSingleObject, EVENT_MODIFY_STATE,
};
static NEXT: AtomicU64 = AtomicU64::new(1);
fn dir_for(suite: &Suite, name: &str) -> PathBuf {
suite.service_dir(&format!("wake.{name}"))
}
fn wide(s: &str) -> Vec<u16> {
s.encode_utf16().chain(std::iter::once(0)).collect()
}
pub(super) struct Inner {
event: HANDLE,
registration: PathBuf,
}
unsafe impl Send for Inner {}
unsafe impl Sync for Inner {}
pub(super) fn listen(suite: &Suite, name: &str) -> io::Result<Inner> {
let dir = dir_for(suite, name);
crate::suite::make_private_dir(&dir).map_err(io::Error::other)?;
let event_name = format!(
"Local\\rk-wake-{}-{}-{}",
hash_hex(&format!("{}|{name}", suite.root().display())),
std::process::id(),
NEXT.fetch_add(1, Ordering::SeqCst)
);
let attrs = crate::peer::owner_only_security_attributes()?;
let wname = wide(&event_name);
let event = unsafe {
CreateEventW(
Some(attrs.raw() as *const _),
false,
false,
PCWSTR(wname.as_ptr()),
)
}
.map_err(|e| io::Error::from_raw_os_error((e.code().0 as u32 & 0xFFFF) as i32))?;
let registration = dir.join(format!(
"{}-{}.ev",
std::process::id(),
NEXT.fetch_add(1, Ordering::SeqCst)
));
if let Err(e) = crate::record::write_atomic(®istration, event_name.as_bytes()) {
unsafe {
let _ = CloseHandle(event);
}
return Err(e);
}
Ok(Inner {
event,
registration,
})
}
impl Inner {
pub(super) fn wait(&self, timeout: Duration) -> io::Result<bool> {
let ms = timeout.as_millis().min(u128::from(u32::MAX - 1)) as u32;
let status = unsafe { WaitForSingleObject(self.event, ms) };
Ok(status == WAIT_OBJECT_0)
}
}
impl Drop for Inner {
fn drop(&mut self) {
let _ = std::fs::remove_file(&self.registration);
unsafe {
let _ = CloseHandle(self.event);
}
}
}
pub(super) fn post(suite: &Suite, name: &str) -> io::Result<usize> {
let dir = dir_for(suite, name);
let entries = match std::fs::read_dir(&dir) {
Ok(entries) => entries,
Err(e) if e.kind() == io::ErrorKind::NotFound => return Ok(0),
Err(e) => return Err(e),
};
let mut woken = 0;
for entry in entries.flatten() {
let path = entry.path();
if path.extension().and_then(|e| e.to_str()) != Some("ev") {
continue;
}
let Ok(event_name) = std::fs::read_to_string(&path) else {
continue;
};
let wname = wide(event_name.trim());
match unsafe { OpenEventW(EVENT_MODIFY_STATE, false, PCWSTR(wname.as_ptr())) } {
Ok(handle) => {
unsafe {
if SetEvent(handle).is_ok() {
woken += 1;
}
let _ = CloseHandle(handle);
}
}
Err(_) => {
let _ = std::fs::remove_file(&path);
}
}
}
Ok(woken)
}
}
#[cfg(not(any(unix, windows)))]
mod imp {
use super::*;
pub(super) struct Inner;
pub(super) fn listen(_suite: &Suite, _name: &str) -> io::Result<Inner> {
Err(io::Error::new(
io::ErrorKind::Unsupported,
"no cross-process wake on this platform",
))
}
impl Inner {
pub(super) fn wait(&self, _timeout: Duration) -> io::Result<bool> {
Ok(false)
}
}
pub(super) fn post(_suite: &Suite, _name: &str) -> io::Result<usize> {
Ok(0)
}
}