1use std::fs::{self, File, OpenOptions};
6use std::io::{self, Write};
7use std::path::{Path, PathBuf};
8
9pub fn log_path() -> io::Result<PathBuf> {
14 directories::ProjectDirs::from("", "", "apeirogon")
15 .map(|dirs| dirs.data_local_dir().join("apeirogond.log"))
16 .ok_or_else(|| io::Error::new(io::ErrorKind::NotFound, "no home directory found"))
17}
18
19pub fn append_user_only(path: &Path) -> io::Result<File> {
24 if let Some(parent) = path.parent() {
25 fs::create_dir_all(parent)?;
26 }
27 let mut options = OpenOptions::new();
28 options.create(true).append(true);
29 #[cfg(unix)]
30 {
31 use std::os::unix::fs::{OpenOptionsExt, PermissionsExt};
32 options.mode(USER_ONLY_MODE);
33 let file = options.open(path)?;
34 file.set_permissions(fs::Permissions::from_mode(USER_ONLY_MODE))?;
35 Ok(file)
36 }
37 #[cfg(not(unix))]
38 options.open(path)
39}
40
41#[cfg(unix)]
43const USER_ONLY_MODE: u32 = 0o600;
44
45pub fn write_user_only(path: &Path, bytes: &[u8]) -> io::Result<()> {
51 let mut temp_name = path.file_name().unwrap_or_default().to_os_string();
52 temp_name.push(".tmp");
53 let temp_path = path.with_file_name(temp_name);
54
55 let result = write_temp(&temp_path, bytes).and_then(|()| fs::rename(&temp_path, path));
56 if result.is_err() {
57 let _ = fs::remove_file(&temp_path);
59 }
60 result
61}
62
63fn write_temp(temp_path: &Path, bytes: &[u8]) -> io::Result<()> {
64 let mut file = create_user_only(temp_path)?;
65 file.write_all(bytes)?;
66 file.sync_all()
67}
68
69#[cfg(unix)]
70fn create_user_only(path: &Path) -> io::Result<File> {
71 use std::os::unix::fs::OpenOptionsExt;
72 OpenOptions::new()
73 .write(true)
74 .create(true)
75 .truncate(true)
76 .mode(USER_ONLY_MODE)
77 .open(path)
78}
79
80#[cfg(not(unix))]
83fn create_user_only(path: &Path) -> io::Result<File> {
84 OpenOptions::new()
85 .write(true)
86 .create(true)
87 .truncate(true)
88 .open(path)
89}
90
91#[cfg(all(test, unix))]
92mod tests {
93 use super::*;
94 use std::os::unix::fs::PermissionsExt;
95
96 #[test]
97 fn write_user_only_new_file_is_user_only() {
98 let dir = tempfile::tempdir().unwrap();
100 let path = dir.path().join("secret");
101
102 write_user_only(&path, b"key").unwrap();
104
105 let mode = fs::metadata(&path).unwrap().permissions().mode();
107 assert_eq!(mode & 0o777, USER_ONLY_MODE);
108 }
109}
110
111#[cfg(unix)]
116pub fn same_file(left: &File, right: &File) -> io::Result<bool> {
117 use std::os::unix::fs::MetadataExt;
118 let (left, right) = (left.metadata()?, right.metadata()?);
119 Ok(left.dev() == right.dev() && left.ino() == right.ino())
120}
121
122#[cfg(windows)]
127pub fn same_file(left: &File, right: &File) -> io::Result<bool> {
128 use std::os::windows::io::AsRawHandle;
129 use windows_sys::Win32::Storage::FileSystem::{
130 BY_HANDLE_FILE_INFORMATION, GetFileInformationByHandle,
131 };
132 fn identity(file: &File) -> io::Result<(u32, u32, u32)> {
133 let mut info = BY_HANDLE_FILE_INFORMATION::default();
134 if unsafe { GetFileInformationByHandle(file.as_raw_handle().cast(), &raw mut info) } == 0 {
136 return Err(io::Error::last_os_error());
137 }
138 Ok((
139 info.dwVolumeSerialNumber,
140 info.nFileIndexHigh,
141 info.nFileIndexLow,
142 ))
143 }
144 Ok(identity(left)? == identity(right)?)
145}