Skip to main content

bark_cli/
connection.rs

1//! The barkd single-instance guarantee: `barkd.lock`, exclusively locked
2//! for the process lifetime. The wallet's `LOCK` can't serve here, since
3//! it is only taken once a wallet is open.
4
5use std::fs;
6use std::path::Path;
7
8use anyhow::{bail, Context};
9
10use bark::lock_manager::PidLockError;
11use bark::lock_manager::pid_flock::FlockPidLockManager;
12
13use crate::wallet::AUTH_TOKEN_FILE;
14
15/// File in the datadir exclusively locked for the lifetime of a barkd process.
16pub const BARKD_LOCK_FILE: &str = "barkd.lock";
17
18/// Remove everything in the datadir except the running barkd's own files,
19/// so the held lock and auth token survive a wallet wipe.
20pub fn wipe_datadir_except_barkd_files(datadir: &Path) -> anyhow::Result<()> {
21	let barkd_files = [BARKD_LOCK_FILE, AUTH_TOKEN_FILE];
22	for entry in fs::read_dir(datadir).context("failed to list datadir")? {
23		let entry = entry?;
24		if barkd_files.iter().any(|k| entry.file_name() == *k) {
25			continue;
26		}
27		let path = entry.path();
28		if entry.file_type()?.is_dir() {
29			fs::remove_dir_all(&path)
30		} else {
31			fs::remove_file(&path)
32		}.with_context(|| format!("failed to remove {}", path.display()))?;
33	}
34	Ok(())
35}
36
37/// Take the exclusive barkd lock on the datadir, or fail fast when another
38/// barkd already runs there. The OS releases the lock when the returned value
39/// is dropped or the process exits, even on SIGKILL or a crash.
40pub fn acquire_barkd_lock(datadir: &Path) -> anyhow::Result<FlockPidLockManager> {
41	match FlockPidLockManager::new_with_lock_file(datadir, BARKD_LOCK_FILE) {
42		Ok(lock) => Ok(lock),
43		Err(PidLockError::AlreadyHeld { datadir, pid }) => match pid {
44			Some(pid) => bail!(
45				"another barkd is already running on datadir {} (pid {})",
46				datadir.display(), pid,
47			),
48			None => bail!(
49				"another barkd is already running on datadir {}",
50				datadir.display(),
51			),
52		},
53		Err(e) => Err(e).context("failed to acquire barkd lock"),
54	}
55}
56
57#[cfg(test)]
58mod test {
59	use super::*;
60
61	fn tmp_dir() -> std::path::PathBuf {
62		let dir = std::env::temp_dir()
63			.join(format!("bark-connection-{}", std::process::id()))
64			.join(format!("{}", std::time::SystemTime::now()
65				.duration_since(std::time::UNIX_EPOCH).unwrap().as_nanos()));
66		fs::create_dir_all(&dir).unwrap();
67		dir
68	}
69
70	#[test]
71	fn wipe_keeps_barkd_files() {
72		let dir = tmp_dir();
73		let _lock = acquire_barkd_lock(&dir).unwrap();
74		fs::write(dir.join(AUTH_TOKEN_FILE), "tok").unwrap();
75		fs::write(dir.join("db.sqlite"), "x").unwrap();
76		fs::create_dir(dir.join("sub")).unwrap();
77		fs::write(dir.join("sub").join("f"), "y").unwrap();
78
79		wipe_datadir_except_barkd_files(&dir).unwrap();
80
81		assert!(dir.join(BARKD_LOCK_FILE).exists());
82		assert!(dir.join(AUTH_TOKEN_FILE).exists());
83		assert!(!dir.join("db.sqlite").exists());
84		assert!(!dir.join("sub").exists());
85
86		let _ = fs::remove_dir_all(&dir);
87	}
88
89	#[test]
90	fn barkd_lock_refuses_second_holder() {
91		let dir = tmp_dir();
92		let held = acquire_barkd_lock(&dir).unwrap();
93
94		// flock is per open file description, so a second open in the same
95		// process conflicts just like another process would.
96		let err = acquire_barkd_lock(&dir).unwrap_err();
97		assert!(
98			err.to_string().contains("another barkd is already running"),
99			"unexpected error: {}", err,
100		);
101		assert!(err.to_string().contains(&std::process::id().to_string()));
102
103		drop(held);
104		let _reacquired = acquire_barkd_lock(&dir).unwrap();
105
106		let _ = fs::remove_dir_all(&dir);
107	}
108}