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_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 TARGET_COMMAND_FALLBACK_DIRS: &[&str] = &[
|
||||
"/usr/local/sbin",
|
||||
"/usr/local/bin",
|
||||
"/usr/sbin",
|
||||
"/usr/bin",
|
||||
"/sbin",
|
||||
"/bin",
|
||||
];
|
||||
const DEFAULT_CERTDATA_URL: &str =
|
||||
"https://hg.mozilla.org/projects/nss/raw-file/tip/lib/ckfw/builtins/certdata.txt";
|
||||
const DEFAULT_CERTDATA_OUTPUT: &str = "certdata.txt";
|
||||
@@ -2073,19 +2081,22 @@ fn run_target_command(
|
||||
dry_run: bool,
|
||||
envs: &[(&str, &str)],
|
||||
) -> Result<()> {
|
||||
let resolved_program = resolve_target_program(root, program);
|
||||
let resolved_program_display = resolved_program.display().to_string();
|
||||
|
||||
if dry_run {
|
||||
print_command_preview(root, program, args, envs);
|
||||
print_command_preview(root, &resolved_program_display, args, envs);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Execute directly on the host or via chroot for image builds.
|
||||
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
|
||||
} else {
|
||||
let mut cmd = Command::new("chroot");
|
||||
cmd.arg(root).arg(program).args(args);
|
||||
cmd.arg(root).arg(&resolved_program).args(args);
|
||||
cmd
|
||||
};
|
||||
|
||||
@@ -2096,9 +2107,13 @@ fn run_target_command(
|
||||
|
||||
let output = cmd.output().with_context(|| {
|
||||
if is_root_fs(root) {
|
||||
format!("failed to execute `{}`", program)
|
||||
format!("failed to execute `{}`", resolved_program_display)
|
||||
} 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)]) {
|
||||
// Keep dry-run output close to the real shell command for easier debugging.
|
||||
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")));
|
||||
}
|
||||
|
||||
#[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]
|
||||
fn certdata_parser_extracts_revision_and_objects() {
|
||||
let doc = parse_certdata(SAMPLE_CERTDATA).unwrap();
|
||||
|
||||
Reference in New Issue
Block a user