use super::*;
pub(crate) fn case_python_source_dir(case: &TestQemuCase) -> PathBuf {
case.case_dir.join("python")
}
pub(crate) fn prepare_python_case_assets_sync(
arch: &str,
case: &TestQemuCase,
case_rootfs: &Path,
layout: &case_assets::CaseAssetLayout,
config: &CaseAssetConfig,
) -> anyhow::Result<()> {
let python_dir = case_python_source_dir(case);
ensure!(
python_dir.is_dir(),
"missing case Python source directory `{}`",
python_dir.display()
);
case_assets::reset_dir(&layout.staging_root)?;
case_assets::reset_dir(&layout.overlay_dir)?;
case_assets::reset_dir(&layout.command_wrapper_dir)?;
fs::create_dir_all(&layout.apk_cache_dir)
.with_context(|| format!("failed to create {}", layout.apk_cache_dir.display()))?;
crate::rootfs::inject::extract_rootfs(case_rootfs, &layout.staging_root)?;
(config.prepare_staging_root)(&layout.staging_root)?;
write_musl_loader_search_path(arch, &layout.staging_root)?;
let extra_script_envs = prepare_guest_package_env(config, &layout.staging_root)?;
let qemu_runner = find_host_binary_candidates(qemu_user_binary_names(arch)?)?;
write_guest_command_wrappers(layout, &qemu_runner)?;
let guest_busybox = layout.staging_root.join("bin/busybox");
let guest_shell = layout.staging_root.join("bin/sh");
let mut prebuild_cmd = Command::new(&qemu_runner);
prebuild_cmd.arg("-L").arg(&layout.staging_root);
if guest_busybox.is_file() {
prebuild_cmd.arg(&guest_busybox).arg("sh");
} else {
ensure!(
guest_shell.is_file(),
"staging root is missing guest shell `{}`",
guest_shell.display()
);
prebuild_cmd.arg(&guest_shell);
}
prebuild_cmd.arg("-eu").arg("-c").arg("apk add python3");
let host_path = std::env::var_os("PATH").unwrap_or_default();
let mut path_entries = Vec::new();
path_entries.push(layout.command_wrapper_dir.clone());
path_entries.extend(std::env::split_paths(&host_path));
let path = std::env::join_paths(path_entries)
.map_err(|e| anyhow::anyhow!("failed to build guest prebuild PATH: {e}"))?;
prebuild_cmd.env("PATH", path);
prebuild_cmd.env("QEMU_LD_PREFIX", &layout.staging_root);
prebuild_cmd.env("LD_LIBRARY_PATH", guest_library_path(&layout.staging_root));
for (key, value) in extra_script_envs {
prebuild_cmd.env(key, value);
}
prebuild_cmd
.exec()
.context("failed to install python3 via apk in staging rootfs")?;
let python_dirs_to_copy: &[&str] = &["usr/bin", "usr/lib", "lib"];
for rel_dir in python_dirs_to_copy {
let src = layout.staging_root.join(rel_dir);
let dst = layout.overlay_dir.join(rel_dir);
if src.is_dir() {
copy_dir_recursive(&src, &dst, &layout.staging_root)
.with_context(|| format!("failed to copy {} to overlay", src.display()))?;
}
}
let dest_bin = layout.overlay_dir.join("usr/bin");
fs::create_dir_all(&dest_bin)
.with_context(|| format!("failed to create {}", dest_bin.display()))?;
for entry in fs::read_dir(&python_dir)
.with_context(|| format!("failed to read {}", python_dir.display()))?
{
let entry = entry?;
let path = entry.path();
if !path.is_file() {
continue;
}
let dest = dest_bin.join(entry.file_name());
fs::copy(&path, &dest)
.with_context(|| format!("failed to copy {} to {}", path.display(), dest.display()))?;
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
let mut perms = fs::metadata(&dest)
.with_context(|| format!("failed to stat {}", dest.display()))?
.permissions();
perms.set_mode(0o755);
fs::set_permissions(&dest, perms)
.with_context(|| format!("failed to chmod {}", dest.display()))?;
}
}
crate::rootfs::inject::inject_overlay(case_rootfs, &layout.overlay_dir)
}
pub(super) fn write_musl_loader_search_path(arch: &str, staging_root: &Path) -> anyhow::Result<()> {
let loader_path = staging_root
.join("lib")
.join(format!("ld-musl-{arch}.so.1"));
if !loader_path.is_file() {
return Ok(());
}
let etc_dir = staging_root.join("etc");
fs::create_dir_all(&etc_dir)
.with_context(|| format!("failed to create {}", etc_dir.display()))?;
let path_file = etc_dir.join(format!("ld-musl-{arch}.path"));
fs::write(&path_file, "/usr/lib\n/lib\n")
.with_context(|| format!("failed to write {}", path_file.display()))?;
Ok(())
}
pub(super) fn copy_dir_recursive(
src: &Path,
dst: &Path,
allowed_root: &Path,
) -> anyhow::Result<()> {
let canonical_root = fs::canonicalize(allowed_root).with_context(|| {
format!(
"failed to canonicalize allowed root {}",
allowed_root.display()
)
})?;
copy_dir_recursive_inner(src, dst, &canonical_root)
}
pub(super) fn copy_dir_recursive_inner(
src: &Path,
dst: &Path,
canonical_root: &Path,
) -> anyhow::Result<()> {
fs::create_dir_all(dst).with_context(|| format!("failed to create {}", dst.display()))?;
for entry in fs::read_dir(src).with_context(|| format!("failed to read {}", src.display()))? {
let entry = entry?;
let src_path = entry.path();
let dst_path = dst.join(entry.file_name());
let file_type = entry
.file_type()
.with_context(|| format!("failed to inspect {}", src_path.display()))?;
if file_type.is_dir() {
copy_dir_recursive_inner(&src_path, &dst_path, canonical_root)?;
} else if file_type.is_symlink() {
let link_target = fs::read_link(&src_path)
.with_context(|| format!("failed to read symlink {}", src_path.display()))?;
let host_target = if link_target.is_absolute() {
let rel = link_target.strip_prefix("/").unwrap_or(&link_target);
canonical_root.join(rel)
} else {
src_path
.parent()
.unwrap_or(Path::new("."))
.join(&link_target)
};
match fs::canonicalize(&host_target) {
Ok(resolved) => {
ensure!(
resolved.starts_with(canonical_root),
"symlink `{}` resolves to `{}` which escapes the staging root `{}`",
src_path.display(),
resolved.display(),
canonical_root.display()
);
if resolved.is_file() {
fs::copy(&resolved, &dst_path).with_context(|| {
format!(
"failed to copy {} to {}",
resolved.display(),
dst_path.display()
)
})?;
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
let mode = fs::metadata(&resolved)
.with_context(|| format!("failed to stat {}", resolved.display()))?
.permissions()
.mode();
fs::set_permissions(&dst_path, fs::Permissions::from_mode(mode))
.with_context(|| {
format!("failed to chmod {}", dst_path.display())
})?;
}
}
}
Err(_) if link_target.is_relative() => {
continue;
}
Err(_) => {
continue;
}
}
} else if file_type.is_file() {
fs::copy(&src_path, &dst_path).with_context(|| {
format!(
"failed to copy {} to {}",
src_path.display(),
dst_path.display()
)
})?;
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
let mode = fs::metadata(&src_path)
.with_context(|| format!("failed to stat {}", src_path.display()))?
.permissions()
.mode();
fs::set_permissions(&dst_path, fs::Permissions::from_mode(mode))
.with_context(|| format!("failed to chmod {}", dst_path.display()))?;
}
}
}
Ok(())
}