1use {
4 std::{
5 io,
6 mem::forget,
7 num::ParseIntError,
8 path::{
9 Path,
10 PathBuf,
11 },
12 sync::Arc,
13 thread,
14 time::Duration,
15 },
16 sysinfo::{
17 Pid,
18 ProcessRefreshKind,
19 ProcessesToUpdate,
20 },
21 thiserror::Error,
22 tokio::{
23 fs,
24 time::sleep,
25 },
26};
27
28#[must_use = "should call the drop_async method to unlock"]
44pub struct DirLock(PathBuf);
45
46#[derive(Debug, Error, Clone)]
48#[allow(missing_docs)]
49pub enum Error {
50 #[error("I/O error{}: {}", if let Some(path) = .1 { format!(" at {}", path.display()) } else { String::default() }, .0)] Io(#[source] Arc<io::Error>, Option<PathBuf>),
51 #[error(transparent)] ParseInt(#[from] ParseIntError),
52}
53
54trait IoResultExt {
55 type T;
56
57 fn at(self, path: impl AsRef<Path>) -> Self::T;
58}
59
60impl IoResultExt for io::Error {
61 type T = Error;
62
63 fn at(self, path: impl AsRef<Path>) -> Error {
64 Error::Io(Arc::new(self), Some(path.as_ref().to_owned()))
65 }
66}
67
68impl<T, E: IoResultExt> IoResultExt for Result<T, E> {
69 type T = Result<T, E::T>;
70
71 fn at(self, path: impl AsRef<Path>) -> Result<T, E::T> {
72 self.map_err(|e| e.at(path))
73 }
74}
75
76impl DirLock {
77 pub async fn new(path: impl AsRef<Path>) -> Result<Self, Error> {
81 let path = path.as_ref().to_owned();
82 loop {
83 match fs::create_dir(&path).await {
84 Ok(()) => {
85 let pidfile = path.join("pid");
86 fs::write(&pidfile, format!("{}\n", std::process::id())).await.at(pidfile)?;
87 return Ok(Self(path))
88 }
89 Err(e) => match e.kind() {
90 io::ErrorKind::AlreadyExists => {
91 let pidfile = path.join("pid");
92 if match fs::read_to_string(&pidfile).await {
93 Ok(buf) => {
94 !buf.is_empty() && !pid_exists(buf.trim().parse()?)
96 }
97 Err(e) => if e.kind() == io::ErrorKind::NotFound {
98 false
99 } else {
100 return Err(e.at(path.join("pid")))
101 },
102 } {
103 clean_up_path(&path).await?;
104 }
105 sleep(Duration::from_secs(1)).await;
106 continue
107 }
108 _ => return Err(e.at(path)),
109 },
110 }
111 }
112 }
113
114 pub fn new_sync(path: &impl AsRef<Path>) -> Result<Self, Error> {
116 let path = path.as_ref().to_owned();
117 loop {
118 match std::fs::create_dir(&path) {
119 Ok(()) => {
120 let pidfile = path.join("pid");
121 std::fs::write(&pidfile, format!("{}\n", std::process::id())).at(pidfile)?;
122 return Ok(Self(path))
123 }
124 Err(e) => match e.kind() {
125 io::ErrorKind::AlreadyExists => {
126 let pidfile = path.join("pid");
127 if match std::fs::read_to_string(&pidfile) {
128 Ok(buf) => {
129 !buf.is_empty() && !pid_exists(buf.trim().parse()?)
131 }
132 Err(e) => if e.kind() == io::ErrorKind::NotFound {
133 false
134 } else {
135 return Err(e.at(path.join("pid")))
136 },
137 } {
138 clean_up_path_sync(&path)?;
139 }
140 thread::sleep(Duration::from_secs(1));
141 continue
142 }
143 _ => return Err(e.at(path)),
144 },
145 }
146 }
147 }
148
149 pub fn path(&self) -> &Path {
151 self.0.as_path()
152 }
153
154 pub async fn drop_async(self) -> Result<(), Error> {
156 self.clean_up().await?;
157 forget(self);
158 Ok(())
159 }
160
161 async fn clean_up(&self) -> Result<(), Error> {
162 clean_up_path(&self.0).await
163 }
164
165 fn clean_up_sync(&self) -> Result<(), Error> {
166 clean_up_path_sync(&self.0)
167 }
168}
169
170impl Drop for DirLock {
171 fn drop(&mut self) {
178 self.clean_up_sync().expect("failed to clean up dir lock");
179 }
180}
181
182async fn clean_up_path(path: &Path) -> Result<(), Error> {
183 if let Err(e) = fs::remove_file(path.join("pid")).await {
184 if e.kind() != io::ErrorKind::NotFound {
185 return Err(e.at(path.join("pid")));
186 }
187 }
188 if let Err(e) = fs::remove_dir(path).await {
189 if e.kind() != io::ErrorKind::NotFound {
190 return Err(e.at(path))
191 }
192 }
193 Ok(())
194}
195
196fn clean_up_path_sync(path: &Path) -> Result<(), Error> {
197 if let Err(e) = std::fs::remove_file(path.join("pid")) {
198 if e.kind() != io::ErrorKind::NotFound {
199 return Err(e.at(path.join("pid")))
200 }
201 }
202 if let Err(e) = std::fs::remove_dir(path) {
203 if e.kind() != io::ErrorKind::NotFound {
204 return Err(e.at(path))
205 }
206 }
207 Ok(())
208}
209
210fn pid_exists(pid: Pid) -> bool {
211 let mut system = sysinfo::System::default();
212 system.refresh_processes_specifics(ProcessesToUpdate::Some(&[pid]), true, ProcessRefreshKind::default()) > 0
213}