tar-rs applies a directory's tar mode as soon as the entry is
extracted, so images shipping read-only directories abort on their own
contents: fedora:latest's dr-xr-xr-x ca-trust tree fails with EACCES
on etc/pki/ca-trust/extracted/pem/directory-hash/002c0b4f.0. Directory
modes are now recorded and applied once after the last layer is
written.
Hard links were canonicalized inline, failing with ENOENT when the
target appears later in the tar, lives in an earlier layer, or exists
nowhere. Links are now applied after the file pass and fall back to a
verbatim symlink with a warning when the target is absent, matching
Docker/podman behaviour for these entries.
Verified against a real fedora:latest layer: extraction previously
aborted at the first directory-hash symlink, now all 438 entries land
with tls-ca-bundle.pem intact.
Ubuntu builds its riscv64 port against the RVA23 profile (since
25.10), and QEMU's default rv64 CPU does not implement all profile
extensions: Ubuntu binaries die with SIGILL during init and the
kernel panics with 'Attempted to kill init'. Select the rva23s64
profile CPU (QEMU 10.1+) for riscv64 under TCG; profiles are
supersets, so baseline rv64gc rootfses run unchanged on it.
KVM mode keeps -cpu host.
qemu-system-riscv64's default machine is spike, not virt: spike has no
PCI bus, so virtio-net-pci failed with "No 'PCI' bus found", and no
16550 UART, so console=ttyS0 output went nowhere. Pass -machine virt
explicitly for riscv64; other architectures keep their emulator's
default.
Alpine does not build the linux-virt flavor for riscv64, so --kernel
without a path failed there with 'linux-virt package not found'. Scan
the APKINDEX once for both flavors and prefer linux-virt, taking
linux-lts when the virt flavor is missing.
Also store the cached kernel decompressed: riscv64 and aarch64 ship
their image gzipped (Image.gz), and QEMU's riscv -kernel loader
understands only ELF, uImage and raw images, so the gzipped image hung
at boot after the OpenSBI banner.
execve(2) does not search PATH, so `ecr alpine -- echo hi` failed with
ENOENT: the old code only used PATH as an existence check and then
still exec'd the bare name, which the kernel resolved relative to the
working directory. Pre-dates the crate split, but bare names are the
natural CLI usage so it needs to work.
Programs are now resolved execvp-style: a bare name is looked up in
the caller-composed PATH (empty components skipped rather than treated
as the cwd), paths containing '/' are used as-is with a friendlier
error than a raw ENOENT.
Add ecr::rootfs with the full rootfs lifecycle behind the library:
- RootfsCache::prepare resolves an image reference, downloads through
the tarball cache (with the OCI :latest digest freshness check) and
extracts into a scratch directory tracked by PreparedRootfs.
- PreparedRootfs::persist packs the current rootfs back into its cache
entry (compressed to match the entry's extension, symlinks and
permissions preserved) and marks it with a .provisioned sidecar.
- RootfsCache::prepare_provisioned composes both into the hot-cell
flow: provision once, and every later call sharing the cache skips
the download and the provisioning step.
extract: an "oci-" cache entry without a layers.manifest is a
persisted provisioned rootfs; extract it as a plain archive.
The CLI now drives prepare and drops its inline cache/orchestration
code and the dirs/tempfile dependencies. The binfmt check moves ahead
of the download so foreign-arch runs fail before pulling an image.
Add ecr::exec: run a command inside a prepared rootfs in fresh
user/PID/mount/UTS namespaces, with the caller composing the full
environment (ExecOptions::env), the bind targets (BindTarget, with
explicit absolute mount points inside the rootfs) and the target
architecture (binfmt_misc is verified for foreign arches).
mount::setup_mounts now takes &[BindTarget] instead of parallel
read-only/read-write path lists; chroot::run_chroot takes the envp and
a resolved working directory, and chroot::default_env composes the
previous hardcoded environment as a starting point for callers.
The CLI maps its flags onto the new API; behavior is unchanged
(overlay at /root/<basename>, rw bind at /mnt/<basename>, cwd default).
The root package becomes a virtual workspace: `crates/ecr` holds the
library (package name `ecr`) and `crates/ecr-cli` the command line
front-end, which keeps installing the `ecr` binary. No behavior change.
The library must not depend on CLI types, so mount::setup_mounts now
takes the `no_bind` flag instead of a `&Args`.