Skip to content

Commit 63b6a52

Browse files
committed
watt: simplify lock acquisition; force single exclusive lock
Signed-off-by: NotAShelf <raf@notashelf.dev> Change-Id: I563f9989cf73ce99f227555a29c770916a6a6964
1 parent a8412d2 commit 63b6a52

File tree

2 files changed

+36
-113
lines changed

2 files changed

+36
-113
lines changed

watt/lib.rs

Lines changed: 8 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,4 @@
1-
use std::{
2-
env,
3-
path::PathBuf,
4-
};
1+
use std::path::PathBuf;
52

63
use anyhow::Context as _;
74
use clap::Parser as _;
@@ -25,11 +22,6 @@ pub struct Cli {
2522
/// The daemon config path.
2623
#[arg(long, env = "WATT_CONFIG")]
2724
config: Option<PathBuf>,
28-
29-
/// Force running even if another instance is already running. Potentially
30-
/// destructive.
31-
#[arg(long)]
32-
force: bool,
3325
}
3426

3527
pub fn main() -> anyhow::Result<()> {
@@ -48,14 +40,13 @@ pub fn main() -> anyhow::Result<()> {
4840

4941
log::info!("starting watt daemon");
5042

51-
let lock_path = env::var_os("XDG_RUNTIME_DIR")
52-
.map_or_else(|| PathBuf::from("/run"), PathBuf::from)
53-
.join("watt.pid");
54-
55-
let _lock =
56-
lock::LockFile::acquire(&lock_path, cli.force).with_context(|| {
57-
format!("failed to acquire pid lock at {}", lock_path.display())
58-
})?;
43+
let lock_path = PathBuf::from("/run/watt.lock");
44+
let _lock = lock::LockFile::acquire(&lock_path).with_context(|| {
45+
format!(
46+
"failed to acquire exclusive lock at {}",
47+
lock_path.display()
48+
)
49+
})?;
5950

6051
system::run_daemon(config)
6152
}

watt/lock.rs

Lines changed: 28 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,15 @@ use std::{
22
error::Error,
33
fmt,
44
fs::{
5-
self,
65
File,
76
OpenOptions,
87
},
9-
io::Write,
108
ops,
9+
os::unix::fs::OpenOptionsExt,
1110
path::{
1211
Path,
1312
PathBuf,
1413
},
15-
process,
1614
};
1715

1816
#[cfg(unix)] use nix::fcntl::{
@@ -30,17 +28,17 @@ pub struct LockFile {
3028

3129
#[derive(Debug)]
3230
pub struct LockFileError {
33-
pub path: PathBuf,
34-
pid: u32,
31+
pub path: PathBuf,
32+
pub message: Option<String>,
3533
}
3634

3735
impl fmt::Display for LockFileError {
3836
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
39-
if self.pid == 0 {
40-
write!(f, "failed to acquire lock at {}", self.path.display())
41-
} else {
42-
write!(f, "another watt daemon is running (PID: {})", self.pid)
37+
write!(f, "failed to acquire lock on {}", self.path.display())?;
38+
if let Some(msg) = &self.message {
39+
write!(f, ": {}", msg)?;
4340
}
41+
Ok(())
4442
}
4543
}
4644

@@ -65,17 +63,13 @@ impl LockFile {
6563
&self.path
6664
}
6765

68-
pub fn acquire(
69-
lock_path: &Path,
70-
force: bool,
71-
) -> Result<Option<Self>, LockFileError> {
72-
let pid = process::id();
73-
66+
pub fn acquire(lock_path: &Path) -> Result<Self, LockFileError> {
7467
#[allow(clippy::suspicious_open_options)]
7568
let file = OpenOptions::new()
7669
.create(true)
7770
.read(true)
7871
.write(true)
72+
.mode(0o600)
7973
.open(lock_path)
8074
.map_err(|error| {
8175
log::error!(
@@ -84,96 +78,34 @@ impl LockFile {
8478
error
8579
);
8680
LockFileError {
87-
path: lock_path.to_owned(),
88-
pid: 0,
81+
path: lock_path.to_owned(),
82+
message: Some(error.to_string()),
8983
}
9084
})?;
9185

92-
let mut lock = match Flock::lock(file, FlockArg::LockExclusiveNonblock) {
93-
Ok(lock) => lock,
94-
Err((_, nix::errno::Errno::EWOULDBLOCK)) => {
95-
let Some(existing_pid) = Self::read_pid(lock_path) else {
96-
if force {
97-
log::warn!(
98-
"could not determine PID of existing watt instance, starting \
99-
anyway",
100-
);
101-
return Ok(None);
102-
}
103-
104-
return Err(LockFileError {
105-
path: lock_path.to_owned(),
106-
pid: 0,
107-
});
108-
};
109-
110-
if force {
111-
log::warn!(
112-
"another watt instance is running (PID: {existing_pid}), starting \
113-
anyway",
86+
let lock = Flock::lock(file, FlockArg::LockExclusiveNonblock).map_err(
87+
|(_, error)| {
88+
let message = if error == nix::errno::Errno::EWOULDBLOCK {
89+
log::error!(
90+
"another watt instance is already running (lock held on {})",
91+
lock_path.display()
11492
);
115-
return Ok(None);
116-
}
117-
118-
return Err(LockFileError {
119-
path: lock_path.to_owned(),
120-
pid: existing_pid,
121-
});
122-
},
93+
Some("another instance is running".to_string())
94+
} else {
95+
log::error!("failed to acquire lock: {}", error);
96+
Some(error.to_string())
97+
};
12398

124-
Err((_, error)) => {
125-
log::error!("failed to acquire lock: {}", error);
126-
return Err(LockFileError {
99+
LockFileError {
127100
path: lock_path.to_owned(),
128-
pid: 0,
129-
});
101+
message,
102+
}
130103
},
131-
};
132-
133-
if let Err(e) = lock.set_len(0) {
134-
log::error!("failed to truncate lock file: {}", e);
135-
return Err(LockFileError {
136-
path: lock_path.to_owned(),
137-
pid: 0,
138-
});
139-
}
140-
141-
if let Err(e) = lock.write_all(format!("{pid}\n").as_bytes()) {
142-
log::error!("failed to write PID to lock file: {}", e);
143-
return Err(LockFileError {
144-
path: lock_path.to_owned(),
145-
pid: 0,
146-
});
147-
}
104+
)?;
148105

149-
if let Err(e) = lock.sync_all() {
150-
log::error!("failed to sync lock file: {}", e);
151-
return Err(LockFileError {
152-
path: lock_path.to_owned(),
153-
pid: 0,
154-
});
155-
}
156-
157-
Ok(Some(LockFile {
106+
Ok(LockFile {
158107
lock,
159108
path: lock_path.to_owned(),
160-
}))
161-
}
162-
163-
fn read_pid(lock_path: &Path) -> Option<u32> {
164-
match fs::read_to_string(lock_path) {
165-
Ok(content) => content.trim().parse().ok(),
166-
Err(_) => None,
167-
}
168-
}
169-
170-
pub fn release(&mut self) {
171-
let _ = fs::remove_file(&self.path);
172-
}
173-
}
174-
175-
impl Drop for LockFile {
176-
fn drop(&mut self) {
177-
self.release();
109+
})
178110
}
179111
}

0 commit comments

Comments
 (0)