write_file: Do not overwrite with same content

The `write_file` function will no longer overwrite existing files if the
content has not changed.  This will ensure file metadata timestamps are
accurate, and reduce unnecessary filesystem activity.
master
Dustin 2024-01-18 19:46:25 -06:00
parent e5403dbc66
commit 1099fa40c7
3 changed files with 41 additions and 10 deletions

7
Cargo.lock generated
View File

@ -300,6 +300,12 @@ version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
[[package]]
name = "hex"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
[[package]] [[package]]
name = "humansize" name = "humansize"
version = "2.1.3" version = "2.1.3"
@ -923,6 +929,7 @@ dependencies = [
"argparse", "argparse",
"blake2", "blake2",
"file-mode", "file-mode",
"hex",
"pwd-grp", "pwd-grp",
"serde", "serde",
"serde_yaml", "serde_yaml",

View File

@ -9,6 +9,7 @@ edition = "2021"
argparse = "0.2.2" argparse = "0.2.2"
blake2 = "0.10.6" blake2 = "0.10.6"
file-mode = { version = "0.1.2", features = ["serde"] } file-mode = { version = "0.1.2", features = ["serde"] }
hex = "0.4.3"
pwd-grp = "0.1.1" pwd-grp = "0.1.1"
serde = { version = "1.0.195", features = ["derive"] } serde = { version = "1.0.195", features = ["derive"] }
serde_yaml = "0.9.30" serde_yaml = "0.9.30"

View File

@ -12,7 +12,7 @@ use serde::de::DeserializeOwned;
use serde_yaml::Value; use serde_yaml::Value;
use shlex::Shlex; use shlex::Shlex;
use tera::{Context, Tera}; use tera::{Context, Tera};
use tracing::{debug, error, info, warn}; use tracing::{debug, error, info, trace, warn};
use model::Instructions; use model::Instructions;
@ -161,13 +161,18 @@ fn process_instructions(
}; };
let mut dest = PathBuf::from(destdir.as_ref()); let mut dest = PathBuf::from(destdir.as_ref());
dest.push(i.dest.strip_prefix("/").unwrap_or(i.dest.as_path())); dest.push(i.dest.strip_prefix("/").unwrap_or(i.dest.as_path()));
let orig_cksm = checksum(&dest).ok(); let changed = match write_file(&dest, out.as_bytes()) {
if let Err(e) = write_file(&dest, out.as_bytes()) { Ok(c) => c,
error!("Failed to write output file {}: {}", dest.display(), e); Err(e) => {
error!(
"Failed to write output file {}: {}",
dest.display(),
e
);
continue; continue;
} }
let new_cksm = checksum(&dest).ok(); };
if orig_cksm != new_cksm { if changed {
info!("File {} was changed", dest.display()); info!("File {} was changed", dest.display());
if let Some(hooks) = i.hooks { if let Some(hooks) = i.hooks {
if let Some(changed) = hooks.changed { if let Some(changed) = hooks.changed {
@ -219,19 +224,37 @@ fn process_instructions(
fn write_file( fn write_file(
dest: impl AsRef<Path>, dest: impl AsRef<Path>,
data: &[u8], data: &[u8],
) -> Result<(), std::io::Error> { ) -> Result<bool, std::io::Error> {
if let Some(p) = dest.as_ref().parent() { if let Some(p) = dest.as_ref().parent() {
if !p.exists() { if !p.exists() {
info!("Creating directory {}", p.display()); info!("Creating directory {}", p.display());
std::fs::create_dir_all(p)?; std::fs::create_dir_all(p)?;
} }
} }
if let Ok(orig_cksm) = checksum(&dest) {
trace!(
"Original checksum: {}: {}",
dest.as_ref().display(),
hex::encode(&orig_cksm)
);
let mut blake = Blake2b512::new();
blake.update(data);
let new_cksm = blake.finalize().to_vec();
trace!(
"New checksum: {}: {}",
dest.as_ref().display(),
hex::encode(&new_cksm)
);
if orig_cksm == new_cksm {
return Ok(false);
}
}
debug!("Writing output: {}", dest.as_ref().display()); debug!("Writing output: {}", dest.as_ref().display());
let mut f = std::fs::File::create(&dest)?; let mut f = std::fs::File::create(&dest)?;
f.write_all(data)?; f.write_all(data)?;
let size = f.stream_position()?; let size = f.stream_position()?;
debug!("Wrote output: {} ({} bytes)", dest.as_ref().display(), size); debug!("Wrote output: {} ({} bytes)", dest.as_ref().display(), size);
Ok(()) Ok(true)
} }
fn chown( fn chown(