From ac81f5a68bf3259d1e0c49c93b2f84635b873824 Mon Sep 17 00:00:00 2001 From: tv Date: Sun, 1 Nov 2026 05:32:36 +0100 Subject: allow creating all key files --- src/files.rs | 51 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) (limited to 'src/files.rs') diff --git a/src/files.rs b/src/files.rs index cd2f38c..bc8fb8e 100644 --- a/src/files.rs +++ b/src/files.rs @@ -167,6 +167,57 @@ fn sync_parent(path: &Path) { } } +/// A key file's bytes: read from `path`; or with `create`, if there is no file there, +/// `len` random bytes in a new one only its owner may read (then also `true`). Without +/// `create`, a missing file is an error saying to give `create_option` (`what`: which +/// key; `hint`: what a new one means). Never created where a file is: a mistyped path +/// stays an error unless asked. +pub(crate) fn key_file( + path: &Path, + what: &str, + create: bool, + create_option: &str, + hint: &str, + len: usize, +) -> anyhow::Result<(Vec, bool)> { + use anyhow::Context; + use std::io::Write; + match std::fs::read(path) { + Ok(bytes) => { + warn_if_readable_by_others(path, what); + Ok((bytes, false)) + } + Err(error) if error.kind() == std::io::ErrorKind::NotFound => { + if !create { + anyhow::bail!( + "The {what} file {} doesn't exist; add --{create_option} to create a new {what} there ({hint})", + path.display() + ); + } + let mut bytes = vec![0u8; len]; + getrandom::fill(&mut bytes) + .map_err(|error| anyhow::anyhow!("No randomness: {error}"))?; + let mut options = std::fs::OpenOptions::new(); + options.write(true).create_new(true); + #[cfg(unix)] + { + use std::os::unix::fs::OpenOptionsExt; + options.mode(0o600); + } + let written = (|| { + let mut file = options.open(path)?; + file.write_all(&bytes)?; + file.sync_all() + })(); + written.with_context(|| format!("Creating the {what} file {}", path.display()))?; + Ok((bytes, true)) + } + Err(error) => { + Err(error).with_context(|| format!("Reading the {what} from {}", path.display())) + } + } +} + /// A key is a secret: a file others may read is worth a warning (not an error: a /// group may be deliberate, e.g. shared with a deployment tool). `what`: which key. pub(crate) fn warn_if_readable_by_others(path: &Path, what: &str) { -- cgit v1.3.1