agent_tui_infra/infra/daemon/
file_lock.rs1use std::fs::File;
4use std::fs::OpenOptions;
5use std::io::Write;
6use std::os::unix::io::AsRawFd;
7use std::path::Path;
8
9use crate::common::DaemonError;
10use crate::infra::ipc::current_process_identity;
11
12#[derive(serde::Serialize)]
13struct LockFilePayload {
14 pid: u32,
15 #[serde(skip_serializing_if = "Option::is_none")]
16 process_started_at: Option<u64>,
17}
18
19pub struct LockFile {
20 _file: File,
21}
22
23impl LockFile {
24 pub fn acquire(lock_path: &Path) -> Result<Self, DaemonError> {
25 let lock_file = OpenOptions::new()
26 .write(true)
27 .create(true)
28 .truncate(false)
29 .open(lock_path)
30 .map_err(|e| DaemonError::LockFailed {
31 operation: "open lock file",
32 source: Box::new(e),
33 })?;
34
35 let fd = lock_file.as_raw_fd();
36
37 let result = unsafe { libc::flock(fd, libc::LOCK_EX | libc::LOCK_NB) };
41 if result != 0 {
42 let err = std::io::Error::last_os_error();
43 match err.raw_os_error() {
44 Some(code) if code == libc::EWOULDBLOCK || code == libc::EAGAIN => {
45 return Err(DaemonError::AlreadyRunning);
46 }
47 _ => {
48 return Err(DaemonError::LockFailed {
49 operation: "flock lock file",
50 source: Box::new(err),
51 });
52 }
53 }
54 }
55
56 lock_file.set_len(0).map_err(|e| DaemonError::LockFailed {
57 operation: "truncate lock file",
58 source: Box::new(e),
59 })?;
60
61 let mut lock_file = lock_file;
62 let identity = current_process_identity();
63 serde_json::to_writer_pretty(
64 &mut lock_file,
65 &LockFilePayload {
66 pid: identity.pid,
67 process_started_at: identity.started_at,
68 },
69 )
70 .map_err(|e| DaemonError::LockFailed {
71 operation: "write daemon identity",
72 source: Box::new(e),
73 })?;
74 writeln!(lock_file).map_err(|e| DaemonError::LockFailed {
75 operation: "flush daemon identity",
76 source: Box::new(e),
77 })?;
78
79 Ok(Self { _file: lock_file })
80 }
81}
82
83pub fn remove_lock_file(lock_path: &Path) {
84 if lock_path.exists() {
85 let _ = std::fs::remove_file(lock_path);
86 }
87}
88
89#[cfg(test)]
90#[path = "file_lock_tests.rs"]
91mod tests;