use anyhow::Result;
use std::collections::HashMap;
use std::fs::File;
use std::io::{BufRead, BufReader};
use std::ops::Deref;
use std::path::Path;
const DPKG_INFO_DIR: &str = "/var/lib/dpkg/info";
const DPKG_STATUS_FILE: &str = "/var/lib/dpkg/status";
pub(crate) fn find_packages_for_paths<'a>(
paths: impl IntoIterator<Item = impl Deref<Target = &'a str>>,
) -> Result<HashMap<String, String>> {
let mut path_to_package = HashMap::new();
let mut search_paths = Vec::new();
for path in paths {
let path_str = *path;
search_paths.push(path_str.to_string());
search_paths.push(format!("/usr{}", path_str));
}
let info_dir = Path::new(DPKG_INFO_DIR);
if !info_dir.exists() {
anyhow::bail!("dpkg info directory not found: {}", DPKG_INFO_DIR);
}
for entry in std::fs::read_dir(info_dir)? {
let entry = entry?;
let file_name = entry.file_name();
let file_name_str = file_name.to_string_lossy();
if !file_name_str.ends_with(".list") {
continue;
}
let package_full = &file_name_str[..file_name_str.len() - 5];
let package_name = if let Some(arch_pos) = package_full.rfind(':') {
&package_full[..arch_pos]
} else {
package_full
};
let file = File::open(entry.path())?;
let reader = BufReader::new(file);
for line in reader.lines() {
let line = line?;
let trimmed = line.trim();
for search_path in &search_paths {
if trimmed == search_path {
path_to_package.insert(trimmed.to_string(), package_name.to_string());
if let Some(stripped) = trimmed.strip_prefix("/usr") {
path_to_package.insert(stripped.to_string(), package_name.to_string());
}
if !trimmed.starts_with("/usr") {
let usr_version = format!("/usr{}", trimmed);
if search_paths.contains(&usr_version) {
path_to_package.insert(trimmed.to_string(), package_name.to_string());
}
}
}
}
}
}
Ok(path_to_package)
}
pub(crate) fn get_package_versions<'a>(
package_names: impl Iterator<Item = impl Deref<Target = &'a str>>,
) -> Result<HashMap<String, String>> {
let mut versions = HashMap::new();
let mut target_packages: HashMap<String, bool> = package_names
.map(|name| ((*name).to_string(), false))
.collect();
if target_packages.is_empty() {
return Ok(versions);
}
let file = File::open(DPKG_STATUS_FILE)?;
let reader = BufReader::new(file);
let mut current_package = String::new();
let mut current_status = String::new();
let mut in_package = false;
for line in reader.lines() {
let line = line?;
if line.is_empty() {
if in_package && current_status.contains("install ok installed") {
if let Some(found) = target_packages.get_mut(¤t_package) {
*found = true;
}
}
current_package.clear();
current_status.clear();
in_package = false;
continue;
}
if let Some(package) = line.strip_prefix("Package: ") {
current_package = package.to_string();
in_package = target_packages.contains_key(¤t_package);
} else if in_package {
if let Some(version) = line.strip_prefix("Version: ") {
versions.insert(current_package.clone(), version.to_string());
} else if let Some(status) = line.strip_prefix("Status: ") {
current_status = status.to_string();
}
}
}
if in_package && current_status.contains("install ok installed") {
if target_packages.contains_key(¤t_package) {
}
}
Ok(versions)
}