1use std::fs::File;
9use std::fs::OpenOptions;
10use std::io;
11use std::path::Path;
12
13#[cfg(unix)]
14use once_cell::sync::Lazy;
15
16use crate::errors::IOContext;
17
18#[cfg(unix)]
19static UMASK: Lazy<u32> = Lazy::new(|| unsafe {
20 let umask = libc::umask(0);
21 libc::umask(umask);
22 #[allow(clippy::useless_conversion)] umask.into()
24});
25
26#[cfg(unix)]
27pub fn apply_umask(mode: u32) -> u32 {
28 mode & !*UMASK
29}
30
31pub fn atomic_write(path: &Path, op: impl FnOnce(&mut File) -> io::Result<()>) -> io::Result<File> {
32 atomicfile::atomic_write(path, 0o644, false, op).path_context("error atomic writing file", path)
33}
34
35pub fn atomic_open(path: &Path) -> io::Result<atomicfile::AtomicFile> {
37 atomicfile::AtomicFile::open(path, 0o644, false).path_context("error atomic opening file", path)
38}
39
40pub fn open(path: impl AsRef<Path>, mode: &str) -> io::Result<File> {
41 let path = path.as_ref();
42
43 let mut opts = OpenOptions::new();
44 for opt in mode.chars() {
45 match opt {
46 'r' => opts.read(true),
47 'w' => opts.write(true),
48 'a' => opts.append(true),
49 'c' => opts.create(true),
50 't' => opts.truncate(true),
51 'x' => opts.create_new(true),
52 _ => {
53 return Err(io::Error::new(
54 io::ErrorKind::Other,
55 format!("invalid open() mode {}", opt),
56 ))
57 .path_context("error opening file", path);
58 }
59 };
60 }
61
62 opts.open(path).path_context("error opening file", path)
63}
64
65pub fn create(path: impl AsRef<Path>) -> io::Result<File> {
66 open(path, "wct")
67}
68
69pub fn exists(path: impl AsRef<Path>) -> io::Result<Option<std::fs::Metadata>> {
70 match std::fs::metadata(path.as_ref()) {
71 Ok(m) => Ok(Some(m)),
72 Err(err) if err.kind() == io::ErrorKind::NotFound => Ok(None),
73 Err(err) => Err(err).path_context("error reading file", path.as_ref()),
74 }
75}
76
77pub fn unlink_if_exists(path: impl AsRef<Path>) -> io::Result<()> {
78 match std::fs::remove_file(path.as_ref()) {
79 Ok(()) => Ok(()),
80 Err(err) if err.kind() == io::ErrorKind::NotFound => Ok(()),
81 Err(err) => Err(err).path_context("error deleting file", path.as_ref()),
82 }
83}
84
85pub fn read_to_string_if_exists(path: impl AsRef<Path>) -> io::Result<Option<String>> {
86 match std::fs::read_to_string(path.as_ref()) {
87 Ok(contents) => Ok(Some(contents)),
88 Err(err) if err.kind() == io::ErrorKind::NotFound => Ok(None),
89 Err(err) => Err(err).path_context("error reading file", path.as_ref()),
90 }
91}
92
93#[cfg(test)]
94mod test {
95 use anyhow::Result;
96 use tempfile::tempdir;
97
98 use super::*;
99
100 #[test]
101 fn test_open_context() -> Result<()> {
102 let dir = tempdir()?;
103
104 let path = dir.path().join("doesnt").join("exist");
105 let err_str = format!("{}", open(&path, "cwa").unwrap_err());
106
107 assert!(err_str.contains(path.display().to_string().as_str()));
109
110 let orig_err = format!("{}", std::fs::File::open(&path).unwrap_err());
112 assert!(err_str.contains(&orig_err));
113
114 Ok(())
115 }
116}