19
19
package disks
20
20
21
21
import (
22
+ "bufio"
22
23
"errors"
23
24
"fmt"
25
+ "os"
26
+ "os/exec"
27
+ "path/filepath"
24
28
"regexp"
25
29
"sort"
26
30
"strconv"
27
31
"strings"
28
32
29
33
cutil "github.com/coreos/ignition/v2/config/util"
30
34
"github.com/coreos/ignition/v2/config/v3_5_experimental/types"
35
+ "github.com/coreos/ignition/v2/internal/distro"
31
36
"github.com/coreos/ignition/v2/internal/exec/util"
32
37
"github.com/coreos/ignition/v2/internal/sgdisk"
38
+ iutil "github.com/coreos/ignition/v2/internal/util"
33
39
)
34
40
35
41
var (
@@ -317,11 +323,126 @@ func (p PartitionList) Swap(i, j int) {
317
323
p [i ], p [j ] = p [j ], p [i ]
318
324
}
319
325
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
+
320
426
// partitionDisk partitions devAlias according to the spec given by dev
321
427
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
+ }
322
440
if cutil .IsTrue (dev .WipeTable ) {
323
441
op := sgdisk .Begin (s .Logger , devAlias )
324
442
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
+ }
325
446
op .WipeTable (true )
326
447
if err := op .Commit (); err != nil {
327
448
// `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 {
343
464
return err
344
465
}
345
466
467
+ prefix := partitionNumberPrefix (blockDevResolved )
468
+
346
469
// get a list of parititions that have size and start 0 replaced with the real sizes
347
470
// that would be used if all specified partitions were to be created anew.
348
471
// Also calculate sectors for all of the start/size values.
@@ -351,6 +474,10 @@ func (s stage) partitionDisk(dev types.Disk, devAlias string) error {
351
474
return err
352
475
}
353
476
477
+ var partxAdd []uint64
478
+ var partxDelete []uint64
479
+ var partxUpdate []uint64
480
+
354
481
for _ , part := range resolvedPartitions {
355
482
shouldExist := partitionShouldExist (part )
356
483
info , exists := diskInfo .GetPartition (part .Number )
@@ -360,17 +487,24 @@ func (s stage) partitionDisk(dev types.Disk, devAlias string) error {
360
487
}
361
488
matches := exists && matchErr == nil
362
489
wipeEntry := cutil .IsTrue (part .WipePartitionEntry )
490
+ partInUse := iutil .StrSliceContains (activeParts , fmt .Sprintf ("%s%s%d" , blockDevResolved , prefix , part .Number ))
491
+
492
+ var modification bool
363
493
364
494
// This is a translation of the matrix in the operator notes.
365
495
switch {
366
496
case ! exists && ! shouldExist :
367
497
s .Logger .Info ("partition %d specified as nonexistant and no partition was found. Success." , part .Number )
368
498
case ! exists && shouldExist :
369
499
op .CreatePartition (part )
500
+ modification = true
501
+ partxAdd = append (partxAdd , uint64 (part .Number ))
370
502
case exists && ! shouldExist && ! wipeEntry :
371
503
return fmt .Errorf ("partition %d exists but is specified as nonexistant and wipePartitionEntry is false" , part .Number )
372
504
case exists && ! shouldExist && wipeEntry :
373
505
op .DeletePartition (part .Number )
506
+ modification = true
507
+ partxDelete = append (partxDelete , uint64 (part .Number ))
374
508
case exists && shouldExist && matches :
375
509
s .Logger .Info ("partition %d found with correct specifications" , part .Number )
376
510
case exists && shouldExist && ! wipeEntry && ! matches :
@@ -383,23 +517,55 @@ func (s stage) partitionDisk(dev types.Disk, devAlias string) error {
383
517
part .Label = & info .Label
384
518
part .StartSector = & info .StartSector
385
519
op .CreatePartition (part )
520
+ modification = true
521
+ partxUpdate = append (partxUpdate , uint64 (part .Number ))
386
522
} else {
387
523
return fmt .Errorf ("Partition %d didn't match: %v" , part .Number , matchErr )
388
524
}
389
525
case exists && shouldExist && wipeEntry && ! matches :
390
526
s .Logger .Info ("partition %d did not meet specifications, wiping partition entry and recreating" , part .Number )
391
527
op .DeletePartition (part .Number )
392
528
op .CreatePartition (part )
529
+ modification = true
530
+ partxUpdate = append (partxUpdate , uint64 (part .Number ))
393
531
default :
394
532
// unfortunatey, golang doesn't check that all cases are handled exhaustively
395
533
return fmt .Errorf ("Unreachable code reached when processing partition %d. golang--" , part .Number )
396
534
}
535
+
536
+ if partInUse && modification {
537
+ return fmt .Errorf ("refusing to modify active partition %d on %q" , part .Number , devAlias )
538
+ }
397
539
}
398
540
399
541
if err := op .Commit (); err != nil {
400
542
return fmt .Errorf ("commit failure: %v" , err )
401
543
}
402
544
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
+
403
569
// It's best to wait here for the /dev/ABC entries to be
404
570
// (re)created, not only for other parts of the initramfs but
405
571
// also because s.waitOnDevices() can still race with udev's
0 commit comments