evobench_tools/io_utils/
tempfile_utils.rs1use std::{
4 fs::File,
5 io::{BufWriter, Write},
6 os::unix::fs::{MetadataExt, PermissionsExt},
7 path::{Path, PathBuf},
8};
9
10use cj_path_util::path_util::AppendToPath;
11use nix::{
12 errno::Errno,
13 unistd::{Gid, Uid, chown, getpid},
14};
15
16use crate::info;
17
18#[derive(Debug, thiserror::Error)]
19pub enum TempfileError {
20 #[error("path is missing parent directory part")]
21 MissingParent,
22 #[error("path is missing file name part")]
23 MissingFileName,
24 #[error("IO error while {0}: {1:#}")]
25 IOError(&'static str, std::io::Error),
26 #[error("IO error while {0}: {1:#}")]
27 IOErrno(&'static str, Errno),
28}
29
30pub fn temp_path(target_path: impl AsRef<Path>) -> Result<PathBuf, TempfileError> {
32 let target_path = target_path.as_ref();
33 let dir = target_path
34 .parent()
35 .ok_or_else(|| TempfileError::MissingParent)?;
36 let file_name = target_path
37 .file_name()
38 .ok_or_else(|| TempfileError::MissingFileName)?;
39 let mut file_name: Vec<u8> = file_name.to_string_lossy().to_string().into();
40 let pid = getpid();
41 let tid = std::thread::current().id();
42 write!(&mut file_name, ".tmp~{pid}-{tid:?}").expect("nofail: no IO");
43 let file_name =
44 String::from_utf8(file_name).expect("nofail: was a string, with strings appended");
45 Ok(dir.append(file_name))
46}
47
48#[derive(Debug, Clone)]
54pub struct TempfileOptions {
55 pub target_path: PathBuf,
56 pub retain_tempfile: bool,
57 pub migrate_access: bool,
58}
59
60impl TempfileOptions {
61 pub fn tempfile(self) -> Result<Tempfile, TempfileError> {
62 Tempfile::try_from(self)
63 }
64}
65
66#[derive(Debug)]
67pub struct Tempfile {
68 pub opts: TempfileOptions,
69 pub temp_path: PathBuf,
70}
71
72impl TryFrom<TempfileOptions> for Tempfile {
73 type Error = TempfileError;
74
75 fn try_from(opts: TempfileOptions) -> Result<Self, TempfileError> {
76 let temp_path = temp_path(&opts.target_path)?;
77 Ok(Tempfile { opts, temp_path })
78 }
79}
80
81impl Tempfile {
82 pub fn temp_path(&self) -> &Path {
83 &self.temp_path
84 }
85
86 pub fn target_path(&self) -> &Path {
87 &self.opts.target_path
88 }
89
90 pub fn finish(mut self) -> Result<(), TempfileError> {
91 self.opts.retain_tempfile = true; let Self {
93 opts:
94 TempfileOptions {
95 ref target_path,
96 retain_tempfile: _,
97 migrate_access,
98 },
99 ref temp_path,
100 } = self;
101 let meta = if migrate_access {
102 match target_path.metadata() {
103 Ok(m) => Some(m),
104 Err(e) => match e.kind() {
105 std::io::ErrorKind::NotFound => None,
106 _ => return Err(TempfileError::IOError("getting metadata on target file", e)),
107 },
108 }
109 } else {
110 None
111 };
112 if let Some(meta) = meta {
113 let uid = meta.uid();
116 let gid = meta.gid();
117 chown(
118 temp_path.into(),
119 Some(Uid::from_raw(uid)),
120 Some(Gid::from_raw(gid)),
121 )
122 .map_err(|e| TempfileError::IOErrno("copying owner/group to new file", e))?;
123
124 let perms = meta.permissions();
125 let mode = perms.mode();
126 std::fs::set_permissions(&temp_path, std::fs::Permissions::from_mode(mode))
128 .map_err(|e| TempfileError::IOError("copying permissions to new file", e))?;
129 }
130 std::fs::rename(temp_path, target_path)
131 .map_err(|e| TempfileError::IOError("renaming to target", e))?;
132 Ok(())
133 }
134}
135
136impl Drop for Tempfile {
137 fn drop(&mut self) {
138 let Self {
139 opts:
140 TempfileOptions {
141 target_path: _,
142 retain_tempfile,
143 migrate_access: _,
144 },
145 temp_path,
146 } = self;
147 if !*retain_tempfile {
148 match std::fs::remove_file(&*temp_path) {
149 Ok(()) => (),
150 Err(e) => match e.kind() {
151 std::io::ErrorKind::NotFound => (),
152 _ => info!("error deleting temporary file {:?}: {e:#}", temp_path),
153 },
154 }
155 }
156 }
157}
158
159pub fn tempfile(
162 target_path: PathBuf,
163 migrate_access: bool,
164) -> Result<(Tempfile, BufWriter<File>), TempfileError> {
165 let tmp_file = TempfileOptions {
166 target_path,
167 retain_tempfile: false,
168 migrate_access,
169 }
170 .tempfile()?;
171 let temp_path = &tmp_file.temp_path;
172 let out = BufWriter::new(
173 File::create(temp_path)
174 .map_err(|e| TempfileError::IOError("creating temporary file", e))?,
175 );
176 Ok((tmp_file, out))
177}
178
179pub struct TempfileWithFlush {
181 pub tempfile: Tempfile,
182 pub file: File,
183}