Skip to content

Commit 594a7e3

Browse files
committed
Prevent lockfile creation when project folder is missing
1 parent 24b02f1 commit 594a7e3

File tree

1 file changed

+81
-19
lines changed

1 file changed

+81
-19
lines changed

rewatch/src/lock.rs

Lines changed: 81 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ pub enum Error {
1616
ParsingLockfile(std::num::ParseIntError),
1717
ReadingLockfile(std::io::Error),
1818
WritingLockfile(std::io::Error),
19+
ProjectFolderMissing(std::path::PathBuf),
1920
}
2021

2122
impl std::fmt::Display for Error {
@@ -31,6 +32,10 @@ impl std::fmt::Display for Error {
3132
format!("Could not read lockfile: \n {e} \n (try removing it and running the command again)")
3233
}
3334
Error::WritingLockfile(e) => format!("Could not write lockfile: \n {e}"),
35+
Error::ProjectFolderMissing(path) => format!(
36+
"Could not write lockfile because the specified project folder does not exist: {}",
37+
path.to_string_lossy()
38+
),
3439
};
3540
write!(f, "{msg}")
3641
}
@@ -41,35 +46,92 @@ pub enum Lock {
4146
Error(Error),
4247
}
4348

44-
fn exists(to_check_pid: u32) -> bool {
49+
fn pid_exists(to_check_pid: u32) -> bool {
4550
System::new_all()
4651
.processes()
4752
.iter()
4853
.any(|(pid, _process)| pid.as_u32() == to_check_pid)
4954
}
5055

51-
fn create(lockfile_location: &Path, pid: u32) -> Lock {
52-
// Create /lib if not exists
53-
if let Some(Err(e)) = lockfile_location.parent().map(fs::create_dir_all) {
54-
return Lock::Error(Error::WritingLockfile(e));
55-
};
56-
57-
File::create(lockfile_location)
58-
.and_then(|mut file| file.write(pid.to_string().as_bytes()).map(|_| Lock::Aquired(pid)))
59-
.unwrap_or_else(|e| Lock::Error(Error::WritingLockfile(e)))
60-
}
61-
6256
pub fn get(folder: &str) -> Lock {
63-
let location = Path::new(folder).join("lib").join(LOCKFILE);
57+
let project_folder = Path::new(folder);
58+
if !project_folder.exists() {
59+
return Lock::Error(Error::ProjectFolderMissing(project_folder.to_path_buf()));
60+
}
61+
62+
// `lib` sits directly under the provided project folder; compute it once so later code stays simple.
63+
let lib_dir = project_folder.join("lib");
64+
let location = lib_dir.join(LOCKFILE);
6465
let pid = process::id();
6566

67+
// When a lockfile already exists we parse its PID: if the process is still alive we refuse to
68+
// proceed, otherwise we will overwrite the stale lock with our own PID.
6669
match fs::read_to_string(&location) {
67-
Err(e) if (e.kind() == std::io::ErrorKind::NotFound) => create(&location, pid),
68-
Err(e) => Lock::Error(Error::ReadingLockfile(e)),
69-
Ok(s) => match s.parse::<u32>() {
70-
Ok(parsed_pid) if !exists(parsed_pid) => create(&location, pid),
71-
Ok(parsed_pid) => Lock::Error(Error::Locked(parsed_pid)),
72-
Err(e) => Lock::Error(Error::ParsingLockfile(e)),
70+
Ok(contents) => match contents.parse::<u32>() {
71+
Ok(parsed_pid) if pid_exists(parsed_pid) => return Lock::Error(Error::Locked(parsed_pid)),
72+
Ok(_) => (),
73+
Err(e) => return Lock::Error(Error::ParsingLockfile(e)),
74+
},
75+
Err(e) if e.kind() == std::io::ErrorKind::NotFound => (),
76+
Err(e) => return Lock::Error(Error::ReadingLockfile(e)),
77+
}
78+
79+
if let Err(e) = fs::create_dir_all(&lib_dir) {
80+
return Lock::Error(Error::WritingLockfile(e));
81+
}
82+
83+
// Rewrite the lockfile with just our PID.
84+
match File::create(&location) {
85+
Ok(mut file) => match file.write(pid.to_string().as_bytes()) {
86+
Ok(_) => Lock::Aquired(pid),
87+
Err(e) => Lock::Error(Error::WritingLockfile(e)),
7388
},
89+
Err(e) => Lock::Error(Error::WritingLockfile(e)),
90+
}
91+
}
92+
93+
#[cfg(test)]
94+
mod tests {
95+
use super::*;
96+
use std::fs;
97+
use tempfile::TempDir;
98+
99+
#[test]
100+
fn returns_error_when_project_folder_missing() {
101+
let temp_dir = TempDir::new().expect("temp dir should be created");
102+
let missing_folder = temp_dir.path().join("missing_project");
103+
104+
match get(missing_folder.to_str().expect("path should be valid")) {
105+
Lock::Error(Error::ProjectFolderMissing(path)) => {
106+
assert_eq!(path, missing_folder);
107+
}
108+
_ => panic!("expected ProjectFolderMissing error"),
109+
}
110+
111+
assert!(
112+
!missing_folder.exists(),
113+
"missing project folder should not be created"
114+
);
115+
}
116+
117+
#[test]
118+
fn creates_lock_when_project_folder_exists() {
119+
let temp_dir = TempDir::new().expect("temp dir should be created");
120+
let project_folder = temp_dir.path().join("project");
121+
fs::create_dir(&project_folder).expect("project folder should be created");
122+
123+
match get(project_folder.to_str().expect("path should be valid")) {
124+
Lock::Aquired(_) => {}
125+
_ => panic!("expected lock to be acquired"),
126+
}
127+
128+
assert!(
129+
project_folder.join("lib").exists(),
130+
"lib directory should be created"
131+
);
132+
assert!(
133+
project_folder.join("lib").join(LOCKFILE).exists(),
134+
"lockfile should be created"
135+
);
74136
}
75137
}

0 commit comments

Comments
 (0)