From 17fe96e6201142b0901f07a2ca7e1c3351b1a394 Mon Sep 17 00:00:00 2001 From: Chris Lu Date: Mon, 10 Aug 2026 18:42:53 -0700 Subject: [PATCH] filer: fingerprint every read-relevant chunk field The layout binding hashed only offset and file id, so a mutation that kept both - a truncate shrinking chunk.Size, then a sparse extend back to the original length - passed both the size and fingerprint checks and served a stale view. Digest size, modification timestamp, cipher key, compression, manifest status, and SSE type as well. --- weed/server/filer_server_format.go | 8 ++++++-- weed/server/filer_server_format_test.go | 22 ++++++++++++++++++++++ 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/weed/server/filer_server_format.go b/weed/server/filer_server_format.go index 0c91b92fa..d334ccc05 100644 --- a/weed/server/filer_server_format.go +++ b/weed/server/filer_server_format.go @@ -45,11 +45,15 @@ const ( defaultFormatChunkSizeMB = 4 ) -// formatChunkIdentity digests the chunk list a layout was written against. +// formatChunkIdentity digests the chunk list a layout was written against, +// covering every field that changes what a read returns: a FUSE truncate, for +// one, mutates Size while keeping the chunk's id. func formatChunkIdentity(chunks []*filer_pb.FileChunk) []byte { digest := md5.New() for _, chunk := range chunks { - fmt.Fprintf(digest, "%d:%s;", chunk.Offset, chunk.GetFileIdString()) + fmt.Fprintf(digest, "%d:%s:%d:%d:%x:%t:%t:%d;", + chunk.Offset, chunk.GetFileIdString(), chunk.Size, chunk.ModifiedTsNs, + chunk.CipherKey, chunk.IsCompressed, chunk.IsChunkManifest, chunk.SseType) } return digest.Sum(nil) } diff --git a/weed/server/filer_server_format_test.go b/weed/server/filer_server_format_test.go index e980d3406..f95d0e596 100644 --- a/weed/server/filer_server_format_test.go +++ b/weed/server/filer_server_format_test.go @@ -68,4 +68,26 @@ func TestFormatChunkIdentity(t *testing.T) { if bytes.Equal(identity, formatChunkIdentity(changedOffset)) { t.Fatalf("identity ignored an offset change") } + // a truncate mutates Size while keeping the chunk id + changedSize := []*filer_pb.FileChunk{ + {FileId: "1,ab", Offset: 0, Size: 10}, + {FileId: "2,cd", Offset: 10, Size: 15}, + } + if bytes.Equal(identity, formatChunkIdentity(changedSize)) { + t.Fatalf("identity ignored a size change") + } + changedMtime := []*filer_pb.FileChunk{ + {FileId: "1,ab", Offset: 0, Size: 10}, + {FileId: "2,cd", Offset: 10, Size: 20, ModifiedTsNs: 7}, + } + if bytes.Equal(identity, formatChunkIdentity(changedMtime)) { + t.Fatalf("identity ignored a modification timestamp change") + } + changedManifest := []*filer_pb.FileChunk{ + {FileId: "1,ab", Offset: 0, Size: 10}, + {FileId: "2,cd", Offset: 10, Size: 20, IsChunkManifest: true}, + } + if bytes.Equal(identity, formatChunkIdentity(changedManifest)) { + t.Fatalf("identity ignored a manifest flag change") + } }