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
189 lines
4.7 KiB
Go
189 lines
4.7 KiB
Go
package osbuild2
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/osbuild/osbuild-composer/internal/disk"
|
|
)
|
|
|
|
type Devices map[string]Device
|
|
|
|
type Device struct {
|
|
Type string `json:"type"`
|
|
Parent string `json:"parent,omitempty"`
|
|
Options DeviceOptions `json:"options,omitempty"`
|
|
}
|
|
|
|
type DeviceOptions interface {
|
|
isDeviceOptions()
|
|
}
|
|
|
|
func GenDeviceCreationStages(pt *disk.PartitionTable, filename string) []*Stage {
|
|
stages := make([]*Stage, 0)
|
|
|
|
genStages := func(e disk.Entity, path []disk.Entity) error {
|
|
|
|
switch ent := e.(type) {
|
|
case *disk.LUKSContainer:
|
|
// do not include us when getting the devices
|
|
stageDevices, lastName := getDevices(path[:len(path)-1], filename, true)
|
|
|
|
// "org.osbuild.luks2.format" expects a "device" to create the VG on,
|
|
// thus rename the last device to "device"
|
|
lastDevice := stageDevices[lastName]
|
|
delete(stageDevices, lastName)
|
|
stageDevices["device"] = lastDevice
|
|
|
|
stage := NewLUKS2CreateStage(
|
|
&LUKS2CreateStageOptions{
|
|
UUID: ent.UUID,
|
|
Passphrase: ent.Passphrase,
|
|
Cipher: ent.Cipher,
|
|
Label: ent.Label,
|
|
Subsystem: ent.Subsystem,
|
|
SectorSize: ent.SectorSize,
|
|
PBKDF: Argon2id{
|
|
Method: "argon2id",
|
|
Iterations: ent.PBKDF.Iterations,
|
|
Memory: ent.PBKDF.Memory,
|
|
Parallelism: ent.PBKDF.Parallelism,
|
|
},
|
|
},
|
|
stageDevices)
|
|
|
|
stages = append(stages, stage)
|
|
|
|
case *disk.LVMVolumeGroup:
|
|
// do not include us when getting the devices
|
|
stageDevices, lastName := getDevices(path[:len(path)-1], filename, true)
|
|
|
|
// "org.osbuild.lvm2.create" expects a "device" to create the VG on,
|
|
// thus rename the last device to "device"
|
|
lastDevice := stageDevices[lastName]
|
|
delete(stageDevices, lastName)
|
|
stageDevices["device"] = lastDevice
|
|
|
|
volumes := make([]LogicalVolume, len(ent.LogicalVolumes))
|
|
for idx, lv := range ent.LogicalVolumes {
|
|
volumes[idx].Name = lv.Name
|
|
// NB: we need to specify the size in bytes, since lvcreate
|
|
// defaults to megabytes
|
|
volumes[idx].Size = fmt.Sprintf("%dB", lv.Size)
|
|
}
|
|
|
|
stage := NewLVM2CreateStage(
|
|
&LVM2CreateStageOptions{
|
|
Volumes: volumes,
|
|
}, stageDevices)
|
|
|
|
stages = append(stages, stage)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
_ = pt.ForEachEntity(genStages)
|
|
return stages
|
|
}
|
|
|
|
func GenDeviceFinishStages(pt *disk.PartitionTable, filename string) []*Stage {
|
|
stages := make([]*Stage, 0)
|
|
|
|
genStages := func(e disk.Entity, path []disk.Entity) error {
|
|
|
|
switch ent := e.(type) {
|
|
case *disk.LVMVolumeGroup:
|
|
// do not include us when getting the devices
|
|
stageDevices, lastName := getDevices(path[:len(path)-1], filename, true)
|
|
|
|
// "org.osbuild.lvm2.metadata" expects a "device" to rename the VG,
|
|
// thus rename the last device to "device"
|
|
lastDevice := stageDevices[lastName]
|
|
delete(stageDevices, lastName)
|
|
stageDevices["device"] = lastDevice
|
|
|
|
stage := NewLVM2MetadataStage(
|
|
&LVM2MetadataStageOptions{
|
|
VGName: ent.Name,
|
|
}, stageDevices)
|
|
|
|
stages = append(stages, stage)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
_ = pt.ForEachEntity(genStages)
|
|
return stages
|
|
}
|
|
|
|
func deviceName(p disk.Entity) string {
|
|
if p == nil {
|
|
panic("device is nil; this is a programming error")
|
|
}
|
|
|
|
switch payload := p.(type) {
|
|
case disk.Mountable:
|
|
return pathdot(payload.GetMountpoint())
|
|
case *disk.LUKSContainer:
|
|
return "luks-" + payload.UUID[:4]
|
|
case *disk.LVMVolumeGroup:
|
|
return payload.Name + "vg"
|
|
case *disk.LVMLogicalVolume:
|
|
return payload.Name
|
|
}
|
|
panic(fmt.Sprintf("unsupported device type in deviceName: '%T'", p))
|
|
}
|
|
|
|
func getDevices(path []disk.Entity, filename string, lockLoopback bool) (map[string]Device, string) {
|
|
var pt *disk.PartitionTable
|
|
|
|
do := make(map[string]Device)
|
|
parent := ""
|
|
for _, elem := range path {
|
|
switch e := elem.(type) {
|
|
case *disk.PartitionTable:
|
|
pt = e
|
|
case *disk.Partition:
|
|
if pt == nil {
|
|
panic("path does not contain partition table; this is a programming error")
|
|
}
|
|
lbopt := LoopbackDeviceOptions{
|
|
Filename: filename,
|
|
Start: pt.BytesToSectors(e.Start),
|
|
Size: pt.BytesToSectors(e.Size),
|
|
SectorSize: nil,
|
|
Lock: lockLoopback,
|
|
}
|
|
name := deviceName(e.Payload)
|
|
do[name] = *NewLoopbackDevice(&lbopt)
|
|
parent = name
|
|
case *disk.LUKSContainer:
|
|
lo := LUKS2DeviceOptions{
|
|
Passphrase: e.Passphrase,
|
|
}
|
|
name := deviceName(e.Payload)
|
|
do[name] = *NewLUKS2Device(parent, &lo)
|
|
parent = name
|
|
case *disk.LVMLogicalVolume:
|
|
lo := LVM2LVDeviceOptions{
|
|
Volume: e.Name,
|
|
}
|
|
name := deviceName(e.Payload)
|
|
do[name] = *NewLVM2LVDevice(parent, &lo)
|
|
parent = name
|
|
}
|
|
}
|
|
return do, parent
|
|
}
|
|
|
|
func pathdot(path string) string {
|
|
if path == "/" {
|
|
return "root"
|
|
}
|
|
|
|
path = strings.TrimLeft(path, "/")
|
|
|
|
return strings.ReplaceAll(path, "/", ".")
|
|
}
|