diff --git a/internal/disk/customizations.go b/internal/disk/customizations.go new file mode 100644 index 000000000..71e6bac7e --- /dev/null +++ b/internal/disk/customizations.go @@ -0,0 +1,179 @@ +package disk + +import ( + "io" + "math/rand" + + "github.com/google/uuid" + "github.com/osbuild/osbuild-composer/internal/blueprint" + "github.com/osbuild/osbuild-composer/internal/distro" +) + +const ( + sectorSize = 512 + BIOSBootPartitionGUID = "21686148-6449-6E6F-744E-656564454649" + BIOSBootPartitionUUID = "FAC7F1FB-3E8D-4137-A512-961DE09A5549" + + FilesystemDataGUID = "0FC63DAF-8483-4772-8E79-3D69D8477DE4" + FilesystemDataUUID = "CB07C243-BC44-4717-853E-28852021225B" + + EFISystemPartitionGUID = "C12A7328-F81F-11D2-BA4B-00A0C93EC93B" + EFISystemPartitionUUID = "68B2905B-DF3E-4FB3-80FA-49D1E773AA33" + EFIFilesystemUUID = "7B77-95E7" + + RootPartitionUUID = "6264D520-3FB9-423F-8AB8-7A0A8E3D3562" +) + +func CreatePartitionTable( + mountpoints []blueprint.FilesystemCustomization, + imageOptions distro.ImageOptions, + arch distro.Arch, + basePartitionTable PartitionTable, + bootType distro.BootType, + ec2 bool, + rng *rand.Rand, +) PartitionTable { + archName := arch.Name() + + basePartitionTable.Size = imageOptions.Size + partitions := []Partition{} + var start uint64 = 2048 + + if archName == distro.X86_64ArchName { + biosBootPartition := createPartition("bios", 2048, start, archName, rng) + partitions = append(partitions, biosBootPartition) + start += biosBootPartition.Size + if bootType != distro.LegacyBootType { + bootEFIPartition := createPartition("/boot/efi", 204800, start, archName, rng) + partitions = append(partitions, bootEFIPartition) + start += bootEFIPartition.Size + } + } else if archName == distro.Aarch64ArchName { + if ec2 { + bootEFIParition := createPartition("/boot/efi", 409600, start, archName, rng) + partitions = append(partitions, bootEFIParition) + start += bootEFIParition.Size + bootPartition := createPartition("/boot", 1048576, start, archName, rng) + partitions = append(partitions, bootPartition) + start += bootPartition.Size + } else { + bootEFIPartition := createPartition("/boot/efi", 204800, start, archName, rng) + partitions = append(partitions, bootEFIPartition) + start += bootEFIPartition.Size + } + } else if archName == distro.Ppc64leArchName { + biosBootPartition := createPartition("bios", 8192, start, archName, rng) + partitions = append(partitions, biosBootPartition) + start += biosBootPartition.Size + } + + for _, m := range mountpoints { + if m.Mountpoint != "/" { + partitionSize := uint64(m.MinSize) / sectorSize + partition := createPartition(m.Mountpoint, partitionSize, start, archName, rng) + partitions = append(partitions, partition) + start += uint64(m.MinSize / sectorSize) + } + } + + // treat the root partition as a special case + // by setting it last and setting the size + // dynamically + rootSize := (imageOptions.Size / sectorSize) - start - 100 + rootPartition := createPartition("/", rootSize, start, archName, rng) + partitions = append(partitions, rootPartition) + + basePartitionTable.Partitions = append(basePartitionTable.Partitions, partitions...) + + return basePartitionTable +} + +func createPartition(mountpoint string, size uint64, start uint64, archName string, rng *rand.Rand) Partition { + if mountpoint == "bios" { + diskPartition := Partition{ + Start: start, + Size: size, + Bootable: true, + } + if archName == distro.X86_64ArchName { + diskPartition.Type = BIOSBootPartitionGUID + diskPartition.UUID = BIOSBootPartitionUUID + return diskPartition + } + diskPartition.Type = "41" + return diskPartition + } + var filesystem Filesystem + // EFI system is a special case + // return early + if mountpoint == "/boot/efi" { + filesystem = createFilesystemDisk(mountpoint, EFIFilesystemUUID) + return Partition{ + Start: start, + Size: size, + Type: EFISystemPartitionGUID, + UUID: EFISystemPartitionUUID, + Filesystem: &filesystem, + } + } + partition := Partition{ + Start: start, + Size: size, + Filesystem: &filesystem, + } + diskUUID := uuid.Must(newRandomUUIDFromReader(rng)).String() + filesystem = createFilesystemDisk(mountpoint, diskUUID) + if mountpoint == "/boot" { + partition.Type = FilesystemDataGUID + partition.UUID = FilesystemDataUUID + return partition + } + if archName == distro.X86_64ArchName || archName == distro.Aarch64ArchName { + if mountpoint == "/" { + // set Label for root mountpoint + filesystem.Label = "root" + partition.Type = FilesystemDataGUID + partition.UUID = RootPartitionUUID + return partition + } + partition.Type = FilesystemDataGUID + partition.UUID = uuid.Must(newRandomUUIDFromReader(rng)).String() + return partition + } + if mountpoint == "/" && archName == distro.S390xArchName { + partition.Bootable = true + } + return partition +} + +func createFilesystemDisk(mountpoint string, uuid string) Filesystem { + if mountpoint == "/boot/efi" { + return Filesystem{ + Type: "vfat", + UUID: uuid, + Mountpoint: mountpoint, + FSTabOptions: "defaults,uid=0,gid=0,umask=077,shortname=winnt", + FSTabFreq: 0, + FSTabPassNo: 2, + } + } + return Filesystem{ + Type: "xfs", + UUID: uuid, + Mountpoint: mountpoint, + FSTabOptions: "defaults", + FSTabFreq: 0, + FSTabPassNo: 0, + } +} + +func newRandomUUIDFromReader(r io.Reader) (uuid.UUID, error) { + var id uuid.UUID + _, err := io.ReadFull(r, id[:]) + if err != nil { + return uuid.Nil, err + } + id[6] = (id[6] & 0x0f) | 0x40 // Version 4 + id[8] = (id[8] & 0x3f) | 0x80 // Variant is 10 + return id, nil +} diff --git a/internal/distro/distro.go b/internal/distro/distro.go index 7fe6a871b..bbcc2a8de 100644 --- a/internal/distro/distro.go +++ b/internal/distro/distro.go @@ -23,6 +23,15 @@ const ( S390xArchName = "s390x" ) +type BootType string + +const ( + UnsetBootType BootType = "" + LegacyBootType BootType = "legacy" + UEFIBootType BootType = "uefi" + HybridBootType BootType = "hybrid" +) + // A Distro represents composer's notion of what a given distribution is. type Distro interface { // Returns the name of the distro. diff --git a/internal/distro/rhel85/distro.go b/internal/distro/rhel85/distro.go index fd0b8e53f..2a7ea4072 100644 --- a/internal/distro/rhel85/distro.go +++ b/internal/distro/rhel85/distro.go @@ -53,17 +53,8 @@ const ( blueprintPkgsKey = "blueprint" ) -type BootType string - type ValidArches map[string]disk.PartitionTable -const ( - UnsetBootType BootType = "" - LegacyBootType BootType = "legacy" - UEFIBootType BootType = "uefi" - HybridBootType BootType = "hybrid" -) - var mountpointAllowList = []string{"/", "/var", "/var/*", "/home", "/opt", "/srv", "/usr"} type distribution struct { @@ -127,7 +118,7 @@ type architecture struct { imageTypeAliases map[string]string packageSets map[string]rpmmd.PackageSet legacy string - bootType BootType + bootType distro.BootType } func (a *architecture) Name() string { @@ -206,7 +197,7 @@ type imageType struct { // bootable image bootable bool // If set to a value, it is preferred over the architecture value - bootType BootType + bootType distro.BootType // List of valid arches for the image type validArches ValidArches } @@ -275,11 +266,11 @@ func (t *imageType) PackageSets(bp blueprint.Blueprint) map[string]rpmmd.Package var addUEFIBootPkg bool switch bt := t.getBootType(); bt { - case LegacyBootType: + case distro.LegacyBootType: addLegacyBootPkg = true - case UEFIBootType: + case distro.UEFIBootType: addUEFIBootPkg = true - case HybridBootType: + case distro.HybridBootType: addLegacyBootPkg = true addUEFIBootPkg = true default: @@ -326,9 +317,9 @@ func (t *imageType) Exports() []string { // getBootType returns the BootType which should be used for this particular // combination of architecture and image type. -func (t *imageType) getBootType() BootType { +func (t *imageType) getBootType() distro.BootType { bootType := t.arch.bootType - if t.bootType != UnsetBootType { + if t.bootType != distro.UnsetBootType { bootType = t.bootType } return bootType @@ -336,7 +327,7 @@ func (t *imageType) getBootType() BootType { func (t *imageType) supportsUEFI() bool { bootType := t.getBootType() - if bootType == HybridBootType || bootType == UEFIBootType { + if bootType == distro.HybridBootType || bootType == distro.UEFIBootType { return true } return false @@ -505,7 +496,7 @@ func newDistro(name, modulePlatformID, ostreeRef string) distro.Distro { edgePkgsKey: x8664EdgeCommitPackageSet(), }, legacy: "i386-pc", - bootType: HybridBootType, + bootType: distro.HybridBootType, } aarch64 := architecture{ @@ -515,7 +506,7 @@ func newDistro(name, modulePlatformID, ostreeRef string) distro.Distro { bootUEFIPkgsKey: aarch64UEFIBootPackageSet(), edgePkgsKey: aarch64EdgeCommitPackageSet(), }, - bootType: UEFIBootType, + bootType: distro.UEFIBootType, } ppc64le := architecture{ @@ -526,7 +517,7 @@ func newDistro(name, modulePlatformID, ostreeRef string) distro.Distro { buildPkgsKey: ppc64leBuildPackageSet(), }, legacy: "powerpc-ieee1275", - bootType: LegacyBootType, + bootType: distro.LegacyBootType, } s390x := architecture{ distro: rd, @@ -534,7 +525,7 @@ func newDistro(name, modulePlatformID, ostreeRef string) distro.Distro { packageSets: map[string]rpmmd.PackageSet{ bootLegacyPkgsKey: s390xLegacyBootPackageSet(), }, - bootType: LegacyBootType, + bootType: distro.LegacyBootType, } // Shared Services @@ -688,7 +679,7 @@ func newDistro(name, modulePlatformID, ostreeRef string) distro.Distro { enabledServices: ec2EnabledServices, kernelOptions: "console=ttyS0,115200n8 console=tty0 net.ifnames=0 rd.blacklist=nouveau nvme_core.io_timeout=4294967295 crashkernel=auto", bootable: true, - bootType: LegacyBootType, + bootType: distro.LegacyBootType, defaultSize: 10 * GigaByte, pipelines: ec2Pipelines, exports: []string{"image"}, @@ -725,7 +716,7 @@ func newDistro(name, modulePlatformID, ostreeRef string) distro.Distro { enabledServices: ec2EnabledServices, kernelOptions: "console=ttyS0,115200n8 console=tty0 net.ifnames=0 rd.blacklist=nouveau nvme_core.io_timeout=4294967295 crashkernel=auto", bootable: true, - bootType: LegacyBootType, + bootType: distro.LegacyBootType, defaultSize: 10 * GigaByte, pipelines: rhelEc2Pipelines, exports: []string{"archive"}, @@ -762,7 +753,7 @@ func newDistro(name, modulePlatformID, ostreeRef string) distro.Distro { enabledServices: ec2EnabledServices, kernelOptions: "console=ttyS0,115200n8 console=tty0 net.ifnames=0 rd.blacklist=nouveau nvme_core.io_timeout=4294967295 crashkernel=auto", bootable: true, - bootType: LegacyBootType, + bootType: distro.LegacyBootType, defaultSize: 10 * GigaByte, pipelines: rhelEc2Pipelines, exports: []string{"archive"}, diff --git a/internal/distro/rhel85/partition_tables.go b/internal/distro/rhel85/partition_tables.go index 4737f9cb6..e19c5e945 100644 --- a/internal/distro/rhel85/partition_tables.go +++ b/internal/distro/rhel85/partition_tables.go @@ -1,32 +1,10 @@ package rhel85 import ( - "io" - "math/rand" - - "github.com/google/uuid" - "github.com/osbuild/osbuild-composer/internal/blueprint" "github.com/osbuild/osbuild-composer/internal/disk" "github.com/osbuild/osbuild-composer/internal/distro" ) -const ( - sectorSize = 512 - - biosBootPartitionType = "21686148-6449-6E6F-744E-656564454649" - biosBootPartitionUUID = "FAC7F1FB-3E8D-4137-A512-961DE09A5549" - - bootPartitionType = "0FC63DAF-8483-4772-8E79-3D69D8477DE4" - bootPartitionUUID = "CB07C243-BC44-4717-853E-28852021225B" - - bootEFIPartitionType = "C12A7328-F81F-11D2-BA4B-00A0C93EC93B" - bootEFIPartitionUUID = "68B2905B-DF3E-4FB3-80FA-49D1E773AA33" - bootEFIFilesystemUUID = "7B77-95E7" - - defaultPartitionType = "0FC63DAF-8483-4772-8E79-3D69D8477DE4" - rootPartitionUUID = "6264D520-3FB9-423F-8AB8-7A0A8E3D3562" -) - var defaultArches = map[string]disk.PartitionTable{ distro.X86_64ArchName: gptPartitionTable, distro.Aarch64ArchName: gptPartitionTable, @@ -51,159 +29,13 @@ var dosPartitionTable = disk.PartitionTable{ Partitions: []disk.Partition{}, } -func createPartitionTable( - mountpoints []blueprint.FilesystemCustomization, - imageOptions distro.ImageOptions, - t *imageType, - ec2 bool, - rng *rand.Rand, -) disk.PartitionTable { - archName := t.arch.Name() - partitionTable, ok := t.validArches[archName] +func getBasePartitionTable(arch distro.Arch, validArches ValidArches) disk.PartitionTable { + archName := arch.Name() + partitionTable, ok := validArches[archName] if !ok { panic("unknown arch: " + archName) } - partitionTable.Size = imageOptions.Size - partitions := []disk.Partition{} - var start uint64 = 2048 - - if archName == distro.X86_64ArchName { - biosBootPartition := createPartition("bios", 2048, start, archName, rng) - partitions = append(partitions, biosBootPartition) - start += biosBootPartition.Size - if t.bootType != LegacyBootType { - bootEFIPartition := createPartition("/boot/efi", 204800, start, archName, rng) - partitions = append(partitions, bootEFIPartition) - start += bootEFIPartition.Size - } - } else if archName == distro.Aarch64ArchName { - if ec2 { - bootEFIParition := createPartition("/boot/efi", 409600, start, archName, rng) - partitions = append(partitions, bootEFIParition) - start += bootEFIParition.Size - bootPartition := createPartition("/boot", 1048576, 411648, archName, rng) - partitions = append(partitions, bootPartition) - start += bootPartition.Size - } else { - bootEFIPartition := createPartition("/boot/efi", 204800, start, archName, rng) - partitions = append(partitions, bootEFIPartition) - start += bootEFIPartition.Size - } - } else if archName == distro.Ppc64leArchName { - biosBootPartition := createPartition("bios", 8192, start, archName, rng) - partitions = append(partitions, biosBootPartition) - start += biosBootPartition.Size - } - - for _, m := range mountpoints { - if m.Mountpoint != "/" { - partitionSize := uint64(m.MinSize) / sectorSize - partition := createPartition(m.Mountpoint, partitionSize, start, archName, rng) - partitions = append(partitions, partition) - start += uint64(m.MinSize / sectorSize) - } - } - - // treat the root partition as a special case - // by setting it last and setting the size - // dynamically - rootSize := (imageOptions.Size / sectorSize) - start - 100 - rootPartition := createPartition("/", rootSize, start, archName, rng) - partitions = append(partitions, rootPartition) - - partitionTable.Partitions = append(partitionTable.Partitions, partitions...) - return partitionTable } - -func createPartition(mountpoint string, size uint64, start uint64, archName string, rng *rand.Rand) disk.Partition { - if mountpoint == "bios" { - diskPartition := disk.Partition{ - Start: start, - Size: size, - Bootable: true, - } - if archName == distro.X86_64ArchName { - diskPartition.Type = biosBootPartitionType - diskPartition.UUID = biosBootPartitionUUID - return diskPartition - } - diskPartition.Type = "41" - return diskPartition - } - var filesystem disk.Filesystem - // /boot/efi mountpoint is a special case - // return early - if mountpoint == "/boot/efi" { - filesystem = createFilesystemDisk(mountpoint, bootEFIFilesystemUUID) - return disk.Partition{ - Start: start, - Size: size, - Type: bootEFIPartitionType, - UUID: bootEFIPartitionUUID, - Filesystem: &filesystem, - } - } - partition := disk.Partition{ - Start: start, - Size: size, - Filesystem: &filesystem, - } - diskUUID := uuid.Must(newRandomUUIDFromReader(rng)).String() - filesystem = createFilesystemDisk(mountpoint, diskUUID) - if mountpoint == "/boot" { - partition.Type = bootPartitionType - partition.UUID = bootPartitionUUID - return partition - } - if archName == distro.X86_64ArchName || archName == distro.Aarch64ArchName { - if mountpoint == "/" { - // set Label for root mountpoint - filesystem.Label = "root" - partition.Type = defaultPartitionType - partition.UUID = rootPartitionUUID - return partition - } - partition.Type = defaultPartitionType - partition.UUID = uuid.Must(newRandomUUIDFromReader(rng)).String() - return partition - } - if mountpoint == "/" && archName == distro.S390xArchName { - partition.Bootable = true - } - return partition -} - -func createFilesystemDisk(mountpoint string, uuid string) disk.Filesystem { - if mountpoint == "/boot/efi" { - return disk.Filesystem{ - Type: "vfat", - UUID: uuid, - Mountpoint: mountpoint, - FSTabOptions: "defaults,uid=0,gid=0,umask=077,shortname=winnt", - FSTabFreq: 0, - FSTabPassNo: 2, - } - } - return disk.Filesystem{ - Type: "xfs", - UUID: uuid, - Mountpoint: mountpoint, - FSTabOptions: "defaults", - FSTabFreq: 0, - FSTabPassNo: 0, - } -} - -func newRandomUUIDFromReader(r io.Reader) (uuid.UUID, error) { - var id uuid.UUID - _, err := io.ReadFull(r, id[:]) - if err != nil { - return uuid.Nil, err - } - id[6] = (id[6] & 0x0f) | 0x40 // Version 4 - id[8] = (id[8] & 0x3f) | 0x80 // Variant is 10 - return id, nil -} diff --git a/internal/distro/rhel85/pipelines.go b/internal/distro/rhel85/pipelines.go index a83779584..4ef0f93d4 100644 --- a/internal/distro/rhel85/pipelines.go +++ b/internal/distro/rhel85/pipelines.go @@ -23,7 +23,8 @@ func qcow2Pipelines(t *imageType, customizations *blueprint.Customizations, opti return nil, err } - partitionTable := createPartitionTable(customizations.GetFilesystems(), options, t, false, rng) + basePartitionTable := getBasePartitionTable(t.Arch(), t.validArches) + partitionTable := disk.CreatePartitionTable(customizations.GetFilesystems(), options, t.Arch(), basePartitionTable, t.bootType, false, rng) treePipeline = prependKernelCmdlineStage(treePipeline, t, &partitionTable) if options.Subscription == nil { @@ -76,7 +77,8 @@ func vhdPipelines(t *imageType, customizations *blueprint.Customizations, option return nil, err } - partitionTable := createPartitionTable(customizations.GetFilesystems(), options, t, false, rng) + basePartitionTable := getBasePartitionTable(t.Arch(), t.validArches) + partitionTable := disk.CreatePartitionTable(customizations.GetFilesystems(), options, t.Arch(), basePartitionTable, t.bootType, false, rng) treePipeline.AddStage(osbuild.NewFSTabStage(partitionTable.FSTabStageOptionsV2())) kernelVer := kernelVerStr(packageSetSpecs[blueprintPkgsKey], customizations.GetKernel().Name, t.Arch().Name()) treePipeline.AddStage(bootloaderConfigStage(t, partitionTable, customizations.GetKernel(), kernelVer)) @@ -103,7 +105,8 @@ func vmdkPipelines(t *imageType, customizations *blueprint.Customizations, optio return nil, err } - partitionTable := createPartitionTable(customizations.GetFilesystems(), options, t, false, rng) + basePartitionTable := getBasePartitionTable(t.Arch(), t.validArches) + partitionTable := disk.CreatePartitionTable(customizations.GetFilesystems(), options, t.Arch(), basePartitionTable, t.bootType, false, rng) treePipeline.AddStage(osbuild.NewFSTabStage(partitionTable.FSTabStageOptionsV2())) kernelVer := kernelVerStr(packageSetSpecs[blueprintPkgsKey], customizations.GetKernel().Name, t.Arch().Name()) treePipeline.AddStage(bootloaderConfigStage(t, partitionTable, customizations.GetKernel(), kernelVer)) @@ -130,7 +133,8 @@ func openstackPipelines(t *imageType, customizations *blueprint.Customizations, return nil, err } - partitionTable := createPartitionTable(customizations.GetFilesystems(), options, t, false, rng) + basePartitionTable := getBasePartitionTable(t.Arch(), t.validArches) + partitionTable := disk.CreatePartitionTable(customizations.GetFilesystems(), options, t.Arch(), basePartitionTable, t.bootType, false, rng) treePipeline.AddStage(osbuild.NewFSTabStage(partitionTable.FSTabStageOptionsV2())) kernelVer := kernelVerStr(packageSetSpecs[blueprintPkgsKey], customizations.GetKernel().Name, t.Arch().Name()) treePipeline.AddStage(bootloaderConfigStage(t, partitionTable, customizations.GetKernel(), kernelVer)) @@ -392,7 +396,8 @@ func ec2CommonPipelines(t *imageType, customizations *blueprint.Customizations, pipelines := make([]osbuild.Pipeline, 0) pipelines = append(pipelines, *buildPipeline(repos, packageSetSpecs[buildPkgsKey])) - partitionTable := createPartitionTable(customizations.GetFilesystems(), options, t, true, rng) + basePartitionTable := getBasePartitionTable(t.Arch(), t.validArches) + partitionTable := disk.CreatePartitionTable(customizations.GetFilesystems(), options, t.Arch(), basePartitionTable, t.bootType, true, rng) var treePipeline *osbuild.Pipeline var err error switch arch := t.arch.Name(); arch {