diff --git a/crates/lanspread-peer/src/lib.rs b/crates/lanspread-peer/src/lib.rs index db6c216..9bd406c 100644 --- a/crates/lanspread-peer/src/lib.rs +++ b/crates/lanspread-peer/src/lib.rs @@ -595,7 +595,7 @@ pub fn start_peer_with_options( stream_install_provider, local_network_sharing, } = options; - let state_dir = resolve_state_dir(state_dir.as_deref()); + let state_dir = resolve_state_dir(state_dir.as_deref())?; let requested_game_dir = game_dir.into(); let game_dir = canonicalize_game_dir(&requested_game_dir)?; install::intent::scan_active_install_intents(&state_dir, &game_dir)?; diff --git a/crates/lanspread-peer/src/state_paths.rs b/crates/lanspread-peer/src/state_paths.rs index 4dfc846..5e29b97 100644 --- a/crates/lanspread-peer/src/state_paths.rs +++ b/crates/lanspread-peer/src/state_paths.rs @@ -15,20 +15,34 @@ pub(crate) const LEGACY_DOWNLOAD_OWNERSHIP_TMP_FILE: &str = "download_ownership. pub(crate) const LEGACY_DOWNLOAD_OWNERSHIP_RECOVERY_REQUIRED_FILE: &str = "download_ownership.recovery-required"; -pub(crate) fn resolve_state_dir(explicit: Option<&Path>) -> PathBuf { +/// Resolves the directory that holds the peer identity, ownership journals and +/// other durable state. +/// +/// Precedence: an explicit path from the embedding application (the Tauri +/// app passes its app-data directory, the CLI its `--state-dir`), then +/// `LANSPREAD_STATE_DIR`, then `$HOME`/`%USERPROFILE%/.lanspread`. +/// +/// # Errors +/// +/// Returns an error when none of these sources is available. State holds the +/// private identity key, so it is never placed in a shared, world-writable +/// temporary directory where another local user could pre-create it. +pub(crate) fn resolve_state_dir(explicit: Option<&Path>) -> eyre::Result { if let Some(dir) = explicit { - return dir.to_path_buf(); + return Ok(dir.to_path_buf()); } if let Some(dir) = std::env::var_os("LANSPREAD_STATE_DIR") { - return PathBuf::from(dir); + return Ok(PathBuf::from(dir)); } if let Some(home) = std::env::var_os("HOME").or_else(|| std::env::var_os("USERPROFILE")) { - return PathBuf::from(home).join(".lanspread"); + return Ok(PathBuf::from(home).join(".lanspread")); } - std::env::temp_dir().join("lanspread") + eyre::bail!( + "no state directory: pass one explicitly or set LANSPREAD_STATE_DIR, HOME, or USERPROFILE" + ) } pub(crate) fn peer_identity_path(state_dir: &Path) -> PathBuf { @@ -161,3 +175,17 @@ fn hex_encode(bytes: &[u8]) -> String { } encoded } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn explicit_state_dir_takes_precedence() { + let explicit = Path::new("/explicit/state"); + assert_eq!( + resolve_state_dir(Some(explicit)).expect("explicit path resolves"), + explicit + ); + } +}