gardesk/garwarp / 0e63cfd

Browse files

write request store atomically

Authored by mfwolffe <wolffemf@dukes.jmu.edu>
SHA
0e63cfd7996482462630d83ab891589dc413725d
Parents
78232a1
Tree
ee716d3

1 changed file

StatusFile+-
M garwarp/src/request_store.rs 70 2
garwarp/src/request_store.rsmodified
@@ -3,8 +3,10 @@
33
 use std::collections::HashMap;
44
 use std::fs;
55
 use std::io;
6
+#[cfg(unix)]
7
+use std::os::unix::fs::PermissionsExt;
68
 use std::path::Path;
7
-use std::time::{Duration, Instant};
9
+use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
810
 
911
 use crate::request::{RequestOwner, RequestRecord, RequestRegistry, RequestState};
1012
 use crate::window::parse_optional_parent_window;
@@ -45,7 +47,41 @@ pub fn persist_registry(path: &Path, registry: &RequestRegistry) -> io::Result<(
4547
         output.push_str(&format_record_line(&record));
4648
         output.push('\n');
4749
     }
48
-    fs::write(path, output)
50
+    atomic_write(path, output.as_bytes())
51
+}
52
+
53
+fn atomic_write(path: &Path, data: &[u8]) -> io::Result<()> {
54
+    if let Some(parent) = path.parent() {
55
+        fs::create_dir_all(parent)?;
56
+    }
57
+
58
+    let temp_path = unique_temp_path(path);
59
+    let mut file = fs::OpenOptions::new()
60
+        .create_new(true)
61
+        .write(true)
62
+        .open(&temp_path)?;
63
+    #[cfg(unix)]
64
+    {
65
+        file.set_permissions(fs::Permissions::from_mode(0o600))?;
66
+    }
67
+    io::Write::write_all(&mut file, data)?;
68
+    file.sync_all()?;
69
+    drop(file);
70
+
71
+    fs::rename(&temp_path, path)?;
72
+    Ok(())
73
+}
74
+
75
+fn unique_temp_path(path: &Path) -> std::path::PathBuf {
76
+    let nanos = SystemTime::now()
77
+        .duration_since(UNIX_EPOCH)
78
+        .map_or(0, |duration| duration.as_nanos());
79
+    let parent = path.parent().unwrap_or_else(|| Path::new("."));
80
+    let file_name = path
81
+        .file_name()
82
+        .and_then(|name| name.to_str())
83
+        .unwrap_or("state");
84
+    parent.join(format!(".{file_name}.tmp-{}-{nanos}", std::process::id()))
4985
 }
5086
 
5187
 fn format_record_line(record: &RequestRecord) -> String {
@@ -176,4 +212,36 @@ mod tests {
176212
 
177213
         let _ = fs::remove_file(path);
178214
     }
215
+
216
+    #[test]
217
+    fn persist_overwrites_previous_contents() {
218
+        let path = unique_temp_file();
219
+        let mut first = RequestRegistry::new(Duration::from_secs(5));
220
+        first
221
+            .begin_at(
222
+                "req-first",
223
+                RequestOwner::new(":1.2", None),
224
+                None,
225
+                Instant::now(),
226
+            )
227
+            .expect("request should be created");
228
+        persist_registry(&path, &first).expect("first persist should succeed");
229
+
230
+        let mut second = RequestRegistry::new(Duration::from_secs(5));
231
+        second
232
+            .begin_at(
233
+                "req-second",
234
+                RequestOwner::new(":1.3", Some("org.test.App".to_string())),
235
+                None,
236
+                Instant::now(),
237
+            )
238
+            .expect("request should be created");
239
+        persist_registry(&path, &second).expect("second persist should succeed");
240
+
241
+        let loaded = load_registry(&path, Duration::from_secs(5)).expect("registry should load");
242
+        assert_eq!(loaded.state("req-first"), None);
243
+        assert_eq!(loaded.state("req-second"), Some(RequestState::Pending));
244
+
245
+        let _ = fs::remove_file(path);
246
+    }
179247
 }