Files
clr-installer/storage/ops.go
T
John Akre bc8019fc3a storage: Sort partition numbers as integers
When sorting partitions, the partition numbers must be separated from
the partition name and be compared as integers. Comparing the partition
numbers in ascii leads to undesirable sorting results when using numbers
with multiple digits.

Fixes: #510

Signed-off-by: John Akre <john.w.akre@intel.com>
2019-09-27 15:19:59 -07:00

1896 lines
50 KiB
Go

// Copyright © 2019 Intel Corporation
//
// SPDX-License-Identifier: GPL-3.0-only
package storage
import (
"bytes"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"sort"
"strconv"
"strings"
"syscall"
"time"
"github.com/clearlinux/clr-installer/cmd"
"github.com/clearlinux/clr-installer/errors"
"github.com/clearlinux/clr-installer/log"
"github.com/clearlinux/clr-installer/progress"
"github.com/clearlinux/clr-installer/utils"
)
type blockDeviceOps struct {
makeFsCommand func(bd *BlockDevice, args []string) ([]string, error)
makeFsArgs []string
makePartCommand func(bd *BlockDevice) (string, error)
}
var (
bdOps = map[string]*blockDeviceOps{
"ext2": {commonMakeFsCommand, []string{"-v", "-F"}, commonMakePartCommand},
"ext3": {commonMakeFsCommand, []string{"-v", "-F"}, commonMakePartCommand},
"ext4": {commonMakeFsCommand, []string{"-v", "-F", "-b", "4096"}, commonMakePartCommand},
"btrfs": {commonMakeFsCommand, []string{"-f"}, commonMakePartCommand},
"xfs": {commonMakeFsCommand, []string{"-f"}, commonMakePartCommand},
"swap": {swapMakeFsCommand, []string{}, swapMakePartCommand},
"vfat": {commonMakeFsCommand, []string{"-F32"}, vfatMakePartCommand},
}
guidMap = map[string]string{
"/": "4F68BCE3-E8CD-4DB1-96E7-FBCAF984B709",
"/home": "933AC7E1-2EB4-4F13-B844-0E14E2AEF915",
"/srv": "3B8F8425-20E0-4F3B-907F-1A25A76F98E8",
"swap": "0657FD6D-A4AB-43C4-84E5-0933C84B4F4F",
"efi": "C12A7328-F81F-11D2-BA4B-00A0C93EC93B",
}
mountedPoints []string
mountedEncrypts []string
)
// MakeFs runs mkfs.* commands for a BlockDevice definition
func (bd *BlockDevice) MakeFs() error {
if bd.Type == BlockDeviceTypeDisk {
return errors.Errorf("Trying to run MakeFs() against a disk, partition required")
}
if op, ok := bdOps[bd.FsType]; ok {
if cmd, err := op.makeFsCommand(bd, op.makeFsArgs); err == nil {
return makeFs(bd, cmd)
}
}
return errors.Errorf("MakeFs() not implemented for filesystem: %s", bd.FsType)
}
func makeFs(bd *BlockDevice, args []string) error {
if bd.Options != "" {
args = append(args, strings.Split(bd.Options, " ")...)
}
args = append(args, bd.GetMappedDeviceFile())
err := cmd.RunAndLog(args...)
if err != nil {
return errors.Wrap(err)
}
// Updated the UUID and LABEL now that we made the fs
err = bd.updatePartitionInfo()
if err != nil {
return errors.Wrap(err)
}
return nil
}
func (bd *BlockDevice) updatePartitionInfo() error {
if bd.Type == BlockDeviceTypeDisk {
return errors.Errorf("Trying to run updatePartitionInfo() against a disk, partition required")
}
var err error
blkid := bytes.NewBuffer(nil)
devFile := bd.GetDeviceFile()
// Read the partition blkid info
err = cmd.Run(blkid,
"blkid",
"--probe",
devFile,
"--output",
"export",
)
if err != nil {
log.Warning("updatePartitionInfo() had an error reading blkid %q",
fmt.Sprintf("%s", blkid.String()))
return err
}
for _, line := range strings.Split(blkid.String(), "\n") {
fields := strings.Split(line, "=")
if len(fields) == 2 {
if fields[0] == "LABEL" {
bd.Label = fields[1]
log.Debug("updatePartitionInfo: Updated %d LABEL: %s", devFile, bd.Label)
} else if fields[0] == "UUID" {
bd.UUID = fields[1]
log.Debug("updatePartitionInfo: Updated %d UUID: %s", devFile, bd.UUID)
}
} else {
log.Debug("updatePartitionInfo: Ignoring unknown line: %s", line)
}
}
return err
}
// getGUID determines the partition type guid either based on:
// + mount point
// + file system type (i.e swap)
// + or if it's the "special" efi case
func (bd *BlockDevice) getGUID() (string, error) {
if guid, ok := guidMap[bd.MountPoint]; ok {
return guid, nil
}
if guid, ok := guidMap[bd.FsType]; ok {
return guid, nil
}
if bd.FsType == "vfat" && bd.MountPoint == "/boot" {
return guidMap["efi"], nil
}
return "none", errors.Errorf("Could not determine the guid for: %s", bd.Name)
}
func (bd *BlockDevice) isStandardMount() bool {
standard := false
for mnt := range guidMap {
if bd.MountPoint == mnt {
standard = true
}
}
if bd.MountPoint == "/boot" {
standard = true
}
return standard
}
// Mount will mount a block devices bd considering its mount point and the
// root directory
func (bd *BlockDevice) Mount(root string) error {
if bd.Type == BlockDeviceTypeDisk {
return errors.Errorf("Trying to run mountFs() against a disk, partition required")
}
targetPath := filepath.Join(root, bd.MountPoint)
return mountFs(bd.GetMappedDeviceFile(), targetPath, bd.FsType, syscall.MS_RELATIME)
}
// UmountAll unmounts all previously mounted devices
func UmountAll() error {
var mountError error
fails := make([]string, 0)
// Ensure the top level mount point is unmounted last
sort.Sort(sort.Reverse(sort.StringSlice(mountedPoints)))
for _, point := range mountedPoints {
if err := syscall.Unmount(point, syscall.MNT_FORCE|syscall.MNT_DETACH); err != nil {
err = fmt.Errorf("umount %s: %v", point, err)
log.ErrorError(err)
fails = append(fails, point)
} else {
log.Debug("Unmounted ok: %s", point)
}
}
for _, point := range mountedEncrypts {
if err := unMapEncrypted(point); err != nil {
err = fmt.Errorf("unmap encrypted %s: %v", point, err)
log.ErrorError(err)
fails = append(fails, "e-"+point)
} else {
log.Debug("Encrypted partition %q unmapped", point)
}
}
if len(fails) > 0 {
mountError = errors.Errorf("Failed to unmount: %v", fails)
}
return mountError
}
// When you specify a start (or end) position to the parted mkpart command,
// it internally generates a range of acceptable values centered on the value
// you specify, and extends equally on both sides by half the unit size you
// used but ONLY when you use K or M (or G); using B or any of the XiB will
// not auto align.
// We choose M to provide a 1M wide window for a possible optimal value.
func getStartEndMB(start uint64, end uint64) string {
startMB := (start / (1000 * 1000))
endMB := (end / (1000 * 1000))
strStart := fmt.Sprintf("%dM", startMB)
if start < 1 {
strStart = "0%"
}
strEnd := fmt.Sprintf("%dM", endMB)
if end < 1 {
strEnd = "-1"
}
return strStart + " " + strEnd
}
// WritePartitionLabel make a device a 'gpt' partition type
// Only call when we are wiping and reusing the entire disk
func (bd *BlockDevice) WritePartitionLabel() error {
if bd.Type != BlockDeviceTypeDisk && bd.Type != BlockDeviceTypeLoop {
return errors.Errorf("Type is partition, disk required")
}
mesg := utils.Locale.Get("Writing partition table to: %s", bd.Name)
prg := progress.NewLoop(mesg)
log.Info(mesg)
args := []string{
"parted",
"-s",
bd.GetDeviceFile(),
"mklabel",
"gpt",
}
err := cmd.RunAndLog(args...)
if err != nil {
return errors.Wrap(err)
}
prg.Success()
return nil
}
// WritePartitionTable writes the defined partitions to the actual block device
func (bd *BlockDevice) WritePartitionTable(legacyBios bool, wholeDisk bool, dryRun *[]string) error {
if bd.Type != BlockDeviceTypeDisk && bd.Type != BlockDeviceTypeLoop {
return errors.Errorf("Type is partition, disk required")
}
var prg progress.Progress
//write the partition label
if dryRun != nil {
if wholeDisk {
*dryRun = append(*dryRun, bd.Name+": "+utils.Locale.Get(PartitioningWarning))
}
} else {
if wholeDisk {
if err := bd.WritePartitionLabel(); err != nil {
return err
}
} else {
log.Debug("WritePartitionTable: partial disk, skipping mklabel for %s", bd.Name)
}
mesg := utils.Locale.Get("Updating partition table for: %s", bd.Name)
prg = progress.NewLoop(mesg)
log.Info(mesg)
}
var err error
var start uint64
maxFound := false
// First remove any user removed partitions
if len(bd.removedParts) > 0 {
rmArgs := []string{
"parted",
"-a",
"optimal",
bd.GetDeviceFile(),
"unit", "MB",
"--script",
"--",
}
for _, curr := range bd.removedParts {
if dryRun == nil {
rmArgs = append(rmArgs, fmt.Sprintf("rm %d", curr))
} else {
*dryRun = append(*dryRun, fmt.Sprintf("%s: %s",
bd.GetNewPartitionName(curr),
utils.Locale.Get(RemoveParitionWarning)))
}
}
if dryRun == nil {
log.Debug("WritePartitionTable: remove partitions : %v", bd.removedParts)
err = cmd.RunAndLog(rmArgs...)
if err != nil {
log.Warning("Failed to remove existing partition: %v (%s)", bd.removedParts, err)
}
}
}
// Initialize the partition list before we add new ones
currentPartitions := bd.getPartitionList()
// Sort the partitions by name before writing the partition table
log.Debug("Partitions before sorting:")
for _, part := range bd.Children {
part.logDetails()
}
sort.Sort(ByBDName(bd.Children))
log.Debug("Partitions after sorting:")
for _, part := range bd.Children {
part.logDetails()
// Make sure each partition has a number set
part.SetPartitionNumber(part.GetPartitionNumber())
}
// Make the needed new partitions
for _, curr := range bd.Children {
if dryRun != nil {
if curr.MakePartition {
size, _ := HumanReadableSizeWithPrecision(curr.Size, 1)
*dryRun = append(*dryRun, fmt.Sprintf("%s: %s [%s]",
bd.Name, utils.Locale.Get(AddPartitionInfo), size))
}
continue
}
log.Debug("WritePartitionTable: processing child: %v", curr)
baseArgs := []string{
"parted",
"-a",
"optimal",
bd.GetDeviceFile(),
"unit", "MB",
"--script",
"--",
}
if !curr.MakePartition {
log.Debug("WritePartitionTable: skipping partition %s", curr.Name)
continue
}
var mkPart string
op, found := bdOps[curr.FsType]
if !found {
return errors.Errorf("No makePartCommand() implementation for: %s",
curr.FsType)
}
mkPart, err = op.makePartCommand(curr)
if err != nil {
return err
}
size := uint64(curr.Size)
end := start + size
if !wholeDisk {
start, end = bd.getPartitionStartEnd(curr.partition)
} else {
log.Debug("WritePartitionTable: WholeDisk mode")
}
log.Debug("WritePartitionTable: start: %d, end: %d", start, end)
if size < 1 {
if maxFound {
return errors.Errorf("Found more than one partition with size 0 for %s!", bd.Name)
}
maxFound = true
end = 0
}
retries := 3
for {
mkPartCmd := mkPart + " " + getStartEndMB(start, end)
log.Debug("WritePartitionTable: mkPartCmd: %s", mkPartCmd)
args := append(baseArgs, mkPartCmd)
err = cmd.RunAndLog(args...)
if err == nil || retries == 0 {
break
}
// Move the start position ahead one MB in an attempt
// to find a working optimal partition entry
start = start + (1000 * 1000)
retries--
}
if err != nil {
return errors.Wrap(err)
}
// Get the new list of partitions
newPartitions := bd.getPartitionList()
// The current partition is new one added
curr.SetPartitionNumber(findNewPartition(currentPartitions, newPartitions).Number)
start = end
currentPartitions = newPartitions
}
if dryRun == nil {
var bootPartition uint64
bootStyle := "boot"
guids := map[int]string{}
// Now that all new partitions are created,
// and we know their assigned numbers ...
for _, curr := range bd.Children {
// First, check if we have the standard /boot partition
// We have a /boot partition, use this
if curr.MountPoint == "/boot" {
bootPartition = curr.partition
if legacyBios {
bootStyle = "legacy_boot"
}
}
var guid string
guid, err = curr.getGUID()
if err != nil {
log.Warning("%s", err)
}
if curr.FsType != "swap" || curr.Type != BlockDeviceTypeCrypt {
guids[int(curr.partition)] = guid
}
}
prg.Success()
msg := utils.Locale.Get("Adjusting filesystem configurations")
prg = progress.MultiStep(len(guids), "", msg)
log.Info(msg)
cnt := 1
for idx, guid := range guids {
if guid == "none" {
continue
}
args := []string{
"sgdisk",
bd.GetDeviceFile(),
fmt.Sprintf("--typecode=%d:%s", idx, guid),
}
err = cmd.RunAndLog(args...)
if err != nil {
return errors.Wrap(err)
}
prg.Partial(cnt)
cnt = cnt + 1
}
// In case we didn't have a /boot partition, we
// need to set / as boot
for _, curr := range bd.Children {
// Only check for / in new partitions
if !curr.MakePartition {
continue
}
if curr.MountPoint == "/" {
// If legacyBios mode and we do not have a boot, use root
if legacyBios && bootPartition == 0 {
bootPartition = curr.partition
bootStyle = "legacy_boot"
}
}
}
if bootPartition != 0 {
args := []string{
"parted",
bd.GetDeviceFile(),
fmt.Sprintf("set %d %s on", bootPartition, bootStyle),
}
err = cmd.RunAndLog(args...)
if err != nil {
return errors.Wrap(err)
}
}
if err = bd.PartProbe(); err != nil {
prg.Failure()
return err
}
time.Sleep(time.Duration(4) * time.Second)
prg.Success()
}
return nil
}
func (bd *BlockDevice) getPartitionList() []*PartedPartition {
var partitionList []*PartedPartition
var err error
partTable := bytes.NewBuffer(nil)
devFile := bd.GetDeviceFile()
if !utils.IntSliceContains([]int{BlockDeviceTypeDisk, BlockDeviceTypeLoop}, int(bd.Type)) {
log.Warning("getPartitionList() called on non-disk %q", devFile)
return partitionList
}
// Read the partition table for the device
err = cmd.Run(partTable,
"parted",
"--machine",
"--script",
"--",
devFile,
"unit",
"B",
"print",
)
if err != nil {
log.Warning("getPartitionList() had an error reading partition table %q",
fmt.Sprintf("%s", partTable.String()))
return partitionList
}
for _, line := range strings.Split(partTable.String(), ";\n") {
partition := &PartedPartition{}
fields := strings.Split(line, ":")
if len(fields) == 7 {
partition.Number, err = strconv.ParseUint(fields[0], 10, 64)
if err != nil {
log.Warning("getPartitionList: Failed to parse partition number from: %s", line)
}
partition.Start, err = strconv.ParseUint(strings.TrimRight(fields[1], "B"), 10, 64)
if err != nil {
log.Warning("getPartitionList: Failed to parse start position from: %s", line)
}
partition.End, err = strconv.ParseUint(strings.TrimRight(fields[2], "B"), 10, 64)
if err != nil {
log.Warning("getPartitionList: Failed to parse end position from: %s", line)
}
partition.Size, err = strconv.ParseUint(strings.TrimRight(fields[3], "B"), 10, 64)
if err != nil {
log.Warning("getPartitionList: Failed to parse partition size from: %s", line)
}
partition.FileSystem = fields[4]
partition.Name = fields[5]
partition.Flags = fields[6]
partitionList = append(partitionList, partition)
}
}
return partitionList
}
func findNewPartition(currentPartitions, newPartitions []*PartedPartition) *PartedPartition {
newPartition := &PartedPartition{}
if len(newPartitions) <= len(currentPartitions) {
log.Warning("findNewPartition: number of new partitions is not greater than the current")
return newPartition
}
if len(newPartitions)-len(currentPartitions) != 1 {
log.Warning("findNewPartition: number of new partition is more than 1")
return newPartition
}
for _, newPart := range newPartitions {
found := true
for _, curPart := range currentPartitions {
if curPart.Number == newPart.Number {
found = false
continue
}
}
if found {
newPartition = newPart
continue
}
}
return newPartition
}
func (bd *BlockDevice) getPartitionTable() *bytes.Buffer {
partTable := bytes.NewBuffer(nil)
devFile := bd.GetDeviceFile()
if !utils.IntSliceContains([]int{BlockDeviceTypeDisk, BlockDeviceTypeLoop}, int(bd.Type)) {
log.Warning("getPartitionTable() called on non-disk %q", devFile)
return partTable
}
// Read the partition table for the device
err := cmd.Run(partTable,
"parted",
"--machine",
"--script",
"--",
devFile,
"unit",
"B",
"print",
"free",
)
if err != nil {
log.Warning("getPartitionTable() had an error reading partition table %q",
fmt.Sprintf("%s", partTable.String()))
empty := bytes.NewBuffer(nil)
return empty
}
return partTable
}
func (bd *BlockDevice) getPartitionStartEnd(partNumber uint64) (uint64, uint64) {
var start, end uint64
devFile := bd.GetDeviceFile()
if !utils.IntSliceContains([]int{BlockDeviceTypeDisk, BlockDeviceTypeLoop}, int(bd.Type)) {
log.Warning("getPartitionStartEnd() called on non-disk %q", devFile)
return start, end
}
for _, part := range bd.PartTable {
if part.Number == partNumber {
return part.Start, part.End
}
}
log.Warning("getPartitionStartEnd() did not find partition %s for disk %q", partNumber, devFile)
return start, end
}
// LargestContiguousFreeSpace returns the largest, contiguous block of free
// space in the partition table for the block device.
// If none found, returns {0, 0}
func (bd *BlockDevice) LargestContiguousFreeSpace(minSize uint64) (uint64, uint64) {
var start, end, size uint64
devFile := bd.GetDeviceFile()
if !utils.IntSliceContains([]int{BlockDeviceTypeDisk, BlockDeviceTypeLoop}, int(bd.Type)) {
log.Warning("LargestContiguousFreeSpace() called on non-disk %q", devFile)
return start, end
}
size = minSize - 1
for _, part := range bd.PartTable {
if part.Number == 0 && part.FileSystem == "free" {
if part.Size > size {
start = part.Start
end = part.End
}
}
}
return start, end
}
// AddFromFreePartition reduces the free partition by the size given
// User when adding a new partition to a disk from free space
func (bd *BlockDevice) AddFromFreePartition(parted *PartedPartition, child *BlockDevice) {
var next uint64
var partitionList []*PartedPartition
devFile := bd.GetDeviceFile()
if !utils.IntSliceContains([]int{BlockDeviceTypeDisk, BlockDeviceTypeLoop}, int(bd.Type)) {
log.Warning("AddFromFreePartition() called on non-disk %q", devFile)
return
}
const (
maxPartitions = 127
)
found := false
next = 1
for !found && next < maxPartitions {
present := false
for _, partition := range bd.PartTable {
if partition.Number == next {
present = true
break
}
}
if present {
next = next + 1
} else {
found = true
}
}
if next >= maxPartitions {
log.Warning("AddFromFreePartition() could not add new partition: %v", child)
return
}
for _, partition := range bd.PartTable {
// Find the partition to update/remove
if partition.Number == parted.Number &&
partition.Start == parted.Start {
log.Debug("Found the free partition to update: %v", partition)
addPart := partition.Clone()
addPart.Number = next
addPart.End = addPart.Start + (child.Size - 1)
addPart.Size = child.Size
addPart.FileSystem = ""
log.Debug("Adding the new partition: %v", addPart)
partitionList = append(partitionList, addPart)
child.SetPartitionNumber(addPart.Number)
bd.AddChild(child)
log.Debug("Added new child partition: %v", child)
newSize := partition.Size - addPart.Size
newStart := addPart.End + 1
log.Debug("Free partition newStart: %d, newSize: %d", newStart, newSize)
if (int(partition.End) - int(newStart)) <= 0 {
log.Debug("No Free space left: %v", partition)
continue
}
if newSize > (10 * 1024 * 1024) {
newPart := partition.Clone()
newPart.Start = newStart
newPart.Size = newSize
log.Debug("Found enough free to add back: %v", newPart)
partitionList = append(partitionList, newPart)
}
continue
}
log.Debug("Not the right partition, adding back: %v", partition)
partitionList = append(partitionList, partition)
}
bd.PartTable = partitionList
// Consolidate neighboring free partitions
bd.consolidateFree()
}
func (bd *BlockDevice) consolidateFree() {
last := &PartedPartition{}
var newPartTable []*PartedPartition
for _, part := range bd.PartTable {
// Found a free partition
if part.Number == 0 && part.FileSystem == "free" {
// And the last partition was also free, then consolidate
if last.Number == 0 && last.FileSystem == "free" {
last.End = part.End
last.Size = last.Size + part.Size
continue
}
}
newPart := part.Clone()
newPartTable = append(newPartTable, newPart)
last = newPart
}
bd.PartTable = newPartTable
}
// RemovePartition remove a child from the disk and updates
// frees the space in the partition table
func (bd *BlockDevice) RemovePartition(child *BlockDevice) *PartedPartition {
var removedPartition *PartedPartition
devFile := bd.GetDeviceFile()
if !utils.IntSliceContains([]int{BlockDeviceTypeDisk, BlockDeviceTypeLoop}, int(bd.Type)) {
log.Warning("RemovePartition() called on non-disk %q", devFile)
return removedPartition
}
deleteIndex := -1
for idx, curr := range bd.Children {
if curr.Name == child.Name {
child.Parent = nil
deleteIndex = idx
break
}
}
if deleteIndex < 0 {
log.Warning("RemovePartition() fail to find (and remove) child: %v", child)
return removedPartition
}
log.Debug("RemovePartition() found child partition index to delete %d", deleteIndex)
// keep a reference to the child
delelteChild := bd.Children[deleteIndex].Clone()
// Remove the child for the block devices
bd.Children = append(bd.Children[:deleteIndex], bd.Children[deleteIndex+1:]...)
partString := devNameSuffixExp.FindString(delelteChild.Name)
partNumber, err := strconv.ParseUint(partString, 10, 64)
if err != nil {
log.Warning("RemovePartition() fail to find child partition number: %v", child)
return removedPartition
}
log.Debug("RemovePartition() Need to add partition %d to the remove list", partNumber)
for _, partition := range bd.PartTable {
// Find the partition to free/remove
if partition.Number == partNumber {
log.Debug("Found the partition to free partition: %v", partition)
partition.Number = 0
partition.FileSystem = "free"
partition.Name = ""
partition.Flags = ""
removedPartition = partition.Clone()
break
}
}
// Consolidate neighboring free partitions
bd.consolidateFree()
if !delelteChild.MakePartition {
bd.addRemovePartition(partNumber)
log.Debug("RemovePartition() Add partition to be removed %d", partNumber)
}
return removedPartition
}
// Populate the current partition table for a disk device
func (bd *BlockDevice) setPartitionTable(partTable *bytes.Buffer) {
var partitionList []*PartedPartition
devFile := bd.GetDeviceFile()
if !utils.IntSliceContains([]int{BlockDeviceTypeDisk, BlockDeviceTypeLoop}, int(bd.Type)) {
log.Warning("setPartitionTable() called on non-disk %q", devFile)
return
}
var err error
for _, line := range strings.Split(partTable.String(), ";\n") {
partition := &PartedPartition{}
log.Debug("setPartitionTable() line is %q", line)
fields := strings.Split(line, ":")
if len(fields) == 7 {
partition.Number, err = strconv.ParseUint(fields[0], 10, 64)
if err != nil {
log.Warning("setPartitionTable: Failed to parse partition number from: %s", line)
}
partition.Start, err = strconv.ParseUint(strings.TrimRight(fields[1], "B"), 10, 64)
if err != nil {
log.Warning("setPartitionTable: Failed to parse start position from: %s", line)
}
partition.End, err = strconv.ParseUint(strings.TrimRight(fields[2], "B"), 10, 64)
if err != nil {
log.Warning("setPartitionTable: Failed to parse end position from: %s", line)
}
partition.Size, err = strconv.ParseUint(strings.TrimRight(fields[3], "B"), 10, 64)
if err != nil {
log.Warning("setPartitionTable: Failed to parse partition size from: %s", line)
}
partition.FileSystem = fields[4]
partition.Name = fields[5]
partition.Flags = fields[6]
partitionList = append(partitionList, partition)
continue
}
if len(fields) == 5 && fields[4] == "free" {
partition.Number = 0 // We use 0 to special case as a free partition
partition.Start, err = strconv.ParseUint(strings.TrimRight(fields[1], "B"), 10, 64)
if err != nil {
log.Warning("setPartitionTable: Failed to parse start position from: %s", line)
}
partition.End, err = strconv.ParseUint(strings.TrimRight(fields[2], "B"), 10, 64)
if err != nil {
log.Warning("setPartitionTable: Failed to parse end position from: %s", line)
}
partition.Size, err = strconv.ParseUint(strings.TrimRight(fields[3], "B"), 10, 64)
if err != nil {
log.Warning("setPartitionTable: Failed to parse partition size from: %s", line)
}
partition.FileSystem = fields[4]
partitionList = append(partitionList, partition)
}
}
bd.PartTable = partitionList
}
// MountMetaFs mounts proc, sysfs and devfs in the target installation directory
func MountMetaFs(rootDir string) error {
err := mountProcFs(rootDir)
if err != nil {
return err
}
err = mountSysFs(rootDir)
if err != nil {
return err
}
err = mountDevFs(rootDir)
if err != nil {
return err
}
return nil
}
func mountFs(device string, mPointPath string, fsType string, flags uintptr) error {
var err error
if _, err = os.Stat(mPointPath); os.IsNotExist(err) {
if err = os.MkdirAll(mPointPath, 0777); err != nil {
return errors.Errorf("mkdir %s: %v", mPointPath, err)
}
}
if err = syscall.Mount(device, mPointPath, fsType, flags, ""); err != nil {
return errors.Errorf("mount %s %s %s: %v", device, mPointPath, fsType, err)
}
log.Debug("Mounted ok: %s", mPointPath)
// Store the mount point for later unmounting
mountedPoints = append(mountedPoints, mPointPath)
return err
}
func mountDevFs(rootDir string) error {
mPointPath := filepath.Join(rootDir, "dev")
return mountFs("/dev", mPointPath, "devtmpfs", syscall.MS_BIND)
}
func mountSysFs(rootDir string) error {
mPointPath := filepath.Join(rootDir, "sys")
return mountFs("/sys", mPointPath, "sysfs", syscall.MS_BIND)
}
func mountProcFs(rootDir string) error {
mPointPath := filepath.Join(rootDir, "proc")
return mountFs("/proc", mPointPath, "proc", syscall.MS_BIND)
}
func getMakeFsLabel(bd *BlockDevice) []string {
label := []string{}
labelArg := "-L"
if bd.Label != "" {
maxLen := MaxLabelLength(bd.FsType)
if bd.FsType == "vfat" {
labelArg = "-n"
}
if len(bd.Label) > maxLen {
shortLabel := string(bd.Label[0:(maxLen - 1)])
log.Warning("Truncating file system label '%s' to %d character label '%s'",
bd.FsType, maxLen, shortLabel)
bd.Label = shortLabel
}
label = append(label, labelArg, bd.Label)
}
return label
}
func commonMakeFsCommand(bd *BlockDevice, args []string) ([]string, error) {
cmd := []string{
fmt.Sprintf("mkfs.%s", bd.FsType),
}
label := getMakeFsLabel(bd)
if len(label) > 0 {
cmd = append(cmd, label...)
}
cmd = append(cmd, args...)
return cmd, nil
}
func commonMakePartCommand(bd *BlockDevice) (string, error) {
args := []string{
"mkpart",
bd.MountPoint,
}
return strings.Join(args, " "), nil
}
func makeEncryptedSwap(bd *BlockDevice) error {
args := []string{
"wipefs",
bd.GetDeviceFile(),
}
err := cmd.RunAndLog(args...)
if err != nil {
return errors.Wrap(err)
}
args = []string{
"mkfs.ext2",
"-L",
filepath.Base(bd.GetMappedDeviceFile()),
bd.GetDeviceFile(),
"1M",
}
err = cmd.RunAndLog(args...)
if err != nil {
return errors.Wrap(err)
}
return nil
}
func swapMakeFsCommand(bd *BlockDevice, args []string) ([]string, error) {
cmd := []string{
"mkswap",
}
if bd.FsType == "swap" && bd.Type == BlockDeviceTypeCrypt {
// Fake the standard command, and call the special function
cmd = []string{
"/bin/true",
}
if err := makeEncryptedSwap(bd); err != nil {
return cmd, err
}
} else {
label := getMakeFsLabel(bd)
if len(label) > 0 {
cmd = append(cmd, label...)
}
cmd = append(cmd, args...)
}
return cmd, nil
}
func swapMakePartCommand(bd *BlockDevice) (string, error) {
partName := "linux-swap"
if bd.FsType == "swap" && bd.Type == BlockDeviceTypeCrypt {
mapped := fmt.Sprintf("eswap-%s", bd.Name)
bd.MappedName = filepath.Join("mapper", mapped)
partName = mapped
}
args := []string{
"mkpart",
partName,
}
return strings.Join(args, " "), nil
}
func vfatMakePartCommand(bd *BlockDevice) (string, error) {
args := []string{
"mkpart",
"EFI",
"fat32",
}
return strings.Join(args, " "), nil
}
// MakeImage create an image file considering the total block device size
func MakeImage(bd *BlockDevice, file string) error {
size, err := bd.DiskSize()
if err != nil {
return errors.Wrap(err)
}
args := []string{
"qemu-img",
"create",
"-f",
"raw",
file,
fmt.Sprintf("%d", size),
}
err = cmd.RunAndLog(args...)
if err != nil {
return errors.Wrap(err)
}
return nil
}
// SetupLoopDevice sets up a loop device and return the loop device path
func SetupLoopDevice(file string) (string, error) {
args := []string{
"losetup",
"--find",
"--show",
file,
}
buff := bytes.NewBuffer(nil)
err := cmd.Run(buff, args...)
if err != nil {
return "", errors.Wrap(err)
}
result := buff.String()
if result == "" {
return result, errors.Errorf("Could not setup loop device")
}
return strings.Replace(result, "\n", "", -1), nil
}
// DetachLoopDevice detaches a loop device
func DetachLoopDevice(file string) {
args := []string{
"losetup",
"-d",
file,
}
_ = cmd.RunAndLog(args...)
}
// GenerateTabFiles creates the /etc mounting files if needed
func GenerateTabFiles(rootDir string, medias []*BlockDevice) error {
var crypttab []string
var fstab []string
var errFound bool
for _, curr := range medias {
for _, ch := range curr.Children {
// Handle Encrypted partitions
var ctab []string
var ftab []string
if ch.Type == BlockDeviceTypeCrypt {
if ch.FsType == "swap" {
ctab = append(ctab, filepath.Base(ch.MappedName), ch.GetDeviceID(),
"/dev/urandom",
fmt.Sprintf("swap,offset=2048,cipher=%s,size=%d",
EncryptCipher, EncryptKeySize))
ftab = append(ftab, ch.GetMappedDeviceFile(), "none",
"swap", "defaults", "0", "0")
} else {
if !ch.isStandardMount() {
ctab = append(ctab, filepath.Base(ch.MappedName), ch.GetDeviceID())
ftab = append(ftab, ch.GetMappedDeviceFile(), ch.MountPoint,
ch.FsType, "defaults", "0", "2")
}
}
} else {
if !ch.isStandardMount() && ch.MountPoint != "" {
ftab = append(ftab, ch.GetDeviceID(), ch.MountPoint,
ch.FsType, "defaults", "0", "2")
}
}
if len(ctab) > 0 {
crypttab = append(crypttab, strings.Join(ctab, " "))
}
if len(ftab) > 0 {
fstab = append(fstab, strings.Join(ftab, " "))
}
}
}
if len(crypttab) > 0 {
etcDir := filepath.Join(rootDir, "etc")
crypttabFile := filepath.Join(rootDir, "etc", "crypttab")
lines := strings.Join(crypttab, "\n") + "\n"
if err := utils.MkdirAll(etcDir, 0755); err != nil {
log.Error("Failed to create %s dir: %v", etcDir, err)
errFound = true
}
if err := ioutil.WriteFile(crypttabFile, []byte(lines), 0644); err != nil {
log.Error("Failed to write crypttab: %v", err)
errFound = true
}
}
if len(fstab) > 0 {
etcDir := filepath.Join(rootDir, "etc")
fstabFile := filepath.Join(rootDir, "etc", "fstab")
lines := strings.Join(fstab, "\n") + "\n"
if err := utils.MkdirAll(etcDir, 0755); err != nil {
log.Error("Failed to create %s dir: %v", etcDir, err)
errFound = true
}
if err := ioutil.WriteFile(fstabFile, []byte(lines), 0644); err != nil {
log.Error("Failed to write fstab: %v", err)
errFound = true
}
}
if errFound {
return errors.Errorf("Error while creating mount files")
}
return nil
}
// InstallTarget describes a BlockDevice which is a valid installation target
type InstallTarget struct {
Name string // block device name
Friendly string // user friendly device name
WholeDisk bool // Can we use the whole disk?
Removable bool // Is this removable/hotswap media?
EraseDisk bool // Are we wiping the disk? New partition table
DataLoss bool // Are we making changes which will lose data
Advanced bool // Was this disk configured via advanced mode?
FreeStart uint64 // Starting position of free space
FreeEnd uint64 // Ending position of free space
}
const (
// MinimumServerInstallSize is the smallest installation size in bytes for a Server
MinimumServerInstallSize = uint64(4) * (1000 * 1000 * 1000) // 4GiB
// MinimumDesktopInstallSize is the smallest installation size in bytes for a Desktop
MinimumDesktopInstallSize = uint64(20) * (1000 * 1000 * 1000) // 20GiB
)
func sortInstallTargets(targets []InstallTarget) []InstallTarget {
sort.SliceStable(targets, func(i, j int) bool {
// Ordering is:
// -- Non-removable disks
// -- Whole Disk
// -- Disk with with largest free space
if !targets[i].Removable && targets[j].Removable {
return true
}
if targets[i].Removable && !targets[j].Removable {
return false
}
if targets[i].WholeDisk && !targets[j].WholeDisk {
return true
}
if !targets[i].WholeDisk && targets[j].WholeDisk {
return false
}
iSize := targets[i].FreeEnd - targets[i].FreeStart
jSize := targets[j].FreeEnd - targets[j].FreeStart
return jSize <= iSize
})
return targets
}
// FindSafeInstallTargets creates an order list of possible installation targets
// Only disk with gpt partition are safe to use
// There must be at least 3 free partition in the table (gpt can have 127)
// There must be at least minSize free space on the disk
func FindSafeInstallTargets(rootSize uint64, medias []*BlockDevice) []InstallTarget {
var installTargets []InstallTarget
// Add the default boot and swap to the passed root size
minSize := rootSize + bootSize + swapSize
for _, curr := range medias {
// Either 'gpt' or no partition table type
if curr.PtType != "gpt" && curr.PtType != "" {
log.Debug("FindSafeInstallTargets(): ignoring disk %s with partition table type %s",
curr.Name, curr.PtType)
continue
}
if curr.Children != nil && len(curr.Children) > 125 {
log.Debug("FindSafeInstallTargets(): ignoring disk %s with too many partitions (%d)",
curr.Name, len(curr.Children))
continue
}
if (curr.Children == nil || len(curr.Children) == 0) && curr.Size >= minSize {
// No partition type and no children we write the whole disk
installTargets = append(installTargets,
InstallTarget{Name: curr.Name, Friendly: curr.Model,
WholeDisk: true, Removable: curr.RemovableDevice,
FreeStart: 0, FreeEnd: curr.Size})
log.Debug("FindSafeInstallTargets(): found whole disk %s", curr.Name)
continue
}
if start, end := curr.LargestContiguousFreeSpace(minSize); start != 0 && end != 0 {
installTargets = append(installTargets,
InstallTarget{Name: curr.Name, Friendly: curr.Model,
Removable: curr.RemovableDevice, FreeStart: start, FreeEnd: end})
log.Debug("FindSafeInstallTargets(): Room on disk %s: %d to %d", curr.Name, start, end)
continue
}
}
return sortInstallTargets(installTargets)
}
// FindAllInstallTargets creates an order list of all possible installation targets
// There must be at least minSize free space on the disk
func FindAllInstallTargets(rootSize uint64, medias []*BlockDevice) []InstallTarget {
var installTargets []InstallTarget
// Add the default boot and swap to the passed root size
minSize := rootSize + bootSize + swapSize
// All Disk are possible destructive installs
for _, curr := range medias {
if curr.Size >= minSize {
target := InstallTarget{Name: curr.Name, Friendly: curr.Model,
WholeDisk: true, Removable: curr.RemovableDevice, EraseDisk: true,
FreeStart: 0, FreeEnd: curr.Size}
installTargets = append(installTargets, target)
} else {
currSizeStr, _ := HumanReadableSizeWithPrecision(curr.Size, 1)
minSizeStr, _ := HumanReadableSizeWithPrecision(minSize, 1)
log.Debug("FindAllInstallTargets: Media %s %s smaller than minSize %s", curr.Name,
currSizeStr, minSizeStr)
}
}
return sortInstallTargets(installTargets)
}
// FindModifyInstallTargets creates an order list of possible installation targets
// Only Disk with a 'gpt' partition table are candidates for modification
func FindModifyInstallTargets(medias []*BlockDevice) []InstallTarget {
var installTargets []InstallTarget
for _, curr := range medias {
if curr.PtType != "gpt" {
log.Debug("FindModifyInstallTargets(): ignoring disk %s with partition table type %s",
curr.Name, curr.PtType)
continue
}
target := InstallTarget{Name: curr.Name, Friendly: curr.Model,
WholeDisk: true, Removable: curr.RemovableDevice, EraseDisk: true,
FreeStart: 0, FreeEnd: curr.Size}
installTargets = append(installTargets, target)
}
return sortInstallTargets(installTargets)
}
// FindAdvancedInstallTargets creates a list of advanced installation targets
// We use Partition Labels to tag and convey which partitions should be used
// for an advanced installations.
// CLR_BOOT = The /boot partition; must be vfat
// CLR_SWAP = A swap partition to use; can be more than one
// CLR_ROOT = The / root partition; must be ext[234] or xfs
// due to clr-boot-manager
// CLR_MNT = Any additional partitions that should be
// included in the install like /srv, /home, ...
//
// Appending "_E" to the label marks it for encryption; not valid for CLR_BOOT
// Appending "_F" to the label marks it for formatting (newfs)
func FindAdvancedInstallTargets(medias []*BlockDevice) []*BlockDevice {
var targetMedias []*BlockDevice
defaultFsType := "ext4"
defaultBootFsType := "vfat"
for _, curr := range medias {
var installBlockDevice *BlockDevice
clrAdded := false
installBlockDevice = curr.Clone()
for _, ch := range installBlockDevice.Children {
clrFound := false
label := ch.PartitionLabel
if label != "" {
log.Debug("FindAdvancedInstallTargets: Found partition %s with name %s", ch.Name, label)
}
for _, part := range strings.Split(label, "_") {
lowerPart := strings.ToLower(part)
if !clrFound {
if lowerPart == "clr" {
log.Debug("FindAdvancedInstallTargets: Partition name contains clr %s", ch.Name)
clrFound = true
}
continue
}
switch lowerPart {
case "boot":
if ch.Type == BlockDeviceTypeCrypt {
log.Warning("AdvancedPartitioning: /boot can no be encrypted, skipping")
ch.Type = BlockDeviceTypePart
}
log.Debug("AdvancedPartitioning: Boot is %s", ch.Name)
ch.MountPoint = "/boot"
if ch.FsType == "" {
log.Debug("AdvancedPartitioning: No FsType set for %s, defaulting to %s", ch.Name, defaultBootFsType)
ch.FsType = defaultBootFsType
log.Debug("AdvancedPartitioning: Forcing Format partition %s enabled", ch.Name)
ch.FormatPartition = true
}
clrAdded = true
case "root":
log.Debug("AdvancedPartitioning: Root is %s", ch.Name)
ch.MountPoint = "/"
if ch.FsType == "" {
log.Debug("AdvancedPartitioning: No FsType set for %s, defaulting to %s", ch.Name, defaultFsType)
ch.FsType = defaultFsType
log.Debug("AdvancedPartitioning: Forcing Format partition %s enabled", ch.Name)
ch.FormatPartition = true
}
clrAdded = true
case "swap":
log.Debug("AdvancedPartitioning: Swap on %s", ch.Name)
clrAdded = true
if ch.FsType == "" {
log.Debug("AdvancedPartitioning: No FsType set for %s, defaulting to %s", ch.Name, "swap")
ch.FsType = "swap"
log.Debug("AdvancedPartitioning: Forcing Format partition %s enabled", ch.Name)
ch.FormatPartition = true
}
case "mnt":
mntParts := strings.Split(label, "MNT_")
if len(mntParts) == 2 {
path := filepath.Clean(mntParts[1])
if filepath.IsAbs(path) {
log.Debug("AdvancedPartitioning: Extra mount %q for %s", path, ch.Name)
ch.MountPoint = path
if ch.FsType == "" {
log.Debug("AdvancedPartitioning: No FsType set for %s, defaulting to %s", ch.Name, defaultFsType)
ch.FsType = defaultFsType
log.Debug("AdvancedPartitioning: Forcing Format partition %s enabled", ch.Name)
ch.FormatPartition = true
}
clrAdded = true
}
}
break
/*
case "e":
if ch.MountPoint == "/boot" {
log.Warning("AdvancedPartitioning: /boot can no be encrypted, skipping")
} else {
ch.Type = BlockDeviceTypeCrypt
log.Debug("AdvancedPartitioning: Encrypt partition %s", ch.Name)
}
*/
case "f":
ch.FormatPartition = true
log.Debug("AdvancedPartitioning: Format partition %s enabled", ch.Name)
}
}
}
if clrAdded {
targetMedias = append(targetMedias, installBlockDevice)
}
}
for _, curr := range targetMedias {
for _, ch := range curr.Children {
log.Debug("FindAdvancedInstallTargets: child: %+v", ch)
}
}
return targetMedias
}
// FormatInstallPortion is the common code for describing
// the amount of disk used
func FormatInstallPortion(target InstallTarget) string {
portion := utils.Locale.Get("Partial")
if target.WholeDisk || target.EraseDisk {
portion = utils.Locale.Get("Entire Disk")
}
if target.Advanced {
if target.EraseDisk {
portion = ""
} else {
portion = utils.Locale.Get("Advanced")
}
}
if portion != "" {
portion = "[" + portion + "]"
}
return portion
}
// ByBDName implements sort.Interface for []*BlockDevice based on the Name field.
type ByBDName []*BlockDevice
func (a ByBDName) Len() int { return len(a) }
func (a ByBDName) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func (a ByBDName) Less(i, j int) bool {
iPartNum := devNameSuffixExp.FindString(a[i].Name)
jPartNum := devNameSuffixExp.FindString(a[j].Name)
// When both partitions end with a number and the partition names
// without partion numbers match, use the partion numbers to
// compare the partitions
if iPartNum != "" && jPartNum != "" {
iPartName := devNameSuffixExp.Split(a[i].Name, 2)[0]
jPartName := devNameSuffixExp.Split(a[j].Name, 2)[0]
if iPartName == jPartName {
iNum, _ := strconv.Atoi(iPartNum)
jNum, _ := strconv.Atoi(jPartNum)
return iNum < jNum
}
}
return a[i].Name < a[j].Name
}
// ServerValidateAdvancedPartitions returns an array of validation error
// strings for the advanced partitions based on a Server installation.
func ServerValidateAdvancedPartitions(medias []*BlockDevice) []string {
return validateAdvancedPartitions(MinimumServerInstallSize, medias)
}
// DesktopValidateAdvancedPartitions returns an array of validation error
// strings for the advanced partitions based on a Desktop installation.
func DesktopValidateAdvancedPartitions(medias []*BlockDevice) []string {
return validateAdvancedPartitions(MinimumDesktopInstallSize, medias)
}
// validateAdvancedPartitions returns an array of validation error
// strings for the advanced partitions
func validateAdvancedPartitions(rootSize uint64, medias []*BlockDevice) []string {
results := []string{}
bootFound := false
bootSize := uint64(100) * (1000 * 1000) // 100MB recommend for 4-5 kernels
swapFound := false
swapSize := uint64(32) * (1000 * 1000) // 32MB recommend smallest for memory crunch times
rootFound := false
for _, curr := range medias {
for _, ch := range curr.Children {
clrFound := false
label := ch.PartitionLabel
if label != "" {
log.Debug("validateAdvancedPartitions: Found partition %s with name %s", ch.Name, label)
}
for _, part := range strings.Split(label, "_") {
lowerPart := strings.ToLower(part)
if !clrFound {
if lowerPart == "clr" {
clrFound = true
}
continue
}
switch lowerPart {
case "boot":
if bootFound {
warning := utils.Locale.Get("Found multiple %s partition names", "CLR_BOOT")
results = append(results, warning)
log.Warning("validateAdvancedPartitions: %s %+v", warning, ch)
} else {
bootFound = true
if ch.FsType != "vfat" {
warning := utils.Locale.Get("%s must be %s", "CLR_BOOT", "vfat")
results = append(results, warning)
log.Warning("validateAdvancedPartitions: %s %+v", warning, ch)
}
}
if ch.Size < bootSize {
size, _ := HumanReadableSizeWithPrecision(bootSize, 1)
warning := utils.Locale.Get("%s must be %s", "CLR_BOOT",
fmt.Sprintf(">= %s", size))
results = append(results, warning)
log.Warning("validateAdvancedPartitions: %s %+v", warning, ch)
}
case "root":
if rootFound {
warning := utils.Locale.Get("Found multiple %s partition names", "CLR_ROOT")
results = append(results, warning)
log.Warning("validateAdvancedPartitions: %s %+v", warning, ch)
} else {
rootFound = true
if !(ch.FsType == "ext2" || ch.FsType == "ext3" ||
ch.FsType == "ext4" || ch.FsType == "xfs") {
warning := utils.Locale.Get("%s must be %s", "CLR_ROOT", "ext*|xfs")
results = append(results, warning)
log.Warning("validateAdvancedPartitions: %s %+v", warning, ch)
}
}
if ch.Size < rootSize {
size, _ := HumanReadableSizeWithPrecision(rootSize, 1)
warning := utils.Locale.Get("%s must be %s", "CLR_ROOT",
fmt.Sprintf(">= %s", size))
results = append(results, warning)
log.Warning("validateAdvancedPartitions: %s %+v", warning, ch)
}
case "swap":
if swapFound {
warning := utils.Locale.Get("Found multiple %s partition names", "CLR_SWAP")
results = append(results, warning)
log.Warning("validateAdvancedPartitions: %s %+v", warning, ch)
} else {
swapFound = true
if ch.FsType != "swap" {
warning := utils.Locale.Get("%s must be %s", "CLR_SWAP", "linux-swap")
results = append(results, warning)
log.Warning("validateAdvancedPartitions: %s %+v", warning, ch)
}
}
if ch.Size < swapSize {
size, _ := HumanReadableSizeWithPrecision(swapSize, 1)
warning := utils.Locale.Get("%s must be %s", "CLR_SWAP",
fmt.Sprintf(">= %s", size))
results = append(results, warning)
log.Warning("validateAdvancedPartitions: %s %+v", warning, ch)
}
case "mnt":
failed := false
warning := utils.Locale.Get("Found invalid %s partition name", label)
mntParts := strings.Split(label, "MNT_")
if len(mntParts) != 2 {
failed = true
log.Warning("validateAdvancedPartitions: %s %+v (%s)", warning, ch, "too many parts")
}
if !strings.HasPrefix(mntParts[1], "/") {
failed = true
log.Warning("validateAdvancedPartitions: %s %+v (%s)", warning, ch, "must start with '/'")
}
path := filepath.Clean(mntParts[1])
if !filepath.IsAbs(path) {
failed = true
log.Warning("validateAdvancedPartitions: %s %+v (%s)", warning, ch, "must be an absolute path")
}
if failed {
results = append(results, warning)
}
break
/*
case "e":
if strings.HasPrefix(strings.ToLower(ch.PartitionLabel), "clr_boot") {
warning := utils.Locale.Get("Encryption of %s is not supported", "CLR_BOOT")
results = append(results, warning)
log.Warning("validateAdvancedPartitions: %s %+v", warning, ch)
}
*/
}
}
}
}
if !bootFound {
warning := utils.Locale.Get("Missing %s partition name", "CLR_BOOT")
results = append(results, warning)
log.Warning("validateAdvancedPartitions: %s", warning)
}
if !swapFound {
warning := utils.Locale.Get("Missing %s partition name", "CLR_SWAP")
results = append(results, warning)
log.Warning("validateAdvancedPartitions: %s", warning)
}
if !rootFound {
warning := utils.Locale.Get("Missing %s partition name", "CLR_ROOT")
results = append(results, warning)
log.Warning("validateAdvancedPartitions: %s", warning)
}
return results
}
// AdvancedPartitionsRequireEncryption returns an array of validation error
// strings for the advanced partitions
func AdvancedPartitionsRequireEncryption(medias []*BlockDevice) bool {
encryptionFound := false
for _, curr := range medias {
for _, ch := range curr.Children {
clrFound := false
label := ch.PartitionLabel
for _, part := range strings.Split(label, "_") {
lowerPart := strings.ToLower(part)
if !clrFound {
if lowerPart == "clr" {
clrFound = true
}
continue
}
switch lowerPart {
case "boot":
break
/*
case "e":
encryptionFound = true
*/
}
}
}
}
if encryptionFound {
log.Debug("AdvancedPartitionsRequireEncryption Found at least one partition which requires encryption")
}
return encryptionFound
}
// GetAdvancedPartitions returns an array of strings for the
// assigned advanced partitions used
func GetAdvancedPartitions(medias []*BlockDevice) []string {
results := []string{}
for _, curr := range medias {
for _, ch := range curr.Children {
if strings.HasPrefix(ch.PartitionLabel, "CLR_") {
var name string
if ch.MountPoint != "" {
name = ch.Name + ":" + ch.MountPoint
} else {
name = ch.Name + ":" + ch.FsType
}
if ch.Type == BlockDeviceTypeCrypt {
name = name + "*"
}
results = append(results, name)
}
}
}
return results
}
func getPlannedPartitionChanges(media *BlockDevice) []string {
results := []string{}
for _, ch := range media.Children {
if ch.FormatPartition {
partName := ch.Name
if partName == "" {
partName = ch.GetNewPartitionName(ch.partition)
}
part := fmt.Sprintf("%s: %s", partName,
utils.Locale.Get(FormatPartitionInfo, ch.FsType))
if ch.MountPoint != "" {
part = part + fmt.Sprintf(" [%s]", ch.MountPoint)
}
if ch.Type == BlockDeviceTypeCrypt {
part = part + " " + utils.Locale.Get("Encrypted")
}
results = append(results, part)
} else if ch.MountPoint != "" || !ch.FsTypeNotSwap() {
partName := ch.Name
if partName == "" {
partName = ch.GetNewPartitionName(ch.partition)
}
part := fmt.Sprintf("%s: %s", partName, utils.Locale.Get(UsePartitionInfo))
if ch.MountPoint != "" {
part = part + fmt.Sprintf(" [%s]", ch.MountPoint)
} else if ch.FsType != "" {
part = part + fmt.Sprintf(" (%s)", ch.FsType)
}
if ch.Type == BlockDeviceTypeCrypt {
part = part + " " + utils.Locale.Get("Encrypted")
}
results = append(results, part)
}
}
return results
}
// GetPlannedMediaChanges returns an array of strings with all of
// disk and partition planned changes to advise the user before start
func GetPlannedMediaChanges(targets map[string]InstallTarget, medias []*BlockDevice) []string {
results := []string{}
if len(targets) != len(medias) {
log.Warning("The number of install targets (%d) != media devices (%d)",
len(targets), len(medias))
for _, target := range targets {
log.Warning("Install Target: %+v", target)
}
for _, curr := range medias {
log.Warning("Media Device: %+v", curr)
}
}
for _, target := range targets {
if target.EraseDisk {
results = append(results, target.Name+": "+utils.Locale.Get(DestructiveWarning))
} else if target.DataLoss {
results = append(results, target.Name+": "+utils.Locale.Get(DataLossWarning))
} else if target.WholeDisk {
results = append(results, target.Name+": "+utils.Locale.Get(SafeWholeWarning))
} else {
results = append(results, target.Name+": "+utils.Locale.Get(MediaToBeUsed))
}
for _, curr := range medias {
if target.Name == curr.Name {
if err := curr.WritePartitionTable(false, target.WholeDisk, &results); err != nil {
results = append(results, FailedPartitionWarning)
}
if partChanges := getPlannedPartitionChanges(curr); len(partChanges) > 0 {
results = append(results, partChanges...)
}
}
}
}
return results
}