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, "/", ".") }