Merge pull request #83 from nix-community/atomic-writes

tool: atomically write to ESP
This commit is contained in:
nikstur 2023-01-29 16:17:07 +01:00 committed by GitHub
commit 57b56e104c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
1 changed files with 41 additions and 18 deletions

View File

@ -143,7 +143,7 @@ impl Installer {
println!("Appending secrets to initrd..."); println!("Appending secrets to initrd...");
let initrd_location = tempdir.path().join("initrd"); let initrd_location = tempdir.path().join("initrd");
copy( fs::copy(
bootspec bootspec
.initrd .initrd
.as_ref() .as_ref()
@ -241,25 +241,39 @@ fn install_signed(key_pair: &KeyPair, from: &Path, to: &Path) -> Result<()> {
/// Sign and forcibly install a PE file. /// Sign and forcibly install a PE file.
/// ///
/// If the file already exists at the destination, it is overwritten. /// If the file already exists at the destination, it is overwritten.
///
/// This is implemented as an atomic write. The file is first written to the destination with a
/// `.tmp` suffix and then renamed to its final name. This is atomic, because a rename is an atomic
/// operation on POSIX platforms.
fn force_install_signed(key_pair: &KeyPair, from: &Path, to: &Path) -> Result<()> { fn force_install_signed(key_pair: &KeyPair, from: &Path, to: &Path) -> Result<()> {
println!("Signing and installing {}...", to.display()); println!("Signing and installing {}...", to.display());
ensure_parent_dir(to); let to_tmp = to.with_extension(".tmp");
ensure_parent_dir(&to_tmp);
key_pair key_pair
.sign_and_copy(from, to) .sign_and_copy(from, &to_tmp)
.with_context(|| format!("Failed to copy and sign file from {from:?} to {to:?}"))?; .with_context(|| format!("Failed to copy and sign file from {from:?} to {to:?}"))?;
fs::rename(&to_tmp, to).with_context(|| {
format!("Failed to move temporary file {to_tmp:?} to final location {to:?}")
})?;
Ok(()) Ok(())
} }
/// Install an arbitrary file /// Install an arbitrary file.
/// ///
/// The file is only copied if it doesn't exist at the destination /// The file is only copied if it doesn't exist at the destination.
///
/// This function is only designed to copy files to the ESP. It sets the permission bits of the
/// file at the destination to 0o755, the expected permissions for a vfat ESP. This is useful for
/// producing file systems trees which can then be converted to a file system image.
fn install(from: &Path, to: &Path) -> Result<()> { fn install(from: &Path, to: &Path) -> Result<()> {
if to.exists() { if to.exists() {
println!("{} already exists, skipping...", to.display()); println!("{} already exists, skipping...", to.display());
} else { } else {
println!("Installing {}...", to.display()); println!("Installing {}...", to.display());
ensure_parent_dir(to); ensure_parent_dir(to);
copy(from, to)?; atomic_copy(from, to)?;
set_permission_bits(to, 0o755)
.with_context(|| format!("Failed to set permission bits to 0o755 on file: {to:?}"))?;
} }
Ok(()) Ok(())
@ -293,20 +307,29 @@ fn assemble_kernel_cmdline(init: &Path, kernel_params: Vec<String>) -> Vec<Strin
kernel_cmdline kernel_cmdline
} }
fn copy(from: &Path, to: &Path) -> Result<()> { /// Atomically copy a file.
ensure_parent_dir(to); ///
fs::copy(from, to) /// The file is first written to the destination with a `.tmp` suffix and then renamed to its final
.with_context(|| format!("Failed to copy from {} to {}", from.display(), to.display()))?; /// name. This is atomic, because a rename is an atomic operation on POSIX platforms.
fn atomic_copy(from: &Path, to: &Path) -> Result<()> {
let to_tmp = to.with_extension(".tmp");
// Set permission of all files copied to 0o755 fs::copy(from, &to_tmp)
let mut perms = fs::metadata(to) .with_context(|| format!("Failed to copy from {from:?} to {to_tmp:?}",))?;
.with_context(|| format!("File {} doesn't have metadata", to.display()))?
fs::rename(&to_tmp, to).with_context(|| {
format!("Failed to move temporary file {to_tmp:?} to final location {to:?}")
})
}
/// Set the octal permission bits of the specified file.
fn set_permission_bits(path: &Path, permission_bits: u32) -> Result<()> {
let mut perms = fs::metadata(path)
.with_context(|| format!("File {path:?} doesn't have any metadata"))?
.permissions(); .permissions();
perms.set_mode(0o755); perms.set_mode(permission_bits);
fs::set_permissions(to, perms) fs::set_permissions(path, perms)
.with_context(|| format!("Failed to set permissions to: {}", to.display()))?; .with_context(|| format!("Failed to set permissions on {path:?}"))
Ok(())
} }
// Ensures the parent directory of an arbitrary path exists // Ensures the parent directory of an arbitrary path exists