fix(restore): preserve versions across cleanup and failed reads

Reject replacement markers and legacy objects before restored-copy cleanup
can mutate their type or data. Prevent restore lookups and failure cleanup
from purging dangling versions, preserve Transition on metadata rereads,
and return without writing when the initial restore read fails.

Add real-disk regressions for stale null tasks, minority metadata, initial
and cleanup read failures, and stored marker/legacy types. Keep ordinary
dangling cleanup and marker purge as positive controls.

Signed-off-by: Feng Ruohang <rh@vonng.com>
This commit is contained in:
Feng Ruohang
2026-11-03 22:32:33 +08:00
parent e7103d77cb
commit 8579466557
4 changed files with 318 additions and 12 deletions
+22 -10
View File
@@ -1372,6 +1372,26 @@ func (x *xlMetaV2) DeleteVersion(fi FileInfo) (string, error) {
}
}
if fi.ExpireRestored {
i, ver, err := x.findVersion(uv)
if err != nil {
return "", err
}
// Check the stored type and transition before any marker, legacy or
// replication mutation. Only completed tiered objects own a cache.
if ver.Type != ObjectType || string(ver.ObjectV2.MetaSys[metaTierStatus]) != lifecycle.TransitionComplete {
return "", nil
}
ver.ObjectV2.RemoveRestoreHdrs()
if err := x.setIdx(i, *ver); err != nil {
return "", err
}
if x.SharedDataDirCount(ver.ObjectV2.VersionID, ver.ObjectV2.DataDir) > 0 {
return "", nil
}
return uuid.UUID(ver.ObjectV2.DataDir).String(), nil
}
var ventry xlMetaV2Version
if fi.Deleted {
ventry = xlMetaV2Version{
@@ -1496,16 +1516,8 @@ func (x *xlMetaV2) DeleteVersion(fi FileInfo) (string, error) {
if err != nil {
return "", err
}
switch {
case fi.ExpireRestored:
// Only a completed transition has a remote copy. Trust the selected
// stored version, not the transition status supplied by the caller.
if string(ver.ObjectV2.MetaSys[metaTierStatus]) != lifecycle.TransitionComplete {
return "", nil
}
ver.ObjectV2.RemoveRestoreHdrs()
err = x.setIdx(i, *ver)
case fi.TransitionStatus == lifecycle.TransitionComplete:
switch fi.TransitionStatus {
case lifecycle.TransitionComplete:
ver.ObjectV2.SetTransition(fi)
ver.ObjectV2.ResetInlineData()
err = x.setIdx(i, *ver)