use std::path::{Path, PathBuf};
use serde::Deserialize;
use crate::core::doctor::CheckStatus;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum InstallSource {
Registry,
Git(String),
Path(PathBuf),
Other(String),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CargoInstall {
pub package: String,
pub version: String,
pub source: InstallSource,
pub bins: Vec<String>,
}
#[derive(Deserialize)]
struct Crates2Entry {
#[serde(default)]
bins: Vec<String>,
}
#[derive(Deserialize)]
struct Crates2Doc {
#[serde(default)]
installs: std::collections::BTreeMap<String, Crates2Entry>,
}
pub fn parse_cargo_installs(json: &str) -> Option<Vec<CargoInstall>> {
let doc: Crates2Doc = serde_json::from_str(json).ok()?;
let mut out = Vec::new();
for (key, entry) in doc.installs {
let Some((head, source)) = key.rsplit_once(" (") else {
continue;
};
let source = source.strip_suffix(')').unwrap_or(source);
let Some((package, version)) = head.rsplit_once(' ') else {
continue;
};
out.push(CargoInstall {
package: package.to_string(),
version: version.to_string(),
source: classify_source(source),
bins: entry.bins,
});
}
out.sort_by(|a, b| a.package.cmp(&b.package).then(a.version.cmp(&b.version)));
Some(out)
}
fn classify_source(fragment: &str) -> InstallSource {
if fragment.starts_with("registry+") {
InstallSource::Registry
} else if let Some(rest) = fragment.strip_prefix("git+") {
InstallSource::Git(rest.to_string())
} else if let Some(rest) = fragment.strip_prefix("path+file://") {
InstallSource::Path(PathBuf::from(rest))
} else {
InstallSource::Other(fragment.to_string())
}
}
pub fn provenance_report(
bin_name: &str,
running_version: &str,
running_exe: &Path,
cargo_bin_dir: &Path,
ledger: Option<&[CargoInstall]>,
source_exists: &dyn Fn(&Path) -> bool,
) -> (CheckStatus, String) {
let where_from = format!(" (running `{}`)", running_exe.display());
let Some(ledger) = ledger else {
return (
CheckStatus::Unknown,
format!(
"cargo's install ledger (`$CARGO_HOME/.crates2.json`) is missing or unreadable \
— cannot verify how `{bin_name}` was installed{where_from}"
),
);
};
let providers: Vec<&CargoInstall> = ledger
.iter()
.filter(|i| i.bins.iter().any(|b| b == bin_name))
.collect();
if providers.is_empty() {
return (
CheckStatus::Unknown,
format!(
"`{bin_name}` is not recorded in cargo's install ledger{where_from} — it was \
placed by the prebuilt installer, a package manager, or built in place, so its \
provenance and upgrade path cannot be verified from here (issue #4033)"
),
);
}
if providers.len() > 1 {
let sources: Vec<String> = providers
.iter()
.map(|i| format!("{} {} from {}", i.package, i.version, describe(&i.source)))
.collect();
return (
CheckStatus::Fail,
format!(
"`{bin_name}` is provided by {} separate installs ({}) — whichever wrote last \
wins and the others are silently shadowed, so what runs is not determined by \
version{where_from} (issue #4033)",
providers.len(),
sources.join("; ")
),
);
}
let record = providers[0];
let installed = cargo_bin_dir.join(bin_name);
if !same_file(running_exe, &installed) {
return (
CheckStatus::Unknown,
format!(
"the running `{bin_name}` is `{}`, but cargo's ledger describes a DIFFERENT file — `{}` ({} {} from {}). The running binary was not installed by cargo, so its provenance and upgrade path cannot be verified from here, and upgrading the cargo install would not replace it. Check `which -a {bin_name}` for a shadowing copy (issue #4033, ADR-0021)",
running_exe.display(),
installed.display(),
record.package,
record.version,
describe(&record.source),
),
);
}
if record.version != running_version {
return (
CheckStatus::Fail,
format!(
"the running `{bin_name}` reports version {running_version}, but cargo's ledger \
records {} {} installed from {} — the binary on disk is not the one cargo \
tracks, so an upgrade will not replace what is actually running{where_from} \
(issue #4033)",
record.package,
record.version,
describe(&record.source),
),
);
}
match &record.source {
InstallSource::Registry => (
CheckStatus::Ok,
format!(
"`{bin_name}` {running_version} installed from crates.io; cargo's ledger \
agrees with the running version{where_from}"
),
),
InstallSource::Path(dir) if !source_exists(dir) => (
CheckStatus::Fail,
format!(
"`{bin_name}` {running_version} was installed with `cargo install --path` from \
`{}`, and that directory NO LONGER EXISTS — the binary has no provenance and \
no upgrade path; reinstall from crates.io (`cargo install {} --locked`) \
{where_from} (issue #4033)",
dir.display(),
record.package,
),
),
InstallSource::Path(dir) => (
CheckStatus::Warn,
format!(
"`{bin_name}` {running_version} was installed with `cargo install --path` from \
`{}` — a path install is invisible to registry update detection, so a newer \
published version will never be offered; reinstall with \
`cargo install {} --locked` once the change is published{where_from} \
(issue #4033, ADR-0021)",
dir.display(),
record.package,
),
),
InstallSource::Git(rev) => (
CheckStatus::Warn,
format!(
"`{bin_name}` {running_version} was installed from git ({rev}) — pinned to a \
commit, so registry update detection will never offer a newer published \
version{where_from} (issue #4033)"
),
),
InstallSource::Other(raw) => (
CheckStatus::Warn,
format!(
"`{bin_name}` {running_version} was installed from an unrecognised source \
(`{raw}`) — its upgrade path cannot be classified{where_from} (issue #4033)"
),
),
}
}
fn same_file(a: &Path, b: &Path) -> bool {
match (a.canonicalize(), b.canonicalize()) {
(Ok(a), Ok(b)) => a == b,
_ => a == b,
}
}
fn describe(source: &InstallSource) -> String {
match source {
InstallSource::Registry => "crates.io".to_string(),
InstallSource::Git(rev) => format!("git ({rev})"),
InstallSource::Path(p) => format!("path `{}`", p.display()),
InstallSource::Other(raw) => format!("`{raw}`"),
}
}
#[cfg(test)]
#[path = "binary_provenance_tests.rs"]
mod tests;