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:
+16
-3
@@ -332,21 +332,34 @@ fn hashes_in_context(
|
||||
.map(|n| (n.clone(), ArtifactHashes::default()))
|
||||
.collect();
|
||||
|
||||
// Sizes.
|
||||
// Sizes. A failed `stat` must fail the metadata generation: an unchecked
|
||||
// exit status would leave the default size 0 in the produced
|
||||
// `.changes`/`.buildinfo` checksum entries.
|
||||
let output = ctx
|
||||
.command("stat")
|
||||
.current_dir(dir)
|
||||
.arg("-c")
|
||||
.arg("%s %n")
|
||||
.args(names)
|
||||
.output()?;
|
||||
.output()
|
||||
.map_err(|e| format!("failed to run 'stat' inside the build context: {e}"))?;
|
||||
if !output.status.success() {
|
||||
return Err(format!(
|
||||
"'stat' failed inside the build context: {}",
|
||||
String::from_utf8_lossy(&output.stderr).trim()
|
||||
)
|
||||
.into());
|
||||
}
|
||||
let stdout = String::from_utf8_lossy(&output.stdout);
|
||||
for line in stdout.lines() {
|
||||
let Some((size, name)) = line.trim().split_once(' ') else {
|
||||
continue;
|
||||
};
|
||||
let size = size
|
||||
.parse::<u64>()
|
||||
.map_err(|_| format!("'stat' reported an invalid size '{size}' for '{name}'"))?;
|
||||
if let Some(slot) = out.get_mut(name) {
|
||||
slot.size = size.parse().unwrap_or(0);
|
||||
slot.size = size;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+40
-4
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user