use std::path::{Path, PathBuf};
pub const MUR_BINARY_PREFIX: &str = "mur-";
pub const MUR_CLI_NAMES: &[&str] = &["mur", "mur.exe"];
pub fn is_mur_binary_name(name: &str) -> bool {
MUR_CLI_NAMES.contains(&name) || name.starts_with(MUR_BINARY_PREFIX)
}
pub fn first_party_target(
command: &str,
bundled: Option<&Path>,
install_dir: &Path,
) -> Option<PathBuf> {
if let Some(bundled) = bundled {
let c = Path::new(command);
if c == bundled || c.file_name() == bundled.file_name() {
return Some(bundled.to_path_buf());
}
}
let prog = command.split_whitespace().next()?;
if !is_mur_binary_name(Path::new(prog).file_name()?.to_str()?) {
return None;
}
let resolved =
crate::exec::resolve_command_in(&crate::exec::augmented_path_var(), prog).ok()?;
if !is_mur_binary_name(resolved.file_name()?.to_str()?) {
return None;
}
(resolved.parent()? == install_dir).then_some(resolved)
}
pub fn current_install_dir() -> Option<PathBuf> {
install_dir_of(&std::env::current_exe().ok()?)
}
pub fn install_dir_of(exe: &Path) -> Option<PathBuf> {
exe.canonicalize().ok()?.parent().map(Path::to_path_buf)
}
#[cfg(test)]
mod tests {
use super::*;
fn install_dir() -> (tempfile::TempDir, PathBuf) {
let dir = tempfile::tempdir().unwrap();
let canon = dir.path().canonicalize().unwrap();
(dir, canon)
}
#[test]
fn a_mur_binary_in_the_install_dir_is_first_party() {
let (_d, dir) = install_dir();
let gw = dir.join("mur-research-gateway");
std::fs::write(&gw, b"gw").unwrap();
assert_eq!(
first_party_target(gw.to_str().unwrap(), None, &dir),
Some(gw)
);
}
#[test]
fn a_third_party_binary_in_the_install_dir_is_not() {
let (_d, dir) = install_dir();
let other = dir.join("some-mcp");
std::fs::write(&other, b"x").unwrap();
assert_eq!(
first_party_target(other.to_str().unwrap(), None, &dir),
None
);
}
#[test]
fn a_mur_named_binary_elsewhere_is_not() {
let (_d, dir) = install_dir();
let (_e, elsewhere) = install_dir();
let gw = elsewhere.join("mur-research-gateway");
std::fs::write(&gw, b"gw").unwrap();
assert_eq!(first_party_target(gw.to_str().unwrap(), None, &dir), None);
}
#[test]
fn the_mur_cli_in_the_install_dir_is_first_party() {
let (_d, dir) = install_dir();
let mur = dir.join("mur");
std::fs::write(&mur, b"mur").unwrap();
assert_eq!(
first_party_target(mur.to_str().unwrap(), None, &dir),
Some(mur)
);
}
#[test]
fn the_mur_cli_elsewhere_on_path_is_not() {
let (_d, dir) = install_dir();
let (_e, elsewhere) = install_dir();
let mur = elsewhere.join("mur");
std::fs::write(&mur, b"mur").unwrap();
assert_eq!(first_party_target(mur.to_str().unwrap(), None, &dir), None);
}
#[test]
fn a_mur_prefixed_lookalike_name_is_not_the_cli() {
let (_d, dir) = install_dir();
let other = dir.join("murky");
std::fs::write(&other, b"x").unwrap();
assert_eq!(
first_party_target(other.to_str().unwrap(), None, &dir),
None
);
}
#[cfg(unix)]
#[test]
fn a_symlinked_mur_cli_canonicalizes_into_the_install_dir() {
let (_d, dir) = install_dir();
let (_l, link_dir) = install_dir();
let mur = dir.join("mur");
std::fs::write(&mur, b"mur").unwrap();
let link = link_dir.join("mur");
std::os::unix::fs::symlink(&mur, &link).unwrap();
assert_eq!(
first_party_target(link.to_str().unwrap(), None, &dir),
Some(mur)
);
}
#[cfg(unix)]
#[test]
fn a_mur_named_symlink_to_a_third_party_binary_is_not() {
let (_d, dir) = install_dir();
let other = dir.join("some-mcp");
std::fs::write(&other, b"x").unwrap();
let link = dir.join("mur-sneaky");
std::os::unix::fs::symlink(&other, &link).unwrap();
assert_eq!(first_party_target(link.to_str().unwrap(), None, &dir), None);
}
#[test]
fn the_bundled_server_matches_by_name() {
let (_d, dir) = install_dir();
let bundled = PathBuf::from("/x/.mur/mcp-servers/mur-mcp-server");
assert_eq!(
first_party_target("mur-mcp-server", Some(&bundled), &dir),
Some(bundled)
);
}
#[test]
fn install_dir_of_survives_replacing_the_binary() {
let (_g, dir) = install_dir();
let exe = dir.join("mur");
std::fs::write(&exe, b"old").unwrap();
let captured = install_dir_of(&exe);
let staged = dir.join("mur.new");
std::fs::write(&staged, b"new").unwrap();
std::fs::rename(&staged, &exe).unwrap();
assert_eq!(captured.as_deref(), Some(dir.as_path()));
assert_eq!(install_dir_of(&exe).as_deref(), Some(dir.as_path()));
}
#[cfg(target_os = "linux")]
#[test]
fn linux_exe_link_breaks_after_swap_but_captured_dir_does_not() {
let (_g, dir) = install_dir();
let exe = dir.join("mur");
std::fs::copy("/bin/sleep", &exe).unwrap();
let captured = install_dir_of(&exe);
let mut child = std::process::Command::new(&exe).arg("30").spawn().unwrap();
let proc_exe = PathBuf::from(format!("/proc/{}/exe", child.id()));
const EXEC_SETTLE_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(5);
const EXEC_SETTLE_POLL: std::time::Duration = std::time::Duration::from_millis(10);
let settle_deadline = std::time::Instant::now() + EXEC_SETTLE_TIMEOUT;
while install_dir_of(&proc_exe).as_deref() != Some(dir.as_path())
&& std::time::Instant::now() < settle_deadline
{
std::thread::sleep(EXEC_SETTLE_POLL);
}
assert_eq!(install_dir_of(&proc_exe).as_deref(), Some(dir.as_path()));
let staged = dir.join("mur.new");
std::fs::copy("/bin/sleep", &staged).unwrap();
std::fs::rename(&staged, &exe).unwrap();
let after = install_dir_of(&proc_exe);
child.kill().ok();
child.wait().ok();
assert_eq!(
after, None,
"the replaced exe link should no longer resolve"
);
assert_eq!(captured.as_deref(), Some(dir.as_path()));
}
}