From 05b9befc64a94923e189ce26e4e55899b939394b Mon Sep 17 00:00:00 2001
From: Matthew Jackson <1085847+MattJackson@users.noreply.github.com>
Date: Wed, 24 Jun 2026 20:46:31 -0700
Subject: [PATCH] libfreemkv: keydb default location is local to the executable
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
default_path() now resolves
/keydb.cfg with no OS-config
fallback — freemkv is a portable standalone binary, so the keydb lives next to
it. --keydb still overrides.
---
src/keydb.rs | 83 +++++++++++++++++++++++++---------------------------
1 file changed, 40 insertions(+), 43 deletions(-)
diff --git a/src/keydb.rs b/src/keydb.rs
index 36bbdb9..277804c 100644
--- a/src/keydb.rs
+++ b/src/keydb.rs
@@ -43,12 +43,13 @@ fn read_capped_to_string(reader: R) -> Result {
String::from_utf8(buf).map_err(|_| Error::KeydbParse)
}
-/// Build the error returned when no home directory can be determined
-/// (`HOME`/`USERPROFILE` unset). This is an *environment* failure — the
-/// process has no home dir, which typically signals a stripped container
-/// or CI configuration — not a corrupt or unparseable keydb file. Map it
-/// to a `NotFound` I/O error so display/remediation paths never claim a
-/// keydb parse failure for a file that was never consulted.
+/// Build the error returned when the executable's own directory can't be
+/// determined (`std::env::current_exe()` fails or has no parent). This is an
+/// *environment* failure — the process can't locate itself, which typically
+/// signals a stripped container or CI configuration — not a corrupt or
+/// unparseable keydb file. Map it to a `NotFound` I/O error so
+/// display/remediation paths never claim a keydb parse failure for a file
+/// that was never consulted.
fn no_home_dir() -> Error {
Error::IoError {
source: std::io::Error::from(std::io::ErrorKind::NotFound),
@@ -57,45 +58,24 @@ fn no_home_dir() -> Error {
/// Standard keydb storage path — the canonical location to write the keydb to.
///
-/// On Windows this is the idiomatic per-user roaming dir
-/// `%APPDATA%\freemkv\keydb.cfg`, falling back to the legacy
-/// `%USERPROFILE%\.config\freemkv\keydb.cfg` only if `APPDATA` is unset. On
-/// Linux/macOS it stays the long-standing `$HOME/.config/freemkv/keydb.cfg`.
+/// The keydb lives *next to the executable*: `/keydb.cfg`,
+/// where the directory is `std::env::current_exe()`'s parent. This makes
+/// freemkv a portable, standalone binary — drop the exe and its `keydb.cfg`
+/// in the same folder and it works, with no OS-specific config dir
+/// (`%APPDATA%`, `~/.config`, XDG) involved at all.
///
-/// The CLI's read-side search (first existing of several locations) lives in
-/// `freemkv-keysources::keydb_search_paths`; this function is the single
-/// *write* default used by `save`/`update`. On Windows the two agree. On
-/// Linux they can diverge: this write path always uses
-/// `$HOME/.config/freemkv/keydb.cfg` and ignores `XDG_CONFIG_HOME`, whereas
-/// the read-side search additionally checks `$XDG_CONFIG_HOME/freemkv` first.
-/// A user who sets `XDG_CONFIG_HOME` to a non-`$HOME/.config` location will
-/// therefore have `update-keys` write to the `$HOME` path while the read-side
-/// search may prefer the `XDG_CONFIG_HOME` location — the `$HOME` path is still
-/// in the search list, so the freshly-written keydb is found, just not first.
+/// The CLI's read-side search lives in
+/// `freemkv-keysources::keydb_search_paths`; it resolves the same exe-local
+/// location, so the *write* default used by `save`/`update` and the read-side
+/// search always agree.
+///
+/// Returns a `NotFound` I/O error (via [`no_home_dir`]) if the executable's
+/// own directory can't be determined.
pub fn default_path() -> Result {
- #[cfg(windows)]
- {
- if let Ok(appdata) = std::env::var("APPDATA") {
- if !appdata.is_empty() {
- return Ok(PathBuf::from(appdata).join("freemkv").join("keydb.cfg"));
- }
- }
- let profile = std::env::var("USERPROFILE").map_err(|_| no_home_dir())?;
- Ok(PathBuf::from(profile)
- .join(".config")
- .join("freemkv")
- .join("keydb.cfg"))
- }
- #[cfg(not(windows))]
- {
- let home = std::env::var("HOME")
- .or_else(|_| std::env::var("USERPROFILE"))
- .map_err(|_| no_home_dir())?;
- Ok(PathBuf::from(home)
- .join(".config")
- .join("freemkv")
- .join("keydb.cfg"))
- }
+ std::env::current_exe()
+ .ok()
+ .and_then(|exe| exe.parent().map(|dir| dir.join("keydb.cfg")))
+ .ok_or_else(no_home_dir)
}
/// Download a KEYDB from a URL, verify, save to the standard path.
@@ -450,6 +430,23 @@ mod tests {
assert_ne!(no_home_dir().code(), Error::KeydbParse.code());
}
+ // The write default is local to the executable: `/keydb.cfg`.
+ // Under `cargo test`, `current_exe()` is the test binary in `target/…`;
+ // assert against the same computation, not a hardcoded path.
+ #[test]
+ fn default_path_is_local_to_executable() {
+ let expected = std::env::current_exe()
+ .ok()
+ .and_then(|exe| exe.parent().map(|dir| dir.join("keydb.cfg")));
+ match expected {
+ Some(p) => assert_eq!(default_path().unwrap(), p),
+ None => {
+ // No exe parent available → must surface the env failure.
+ assert!(matches!(default_path(), Err(Error::IoError { .. })));
+ }
+ }
+ }
+
#[test]
fn write_atomic_replaces_existing_and_leaves_no_temp() {
let dir = scratch("atomic");