Skip to content

Commit

Permalink
Merge pull request #1717 from pothos/kai/partprobe
Browse files Browse the repository at this point in the history
  • Loading branch information
jlebon committed Jul 4, 2024
2 parents 7548ac6 + c2cc56c commit 494403a
Show file tree
Hide file tree
Showing 4 changed files with 173 additions and 0 deletions.
4 changes: 4 additions & 0 deletions docs/release-notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,12 @@ nav_order: 9

### Features

- Support partitioning disk with mounted partitions

### Changes

- The Dracut module now installs partx

### Bug fixes

- Fix Akamai Ignition base64 decoding on padded payloads
Expand Down
1 change: 1 addition & 0 deletions dracut/30ignition/module-setup.sh
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ install() {
mkfs.fat \
mkfs.xfs \
mkswap \
partx \
sgdisk \
useradd \
userdel \
Expand Down
2 changes: 2 additions & 0 deletions internal/distro/distro.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ var (
groupdelCmd = "groupdel"
mdadmCmd = "mdadm"
mountCmd = "mount"
partxCmd = "partx"
sgdiskCmd = "sgdisk"
modprobeCmd = "modprobe"
udevadmCmd = "udevadm"
Expand Down Expand Up @@ -92,6 +93,7 @@ func GroupaddCmd() string { return groupaddCmd }
func GroupdelCmd() string { return groupdelCmd }
func MdadmCmd() string { return mdadmCmd }
func MountCmd() string { return mountCmd }
func PartxCmd() string { return partxCmd }
func SgdiskCmd() string { return sgdiskCmd }
func ModprobeCmd() string { return modprobeCmd }
func UdevadmCmd() string { return udevadmCmd }
Expand Down
166 changes: 166 additions & 0 deletions internal/exec/stages/disks/partitions.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,23 @@
package disks

import (
"bufio"
"errors"
"fmt"
"os"
"os/exec"
"path/filepath"
"regexp"
"sort"
"strconv"
"strings"

cutil "github.com/coreos/ignition/v2/config/util"
"github.com/coreos/ignition/v2/config/v3_5_experimental/types"
"github.com/coreos/ignition/v2/internal/distro"
"github.com/coreos/ignition/v2/internal/exec/util"
"github.com/coreos/ignition/v2/internal/sgdisk"
iutil "github.com/coreos/ignition/v2/internal/util"
)

var (
Expand Down Expand Up @@ -317,11 +323,126 @@ func (p PartitionList) Swap(i, j int) {
p[i], p[j] = p[j], p[i]
}

// Expects a /dev/xyz path
func blockDevHeld(blockDevResolved string) (bool, error) {
_, blockDevNode := filepath.Split(blockDevResolved)

holdersDir := fmt.Sprintf("/sys/class/block/%s/holders/", blockDevNode)
entries, err := os.ReadDir(holdersDir)
if err != nil {
return false, fmt.Errorf("failed to retrieve holders of %q: %v", blockDevResolved, err)
}
return len(entries) > 0, nil
}

// Expects a /dev/xyz path
func blockDevMounted(blockDevResolved string) (bool, error) {
mounts, err := os.Open("/proc/mounts")
if err != nil {
return false, fmt.Errorf("failed to open /proc/mounts: %v", err)
}
scanner := bufio.NewScanner(mounts)
for scanner.Scan() {
mountSource := strings.Split(scanner.Text(), " ")[0]
if strings.HasPrefix(mountSource, "/") {
mountSourceResolved, err := filepath.EvalSymlinks(mountSource)
if err != nil {
return false, fmt.Errorf("failed to resolve %q: %v", mountSource, err)
}
if mountSourceResolved == blockDevResolved {
return true, nil
}
}
}
if err := scanner.Err(); err != nil {
return false, fmt.Errorf("failed to check mounts for %q: %v", blockDevResolved, err)
}
return false, nil
}

// Expects a /dev/xyz path
func blockDevPartitions(blockDevResolved string) ([]string, error) {
_, blockDevNode := filepath.Split(blockDevResolved)

// This also works for extended MBR partitions
sysDir := fmt.Sprintf("/sys/class/block/%s/", blockDevNode)
entries, err := os.ReadDir(sysDir)
if err != nil {
return nil, fmt.Errorf("failed to retrieve sysfs entries of %q: %v", blockDevResolved, err)
}
var partitions []string
for _, entry := range entries {
if strings.HasPrefix(entry.Name(), blockDevNode) {
partitions = append(partitions, "/dev/"+entry.Name())
}
}

return partitions, nil
}

// Expects a /dev/xyz path
func blockDevInUse(blockDevResolved string, skipPartitionCheck bool) (bool, []string, error) {
// Note: This ignores swap and LVM usage
inUse := false
held, err := blockDevHeld(blockDevResolved)
if err != nil {
return false, nil, fmt.Errorf("failed to check if %q is held: %v", blockDevResolved, err)
}
mounted, err := blockDevMounted(blockDevResolved)
if err != nil {
return false, nil, fmt.Errorf("failed to check if %q is mounted: %v", blockDevResolved, err)
}
inUse = held || mounted
if skipPartitionCheck {
return inUse, nil, nil
}
partitions, err := blockDevPartitions(blockDevResolved)
if err != nil {
return false, nil, fmt.Errorf("failed to retrieve partitions of %q: %v", blockDevResolved, err)
}
var activePartitions []string
for _, partition := range partitions {
partInUse, _, err := blockDevInUse(partition, true)
if err != nil {
return false, nil, fmt.Errorf("failed to check if partition %q is in use: %v", partition, err)
}
if partInUse {
activePartitions = append(activePartitions, partition)
inUse = true
}
}
return inUse, activePartitions, nil
}

// Expects a /dev/xyz path
func partitionNumberPrefix(blockDevResolved string) string {
lastChar := blockDevResolved[len(blockDevResolved)-1]
if '0' <= lastChar && lastChar <= '9' {
return "p"
}
return ""
}

// partitionDisk partitions devAlias according to the spec given by dev
func (s stage) partitionDisk(dev types.Disk, devAlias string) error {
blockDevResolved, err := filepath.EvalSymlinks(devAlias)
if err != nil {
return fmt.Errorf("failed to resolve %q: %v", devAlias, err)
}

inUse, activeParts, err := blockDevInUse(blockDevResolved, false)
if err != nil {
return fmt.Errorf("failed usage check on %q: %v", devAlias, err)
}
if inUse && len(activeParts) == 0 {
return fmt.Errorf("refusing to operate on directly active disk %q", devAlias)
}
if cutil.IsTrue(dev.WipeTable) {
op := sgdisk.Begin(s.Logger, devAlias)
s.Logger.Info("wiping partition table requested on %q", devAlias)
if len(activeParts) > 0 {
return fmt.Errorf("refusing to wipe active disk %q", devAlias)
}
op.WipeTable(true)
if err := op.Commit(); err != nil {
// `sgdisk --zap-all` will exit code 2 if the table was corrupted; retry it
Expand All @@ -343,6 +464,8 @@ func (s stage) partitionDisk(dev types.Disk, devAlias string) error {
return err
}

prefix := partitionNumberPrefix(blockDevResolved)

// get a list of parititions that have size and start 0 replaced with the real sizes
// that would be used if all specified partitions were to be created anew.
// Also calculate sectors for all of the start/size values.
Expand All @@ -351,6 +474,10 @@ func (s stage) partitionDisk(dev types.Disk, devAlias string) error {
return err
}

var partxAdd []uint64
var partxDelete []uint64
var partxUpdate []uint64

for _, part := range resolvedPartitions {
shouldExist := partitionShouldExist(part)
info, exists := diskInfo.GetPartition(part.Number)
Expand All @@ -360,17 +487,24 @@ func (s stage) partitionDisk(dev types.Disk, devAlias string) error {
}
matches := exists && matchErr == nil
wipeEntry := cutil.IsTrue(part.WipePartitionEntry)
partInUse := iutil.StrSliceContains(activeParts, fmt.Sprintf("%s%s%d", blockDevResolved, prefix, part.Number))

var modification bool

// This is a translation of the matrix in the operator notes.
switch {
case !exists && !shouldExist:
s.Logger.Info("partition %d specified as nonexistant and no partition was found. Success.", part.Number)
case !exists && shouldExist:
op.CreatePartition(part)
modification = true
partxAdd = append(partxAdd, uint64(part.Number))
case exists && !shouldExist && !wipeEntry:
return fmt.Errorf("partition %d exists but is specified as nonexistant and wipePartitionEntry is false", part.Number)
case exists && !shouldExist && wipeEntry:
op.DeletePartition(part.Number)
modification = true
partxDelete = append(partxDelete, uint64(part.Number))
case exists && shouldExist && matches:
s.Logger.Info("partition %d found with correct specifications", part.Number)
case exists && shouldExist && !wipeEntry && !matches:
Expand All @@ -383,23 +517,55 @@ func (s stage) partitionDisk(dev types.Disk, devAlias string) error {
part.Label = &info.Label
part.StartSector = &info.StartSector
op.CreatePartition(part)
modification = true
partxUpdate = append(partxUpdate, uint64(part.Number))
} else {
return fmt.Errorf("Partition %d didn't match: %v", part.Number, matchErr)
}
case exists && shouldExist && wipeEntry && !matches:
s.Logger.Info("partition %d did not meet specifications, wiping partition entry and recreating", part.Number)
op.DeletePartition(part.Number)
op.CreatePartition(part)
modification = true
partxUpdate = append(partxUpdate, uint64(part.Number))
default:
// unfortunatey, golang doesn't check that all cases are handled exhaustively
return fmt.Errorf("Unreachable code reached when processing partition %d. golang--", part.Number)
}

if partInUse && modification {
return fmt.Errorf("refusing to modify active partition %d on %q", part.Number, devAlias)
}
}

if err := op.Commit(); err != nil {
return fmt.Errorf("commit failure: %v", err)
}

// In contrast to similar tools, sgdisk does not trigger the update of the
// kernel partition table with BLKPG but only uses BLKRRPART which fails
// as soon as one partition of the disk is mounted
if len(activeParts) > 0 {
runPartxCommand := func(op string, partitions []uint64) error {
for _, partNr := range partitions {
cmd := exec.Command(distro.PartxCmd(), "--"+op, "--nr", strconv.FormatUint(partNr, 10), blockDevResolved)
if _, err := s.Logger.LogCmd(cmd, "triggering partition %d %s on %q", partNr, op, devAlias); err != nil {
return fmt.Errorf("partition %s failed: %v", op, err)
}
}
return nil
}
if err := runPartxCommand("delete", partxDelete); err != nil {
return err
}
if err := runPartxCommand("update", partxUpdate); err != nil {
return err
}
if err := runPartxCommand("add", partxAdd); err != nil {
return err
}
}

// It's best to wait here for the /dev/ABC entries to be
// (re)created, not only for other parts of the initramfs but
// also because s.waitOnDevices() can still race with udev's
Expand Down

0 comments on commit 494403a

Please sign in to comment.