Create symbolic links atomically.

- When creating a symbolic link and the link already exists, do it
    atomically.  This prevents two problems. 1) If we fail between
    deleting the old link and creating the new link, then we have
    nothing.  2) If a reader accesses the link between unlinking the
    old version and creating the new version, it sees nothing.
This commit is contained in:
Neal H. Walfield 2019-02-22 20:26:10 +01:00
parent eb71b8a655
commit dfbb4ec553
1 changed files with 27 additions and 31 deletions

View File

@ -1,6 +1,5 @@
use std::fs::{create_dir_all, read_link, remove_file, File};
use std::fs::{create_dir_all, read_link, remove_file, rename, File};
use std::io::{Read, Write};
use std::os::unix::fs::symlink;
use std::path::{Path, PathBuf};
use std::str;
@ -145,6 +144,23 @@ impl Filesystem {
}
}
// Like `symlink`, but instead of failing if `symlink_name` already
// exists, atomically update `symlink_name` to have `symlink_content`.
fn symlink(symlink_content: &Path, symlink_name: &Path) -> Result<()> {
use std::os::unix::fs::{symlink};
let symlink_dir = ensure_parent(symlink_name)?.parent().unwrap();
let tmp_dir = tempfile::Builder::new()
.prefix("link")
.rand_bytes(16)
.tempdir_in(symlink_dir)?;
let symlink_name_tmp = tmp_dir.path().join("link");
symlink(&symlink_content, &symlink_name_tmp)?;
rename(&symlink_name_tmp, &symlink_name)?;
Ok(())
}
impl Database for Filesystem {
fn new_verify_token(&self, payload: Verify) -> Result<String> {
let (mut fd, name) = self.new_token("verification_tokens")?;
@ -207,17 +223,11 @@ impl Database for Filesystem {
let target = diff_paths(&self.path_to_fingerprint(fpr),
link.parent().unwrap()).unwrap();
if link.exists() {
match link.symlink_metadata() {
Ok(ref meta) if meta.file_type().is_symlink() => {
remove_file(link.clone())?;
}
_ => {}
}
if link == target {
return Ok(());
}
symlink(target, ensure_parent(&link)?)?;
Ok(())
symlink(&target, ensure_parent(&link)?)
}
fn unlink_email(&self, email: &Email, fpr: &Fingerprint) -> Result<()> {
@ -237,6 +247,7 @@ impl Database for Filesystem {
}
Err(_) => {}
}
Ok(())
}
@ -245,17 +256,11 @@ impl Database for Filesystem {
let target = diff_paths(&self.path_to_fingerprint(fpr),
link.parent().unwrap()).unwrap();
if link.exists() {
match link.symlink_metadata() {
Ok(ref meta) if meta.file_type().is_symlink() => {
remove_file(link.clone())?;
}
_ => {}
}
if link == target {
return Ok(());
}
symlink(target, ensure_parent(&link)?)?;
Ok(())
symlink(&target, ensure_parent(&link)?)
}
fn unlink_kid(&self, kid: &KeyID, fpr: &Fingerprint) -> Result<()> {
@ -271,6 +276,7 @@ impl Database for Filesystem {
}
Err(_) => {}
}
Ok(())
}
@ -283,17 +289,7 @@ impl Database for Filesystem {
let target = diff_paths(&self.path_to_fingerprint(fpr),
link.parent().unwrap()).unwrap();
if link.exists() {
match link.symlink_metadata() {
Ok(ref meta) if meta.file_type().is_symlink() => {
remove_file(link.clone())?;
}
_ => {}
}
}
symlink(target, ensure_parent(&link)?)?;
Ok(())
symlink(&target, ensure_parent(&link)?)
}
fn unlink_fpr(&self, from: &Fingerprint, fpr: &Fingerprint) -> Result<()> {