1use crate::{hash_path, user_forc_directory};
2use std::{
3 fs::{create_dir_all, remove_file, File},
4 io::{self, Read, Write},
5 path::{Path, PathBuf},
6};
7
8pub struct PidFileLocking(PathBuf);
15
16impl PidFileLocking {
17 pub fn new<X: AsRef<Path>, Y: AsRef<Path>>(
18 filename: X,
19 dir: Y,
20 extension: &str,
21 ) -> PidFileLocking {
22 let file_name = hash_path(filename);
23 Self(
24 user_forc_directory()
25 .join(dir)
26 .join(file_name)
27 .with_extension(extension),
28 )
29 }
30
31 pub fn lsp<X: AsRef<Path>>(filename: X) -> PidFileLocking {
34 Self::new(filename, ".lsp-locks", "lock")
35 }
36
37 #[cfg(not(target_os = "windows"))]
39 fn is_pid_active(pid: usize) -> bool {
40 use std::process::Command;
43 let output = Command::new("ps")
44 .arg("-p")
45 .arg(pid.to_string())
46 .output()
47 .expect("Failed to execute ps command");
48
49 let output_str = String::from_utf8_lossy(&output.stdout);
50 output_str.contains(&format!("{} ", pid))
51 }
52
53 #[cfg(target_os = "windows")]
54 fn is_pid_active(pid: usize) -> bool {
55 use std::process::Command;
58 let output = Command::new("tasklist")
59 .arg("/FI")
60 .arg(format!("PID eq {}", pid))
61 .output()
62 .expect("Failed to execute tasklist command");
63
64 let output_str = String::from_utf8_lossy(&output.stdout);
65 output_str.contains(&format!("{}", pid))
67 }
68
69 pub fn release(&self) -> io::Result<()> {
71 if self.is_locked() {
72 Err(io::Error::new(
73 std::io::ErrorKind::Other,
74 format!(
75 "Cannot remove a dirty lock file, it is locked by another process (PID: {:#?})",
76 self.get_locker_pid()
77 ),
78 ))
79 } else {
80 self.remove_file()?;
81 Ok(())
82 }
83 }
84
85 fn remove_file(&self) -> io::Result<()> {
86 match remove_file(&self.0) {
87 Err(e) => {
88 if e.kind() != std::io::ErrorKind::NotFound {
89 return Err(e);
90 }
91 Ok(())
92 }
93 _ => Ok(()),
94 }
95 }
96
97 pub fn get_locker_pid(&self) -> Option<usize> {
100 let fs = File::open(&self.0);
101 if let Ok(mut file) = fs {
102 let mut contents = String::new();
103 file.read_to_string(&mut contents).ok();
104 drop(file);
105 if let Ok(pid) = contents.trim().parse::<usize>() {
106 return if Self::is_pid_active(pid) {
107 Some(pid)
108 } else {
109 let _ = self.remove_file();
110 None
111 };
112 }
113 }
114 None
115 }
116
117 pub fn is_locked(&self) -> bool {
120 self.get_locker_pid()
121 .map(|pid| pid != (std::process::id() as usize))
122 .unwrap_or_default()
123 }
124
125 pub fn lock(&self) -> io::Result<()> {
127 self.release()?;
128 if let Some(dir) = self.0.parent() {
129 create_dir_all(dir)?;
131 }
132
133 let mut fs = File::create(&self.0)?;
134 fs.write_all(std::process::id().to_string().as_bytes())?;
135 fs.sync_all()?;
136 fs.flush()?;
137 Ok(())
138 }
139}
140
141pub fn is_file_dirty<X: AsRef<Path>>(path: X) -> bool {
147 PidFileLocking::lsp(path.as_ref()).is_locked()
148}
149
150#[cfg(test)]
151mod test {
152 use super::PidFileLocking;
153 use std::{
154 fs::{metadata, File},
155 io::{ErrorKind, Write},
156 os::unix::fs::MetadataExt,
157 };
158
159 #[test]
160 fn test_fs_locking_same_process() {
161 let x = PidFileLocking::lsp("test");
162 assert!(!x.is_locked()); assert!(x.lock().is_ok());
164 let x = PidFileLocking::lsp("test");
166 assert!(!x.is_locked());
167 }
168
169 #[test]
170 fn test_legacy() {
171 let x = PidFileLocking::lsp("legacy");
173 assert!(x.lock().is_ok());
174 assert!(metadata(&x.0).is_ok());
176
177 let _ = File::create(&x.0).unwrap();
179 assert_eq!(metadata(&x.0).unwrap().size(), 0);
180
181 let x = PidFileLocking::lsp("legacy");
182 assert!(!x.is_locked());
183 }
184
185 #[test]
186 fn test_remove() {
187 let x = PidFileLocking::lsp("lock");
188 assert!(x.lock().is_ok());
189 assert!(x.release().is_ok());
190 assert!(x.release().is_ok());
191 }
192
193 #[test]
194 fn test_fs_locking_stale() {
195 let x = PidFileLocking::lsp("stale");
196 assert!(x.lock().is_ok());
197
198 assert!(metadata(&x.0).is_ok());
200
201 let mut x = File::create(&x.0).unwrap();
203 x.write_all(b"191919191919").unwrap();
204 x.flush().unwrap();
205 drop(x);
206
207 let x = PidFileLocking::lsp("stale");
209 assert!(!x.is_locked());
210 let e = metadata(&x.0).unwrap_err().kind();
211 assert_eq!(e, ErrorKind::NotFound);
212 }
213}