Module tor_persist::state_dir

source ·
Available on crate feature 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 with tor_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 model StorageHandle as containing a T 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 of raw_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 implement raw_subdir.

  • Call sites using raw_subdir will no longer compile. Use #[cfg] at call sites to replace the raw_subdir with whatever is appropriate for the platform.

Re-exports§

Structs§

Enums§

Traits§

Type Aliases§