diff --git a/blkid/blkid.go b/blkid/blkid.go index 1964e3c..d2bfc38 100644 --- a/blkid/blkid.go +++ b/blkid/blkid.go @@ -11,6 +11,7 @@ import ( "github.com/google/uuid" "go.uber.org/zap" + "github.com/siderolabs/go-blockdevice/v2/blkid/internal/probe" "github.com/siderolabs/go-blockdevice/v2/block" ) @@ -51,11 +52,13 @@ type Info struct { //nolint:govet } // ProbeResult is a result of probing a single filesystem/partition. -type ProbeResult struct { //nolint:govet +type ProbeResult struct { Name string UUID *uuid.UUID Label *string + SignatureRanges []SignatureRange + BlockSize uint32 FilesystemBlockSize uint32 ProbedSize uint64 @@ -81,6 +84,9 @@ type NestedProbeResult struct { //nolint:govet Parts []NestedProbeResult } +// SignatureRange is a range of bytes for signature detection. +type SignatureRange = probe.SignatureRange + // ProbeOptions is the options for probing. type ProbeOptions struct { // Logger to use for logging. diff --git a/blkid/blkid_linux_test.go b/blkid/blkid_linux_test.go index 9912c55..e911c42 100644 --- a/blkid/blkid_linux_test.go +++ b/blkid/blkid_linux_test.go @@ -231,6 +231,7 @@ func TestProbePathFilesystems(t *testing.T) { expectedBlockSize []uint32 expectedFSBlockSize []uint32 expectedFSSize uint64 + expectedSignatures []blkid.SignatureRange }{ { name: "xfs", @@ -245,6 +246,9 @@ func TestProbePathFilesystems(t *testing.T) { expectedBlockSize: []uint32{512}, expectedFSBlockSize: []uint32{4096}, expectedFSSize: 436 * MiB, + expectedSignatures: []blkid.SignatureRange{ + {Offset: 0, Size: 4}, + }, }, { name: "extfs", @@ -259,6 +263,9 @@ func TestProbePathFilesystems(t *testing.T) { expectedBlockSize: []uint32{1024, 4096}, expectedFSBlockSize: []uint32{1024, 4096}, expectedFSSize: 500 * MiB, + expectedSignatures: []blkid.SignatureRange{ + {Offset: 1080, Size: 2}, + }, }, { name: "vfat small", @@ -270,6 +277,9 @@ func TestProbePathFilesystems(t *testing.T) { expectedBlockSize: []uint32{512}, expectedFSBlockSize: []uint32{2048}, expectedFSSize: 100 * MiB, + expectedSignatures: []blkid.SignatureRange{ + {Offset: 54, Size: 8}, + }, }, { name: "vfat big", @@ -281,6 +291,9 @@ func TestProbePathFilesystems(t *testing.T) { expectedBlockSize: []uint32{512}, expectedFSBlockSize: []uint32{8192}, expectedFSSize: 524256768, + expectedSignatures: []blkid.SignatureRange{ + {Offset: 54, Size: 8}, + }, }, { name: "luks", @@ -291,6 +304,9 @@ func TestProbePathFilesystems(t *testing.T) { expectedName: "luks", expectedLabel: "cryptlabel", expectUUID: true, + expectedSignatures: []blkid.SignatureRange{ + {Offset: 0, Size: 6}, + }, }, { name: "iso", @@ -305,6 +321,9 @@ func TestProbePathFilesystems(t *testing.T) { expectedBlockSize: []uint32{2048}, expectedFSBlockSize: []uint32{2048}, expectedFSSize: 0x157800, + expectedSignatures: []blkid.SignatureRange{ + {Offset: 32769, Size: 5}, + }, }, { name: "iso joilet", @@ -319,6 +338,9 @@ func TestProbePathFilesystems(t *testing.T) { expectedBlockSize: []uint32{2048}, expectedFSBlockSize: []uint32{2048}, expectedFSSize: 0x15b000, + expectedSignatures: []blkid.SignatureRange{ + {Offset: 32769, Size: 5}, + }, }, { name: "swap 8k", @@ -333,6 +355,9 @@ func TestProbePathFilesystems(t *testing.T) { expectedBlockSize: []uint32{8192}, expectedFSBlockSize: []uint32{8192}, expectedFSSize: 524279808, + expectedSignatures: []blkid.SignatureRange{ + {Offset: 8182, Size: 10}, + }, }, { name: "swap 4k", @@ -347,6 +372,9 @@ func TestProbePathFilesystems(t *testing.T) { expectedBlockSize: []uint32{4096}, expectedFSBlockSize: []uint32{4096}, expectedFSSize: 524283904, + expectedSignatures: []blkid.SignatureRange{ + {Offset: 4086, Size: 10}, + }, }, { name: "swap 200 MiB", @@ -361,6 +389,9 @@ func TestProbePathFilesystems(t *testing.T) { expectedBlockSize: []uint32{8192}, expectedFSBlockSize: []uint32{8192}, expectedFSSize: 209707008, + expectedSignatures: []blkid.SignatureRange{ + {Offset: 8182, Size: 10}, + }, }, { name: "lvm2-pv", @@ -371,6 +402,9 @@ func TestProbePathFilesystems(t *testing.T) { expectedName: "lvm2-pv", expectedLabelRegex: regexp.MustCompile(`(?m)^[0-9a-zA-Z]{6}-[0-9a-zA-Z]{4}-[0-9a-zA-Z]{4}-[0-9a-zA-Z]{4}-[0-9a-zA-Z]{4}-[0-9a-zA-Z]{4}-[0-9a-zA-Z]{6}$`), + expectedSignatures: []blkid.SignatureRange{ + {Offset: 536, Size: 8}, + }, }, { name: "zfs", @@ -381,6 +415,7 @@ func TestProbePathFilesystems(t *testing.T) { expectedName: "zfs", expectedLabelRegex: regexp.MustCompile(`^[0-9a-f]{16}$`), + expectedSignatures: zfsSignatures, }, { name: "squashfs", @@ -394,6 +429,9 @@ func TestProbePathFilesystems(t *testing.T) { expectedBlockSize: []uint32{0x20000}, expectedFSBlockSize: []uint32{0x20000}, expectedFSSize: 0x100554, + expectedSignatures: []blkid.SignatureRange{ + {Offset: 0, Size: 4}, + }, }, { name: "talosmeta", @@ -404,6 +442,9 @@ func TestProbePathFilesystems(t *testing.T) { expectedName: "talosmeta", expectedFSSize: 2 * 256 * 1024, + expectedSignatures: []blkid.SignatureRange{ + {Offset: 0, Size: 4}, + }, }, } { for _, useLoopDevice := range []bool{false, true} { @@ -493,6 +534,8 @@ func TestProbePathFilesystems(t *testing.T) { } assert.Equal(t, test.expectedFSSize, info.ProbedSize) + + assert.Equal(t, test.expectedSignatures, info.SignatureRanges) }) }) } @@ -521,6 +564,29 @@ size= 204800, type=0FC63DAF-8483-4772-8E79-3D69D8477DE4, uuid=7F5FCD6C-A703 require.NoError(t, cmd.Run()) } +func corruptGPT(f func(*testing.T, string)) func(*testing.T, string) { + return func(t *testing.T, path string) { + t.Helper() + + f(t, path) + + buf := make([]byte, 512) + + f, err := os.OpenFile(path, os.O_RDWR, 0) + require.NoError(t, err) + + _, err = f.ReadAt(buf, 512) + require.NoError(t, err) + + buf[9] ^= 0xff // flip bits + + _, err = f.WriteAt(buf, 512) + require.NoError(t, err) + + require.NoError(t, f.Close()) + } +} + func wipe1MB(f func(*testing.T, string)) func(*testing.T, string) { return func(t *testing.T, path string) { t.Helper() @@ -607,8 +673,9 @@ func TestProbePathGPT(t *testing.T) { size uint64 setup func(*testing.T, string) - expectedUUID uuid.UUID - expectedParts []blkid.NestedProbeResult + expectedUUID uuid.UUID + expectedParts []blkid.NestedProbeResult + expectedSignatures []blkid.SignatureRange }{ { name: "good GPT", @@ -618,15 +685,35 @@ func TestProbePathGPT(t *testing.T) { expectedUUID: uuid.MustParse("DDDA0816-8B53-47BF-A813-9EBB1F73AAA2"), expectedParts: expectedParts, + expectedSignatures: []blkid.SignatureRange{ + { + Offset: 512, + Size: 512, + }, + { + Offset: 2147483136, + Size: 512, + }, + }, }, { name: "corrupted GPT", size: 2 * GiB, - setup: wipe1MB(setupGPT), + setup: corruptGPT(setupGPT), expectedUUID: uuid.MustParse("DDDA0816-8B53-47BF-A813-9EBB1F73AAA2"), expectedParts: expectedParts, + expectedSignatures: []blkid.SignatureRange{ + { + Offset: 512, + Size: 512, + }, + { + Offset: 2147483136, + Size: 512, + }, + }, }, } { for _, useLoopDevice := range []bool{false, true} { @@ -686,6 +773,8 @@ func TestProbePathGPT(t *testing.T) { require.NotNil(t, info.UUID) assert.Equal(t, test.expectedUUID, *info.UUID) + assert.Equal(t, test.expectedSignatures, info.SignatureRanges) + assert.Equal(t, test.expectedParts, info.Parts) }) }) @@ -693,6 +782,50 @@ func TestProbePathGPT(t *testing.T) { } } +func TestProbeHalfWipedGPT(t *testing.T) { + // wiping first 1MB (first header) should not detect GPT + for _, useLoopDevice := range []bool{false, true} { + t.Run(fmt.Sprintf("loop=%v", useLoopDevice), func(t *testing.T) { + if useLoopDevice && os.Geteuid() != 0 { + t.Skip("test requires root privileges") + } + + tmpDir := t.TempDir() + + rawImage := filepath.Join(tmpDir, "image.raw") + + f, err := os.Create(rawImage) + require.NoError(t, err) + + require.NoError(t, f.Truncate(int64(2*GiB))) + require.NoError(t, f.Close()) + + var probePath string + + if useLoopDevice { + loDev := losetupAttachHelper(t, rawImage, false) + + t.Cleanup(func() { + assert.NoError(t, loDev.Detach()) + }) + + probePath = loDev.Path() + } else { + probePath = rawImage + } + + wipe1MB(setupGPT)(t, probePath) + + logger := zaptest.NewLogger(t) + + info, err := blkid.ProbePath(probePath, blkid.WithProbeLogger(logger)) + require.NoError(t, err) + + assert.Empty(t, info.Name) + }) + } +} + func setupNestedGPT(t *testing.T, path string) { t.Helper() @@ -710,9 +843,7 @@ func TestProbePathNested(t *testing.T) { t.Skip("test requires root privileges") } - if hostname, _ := os.Hostname(); hostname == "buildkitsandbox" { //nolint: errcheck - t.Skip("test not supported under buildkit as partition devices are not propagated from /dev") - } + skipUnderBuildkit(t) for _, test := range []struct { //nolint:govet name string @@ -720,8 +851,9 @@ func TestProbePathNested(t *testing.T) { size uint64 setup func(*testing.T, string) - expectedUUID uuid.UUID - expectedParts []blkid.NestedProbeResult + expectedUUID uuid.UUID + expectedParts []blkid.NestedProbeResult + expectedSignatures []blkid.SignatureRange }{ { name: "good GPT, ext4fs, xfs, vfat, none", @@ -731,6 +863,28 @@ func TestProbePathNested(t *testing.T) { expectedUUID: uuid.MustParse("DDDA0816-8B53-47BF-A813-9EBB1F73AAA2"), expectedParts: expectedParts, + expectedSignatures: []blkid.SignatureRange{ + { + Offset: 512, + Size: 512, + }, + { + Offset: 2147483136, + Size: 512, + }, + { + Offset: 1048630, + Size: 8, + }, + { + Offset: 106955832, + Size: 2, + }, + { + Offset: 1261436928, + Size: 4, + }, + }, }, } { t.Run(test.name, func(t *testing.T) { @@ -774,6 +928,8 @@ func TestProbePathNested(t *testing.T) { require.NotNil(t, info.UUID) assert.Equal(t, test.expectedUUID, *info.UUID) + assert.Equal(t, test.expectedSignatures, info.SignatureRanges) + // extract only partition information and compare it separately partitionsOnly := xslices.Map(info.Parts, func(p blkid.NestedProbeResult) blkid.NestedProbeResult { return blkid.NestedProbeResult{ @@ -811,7 +967,7 @@ func TestProbePathNested(t *testing.T) { } } -func setupOurGPT(t *testing.T, path string) { +func setupOurGPT(t *testing.T, path string, createFilesystems bool) { t.Helper() blk, err := block.NewFromPath(path, block.OpenForWrite()) @@ -855,10 +1011,12 @@ func setupOurGPT(t *testing.T, path string) { require.NoError(t, part.Write()) - vfatSetup(t, path+"p1") - xfsSetup(t, path+"p3") - xfsSetup(t, path+"p5") - xfsSetup(t, path+"p6") + if createFilesystems { + vfatSetup(t, path+"p1") + xfsSetup(t, path+"p3") + xfsSetup(t, path+"p5") + xfsSetup(t, path+"p6") + } require.NoError(t, blk.Unlock()) require.NoError(t, blk.Close()) @@ -869,18 +1027,17 @@ func TestProbePathOurGPT(t *testing.T) { t.Skip("test requires root privileges") } - if hostname, _ := os.Hostname(); hostname == "buildkitsandbox" { //nolint: errcheck - t.Skip("test not supported under buildkit as partition devices are not propagated from /dev") - } + skipUnderBuildkit(t) for _, test := range []struct { //nolint:govet name string size uint64 - setup func(*testing.T, string) + setup func(*testing.T, string, bool) - expectedUUID uuid.UUID - expectedParts []blkid.NestedProbeResult + expectedUUID uuid.UUID + expectedParts []blkid.NestedProbeResult + expectedSignatures []blkid.SignatureRange }{ { name: "good GPT, ext4fs, xfs, vfat, none", @@ -890,6 +1047,28 @@ func TestProbePathOurGPT(t *testing.T) { expectedUUID: uuid.MustParse("DDDA0816-8B53-47BF-A813-9EBB1F73AAA2"), expectedParts: expectedParts, + expectedSignatures: []blkid.SignatureRange{ + { + Offset: 512, + Size: 512, + }, + { + Offset: 2147483136, + Size: 512, + }, + { + Offset: 1048630, + Size: 8, + }, + { + Offset: 106955832, + Size: 2, + }, + { + Offset: 1261436928, + Size: 4, + }, + }, }, } { t.Run(test.name, func(t *testing.T) { @@ -911,7 +1090,7 @@ func TestProbePathOurGPT(t *testing.T) { probePath := loDev.Path() - test.setup(t, probePath) + test.setup(t, probePath, true) logger := zaptest.NewLogger(t) @@ -975,6 +1154,83 @@ func TestProbePathOurGPT(t *testing.T) { } } +func TestProbeWithWipeRanges(t *testing.T) { + if os.Geteuid() != 0 { + t.Skip("test requires root privileges") + } + + skipUnderBuildkit(t) + + tmpDir := t.TempDir() + + rawImage := filepath.Join(tmpDir, "image.raw") + + f, err := os.Create(rawImage) + require.NoError(t, err) + + require.NoError(t, f.Truncate(int64(2*GiB))) + require.NoError(t, f.Close()) + + loDev := losetupAttachHelper(t, rawImage, false) + + t.Cleanup(func() { + assert.NoError(t, loDev.Detach()) + }) + + probePath := loDev.Path() + + // setup initial GPT and filesystems + setupOurGPT(t, probePath, true) + + logger := zaptest.NewLogger(t) + + info, err := blkid.ProbePath(probePath, blkid.WithProbeLogger(logger)) + require.NoError(t, err) + + assert.Equal(t, "gpt", info.Name) + assert.Len(t, info.Parts, 6) + + for _, idx := range []int{0, 2, 4, 5} { + assert.NotEmpty(t, info.Parts[idx].Name) + } + + // wipe by probed ranges + blk, err := block.NewFromPath(probePath, block.OpenForWrite()) + require.NoError(t, err) + + require.NoError(t, blk.Lock(true)) + + require.NoError(t, blk.FastWipe(xslices.Map(info.SignatureRanges, + func(r blkid.SignatureRange) block.Range { + return block.Range(r) + }, + )...)) + + require.NoError(t, blk.Unlock()) + require.NoError(t, blk.Close()) + + // probe again + info, err = blkid.ProbePath(probePath, blkid.WithProbeLogger(logger)) + require.NoError(t, err) + + assert.Empty(t, info.Name) + assert.Empty(t, info.Parts) + + // re-create partitions but without filesystems + setupOurGPT(t, probePath, false) + + info, err = blkid.ProbePath(probePath, blkid.WithProbeLogger(logger)) + require.NoError(t, err) + + assert.Equal(t, "gpt", info.Name) + assert.Len(t, info.Parts, 6) + + // filesystem should not be detected + for _, idx := range []int{0, 2, 4, 5} { + assert.Empty(t, info.Parts[idx].Name) + } +} + func losetupAttachHelper(t *testing.T, rawImage string, readonly bool) losetup.Device { //nolint:unparam t.Helper() @@ -1001,3 +1257,526 @@ func losetupAttachHelper(t *testing.T, rawImage string, readonly bool) losetup.D panic("unreachable") } + +func skipUnderBuildkit(t *testing.T) { + t.Helper() + + if hostname, _ := os.Hostname(); hostname == "buildkitsandbox" { //nolint: errcheck + t.Skip("test not supported under buildkit as partition devices are not propagated from /dev") + } +} + +var zfsSignatures = []blkid.SignatureRange{ + {Offset: 131072, Size: 8}, + {Offset: 132096, Size: 8}, + {Offset: 133120, Size: 8}, + {Offset: 134144, Size: 8}, + {Offset: 135168, Size: 8}, + {Offset: 136192, Size: 8}, + {Offset: 137216, Size: 8}, + {Offset: 138240, Size: 8}, + {Offset: 139264, Size: 8}, + {Offset: 140288, Size: 8}, + {Offset: 141312, Size: 8}, + {Offset: 142336, Size: 8}, + {Offset: 143360, Size: 8}, + {Offset: 144384, Size: 8}, + {Offset: 145408, Size: 8}, + {Offset: 146432, Size: 8}, + {Offset: 147456, Size: 8}, + {Offset: 148480, Size: 8}, + {Offset: 149504, Size: 8}, + {Offset: 150528, Size: 8}, + {Offset: 151552, Size: 8}, + {Offset: 152576, Size: 8}, + {Offset: 153600, Size: 8}, + {Offset: 154624, Size: 8}, + {Offset: 155648, Size: 8}, + {Offset: 156672, Size: 8}, + {Offset: 157696, Size: 8}, + {Offset: 158720, Size: 8}, + {Offset: 159744, Size: 8}, + {Offset: 160768, Size: 8}, + {Offset: 161792, Size: 8}, + {Offset: 162816, Size: 8}, + {Offset: 163840, Size: 8}, + {Offset: 164864, Size: 8}, + {Offset: 165888, Size: 8}, + {Offset: 166912, Size: 8}, + {Offset: 167936, Size: 8}, + {Offset: 168960, Size: 8}, + {Offset: 169984, Size: 8}, + {Offset: 171008, Size: 8}, + {Offset: 172032, Size: 8}, + {Offset: 173056, Size: 8}, + {Offset: 174080, Size: 8}, + {Offset: 175104, Size: 8}, + {Offset: 176128, Size: 8}, + {Offset: 177152, Size: 8}, + {Offset: 178176, Size: 8}, + {Offset: 179200, Size: 8}, + {Offset: 180224, Size: 8}, + {Offset: 181248, Size: 8}, + {Offset: 182272, Size: 8}, + {Offset: 183296, Size: 8}, + {Offset: 184320, Size: 8}, + {Offset: 185344, Size: 8}, + {Offset: 186368, Size: 8}, + {Offset: 187392, Size: 8}, + {Offset: 188416, Size: 8}, + {Offset: 189440, Size: 8}, + {Offset: 190464, Size: 8}, + {Offset: 191488, Size: 8}, + {Offset: 192512, Size: 8}, + {Offset: 193536, Size: 8}, + {Offset: 194560, Size: 8}, + {Offset: 195584, Size: 8}, + {Offset: 196608, Size: 8}, + {Offset: 197632, Size: 8}, + {Offset: 198656, Size: 8}, + {Offset: 199680, Size: 8}, + {Offset: 200704, Size: 8}, + {Offset: 201728, Size: 8}, + {Offset: 202752, Size: 8}, + {Offset: 203776, Size: 8}, + {Offset: 204800, Size: 8}, + {Offset: 205824, Size: 8}, + {Offset: 206848, Size: 8}, + {Offset: 207872, Size: 8}, + {Offset: 208896, Size: 8}, + {Offset: 209920, Size: 8}, + {Offset: 210944, Size: 8}, + {Offset: 211968, Size: 8}, + {Offset: 212992, Size: 8}, + {Offset: 214016, Size: 8}, + {Offset: 215040, Size: 8}, + {Offset: 216064, Size: 8}, + {Offset: 217088, Size: 8}, + {Offset: 218112, Size: 8}, + {Offset: 219136, Size: 8}, + {Offset: 220160, Size: 8}, + {Offset: 221184, Size: 8}, + {Offset: 222208, Size: 8}, + {Offset: 223232, Size: 8}, + {Offset: 224256, Size: 8}, + {Offset: 225280, Size: 8}, + {Offset: 226304, Size: 8}, + {Offset: 227328, Size: 8}, + {Offset: 228352, Size: 8}, + {Offset: 229376, Size: 8}, + {Offset: 230400, Size: 8}, + {Offset: 231424, Size: 8}, + {Offset: 232448, Size: 8}, + {Offset: 233472, Size: 8}, + {Offset: 234496, Size: 8}, + {Offset: 235520, Size: 8}, + {Offset: 236544, Size: 8}, + {Offset: 237568, Size: 8}, + {Offset: 238592, Size: 8}, + {Offset: 239616, Size: 8}, + {Offset: 240640, Size: 8}, + {Offset: 241664, Size: 8}, + {Offset: 242688, Size: 8}, + {Offset: 243712, Size: 8}, + {Offset: 244736, Size: 8}, + {Offset: 245760, Size: 8}, + {Offset: 246784, Size: 8}, + {Offset: 247808, Size: 8}, + {Offset: 248832, Size: 8}, + {Offset: 249856, Size: 8}, + {Offset: 250880, Size: 8}, + {Offset: 251904, Size: 8}, + {Offset: 252928, Size: 8}, + {Offset: 253952, Size: 8}, + {Offset: 254976, Size: 8}, + {Offset: 256000, Size: 8}, + {Offset: 257024, Size: 8}, + {Offset: 258048, Size: 8}, + {Offset: 259072, Size: 8}, + {Offset: 260096, Size: 8}, + {Offset: 261120, Size: 8}, + {Offset: 393216, Size: 8}, + {Offset: 394240, Size: 8}, + {Offset: 395264, Size: 8}, + {Offset: 396288, Size: 8}, + {Offset: 397312, Size: 8}, + {Offset: 398336, Size: 8}, + {Offset: 399360, Size: 8}, + {Offset: 400384, Size: 8}, + {Offset: 401408, Size: 8}, + {Offset: 402432, Size: 8}, + {Offset: 403456, Size: 8}, + {Offset: 404480, Size: 8}, + {Offset: 405504, Size: 8}, + {Offset: 406528, Size: 8}, + {Offset: 407552, Size: 8}, + {Offset: 408576, Size: 8}, + {Offset: 409600, Size: 8}, + {Offset: 410624, Size: 8}, + {Offset: 411648, Size: 8}, + {Offset: 412672, Size: 8}, + {Offset: 413696, Size: 8}, + {Offset: 414720, Size: 8}, + {Offset: 415744, Size: 8}, + {Offset: 416768, Size: 8}, + {Offset: 417792, Size: 8}, + {Offset: 418816, Size: 8}, + {Offset: 419840, Size: 8}, + {Offset: 420864, Size: 8}, + {Offset: 421888, Size: 8}, + {Offset: 422912, Size: 8}, + {Offset: 423936, Size: 8}, + {Offset: 424960, Size: 8}, + {Offset: 425984, Size: 8}, + {Offset: 427008, Size: 8}, + {Offset: 428032, Size: 8}, + {Offset: 429056, Size: 8}, + {Offset: 430080, Size: 8}, + {Offset: 431104, Size: 8}, + {Offset: 432128, Size: 8}, + {Offset: 433152, Size: 8}, + {Offset: 434176, Size: 8}, + {Offset: 435200, Size: 8}, + {Offset: 436224, Size: 8}, + {Offset: 437248, Size: 8}, + {Offset: 438272, Size: 8}, + {Offset: 439296, Size: 8}, + {Offset: 440320, Size: 8}, + {Offset: 441344, Size: 8}, + {Offset: 442368, Size: 8}, + {Offset: 443392, Size: 8}, + {Offset: 444416, Size: 8}, + {Offset: 445440, Size: 8}, + {Offset: 446464, Size: 8}, + {Offset: 447488, Size: 8}, + {Offset: 448512, Size: 8}, + {Offset: 449536, Size: 8}, + {Offset: 450560, Size: 8}, + {Offset: 451584, Size: 8}, + {Offset: 452608, Size: 8}, + {Offset: 453632, Size: 8}, + {Offset: 454656, Size: 8}, + {Offset: 455680, Size: 8}, + {Offset: 456704, Size: 8}, + {Offset: 457728, Size: 8}, + {Offset: 458752, Size: 8}, + {Offset: 459776, Size: 8}, + {Offset: 460800, Size: 8}, + {Offset: 461824, Size: 8}, + {Offset: 462848, Size: 8}, + {Offset: 463872, Size: 8}, + {Offset: 464896, Size: 8}, + {Offset: 465920, Size: 8}, + {Offset: 466944, Size: 8}, + {Offset: 467968, Size: 8}, + {Offset: 468992, Size: 8}, + {Offset: 470016, Size: 8}, + {Offset: 471040, Size: 8}, + {Offset: 472064, Size: 8}, + {Offset: 473088, Size: 8}, + {Offset: 474112, Size: 8}, + {Offset: 475136, Size: 8}, + {Offset: 476160, Size: 8}, + {Offset: 477184, Size: 8}, + {Offset: 478208, Size: 8}, + {Offset: 479232, Size: 8}, + {Offset: 480256, Size: 8}, + {Offset: 481280, Size: 8}, + {Offset: 482304, Size: 8}, + {Offset: 483328, Size: 8}, + {Offset: 484352, Size: 8}, + {Offset: 485376, Size: 8}, + {Offset: 486400, Size: 8}, + {Offset: 487424, Size: 8}, + {Offset: 488448, Size: 8}, + {Offset: 489472, Size: 8}, + {Offset: 490496, Size: 8}, + {Offset: 491520, Size: 8}, + {Offset: 492544, Size: 8}, + {Offset: 493568, Size: 8}, + {Offset: 494592, Size: 8}, + {Offset: 495616, Size: 8}, + {Offset: 496640, Size: 8}, + {Offset: 497664, Size: 8}, + {Offset: 498688, Size: 8}, + {Offset: 499712, Size: 8}, + {Offset: 500736, Size: 8}, + {Offset: 501760, Size: 8}, + {Offset: 502784, Size: 8}, + {Offset: 503808, Size: 8}, + {Offset: 504832, Size: 8}, + {Offset: 505856, Size: 8}, + {Offset: 506880, Size: 8}, + {Offset: 507904, Size: 8}, + {Offset: 508928, Size: 8}, + {Offset: 509952, Size: 8}, + {Offset: 510976, Size: 8}, + {Offset: 512000, Size: 8}, + {Offset: 513024, Size: 8}, + {Offset: 514048, Size: 8}, + {Offset: 515072, Size: 8}, + {Offset: 516096, Size: 8}, + {Offset: 517120, Size: 8}, + {Offset: 518144, Size: 8}, + {Offset: 519168, Size: 8}, + {Offset: 520192, Size: 8}, + {Offset: 521216, Size: 8}, + {Offset: 522240, Size: 8}, + {Offset: 523264, Size: 8}, + {Offset: 66715648, Size: 8}, + {Offset: 66716672, Size: 8}, + {Offset: 66717696, Size: 8}, + {Offset: 66718720, Size: 8}, + {Offset: 66719744, Size: 8}, + {Offset: 66720768, Size: 8}, + {Offset: 66721792, Size: 8}, + {Offset: 66722816, Size: 8}, + {Offset: 66723840, Size: 8}, + {Offset: 66724864, Size: 8}, + {Offset: 66725888, Size: 8}, + {Offset: 66726912, Size: 8}, + {Offset: 66727936, Size: 8}, + {Offset: 66728960, Size: 8}, + {Offset: 66729984, Size: 8}, + {Offset: 66731008, Size: 8}, + {Offset: 66732032, Size: 8}, + {Offset: 66733056, Size: 8}, + {Offset: 66734080, Size: 8}, + {Offset: 66735104, Size: 8}, + {Offset: 66736128, Size: 8}, + {Offset: 66737152, Size: 8}, + {Offset: 66738176, Size: 8}, + {Offset: 66739200, Size: 8}, + {Offset: 66740224, Size: 8}, + {Offset: 66741248, Size: 8}, + {Offset: 66742272, Size: 8}, + {Offset: 66743296, Size: 8}, + {Offset: 66744320, Size: 8}, + {Offset: 66745344, Size: 8}, + {Offset: 66746368, Size: 8}, + {Offset: 66747392, Size: 8}, + {Offset: 66748416, Size: 8}, + {Offset: 66749440, Size: 8}, + {Offset: 66750464, Size: 8}, + {Offset: 66751488, Size: 8}, + {Offset: 66752512, Size: 8}, + {Offset: 66753536, Size: 8}, + {Offset: 66754560, Size: 8}, + {Offset: 66755584, Size: 8}, + {Offset: 66756608, Size: 8}, + {Offset: 66757632, Size: 8}, + {Offset: 66758656, Size: 8}, + {Offset: 66759680, Size: 8}, + {Offset: 66760704, Size: 8}, + {Offset: 66761728, Size: 8}, + {Offset: 66762752, Size: 8}, + {Offset: 66763776, Size: 8}, + {Offset: 66764800, Size: 8}, + {Offset: 66765824, Size: 8}, + {Offset: 66766848, Size: 8}, + {Offset: 66767872, Size: 8}, + {Offset: 66768896, Size: 8}, + {Offset: 66769920, Size: 8}, + {Offset: 66770944, Size: 8}, + {Offset: 66771968, Size: 8}, + {Offset: 66772992, Size: 8}, + {Offset: 66774016, Size: 8}, + {Offset: 66775040, Size: 8}, + {Offset: 66776064, Size: 8}, + {Offset: 66777088, Size: 8}, + {Offset: 66778112, Size: 8}, + {Offset: 66779136, Size: 8}, + {Offset: 66780160, Size: 8}, + {Offset: 66781184, Size: 8}, + {Offset: 66782208, Size: 8}, + {Offset: 66783232, Size: 8}, + {Offset: 66784256, Size: 8}, + {Offset: 66785280, Size: 8}, + {Offset: 66786304, Size: 8}, + {Offset: 66787328, Size: 8}, + {Offset: 66788352, Size: 8}, + {Offset: 66789376, Size: 8}, + {Offset: 66790400, Size: 8}, + {Offset: 66791424, Size: 8}, + {Offset: 66792448, Size: 8}, + {Offset: 66793472, Size: 8}, + {Offset: 66794496, Size: 8}, + {Offset: 66795520, Size: 8}, + {Offset: 66796544, Size: 8}, + {Offset: 66797568, Size: 8}, + {Offset: 66798592, Size: 8}, + {Offset: 66799616, Size: 8}, + {Offset: 66800640, Size: 8}, + {Offset: 66801664, Size: 8}, + {Offset: 66802688, Size: 8}, + {Offset: 66803712, Size: 8}, + {Offset: 66804736, Size: 8}, + {Offset: 66805760, Size: 8}, + {Offset: 66806784, Size: 8}, + {Offset: 66807808, Size: 8}, + {Offset: 66808832, Size: 8}, + {Offset: 66809856, Size: 8}, + {Offset: 66810880, Size: 8}, + {Offset: 66811904, Size: 8}, + {Offset: 66812928, Size: 8}, + {Offset: 66813952, Size: 8}, + {Offset: 66814976, Size: 8}, + {Offset: 66816000, Size: 8}, + {Offset: 66817024, Size: 8}, + {Offset: 66818048, Size: 8}, + {Offset: 66819072, Size: 8}, + {Offset: 66820096, Size: 8}, + {Offset: 66821120, Size: 8}, + {Offset: 66822144, Size: 8}, + {Offset: 66823168, Size: 8}, + {Offset: 66824192, Size: 8}, + {Offset: 66825216, Size: 8}, + {Offset: 66826240, Size: 8}, + {Offset: 66827264, Size: 8}, + {Offset: 66828288, Size: 8}, + {Offset: 66829312, Size: 8}, + {Offset: 66830336, Size: 8}, + {Offset: 66831360, Size: 8}, + {Offset: 66832384, Size: 8}, + {Offset: 66833408, Size: 8}, + {Offset: 66834432, Size: 8}, + {Offset: 66835456, Size: 8}, + {Offset: 66836480, Size: 8}, + {Offset: 66837504, Size: 8}, + {Offset: 66838528, Size: 8}, + {Offset: 66839552, Size: 8}, + {Offset: 66840576, Size: 8}, + {Offset: 66841600, Size: 8}, + {Offset: 66842624, Size: 8}, + {Offset: 66843648, Size: 8}, + {Offset: 66844672, Size: 8}, + {Offset: 66845696, Size: 8}, + {Offset: 66977792, Size: 8}, + {Offset: 66978816, Size: 8}, + {Offset: 66979840, Size: 8}, + {Offset: 66980864, Size: 8}, + {Offset: 66981888, Size: 8}, + {Offset: 66982912, Size: 8}, + {Offset: 66983936, Size: 8}, + {Offset: 66984960, Size: 8}, + {Offset: 66985984, Size: 8}, + {Offset: 66987008, Size: 8}, + {Offset: 66988032, Size: 8}, + {Offset: 66989056, Size: 8}, + {Offset: 66990080, Size: 8}, + {Offset: 66991104, Size: 8}, + {Offset: 66992128, Size: 8}, + {Offset: 66993152, Size: 8}, + {Offset: 66994176, Size: 8}, + {Offset: 66995200, Size: 8}, + {Offset: 66996224, Size: 8}, + {Offset: 66997248, Size: 8}, + {Offset: 66998272, Size: 8}, + {Offset: 66999296, Size: 8}, + {Offset: 67000320, Size: 8}, + {Offset: 67001344, Size: 8}, + {Offset: 67002368, Size: 8}, + {Offset: 67003392, Size: 8}, + {Offset: 67004416, Size: 8}, + {Offset: 67005440, Size: 8}, + {Offset: 67006464, Size: 8}, + {Offset: 67007488, Size: 8}, + {Offset: 67008512, Size: 8}, + {Offset: 67009536, Size: 8}, + {Offset: 67010560, Size: 8}, + {Offset: 67011584, Size: 8}, + {Offset: 67012608, Size: 8}, + {Offset: 67013632, Size: 8}, + {Offset: 67014656, Size: 8}, + {Offset: 67015680, Size: 8}, + {Offset: 67016704, Size: 8}, + {Offset: 67017728, Size: 8}, + {Offset: 67018752, Size: 8}, + {Offset: 67019776, Size: 8}, + {Offset: 67020800, Size: 8}, + {Offset: 67021824, Size: 8}, + {Offset: 67022848, Size: 8}, + {Offset: 67023872, Size: 8}, + {Offset: 67024896, Size: 8}, + {Offset: 67025920, Size: 8}, + {Offset: 67026944, Size: 8}, + {Offset: 67027968, Size: 8}, + {Offset: 67028992, Size: 8}, + {Offset: 67030016, Size: 8}, + {Offset: 67031040, Size: 8}, + {Offset: 67032064, Size: 8}, + {Offset: 67033088, Size: 8}, + {Offset: 67034112, Size: 8}, + {Offset: 67035136, Size: 8}, + {Offset: 67036160, Size: 8}, + {Offset: 67037184, Size: 8}, + {Offset: 67038208, Size: 8}, + {Offset: 67039232, Size: 8}, + {Offset: 67040256, Size: 8}, + {Offset: 67041280, Size: 8}, + {Offset: 67042304, Size: 8}, + {Offset: 67043328, Size: 8}, + {Offset: 67044352, Size: 8}, + {Offset: 67045376, Size: 8}, + {Offset: 67046400, Size: 8}, + {Offset: 67047424, Size: 8}, + {Offset: 67048448, Size: 8}, + {Offset: 67049472, Size: 8}, + {Offset: 67050496, Size: 8}, + {Offset: 67051520, Size: 8}, + {Offset: 67052544, Size: 8}, + {Offset: 67053568, Size: 8}, + {Offset: 67054592, Size: 8}, + {Offset: 67055616, Size: 8}, + {Offset: 67056640, Size: 8}, + {Offset: 67057664, Size: 8}, + {Offset: 67058688, Size: 8}, + {Offset: 67059712, Size: 8}, + {Offset: 67060736, Size: 8}, + {Offset: 67061760, Size: 8}, + {Offset: 67062784, Size: 8}, + {Offset: 67063808, Size: 8}, + {Offset: 67064832, Size: 8}, + {Offset: 67065856, Size: 8}, + {Offset: 67066880, Size: 8}, + {Offset: 67067904, Size: 8}, + {Offset: 67068928, Size: 8}, + {Offset: 67069952, Size: 8}, + {Offset: 67070976, Size: 8}, + {Offset: 67072000, Size: 8}, + {Offset: 67073024, Size: 8}, + {Offset: 67074048, Size: 8}, + {Offset: 67075072, Size: 8}, + {Offset: 67076096, Size: 8}, + {Offset: 67077120, Size: 8}, + {Offset: 67078144, Size: 8}, + {Offset: 67079168, Size: 8}, + {Offset: 67080192, Size: 8}, + {Offset: 67081216, Size: 8}, + {Offset: 67082240, Size: 8}, + {Offset: 67083264, Size: 8}, + {Offset: 67084288, Size: 8}, + {Offset: 67085312, Size: 8}, + {Offset: 67086336, Size: 8}, + {Offset: 67087360, Size: 8}, + {Offset: 67088384, Size: 8}, + {Offset: 67089408, Size: 8}, + {Offset: 67090432, Size: 8}, + {Offset: 67091456, Size: 8}, + {Offset: 67092480, Size: 8}, + {Offset: 67093504, Size: 8}, + {Offset: 67094528, Size: 8}, + {Offset: 67095552, Size: 8}, + {Offset: 67096576, Size: 8}, + {Offset: 67097600, Size: 8}, + {Offset: 67098624, Size: 8}, + {Offset: 67099648, Size: 8}, + {Offset: 67100672, Size: 8}, + {Offset: 67101696, Size: 8}, + {Offset: 67102720, Size: 8}, + {Offset: 67103744, Size: 8}, + {Offset: 67104768, Size: 8}, + {Offset: 67105792, Size: 8}, + {Offset: 67106816, Size: 8}, + {Offset: 67107840, Size: 8}, +} diff --git a/blkid/internal/filesystems/zfs/zfs.go b/blkid/internal/filesystems/zfs/zfs.go index 47f7c1c..0977279 100644 --- a/blkid/internal/filesystems/zfs/zfs.go +++ b/blkid/internal/filesystems/zfs/zfs.go @@ -9,6 +9,9 @@ package zfs import ( "fmt" + "slices" + + "github.com/siderolabs/gen/xslices" "github.com/siderolabs/go-blockdevice/v2/blkid/internal/magic" "github.com/siderolabs/go-blockdevice/v2/blkid/internal/probe" @@ -59,16 +62,18 @@ func (p *Probe) Probe(r probe.Reader, _ magic.Magic) (*probe.Result, error) { found := 0 - var ub ZFSUB + var lastUB ZFSUB labelBuf := make([]byte, zfsVdevLabelSize) - for _, labelOffset := range []uint64{ + labelOffsets := []uint64{ 0, zfsVdevLabelSize, size - 2*zfsVdevLabelSize - lastLabelOffset, size - zfsVdevLabelSize - lastLabelOffset, - } { + } + + for _, labelOffset := range labelOffsets { if _, err := r.ReadAt(labelBuf, int64(labelOffset)); err != nil { return nil, fmt.Errorf("reading at offset %d: %w", labelOffset, err) } @@ -76,9 +81,11 @@ func (p *Probe) Probe(r probe.Reader, _ magic.Magic) (*probe.Result, error) { for i := range zfsUberblockCount { ubOffset := zfsLabelUberblock + uint64(i)*zfsUberblockSize - ub = ZFSUB(labelBuf[ubOffset : ubOffset+ZFSUB_SIZE]) + ub := ZFSUB(labelBuf[ubOffset : ubOffset+ZFSUB_SIZE]) if ub.Get_ub_magic() == zfsMagic || ub.Get_ub_magic() == zfsMagicSwap { found++ + + lastUB = ub } } @@ -93,9 +100,23 @@ func (p *Probe) Probe(r probe.Reader, _ magic.Magic) (*probe.Result, error) { } // TODO: find out GUID name from nvlist - uuidLabel := fmt.Sprintf("%016x", ub.Get_ub_guid_sum()) + uuidLabel := fmt.Sprintf("%016x", lastUB.Get_ub_guid_sum()) res := &probe.Result{ Label: &uuidLabel, + ExtraSignatures: slices.Concat(xslices.Map(labelOffsets, func(labelOffset uint64) []probe.SignatureRange { + ranges := make([]probe.SignatureRange, 0, zfsUberblockCount) + + for i := range zfsUberblockCount { + ranges = append(ranges, + probe.SignatureRange{ + Offset: labelOffset + zfsLabelUberblock + uint64(i)*zfsUberblockSize, + Size: 8, // magic is 8 bytes + }, + ) + } + + return ranges + })...), } return res, nil diff --git a/blkid/internal/partitions/gpt/gpt.go b/blkid/internal/partitions/gpt/gpt.go index 4c57122..06a1008 100644 --- a/blkid/internal/partitions/gpt/gpt.go +++ b/blkid/internal/partitions/gpt/gpt.go @@ -7,6 +7,7 @@ package gpt import ( "bytes" + "errors" "github.com/google/uuid" "github.com/siderolabs/go-pointer" @@ -46,13 +47,20 @@ func (p *Probe) Probe(r probe.Reader, _ magic.Magic) (*probe.Result, error) { // try reading primary header hdr, entries, err := gptstructs.ReadHeader(r, primaryLBA, lastLBA) if err != nil { + if errors.Is(err, gptstructs.ErrZeroedHeader) { + // treat zeroed out primary header as no header, and skip backup header + // Talos before 1.8 wiped only GPT primary headers, so this provides better backwards compatibility + // if the primary header is corrupted, we can still read the backup header + return nil, nil //nolint:nilnil + } + return nil, err } if hdr == nil { // try reading backup header hdr, entries, err = gptstructs.ReadHeader(r, lastLBA, lastLBA) - if err != nil { + if err != nil && !errors.Is(err, gptstructs.ErrZeroedHeader) { // skip zeroed out backup headers return nil, err } } @@ -74,6 +82,17 @@ func (p *Probe) Probe(r probe.Reader, _ magic.Magic) (*probe.Result, error) { BlockSize: uint32(sectorSize), ProbedSize: uint64(sectorSize) * (hdr.Get_last_usable_lba() - hdr.Get_first_usable_lba() + 1), + + ExtraSignatures: []probe.SignatureRange{ + { + Offset: primaryLBA * uint64(sectorSize), + Size: uint64(sectorSize), + }, + { + Offset: lastLBA * uint64(sectorSize), + Size: uint64(sectorSize), + }, + }, } partIdx := uint(1) diff --git a/blkid/internal/probe/probe.go b/blkid/internal/probe/probe.go index 7bef6d2..253b879 100644 --- a/blkid/internal/probe/probe.go +++ b/blkid/internal/probe/probe.go @@ -44,6 +44,8 @@ type Result struct { Parts []Partition + ExtraSignatures []SignatureRange + BlockSize uint32 FilesystemBlockSize uint32 ProbedSize uint64 @@ -60,3 +62,9 @@ type Partition struct { Offset uint64 Size uint64 } + +// SignatureRange is a range of bytes for signature detection. +type SignatureRange struct { + Offset uint64 + Size uint64 +} diff --git a/blkid/probe_linux.go b/blkid/probe_linux.go index 5a4f6e7..5060a92 100644 --- a/blkid/probe_linux.go +++ b/blkid/probe_linux.go @@ -44,21 +44,30 @@ func (i *Info) fillProbeResult(f *os.File, options ProbeOptions) error { return nil } - i.Name = matched.Name() + i.Name = matched.Prober.Name() i.UUID = res.UUID i.BlockSize = res.BlockSize i.FilesystemBlockSize = res.FilesystemBlockSize i.ProbedSize = res.ProbedSize i.Label = res.Label - if err = i.fillNested(f, chain, 0, &i.Parts, res.Parts, options); err != nil { + if len(matched.Magic.Value) > 0 { + i.SignatureRanges = append(i.SignatureRanges, SignatureRange{ + Offset: uint64(matched.Magic.Offset), + Size: uint64(len(matched.Magic.Value)), + }) + } + + i.SignatureRanges = append(i.SignatureRanges, res.ExtraSignatures...) + + if err = i.fillNested(f, chain, 0, &i.Parts, &i.SignatureRanges, res.Parts, options); err != nil { return fmt.Errorf("error probing nested: %w", err) } return nil } -func (i *Info) fillNested(f *os.File, chain chain.Chain, offset uint64, out *[]NestedProbeResult, parts []probe.Partition, options ProbeOptions) error { +func (i *Info) fillNested(f *os.File, chain chain.Chain, offset uint64, out *[]NestedProbeResult, outSignatures *[]SignatureRange, parts []probe.Partition, options ProbeOptions) error { if len(parts) == 0 { return nil } @@ -83,14 +92,23 @@ func (i *Info) fillNested(f *os.File, chain chain.Chain, offset uint64, out *[]N continue } - (*out)[idx].Name = matched.Name() + (*out)[idx].Name = matched.Prober.Name() (*out)[idx].UUID = res.UUID (*out)[idx].BlockSize = res.BlockSize (*out)[idx].FilesystemBlockSize = res.FilesystemBlockSize (*out)[idx].ProbedSize = res.ProbedSize (*out)[idx].Label = res.Label - if err = i.fillNested(f, chain, offset+part.Offset, &(*out)[idx].Parts, res.Parts, options); err != nil { + if len(matched.Magic.Value) > 0 { + *outSignatures = append(*outSignatures, SignatureRange{ + Offset: offset + part.Offset + uint64(matched.Magic.Offset), + Size: uint64(len(matched.Magic.Value)), + }) + } + + *outSignatures = append(*outSignatures, res.ExtraSignatures...) + + if err = i.fillNested(f, chain, offset+part.Offset, &(*out)[idx].Parts, outSignatures, res.Parts, options); err != nil { return fmt.Errorf("error probing nested: %w", err) } } @@ -98,13 +116,13 @@ func (i *Info) fillNested(f *os.File, chain chain.Chain, offset uint64, out *[]N return nil } -func (i *Info) probe(f *os.File, chain chain.Chain, offset, length uint64, options ProbeOptions) (*probe.Result, probe.Prober, error) { +func (i *Info) probe(f *os.File, chain chain.Chain, offset, length uint64, options ProbeOptions) (*probe.Result, probe.MagicMatch, error) { if offset+length > i.Size { - return nil, nil, fmt.Errorf("probing range is out of bounds: offset %d + len %d > size %d", offset, length, i.Size) + return nil, probe.MagicMatch{}, fmt.Errorf("probing range is out of bounds: offset %d + len %d > size %d", offset, length, i.Size) } if length < uint64(chain.MaxMagicSize()) { - return nil, nil, fmt.Errorf("probing range is too small: len %d < max magic size %d", length, chain.MaxMagicSize()) + return nil, probe.MagicMatch{}, fmt.Errorf("probing range is too small: len %d < max magic size %d", length, chain.MaxMagicSize()) } magicReadSize := max(uint(chain.MaxMagicSize()), i.IOSize) @@ -116,7 +134,7 @@ func (i *Info) probe(f *os.File, chain chain.Chain, offset, length uint64, optio buf := make([]byte, magicReadSize) if _, err := f.ReadAt(buf, int64(offset)); err != nil { - return nil, nil, fmt.Errorf("error reading magic buffer: %w", err) + return nil, probe.MagicMatch{}, fmt.Errorf("error reading magic buffer: %w", err) } pR := &probeReader{ @@ -139,8 +157,8 @@ func (i *Info) probe(f *os.File, chain chain.Chain, offset, length uint64, optio continue } - return res, matched.Prober, nil + return res, matched, nil } - return nil, nil, nil + return nil, probe.MagicMatch{}, nil } diff --git a/block/wipe_linux.go b/block/wipe_linux.go index 72a6b16..d2a3802 100644 --- a/block/wipe_linux.go +++ b/block/wipe_linux.go @@ -18,6 +18,12 @@ const ( FastWipeRange = 1024 * 1024 ) +// Range is a range of bytes. +type Range struct { + Offset uint64 + Size uint64 +} + // Wipe the device contents. // // In order of availability this tries to perform the following: @@ -38,7 +44,10 @@ func (d *Device) Wipe() (string, error) { // // This method is much faster than Wipe(), but it doesn't guarantee // that device will be zeroed out completely. -func (d *Device) FastWipe() error { +// +// If ranges are given, only those ranges will be wiped. +// Otherwise, the first FastWipeRange and the last FastWipeRange bytes will be wiped. +func (d *Device) FastWipe(ranges ...Range) error { size, err := d.GetSize() if err != nil { return err @@ -50,17 +59,28 @@ func (d *Device) FastWipe() error { // ignoring the error here as DISCARD might be not supported by the device unix.Syscall(unix.SYS_IOCTL, d.f.Fd(), unix.BLKDISCARD, uintptr(unsafe.Pointer(&r[0]))) //nolint: errcheck - // zero out the first N bytes of the device to clear any partition table - wipeLength := min(size, uint64(FastWipeRange)) + if len(ranges) == 0 { + // zero out the first N bytes of the device to clear any partition table + wipeLength := min(size, uint64(FastWipeRange)) - _, err = d.WipeRange(0, wipeLength) - if err != nil { - return err + _, err = d.WipeRange(0, wipeLength) + if err != nil { + return err + } + + // wipe the last FastWipeRange bytes of the device as well + if size >= FastWipeRange*2 { + _, err = d.WipeRange(size-FastWipeRange, FastWipeRange) + if err != nil { + return err + } + } + + return nil } - // wipe the last FastWipeRange bytes of the device as well - if size >= FastWipeRange*2 { - _, err = d.WipeRange(size-FastWipeRange, FastWipeRange) + for _, r := range ranges { + _, err = d.WipeRange(r.Offset, r.Size) if err != nil { return err } @@ -71,38 +91,54 @@ func (d *Device) FastWipe() error { // WipeRange the device [start, start+length). func (d *Device) WipeRange(start, length uint64) (string, error) { - r := [2]uint64{start, length} + // verify alignment before starting to use ioctl ways + if start&0x7ff == 0 && length&0x7ff == 0 { + r := [2]uint64{start, length} - if _, _, errno := unix.Syscall(unix.SYS_IOCTL, d.f.Fd(), unix.BLKSECDISCARD, uintptr(unsafe.Pointer(&r[0]))); errno == 0 { - runtime.KeepAlive(d) + if _, _, errno := unix.Syscall(unix.SYS_IOCTL, d.f.Fd(), unix.BLKSECDISCARD, uintptr(unsafe.Pointer(&r[0]))); errno == 0 { + runtime.KeepAlive(d) - return "blksecdiscard", nil - } + return "blksecdiscard", nil + } + + var zeroes int - var zeroes int + if _, _, errno := unix.Syscall(unix.SYS_IOCTL, d.f.Fd(), unix.BLKDISCARDZEROES, uintptr(unsafe.Pointer(&zeroes))); errno == 0 && zeroes != 0 { + if _, _, errno = unix.Syscall(unix.SYS_IOCTL, d.f.Fd(), unix.BLKDISCARD, uintptr(unsafe.Pointer(&r[0]))); errno == 0 { + runtime.KeepAlive(d) + + return "blkdiscardzeros", nil + } + } - if _, _, errno := unix.Syscall(unix.SYS_IOCTL, d.f.Fd(), unix.BLKDISCARDZEROES, uintptr(unsafe.Pointer(&zeroes))); errno == 0 && zeroes != 0 { - if _, _, errno = unix.Syscall(unix.SYS_IOCTL, d.f.Fd(), unix.BLKDISCARD, uintptr(unsafe.Pointer(&r[0]))); errno == 0 { + if _, _, errno := unix.Syscall(unix.SYS_IOCTL, d.f.Fd(), unix.BLKZEROOUT, uintptr(unsafe.Pointer(&r[0]))); errno == 0 { runtime.KeepAlive(d) - return "blkdiscardzeros", nil + return "blkzeroout", nil } } - if _, _, errno := unix.Syscall(unix.SYS_IOCTL, d.f.Fd(), unix.BLKZEROOUT, uintptr(unsafe.Pointer(&r[0]))); errno == 0 { - runtime.KeepAlive(d) + if length >= 65536 { // arbitrary threshold to use /dev/zero instead of allocating a zero slice + zero, err := os.Open("/dev/zero") + if err != nil { + return "", err + } - return "blkzeroout", nil - } + defer zero.Close() //nolint: errcheck - zero, err := os.Open("/dev/zero") - if err != nil { - return "", err + _, err = d.f.Seek(int64(start), io.SeekStart) + if err != nil { + return "", err + } + + _, err = io.CopyN(d.f, zero, int64(length)) + + return "writezeroes", err } - defer zero.Close() //nolint: errcheck + zeroes := make([]byte, length) - _, err = io.CopyN(d.f, zero, int64(r[1])) + _, err := d.f.WriteAt(zeroes, int64(start)) - return "writezeroes", err + return "writezero", err } diff --git a/block/wipe_linux_test.go b/block/wipe_linux_test.go index fda8107..d0ea82a 100644 --- a/block/wipe_linux_test.go +++ b/block/wipe_linux_test.go @@ -79,9 +79,17 @@ func TestDeviceWipe(t *testing.T) { assertZeroed(t, f, 0, 1024) assertZeroed(t, f, 2*GiB-1024, 1024) + + _, err = f.WriteAt(magic, 0) + require.NoError(t, err) + + require.NoError(t, devWhole.FastWipe(block.Range{Offset: 0, Size: 4}, block.Range{Offset: 2*GiB - 4, Size: 4})) + + assertZeroed(t, f, 0, 4) + assertZeroed(t, f, 2*GiB-4, 4) } -func assertZeroed(t *testing.T, f *os.File, offset, length int64) { //nolint:unparam +func assertZeroed(t *testing.T, f *os.File, offset, length int64) { t.Helper() buf := make([]byte, length) diff --git a/internal/gptstructs/header_extra.go b/internal/gptstructs/header_extra.go index 3cf03a0..8ca336d 100644 --- a/internal/gptstructs/header_extra.go +++ b/internal/gptstructs/header_extra.go @@ -5,6 +5,8 @@ package gptstructs import ( + "bytes" + "errors" "hash/crc32" "io" "slices" @@ -13,6 +15,9 @@ import ( // HeaderSignature is the signature of the GPT header. const HeaderSignature = 0x5452415020494645 // "EFI PART" +// ErrZeroedHeader is returned when the header is completely zeroed out. +var ErrZeroedHeader = errors.New("zeroed out header") + // CalculateChecksum calculates the checksum of the header. func (h Header) CalculateChecksum() uint32 { b := slices.Clone(h[:HEADER_SIZE]) @@ -42,6 +47,11 @@ func ReadHeader(r HeaderReader, lba, lastLBA uint64) (*Header, []Entry, error) { return nil, nil, err } + // check for completely zeroed out header, and treat that as a special error + if bytes.Equal(buf, make([]byte, sectorSize)) { + return nil, nil, ErrZeroedHeader + } + hdr := Header(buf) // verify the header signature diff --git a/partitioning/gpt/gpt.go b/partitioning/gpt/gpt.go index 4ee0979..cd0daab 100644 --- a/partitioning/gpt/gpt.go +++ b/partitioning/gpt/gpt.go @@ -157,7 +157,7 @@ func Read(dev Device, opts ...Option) (*Table, error) { } hdr, entries, err := gptstructs.ReadHeader(dev, 1, lastLBA) - if err != nil { + if err != nil && !errors.Is(err, gptstructs.ErrZeroedHeader) { // fallback to backup header if header is zeroed return nil, err }