# Copyright 2026 Gentoo Authors
# Distributed under the terms of the GNU General Public License v2

EAPI=8

# Workspace rust-version is 1.95; rust-toolchain.toml pins 1.96 for upstream CI.
RUST_MIN_VER="1.95.0"

inherit cargo linux-info toolchain-funcs

DESCRIPTION="Lightweight VMs that run containers with the isolation of a hypervisor"
HOMEPAGE="https://katacontainers.io/ https://github.com/kata-containers/kata-containers"
# Upstream publishes the whole dependency tree with each release: crates.io,
# the seven git crates and the Go vendor tree. Nothing has to be regenerated
# on a bump beyond the distfile itself.
SRC_URI="
	https://github.com/kata-containers/kata-containers/archive/refs/tags/${PV}.tar.gz
		-> ${P}.tar.gz
	https://github.com/kata-containers/kata-containers/releases/download/${PV}/${P}-vendor.tar.gz
"

LICENSE="Apache-2.0"
# Dependent crate licenses, surveyed with `cargo tree --format {l}` over
# runtime-rs, kata-ctl and kata-agent[seccomp,init-data] for 4.2.0. Only the
# licenses that are not an "OR" alternative to one already listed are named.
# Redo the survey on every bump: the version moves, this list does not.
LICENSE+="
	Apache-2.0 BSD CDLA-Permissive-2.0 ISC MIT MIT-0 MPL-2.0 Unicode-3.0 ZLIB
"
SLOT="0"
KEYWORDS="~amd64 ~arm64"
IUSE="+seccomp"
# The suite needs root, KVM and a running containerd; upstream runs it in CI
# containers, not from a build sandbox.
RESTRICT="test"

# The agent and the libraries it links are copied into the guest initrd, so a
# soname change in any of them must rebuild the image.
DEPEND="
	elibc_glibc? ( sys-libs/glibc:= )
	elibc_musl? ( sys-libs/musl:= )
	seccomp? ( sys-libs/libseccomp:= )
"
# QEMU is the only hypervisor this ebuild configures. Firecracker in runtime-rs
# cannot boot an initrd, and dragonball needs Kata's dragonball-experimental
# guest kernel; both wait for a disk-image rootfs.
RDEPEND="
	${DEPEND}
	app-emulation/virtiofsd
	~sys-kernel/kata-guest-kernel-6.18.35_p202
	|| (
		app-containers/containerd
		app-containers/cri-o
	)
	amd64? ( app-emulation/qemu[qemu_softmmu_targets_x86_64,vhost-net] )
	arm64? ( app-emulation/qemu[fdt,qemu_softmmu_targets_aarch64,vhost-net] )
"
BDEPEND="
	app-arch/libarchive
	app-misc/pax-utils
	dev-build/cmake
"

QA_FLAGS_IGNORED="
	usr/bin/containerd-shim-kata-v2
	usr/bin/kata-ctl
	usr/share/kata-containers/.*
"
# The initrd carries a copy of the host's dynamic loader and libraries for the
# guest, which is the point of building it here.
QA_PREBUILT="usr/share/kata-containers/*"

CONFIG_CHECK="~KVM ~VHOST_VSOCK ~VHOST_NET ~TUN"
ERROR_KVM="Kata needs KVM to start its guest VMs."
ERROR_VHOST_VSOCK="The runtime reaches the in-guest agent over vhost-vsock."

KATA_SHARE="/usr/share/kata-containers"
KATA_INITRD="${KATA_SHARE}/kata-containers-initrd.img"

pkg_setup() {
	linux-info_pkg_setup
	rust_pkg_setup
}

src_unpack() {
	# The vendor tarball is rooted at the source tree: ./vendor (crates),
	# ./.cargo/config.toml (git source replacements) and src/runtime/vendor (Go).
	unpack "${P}.tar.gz"
	tar -xzf "${DISTDIR}/${P}-vendor.tar.gz" -C "${S}" || die
	# The project's .cargo/config.toml outranks CARGO_HOME and already points
	# crates-io and the git sources at ./vendor. The eclass's own "gentoo"
	# source stays on its empty default dir: two sources on one directory is
	# an error in cargo.
	mkdir -p "${ECARGO_VENDOR}" || die
	cargo_gen_config
}

kata_arch() {
	case ${ARCH} in
		amd64) echo x86_64 ;;
		arm64) echo aarch64 ;;
		*) die "unsupported ARCH: ${ARCH}" ;;
	esac
}

# The Makefiles are used only for their sed-based generators. Their build
# rules pin RUSTFLAGS to "--deny warnings" inline, which drops the toolchain
# flags Portage sets and turns every new rustc lint into a build failure.
kata_make() {
	# Portage exports ARCH=amd64; the Makefiles want the uname -m spelling.
	emake ARCH="$(kata_arch)" LIBC=gnu PREFIX=/usr BINDIR=/usr/bin "$@"
}

src_configure() {
	kata_make -C src/agent src/version.rs
	kata_make -C src/tools/kata-ctl src/ops/version.rs
	kata_make -C src/runtime-rs crates/shim/src/config.rs \
		config/configuration-qemu-runtime-rs.toml

	# The guest rootfs is an initrd built below, not a disk image; the two keys
	# are mutually exclusive in the hypervisor section.
	local conf=src/runtime-rs/config/configuration-qemu-runtime-rs.toml
	local from="image = \"${KATA_SHARE}/kata-containers.img\""
	grep -qxF "${from}" "${conf}" || die "image key not found in ${conf}"
	sed -i "s|^${from}\$|initrd = \"${KATA_INITRD}\"|" "${conf}" || die

	cargo_src_configure
}

src_compile() {
	cargo_src_compile -p runtime-rs -p kata-ctl

	local features=( init-data $(usev seccomp) )
	cargo_src_compile -p kata-agent --features "${features[*]}"

	kata_build_initrd
}

# The guest rootfs: the agent runs as PID 1 (Kata's AGENT_INIT=yes), so the
# VM carries no init system at all, and the agent mounts /proc, /sys, /dev and
# /run itself. What is copied in is the agent plus the shared objects it links,
# taken from this system. The archive is written from an mtree manifest so the
# root ownership and /dev/console node need no privileges.
kata_build_initrd() {
	local root="${WORKDIR}/rootfs" agent
	agent="$(cargo_target_dir)/kata-agent"
	[[ -x ${agent} ]] || die "kata-agent was not built"

	# Start from nothing on every run: anything left over would be archived.
	rm -rf "${root}" || die
	mkdir -p "${root}"/{dev,etc,proc,run,sbin,sys,tmp,usr/bin,usr/$(get_libdir)} || die
	cp "${agent}" "${root}/usr/bin/kata-agent" || die
	# Portage strips what it installs, not what is packed inside a file it
	# installs; unstripped, the agent alone is ~34 MiB of every guest's RAM.
	if ! has nostrip ${FEATURES} && ! has strip ${RESTRICT}; then
		$(tc-getSTRIP) --strip-unneeded "${root}/usr/bin/kata-agent" || die
	fi

	# lddtree -l prints the binary first, then the loader and every library
	# it resolves. The loader keeps its path (it is hardcoded in PT_INTERP);
	# libraries go flat into the default search directory, because the guest
	# has no ld.so.cache to find e.g. libgcc_s under /usr/lib/gcc.
	local -a libs
	local lib
	mapfile -t libs < <(lddtree -l "${agent}" | tail -n +2)
	[[ ${#libs[@]} -gt 0 ]] || die "lddtree found no libraries for kata-agent"
	for lib in "${libs[@]}"; do
		# glibc names it ld-linux*, musl ld-musl-*.
		if [[ ${lib##*/} == ld-linux* || ${lib##*/} == ld-musl-* ]]; then
			cp -L --parents "${lib}" "${root}/" || die
		else
			cp -L "${lib}" "${root}/usr/$(get_libdir)/" || die
		fi
	done
	echo kata > "${root}/etc/hostname" || die

	# Parents must precede children in the archive: the kernel's initramfs
	# unpacker does not create missing directories.
	local spec="${WORKDIR}/initrd.mtree" path rel mode
	{
		echo "#mtree"
		echo "/set uid=0 gid=0 time=0.0"
		while IFS= read -r -d '' path; do
			rel=".${path#"${root}"}"
			if [[ -L ${path} ]]; then
				echo "${rel} type=link link=$(readlink "${path}")"
			elif [[ -d ${path} ]]; then
				echo "${rel} type=dir mode=0755"
			else
				mode=0644
				[[ -x ${path} ]] && mode=0755
				echo "${rel} type=file mode=${mode} contents=${path}"
			fi
		done < <(find "${root}" -mindepth 1 -print0 | sort -z)
		echo "./dev/console type=char device=native,5,1 mode=0600"
		echo "./init type=link link=/usr/bin/kata-agent"
		echo "./sbin/init type=link link=/usr/bin/kata-agent"
	} > "${spec}" || die

	bsdtar --format newc -cf - "@${spec}" | gzip -9n > "${WORKDIR}/initrd.img" ||
		die "failed to write the initrd"
	# A guest that cannot exec its init panics before any log reaches the
	# host, so check the archive here instead.
	bsdtar -tf "${WORKDIR}/initrd.img" | grep -qx "./usr/bin/kata-agent" ||
		die "initrd lacks the agent"
}

src_install() {
	local target
	target="$(cargo_target_dir)"
	dobin "${target}"/{containerd-shim-kata-v2,kata-ctl}

	insinto "${KATA_SHARE}"
	newins "${WORKDIR}/initrd.img" "${KATA_INITRD##*/}"

	insinto /usr/share/defaults/kata-containers/runtime-rs
	doins src/runtime-rs/config/configuration-qemu-runtime-rs.toml
	dosym configuration-qemu-runtime-rs.toml \
		/usr/share/defaults/kata-containers/runtime-rs/configuration.toml

	dodoc README.md
}

pkg_postinst() {
	elog "Check that this host can run Kata guests with:"
	elog "  kata-ctl check all"
	elog "On x86_64 that check only knows Intel CPUs in ${PV}: on AMD it reports"
	elog "missing GenuineIntel/vmx even when KVM works."
	elog "The host-to-guest vsock fails with ENODEV while vmw_vsock_vmci_transport"
	elog "(VMware guest/host support) is loaded alongside vhost_vsock."
	elog "Containerd picks the shim up by runtime name, for example:"
	elog "  nerdctl run --runtime io.containerd.kata.v2 --rm alpine uname -r"
	elog "Local changes belong in /etc/kata-containers/runtime-rs/configuration.toml,"
	elog "which takes precedence over the defaults in"
	elog "/usr/share/defaults/kata-containers/runtime-rs/."
}