1#![forbid(unsafe_code)]
27
28#![deny(
29 clippy::allow_attributes_without_reason,
30 clippy::correctness,
31 unreachable_pub,
32)]
33
34#![warn(
35 clippy::complexity,
36 clippy::nursery,
37 clippy::pedantic,
38 clippy::perf,
39 clippy::style,
40
41 clippy::allow_attributes,
42 clippy::clone_on_ref_ptr,
43 clippy::create_dir,
44 clippy::filetype_is_file,
45 clippy::format_push_string,
46 clippy::get_unwrap,
47 clippy::impl_trait_in_params,
48 clippy::lossy_float_literal,
49 clippy::missing_assert_message,
50 clippy::missing_docs_in_private_items,
51 clippy::needless_raw_strings,
52 clippy::panic_in_result_fn,
53 clippy::pub_without_shorthand,
54 clippy::rest_pat_in_fully_bound_structs,
55 clippy::semicolon_inside_block,
56 clippy::str_to_string,
57 clippy::string_to_string,
58 clippy::todo,
59 clippy::undocumented_unsafe_blocks,
60 clippy::unneeded_field_pattern,
61 clippy::unseparated_literal_suffix,
62 clippy::unwrap_in_result,
63
64 macro_use_extern_crate,
65 missing_copy_implementations,
66 missing_docs,
67 non_ascii_idents,
68 trivial_casts,
69 trivial_numeric_casts,
70 unused_crate_dependencies,
71 unused_extern_crates,
72 unused_import_braces,
73)]
74
75
76
77use std::{
78 fs::File,
79 io::{
80 Error,
81 ErrorKind,
82 Result,
83 Write,
84 },
85 path::{
86 Path,
87 PathBuf,
88 },
89};
90use tempfile::NamedTempFile;
91
92
93
94pub fn copy_file<P>(src: P, dst: P) -> Result<()>
109where P: AsRef<Path> {
110 let src = src.as_ref();
111 let (dst, parent) = check_path(dst)?;
112
113 let file = tempfile::Builder::new().tempfile_in(parent)?;
114 std::fs::copy(src, &file)?;
115
116 let touched = touch_if(&dst)?;
117 if let Err(e) = write_finish(file, &dst) {
118 if touched { let _res = std::fs::remove_file(dst); }
120 Err(e)
121 }
122 else { Ok(()) }
123}
124
125pub fn write_file<P>(src: P, data: &[u8]) -> Result<()>
144where P: AsRef<Path> {
145 let (dst, parent) = check_path(src)?;
146
147 let mut file = tempfile::Builder::new().tempfile_in(parent)?;
148 file.write_all(data)?;
149 file.flush()?;
150
151 let touched = touch_if(&dst)?;
152 if let Err(e) = write_finish(file, &dst) {
153 if touched { let _res = std::fs::remove_file(dst); }
155 Err(e)
156 }
157 else { Ok(()) }
158}
159
160
161
162fn check_path<P>(src: P) -> Result<(PathBuf, PathBuf)>
167where P: AsRef<Path> {
168 let src = src.as_ref();
169
170 if src.is_dir() {
172 return Err(Error::new(ErrorKind::InvalidInput, "Path cannot be a directory."));
173 }
174
175 let src: PathBuf =
178 if src.is_absolute() { src.to_path_buf() }
179 else {
180 let mut absolute = std::env::current_dir()?;
181 absolute.push(src);
182 absolute
183 };
184
185 let parent: PathBuf = src.parent()
187 .map(Path::to_path_buf)
188 .ok_or_else(|| Error::new(ErrorKind::NotFound, "Path must have a parent directory."))?;
189
190 std::fs::create_dir_all(&parent)?;
192
193 Ok((src, parent))
195}
196
197fn copy_metadata(src: &Path, dst: &File) -> Result<()> {
202 let metadata = match src.metadata() {
203 Ok(metadata) => metadata,
204 Err(ref e) if ErrorKind::NotFound == e.kind() => return Ok(()),
205 Err(e) => return Err(e),
206 };
207
208 dst.set_permissions(metadata.permissions())?;
209
210 #[cfg(unix)]
211 copy_ownership(&metadata, dst)?;
212
213 Ok(())
214}
215
216#[cfg(unix)]
217fn copy_ownership(src: &std::fs::Metadata, dst: &File) -> Result<()> {
221 use std::os::unix::fs::MetadataExt;
222 std::os::unix::fs::fchown(dst, Some(src.uid()), Some(src.gid()))
223}
224
225fn touch_if(src: &Path) -> Result<bool> {
230 if src.exists() { Ok(false) }
231 else {
232 File::create(src)?;
233 Ok(true)
234 }
235}
236
237fn write_finish(file: NamedTempFile, dst: &Path) -> Result<()> {
241 copy_metadata(dst, file.as_file())
242 .and_then(|()| file.persist(dst).map(|_| ()).map_err(|e| e.error))
243}
244
245
246
247#[cfg(test)]
248mod tests {
249 use super::*;
250
251 #[test]
252 fn test_write() {
253 let mut path = std::env::temp_dir();
256 if ! path.is_dir() { return; }
257 path.push("write-atomic-test.txt");
258
259 assert!(write_file(&path, b"This is the first write!").is_ok());
261
262 assert_eq!(
264 std::fs::read(&path).expect("Unable to open file."),
265 b"This is the first write!",
266 );
267
268 assert!(write_file(&path, b"This is the second write!").is_ok());
270
271 assert_eq!(
273 std::fs::read(&path).expect("Unable to open file."),
274 b"This is the second write!",
275 );
276
277 let path2 = path.parent()
279 .expect("Missing parent?!")
280 .join("copy-atomic-test.txt");
281 assert!(copy_file(&path, &path2).is_ok());
282 assert_eq!(
283 std::fs::read(&path2).expect("Unable to open file."),
284 b"This is the second write!",
285 );
286
287 let _res = std::fs::remove_file(path);
289 let _res = std::fs::remove_file(path2);
290 }
291}