Skip to content

Commit 494403a

Browse files
authored
Merge pull request #1717 from pothos/kai/partprobe
2 parents 7548ac6 + c2cc56c commit 494403a

File tree

4 files changed

+173
-0
lines changed

4 files changed

+173
-0
lines changed

docs/release-notes.md

+4
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,12 @@ nav_order: 9
1010

1111
### Features
1212

13+
- Support partitioning disk with mounted partitions
14+
1315
### Changes
1416

17+
- The Dracut module now installs partx
18+
1519
### Bug fixes
1620

1721
- Fix Akamai Ignition base64 decoding on padded payloads

dracut/30ignition/module-setup.sh

+1
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ install() {
3939
mkfs.fat \
4040
mkfs.xfs \
4141
mkswap \
42+
partx \
4243
sgdisk \
4344
useradd \
4445
userdel \

internal/distro/distro.go

+2
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ var (
3636
groupdelCmd = "groupdel"
3737
mdadmCmd = "mdadm"
3838
mountCmd = "mount"
39+
partxCmd = "partx"
3940
sgdiskCmd = "sgdisk"
4041
modprobeCmd = "modprobe"
4142
udevadmCmd = "udevadm"
@@ -92,6 +93,7 @@ func GroupaddCmd() string { return groupaddCmd }
9293
func GroupdelCmd() string { return groupdelCmd }
9394
func MdadmCmd() string { return mdadmCmd }
9495
func MountCmd() string { return mountCmd }
96+
func PartxCmd() string { return partxCmd }
9597
func SgdiskCmd() string { return sgdiskCmd }
9698
func ModprobeCmd() string { return modprobeCmd }
9799
func UdevadmCmd() string { return udevadmCmd }

internal/exec/stages/disks/partitions.go

+166
Original file line numberDiff line numberDiff line change
@@ -19,17 +19,23 @@
1919
package disks
2020

2121
import (
22+
"bufio"
2223
"errors"
2324
"fmt"
25+
"os"
26+
"os/exec"
27+
"path/filepath"
2428
"regexp"
2529
"sort"
2630
"strconv"
2731
"strings"
2832

2933
cutil "github.com/coreos/ignition/v2/config/util"
3034
"github.com/coreos/ignition/v2/config/v3_5_experimental/types"
35+
"github.com/coreos/ignition/v2/internal/distro"
3136
"github.com/coreos/ignition/v2/internal/exec/util"
3237
"github.com/coreos/ignition/v2/internal/sgdisk"
38+
iutil "github.com/coreos/ignition/v2/internal/util"
3339
)
3440

3541
var (
@@ -317,11 +323,126 @@ func (p PartitionList) Swap(i, j int) {
317323
p[i], p[j] = p[j], p[i]
318324
}
319325

326+
// Expects a /dev/xyz path
327+
func blockDevHeld(blockDevResolved string) (bool, error) {
328+
_, blockDevNode := filepath.Split(blockDevResolved)
329+
330+
holdersDir := fmt.Sprintf("/sys/class/block/%s/holders/", blockDevNode)
331+
entries, err := os.ReadDir(holdersDir)
332+
if err != nil {
333+
return false, fmt.Errorf("failed to retrieve holders of %q: %v", blockDevResolved, err)
334+
}
335+
return len(entries) > 0, nil
336+
}
337+
338+
// Expects a /dev/xyz path
339+
func blockDevMounted(blockDevResolved string) (bool, error) {
340+
mounts, err := os.Open("/proc/mounts")
341+
if err != nil {
342+
return false, fmt.Errorf("failed to open /proc/mounts: %v", err)
343+
}
344+
scanner := bufio.NewScanner(mounts)
345+
for scanner.Scan() {
346+
mountSource := strings.Split(scanner.Text(), " ")[0]
347+
if strings.HasPrefix(mountSource, "/") {
348+
mountSourceResolved, err := filepath.EvalSymlinks(mountSource)
349+
if err != nil {
350+
return false, fmt.Errorf("failed to resolve %q: %v", mountSource, err)
351+
}
352+
if mountSourceResolved == blockDevResolved {
353+
return true, nil
354+
}
355+
}
356+
}
357+
if err := scanner.Err(); err != nil {
358+
return false, fmt.Errorf("failed to check mounts for %q: %v", blockDevResolved, err)
359+
}
360+
return false, nil
361+
}
362+
363+
// Expects a /dev/xyz path
364+
func blockDevPartitions(blockDevResolved string) ([]string, error) {
365+
_, blockDevNode := filepath.Split(blockDevResolved)
366+
367+
// This also works for extended MBR partitions
368+
sysDir := fmt.Sprintf("/sys/class/block/%s/", blockDevNode)
369+
entries, err := os.ReadDir(sysDir)
370+
if err != nil {
371+
return nil, fmt.Errorf("failed to retrieve sysfs entries of %q: %v", blockDevResolved, err)
372+
}
373+
var partitions []string
374+
for _, entry := range entries {
375+
if strings.HasPrefix(entry.Name(), blockDevNode) {
376+
partitions = append(partitions, "/dev/"+entry.Name())
377+
}
378+
}
379+
380+
return partitions, nil
381+
}
382+
383+
// Expects a /dev/xyz path
384+
func blockDevInUse(blockDevResolved string, skipPartitionCheck bool) (bool, []string, error) {
385+
// Note: This ignores swap and LVM usage
386+
inUse := false
387+
held, err := blockDevHeld(blockDevResolved)
388+
if err != nil {
389+
return false, nil, fmt.Errorf("failed to check if %q is held: %v", blockDevResolved, err)
390+
}
391+
mounted, err := blockDevMounted(blockDevResolved)
392+
if err != nil {
393+
return false, nil, fmt.Errorf("failed to check if %q is mounted: %v", blockDevResolved, err)
394+
}
395+
inUse = held || mounted
396+
if skipPartitionCheck {
397+
return inUse, nil, nil
398+
}
399+
partitions, err := blockDevPartitions(blockDevResolved)
400+
if err != nil {
401+
return false, nil, fmt.Errorf("failed to retrieve partitions of %q: %v", blockDevResolved, err)
402+
}
403+
var activePartitions []string
404+
for _, partition := range partitions {
405+
partInUse, _, err := blockDevInUse(partition, true)
406+
if err != nil {
407+
return false, nil, fmt.Errorf("failed to check if partition %q is in use: %v", partition, err)
408+
}
409+
if partInUse {
410+
activePartitions = append(activePartitions, partition)
411+
inUse = true
412+
}
413+
}
414+
return inUse, activePartitions, nil
415+
}
416+
417+
// Expects a /dev/xyz path
418+
func partitionNumberPrefix(blockDevResolved string) string {
419+
lastChar := blockDevResolved[len(blockDevResolved)-1]
420+
if '0' <= lastChar && lastChar <= '9' {
421+
return "p"
422+
}
423+
return ""
424+
}
425+
320426
// partitionDisk partitions devAlias according to the spec given by dev
321427
func (s stage) partitionDisk(dev types.Disk, devAlias string) error {
428+
blockDevResolved, err := filepath.EvalSymlinks(devAlias)
429+
if err != nil {
430+
return fmt.Errorf("failed to resolve %q: %v", devAlias, err)
431+
}
432+
433+
inUse, activeParts, err := blockDevInUse(blockDevResolved, false)
434+
if err != nil {
435+
return fmt.Errorf("failed usage check on %q: %v", devAlias, err)
436+
}
437+
if inUse && len(activeParts) == 0 {
438+
return fmt.Errorf("refusing to operate on directly active disk %q", devAlias)
439+
}
322440
if cutil.IsTrue(dev.WipeTable) {
323441
op := sgdisk.Begin(s.Logger, devAlias)
324442
s.Logger.Info("wiping partition table requested on %q", devAlias)
443+
if len(activeParts) > 0 {
444+
return fmt.Errorf("refusing to wipe active disk %q", devAlias)
445+
}
325446
op.WipeTable(true)
326447
if err := op.Commit(); err != nil {
327448
// `sgdisk --zap-all` will exit code 2 if the table was corrupted; retry it
@@ -343,6 +464,8 @@ func (s stage) partitionDisk(dev types.Disk, devAlias string) error {
343464
return err
344465
}
345466

467+
prefix := partitionNumberPrefix(blockDevResolved)
468+
346469
// get a list of parititions that have size and start 0 replaced with the real sizes
347470
// that would be used if all specified partitions were to be created anew.
348471
// Also calculate sectors for all of the start/size values.
@@ -351,6 +474,10 @@ func (s stage) partitionDisk(dev types.Disk, devAlias string) error {
351474
return err
352475
}
353476

477+
var partxAdd []uint64
478+
var partxDelete []uint64
479+
var partxUpdate []uint64
480+
354481
for _, part := range resolvedPartitions {
355482
shouldExist := partitionShouldExist(part)
356483
info, exists := diskInfo.GetPartition(part.Number)
@@ -360,17 +487,24 @@ func (s stage) partitionDisk(dev types.Disk, devAlias string) error {
360487
}
361488
matches := exists && matchErr == nil
362489
wipeEntry := cutil.IsTrue(part.WipePartitionEntry)
490+
partInUse := iutil.StrSliceContains(activeParts, fmt.Sprintf("%s%s%d", blockDevResolved, prefix, part.Number))
491+
492+
var modification bool
363493

364494
// This is a translation of the matrix in the operator notes.
365495
switch {
366496
case !exists && !shouldExist:
367497
s.Logger.Info("partition %d specified as nonexistant and no partition was found. Success.", part.Number)
368498
case !exists && shouldExist:
369499
op.CreatePartition(part)
500+
modification = true
501+
partxAdd = append(partxAdd, uint64(part.Number))
370502
case exists && !shouldExist && !wipeEntry:
371503
return fmt.Errorf("partition %d exists but is specified as nonexistant and wipePartitionEntry is false", part.Number)
372504
case exists && !shouldExist && wipeEntry:
373505
op.DeletePartition(part.Number)
506+
modification = true
507+
partxDelete = append(partxDelete, uint64(part.Number))
374508
case exists && shouldExist && matches:
375509
s.Logger.Info("partition %d found with correct specifications", part.Number)
376510
case exists && shouldExist && !wipeEntry && !matches:
@@ -383,23 +517,55 @@ func (s stage) partitionDisk(dev types.Disk, devAlias string) error {
383517
part.Label = &info.Label
384518
part.StartSector = &info.StartSector
385519
op.CreatePartition(part)
520+
modification = true
521+
partxUpdate = append(partxUpdate, uint64(part.Number))
386522
} else {
387523
return fmt.Errorf("Partition %d didn't match: %v", part.Number, matchErr)
388524
}
389525
case exists && shouldExist && wipeEntry && !matches:
390526
s.Logger.Info("partition %d did not meet specifications, wiping partition entry and recreating", part.Number)
391527
op.DeletePartition(part.Number)
392528
op.CreatePartition(part)
529+
modification = true
530+
partxUpdate = append(partxUpdate, uint64(part.Number))
393531
default:
394532
// unfortunatey, golang doesn't check that all cases are handled exhaustively
395533
return fmt.Errorf("Unreachable code reached when processing partition %d. golang--", part.Number)
396534
}
535+
536+
if partInUse && modification {
537+
return fmt.Errorf("refusing to modify active partition %d on %q", part.Number, devAlias)
538+
}
397539
}
398540

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

545+
// In contrast to similar tools, sgdisk does not trigger the update of the
546+
// kernel partition table with BLKPG but only uses BLKRRPART which fails
547+
// as soon as one partition of the disk is mounted
548+
if len(activeParts) > 0 {
549+
runPartxCommand := func(op string, partitions []uint64) error {
550+
for _, partNr := range partitions {
551+
cmd := exec.Command(distro.PartxCmd(), "--"+op, "--nr", strconv.FormatUint(partNr, 10), blockDevResolved)
552+
if _, err := s.Logger.LogCmd(cmd, "triggering partition %d %s on %q", partNr, op, devAlias); err != nil {
553+
return fmt.Errorf("partition %s failed: %v", op, err)
554+
}
555+
}
556+
return nil
557+
}
558+
if err := runPartxCommand("delete", partxDelete); err != nil {
559+
return err
560+
}
561+
if err := runPartxCommand("update", partxUpdate); err != nil {
562+
return err
563+
}
564+
if err := runPartxCommand("add", partxAdd); err != nil {
565+
return err
566+
}
567+
}
568+
403569
// It's best to wait here for the /dev/ABC entries to be
404570
// (re)created, not only for other parts of the initramfs but
405571
// also because s.waitOnDevices() can still race with udev's

0 commit comments

Comments
 (0)