1use std::fs;
2use std::io;
3use std::path::{Path, PathBuf};
4
5pub struct FileLock {
8 lock_path: PathBuf,
9 #[cfg(unix)]
10 _file: fs::File,
11}
12
13impl FileLock {
14 pub fn acquire(path: &Path) -> io::Result<Self> {
18 let mut lock_name = path.file_name().unwrap_or_default().to_os_string();
19 lock_name.push(".purple_lock");
20 let lock_path = path.with_file_name(lock_name);
21
22 #[cfg(unix)]
23 {
24 use std::os::unix::fs::OpenOptionsExt;
25 let file = fs::OpenOptions::new()
26 .write(true)
27 .create(true)
28 .truncate(false)
29 .mode(0o600)
30 .open(&lock_path)?;
31
32 let ret =
34 unsafe { libc::flock(std::os::unix::io::AsRawFd::as_raw_fd(&file), libc::LOCK_EX) };
35 if ret != 0 {
36 return Err(io::Error::last_os_error());
37 }
38
39 Ok(FileLock {
40 lock_path,
41 _file: file,
42 })
43 }
44
45 #[cfg(not(unix))]
46 {
47 let file = fs::OpenOptions::new()
49 .write(true)
50 .create_new(true)
51 .open(&lock_path)
52 .or_else(|_| {
53 std::thread::sleep(std::time::Duration::from_millis(100));
55 fs::remove_file(&lock_path).ok();
56 fs::OpenOptions::new()
57 .write(true)
58 .create_new(true)
59 .open(&lock_path)
60 })?;
61 Ok(FileLock {
62 lock_path,
63 _file: file,
64 })
65 }
66 }
67}
68
69impl Drop for FileLock {
70 fn drop(&mut self) {
71 let _ = fs::remove_file(&self.lock_path);
74 }
75}
76
77pub fn atomic_write(path: &Path, content: &[u8]) -> io::Result<()> {
81 if let Some(parent) = path.parent() {
83 fs::create_dir_all(parent)?;
84 }
85
86 let mut tmp_name = path.file_name().unwrap_or_default().to_os_string();
87 tmp_name.push(format!(".purple_tmp.{}", std::process::id()));
88 let tmp_path = path.with_file_name(tmp_name);
89
90 #[cfg(unix)]
91 {
92 use std::io::Write;
93 use std::os::unix::fs::OpenOptionsExt;
94 let open = || {
97 fs::OpenOptions::new()
98 .write(true)
99 .create_new(true)
100 .mode(0o600)
101 .open(&tmp_path)
102 };
103 let mut file = match open() {
104 Ok(f) => f,
105 Err(e) if e.kind() == io::ErrorKind::AlreadyExists => {
106 let _ = fs::remove_file(&tmp_path);
107 open().map_err(|e| {
108 io::Error::new(
109 e.kind(),
110 format!("Failed to create temp file {}: {}", tmp_path.display(), e),
111 )
112 })?
113 }
114 Err(e) => {
115 return Err(io::Error::new(
116 e.kind(),
117 format!("Failed to create temp file {}: {}", tmp_path.display(), e),
118 ));
119 }
120 };
121 if let Err(e) = file.write_all(content) {
122 drop(file);
123 let _ = fs::remove_file(&tmp_path);
124 return Err(e);
125 }
126 if let Err(e) = file.sync_all() {
127 drop(file);
128 let _ = fs::remove_file(&tmp_path);
129 return Err(e);
130 }
131 }
132
133 #[cfg(not(unix))]
134 {
135 if let Err(e) = fs::write(&tmp_path, content) {
136 let _ = fs::remove_file(&tmp_path);
137 return Err(e);
138 }
139 match fs::File::open(&tmp_path) {
141 Ok(f) => {
142 if let Err(e) = f.sync_all() {
143 let _ = fs::remove_file(&tmp_path);
144 return Err(e);
145 }
146 }
147 Err(e) => {
148 let _ = fs::remove_file(&tmp_path);
149 return Err(e);
150 }
151 }
152 }
153
154 let result = fs::rename(&tmp_path, path);
155 if result.is_err() {
156 let _ = fs::remove_file(&tmp_path);
157 }
158 result
159}