mirror of
https://github.com/sigp/lighthouse.git
synced 2026-04-16 20:39:10 +00:00
Write validator definitions atomically (#2338)
## Issue Addressed Closes https://github.com/sigp/lighthouse/issues/2159 ## Proposed Changes Rather than trying to write the validator definitions to disk directly, use a temporary file called `.validator_defintions.yml.tmp` and then atomically rename it to `validator_definitions.yml`. This avoids truncating the primary file, which can cause permanent damage when the disk is full. The same treatment is also applied to the validator key cache, although the situation is less dire if it becomes corrupted because it can just be deleted without the user having to reimport keys or resupply passwords. ## Additional Info * `File::create` truncates upon opening: https://doc.rust-lang.org/std/fs/struct.File.html#method.create * `fs::rename` uses `rename` on UNIX and `MoveFileEx` on Windows: https://doc.rust-lang.org/std/fs/fn.rename.html * UNIX `rename` call is atomic: https://unix.stackexchange.com/questions/322038/is-mv-atomic-on-my-fs * Windows `MoveFileEx` is _not_ atomic in general, and Windows lacks any clear API for atomic file renames :( https://stackoverflow.com/questions/167414/is-an-atomic-file-rename-with-overwrite-possible-on-windows ## Further Work * Consider whether we want to try a different Windows syscall as part of #2333. The `rust-atomicwrites` crate seems promising, but actually uses the same syscall under the hood presently: https://github.com/untitaker/rust-atomicwrites/issues/27.
This commit is contained in:
@@ -76,6 +76,32 @@ pub fn create_with_600_perms<P: AsRef<Path>>(path: P, bytes: &[u8]) -> Result<()
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Write a file atomically by using a temporary file as an intermediate.
|
||||
///
|
||||
/// Care is taken to preserve the permissions of the file at `file_path` being written.
|
||||
///
|
||||
/// If no file exists at `file_path` one will be created with restricted 0o600-equivalent
|
||||
/// permissions.
|
||||
pub fn write_file_via_temporary(
|
||||
file_path: &Path,
|
||||
temp_path: &Path,
|
||||
bytes: &[u8],
|
||||
) -> Result<(), io::Error> {
|
||||
// If the file already exists, preserve its permissions by copying it.
|
||||
// Otherwise, create a new file with restricted permissions.
|
||||
if file_path.exists() {
|
||||
fs::copy(&file_path, &temp_path)?;
|
||||
fs::write(&temp_path, &bytes)?;
|
||||
} else {
|
||||
create_with_600_perms(&temp_path, &bytes)?;
|
||||
}
|
||||
|
||||
// With the temporary file created, perform an atomic rename.
|
||||
fs::rename(&temp_path, &file_path)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Generates a random alphanumeric password of length `DEFAULT_PASSWORD_LEN`.
|
||||
pub fn random_password() -> PlainText {
|
||||
rand::thread_rng()
|
||||
|
||||
Reference in New Issue
Block a user