tool: fix atomic write
Atomic write works by first writing a temporary file, then syncing that temporary file to ensure it is fully on disk before the program can continue, and in the last step renaming the temporary file to the target. The middle step was missing, which is likely to lead to a truncated target file being present after power loss. Add this step. Furthermore, even with this fix, atomicity is not fully guaranteed, because FAT32 can become corrupted after power loss due to its design shortcomings. Even though we cannot really do anything about this case, adjust the comment to at least acknowledge the situation.
This commit is contained in:
parent
4fd37670e2
commit
90a1adac54
|
@ -465,17 +465,29 @@ fn assemble_kernel_cmdline(init: &Path, kernel_params: Vec<String>) -> Vec<Strin
|
||||||
|
|
||||||
/// Atomically copy a file.
|
/// Atomically copy a file.
|
||||||
///
|
///
|
||||||
/// The file is first written to the destination with a `.tmp` suffix and then renamed to its final
|
/// First, the content is written to a temporary file (with a `.tmp` extension).
|
||||||
/// name. This is atomic, because a rename is an atomic operation on POSIX platforms.
|
/// Then, this file is synced, to ensure its data and metadata are fully on disk before continuing.
|
||||||
|
/// In the last step, the temporary file is renamed to the final destination.
|
||||||
|
///
|
||||||
|
/// Due to the deficiencies of FAT32, it is possible for the filesystem to become corrupted after power loss.
|
||||||
|
/// It is not possible to fully defend against this situation, so this operation is not actually fully atomic.
|
||||||
|
/// However, in all other cases, the target file is either present with its correct content or not present at all.
|
||||||
fn atomic_copy(from: &Path, to: &Path) -> Result<()> {
|
fn atomic_copy(from: &Path, to: &Path) -> Result<()> {
|
||||||
let to_tmp = to.with_extension(".tmp");
|
let tmp = to.with_extension(".tmp");
|
||||||
|
{
|
||||||
fs::copy(from, &to_tmp)
|
let mut from_file =
|
||||||
.with_context(|| format!("Failed to copy from {from:?} to {to_tmp:?}",))?;
|
File::open(from).with_context(|| format!("Failed to read the source file {from:?}"))?;
|
||||||
|
let mut tmp_file = File::create(&tmp)
|
||||||
fs::rename(&to_tmp, to).with_context(|| {
|
.with_context(|| format!("Failed to create the temporary file {tmp:?}"))?;
|
||||||
format!("Failed to move temporary file {to_tmp:?} to final location {to:?}")
|
std::io::copy(&mut from_file, &mut tmp_file).with_context(|| {
|
||||||
})
|
format!("Failed to copy from {from:?} to the temporary file {tmp:?}")
|
||||||
|
})?;
|
||||||
|
tmp_file
|
||||||
|
.sync_all()
|
||||||
|
.with_context(|| format!("Failed to sync the temporary file {tmp:?}"))?;
|
||||||
|
}
|
||||||
|
fs::rename(&tmp, to)
|
||||||
|
.with_context(|| format!("Failed to move temporary file {tmp:?} to target {to:?}"))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set the octal permission bits of the specified file.
|
/// Set the octal permission bits of the specified file.
|
||||||
|
|
Loading…
Reference in New Issue