diff --git a/src/pages.rs b/src/pages.rs index d8ec3cf4..19c35034 100644 --- a/src/pages.rs +++ b/src/pages.rs @@ -9,7 +9,6 @@ use std::{ collections::{HashMap, HashSet}, - fs::write, path::{Path, PathBuf}, sync::mpsc::RecvTimeoutError, }; @@ -33,7 +32,7 @@ use crate::{ msg::{RunId, RunResult}, notif::NotificationReceiver, run::{Run, RunState, Whence}, - util::{parse_timestamp, rfc822_timestamp}, + util::{parse_timestamp, rfc822_timestamp, safely_overwrite}, worker::Worker, }; @@ -53,7 +52,7 @@ pub enum PageError { Timeformat(#[from] time::error::Format), #[error("failed to write status page to {0}")] - Write(PathBuf, #[source] std::io::Error), + Write(PathBuf, #[source] crate::util::UtilError), #[error("no node alias has been set for builder")] NoAlias, @@ -972,7 +971,8 @@ impl StatusPage { } fn write_file(filename: &Path, text: &str) -> Result<(), PageError> { - write(filename, text).map_err(|e| PageError::Write(filename.into(), e))?; + safely_overwrite(filename, text.as_bytes()) + .map_err(|err| PageError::Write(filename.into(), err))?; Ok(()) } } diff --git a/src/util.rs b/src/util.rs index ec35a9be..1842f85c 100644 --- a/src/util.rs +++ b/src/util.rs @@ -1,8 +1,10 @@ use std::{ + io::Write, path::{Path, PathBuf}, str::FromStr, }; +use tempfile::NamedTempFile; use time::{ format_description::{well_known::Rfc2822, FormatItem}, macros::format_description, @@ -138,6 +140,20 @@ pub fn read_file_as_objectid(filename: &Path) -> Result { ObjectId::from_str(s.trim()).map_err(|err| UtilError::ReadObjectId(filename.into(), err)) } +pub fn safely_overwrite>(filename: P, data: &[u8]) -> Result<(), UtilError> { + let filename = filename.as_ref(); + let dirname = filename + .parent() + .ok_or(UtilError::NoParent(filename.to_path_buf()))?; + let mut tmp = NamedTempFile::new_in(dirname) + .map_err(|err| UtilError::CreateTemp(dirname.to_path_buf(), err))?; + tmp.write_all(data) + .map_err(|err| UtilError::WriteTemp(dirname.to_path_buf(), err))?; + tmp.persist(filename) + .map_err(|err| UtilError::RenameTemp(filename.to_path_buf(), err))?; + Ok(()) +} + #[derive(Debug, thiserror::Error)] pub enum UtilError { #[error("failed to look up node profile")] @@ -178,4 +194,16 @@ pub enum UtilError { #[error("failed to read object id from {0}")] ReadObjectId(PathBuf, #[source] radicle::cob::object::ParseObjectId), + + #[error("file name to write to doesn't have a parent directory: {0}")] + NoParent(PathBuf), + + #[error("failed to create temporary file in directory {0}")] + CreateTemp(PathBuf, #[source] std::io::Error), + + #[error("failed to write to temporary file in {0}")] + WriteTemp(PathBuf, #[source] std::io::Error), + + #[error("failed to rename temporary file to {0}")] + RenameTemp(PathBuf, #[source] tempfile::PersistError), }