Since udev will probe block devices it is advisable to hold a lock on the device when modifying its partition table or the superblock of the filesystem (see [1]). osbuild loopback devices do support this via the `lock` option. Set this option for all operation that involve changing block device "metadata" that could potentionally race with udev, such as sfdisk, mkfs, creating a luks2 container and creating LVM2 volume groups and logical volumes. NB: osbuild also has its own device inhibition logic to prevent udev/lvm2 from auto activating devices and in general to limit the interaction between the host and devices used by osbuild. See [2] for more information. NB: this also locks the loopback device in situation where we the it is strickly not the right thing to do, e.g. when creating a fs on a logical voume that is located on a loopback device, since in this case the device we would need to lock is the logical volume. Sadly, LVM/DM devices are exempt from block device locking. But, due to a bug in osbuild < 50, the udev inhibitor does *not* work for loopback devices and therefore we have to use the actual lock to preven LVM device auto-activation via `69-dm-lvm-metad.rules`. The change was implemented by adding a new boolean to `getDevices` indicating if the loopback device should be locked or not. Once we depend on osbuild 50 we can change the logic in `getDevices` to only lock the loopback device if the number of devices is one, i.e. we are working directly on the loopback device. [1] https://systemd.io/BLOCK_DEVICE_LOCKING/ [2] /usr/lib/udev/rules.d/10-osbuild-inhibitor.rules
84 lines
2 KiB
Go
84 lines
2 KiB
Go
package osbuild2
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"github.com/osbuild/osbuild-composer/internal/disk"
|
|
)
|
|
|
|
// sfdiskStageOptions creates the options and devices properties for an
|
|
// org.osbuild.sfdisk stage based on a partition table description
|
|
func sfdiskStageOptions(pt *disk.PartitionTable) *SfdiskStageOptions {
|
|
partitions := make([]Partition, len(pt.Partitions))
|
|
for idx, p := range pt.Partitions {
|
|
partitions[idx] = Partition{
|
|
Bootable: p.Bootable,
|
|
Start: pt.BytesToSectors(p.Start),
|
|
Size: pt.BytesToSectors(p.Size),
|
|
Type: p.Type,
|
|
UUID: p.UUID,
|
|
}
|
|
}
|
|
stageOptions := &SfdiskStageOptions{
|
|
Label: pt.Type,
|
|
UUID: pt.UUID,
|
|
Partitions: partitions,
|
|
}
|
|
|
|
return stageOptions
|
|
}
|
|
|
|
func GenImagePrepareStages(pt *disk.PartitionTable, filename string) []*Stage {
|
|
stages := make([]*Stage, 0)
|
|
|
|
// create an empty file of the given size via `org.osbuild.truncate`
|
|
stage := NewTruncateStage(
|
|
&TruncateStageOptions{
|
|
Filename: filename,
|
|
Size: fmt.Sprintf("%d", pt.Size),
|
|
})
|
|
|
|
stages = append(stages, stage)
|
|
|
|
// create the partition layout in the empty file
|
|
sfOptions := sfdiskStageOptions(pt)
|
|
loopback := NewLoopbackDevice(
|
|
&LoopbackDeviceOptions{
|
|
Filename: filename,
|
|
Lock: true,
|
|
},
|
|
)
|
|
|
|
sfdisk := NewSfdiskStage(sfOptions, loopback)
|
|
stages = append(stages, sfdisk)
|
|
|
|
// Generate all the needed "devices", like LUKS2 and LVM2
|
|
s := GenDeviceCreationStages(pt, filename)
|
|
stages = append(stages, s...)
|
|
|
|
// Generate all the filesystems on partitons and devices
|
|
s = GenMkfsStages(pt, loopback)
|
|
stages = append(stages, s...)
|
|
|
|
return stages
|
|
}
|
|
|
|
func GenImageFinishStages(pt *disk.PartitionTable, filename string) []*Stage {
|
|
return GenDeviceFinishStages(pt, filename)
|
|
}
|
|
|
|
func GenImageKernelOptions(pt *disk.PartitionTable) []string {
|
|
cmdline := make([]string, 0)
|
|
|
|
genOptions := func(e disk.Entity, path []disk.Entity) error {
|
|
switch ent := e.(type) {
|
|
case *disk.LUKSContainer:
|
|
karg := "luks.uuid=" + ent.UUID
|
|
cmdline = append(cmdline, karg)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
_ = pt.ForEachEntity(genOptions)
|
|
return cmdline
|
|
}
|