pub struct FileBackend { /* private fields */ }Expand description
Filesystem-backed keychain.
Thread-safe — KeychainBackend is Send + Sync, and all operations use
OS-level atomic primitives (rename, unlink). Multiple FileBackend
instances pointing at the same root directory coexist without mutual
serialization; the tmp-file names include a random suffix so concurrent
writes to the same BackendKey do not step on each other’s tmp files.
§Example
use std::sync::Arc;
use dig_keystore::{
backend::{FileBackend, BackendKey, KeychainBackend},
};
let backend: Arc<dyn KeychainBackend> = Arc::new(
FileBackend::new("/var/lib/dig/keys")
);
backend.write(&BackendKey::new("v1"), b"...").unwrap();Implementations§
Source§impl FileBackend
impl FileBackend
Sourcepub fn new(root: impl Into<PathBuf>) -> Self
pub fn new(root: impl Into<PathBuf>) -> Self
Create a new file backend rooted at root.
The directory is not created immediately — it is lazily created on
the first write call (with mode 0700 on Unix). This lets callers
construct a FileBackend in tests without side effects; no files are
written until the first write.
§Example
use dig_keystore::backend::FileBackend;
let be = FileBackend::new("/var/lib/dig/keys");
let _ = be; // directory not created yetTrait Implementations§
Source§impl KeychainBackend for FileBackend
impl KeychainBackend for FileBackend
Source§fn read(&self, key: &BackendKey) -> Result<Vec<u8>>
fn read(&self, key: &BackendKey) -> Result<Vec<u8>>
Read the entire file at <root>/<key>.dks.
Returns KeystoreError::Backend wrapping an io::Error with
ErrorKind::NotFound if the file does not exist.
Source§fn write(&self, key: &BackendKey, data: &[u8]) -> Result<()>
fn write(&self, key: &BackendKey, data: &[u8]) -> Result<()>
Atomically write data to <root>/<key>.dks.
Steps:
- Ensure
rootexists, is a directory rather than a symlink, and is verified owner-only. - Create sibling
<key>.dks.tmp.<random>file with mode0600requested in theopen(2)call on Unix, then verify the mode that actually took effect before any bytes are written — so a root that cannot hold key material safely yieldsKeystoreError::InsecurePermissionsand an empty, removed tmp file rather than an exposed blob. - Write
data,fsyncthe file handle. renamethe tmp file onto the final name.- On Unix,
fsyncthe containing directory so the rename is durable. - On error in step 4, best-effort unlink the tmp file.
The random suffix in step 2 is not cryptographic — it exists only
to disambiguate two concurrent writes to the same key from the same
process. Uses a hash of (nanoseconds_since_epoch, pid).
Source§fn delete(&self, key: &BackendKey) -> Result<()>
fn delete(&self, key: &BackendKey) -> Result<()>
Best-effort secure delete, then unlink.
Steps:
- No-op if the file is confidently absent (idempotent); refuses if its presence could not be determined, rather than reporting a completed erase over a blob that may still be there.
- Open the file for writing; overwrite with zeros in 4 KiB chunks.
fsyncthe overwritten file so zeros hit storage.unlinkthe file.
Step 2 is best-effort. On SSDs with flash translation layer or on copy-on-write filesystems (btrfs, ZFS), the zero pass may not reach the sectors that held the ciphertext. Use full-disk encryption for stronger guarantees.
Source§fn list(&self, prefix: &str) -> Result<Vec<BackendKey>>
fn list(&self, prefix: &str) -> Result<Vec<BackendKey>>
Enumerate keys whose names start with prefix.
Scans the root directory; skips any file that:
- does not end in
.dks - has a non-UTF-8 name
- does not start with
prefix
Returns an empty vec if the root directory is confidently absent, and
an error if it could not be inspected at all — the same three-valued
contract exists keeps, for the same reason.
Source§fn exists(&self, key: &BackendKey) -> Result<bool>
fn exists(&self, key: &BackendKey) -> Result<bool>
Stat the path without opening it, preserving the trait’s three-valued contract: present, confidently absent, or could not determine.
Uses symlink_metadata rather than Path::exists() or try_exists().
Path::exists() maps every error to false, which turns an
inspection failure into a confident negative — and the caller uses that
answer to decide whether to mint over a write that replaces.
symlink_metadata is also the stricter of the two honest options: it
does not follow links, so a dangling symlink at the key path counts
as present. Something occupies that name; refusing to write over it is
the fail-closed reading, whereas try_exists() would report false and
invite exactly the overwrite this method exists to prevent.
Source§fn write_new(&self, key: &BackendKey, data: &[u8]) -> Result<()>
fn write_new(&self, key: &BackendKey, data: &[u8]) -> Result<()>
Establish <root>/<key>.dks only if it does not already exist.
Exclusivity comes from the OS: the file is opened with create_new, so
exactly one racer creates it and every other gets
KeystoreError::AlreadyExists — a distinguishable error the loser can
adopt on, rather than a generic I/O failure it can only give up on.
§Why this does not use tmp + rename
rename always replaces, so it cannot express “only if absent”; the two
guarantees are not simultaneously available without a hard link, which
not every filesystem supports. Exclusivity is the one that matters here,
and the cost is bounded: a crash mid-write leaves a short file, which
the format’s magic, length and CRC all detect on the next read
(SPEC.md §3.2), and which is repaired by deleting it and retrying. The
state this method exists to prevent — a coupled pair that settled
mismatched — is neither detectable nor repairable. A best-effort unlink
removes the partial file on the way out of any failure.
Source§fn write_new_exclusivity(&self) -> Exclusivity
fn write_new_exclusivity(&self) -> Exclusivity
create_new(true) is an atomic create-if-absent at the OS level, so two
concurrent calls cannot both succeed.
Auto Trait Implementations§
impl Freeze for FileBackend
impl RefUnwindSafe for FileBackend
impl Send for FileBackend
impl Sync for FileBackend
impl Unpin for FileBackend
impl UnsafeUnpin for FileBackend
impl UnwindSafe for FileBackend
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read more