diff --git a/crates/backend/src/local.rs b/crates/backend/src/local.rs index b4d0a8f5..d05bb2bb 100644 --- a/crates/backend/src/local.rs +++ b/crates/backend/src/local.rs @@ -90,6 +90,13 @@ impl LocalBackend { } } + fn filename(tpe: FileType, id: &Id) -> String { + match tpe { + FileType::Config => "config".to_string(), + _ => id.to_hex().to_string(), + } + } + /// Path to the given file type and id. /// /// If the file type is `FileType::Pack`, the id will be used to determine the subdirectory. @@ -103,11 +110,7 @@ impl LocalBackend { /// /// The path to the file. fn path(&self, tpe: FileType, id: &Id) -> PathBuf { - let hex_id = id.to_hex(); - match tpe { - FileType::Config => self.path.join("config"), - _ => self.base_path(tpe, id).join(hex_id), - } + self.base_path(tpe, id).join(Self::filename(tpe, id)) } /// Call the given command. @@ -469,70 +472,97 @@ impl WriteBackend for LocalBackend { _cacheable: bool, buf: Bytes, ) -> RusticResult<()> { - trace!("writing tpe: {:?}, id: {}", &tpe, &id); - let filename = self.path(tpe, id); - - let parent = self.base_path(tpe, id); + fn write_local_file(filename: &Path, buf: &[u8]) -> RusticResult<()> { + let length = buf.len().try_into().map_err(|err| { + RusticError::with_source( + ErrorKind::Internal, + "Failed to convert length `{length}` to u64.", + err, + ) + .attach_context("length", buf.len().to_string()) + .ask_report() + })?; - // create parent directory if it does not exist - fs::create_dir_all(&parent).map_err(|err| { - RusticError::with_source( - ErrorKind::InputOutput, - "Failed to create directories `{path}`. Does the directory already exist? Please check the file and try again.", - err, - ) - .attach_context("path", parent.display().to_string()) - .ask_report() - })?; + let mut file = fs::OpenOptions::new() + .create(true) + .truncate(true) + .write(true) + .open(filename) + .map_err(|err| { + RusticError::with_source( + ErrorKind::InputOutput, + "Failed to open the file `{path}`. Please check the file and try again.", + err, + ) + .attach_context("path", filename.to_string_lossy()) + })?; + + file.set_len(length) + .map_err(|err| { + RusticError::with_source( + ErrorKind::InputOutput, + "Failed to set the length of the file `{path}`. Please check the file and try again.", + err, + ) + .attach_context("path", filename.to_string_lossy()) + })?; - let mut file = fs::OpenOptions::new() - .create(true) - .truncate(true) - .write(true) - .open(&filename) - .map_err(|err| { + file.write_all(buf).map_err(|err| { + RusticError::with_source( + ErrorKind::InputOutput, + "Failed to write to the buffer: `{path}`. Please check the file and try again.", + err, + ) + .attach_context("path", filename.to_string_lossy()) + })?; + file.sync_all().map_err(|err| { RusticError::with_source( ErrorKind::InputOutput, - "Failed to open the file `{path}`. Please check the file and try again.", + "Failed to sync OS Metadata to disk: `{path}`. Please check the file and try again.", err, ) .attach_context("path", filename.to_string_lossy()) })?; + Ok(()) + } - file.set_len(buf.len().try_into().map_err(|err| { - RusticError::with_source( - ErrorKind::Internal, - "Failed to convert length `{length}` to u64.", - err, - ) - .attach_context("length", buf.len().to_string()) - .ask_report() - })?) - .map_err(|err| { - RusticError::with_source( - ErrorKind::InputOutput, - "Failed to set the length of the file `{path}`. Please check the file and try again.", - err, - ) - .attach_context("path", filename.to_string_lossy()) - })?; + trace!("writing tpe: {:?}, id: {}", &tpe, &id); + let filename = self.path(tpe, id); + + let parent = self.base_path(tpe, id); - file.write_all(&buf).map_err(|err| { + // create parent directory if it does not exist + fs::create_dir_all(&parent) + .map_err(|err| { RusticError::with_source( ErrorKind::InputOutput, - "Failed to write to the buffer: `{path}`. Please check the file and try again.", + "Failed to create directories `{path}`. Does the directory already exist? Please check the file and try again.", err, ) - .attach_context("path", filename.to_string_lossy()) + .attach_context("path", parent.display().to_string()) + .ask_report() })?; - file.sync_all().map_err(|err| { + // Write to temporary file + let filename_tmp = parent.join(Self::filename(tpe, id) + "-tmp-"); + match write_local_file(&filename_tmp, &buf) { + Ok(file) => file, + Err(err) => { + // Clean-up in case of error + _ = fs::remove_file(&filename_tmp); + return Err(err); + } + } + // rename temporary file to real file + fs::rename(&filename_tmp, &filename).map_err(|err| { RusticError::with_source( ErrorKind::InputOutput, - "Failed to sync OS Metadata to disk: `{path}`. Please check the file and try again.", + "Failed to move `{path_tmp}` to `{path}`", err, ) - .attach_context("path", filename.to_string_lossy()) + .attach_context("path_tmp", filename_tmp.display().to_string()) + .attach_context("path", filename.display().to_string()) + .ask_report() })?; if let Some(command) = &self.post_create_command diff --git a/crates/core/src/backend/cache.rs b/crates/core/src/backend/cache.rs index 3052e3ce..736ed2a9 100644 --- a/crates/core/src/backend/cache.rs +++ b/crates/core/src/backend/cache.rs @@ -2,7 +2,7 @@ use std::{ collections::HashMap, fs::{self, File}, io::{self, Read, Seek, SeekFrom, Write}, - path::PathBuf, + path::{Path, PathBuf}, sync::Arc, }; @@ -538,10 +538,37 @@ impl Cache { /// /// * If the file could not be written. pub fn write_bytes(&self, tpe: FileType, id: &Id, buf: &Bytes) -> RusticResult<()> { + fn write_local_file(filename: &Path, buf: &[u8]) -> RusticResult<()> { + let mut file = fs::OpenOptions::new() + .create(true) + .truncate(true) + .write(true) + .open(filename) + .map_err(|err| { + RusticError::with_source( + ErrorKind::InputOutput, + "Failed to open the file `{path}`.", + err, + ) + .attach_context("path", filename.to_string_lossy()) + })?; + + file.write_all(buf).map_err(|err| { + RusticError::with_source( + ErrorKind::InputOutput, + "Failed to write to the buffer: `{path}`.", + err, + ) + .attach_context("path", filename.to_string_lossy()) + })?; + Ok(()) + } + trace!("cache writing tpe: {:?}, id: {}", &tpe, &id); let dir = self.dir(tpe, id); + // create parent directory if it does not exist fs::create_dir_all(&dir).map_err(|err| { RusticError::with_source( ErrorKind::InputOutput, @@ -554,30 +581,25 @@ impl Cache { })?; let filename = self.path(tpe, id); - - let mut file = fs::OpenOptions::new() - .create(true) - .truncate(true) - .write(true) - .open(&filename) - .map_err(|err| { - RusticError::with_source( - ErrorKind::InputOutput, - "Failed to open file at `{path}`", - err, - ) - .attach_context("path", filename.display().to_string()) - })?; - - file.write_all(buf).map_err(|err| { + let filename_tmp = dir.join(id.to_hex().to_string() + "-tmp-"); + match write_local_file(&filename_tmp, buf) { + Ok(file) => file, + Err(err) => { + // Clean-up in case of error + _ = fs::remove_file(&filename_tmp); + return Err(err); + } + } + // rename temporary file to real file + fs::rename(&filename_tmp, &filename).map_err(|err| { RusticError::with_source( ErrorKind::InputOutput, - "Failed to write to buffer at `{path}`", + "Failed to move `{path_tmp}` to `{path}`", err, ) + .attach_context("path_tmp", filename_tmp.display().to_string()) .attach_context("path", filename.display().to_string()) - .attach_context("tpe", tpe.to_string()) - .attach_context("id", id.to_string()) + .ask_report() })?; Ok(())