1use crate::worker_asset::WORKER_JAVA;
6use sha2::{Digest, Sha256};
7use std::path::PathBuf;
8
9pub fn data_dir() -> PathBuf {
11 if let Ok(h) = std::env::var("GHIDRA_MCP_HOME") {
12 if !h.is_empty() {
13 return PathBuf::from(h);
14 }
15 }
16 let home = std::env::var("USERPROFILE")
17 .or_else(|_| std::env::var("HOME"))
18 .ok()
19 .filter(|s| !s.is_empty());
20 match home {
21 Some(h) => PathBuf::from(h).join(".ghidra-mcp"),
22 None => std::env::temp_dir().join("ghidra-mcp"),
23 }
24}
25
26pub fn versioned_script_dir() -> PathBuf {
31 let mut h = Sha256::new();
32 h.update(WORKER_JAVA.as_bytes());
33 let digest = h.finalize();
34 let hash16: String = digest.iter().take(8).map(|b| format!("{b:02x}")).collect();
35 data_dir().join("scripts").join(hash16)
36}
37
38pub fn instance_log_path() -> PathBuf {
40 data_dir()
41 .join("logs")
42 .join(format!("worker-{}.log", std::process::id()))
43}
44
45#[cfg(test)]
46mod tests {
47 use super::*;
48
49 static ENV_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());
50
51 struct HomeGuard {
57 _lock: std::sync::MutexGuard<'static, ()>,
58 prior: Option<String>,
59 }
60
61 impl HomeGuard {
62 fn set(value: &str) -> Self {
63 let lock = ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner());
64 let prior = std::env::var("GHIDRA_MCP_HOME").ok();
65 std::env::set_var("GHIDRA_MCP_HOME", value);
66 Self { _lock: lock, prior }
67 }
68 }
69
70 impl Drop for HomeGuard {
71 fn drop(&mut self) {
72 match self.prior.take() {
73 Some(v) => std::env::set_var("GHIDRA_MCP_HOME", v),
74 None => std::env::remove_var("GHIDRA_MCP_HOME"),
75 }
76 }
77 }
78
79 #[test]
80 fn ghidra_mcp_home_env_overrides_base() {
81 let _guard = HomeGuard::set(r"C:\tmp\gh");
82 assert_eq!(data_dir(), PathBuf::from(r"C:\tmp\gh"));
83 }
84
85 #[test]
86 fn versioned_script_dir_is_under_scripts_and_hash_named() {
87 let _guard = HomeGuard::set(r"C:\tmp\gh");
88 let d = versioned_script_dir();
89 assert!(d.starts_with(r"C:\tmp\gh"));
90 assert!(d.to_string_lossy().contains("scripts"));
91 let leaf = d.file_name().unwrap().to_string_lossy().into_owned();
93 assert_eq!(leaf.len(), 16);
94 assert!(leaf.chars().all(|c| c.is_ascii_hexdigit()));
95 }
96
97 #[test]
98 fn instance_log_path_names_this_pid() {
99 let _guard = HomeGuard::set(r"C:\tmp\gh");
100 let p = instance_log_path();
101 assert!(p
102 .to_string_lossy()
103 .contains(&format!("worker-{}", std::process::id())));
104 }
105}