sysupdate: Allow vacuuming partially downloaded updates and improve error reporting in that case (#41775)
See the commit messages for details. Fixes: https://github.com/systemd/systemd/issues/41502
This commit is contained in:
@@ -139,9 +139,11 @@ static int resource_load_from_directory_recursive(
|
||||
if ((stripped = startswith(de->d_name, ".sysupdate.partial."))) {
|
||||
de_d_name_stripped = stripped;
|
||||
is_partial = true;
|
||||
is_pending = false;
|
||||
} else if ((stripped = startswith(de->d_name, ".sysupdate.pending."))) {
|
||||
de_d_name_stripped = stripped;
|
||||
is_pending = true;
|
||||
is_partial = false;
|
||||
} else
|
||||
de_d_name_stripped = de->d_name;
|
||||
|
||||
@@ -192,6 +194,9 @@ static int resource_load_from_directory_recursive(
|
||||
if (instance->metadata.mode == MODE_INVALID)
|
||||
instance->metadata.mode = st.st_mode & 0775; /* mask out world-writability and suid and stuff, for safety */
|
||||
|
||||
/* Can’t be both partial and pending. */
|
||||
assert(!(is_partial && is_pending));
|
||||
|
||||
instance->is_partial = is_partial;
|
||||
instance->is_pending = is_pending;
|
||||
}
|
||||
@@ -313,6 +318,9 @@ static int resource_load_from_blockdev(Resource *rr) {
|
||||
if (instance->metadata.read_only < 0)
|
||||
instance->metadata.read_only = instance->partition_info.read_only;
|
||||
|
||||
/* Can’t be both partial and pending. */
|
||||
assert(!(is_partial && is_pending));
|
||||
|
||||
instance->is_partial = is_partial;
|
||||
instance->is_pending = is_pending;
|
||||
}
|
||||
|
||||
@@ -816,10 +816,13 @@ int transfer_vacuum(
|
||||
continue;
|
||||
}
|
||||
|
||||
/* If this is listed among the protected versions, then let's not remove it */
|
||||
if (strv_contains(t->protected_versions, instance->metadata.version) ||
|
||||
(extra_protected_version && streq(extra_protected_version, instance->metadata.version))) {
|
||||
log_debug("Version '%s' is pending/partial but protected, not removing.", instance->metadata.version);
|
||||
/* If this is pending and listed among the protected versions, then let's not remove it.
|
||||
* In future, we will also want to keep partial protected versions, but that’s only useful
|
||||
* once we support resuming downloads. */
|
||||
if (instance->is_pending &&
|
||||
(strv_contains(t->protected_versions, instance->metadata.version) ||
|
||||
(extra_protected_version && streq(extra_protected_version, instance->metadata.version)))) {
|
||||
log_debug("Version '%s' is pending but protected, not removing.", instance->metadata.version);
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -70,6 +70,11 @@ const char* update_set_flags_to_string(UpdateSetFlags flags) {
|
||||
case UPDATE_INSTALLED|UPDATE_PARTIAL|UPDATE_NEWEST|UPDATE_PROTECTED:
|
||||
case UPDATE_INSTALLED|UPDATE_AVAILABLE|UPDATE_PARTIAL|UPDATE_NEWEST:
|
||||
case UPDATE_INSTALLED|UPDATE_AVAILABLE|UPDATE_PARTIAL|UPDATE_NEWEST|UPDATE_PROTECTED:
|
||||
/* can also contain pending instances: */
|
||||
case UPDATE_INSTALLED|UPDATE_PARTIAL|UPDATE_PENDING|UPDATE_NEWEST:
|
||||
case UPDATE_INSTALLED|UPDATE_PARTIAL|UPDATE_PENDING|UPDATE_NEWEST|UPDATE_PROTECTED:
|
||||
case UPDATE_INSTALLED|UPDATE_AVAILABLE|UPDATE_PARTIAL|UPDATE_PENDING|UPDATE_NEWEST:
|
||||
case UPDATE_INSTALLED|UPDATE_AVAILABLE|UPDATE_PARTIAL|UPDATE_PENDING|UPDATE_NEWEST|UPDATE_PROTECTED:
|
||||
return "current+partial";
|
||||
|
||||
case UPDATE_AVAILABLE|UPDATE_NEWEST:
|
||||
|
||||
@@ -410,7 +410,10 @@ static int context_discover_update_sets_by_flag(Context *c, UpdateSetFlags flags
|
||||
extra_flags |= UPDATE_PROTECTED;
|
||||
|
||||
/* Partial or pending updates by definition are not incomplete, they’re
|
||||
* partial/pending instead */
|
||||
* partial/pending instead. While an individual Instance cannot be both partial and
|
||||
* pending, an UpdateSet as a whole can contain both partial and pending instances. */
|
||||
assert(!match || !(match->is_partial && match->is_pending));
|
||||
|
||||
if (match && match->is_partial)
|
||||
extra_flags = (extra_flags | UPDATE_PARTIAL) & ~UPDATE_INCOMPLETE;
|
||||
|
||||
@@ -502,8 +505,9 @@ static int context_discover_update_sets_by_flag(Context *c, UpdateSetFlags flags
|
||||
c->candidate && strverscmp_improved(c->newest_installed->version, c->candidate->version) >= 0)
|
||||
c->candidate = NULL;
|
||||
|
||||
/* Newest installed is still pending and no candidate is set? Then it becomes the candidate. */
|
||||
if (c->newest_installed && FLAGS_SET(c->newest_installed->flags, UPDATE_PENDING) &&
|
||||
/* Newest installed is still pending or partial and no candidate is set? Then it becomes the candidate. */
|
||||
if (c->newest_installed &&
|
||||
(c->newest_installed->flags & (UPDATE_PENDING|UPDATE_PARTIAL)) &&
|
||||
!c->candidate)
|
||||
c->candidate = c->newest_installed;
|
||||
|
||||
@@ -666,7 +670,7 @@ static int context_show_version(Context *c, const char *version) {
|
||||
Instance *i = *inst;
|
||||
|
||||
if (!i) {
|
||||
assert(FLAGS_SET(us->flags, UPDATE_INCOMPLETE));
|
||||
assert(us->flags & (UPDATE_INCOMPLETE|UPDATE_PARTIAL|UPDATE_PENDING));
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -1025,9 +1029,7 @@ static int context_acquire(
|
||||
if (FLAGS_SET(us->flags, UPDATE_INCOMPLETE))
|
||||
log_info("Selected update '%s' is already installed, but incomplete. Repairing.", us->version);
|
||||
else if (FLAGS_SET(us->flags, UPDATE_PARTIAL)) {
|
||||
log_info("Selected update '%s' is already acquired and partially installed. Vacuum it to try installing again.", us->version);
|
||||
|
||||
return 0;
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EUCLEAN), "Selected update '%s' is already acquired and partially installed. Vacuum it to try installing again.", us->version);
|
||||
} else if (FLAGS_SET(us->flags, UPDATE_PENDING)) {
|
||||
log_info("Selected update '%s' is already acquired and pending installation.", us->version);
|
||||
|
||||
|
||||
@@ -867,6 +867,10 @@ static int update_render_progress(sd_event_source *source, void *userdata) {
|
||||
clear_progress_bar_unbuffered(target);
|
||||
fprintf(stderr, "%s: %s Already up-to-date\n", target, GREEN_CHECK_MARK());
|
||||
n--; /* Don't consider this target in the total */
|
||||
} else if (progress == -EUCLEAN) {
|
||||
clear_progress_bar_unbuffered(target);
|
||||
fprintf(stderr, "%s: %s Update is already acquired and partially installed. Vacuum it to try installing again.\n", target, RED_CROSS_MARK());
|
||||
total += 100;
|
||||
} else if (progress < 0) {
|
||||
clear_progress_bar_unbuffered(target);
|
||||
fprintf(stderr, "%s: %s %s\n", target, RED_CROSS_MARK(), STRERROR(progress));
|
||||
|
||||
@@ -66,6 +66,7 @@ update_checksums_with_best_before() {
|
||||
new_version() {
|
||||
local sector_size="${1:?}"
|
||||
local version="${2:?}"
|
||||
local corrupt="${3:-}"
|
||||
|
||||
# Create a pair of random partition payloads, and compress one.
|
||||
# To make not the initial bytes of part1-xxx.raw accidentally match one of the compression header,
|
||||
@@ -90,11 +91,26 @@ new_version() {
|
||||
echo $RANDOM >"$WORKDIR/source/dir-$version/bar.txt"
|
||||
tar --numeric-owner -C "$WORKDIR/source/dir-$version/" -czf "$WORKDIR/source/dir-$version.tar.gz" .
|
||||
|
||||
update_checksums
|
||||
if [[ "$corrupt" == "corrupt-checksum" ]]; then
|
||||
# As requested, add a deliberately corrupt checksum for this file. This
|
||||
# will get overwritten next time update_checksums() is called, but the
|
||||
# integration test will probably have moved on to other things by then.
|
||||
{
|
||||
echo "abad1deaabad1deaabad1deaabad1deaabad1deaabad1deaabad1deaabad1dea part1-$version.raw"
|
||||
echo "abad1deaabad1deaabad1deaabad1deaabad1deaabad1deaabad1deaabad1dea part2-$version.raw"
|
||||
echo "abad1deaabad1deaabad1deaabad1deaabad1deaabad1deaabad1deaabad1dea part2-$version.raw.gz"
|
||||
echo "abad1deaabad1deaabad1deaabad1deaabad1deaabad1deaabad1deaabad1dea uki-$version.efi"
|
||||
echo "abad1deaabad1deaabad1deaabad1deaabad1deaabad1deaabad1deaabad1dea uki-extra-$version.efi"
|
||||
echo "abad1deaabad1deaabad1deaabad1deaabad1deaabad1deaabad1deaabad1dea dir-$version.tar.gz"
|
||||
} >> "$WORKDIR/source/SHA256SUMS"
|
||||
else
|
||||
update_checksums
|
||||
fi
|
||||
}
|
||||
|
||||
update_now() {
|
||||
local update_type="${1:?}"
|
||||
local checks="${2:-}"
|
||||
|
||||
# Update to newest version. First there should be an update ready, then we
|
||||
# do the update, and then there should not be any ready anymore
|
||||
@@ -105,7 +121,10 @@ update_now() {
|
||||
# modes. Some updates in the test suite need to be monolithic (e.g. when
|
||||
# repairing an installation), so that can be overridden via the local.
|
||||
|
||||
"$SYSUPDATE" --verify=no check-new
|
||||
if [[ "$checks" != "no-checks" ]]; then
|
||||
"$SYSUPDATE" --verify=no check-new
|
||||
fi
|
||||
|
||||
if [[ "$update_type" == "monolithic" ]]; then
|
||||
"$SYSUPDATE" --verify=no update
|
||||
elif [[ "$update_type" == "split-offline" ]]; then
|
||||
@@ -125,7 +144,10 @@ update_now() {
|
||||
else
|
||||
exit 1
|
||||
fi
|
||||
(! "$SYSUPDATE" --verify=no check-new)
|
||||
|
||||
if [[ "$checks" != "no-checks" ]]; then
|
||||
(! "$SYSUPDATE" --verify=no check-new)
|
||||
fi
|
||||
}
|
||||
|
||||
verify_version() {
|
||||
@@ -462,6 +484,28 @@ EOF
|
||||
verify_version_current "$blockdev" "$sector_size" v8 1
|
||||
verify_version "$blockdev" "$sector_size" v7 2
|
||||
|
||||
# Create a 9th version but corrupt the checksum in SHA256SUMS so pulling it
|
||||
# fails when verifying the checksum, in order to create a current+partial
|
||||
# state. Try to update again and verify that this results in an error.
|
||||
# Vacuum the partial version, regenerate it on the server, try updating
|
||||
# again and it should succeed.
|
||||
new_version "$sector_size" v9 "corrupt-checksum"
|
||||
(! update_now "$update_type")
|
||||
"$SYSUPDATE" --offline list v9 | grep "partial" >/dev/null
|
||||
verify_version_current "$blockdev" "$sector_size" v8 1
|
||||
# don’t verify the other part of the block device as it’s in an indeterminate state
|
||||
(! update_now "$update_type" "no-checks") |& tee "$WORKDIR"/update_now-9
|
||||
cat "$WORKDIR"/update_now-9
|
||||
grep "is already acquired and partially installed. Vacuum it to try installing again." "$WORKDIR"/update_now-9
|
||||
"$SYSUPDATE" --offline vacuum |& grep "Removing old partial" >/dev/null
|
||||
verify_version_current "$blockdev" "$sector_size" v8 1
|
||||
# don’t verify the other part of the block device as it’s in an indeterminate state
|
||||
"$SYSUPDATE" --verify=no list v9 | grep "candidate" >/dev/null
|
||||
new_version "$sector_size" v9
|
||||
update_now "$update_type"
|
||||
verify_version "$blockdev" "$sector_size" v8 1
|
||||
verify_version_current "$blockdev" "$sector_size" v9 2
|
||||
|
||||
# Cleanup
|
||||
[[ -b "$blockdev" ]] && losetup --detach "$blockdev"
|
||||
rm "$BACKING_FILE"
|
||||
|
||||
Reference in New Issue
Block a user