diff --git a/sync/verify.go b/sync/verify.go index cdd5ff01..0c8e1a9b 100644 --- a/sync/verify.go +++ b/sync/verify.go @@ -21,26 +21,37 @@ func verifyBlockCopy(d *disk.Disk, from, to int, expectedSize int64) error { if err != nil { return err } - // create a sha256sum of both partitions and compare - // but limit it to expectedSize + + // Source partition must hold at least expectedSize bytes — the + // copy reported writing that many, so reading less than that back + // indicates a real inconsistency. + if got := origPart.GetSize(); got < expectedSize { + return fmt.Errorf("original partition size %d is smaller than expected size %d", got, expectedSize) + } + // Target partition must hold at least expectedSize bytes; it may + // be larger (the common case when growing a partition before a + // later swap+delete renames it into place). The verification only + // needs to confirm the leading expectedSize bytes match. + if got := targetPart.GetSize(); got < expectedSize { + return fmt.Errorf("target partition size %d is smaller than expected size %d", got, expectedSize) + } + + // Hash the leading expectedSize bytes of both partitions and + // compare. NewLimitWriter caps the hasher's input, and + // origPart.ReadContents would otherwise feed all of the source + // partition's bytes into the hasher — for our purposes, we want + // only expectedSize bytes hashed on each side regardless of how + // large either partition is. origHasher := sha256.New() - size, err := origPart.ReadContents(d.Backend, origHasher) - if err != nil { + if _, err := origPart.ReadContents(d.Backend, NewLimitWriter(origHasher, expectedSize)); err != nil { return err } - if size != expectedSize { - return fmt.Errorf("original partition size %d is different than expected size %d", size, expectedSize) - } origResult := origHasher.Sum(nil) targetHasher := sha256.New() - size, err = targetPart.ReadContents(d.Backend, NewLimitWriter(targetHasher, expectedSize)) - if err != nil { + if _, err := targetPart.ReadContents(d.Backend, NewLimitWriter(targetHasher, expectedSize)); err != nil { return err } - if size != expectedSize { - return fmt.Errorf("target partition size %d is different than expected size %d", size, expectedSize) - } targetResult := targetHasher.Sum(nil) if !bytes.Equal(origResult, targetResult) { diff --git a/sync/verify_blockcopy_test.go b/sync/verify_blockcopy_test.go new file mode 100644 index 00000000..b2dc0d91 --- /dev/null +++ b/sync/verify_blockcopy_test.go @@ -0,0 +1,88 @@ +package sync + +import ( + "os" + "path/filepath" + "testing" + + diskfs "github.com/diskfs/go-diskfs" + "github.com/diskfs/go-diskfs/disk" + "github.com/diskfs/go-diskfs/partition/gpt" +) + +// TestVerifyBlockCopyTargetLargerThanSource covers the grow case: a smaller +// source partition copied into a larger target. verifyBlockCopy must compare +// only the leading expectedSize bytes and accept a target that is larger than +// the source, while still catching a real mismatch within that region. +func TestVerifyBlockCopyTargetLargerThanSource(t *testing.T) { + const ( + sectorSize = 512 + diskSize = 64 * 1024 * 1024 + srcStart = 2048 // sectors => 1 MiB in + tgtStart = srcStart + 32768 // 16 MiB after source start + srcSize = 8 * 1024 * 1024 // source partition + tgtSize = 24 * 1024 * 1024 // target is 3x larger + expected = int64(srcSize) + ) + + imgPath := filepath.Join(t.TempDir(), "disk.img") + d, err := diskfs.Create(imgPath, diskSize, sectorSize) + if err != nil { + t.Fatalf("create disk: %v", err) + } + table := &gpt.Table{ + LogicalSectorSize: sectorSize, + PhysicalSectorSize: sectorSize, + ProtectiveMBR: true, + Partitions: []*gpt.Partition{ + {Index: 1, Start: srcStart, Size: srcSize, Type: gpt.LinuxFilesystem, Name: "source"}, + {Index: 2, Start: tgtStart, Size: tgtSize, Type: gpt.LinuxFilesystem, Name: "target"}, + }, + } + if err := d.Partition(table); err != nil { + t.Fatalf("write partition table: %v", err) + } + + // Write identical leading expectedSize bytes into both partitions; the + // target's trailing bytes stay zero and must not affect the result. + content := make([]byte, expected) + for i := range content { + content[i] = byte(i*7 + 1) + } + writeAt := func(off int64, b []byte) { + t.Helper() + img, err := os.OpenFile(imgPath, os.O_RDWR, 0) + if err != nil { + t.Fatalf("open image for write: %v", err) + } + defer img.Close() + if _, err := img.WriteAt(b, off); err != nil { + t.Fatalf("write image at %d: %v", off, err) + } + } + writeAt(srcStart*sectorSize, content) + writeAt(tgtStart*sectorSize, content) + + openDisk := func() *disk.Disk { + t.Helper() + dd, err := diskfs.Open(imgPath, diskfs.WithSectorSize(sectorSize)) + if err != nil { + t.Fatalf("open disk: %v", err) + } + if _, err := dd.GetPartitionTable(); err != nil { + t.Fatalf("read partition table: %v", err) + } + return dd + } + + // Grow case: larger target, identical leading bytes — must verify clean. + if err := verifyBlockCopy(openDisk(), 1, 2, expected); err != nil { + t.Errorf("grow with identical leading bytes: unexpected error: %v", err) + } + + // Corrupt a byte inside the compared region of the target; must be caught. + writeAt(tgtStart*sectorSize, []byte{^content[0]}) + if err := verifyBlockCopy(openDisk(), 1, 2, expected); err == nil { + t.Error("corrupted target within compared region: expected mismatch error, got nil") + } +}