sysupdate: keep database of installed files/patterns, and use to GC them (#42646)
Transfer files might come and go, components might be enabled and disabled. Patterns might change. Let's keep track of what we install, so that we can automatically gc everything no longer owned by any enabled transfer.
This commit is contained in:
@@ -643,4 +643,219 @@ set -e
|
||||
[[ $rc -ne 134 ]]
|
||||
grep -F "Manifest hash at line 1 decoded to 31 bytes" "$WORKDIR/malformed-manifest/check-new.log" >/dev/null
|
||||
|
||||
# Check the "cleanup" verb and the underlying install database. A successful
|
||||
# update must record an install database entry for every transfer that installs
|
||||
# into the file system, and those entries must cover all installed resources
|
||||
# (regular files as well as directories). Once the transfer file owning some
|
||||
# resources is removed, "systemd-sysupdate cleanup" must delete the now-orphaned
|
||||
# resources (and their install database entries), while leaving resources that
|
||||
# are still owned by a transfer file untouched.
|
||||
INSTALLDB="/var/lib/systemd/sysupdate/installdb"
|
||||
CLEANUP="$WORKDIR/cleanup"
|
||||
rm -rf "$CONFIGDIR" "$INSTALLDB" "$CLEANUP"
|
||||
mkdir -p "$CONFIGDIR" "$CLEANUP/source" "$CLEANUP/target"
|
||||
|
||||
# The "alpha" transfer installs plain regular files, while the "beta" transfer
|
||||
# installs whole directories (each populated with a couple of files), to exercise
|
||||
# the recursive removal of orphaned directory resources during cleanup.
|
||||
cleanup_new_version() {
|
||||
local version="${1:?}"
|
||||
echo "$RANDOM" >"$CLEANUP/source/alpha-$version.bin"
|
||||
rm -rf "$CLEANUP/source/beta-$version"
|
||||
mkdir -p "$CLEANUP/source/beta-$version"
|
||||
echo "$RANDOM" >"$CLEANUP/source/beta-$version/one.txt"
|
||||
echo "$RANDOM" >"$CLEANUP/source/beta-$version/two.txt"
|
||||
(cd "$CLEANUP/source" && sha256sum alpha-* >SHA256SUMS)
|
||||
}
|
||||
|
||||
# Number of install database entries (symlinks) currently recorded.
|
||||
installdb_count() {
|
||||
if [[ -d "$INSTALLDB" ]]; then
|
||||
find "$INSTALLDB" -mindepth 1 -maxdepth 1 -type l | wc -l
|
||||
else
|
||||
echo 0
|
||||
fi
|
||||
}
|
||||
|
||||
# Assert that every resource (file or directory) currently installed in the
|
||||
# target directory is covered by at least one install database entry (i.e. its
|
||||
# name matches a recorded pattern that points at the target directory).
|
||||
assert_installdb_covers_target() {
|
||||
local f base link tgt path pattern glob covered
|
||||
for f in "$CLEANUP/target"/*; do
|
||||
[[ -e "$f" ]] || continue
|
||||
base="$(basename "$f")"
|
||||
covered=0
|
||||
while read -r link; do
|
||||
tgt="$(readlink "$link")"
|
||||
# Entries are stored as "<path>/./<pattern>".
|
||||
path="${tgt%%/./*}"
|
||||
pattern="${tgt#*/./}"
|
||||
# Translate the sysupdate pattern into a shell glob (only @v is used here).
|
||||
glob="${pattern//@v/*}"
|
||||
# shellcheck disable=SC2053
|
||||
if [[ "$path" == "$CLEANUP/target" && "$base" == $glob ]]; then
|
||||
covered=1
|
||||
break
|
||||
fi
|
||||
done < <(find "$INSTALLDB" -mindepth 1 -maxdepth 1 -type l)
|
||||
[[ "$covered" -eq 1 ]] || { echo "Installed resource '$f' not covered by install database" >&2; exit 1; }
|
||||
done
|
||||
}
|
||||
|
||||
# Verify the installed beta-<version> directory matches its source.
|
||||
verify_beta_synced() {
|
||||
local version="${1:?}"
|
||||
test -d "$CLEANUP/target/beta-$version"
|
||||
cmp "$CLEANUP/source/beta-$version/one.txt" "$CLEANUP/target/beta-$version/one.txt"
|
||||
cmp "$CLEANUP/source/beta-$version/two.txt" "$CLEANUP/target/beta-$version/two.txt"
|
||||
}
|
||||
|
||||
cat >"$CONFIGDIR/01-alpha.transfer" <<EOF
|
||||
[Source]
|
||||
Type=regular-file
|
||||
Path=$CLEANUP/source
|
||||
MatchPattern=alpha-@v.bin
|
||||
|
||||
[Target]
|
||||
Type=regular-file
|
||||
Path=$CLEANUP/target
|
||||
MatchPattern=alpha-@v.bin
|
||||
InstancesMax=2
|
||||
EOF
|
||||
|
||||
cat >"$CONFIGDIR/02-beta.transfer" <<EOF
|
||||
[Source]
|
||||
Type=directory
|
||||
Path=$CLEANUP/source
|
||||
MatchPattern=beta-@v
|
||||
|
||||
[Target]
|
||||
Type=directory
|
||||
Path=$CLEANUP/target
|
||||
MatchPattern=beta-@v
|
||||
InstancesMax=2
|
||||
EOF
|
||||
|
||||
# Install two versions; with InstancesMax=2 both are kept for each transfer.
|
||||
cleanup_new_version v1
|
||||
"$SYSUPDATE" --verify=no update
|
||||
cleanup_new_version v2
|
||||
"$SYSUPDATE" --verify=no update
|
||||
|
||||
# All four resources must be installed, and the directory resources must have
|
||||
# been synced over completely.
|
||||
test -f "$CLEANUP/target/alpha-v1.bin"
|
||||
test -f "$CLEANUP/target/alpha-v2.bin"
|
||||
verify_beta_synced v1
|
||||
verify_beta_synced v2
|
||||
|
||||
# The update must have recorded one install database entry per transfer pattern,
|
||||
# and those entries must cover every installed resource.
|
||||
[[ "$(installdb_count)" -eq 2 ]]
|
||||
assert_installdb_covers_target
|
||||
|
||||
# Running cleanup while all transfer files are still in place must be a no-op:
|
||||
# nothing is orphaned, so nothing must be removed.
|
||||
"$SYSUPDATE" cleanup
|
||||
test -f "$CLEANUP/target/alpha-v1.bin"
|
||||
test -f "$CLEANUP/target/alpha-v2.bin"
|
||||
verify_beta_synced v1
|
||||
verify_beta_synced v2
|
||||
[[ "$(installdb_count)" -eq 2 ]]
|
||||
assert_installdb_covers_target
|
||||
|
||||
# Remove the transfer file owning the "beta" directories and clean up. The beta
|
||||
# directories (with all their contents) and their install database entry must be
|
||||
# removed, while the alpha files must be kept since their transfer file is still
|
||||
# in place.
|
||||
rm "$CONFIGDIR/02-beta.transfer"
|
||||
"$SYSUPDATE" cleanup
|
||||
test -f "$CLEANUP/target/alpha-v1.bin"
|
||||
test -f "$CLEANUP/target/alpha-v2.bin"
|
||||
test ! -e "$CLEANUP/target/beta-v1"
|
||||
test ! -e "$CLEANUP/target/beta-v2"
|
||||
[[ "$(installdb_count)" -eq 1 ]]
|
||||
assert_installdb_covers_target
|
||||
|
||||
# Now remove the remaining transfer file and clean up again. The alpha files and
|
||||
# the last install database entry must be removed too.
|
||||
rm "$CONFIGDIR/01-alpha.transfer"
|
||||
"$SYSUPDATE" cleanup
|
||||
test ! -f "$CLEANUP/target/alpha-v1.bin"
|
||||
test ! -f "$CLEANUP/target/alpha-v2.bin"
|
||||
[[ "$(installdb_count)" -eq 0 ]]
|
||||
|
||||
rm -rf "$CONFIGDIR" "$INSTALLDB" "$CLEANUP"
|
||||
|
||||
# Briefly check the "--component-all" switch of the "cleanup" verb. Each component
|
||||
# keeps its own install database (installdb.<component>), and "cleanup
|
||||
# --component-all" must clean up orphaned resources across *all* of them in one
|
||||
# go. Set up two components, install a resource into each, then drop both transfer
|
||||
# files and run a single "cleanup --component-all" — it must remove both
|
||||
# components' resources (and their install database entries).
|
||||
COMPALL="$WORKDIR/component-all"
|
||||
rm -rf "$COMPALL" /run/sysupdate.comp-a.d /run/sysupdate.comp-b.d \
|
||||
/var/lib/systemd/sysupdate/installdb.comp-a /var/lib/systemd/sysupdate/installdb.comp-b
|
||||
mkdir -p "$COMPALL/source" "$COMPALL/target-a" "$COMPALL/target-b" \
|
||||
/run/sysupdate.comp-a.d /run/sysupdate.comp-b.d
|
||||
|
||||
echo "$RANDOM" >"$COMPALL/source/comp-a-v1.bin"
|
||||
echo "$RANDOM" >"$COMPALL/source/comp-b-v1.bin"
|
||||
(cd "$COMPALL/source" && sha256sum comp-* >SHA256SUMS)
|
||||
|
||||
cat >/run/sysupdate.comp-a.d/01-comp-a.transfer <<EOF
|
||||
[Source]
|
||||
Type=regular-file
|
||||
Path=$COMPALL/source
|
||||
MatchPattern=comp-a-@v.bin
|
||||
|
||||
[Target]
|
||||
Type=regular-file
|
||||
Path=$COMPALL/target-a
|
||||
MatchPattern=comp-a-@v.bin
|
||||
InstancesMax=1
|
||||
EOF
|
||||
|
||||
cat >/run/sysupdate.comp-b.d/01-comp-b.transfer <<EOF
|
||||
[Source]
|
||||
Type=regular-file
|
||||
Path=$COMPALL/source
|
||||
MatchPattern=comp-b-@v.bin
|
||||
|
||||
[Target]
|
||||
Type=regular-file
|
||||
Path=$COMPALL/target-b
|
||||
MatchPattern=comp-b-@v.bin
|
||||
InstancesMax=1
|
||||
EOF
|
||||
|
||||
"$SYSUPDATE" --component=comp-a --verify=no update
|
||||
"$SYSUPDATE" --component=comp-b --verify=no update
|
||||
test -f "$COMPALL/target-a/comp-a-v1.bin"
|
||||
test -f "$COMPALL/target-b/comp-b-v1.bin"
|
||||
test -d /var/lib/systemd/sysupdate/installdb.comp-a
|
||||
test -d /var/lib/systemd/sysupdate/installdb.comp-b
|
||||
|
||||
# --component-all is only supported for the "cleanup" verb, refuse it elsewhere.
|
||||
(! "$SYSUPDATE" --component-all --verify=no update)
|
||||
|
||||
# With the transfer files still in place "cleanup --component-all" is a no-op:
|
||||
# nothing is orphaned.
|
||||
"$SYSUPDATE" --component-all cleanup
|
||||
test -f "$COMPALL/target-a/comp-a-v1.bin"
|
||||
test -f "$COMPALL/target-b/comp-b-v1.bin"
|
||||
|
||||
# Drop both transfer files and clean up all components at once. Both resources
|
||||
# (and their install database entries) must now be gone.
|
||||
rm /run/sysupdate.comp-a.d/01-comp-a.transfer /run/sysupdate.comp-b.d/01-comp-b.transfer
|
||||
"$SYSUPDATE" --component-all cleanup
|
||||
test ! -e "$COMPALL/target-a/comp-a-v1.bin"
|
||||
test ! -e "$COMPALL/target-b/comp-b-v1.bin"
|
||||
[[ "$(find /var/lib/systemd/sysupdate/installdb.comp-a -type l | wc -l)" -eq 0 ]]
|
||||
[[ "$(find /var/lib/systemd/sysupdate/installdb.comp-b -type l | wc -l)" -eq 0 ]]
|
||||
|
||||
rm -rf "$COMPALL" /run/sysupdate.comp-a.d /run/sysupdate.comp-b.d \
|
||||
/var/lib/systemd/sysupdate/installdb.comp-a /var/lib/systemd/sysupdate/installdb.comp-b
|
||||
|
||||
touch /testok
|
||||
|
||||
Reference in New Issue
Block a user