filer.remote.sync: stamp entries with IF_ENTRY_EQUAL so stale inline content or metadata cannot be restored

The IF_CHUNKS_EQUAL guard compared only the chunk fid multiset, so a
rewrite that touched inline content or metadata alone still compared
equal and the stale snapshot overwrote the live entry. The new clause
compares the whole stored entry against the event's entry under the
same path lock.
This commit is contained in:
Chris Lu committed 2026-09-23 16:34:29 -07:00
1 parent 071d9d2f3c
commit 4f1178e0ba
7 files changed
+206 -144

No files matched your search

+13 -19
View File
@@ -420,21 +420,22 @@ func shouldSendToRemote(entry *filer_pb.Entry) bool {
}
// updateLocalEntry stamps the entry an event described with its RemoteEntry.
// The write carries IF_CHUNKS_EQUAL over the event's chunk fids: the filer
// deletes every stored chunk absent from an updated entry, so a snapshot
// older than the live entry (the file was rewritten while its upload was in
// flight, or the event is a replay) would delete the live chunks. A failed
// precondition means the filer moved past this event; the event that
// superseded it follows in the log and stamps the current entry, so the stale
// stamp is skipped the same way a superseded upload is.
// The write carries IF_ENTRY_EQUAL over the event's entry: the filer deletes
// every stored chunk absent from an updated entry, so a snapshot older than
// the live entry (the file was rewritten while its upload was in flight, or
// the event is a replay) would delete the live chunks. A failed precondition
// means the filer moved past this event; the event that superseded it follows
// in the log and stamps the current entry, so the stale stamp is skipped the
// same way a superseded upload is.
func updateLocalEntry(filerClient filer_pb.FilerClient, dir string, entry *filer_pb.Entry, remoteEntry *filer_pb.RemoteEntry) error {
remoteEntry.LastLocalSyncTsNs = time.Now().UnixNano()
expected := proto.Clone(entry).(*filer_pb.Entry)
entry.RemoteEntry = remoteEntry
err := filerClient.WithFilerClient(false, func(client filer_pb.SeaweedFilerClient) error {
_, err := client.UpdateEntry(context.Background(), &filer_pb.UpdateEntryRequest{
Directory: dir,
Entry: entry,
Condition: ifChunksEqual(entry),
Condition: ifEntryEqual(expected),
})
return err
})
@@ -445,18 +446,11 @@ func updateLocalEntry(filerClient filer_pb.FilerClient, dir string, entry *filer
return err
}
// ifChunksEqual builds the precondition that the stored entry still holds
// exactly the chunks the event described. An entry with inline content and no
// chunks yields an empty fid list, which the filer satisfies only while the
// stored entry has no chunks either.
func ifChunksEqual(entry *filer_pb.Entry) *filer_pb.WriteCondition {
chunks := entry.GetChunks()
fids := make([]string, 0, len(chunks))
for _, chunk := range chunks {
fids = append(fids, chunk.GetFileIdString())
}
// ifEntryEqual builds the precondition that the stored entry still equals the
// one the event described: chunk fids, inline content, and metadata alike.
func ifEntryEqual(entry *filer_pb.Entry) *filer_pb.WriteCondition {
return &filer_pb.WriteCondition{
Clauses: []*filer_pb.WriteCondition_Clause{{Kind: filer_pb.WriteCondition_IF_CHUNKS_EQUAL, Fids: fids}},
Clauses: []*filer_pb.WriteCondition_Clause{{Kind: filer_pb.WriteCondition_IF_ENTRY_EQUAL, ExpectedEntry: entry}},
}
}