1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
//! Detecting whether a serial port is already in use.
//!
//! A COM port allows only one open at a time, so opening it tells us if someone
//! else holds it. We open with zero desired access, which performs that check
//! without initialising the UART, so it never toggles DTR/RTS and never resets
//! the attached device.
//!
//! Two smon instances polling at the same instant would each briefly hold the
//! port and so report the other as busy. [`Lock`] is a cross-process named
//! mutex that serialises the probe pass, so only one instance probes at a time
//! and they never see each other.
#[cfg(windows)]
mod imp {
use std::{
ffi::{OsStr, c_void},
os::windows::ffi::OsStrExt,
ptr::null_mut,
};
#[link(name = "kernel32")]
unsafe extern "system" {
fn CreateFileW(
name: *const u16,
access: u32,
share: u32,
security: *mut c_void,
disposition: u32,
flags: u32,
template: *mut c_void,
) -> *mut c_void;
fn CloseHandle(handle: *mut c_void) -> i32;
fn GetLastError() -> u32;
fn CreateMutexW(security: *mut c_void, initial_owner: i32, name: *const u16) -> *mut c_void;
fn WaitForSingleObject(handle: *mut c_void, millis: u32) -> u32;
fn ReleaseMutex(handle: *mut c_void) -> i32;
}
fn wide(s: &str) -> Vec<u16> {
OsStr::new(s).encode_wide().chain([0]).collect()
}
pub struct Lock {
handle: *mut c_void,
owned: bool,
}
impl Lock {
pub fn acquire() -> Lock {
const WAIT_OBJECT_0: u32 = 0;
const WAIT_ABANDONED: u32 = 0x80;
unsafe {
let handle = CreateMutexW(null_mut(), 0, wide("smon_port_probe").as_ptr());
let owned = if handle.is_null() {
false
} else {
matches!(WaitForSingleObject(handle, 1000), WAIT_OBJECT_0 | WAIT_ABANDONED)
};
Lock { handle, owned }
}
}
}
impl Lock {
pub fn release(self) {}
}
impl Drop for Lock {
fn drop(&mut self) {
unsafe {
if self.owned {
ReleaseMutex(self.handle);
}
if !self.handle.is_null() {
CloseHandle(self.handle);
}
}
}
}
pub fn is_busy(port: &str) -> bool {
const OPEN_EXISTING: u32 = 3;
const ERROR_ACCESS_DENIED: u32 = 5;
const ERROR_SHARING_VIOLATION: u32 = 32;
let invalid = usize::MAX as *mut c_void; // INVALID_HANDLE_VALUE is -1
unsafe {
let handle = CreateFileW(
wide(&format!(r"\\.\{port}")).as_ptr(),
0,
0,
null_mut(),
OPEN_EXISTING,
0,
null_mut(),
);
if handle == invalid {
let err = GetLastError();
return err == ERROR_ACCESS_DENIED || err == ERROR_SHARING_VIOLATION;
}
CloseHandle(handle);
false
}
}
}
#[cfg(not(windows))]
mod imp {
pub struct Lock;
impl Lock {
pub fn acquire() -> Lock {
Lock
}
pub fn release(self) {}
}
// There is no cheap busy check here, so a port another process holds is
// only discovered by trying to open it.
pub fn is_busy(_port: &str) -> bool {
false
}
}
use imp::Lock;
pub use imp::is_busy;
/// Run `f` while holding the cross-process probe lock, so two instances never
/// probe or open the same port at the same instant and each report the other as
/// busy.
pub fn hold<T>(f: impl FnOnce() -> T) -> T {
let lock = Lock::acquire();
let out = f();
lock.release();
out
}