Add target command resolution and fallback directory support with tests
This commit is contained in:
+112
-5
@@ -22,6 +22,14 @@ const DEFAULT_JAVA_CACERTS_LINK: &str = "/etc/ssl/certs/java/cacerts";
|
|||||||
const DEFAULT_PKI_TLS_CA_BUNDLE_CRT_LINK: &str = "/etc/pki/tls/certs/ca-bundle.crt";
|
const DEFAULT_PKI_TLS_CA_BUNDLE_CRT_LINK: &str = "/etc/pki/tls/certs/ca-bundle.crt";
|
||||||
const DEFAULT_PKI_TLS_CA_BUNDLE_TRUST_CRT_LINK: &str = "/etc/pki/tls/certs/ca-bundle.trust.crt";
|
const DEFAULT_PKI_TLS_CA_BUNDLE_TRUST_CRT_LINK: &str = "/etc/pki/tls/certs/ca-bundle.trust.crt";
|
||||||
const DEFAULT_PKI_TLS_JAVA_CACERTS_LINK: &str = "/etc/pki/tls/java/cacerts";
|
const DEFAULT_PKI_TLS_JAVA_CACERTS_LINK: &str = "/etc/pki/tls/java/cacerts";
|
||||||
|
const TARGET_COMMAND_FALLBACK_DIRS: &[&str] = &[
|
||||||
|
"/usr/local/sbin",
|
||||||
|
"/usr/local/bin",
|
||||||
|
"/usr/sbin",
|
||||||
|
"/usr/bin",
|
||||||
|
"/sbin",
|
||||||
|
"/bin",
|
||||||
|
];
|
||||||
const DEFAULT_CERTDATA_URL: &str =
|
const DEFAULT_CERTDATA_URL: &str =
|
||||||
"https://hg.mozilla.org/projects/nss/raw-file/tip/lib/ckfw/builtins/certdata.txt";
|
"https://hg.mozilla.org/projects/nss/raw-file/tip/lib/ckfw/builtins/certdata.txt";
|
||||||
const DEFAULT_CERTDATA_OUTPUT: &str = "certdata.txt";
|
const DEFAULT_CERTDATA_OUTPUT: &str = "certdata.txt";
|
||||||
@@ -2073,19 +2081,22 @@ fn run_target_command(
|
|||||||
dry_run: bool,
|
dry_run: bool,
|
||||||
envs: &[(&str, &str)],
|
envs: &[(&str, &str)],
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
|
let resolved_program = resolve_target_program(root, program);
|
||||||
|
let resolved_program_display = resolved_program.display().to_string();
|
||||||
|
|
||||||
if dry_run {
|
if dry_run {
|
||||||
print_command_preview(root, program, args, envs);
|
print_command_preview(root, &resolved_program_display, args, envs);
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Execute directly on the host or via chroot for image builds.
|
// Execute directly on the host or via chroot for image builds.
|
||||||
let mut cmd = if is_root_fs(root) {
|
let mut cmd = if is_root_fs(root) {
|
||||||
let mut cmd = Command::new(program);
|
let mut cmd = Command::new(&resolved_program);
|
||||||
cmd.args(args);
|
cmd.args(args);
|
||||||
cmd
|
cmd
|
||||||
} else {
|
} else {
|
||||||
let mut cmd = Command::new("chroot");
|
let mut cmd = Command::new("chroot");
|
||||||
cmd.arg(root).arg(program).args(args);
|
cmd.arg(root).arg(&resolved_program).args(args);
|
||||||
cmd
|
cmd
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -2096,9 +2107,13 @@ fn run_target_command(
|
|||||||
|
|
||||||
let output = cmd.output().with_context(|| {
|
let output = cmd.output().with_context(|| {
|
||||||
if is_root_fs(root) {
|
if is_root_fs(root) {
|
||||||
format!("failed to execute `{}`", program)
|
format!("failed to execute `{}`", resolved_program_display)
|
||||||
} else {
|
} else {
|
||||||
format!("failed to execute `chroot {} {}`", root.display(), program)
|
format!(
|
||||||
|
"failed to execute `chroot {} {}`",
|
||||||
|
root.display(),
|
||||||
|
resolved_program_display
|
||||||
|
)
|
||||||
}
|
}
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
@@ -2138,6 +2153,48 @@ fn run_target_command(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn resolve_target_program(root: &Path, program: &str) -> PathBuf {
|
||||||
|
let path_env = std::env::var_os("PATH");
|
||||||
|
resolve_target_program_with_path(root, program, path_env.as_deref())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn resolve_target_program_with_path(
|
||||||
|
root: &Path,
|
||||||
|
program: &str,
|
||||||
|
path_env: Option<&OsStr>,
|
||||||
|
) -> PathBuf {
|
||||||
|
if program.contains('/') {
|
||||||
|
return PathBuf::from(program);
|
||||||
|
}
|
||||||
|
|
||||||
|
for candidate in target_command_search_candidates(program, path_env) {
|
||||||
|
if path_in_root(root, &candidate).exists() {
|
||||||
|
return candidate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PathBuf::from(program)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn target_command_search_candidates(program: &str, path_env: Option<&OsStr>) -> Vec<PathBuf> {
|
||||||
|
let mut candidates = Vec::new();
|
||||||
|
|
||||||
|
if let Some(path_env) = path_env {
|
||||||
|
for dir in std::env::split_paths(path_env).filter(|dir| dir.is_absolute()) {
|
||||||
|
candidates.push(dir.join(program));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for dir in TARGET_COMMAND_FALLBACK_DIRS {
|
||||||
|
let candidate = Path::new(dir).join(program);
|
||||||
|
if !candidates.iter().any(|existing| existing == &candidate) {
|
||||||
|
candidates.push(candidate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
candidates
|
||||||
|
}
|
||||||
|
|
||||||
fn print_command_preview(root: &Path, program: &str, args: &[String], envs: &[(&str, &str)]) {
|
fn print_command_preview(root: &Path, program: &str, args: &[String], envs: &[(&str, &str)]) {
|
||||||
// Keep dry-run output close to the real shell command for easier debugging.
|
// Keep dry-run output close to the real shell command for easier debugging.
|
||||||
let env_prefix = envs
|
let env_prefix = envs
|
||||||
@@ -2278,6 +2335,56 @@ CKA_TRUST_SERVER_AUTH CK_TRUST CKT_NSS_TRUSTED_DELEGATOR
|
|||||||
assert!(!is_root_fs(Path::new("/mnt/rootfs")));
|
assert!(!is_root_fs(Path::new("/mnt/rootfs")));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn target_command_candidates_use_absolute_path_entries_and_fallbacks() {
|
||||||
|
let candidates = target_command_search_candidates(
|
||||||
|
"trust",
|
||||||
|
Some(std::ffi::OsStr::new("/custom/bin:relative:/usr/bin")),
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(candidates[0], PathBuf::from("/custom/bin/trust"));
|
||||||
|
assert_eq!(candidates[1], PathBuf::from("/usr/bin/trust"));
|
||||||
|
assert!(candidates.contains(&PathBuf::from("/bin/trust")));
|
||||||
|
assert!(!candidates.contains(&PathBuf::from("relative/trust")));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn resolve_target_program_finds_binary_inside_selected_root() {
|
||||||
|
let root = unique_test_dir("target-command-root");
|
||||||
|
let trust_host_path = root.join("custom/bin/trust");
|
||||||
|
fs::create_dir_all(trust_host_path.parent().unwrap()).unwrap();
|
||||||
|
fs::write(&trust_host_path, b"").unwrap();
|
||||||
|
|
||||||
|
let resolved = resolve_target_program_with_path(
|
||||||
|
&root,
|
||||||
|
"trust",
|
||||||
|
Some(std::ffi::OsStr::new("/custom/bin")),
|
||||||
|
);
|
||||||
|
|
||||||
|
fs::remove_dir_all(&root).unwrap();
|
||||||
|
assert_eq!(resolved, PathBuf::from("/custom/bin/trust"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn resolve_target_program_leaves_missing_program_unqualified() {
|
||||||
|
let root = unique_test_dir("target-command-missing");
|
||||||
|
fs::create_dir_all(&root).unwrap();
|
||||||
|
|
||||||
|
let resolved =
|
||||||
|
resolve_target_program_with_path(&root, "missing-tool", Some(std::ffi::OsStr::new("")));
|
||||||
|
|
||||||
|
fs::remove_dir_all(&root).unwrap();
|
||||||
|
assert_eq!(resolved, PathBuf::from("missing-tool"));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn unique_test_dir(name: &str) -> PathBuf {
|
||||||
|
let nonce = SystemTime::now()
|
||||||
|
.duration_since(UNIX_EPOCH)
|
||||||
|
.unwrap_or_default()
|
||||||
|
.as_nanos();
|
||||||
|
std::env::temp_dir().join(format!("ca-certs-{name}-{}-{nonce}", std::process::id()))
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn certdata_parser_extracts_revision_and_objects() {
|
fn certdata_parser_extracts_revision_and_objects() {
|
||||||
let doc = parse_certdata(SAMPLE_CERTDATA).unwrap();
|
let doc = parse_certdata(SAMPLE_CERTDATA).unwrap();
|
||||||
|
|||||||
Reference in New Issue
Block a user