build: check stat's exit status and honor DPKG_ORIGINS_DIR

hashes_in_context never checked stat's exit status and parsed its size
with unwrap_or(0), silently recording zero-size artifacts in the
generated .changes/.buildinfo; stat failures and unparsable sizes are
now errors naming the file. current_vendor hardcoded
/etc/dpkg/origins/default while dpkg honors DPKG_ORIGINS_DIR (already
in the file's own ENV_ALLOWED list); the origins default is now
resolved against it with the usual fallback.
This commit is contained in:
2026-09-17 18:56:32 +02:00
parent 22e43741f3
commit fa39851783
2 changed files with 56 additions and 7 deletions
+40 -4
View File
@@ -3,7 +3,7 @@
//! sanitized environment recorded in `.buildinfo` files.
use std::collections::BTreeMap;
use std::path::Path;
use std::path::{Path, PathBuf};
/// Number of parallel jobs to advertise in `DEB_BUILD_OPTIONS`.
pub fn num_parallel() -> usize {
@@ -57,15 +57,31 @@ pub fn arch_env(host_arch: Option<&str>) -> Result<BTreeMap<String, String>, Str
crate::debian::arch::arch_env(host_arch)
}
/// Read the current vendor name from `/etc/dpkg/origins/default`
/// (its `Vendor:` or `Origin:` field), defaulting to `"debian"`.
/// Read the current vendor name from the active dpkg origins `default` file
/// (`$DPKG_ORIGINS_DIR/default`, falling back to `/etc/dpkg/origins/default`;
/// its `Vendor:` or `Origin:` field), defaulting to `"debian"`.
pub fn current_vendor() -> String {
std::fs::read_to_string(Path::new("/etc/dpkg/origins/default"))
let path = resolve_origins_default(
std::env::var("DPKG_ORIGINS_DIR").ok().as_deref(),
"/etc/dpkg/origins",
);
std::fs::read_to_string(path)
.ok()
.and_then(|content| vendor_from_origins_content(&content))
.unwrap_or_else(|| "debian".to_string())
}
/// Resolve the path of the active dpkg origins file from the
/// `DPKG_ORIGINS_DIR` value (the directory holding the origin files, where
/// `default` selects the active one) and the fallback directory
/// (`/etc/dpkg/origins`). An unset or empty directory value falls back.
fn resolve_origins_default(origins_dir: Option<&str>, fallback_dir: &str) -> PathBuf {
let dir = origins_dir
.filter(|d| !d.is_empty())
.unwrap_or(fallback_dir);
Path::new(dir).join("default")
}
/// Extract the vendor name from the content of a dpkg origins file: its
/// `Vendor:` field, falling back to `Origin:` when absent. `None` when
/// neither field carries a non-empty value.
@@ -306,6 +322,26 @@ mod tests {
assert_eq!(vendor_from_origins_content("Suite: stable\n"), None);
}
/// `current_vendor` must honor `DPKG_ORIGINS_DIR` (already on the
/// `.buildinfo` allow-list) when locating the `default` origins file,
/// falling back to `/etc/dpkg/origins/default` when unset or empty.
#[test]
fn origins_default_path_honors_dpkg_origins_dir() {
assert_eq!(
resolve_origins_default(Some("/custom/origins"), "/etc/dpkg/origins"),
PathBuf::from("/custom/origins/default")
);
assert_eq!(
resolve_origins_default(None, "/etc/dpkg/origins"),
PathBuf::from("/etc/dpkg/origins/default")
);
// An empty value behaves as unset, like dpkg's `$dir || $default`.
assert_eq!(
resolve_origins_default(Some(""), "/etc/dpkg/origins"),
PathBuf::from("/etc/dpkg/origins/default")
);
}
#[test]
fn environment_escaping() {
// The function reads the process env; just verify formatting helpers