use std::path::{Path, PathBuf};
use crate::encryption::EncryptionInput;
use crate::{Emdb, EmdbBuilder, Error, Result};
type NamespaceSnapshot = (String, Vec<(Vec<u8>, Vec<u8>)>);
fn open_with_mode(path: &Path, mode: Option<&EncryptionInput>) -> Result<Emdb> {
let mut builder = EmdbBuilder::new().path(path.to_path_buf());
match mode {
None => {}
Some(EncryptionInput::Key(k)) => {
builder = builder.encryption_key(*k);
}
Some(EncryptionInput::Passphrase(p)) => {
builder = builder.encryption_passphrase(p.clone());
}
}
builder.build()
}
fn temp_path_for(path: &Path) -> PathBuf {
let mut out = path.to_path_buf();
let original_name = path
.file_name()
.and_then(|name| name.to_str())
.unwrap_or("emdb");
out.set_file_name(format!("{original_name}.enc.tmp"));
out
}
fn backup_path_for(path: &Path) -> PathBuf {
let mut out = path.to_path_buf();
let original_name = path
.file_name()
.and_then(|name| name.to_str())
.unwrap_or("emdb");
out.set_file_name(format!("{original_name}.encbak"));
out
}
fn remove_sidecars(path: &Path) {
let display = path.display().to_string();
let _ = std::fs::remove_file(format!("{display}.lock"));
}
pub(crate) fn rewrite_database(
path: &Path,
from: Option<&EncryptionInput>,
to: Option<&EncryptionInput>,
) -> Result<()> {
if !path.exists() {
return Err(Error::InvalidConfig(
"encryption admin: source database file does not exist",
));
}
let tmp = temp_path_for(path);
let bak = backup_path_for(path);
let _ = std::fs::remove_file(&tmp);
remove_sidecars(&tmp);
let src = open_with_mode(path, from)?;
src.flush()?;
let default_records: Vec<(Vec<u8>, Vec<u8>)> = src.iter()?.collect();
let named_namespaces: Vec<String> = src
.list_namespaces()?
.into_iter()
.filter(|n| !n.is_empty())
.collect();
let mut named_records: Vec<NamespaceSnapshot> = Vec::with_capacity(named_namespaces.len());
for ns_name in &named_namespaces {
let ns = src.namespace(ns_name)?;
let pairs: Vec<(Vec<u8>, Vec<u8>)> = ns.iter()?.collect();
named_records.push((ns_name.clone(), pairs));
}
drop(src);
let dst = match open_with_mode(&tmp, to) {
Ok(d) => d,
Err(err) => {
let _ = std::fs::remove_file(&tmp);
remove_sidecars(&tmp);
return Err(err);
}
};
let copy_result = (|| -> Result<()> {
for (k, v) in default_records {
dst.insert(k, v)?;
}
for (ns_name, pairs) in named_records {
let ns = dst.namespace(&ns_name)?;
for (k, v) in pairs {
ns.insert(k, v)?;
}
}
dst.flush()
})();
drop(dst);
if let Err(err) = copy_result {
let _ = std::fs::remove_file(&tmp);
remove_sidecars(&tmp);
return Err(err);
}
if bak.exists() {
let _ = std::fs::remove_file(&bak);
}
std::fs::rename(path, &bak).map_err(Error::from)?;
if let Err(err) = std::fs::rename(&tmp, path) {
let _ = std::fs::rename(&bak, path);
let _ = std::fs::remove_file(&tmp);
return Err(Error::from(err));
}
Ok(())
}
pub fn enable_encryption(path: impl AsRef<Path>, target: EncryptionInput) -> Result<()> {
let path = path.as_ref();
if let Some(header) = crate::storage::store::Store::peek_header_path(path)? {
if header.flags & crate::storage::format::FLAG_ENCRYPTED != 0 {
return Err(Error::InvalidConfig(
"enable_encryption: file is already encrypted",
));
}
} else {
return Err(Error::InvalidConfig(
"enable_encryption: file does not exist",
));
}
rewrite_database(path, None, Some(&target))
}
pub fn disable_encryption(path: impl AsRef<Path>, current: EncryptionInput) -> Result<()> {
let path = path.as_ref();
if let Some(header) = crate::storage::store::Store::peek_header_path(path)? {
if header.flags & crate::storage::format::FLAG_ENCRYPTED == 0 {
return Err(Error::InvalidConfig(
"disable_encryption: file is already unencrypted",
));
}
} else {
return Err(Error::InvalidConfig(
"disable_encryption: file does not exist",
));
}
rewrite_database(path, Some(¤t), None)
}
pub fn rotate_encryption_key(
path: impl AsRef<Path>,
from: EncryptionInput,
to: EncryptionInput,
) -> Result<()> {
let path = path.as_ref();
if let Some(header) = crate::storage::store::Store::peek_header_path(path)? {
if header.flags & crate::storage::format::FLAG_ENCRYPTED == 0 {
return Err(Error::InvalidConfig(
"rotate_encryption_key: file is not encrypted; use enable_encryption \
to add encryption to an unencrypted database",
));
}
} else {
return Err(Error::InvalidConfig(
"rotate_encryption_key: file does not exist",
));
}
rewrite_database(path, Some(&from), Some(&to))
}