fix: exclusive serial port lock prevents USB port theft
The mux now acquires an flock-based exclusive lock on the serial port before opening it. This prevents bridges or other processes from directly opening the same device while the mux owns it. The lock is held for the duration of the dongle session and auto-released on disconnect or crash.
This commit is contained in:
4 files changed
+50
-8
No files matched your search
@@ -1,5 +1,15 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 0.2.1 — 2026-04-07
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- **Exclusive serial port lock.** The mux now acquires an `flock`-based
|
||||||
|
exclusive lock on the serial port before opening it. This prevents other
|
||||||
|
processes (bridges, other mux instances) from directly opening the same
|
||||||
|
device while the mux owns it. The lock is held for the duration of the
|
||||||
|
dongle session and auto-released on disconnect or crash.
|
||||||
|
|
||||||
## 0.2.0 — 2026-04-07
|
## 0.2.0 — 2026-04-07
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|||||||
Generated
+3
-3
@@ -161,9 +161,9 @@ checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "donglora-client"
|
name = "donglora-client"
|
||||||
version = "0.2.0"
|
version = "0.2.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1936935d31ab56a8cd70ceecfd6d447f2628a73a123e1cfe37ee4987eab0fa70"
|
checksum = "343d750fe1e5dad94d4b31e6dbbb05214b39e66f3c94a2ff2a29b0031ffd2826"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"serialport",
|
"serialport",
|
||||||
@@ -173,7 +173,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "donglora-mux"
|
name = "donglora-mux"
|
||||||
version = "0.2.0"
|
version = "0.2.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"clap",
|
"clap",
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "donglora-mux"
|
name = "donglora-mux"
|
||||||
version = "0.2.0"
|
version = "0.2.1"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
rust-version = "1.85"
|
rust-version = "1.85"
|
||||||
authors = ["Stephen Waits <steve@waits.net>"]
|
authors = ["Stephen Waits <steve@waits.net>"]
|
||||||
|
|||||||
+36
-4
@@ -156,12 +156,12 @@ impl MuxDaemon {
|
|||||||
}
|
}
|
||||||
|
|
||||||
info!("opening dongle on {}", self.serial_port);
|
info!("opening dongle on {}", self.serial_port);
|
||||||
let serial = loop {
|
let (serial, _serial_lock_guard) = loop {
|
||||||
if self.shutdown.is_cancelled() {
|
if self.shutdown.is_cancelled() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
match open_and_ping(&self.serial_port).await {
|
match open_and_ping(&self.serial_port).await {
|
||||||
Ok(s) => break s,
|
Ok(pair) => break pair,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
debug!("could not open dongle: {e}");
|
debug!("could not open dongle: {e}");
|
||||||
tokio::select! {
|
tokio::select! {
|
||||||
@@ -224,7 +224,39 @@ impl MuxDaemon {
|
|||||||
|
|
||||||
// ── Dongle I/O ─────────────────────────────────────────────────────
|
// ── Dongle I/O ─────────────────────────────────────────────────────
|
||||||
|
|
||||||
async fn open_and_ping(port: &str) -> anyhow::Result<tokio_serial::SerialStream> {
|
/// Lock file path for exclusive serial port access.
|
||||||
|
fn serial_lock_path(port: &str) -> String {
|
||||||
|
format!("/tmp/donglora-serial-{}.lock", port.replace('/', "_"))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Open the serial port with an exclusive lock and verify the dongle responds to ping.
|
||||||
|
///
|
||||||
|
/// Returns the serial stream and the lock. The caller must keep `_serial_lock` alive
|
||||||
|
/// for the duration of the dongle session. Dropping it releases the exclusive flock,
|
||||||
|
/// allowing other processes to claim the port.
|
||||||
|
async fn open_and_ping(
|
||||||
|
port: &str,
|
||||||
|
) -> anyhow::Result<(tokio_serial::SerialStream, fd_lock::RwLock<std::fs::File>)> {
|
||||||
|
// Acquire exclusive flock before opening the serial port. This prevents bridges
|
||||||
|
// or other processes from directly opening the same device while the mux owns it.
|
||||||
|
let lock_path = serial_lock_path(port);
|
||||||
|
let lock_file = std::fs::OpenOptions::new()
|
||||||
|
.create(true)
|
||||||
|
.write(true)
|
||||||
|
.truncate(false)
|
||||||
|
.open(&lock_path)
|
||||||
|
.map_err(|e| anyhow::anyhow!("failed to open serial lock file {lock_path}: {e}"))?;
|
||||||
|
let mut lock = fd_lock::RwLock::new(lock_file);
|
||||||
|
// Acquire exclusive flock (non-blocking). Fails if another process holds it.
|
||||||
|
let guard = lock
|
||||||
|
.try_write()
|
||||||
|
.map_err(|_| anyhow::anyhow!("serial port {port} is locked by another process"))?;
|
||||||
|
// Forget the guard so its Drop doesn't call flock(UNLOCK). The underlying flock
|
||||||
|
// stays held as long as the file descriptor (owned by `lock`) remains open.
|
||||||
|
// When `lock` is dropped at the end of the dongle session, the fd closes and
|
||||||
|
// the OS releases the flock automatically.
|
||||||
|
std::mem::forget(guard);
|
||||||
|
|
||||||
let builder = tokio_serial::new(port, 115_200).timeout(Duration::from_secs(2));
|
let builder = tokio_serial::new(port, 115_200).timeout(Duration::from_secs(2));
|
||||||
let mut serial =
|
let mut serial =
|
||||||
tokio_serial::SerialStream::open(&builder).map_err(|e| anyhow::anyhow!("failed to open {port}: {e}"))?;
|
tokio_serial::SerialStream::open(&builder).map_err(|e| anyhow::anyhow!("failed to open {port}: {e}"))?;
|
||||||
@@ -260,7 +292,7 @@ async fn open_and_ping(port: &str) -> anyhow::Result<tokio_serial::SerialStream>
|
|||||||
for frame in reader.feed(&buf[..n]) {
|
for frame in reader.feed(&buf[..n]) {
|
||||||
if frame.first() == Some(&0) {
|
if frame.first() == Some(&0) {
|
||||||
info!("dongle responded to Ping");
|
info!("dongle responded to Ping");
|
||||||
return Ok(serial);
|
return Ok((serial, lock));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user