diff --git a/internal/pkg/partition/format.go b/internal/pkg/partition/format.go index c43eacfa7..92ddbd77f 100644 --- a/internal/pkg/partition/format.go +++ b/internal/pkg/partition/format.go @@ -7,9 +7,7 @@ package partition import ( "fmt" - "io" "log" - "os" "github.com/siderolabs/go-blockdevice/blockdevice" @@ -39,7 +37,7 @@ func NewFormatOptions(label string) *FormatOptions { // Format zeroes the device and formats it using filesystem type provided. func Format(devname string, t *FormatOptions) error { if t.FileSystemType == FilesystemTypeNone { - return zeroPartition(devname, int64(t.Size)) + return zeroPartition(devname) } opts := []makefs.Option{makefs.WithForce(t.Force), makefs.WithLabel(t.Label)} @@ -56,29 +54,17 @@ func Format(devname string, t *FormatOptions) error { } // zeroPartition fills the partition with zeroes. -func zeroPartition(devname string, size int64) (err error) { +func zeroPartition(devname string) (err error) { log.Printf("zeroing out %q", devname) - zeroes, err := os.Open("/dev/zero") - if err != nil { - return err - } - - defer zeroes.Close() //nolint:errcheck - - part, err := os.OpenFile(devname, os.O_WRONLY, 0) + part, err := blockdevice.Open(devname, blockdevice.WithExclusiveLock(true)) if err != nil { return err } defer part.Close() //nolint:errcheck - // wipe at least minimal header size - if size == 0 { - size = blockdevice.FastWipeRange - } - - _, err = io.CopyN(part, zeroes, size) + _, err = part.Wipe() return err } diff --git a/internal/pkg/partition/format_test.go b/internal/pkg/partition/format_test.go new file mode 100644 index 000000000..d17ba1b1c --- /dev/null +++ b/internal/pkg/partition/format_test.go @@ -0,0 +1,157 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +// Package partition provides common utils for system partition format. +package partition_test + +import ( + "bytes" + "io" + "os" + "testing" + + "github.com/siderolabs/go-blockdevice/blockdevice" + "github.com/siderolabs/go-blockdevice/blockdevice/loopback" + "github.com/siderolabs/go-blockdevice/blockdevice/partition/gpt" + "github.com/stretchr/testify/suite" + + "github.com/siderolabs/talos/internal/pkg/partition" +) + +type manifestSuite struct { + suite.Suite + + disk *os.File + loopbackDevice *os.File +} + +const ( + diskSize = 10 * 1024 * 1024 // 10 MiB +) + +func TestManifestSuite(t *testing.T) { + suite.Run(t, new(manifestSuite)) +} + +func (suite *manifestSuite) SetupTest() { + suite.skipIfNotRoot() + + var err error + + suite.disk, err = os.CreateTemp("", "talos") + suite.Require().NoError(err) + + suite.Require().NoError(suite.disk.Truncate(diskSize)) + + suite.loopbackDevice, err = loopback.NextLoopDevice() + suite.Require().NoError(err) + + suite.T().Logf("Using %s", suite.loopbackDevice.Name()) + + suite.Require().NoError(loopback.Loop(suite.loopbackDevice, suite.disk)) + + suite.Require().NoError(loopback.LoopSetReadWrite(suite.loopbackDevice)) +} + +func (suite *manifestSuite) TearDownTest() { + if suite.loopbackDevice != nil { + suite.Assert().NoError(loopback.Unloop(suite.loopbackDevice)) + } + + if suite.disk != nil { + suite.Assert().NoError(os.Remove(suite.disk.Name())) + suite.Assert().NoError(suite.disk.Close()) + } +} + +func (suite *manifestSuite) skipIfNotRoot() { + if os.Getuid() != 0 { + suite.T().Skip("can't run the test as non-root") + } +} + +func (suite *manifestSuite) skipUnderBuildkit() { + hostname, _ := os.Hostname() //nolint:errcheck + + if hostname == "buildkitsandbox" { + suite.T().Skip("test not supported under buildkit as partition devices are not propagated from /dev") + } +} + +func (suite *manifestSuite) TestZeroPartition() { + suite.skipUnderBuildkit() + + bd, err := blockdevice.Open(suite.loopbackDevice.Name(), blockdevice.WithExclusiveLock(true)) + suite.Require().NoError(err) + + defer bd.Close() //nolint:errcheck + + pt, err := gpt.New(bd.Device(), gpt.WithMarkMBRBootable(false)) + suite.Require().NoError(err) + + // Create a partition table with a single partition. + _, err = pt.Add(0, gpt.WithMaximumSize(true), gpt.WithPartitionName("zerofill")) + suite.Require().NoError(err) + + suite.Require().NoError(pt.Write()) + suite.Require().NoError(bd.Close()) + + bd, err = blockdevice.Open(suite.loopbackDevice.Name(), blockdevice.WithExclusiveLock(true)) + suite.Require().NoError(err) + + defer bd.Close() //nolint:errcheck + + fills := bytes.NewBuffer(bytes.Repeat([]byte{1}, 10)) + + parts, err := bd.GetPartition("zerofill") + suite.Require().NoError(err) + + part, err := parts.Path() + suite.Require().NoError(err) + + // open the partition as read write + dst, err := os.OpenFile(part, os.O_WRONLY, 0o644) + suite.Require().NoError(err) + + defer dst.Close() //nolint:errcheck + + // Write some data to the partition. + _, err = io.Copy(dst, fills) + suite.Require().NoError(err) + + data, err := os.Open(part) + suite.Require().NoError(err) + + defer data.Close() //nolint:errcheck + + read := make([]byte, fills.Len()) + + _, err = data.Read(read) + suite.Require().NoError(err) + suite.Require().NoError(data.Close()) + + suite.Assert().True(bytes.Equal(fills.Bytes(), read)) + + suite.Require().NoError(bd.Close()) + + err = partition.Format(part, &partition.FormatOptions{ + FileSystemType: partition.FilesystemTypeNone, + }) + suite.Require().NoError(err) + + // reading 10 times more than what we wrote should still return 0 since the partition is wiped + zerofills := bytes.NewBuffer(bytes.Repeat([]byte{0}, 100)) + + data, err = os.Open(part) + suite.Require().NoError(err) + + defer data.Close() //nolint:errcheck + + read = make([]byte, zerofills.Len()) + + _, err = data.Read(read) + suite.Require().NoError(err) + + suite.Assert().True(bytes.Equal(zerofills.Bytes(), read)) +}