diff --git a/blockdevice/blkpg/blkpg.go b/blockdevice/blkpg/blkpg.go new file mode 100644 index 0000000..15f51e1 --- /dev/null +++ b/blockdevice/blkpg/blkpg.go @@ -0,0 +1,12 @@ +// 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 blkpg + +// KernelPartition represents kernel partition info. +type KernelPartition struct { + No int + Start int64 + Length int64 +} diff --git a/blockdevice/blkpg/blkpg_darwin.go b/blockdevice/blkpg/blkpg_darwin.go index 4e16c38..7c0fc1b 100644 --- a/blockdevice/blkpg/blkpg_darwin.go +++ b/blockdevice/blkpg/blkpg_darwin.go @@ -25,3 +25,8 @@ func InformKernelOfResize(f *os.File, partition table.Partition) error { func InformKernelOfDelete(f *os.File, partition table.Partition) error { return fmt.Errorf("not implemented") } + +// GetKernelPartitions returns kernel state of partitions. +func GetKernelPartitions(f *os.File, devPath string) ([]KernelPartition, error) { + return nil, fmt.Errorf("not implemented") +} diff --git a/blockdevice/blkpg/blkpg_linux.go b/blockdevice/blkpg/blkpg_linux.go index 56eee75..e1338ef 100644 --- a/blockdevice/blkpg/blkpg_linux.go +++ b/blockdevice/blkpg/blkpg_linux.go @@ -6,7 +6,11 @@ package blkpg import ( "fmt" + "io/ioutil" "os" + "path/filepath" + "strconv" + "strings" "syscall" "time" "unsafe" @@ -16,6 +20,7 @@ import ( "github.com/talos-systems/go-blockdevice/blockdevice/lba" "github.com/talos-systems/go-blockdevice/blockdevice/table" + "github.com/talos-systems/go-blockdevice/blockdevice/util" ) // InformKernelOfAdd invokes the BLKPG_ADD_PARTITION ioctl. @@ -98,3 +103,50 @@ func inform(f *os.File, partition table.Partition, op int32) (err error) { return nil } + +// GetKernelPartitions returns kernel partition table state. +func GetKernelPartitions(f *os.File, devPath string) ([]KernelPartition, error) { + result := []KernelPartition{} + + for i := 1; i <= 256; i++ { + partName := util.PartName(devPath, i) + partPath := filepath.Join("/sys/block", filepath.Base(devPath), partName) + + _, err := os.Stat(partPath) + if err != nil { + if os.IsNotExist(err) { + continue + } + + return nil, err + } + + startS, err := ioutil.ReadFile(filepath.Join(partPath, "start")) + if err != nil { + return nil, err + } + + sizeS, err := ioutil.ReadFile(filepath.Join(partPath, "size")) + if err != nil { + return nil, err + } + + start, err := strconv.ParseInt(strings.TrimSpace(string(startS)), 10, 64) + if err != nil { + return nil, err + } + + size, err := strconv.ParseInt(strings.TrimSpace(string(sizeS)), 10, 64) + if err != nil { + return nil, err + } + + result = append(result, KernelPartition{ + No: i, + Start: start, + Length: size, + }) + } + + return result, nil +} diff --git a/blockdevice/table/gpt/gpt.go b/blockdevice/table/gpt/gpt.go index 91bf034..00bee32 100644 --- a/blockdevice/table/gpt/gpt.go +++ b/blockdevice/table/gpt/gpt.go @@ -8,7 +8,6 @@ import ( "encoding/binary" "fmt" "os" - "path/filepath" "github.com/google/uuid" @@ -18,7 +17,6 @@ import ( "github.com/talos-systems/go-blockdevice/blockdevice/table" "github.com/talos-systems/go-blockdevice/blockdevice/table/gpt/header" "github.com/talos-systems/go-blockdevice/blockdevice/table/gpt/partition" - "github.com/talos-systems/go-blockdevice/blockdevice/util" ) // GPT represents the GUID partition table. @@ -102,44 +100,88 @@ func (gpt *GPT) Write() error { return err } - if err := gpt.writePrimary(partitions); err != nil { + if err = gpt.writePrimary(partitions); err != nil { return fmt.Errorf("failed to write primary table: %w", err) } - if err := gpt.writeSecondary(partitions); err != nil { + if err = gpt.writeSecondary(partitions); err != nil { return fmt.Errorf("failed to write secondary table: %w", err) } - if err := gpt.f.Sync(); err != nil { + if err = gpt.f.Sync(); err != nil { return err } - for i := 1; ; i++ { - partName := util.PartName(gpt.devname, i) + if err = gpt.syncKernelPartitions(); err != nil { + return fmt.Errorf("failed to sync kernel partitions: %w", err) + } + + return gpt.Read() +} + +func (gpt *GPT) syncKernelPartitions() error { + kernelPartitions, err := blkpg.GetKernelPartitions(gpt.f, gpt.devname) + if err != nil { + return err + } + + // filter out nil partitions + newPartitions := make([]table.Partition, 0, len(gpt.partitions)) + + for _, part := range gpt.partitions { + if part == nil { + continue + } + + newPartitions = append(newPartitions, part) + } + + var i int - _, err := os.Stat(filepath.Join("/sys/block", filepath.Base(gpt.devname), partName)) - if err != nil { + // find partitions matching exactly or partitions which can be simply resized + for i = 0; i < len(kernelPartitions) && i < len(newPartitions); i++ { + kernelPart := kernelPartitions[i] + newPart := newPartitions[i] + + // non-contiguous kernel partition table, stop + if kernelPart.No != i+1 { break } + // skip partitions without any changes + if kernelPart.Start == newPart.Start() && kernelPart.Length == newPart.Length() { + continue + } + + // resizing a partition which is the last one in the kernel list (no overlaps) + if kernelPart.Start == newPart.Start() && i == len(kernelPartitions)-1 { + if err := blkpg.InformKernelOfResize(gpt.f, newPart); err != nil { + return err + } + + continue + } + + // partitions don't match, stop + break + } + + // process remaining partitions: delete all the kernel partitions left, add new partitions from in-memory set + for j := i; j < len(kernelPartitions); j++ { if err := blkpg.InformKernelOfDelete(gpt.f, &partition.Partition{ - Number: int32(i), + Number: int32(kernelPartitions[j].No), }); err != nil { return err } } - for _, part := range gpt.partitions { - if part == nil { - continue - } - - if err := blkpg.InformKernelOfAdd(gpt.f, part); err != nil { + for j := i; j < len(newPartitions); j++ { + if err := blkpg.InformKernelOfAdd(gpt.f, newPartitions[j]); err != nil { return err } } - return gpt.Read() + return nil } // New creates a new partition table and writes it to disk.