1use std::{
2 fmt::Display,
3 fs::{File, remove_file},
4 io::{BufReader, BufWriter, Write},
5 path::{Path, PathBuf},
6};
7
8use log::{debug, error};
9
10#[derive(Debug, Clone)]
11pub struct FileLock {
12 _lock_p: PathBuf,
13 inner_p: PathBuf,
14}
15
16fn pid_exists(pid: u32) -> bool {
17 Path::new(&format!("/proc/{pid}")).exists()
18}
19
20#[derive(Debug)]
21pub enum FileLockError {
22 PidExist(u32),
23 PidFileDoesntExist,
24 Unknown(String),
25}
26
27impl Display for FileLockError {
28 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
29 match self {
30 FileLockError::PidExist(pid) => {
31 write!(f, "Could not acquire lock (pid exists: {pid})")
32 }
33 FileLockError::PidFileDoesntExist => write!(
34 f,
35 "Lock exist but pid file doesn't! this is probably a bug."
36 ),
37 FileLockError::Unknown(e) => write!(f, "{e}"),
38 }
39 }
40}
41
42pub fn read_file(p: &PathBuf) -> anyhow::Result<BufReader<File>> {
43 let _inner = File::options().read(true).open(p)?;
44 let reader = BufReader::new(_inner);
45 Ok(reader)
46}
47
48impl FileLock {
49 pub fn get_path(&self) -> &PathBuf {
50 &self.inner_p
51 }
52 pub fn open<P: AsRef<Path>>(path: P) -> Result<FileLock, FileLockError> {
53 let _lock_p = path.as_ref().with_extension("lock");
54 let inner_p = path.as_ref().to_path_buf();
55 if Path::exists(&_lock_p) {
56 let pid = Self::read_pid(&path);
57
58 match pid {
59 Ok(pid) => {
60 if pid_exists(pid) {
61 error!("{pid} exist!");
62 return Err(FileLockError::PidExist(pid));
63 }
64 }
65 _ => {
66 return Err(FileLockError::PidFileDoesntExist);
67 }
68 }
69
70 let _ = {
72 let _ = FileLock {
73 _lock_p: _lock_p.clone(),
74 inner_p: inner_p.clone(),
75 };
76 Some(())
77 };
78 }
79 let _ = File::options()
81 .create(true)
82 .append(true)
83 .open(&path)
84 .map_err(|e| FileLockError::Unknown(e.to_string()))?;
85 let _ = File::create(&_lock_p)
86 .map_err(|e| FileLockError::Unknown(e.to_string()))?;
87 Self::write_pid(&path)
88 .map_err(|e| FileLockError::Unknown(e.to_string()))?;
89
90 std::fs::copy(&path, &_lock_p)
91 .map_err(|e| FileLockError::Unknown(e.to_string()))?;
92
93 Ok(FileLock { _lock_p, inner_p })
94 }
95
96 pub fn read(&self) -> anyhow::Result<BufReader<File>> {
97 read_file(&self.inner_p)
98 }
99
100 pub fn write(&self, buf: &[u8]) -> anyhow::Result<()> {
101 let _lock = File::create(&self._lock_p)?;
102 let mut writer = BufWriter::new(_lock);
103 writer.write_all(buf)?;
104 writer.flush()?;
105 Ok(())
106 }
107
108 fn write_pid<P: AsRef<Path>>(path: P) -> anyhow::Result<()> {
109 let pid_p = path.as_ref().with_extension("pid");
110 let pid_id = std::process::id();
111 std::fs::write(pid_p, pid_id.to_string().as_bytes())?;
112 Ok(())
113 }
114
115 fn read_pid<P: AsRef<Path>>(path: P) -> anyhow::Result<u32> {
116 let pid_p = path.as_ref().with_extension("pid");
117 let pid = std::fs::read_to_string(pid_p)?;
118 Ok(str::parse::<u32>(&pid)?)
119 }
120
121 pub fn flush(&self) -> anyhow::Result<()> {
122 debug!("flush file");
123 let swp = &self.inner_p.with_extension("swp");
124 let _ = File::create(swp)?;
125 let _ = File::options()
126 .write(true)
127 .create(true)
128 .truncate(true)
129 .open(&self.inner_p)
130 .unwrap();
131
132 std::fs::rename(&self.inner_p, swp)?;
133
134 std::fs::copy(&self._lock_p, &self.inner_p)
135 .map_err(|e| anyhow::format_err!("{e}"))?;
136
137 remove_file(swp)?;
138 Ok(())
139 }
140
141 fn cleanup_and_flush(&mut self) -> anyhow::Result<()> {
142 debug!("remove lock for {}", self._lock_p.as_path().to_string_lossy());
143
144 let pid = &self.inner_p.with_extension("pid");
145
146 self.flush()?;
147
148 remove_file(&self._lock_p)?;
149 remove_file(pid)?;
150
151 Ok(())
152 }
153}
154
155impl Drop for FileLock {
156 fn drop(&mut self) {
157 self.cleanup_and_flush().unwrap();
158 }
159}
160
161#[cfg(test)]
162mod test {
163 use std::io::BufRead;
164
165 use super::FileLock;
166
167 #[test]
168 fn test_lock() {
169 let path = "/tmp/db.json";
170 let file = FileLock::open(path).unwrap();
171 let text = "\ni wanna wanna way";
172
173 file.write(text.as_bytes()).unwrap();
174
175 let mut reader = file.read().unwrap();
176 let mut line = String::new();
177 let len = reader.read_line(&mut line).unwrap();
178 println!("First line is {len} bytes long");
179
180 let open_file_twice = FileLock::open(path);
181
182 if let Err(e) = open_file_twice {
183 assert!(
184 e.to_string()
185 .starts_with("Could not acquire lock (pid exists: ")
186 );
187 }
188 }
189}