diff --git a/mkosi/mkosi.conf.d/debian-ubuntu/mkosi.prepare b/mkosi/mkosi.conf.d/debian-ubuntu/mkosi.prepare index 3a4415c547..8a166cd166 100755 --- a/mkosi/mkosi.conf.d/debian-ubuntu/mkosi.prepare +++ b/mkosi/mkosi.conf.d/debian-ubuntu/mkosi.prepare @@ -40,6 +40,7 @@ if [[ $deltaver -le 0 ]]; then exit 0 fi +declare -A packages=() distro_packages=() devel_packages=() @@ -48,7 +49,18 @@ while IFS=$'\t' read -r package source package_architecture; do [[ $package_architecture == "$DISTRIBUTION_ARCHITECTURE" || $package_architecture == all ]] || continue [[ $package != *-dbgsym* ]] || continue - devel_packages+=("$package=$(dpkg-query --root="$BUILDROOT" --showformat='${Version}' --show "$package")") + packages["$package"]= +done < <(dpkg-query --root="$BUILDROOT" --show --showformat='${Package}\t${source:Package}\t${Architecture}\n') + +# Prepare scripts run before VolatilePackages are installed, so include them explicitly. +while read -r package; do + [[ $package != *-dbgsym* ]] || continue + + packages["$package"]= +done < <(jq --raw-output '.VolatilePackages[]' <"$MKOSI_CONFIG") + +for package in "${!packages[@]}"; do + devel_packages+=("$package=$develver") package_versions=$(apt-cache madison "$package") if awk -F '|' -v version="$distrover" ' @@ -63,7 +75,7 @@ while IFS=$'\t' read -r package source package_architecture; do ' <<<"$package_versions"; then distro_packages+=("$package=$distrover") fi -done < <(dpkg-query --root="$BUILDROOT" --show --showformat='${binary:Package}\t${source:Package}\t${Architecture}\n') +done if (( ${#distro_packages[@]} == 0 )); then echo "Skipping upgrade test, no distro packages found" diff --git a/test/units/TEST-88-UPGRADE.sh b/test/units/TEST-88-UPGRADE.sh index 90443dd09c..4f0b620db5 100755 --- a/test/units/TEST-88-UPGRADE.sh +++ b/test/units/TEST-88-UPGRADE.sh @@ -44,20 +44,181 @@ if command -v udevadm >/dev/null && systemctl is-active --quiet systemd-udevd.se udev=1 fi +: >/tmp/failed-units if [[ $($unitscmd --output json | jq length) -gt 0 ]]; then echo 'Systemd failed units found before the test:' $unitscmd $unitscmd --output json | jq -r '.[].unit' >/tmp/failed-units fi +save_unit_state() { + local snapshot_failed="" trigger_list unit + local unit_files=/tmp/unit-files-before-package-operation.json + local units=/tmp/units-before-package-operation.json + local -a state_files=(/tmp/active-units /tmp/preexisting-units /tmp/triggered-units) + local -a triggers + + rm -f "${state_files[@]}" "$unit_files" "$units" + if ! systemctl list-units --all '*systemd*' --output json >"$units" || + ! systemctl list-unit-files '*systemd*' --output json >"$unit_files" || + ! jq -r '.[].unit' "$units" >/tmp/preexisting-units || + ! jq -r '.[].unit_file' "$unit_files" >>/tmp/preexisting-units || + ! jq -r ' + .[] + | select(.active == "active" or .active == "activating" or .active == "reloading") + | .unit + ' "$units" >/tmp/active-units; then + echo 'Failed to save unit state before package operation, unit retries will be skipped.' + snapshot_failed=1 + fi + + if [[ -z $snapshot_failed ]]; then + : >/tmp/triggered-units + while read -r unit; do + if ! trigger_list=$(systemctl show -P Triggers "$unit"); then + echo 'Failed to save unit triggers before package operation, unit retries will be skipped.' + snapshot_failed=1 + break + fi + + if [[ -n $trigger_list ]]; then + read -ra triggers <<<"$trigger_list" + printf '%s\n' "${triggers[@]}" >>/tmp/triggered-units + fi + done >"$paths"; then + echo "Failed to inspect $package_set packages, removed units will be treated as failures." + rm -f "$paths" + return 0 + fi + elif ! rpm -qlp "$package" >>"$paths"; then + echo "Failed to inspect $package_set packages, removed units will be treated as failures." + rm -f "$paths" + return 0 + fi + done + + if [[ -z $packages_found ]]; then + echo "No $package_set packages found, removed units will be treated as failures." + return 0 + fi + + sed -n -E 's#^(\./|/)?(usr/)?lib/systemd/system/([^/]+)$#\3#p' "$paths" | + sort -u >"$units" + rm -f "$paths" +} + +unit_removal_expected() { + local operation=$1 unit=$2 + local template units + + if [[ $operation == downgrade ]]; then + units=/tmp/distro-units + else + units=/tmp/devel-units + fi + + [[ -s $units ]] || return 1 + grep -sxqF "$unit" "$units" && return 1 + + if [[ $unit == *@*.* ]]; then + template="${unit%%@*}@.${unit##*.}" + grep -sxqF "$template" "$units" && return 1 + fi + + return 0 +} + check_sd() { - local unit fail=0 timer1_new timer2_new + local operation=$1 load_state unit fail=0 timer1_new timer2_new if ! systemctl daemon-reload; then echo 'System manager reload failed after the test!' fail=1 fi + # Reset units activated during package replacement, then restart only units + # that were previously running or are newly introduced. + rm -f /tmp/restart-units /tmp/retry-units + if [[ -e /tmp/active-units && -e /tmp/preexisting-units && -e /tmp/triggered-units ]]; then + : >/tmp/restart-units + : >/tmp/retry-units + for unit in $($unitscmd --output json | jq -r '.[].unit'); do + if grep -sxqF "$unit" /tmp/failed-units; then + continue + fi + + if ! grep -sxqF "$unit" /tmp/active-units && + ! grep -sxqF "$unit" /tmp/triggered-units && + grep -sxqF "$unit" /tmp/preexisting-units; then + continue + fi + + if ! load_state=$(systemctl show -P LoadState "$unit"); then + fail=1 + continue + fi + + if [[ $load_state == not-found ]]; then + if unit_removal_expected "$operation" "$unit"; then + systemctl reset-failed "$unit" || true + else + fail=1 + systemctl status "$unit" || true + fi + continue + fi + + if ! systemctl reset-failed "$unit"; then + fail=1 + continue + fi + + if grep -sxqF "$unit" /tmp/active-units || ! grep -sxqF "$unit" /tmp/preexisting-units; then + echo "$unit" >>/tmp/restart-units + else + echo "$unit" >>/tmp/retry-units + fi + done + + while read -r unit; do + if ! systemctl start "$unit"; then + fail=1 + systemctl status "$unit" || true + fi + done