From ceae64edb3a591c6f6bbd75b1149d1cfe426dd8e Mon Sep 17 00:00:00 2001 From: Andrey Smirnov Date: Tue, 27 Oct 2020 16:34:13 +0300 Subject: [PATCH] fix: sync kernel partition table incrementally Previous strategy was to drop every kernel partition and replace it with the new partitions, but this fails when Talos tries to resize ephemeral partition on boot: other partitions are already mounted, so they can't be removed from the kernel state. Signed-off-by: Andrey Smirnov --- blockdevice/blkpg/blkpg.go | 12 +++++ blockdevice/blkpg/blkpg_darwin.go | 5 ++ blockdevice/blkpg/blkpg_linux.go | 52 +++++++++++++++++++++ blockdevice/table/gpt/gpt.go | 76 ++++++++++++++++++++++++------- 4 files changed, 128 insertions(+), 17 deletions(-) create mode 100644 blockdevice/blkpg/blkpg.go 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.