mod analyzer;
use std::ffi::{OsStr, OsString};
use std::path::Path;
use anyhow::Result;
#[cfg_attr(not(unix), allow(dead_code))]
pub const LEGACY_NAMES: &[&str] = &[
"actisense-serial",
"ikonvert-serial",
"socketcan-serial",
"maretron-ipg",
"canboat-pipeline",
"canboat-tui",
"replay",
"n2kd",
"analyzer",
];
pub enum Route {
Alias(Result<()>),
Canboat(Vec<OsString>),
}
pub fn route(argv: Vec<OsString>) -> Route {
let prog = argv.first().map(|a| program_name(a)).unwrap_or_default();
if prog == "analyzer" {
return Route::Alias(analyzer::run(argv));
}
if let Some(prefix) = prefix_subcommand(&prog) {
return Route::Canboat(rewrite(prefix, argv));
}
Route::Canboat(argv)
}
fn prefix_subcommand(prog: &str) -> Option<&'static [&'static str]> {
Some(match prog {
"actisense-serial" => &["interface", "--kind", "ngt1"],
"ikonvert-serial" => &["interface", "--kind", "ikonvert"],
"socketcan-serial" => &["interface", "--kind", "socketcan"],
"maretron-ipg" => &["interface", "--kind", "maretron"],
"canboat-pipeline" => &["server"],
"canboat-tui" => &["tui"],
"replay" => &["replay"],
"n2kd" => &["n2kd"],
_ => return None,
})
}
fn rewrite(prefix: &[&str], argv: Vec<OsString>) -> Vec<OsString> {
let mut out = Vec::with_capacity(prefix.len() + argv.len());
out.push(OsString::from("canboat"));
out.extend(prefix.iter().map(OsString::from));
out.extend(argv.into_iter().skip(1));
out
}
fn program_name(argv0: &OsStr) -> String {
let base = Path::new(argv0)
.file_name()
.unwrap_or(argv0)
.to_string_lossy();
base.strip_suffix(".exe").unwrap_or(&base).to_string()
}
#[cfg(unix)]
pub fn install_shims(dir: &Path) -> Result<()> {
use anyhow::Context;
use std::os::unix::fs::symlink;
let exe = std::env::current_exe().context("locating the canboat executable")?;
let exe_name = exe
.file_name()
.context("canboat executable has no file name")?;
let same_dir = exe.parent() == Some(dir);
let exe_canon = exe.canonicalize().unwrap_or_else(|_| exe.clone());
let mut installed = 0usize;
let mut kept = 0usize;
for name in LEGACY_NAMES {
let link = dir.join(name);
if link == exe {
continue; }
if let Ok(md) = std::fs::symlink_metadata(&link) {
let ours = md.file_type().is_symlink()
&& match link.canonicalize() {
Ok(p) => p == exe_canon,
Err(_) => true,
};
if !ours {
let what = if md.file_type().is_symlink() {
"a symlink to something else"
} else {
"a file"
};
println!("{}: left alone, {what} is already there", link.display());
kept += 1;
continue;
}
std::fs::remove_file(&link)
.with_context(|| format!("removing existing {}", link.display()))?;
}
let target: &Path = if same_dir { Path::new(exe_name) } else { &exe };
symlink(target, &link).with_context(|| format!("symlinking {}", link.display()))?;
println!("{} -> {}", link.display(), target.display());
installed += 1;
}
if kept > 0 {
println!(
"\n{installed} shim(s) installed, {kept} name(s) left alone because a program of \
that name is already installed there. Remove those by hand first if you do want \
canboat to take the name over."
);
}
Ok(())
}
#[cfg(not(unix))]
pub fn install_shims(_dir: &Path) -> Result<()> {
anyhow::bail!("install-shims is only supported on Unix");
}
#[cfg(all(test, unix))]
mod shim_tests {
use super::*;
#[test]
fn install_shims_does_not_clobber_an_existing_program() {
let dir = std::env::temp_dir().join(format!("canboat-shim-{}", std::process::id()));
let _ = std::fs::remove_dir_all(&dir);
std::fs::create_dir_all(&dir).expect("temp dir");
let c_analyzer = dir.join("analyzer");
std::fs::write(&c_analyzer, b"#!/bin/sh\necho C analyzer\n").expect("write");
let elsewhere = dir.join("some-other-tool");
std::fs::write(&elsewhere, b"x").expect("write");
std::os::unix::fs::symlink(&elsewhere, dir.join("replay")).expect("symlink");
install_shims(&dir).expect("install");
let md = std::fs::symlink_metadata(&c_analyzer).expect("analyzer still there");
assert!(md.file_type().is_file(), "the C analyzer was replaced");
assert_eq!(
std::fs::read(&c_analyzer).expect("read"),
b"#!/bin/sh\necho C analyzer\n"
);
assert_eq!(
std::fs::read_link(dir.join("replay")).expect("read_link"),
elsewhere
);
assert!(
std::fs::symlink_metadata(dir.join("n2kd"))
.expect("n2kd shim")
.file_type()
.is_symlink()
);
install_shims(&dir).expect("re-install");
assert!(
std::fs::symlink_metadata(dir.join("n2kd"))
.expect("n2kd shim still there")
.file_type()
.is_symlink()
);
let _ = std::fs::remove_dir_all(&dir);
}
}