1use std::fs::{File, OpenOptions};
4use std::io::Write;
5use std::path::Path;
6use std::sync::atomic::{AtomicU64, Ordering};
7
8use crate::error::{Error, Result};
9
10static TMP_COUNTER: AtomicU64 = AtomicU64::new(0);
14
15pub fn write_atomic(path: &Path, bytes: &[u8]) -> Result<()> {
19 write_atomic_inner(path, bytes, true)
20}
21
22pub fn write_atomic_no_create(path: &Path, bytes: &[u8]) -> Result<()> {
29 write_atomic_inner(path, bytes, false)
30}
31
32fn write_atomic_inner(path: &Path, bytes: &[u8], create_parent: bool) -> Result<()> {
33 let dir = path.parent().ok_or_else(|| {
34 Error::IoBare(std::io::Error::new(
35 std::io::ErrorKind::InvalidInput,
36 format!("path {} has no parent directory", path.display()),
37 ))
38 })?;
39 if create_parent {
40 std::fs::create_dir_all(dir).map_err(|e| Error::io(dir, e))?;
41 }
42 let fname = path
43 .file_name()
44 .ok_or_else(|| {
45 Error::IoBare(std::io::Error::new(
46 std::io::ErrorKind::InvalidInput,
47 format!("path {} has no file name", path.display()),
48 ))
49 })?
50 .to_string_lossy();
51 let seq = TMP_COUNTER.fetch_add(1, Ordering::Relaxed);
52 let tmp = dir.join(format!(".{fname}.tmp.{}.{seq}", std::process::id()));
53 {
54 let mut opts = OpenOptions::new();
55 opts.create_new(true).write(true);
56 crate::paths::nofollow(&mut opts);
59 let mut f = opts.open(&tmp).map_err(|e| Error::io(&tmp, e))?;
60 f.write_all(bytes).map_err(|e| Error::io(&tmp, e))?;
61 f.sync_all().map_err(|e| Error::io(&tmp, e))?;
62 }
63 std::fs::rename(&tmp, path).map_err(|e| Error::io(path, e))?;
64 if let Ok(dir_file) = File::open(dir) {
68 let _ = dir_file.sync_all();
69 }
70 Ok(())
71}
72
73pub fn write_json_atomic<T: serde::Serialize>(path: &Path, value: &T) -> Result<()> {
75 let bytes = serde_json::to_vec_pretty(value).map_err(|e| Error::json(path, e))?;
76 write_atomic(path, &bytes)
77}
78
79pub fn write_json_atomic_no_create<T: serde::Serialize>(path: &Path, value: &T) -> Result<()> {
82 let bytes = serde_json::to_vec_pretty(value).map_err(|e| Error::json(path, e))?;
83 write_atomic_no_create(path, &bytes)
84}
85
86pub fn open_events_append(path: &Path) -> Result<File> {
93 if let Some(p) = path.parent() {
94 std::fs::create_dir_all(p).map_err(|e| Error::io(p, e))?;
95 }
96 let mut opts = OpenOptions::new();
97 opts.create(true).append(true);
98 crate::paths::nofollow(&mut opts);
99 opts.open(path).map_err(|e| Error::io(path, e))
100}