#!/bin/sh

PODMAN_ROOT=/var/lib/containers/storage_readonly
CONFDIR=
FAIL_MISSING=
DRY_RUN=

error() {
	printf -- "----------------------------------------------\n" >&2
	printf -- "/!\ %s\n" "$@" >&2
	printf -- "----------------------------------------------\n" >&2
	exit 1
}

warning() {
	case "$SWUPDATE_WARN_FD" in
	4) printf "%s\n" "$@" >&4;;
	*) printf "%s\n" "$@" >&2;;
	esac
}

usage() {
	echo "Usage: $0 [options]"
	echo
	echo "Options:"
	echo " --storage <path> Path to podman storage to cleanup"
	echo " --confdir <path> Path to atmark config directory to check in-use image"
	echo "                  If unset, all images with a tag are kept"
	echo " --fail-missing   Fail if a configured image is not found"
	echo " --dry-run        Print what would be removed instead of removing"
}

get_pod_infra_image_tag() {
	local infra_image

	infra_image=$({ echo 'set_infra_image() { infra_image=$1; }';
			grep -E '^(set_)?infra_image' "$conf" && echo 'echo "$infra_image"'; } | sh)

	if [ -z "$infra_image" ]; then
		# get default name of the pod infra container... in the target rootfs!
		# note: dry run uses running system instead for easy use
		WRAP="podman run --net=none --rootfs /target"
		[ -n "$DRY_RUN" ] && WRAP=""
		infra_image=$($WRAP podman pod create --help | \
					sed -ne 's/.*infra-image.*default "\([^"]*\)".*/\1/p')

		# if this is empty (podman >= 4.0, abos >= 3.16) we can just skip the rest,
		# pod can be built locally
		[ -z "$infra_image" ] && return
	fi

	if ! podman --root "$PODMAN_ROOT" --storage-opt additionalimagestore="" \
			image inspect --format '{{.Id}}' "$infra_image" 2>/dev/null; then
		warning "Warning: pod configured but pod infra container $infra_image was not found!" \
			"Trying to get it now"
		[ -n "$DRY_RUN" ] && return
		# pull command prints the id of the image it just fetched
		if ! podman --root "$PODMAN_ROOT" --storage-opt additionalimagestore="" \
				pull "$infra_image"; then
			[ -n "$FAIL_MISSING" ] || return
			error "Could not pull infra container $infra_image, failing update."
		fi
	fi
}

get_conf_image() {
	local tag type autostart pull
	tag=$({ echo 'set_image() { image="$1"; [ -n "$image" ] || image=unknown; }';
		grep -E '^(set_)?image' "$conf" && echo 'echo "$image"'; } | sh)
	case "$tag" in
	"")
		type=$({ echo 'set_type() { type=$1; }';
			grep -E '^(set_)?type' "$conf" && echo 'echo "$type"'; } | sh)
		case "$type" in
		pod)
			get_pod_infra_image_tag
			return
			;;
		container|"")
			# tag defaults to name, which itself defaults to filename
			tag=$({ grep -E '^name=' "$conf" && echo 'echo "$name"'; } | sh)
			if [ -z "$tag" ]; then
				tag="${conf##*/}"
				tag="${tag%.conf}"
			fi
			;;
		*)
			# network or other new unhandled type
			return
		esac
		;;
	unknown)
		warning "Warning: could not resolve container tag with just grep, executing $conf"
		warning "Please consider using set_image without intermediate variable"
		tag=$(set_image() { echo "$1"; exit 0; };
		      . "$conf" 2>/dev/null; exit 1) \
			|| error "Could not extract image from $conf"
		;;
	esac

	# if tag starts with a / it's likely a rootfs,
	# and anyway it's an "invalid reference format" so skip.
	[ "${tag#/}" != "$tag" ] && return
	# also skip explicit --rootfs
	[ "$tag" = "--rootfs" ] && return

	if ! podman --root "$PODMAN_ROOT" \
			--storage-opt additionalimagestore="" \
			image inspect --format '{{.Id}}' \
			"$tag" 2>/dev/null; then
		[ -n "$FAIL_MISSING" ] || return
		# don't fail if image is no autostart: user can pull themselves
		autostart=$({ echo 'set_autostart() { autostart=$1; }';
			grep -E '^(set_)?autostart' "$conf" && echo 'echo "$autostart"' || echo 'echo yes'; } | sh)
		case "$autostart" in
		[Nn]|[Nn][Oo]|[Ff][Aa][Ll][Ss][Ee]|0|"") return;;
		esac
		# also don't fail if it is set to pull automatically
		pull=$({ echo 'set_pull() { pull=$1; }';
			grep -E '^(set_)?pull' "$conf" && echo 'echo "$pull"'; } | sh)
		case "$pull" in
		always|missing) return;;
		esac
		if [ "$conf" != "${conf#/target}" ] \
		    && cmp -s "$conf" "${conf#/target}" 2>/dev/null; then
			# do not fail update if the config file was already present
			warning "Warning: image $tag in $conf not found in image store !"
		else
			error "image $tag in $conf not found in image store !"
		fi
	fi
}

trim_tags() {
	local conf
	[ -n "$CONFDIR" ] || return
	# if no configuration is present, skip this step
	stat "$CONFDIR/"*.conf >/dev/null 2>&1 || return

	# build list of images in use
	: > "$tmpdir/ids"
	for conf in "$CONFDIR/"*.conf; do
		[ -e "$conf" ] || continue
		get_conf_image >> "$tmpdir/ids"
	done

	# untag any unused image
	podman --root "$PODMAN_ROOT" \
		--storage-opt additionalimagestore="" \
		image list \
		--format '{{.Id}}' |
		sort -u |
		while read -r id; do
			grep -q -F "$id" "$tmpdir/ids" && continue
			if [ -n "$DRY_RUN" ]; then
				echo "Would remove tag from $id"
				continue
			fi
			podman --root "$PODMAN_ROOT" \
				--storage-opt additionalimagestore="" \
				untag "$id"
		done
}

podman_prune() {
	# skip dry run
	[ -n "$DRY_RUN" ] && return

	# Image prune --external was added in podman 4.x (abos 3.16+, 2022/06)
	# We assume almost no-one will run newer mkswu on such old images, but
	# it is still supported so fallback to non-external on error.
	# (Error is not hidden on purpose, will try harder if someone complains)
	podman --root "$PODMAN_ROOT" \
		--storage-opt additionalimagestore="" \
		image prune -f --external \
		|| podman --root "$PODMAN_ROOT" \
			--storage-opt additionalimagestore="" \
			image prune -f
}

cleanup_images() {
	local tmpdir

	mkdir -p "${TMPDIR:-/var/tmp}/scripts"
	tmpdir=$(mktemp -d "${TMPDIR:-/var/tmp}/scripts/mkswu_podman_cleanup.XXXXXX") \
		|| error "Could not create tmpdir in ${TMPDIR:-/var/tmp}"
	trap "rm -rf '$tmpdir'" EXIT

	trim_tags
	podman_prune

	# storage directory cannot be moved if that file is kept around
	# it only contains state data that is safe to delete
	rm -f "$PODMAN_ROOT/libpod/bolt_state.db" "$PODMAN_ROOT/db.sql"
}

while [ $# -ge 1 ]; do
	case "$1" in
	"--storage")
		[ $# -ge 2 ] || error "$1 needs an argument"
		PODMAN_ROOT="$2"
		shift 2
		;;
	"--confdir")
		[ $# -ge 2 ] || error "$1 needs an argument"
		CONFDIR="$2"
		shift 2
		;;
	"--fail-missing")
		FAIL_MISSING=1
		shift
		;;
	"--dry-run")
		DRY_RUN=1
		shift
		;;
	"--")
		shift
		break
		;;
	"-h"|"--help")
		usage
		exit 0
		;;
	*)
		usage
		error "invalid argument: $1"
		;;
	esac
done

cleanup_images
