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()))
|
.map(|n| (n.clone(), ArtifactHashes::default()))
|
||||||
.collect();
|
.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
|
let output = ctx
|
||||||
.command("stat")
|
.command("stat")
|
||||||
.current_dir(dir)
|
.current_dir(dir)
|
||||||
.arg("-c")
|
.arg("-c")
|
||||||
.arg("%s %n")
|
.arg("%s %n")
|
||||||
.args(names)
|
.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);
|
let stdout = String::from_utf8_lossy(&output.stdout);
|
||||||
for line in stdout.lines() {
|
for line in stdout.lines() {
|
||||||
let Some((size, name)) = line.trim().split_once(' ') else {
|
let Some((size, name)) = line.trim().split_once(' ') else {
|
||||||
continue;
|
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) {
|
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.
|
//! sanitized environment recorded in `.buildinfo` files.
|
||||||
|
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
use std::path::Path;
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
/// Number of parallel jobs to advertise in `DEB_BUILD_OPTIONS`.
|
/// Number of parallel jobs to advertise in `DEB_BUILD_OPTIONS`.
|
||||||
pub fn num_parallel() -> usize {
|
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)
|
crate::debian::arch::arch_env(host_arch)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Read the current vendor name from `/etc/dpkg/origins/default`
|
/// Read the current vendor name from the active dpkg origins `default` file
|
||||||
/// (its `Vendor:` or `Origin:` field), defaulting to `"debian"`.
|
/// (`$DPKG_ORIGINS_DIR/default`, falling back to `/etc/dpkg/origins/default`;
|
||||||
|
/// its `Vendor:` or `Origin:` field), defaulting to `"debian"`.
|
||||||
pub fn current_vendor() -> String {
|
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()
|
.ok()
|
||||||
.and_then(|content| vendor_from_origins_content(&content))
|
.and_then(|content| vendor_from_origins_content(&content))
|
||||||
.unwrap_or_else(|| "debian".to_string())
|
.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
|
/// Extract the vendor name from the content of a dpkg origins file: its
|
||||||
/// `Vendor:` field, falling back to `Origin:` when absent. `None` when
|
/// `Vendor:` field, falling back to `Origin:` when absent. `None` when
|
||||||
/// neither field carries a non-empty value.
|
/// neither field carries a non-empty value.
|
||||||
@@ -306,6 +322,26 @@ mod tests {
|
|||||||
assert_eq!(vendor_from_origins_content("Suite: stable\n"), None);
|
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]
|
#[test]
|
||||||
fn environment_escaping() {
|
fn environment_escaping() {
|
||||||
// The function reads the process env; just verify formatting helpers
|
// The function reads the process env; just verify formatting helpers
|
||||||
|
|||||||
Reference in New Issue
Block a user