aboutsummaryrefslogtreecommitdiffstats
path: root/src/files.rs
diff options
context:
space:
mode:
authortv <tv@krebsco.de>2026-11-01 05:32:36 +0100
committertv <tv@krebsco.de>2026-11-01 05:32:36 +0100
commitd264f2df01c59bf4627b496290a553aa376369ef (patch)
treeb0830b6a5b730223f4ef92ad8e4df863e60bc87d /src/files.rs
parent9f8feeab71ccb864606c336097e20dbb8e1d8c86 (diff)
allow creating all key files
Diffstat (limited to 'src/files.rs')
-rw-r--r--src/files.rs51
1 files changed, 51 insertions, 0 deletions
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<u8>, 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) {