state-dir
only.Expand description
State helper utility
All the methods in this module perform appropriate mistrust checks.
All the methods arrange to ensure suitably-finegrained exclusive access. “Read-only” or “shared” mode is not supported.
§Differences from tor_persist::StorageHandle
-
Explicit provision is made for multiple instances of a single facility. For example, multiple hidden services, each with their own state, and own lock.
-
Locking (via filesystem locks) is mandatory, rather than optional - there is no “shared” mode.
-
Locked state is represented in the Rust type system.
-
We don’t use traits to support multiple implementations. Platform support would be done in the future with
#[cfg]
. Testing is done by temporary directories (as currently withtor_persist
). -
The serde-based
StorageHandle
requires&mut
for writing. This ensures proper serialisation of 1. read-modify-write cycles and 2. use of the temporary file. Or to put it another way, we modelStorageHandle
as containing aT
without interior mutability. -
There’s a way to get a raw directory for filesystem operations (currently, will be used for IPT replay logs).
§Implied filesystem structure
STATE_DIR/
STATE_DIR/KIND/INSTANCE_ID/
STATE_DIR/KIND/INSTANCE_ID/lock
STATE_DIR/KIND/INSTANCE_ID/KEY.json
STATE_DIR/KIND/INSTANCE_ID/KEY.new
STATE_DIR/KIND/INSTANCE_ID/KEY/
eg
STATE_DIR/hss/allium-cepa.lock
STATE_DIR/hss/allium-cepa/ipts.json
STATE_DIR/hss/allium-cepa/iptpub.json
STATE_DIR/hss/allium-cepa/iptreplay/
STATE_DIR/hss/allium-cepa/iptreplay/9aa9517e6901c280a550911d3a3c679630403db1c622eedefbdf1715297f795f.bin
(The lockfile is outside the instance directory to facilitate concurrency-correct deletion.)
§Comprehensive example
use std::{collections::HashSet, fmt, time::{Duration, SystemTime}};
use tor_error::{into_internal, Bug};
use tor_persist::slug::SlugRef;
use tor_persist::state_dir;
use state_dir::{InstanceIdentity, InstancePurgeHandler};
use state_dir::{InstancePurgeInfo, InstanceStateHandle, StateDirectory, StorageHandle};
impl InstanceIdentity for HsNickname {
fn kind() -> &'static str { "hss" }
fn write_identity(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{self}")
}
}
impl OnionService {
fn new(
nick: HsNickname,
state_dir: &StateDirectory,
) -> Result<Self, Error> {
let instance_state = state_dir.acquire_instance(&nick)?;
let replay_log_dir = instance_state.raw_subdir("ipt_replay")?;
let ipts_storage: StorageHandle<ipt_mgr::persist::StateRecord> =
instance_state.storage_handle("ipts")?;
// ..
}
}
struct PurgeHandler<'h>(&'h HashSet<&'h str>, Duration);
impl InstancePurgeHandler for PurgeHandler<'_> {
fn kind(&self) -> &'static str {
<HsNickname as InstanceIdentity>::kind()
}
fn name_filter(&mut self, id: &SlugRef) -> state_dir::Result<state_dir::Liveness> {
Ok(if self.0.contains(id.as_str()) {
state_dir::Liveness::Live
} else {
state_dir::Liveness::PossiblyUnused
})
}
fn age_filter(&mut self, id: &SlugRef, age: Duration)
-> state_dir::Result<state_dir::Liveness>
{
Ok(if age > self.1 {
state_dir::Liveness::PossiblyUnused
} else {
state_dir::Liveness::Live
})
}
fn dispose(&mut self, _info: &InstancePurgeInfo, handle: InstanceStateHandle)
-> state_dir::Result<()> {
// here might be a good place to delete keys too
handle.purge()
}
}
pub fn expire_hidden_services(
state_dir: &StateDirectory,
currently_configured_nicks: &HashSet<&str>,
retain_for: Duration,
) -> Result<(), Error> {
state_dir.purge_instances(
SystemTime::now(),
&mut PurgeHandler(currently_configured_nicks, retain_for),
)?;
Ok(())
}
§Platforms without a filesystem
The implementation and (in places) the documentation
is in terms of filesystems.
But, everything except InstanceStateHandle::raw_subdir
is abstract enough to implement some other way.
If we wish to support such platforms, the approach is:
-
Decide on an approach for
StorageHandle
and for each caller ofraw_subdir
. -
Figure out how the startup code will look. (Currently everything is in terms of
fs_mistrust
and filesystems.) -
Provide a version of this module with a compatible API in terms of whatever underlying facilities are available. Use
#[cfg]
to select it. Don’t implementraw_subdir
. -
Call sites using
raw_subdir
will no longer compile. Use#[cfg]
at call sites to replace theraw_subdir
with whatever is appropriate for the platform.
Re-exports§
pub use crate::Error;
Structs§
- Instance
Purge Info - Information about an instance, passed to
InstancePurgeHandler::dispose
- Instance
RawSubdir - Subdirectory within an instance’s state, for raw filesystem operations
- Instance
State Handle - State or cache directory for an instance of a facility
- Lock
File Guard - Re-export of the lock guard type, as obtained via
ContainsInstanceStateGuard
A lock-file for which we hold the lock. - State
Directory - The whole program’s state directory
- Storage
Handle - A place in the state or cache directory, where we can load/store a serialisable type
Enums§
- Liveness
- Is an instance still relevant?
Traits§
- Contains
Instance State Guard - Objects that co-own a lock on an instance
- Instance
Identity - An instance of a facility that wants to save persistent state (caller-provided impl)
- Instance
Purge Handler - For a facility to be expired using
purge_instances
(caller-provided impl)
Type Aliases§
- Result
Result
throwing astate_dir::Error