1use crate::suite::{valid_service_name, Suite};
21use std::io;
22use std::time::Duration;
23
24fn invalid_name(name: &str) -> io::Error {
26 io::Error::new(
27 io::ErrorKind::InvalidInput,
28 format!("{name:?} is not a valid wake name"),
29 )
30}
31
32fn hash_hex(text: &str) -> String {
33 let mut h: u64 = 0xcbf2_9ce4_8422_2325;
35 for b in text.bytes() {
36 h ^= u64::from(b);
37 h = h.wrapping_mul(0x0000_0100_0000_01b3);
38 }
39 format!("{h:016x}")
40}
41
42pub fn post(suite: &Suite, name: &str) -> io::Result<usize> {
45 if !valid_service_name(name) {
46 return Err(invalid_name(name));
47 }
48 imp::post(suite, name)
49}
50
51pub fn listen(suite: &Suite, name: &str) -> io::Result<WakeListener> {
53 if !valid_service_name(name) {
54 return Err(invalid_name(name));
55 }
56 imp::listen(suite, name).map(|inner| WakeListener { inner })
57}
58
59pub struct WakeListener {
61 inner: imp::Inner,
62}
63
64impl WakeListener {
65 pub fn wait(&self, timeout: Duration) -> io::Result<bool> {
68 self.inner.wait(timeout)
69 }
70}
71
72#[cfg(unix)]
73mod imp {
74 use super::*;
75 use std::os::unix::fs::{DirBuilderExt, MetadataExt, PermissionsExt};
76 use std::os::unix::net::UnixDatagram;
77 use std::path::{Path, PathBuf};
78 use std::sync::atomic::{AtomicU64, Ordering};
79
80 static NEXT: AtomicU64 = AtomicU64::new(1);
81
82 fn dir_for(suite: &Suite, name: &str) -> PathBuf {
83 let key = format!("{}|{name}", suite.root().display());
84 std::env::temp_dir().join(format!(
85 "rkwake-{}-{}",
86 crate::transport::current_uid(),
87 hash_hex(&key)
88 ))
89 }
90
91 fn ensure_dir(dir: &Path) -> io::Result<()> {
94 match std::fs::DirBuilder::new().mode(0o700).create(dir) {
95 Ok(()) => {}
96 Err(e) if e.kind() == io::ErrorKind::AlreadyExists => {}
97 Err(e) => return Err(e),
98 }
99 let md = std::fs::symlink_metadata(dir)?;
100 if !md.is_dir()
101 || md.uid() != crate::transport::current_uid()
102 || md.permissions().mode() & 0o022 != 0
103 {
104 return Err(io::Error::new(
105 io::ErrorKind::PermissionDenied,
106 "the wake directory is not a private directory of this user",
107 ));
108 }
109 Ok(())
110 }
111
112 pub(super) struct Inner {
113 sock: UnixDatagram,
114 path: PathBuf,
115 }
116
117 pub(super) fn listen(suite: &Suite, name: &str) -> io::Result<Inner> {
118 let dir = dir_for(suite, name);
119 ensure_dir(&dir)?;
120 let path = dir.join(format!(
121 "{}-{}.sock",
122 std::process::id(),
123 NEXT.fetch_add(1, Ordering::SeqCst)
124 ));
125 let _ = std::fs::remove_file(&path);
126 let sock = UnixDatagram::bind(&path)?;
127 std::fs::set_permissions(&path, std::fs::Permissions::from_mode(0o600))?;
128 Ok(Inner { sock, path })
129 }
130
131 impl Inner {
132 pub(super) fn wait(&self, timeout: Duration) -> io::Result<bool> {
133 self.sock
134 .set_read_timeout(Some(timeout.max(Duration::from_millis(1))))?;
135 let mut buf = [0u8; 16];
136 match self.sock.recv(&mut buf) {
137 Ok(_) => {}
138 Err(e)
139 if matches!(
140 e.kind(),
141 io::ErrorKind::WouldBlock | io::ErrorKind::TimedOut
142 ) =>
143 {
144 return Ok(false)
145 }
146 Err(e) => return Err(e),
147 }
148 self.sock.set_nonblocking(true)?;
150 while self.sock.recv(&mut buf).is_ok() {}
151 self.sock.set_nonblocking(false)?;
152 Ok(true)
153 }
154 }
155
156 impl Drop for Inner {
157 fn drop(&mut self) {
158 let _ = std::fs::remove_file(&self.path);
159 }
160 }
161
162 pub(super) fn post(suite: &Suite, name: &str) -> io::Result<usize> {
163 let dir = dir_for(suite, name);
164 let entries = match std::fs::read_dir(&dir) {
165 Ok(entries) => entries,
166 Err(e) if e.kind() == io::ErrorKind::NotFound => return Ok(0),
167 Err(e) => return Err(e),
168 };
169 ensure_dir(&dir)?;
170 let sender = UnixDatagram::unbound()?;
171 sender.set_nonblocking(true)?;
172 let mut woken = 0;
173 for entry in entries.flatten() {
174 let path = entry.path();
175 if path.extension().and_then(|e| e.to_str()) != Some("sock") {
176 continue;
177 }
178 match sender.send_to(&[1], &path) {
179 Ok(_) => woken += 1,
180 Err(e) if e.kind() == io::ErrorKind::WouldBlock => woken += 1,
182 Err(e)
183 if matches!(
184 e.kind(),
185 io::ErrorKind::ConnectionRefused | io::ErrorKind::NotFound
186 ) =>
187 {
188 let _ = std::fs::remove_file(&path);
189 }
190 Err(_) => {}
191 }
192 }
193 Ok(woken)
194 }
195}
196
197#[cfg(windows)]
198mod imp {
199 use super::*;
200 use std::path::PathBuf;
201 use std::sync::atomic::{AtomicU64, Ordering};
202 use windows::core::PCWSTR;
203 use windows::Win32::Foundation::{CloseHandle, HANDLE, WAIT_OBJECT_0};
204 use windows::Win32::System::Threading::{
205 CreateEventW, OpenEventW, SetEvent, WaitForSingleObject, EVENT_MODIFY_STATE,
206 };
207
208 static NEXT: AtomicU64 = AtomicU64::new(1);
209
210 fn dir_for(suite: &Suite, name: &str) -> PathBuf {
211 suite.service_dir(&format!("wake.{name}"))
212 }
213
214 fn wide(s: &str) -> Vec<u16> {
215 s.encode_utf16().chain(std::iter::once(0)).collect()
216 }
217
218 pub(super) struct Inner {
219 event: HANDLE,
220 registration: PathBuf,
221 }
222
223 unsafe impl Send for Inner {}
225 unsafe impl Sync for Inner {}
226
227 pub(super) fn listen(suite: &Suite, name: &str) -> io::Result<Inner> {
228 let dir = dir_for(suite, name);
229 crate::suite::make_private_dir(&dir).map_err(io::Error::other)?;
230 let event_name = format!(
231 "Local\\rk-wake-{}-{}-{}",
232 hash_hex(&format!("{}|{name}", suite.root().display())),
233 std::process::id(),
234 NEXT.fetch_add(1, Ordering::SeqCst)
235 );
236 let attrs = crate::peer::owner_only_security_attributes()?;
237 let wname = wide(&event_name);
238 let event = unsafe {
241 CreateEventW(
242 Some(attrs.raw() as *const _),
243 false,
244 false,
245 PCWSTR(wname.as_ptr()),
246 )
247 }
248 .map_err(|e| io::Error::from_raw_os_error((e.code().0 as u32 & 0xFFFF) as i32))?;
249 let registration = dir.join(format!(
250 "{}-{}.ev",
251 std::process::id(),
252 NEXT.fetch_add(1, Ordering::SeqCst)
253 ));
254 if let Err(e) = crate::record::write_atomic(®istration, event_name.as_bytes()) {
255 unsafe {
257 let _ = CloseHandle(event);
258 }
259 return Err(e);
260 }
261 Ok(Inner {
262 event,
263 registration,
264 })
265 }
266
267 impl Inner {
268 pub(super) fn wait(&self, timeout: Duration) -> io::Result<bool> {
269 let ms = timeout.as_millis().min(u128::from(u32::MAX - 1)) as u32;
270 let status = unsafe { WaitForSingleObject(self.event, ms) };
272 Ok(status == WAIT_OBJECT_0)
273 }
274 }
275
276 impl Drop for Inner {
277 fn drop(&mut self) {
278 let _ = std::fs::remove_file(&self.registration);
279 unsafe {
281 let _ = CloseHandle(self.event);
282 }
283 }
284 }
285
286 pub(super) fn post(suite: &Suite, name: &str) -> io::Result<usize> {
287 let dir = dir_for(suite, name);
288 let entries = match std::fs::read_dir(&dir) {
289 Ok(entries) => entries,
290 Err(e) if e.kind() == io::ErrorKind::NotFound => return Ok(0),
291 Err(e) => return Err(e),
292 };
293 let mut woken = 0;
294 for entry in entries.flatten() {
295 let path = entry.path();
296 if path.extension().and_then(|e| e.to_str()) != Some("ev") {
297 continue;
298 }
299 let Ok(event_name) = std::fs::read_to_string(&path) else {
300 continue;
301 };
302 let wname = wide(event_name.trim());
303 match unsafe { OpenEventW(EVENT_MODIFY_STATE, false, PCWSTR(wname.as_ptr())) } {
305 Ok(handle) => {
306 unsafe {
308 if SetEvent(handle).is_ok() {
309 woken += 1;
310 }
311 let _ = CloseHandle(handle);
312 }
313 }
314 Err(_) => {
316 let _ = std::fs::remove_file(&path);
317 }
318 }
319 }
320 Ok(woken)
321 }
322}
323
324#[cfg(not(any(unix, windows)))]
325mod imp {
326 use super::*;
327
328 pub(super) struct Inner;
329
330 pub(super) fn listen(_suite: &Suite, _name: &str) -> io::Result<Inner> {
331 Err(io::Error::new(
332 io::ErrorKind::Unsupported,
333 "no cross-process wake on this platform",
334 ))
335 }
336
337 impl Inner {
338 pub(super) fn wait(&self, _timeout: Duration) -> io::Result<bool> {
339 Ok(false)
340 }
341 }
342
343 pub(super) fn post(_suite: &Suite, _name: &str) -> io::Result<usize> {
344 Ok(0)
345 }
346}