use crate::cli::Options;
use crate::output;
use crate::pacman::Toolchain;
use crate::transaction::Transaction;
use anyhow::Result;
use std::collections::BTreeSet;
use std::fs;
use std::path::Path;
const DEFAULT_COUNTRY: &str = "United States";
pub async fn update(
options: &Options,
tools: &Toolchain,
tx: Option<&Transaction>,
args: &[String],
) -> Result<i32> {
let (country, explicit_keys) = split_country_and_keys(args);
output::status("Mirror country", &country);
for key in short_key_ids(args) {
output::warning(format!(
"ignoring short or ambiguous PGP key id '{key}'; use a 16+ character key id or full fingerprint"
));
}
let aur_keys = collect_aur_keys(tx);
let mut keys = explicit_keys.into_iter().collect::<BTreeSet<_>>();
keys.extend(aur_keys);
let keys = keys.into_iter().collect::<Vec<_>>();
if keys.is_empty() {
output::status("AUR PGP keys", "none found");
} else {
output::status("AUR PGP keys", output::join_inline(&keys, " "));
let code = tools.import_gpg_keys(&keys, options.dry_run).await?;
if code != 0 {
return Ok(code);
}
}
output::status(
"Mirror policy",
"IPv4 HTTPS, fresh, high-score, 5s timeouts, speed-test 25 and keep fastest 8",
);
output::status("Arch keyring", "sync archlinux-keyring first");
let packages = ["archlinux-keyring".to_string(), "reflector".to_string()];
let code = tools
.install_keyring_packages(&packages, options.no_confirm, options.dry_run)
.await?;
if code != 0 {
return Ok(code);
}
output::status("Arch keyring", "initialize pacman keyring if needed");
let code = tools.init_pacman_keys(options.dry_run).await?;
if code != 0 {
return Ok(code);
}
output::status("Arch keyring", "populate archlinux");
let code = tools.populate_pacman_keys(options.dry_run).await?;
if code != 0 {
return Ok(code);
}
output::status("Arch keyring", "update trust database");
let code = tools.update_pacman_key_database(options.dry_run).await?;
if code != 0 {
return Ok(code);
}
output::status("Mirrors", "speed-testing, ranking, and saving mirrorlist");
let code = tools.update_mirrors(&country, options.dry_run).await?;
if code != 0 {
return Ok(code);
}
output::status("System update", "force refresh databases and upgrade");
tools
.force_system_upgrade(options.no_confirm, options.dry_run)
.await
}
fn collect_aur_keys(tx: Option<&Transaction>) -> Vec<String> {
let mut keys = BTreeSet::new();
let Some(tx) = tx else {
return Vec::new();
};
for item in &tx.aur_items {
keys.extend(valid_pgp_keys_from_build_dir(&item.build_dir));
}
keys.into_iter().collect()
}
fn valid_pgp_keys_from_build_dir(dir: &Path) -> Vec<String> {
let srcinfo = dir.join(".SRCINFO");
if let Ok(data) = fs::read_to_string(&srcinfo) {
return valid_pgp_keys_from_srcinfo(&data);
}
let pkgbuild = dir.join("PKGBUILD");
fs::read_to_string(pkgbuild)
.map(|data| valid_pgp_keys_from_pkgbuild(&data))
.unwrap_or_default()
}
pub(crate) fn valid_pgp_keys_from_srcinfo(data: &str) -> Vec<String> {
data.lines()
.filter_map(|line| line.trim().strip_prefix("validpgpkeys = "))
.map(str::trim)
.filter_map(normalize_key)
.collect()
}
pub(crate) fn valid_pgp_keys_from_pkgbuild(data: &str) -> Vec<String> {
let Some(start) = data.find("validpgpkeys=(") else {
return Vec::new();
};
let rest = &data[start + "validpgpkeys=(".len()..];
let Some(end) = rest.find(')') else {
return Vec::new();
};
rest[..end]
.split_whitespace()
.map(|value| value.trim_matches(|ch| ch == '\'' || ch == '"'))
.filter_map(normalize_key)
.collect()
}
pub(crate) fn split_country_and_keys(args: &[String]) -> (String, Vec<String>) {
if args.is_empty() {
return (DEFAULT_COUNTRY.to_string(), Vec::new());
}
let Some(first_key) = args
.iter()
.position(|arg| looks_like_key(arg) || looks_like_short_key(arg))
else {
return (normalize_country(&args.join(" ")), Vec::new());
};
if first_key == 0 {
return (
DEFAULT_COUNTRY.to_string(),
args.iter().filter_map(|arg| normalize_key(arg)).collect(),
);
}
(
normalize_country(&args[..first_key].join(" ")),
args[first_key..]
.iter()
.filter_map(|arg| normalize_key(arg))
.collect(),
)
}
fn normalize_country(value: &str) -> String {
if value.eq_ignore_ascii_case("usa") || value.eq_ignore_ascii_case("us") {
DEFAULT_COUNTRY.to_string()
} else {
value.to_string()
}
}
fn looks_like_key(value: &str) -> bool {
normalize_key(value).is_some()
}
fn short_key_ids(args: &[String]) -> Vec<String> {
args.iter()
.filter(|arg| looks_like_short_key(arg))
.cloned()
.collect()
}
fn looks_like_short_key(value: &str) -> bool {
let value = key_body(value);
(8..16).contains(&value.len()) && value.bytes().all(|byte| byte.is_ascii_hexdigit())
}
fn normalize_key(value: &str) -> Option<String> {
let value = key_body(value);
if value.len() < 16 || !value.bytes().all(|byte| byte.is_ascii_hexdigit()) {
return None;
}
Some(value.to_ascii_uppercase())
}
fn key_body(value: &str) -> &str {
value
.trim()
.strip_prefix("0x")
.or_else(|| value.trim().strip_prefix("0X"))
.unwrap_or_else(|| value.trim())
}
#[cfg(test)]
mod tests {
use super::*;
fn strings(values: &[&str]) -> Vec<String> {
values.iter().map(|value| (*value).to_string()).collect()
}
#[test]
fn splits_country_and_keys() {
assert_eq!(
split_country_and_keys(&[]),
("United States".into(), Vec::new())
);
assert_eq!(
split_country_and_keys(&strings(&["Germany"])),
("Germany".into(), Vec::new())
);
assert_eq!(
split_country_and_keys(&strings(&["ABCD1234"])),
("United States".into(), Vec::new())
);
assert_eq!(
split_country_and_keys(&strings(&["South", "Korea"])),
("South Korea".into(), Vec::new())
);
assert_eq!(
split_country_and_keys(&strings(&["United", "States", "432705FACDD40325"])),
("United States".into(), strings(&["432705FACDD40325"]))
);
assert_eq!(
split_country_and_keys(&strings(&["usa", "432705FACDD40325"])),
("United States".into(), strings(&["432705FACDD40325"]))
);
assert_eq!(
split_country_and_keys(&strings(&["0x432705FACDD40325"])),
("United States".into(), strings(&["432705FACDD40325"]))
);
}
#[test]
fn parses_validpgpkeys_from_srcinfo() {
let data = "pkgbase = foo\n\tvalidpgpkeys = ABCD1234\n\tvalidpgpkeys = 432705FACDD40325\n";
assert_eq!(
valid_pgp_keys_from_srcinfo(data),
strings(&["432705FACDD40325"])
);
}
#[test]
fn parses_validpgpkeys_from_pkgbuild() {
let data = "pkgname=foo\nvalidpgpkeys=('ABCD1234' \"0x432705FACDD40325\")\n";
assert_eq!(
valid_pgp_keys_from_pkgbuild(data),
strings(&["432705FACDD40325"])
);
}
}