From c0f3d212f5537674cbe41129cfdec667d9044b81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vin=C3=ADcius=20Ferr=C3=A3o?= <2031761+viniciusferrao@users.noreply.github.com> Date: Tue, 13 Oct 2026 15:29:27 -0300 Subject: [PATCH] fix(packimage): keep a checksum symlink through a failed publication A failed publication put back only the content of the previous checksum file. A checksum symlink became a regular file, which a later switch of the symlinks would leave stale. publish_rootimg now keeps the previous checksum entry, and a rollback renames it back. When that rename fails, the previous content goes back, and the error reports it. --- xCAT-server/lib/perl/xCAT/SvrUtils.pm | 51 ++++++++++++++++++++------- 1 file changed, 38 insertions(+), 13 deletions(-) diff --git a/xCAT-server/lib/perl/xCAT/SvrUtils.pm b/xCAT-server/lib/perl/xCAT/SvrUtils.pm index 6f2c44f9e..84682d269 100644 --- a/xCAT-server/lib/perl/xCAT/SvrUtils.pm +++ b/xCAT-server/lib/perl/xCAT/SvrUtils.pm @@ -2859,7 +2859,7 @@ sub publish_rootimg { } # Afterwards, only a staging directory that keeps previous files for a failed publication holds them. - unlink(grep { -e $_ || -l $_ } ($state{previous}, "$state{previous}.metainfo")) if !$state{retained}; + unlink(grep { -e $_ || -l $_ } map { "$state{previous}$_" } ('', '.metainfo', '.sha256')) if !$state{retained}; _release_publish_lock($lock); return if !defined($error); return "Cannot publish $target: $error"; @@ -2883,6 +2883,26 @@ sub _replace_rootimg { # A failed publication puts the previous archive back from a link, or from a copy without hard links. return "$!" if -e $target && !_keep_copy($target, $previous); return "$!" if !-s $staged || chmod(0644, @files) != @files; + my $error = _replace_sums($state); + return $error if $error; + + # The previous metainfo leaves first, so it never describes the new archive. + if (-e "$target.metainfo") { + $state->{metainfo_moved} = rename("$target.metainfo", "$previous.metainfo") or return "$!"; + } + $state->{replaced} = rename($staged, $target) or return "$!"; + return "$!" if -e "$staged.metainfo" && !rename("$staged.metainfo", "$target.metainfo"); + return; +} + +# Writes the checksum file of the publication in $state for the time that the archive is replaced, and returns +# why it cannot. +sub _replace_sums { + my ($state) = @_; + my ($staged, $target, $previous) = @$state{qw(staged target previous)}; + + # A rollback renames the previous checksum entry back, so a symlink stays a symlink. + $state->{sums_entry} = (-e "$target.sha256" || -l "$target.sha256") && link("$target.sha256", "$previous.sha256"); # Until the archive is replaced, the checksum file lists the new and the previous archive, so the published # archive matches a line of it, also when the publisher is killed. @@ -2892,19 +2912,12 @@ sub _replace_rootimg { ($previous_line, $error) = _rootimg_checksum_line($target) if !defined($previous_line) && -e $target; return $error if $error; $state->{sums_changed} = 1; - $error = _write_published_file("$target.sha256", _read_published_file("$staged.sha256") . ($previous_line // '')); - return $error if $error; - } elsif (defined($state->{sums})) { + return _write_published_file("$target.sha256", _read_published_file("$staged.sha256") . ($previous_line // '')); + } + if (defined($state->{sums})) { $state->{sums_changed} = 1; unlink("$target.sha256") or return "$!"; } - - # The previous metainfo leaves first, so it never describes the new archive. - if (-e "$target.metainfo") { - $state->{metainfo_moved} = rename("$target.metainfo", "$previous.metainfo") or return "$!"; - } - $state->{replaced} = rename($staged, $target) or return "$!"; - return "$!" if -e "$staged.metainfo" && !rename("$staged.metainfo", "$target.metainfo"); return; } @@ -2960,13 +2973,25 @@ sub _restore_rootimg { push @errors, "the previous metainfo cannot be put back: $error" . (@kept ? '. It is kept in ' . join(' and ', @kept) : ', nor kept'); } if ($state->{sums_changed}) { - my $error = defined($state->{sums}) ? _write_published_file("$target.sha256", $state->{sums}) - : (-e "$target.sha256" && !unlink("$target.sha256")) ? "Cannot remove $target.sha256: $!" : undef; + my $error = _restore_sums($state); push @errors, "the previous checksum cannot be put back: $error" if $error; } return @errors ? join(', and ', @errors) : undef; } +# Puts back the previous checksum file of the publication in $state, or returns why it cannot. The kept entry goes +# back as it was, also a symlink. Otherwise the previous content goes back, so that nodes can check the archive. +sub _restore_sums { + my ($state) = @_; + my ($target, $previous) = @$state{qw(target previous)}; + return if $state->{sums_entry} && rename("$previous.sha256", "$target.sha256"); + my $entry_error = $state->{sums_entry} ? "$!" : undef; + my $error = defined($state->{sums}) ? _write_published_file("$target.sha256", $state->{sums}) + : (-e "$target.sha256" && !unlink("$target.sha256")) ? "Cannot remove $target.sha256: $!" : undef; + return $error if $error; + return defined($entry_error) ? "$entry_error. Its previous content went back instead" : undef; +} + #-------------------------------------------------------------------------------------------------------- #publish_missing_rootimg_checksums: #description: write the checksum of each published compressed rootimage of an image directory that has