diff --git a/controller/controller.go b/controller/controller.go index 683b46a..3a64e1d 100644 --- a/controller/controller.go +++ b/controller/controller.go @@ -211,7 +211,7 @@ func Install(rootDir string, model *model.SystemInstall, options args.Args) erro } // Do not overwrite File System content for pre-existing - if !ch.IsUserDefined() { + if !ch.FormatPartition { msg := fmt.Sprintf("Skipping new file system for %s", ch.Name) log.Debug(msg) continue diff --git a/storage/ops.go b/storage/ops.go index e128cef..3de4bf2 100644 --- a/storage/ops.go +++ b/storage/ops.go @@ -240,11 +240,33 @@ func (bd *BlockDevice) WritePartitionTable(legacyBios bool, wholeDisk bool) erro var start uint64 maxFound := false + // First remove any user removed partitions + log.Debug("WritePartitionTable: remove partitions : %v", bd.removedParts) + if len(bd.removedParts) > 0 { + rmArgs := []string{ + "parted", + "-a", + "optimal", + bd.GetDeviceFile(), + "unit", "MB", + "--script", + "--", + } + for _, curr := range bd.removedParts { + rmArgs = append(rmArgs, fmt.Sprintf("rm %d", curr)) + } + 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() // Make the needed new partitions for _, curr := range bd.Children { + log.Debug("WritePartitionTable: processing child: %v", curr) baseArgs := []string{ "parted", "-a", @@ -255,7 +277,7 @@ func (bd *BlockDevice) WritePartitionTable(legacyBios bool, wholeDisk bool) erro "--", } - if !curr.userDefined { + if !curr.MakePartition { log.Debug("WritePartitionTable: skipping partition %s", curr.Name) continue } @@ -276,9 +298,11 @@ func (bd *BlockDevice) WritePartitionTable(legacyBios bool, wholeDisk bool) erro size := uint64(curr.Size) end := start + size if !wholeDisk { - start = curr.partStart - end = curr.partEnd + 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 { @@ -314,7 +338,7 @@ func (bd *BlockDevice) WritePartitionTable(legacyBios bool, wholeDisk bool) erro // Get the new list of partitions newPartitions := bd.getPartitionList() // The current partition is new one added - curr.SetPartitionNumber(findNewPartition(currentPartitions, newPartitions).number) + curr.SetPartitionNumber(findNewPartition(currentPartitions, newPartitions).Number) log.Debug("WritePartitionTable: Found partition number %d for %s", curr.partition, curr.Name) start = end @@ -338,7 +362,7 @@ func (bd *BlockDevice) WritePartitionTable(legacyBios bool, wholeDisk bool) erro } // Only set GUIDs on newly created partitions - if !curr.userDefined { + if !curr.MakePartition { continue } @@ -383,7 +407,7 @@ func (bd *BlockDevice) WritePartitionTable(legacyBios bool, wholeDisk bool) erro // need to set / as boot for _, curr := range bd.Children { // Only check for / in new partitions - if !curr.userDefined { + if !curr.MakePartition { continue } @@ -421,8 +445,8 @@ func (bd *BlockDevice) WritePartitionTable(legacyBios bool, wholeDisk bool) erro return nil } -func (bd *BlockDevice) getPartitionList() []partedPartition { - var partitionList []partedPartition +func (bd *BlockDevice) getPartitionList() []*PartedPartition { + var partitionList []*PartedPartition var err error partTable := bytes.NewBuffer(nil) @@ -450,30 +474,30 @@ func (bd *BlockDevice) getPartitionList() []partedPartition { return partitionList } - var partition partedPartition - 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) + 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) + 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) + 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) + 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] + partition.FileSystem = fields[4] + partition.Name = fields[5] + partition.Flags = fields[6] partitionList = append(partitionList, partition) } @@ -482,8 +506,8 @@ func (bd *BlockDevice) getPartitionList() []partedPartition { return partitionList } -func findNewPartition(currentPartitions, newPartitions []partedPartition) partedPartition { - var newPartition partedPartition +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 @@ -496,7 +520,7 @@ func findNewPartition(currentPartitions, newPartitions []partedPartition) parted for _, newPart := range newPartitions { found := true for _, curPart := range currentPartitions { - if curPart.number == newPart.number { + if curPart.Number == newPart.Number { found = false continue } @@ -542,29 +566,22 @@ func (bd *BlockDevice) getPartitionTable() *bytes.Buffer { return partTable } -func largestContiguousFreeSpace(partTable *bytes.Buffer, minSize uint64) (uint64, uint64) { - var start, end, size uint64 - size = minSize - 1 +func (bd *BlockDevice) getPartitionStartEnd(partNumber uint64) (uint64, uint64) { + var start, end uint64 + devFile := bd.GetDeviceFile() - for _, line := range strings.Split(partTable.String(), ";\n") { - log.Debug("largestContiguousFreeSpace() line is %q", line) + if !utils.IntSliceContains([]int{BlockDeviceTypeDisk, BlockDeviceTypeLoop}, int(bd.Type)) { + log.Warning("getPartitionStartEnd() called on non-disk %q", devFile) + return start, end + } - fields := strings.Split(line, ":") - if len(fields) == 5 && fields[4] == "free" { - lineSize, err := strconv.ParseUint(strings.TrimRight(fields[3], "B"), 10, 64) - if err == nil { - if lineSize > size { - lineStart, errStart := strconv.ParseUint(strings.TrimRight(fields[1], "B"), 10, 64) - lineEnd, errEnd := strconv.ParseUint(strings.TrimRight(fields[2], "B"), 10, 64) - if errStart == nil && errEnd == nil { - start = lineStart - end = lineEnd - } - } - } + 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 } @@ -572,7 +589,7 @@ func largestContiguousFreeSpace(partTable *bytes.Buffer, minSize uint64) (uint64 // 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 uint64 + var start, end, size uint64 devFile := bd.GetDeviceFile() if !utils.IntSliceContains([]int{BlockDeviceTypeDisk, BlockDeviceTypeLoop}, int(bd.Type)) { @@ -580,14 +597,268 @@ func (bd *BlockDevice) LargestContiguousFreeSpace(minSize uint64) (uint64, uint6 return start, end } - // Read the partition table for the device - partTable := bd.getPartitionTable() + size = minSize - 1 - start, end = largestContiguousFreeSpace(partTable, minSize) + 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 + for i, p := range bd.PartTable { + log.Debug("Dump of PartTable %d: %v", i, p) + } + + // Consolidate neighboring free partitions + bd.consolidateFree() + + for i, p := range bd.PartTable { + log.Debug("Dump of PartTable post consolidate %d: %v", i, p) + } + +} + +func (bd *BlockDevice) consolidateFree() { + last := &PartedPartition{} + var newPartTable []*PartedPartition + + for _, part := range bd.PartTable { + log.Debug("consolidateFree() checking part %v", part) + // Found a free partition + if part.Number == 0 && part.FileSystem == "free" { + log.Debug("consolidateFree() part is free %v", part) + // And the last partition was also free, then consolidate + if last.Number == 0 && last.FileSystem == "free" { + log.Debug("consolidateFree() last is also free %v", last) + 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 { + log.Debug("RemovePartition() called") + 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) @@ -923,22 +1194,14 @@ type InstallTarget struct { 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 + Manual bool // Was this disk manually configured? FreeStart uint64 // Starting position of free space FreeEnd uint64 // Ending position of free space } -type partedPartition struct { - number uint64 // partition number - start uint64 // starting byte location - end uint64 // ending byte location - size uint64 // size in bytes - fileSystem string // file system Type - name string // partition name - flags string // flags for partition -} - const ( - // MinimumServerInstallSize is the smallest installation size in bytes for a Desktop + // MinimumServerInstallSize is the smallest installation size in bytes for a Server MinimumServerInstallSize = 4294967296 // MinimumDesktopInstallSize is the smallest installation size in bytes for a Desktop diff --git a/storage/storage.go b/storage/storage.go index e24706f..1f425a9 100644 --- a/storage/storage.go +++ b/storage/storage.go @@ -22,31 +22,45 @@ import ( "github.com/clearlinux/clr-installer/utils" ) +// PartedPartition hold partition information +// Number 0 and FileSystem "free" are free spaces +type PartedPartition struct { + Number uint64 // partition number 0 indicates free space + Start uint64 // starting byte location + End uint64 // ending byte location + Size uint64 // size in bytes + FileSystem string // file system Type + Name string // partition name + Flags string // flags for partition +} + // A BlockDevice describes a block device and its partitions type BlockDevice struct { - Name string // device name - MappedName string // mapped device name - Model string // device model - MajorMinor string // major:minor device number - PtType string // partition table type - FsType string // filesystem type - UUID string // filesystem uuid - Serial string // device serial number - MountPoint string // where the device is mounted - Label string // label for the partition; set with mkfs - Size uint64 // size of the device - Type BlockDeviceType // device type - State BlockDeviceState // device state (running, live etc) - ReadOnly bool // read-only device - RemovableDevice bool // removable device - Children []*BlockDevice // children devices/partitions - Parent *BlockDevice // Parent block device; nil for disk - userDefined bool // was this value set by user? - available bool // was it mounted the moment we loaded? - partStart uint64 // Start of the partition - partEnd uint64 // End of the partition - partition uint64 // Assigned partition for media - can't set until after mkpart - Options string // arbitrary mkfs.* options + Name string // device name + MappedName string // mapped device name + Model string // device model + MajorMinor string // major:minor device number + PtType string // partition table type + FsType string // filesystem type + UUID string // filesystem uuid + Serial string // device serial number + MountPoint string // where the device is mounted + Label string // label for the partition; set with mkfs + Size uint64 // size of the device + Type BlockDeviceType // device type + State BlockDeviceState // device state (running, live etc) + ReadOnly bool // read-only device + RemovableDevice bool // removable device + Children []*BlockDevice // children devices/partitions + Parent *BlockDevice // Parent block device; nil for disk + UserDefined bool // was this value set by user? + MakePartition bool // Do we need to make a new partition? + FormatPartition bool // Do we need to format the partition + Options string // arbitrary mkfs.* options + available bool // was it mounted the moment we loaded? + partition uint64 // Assigned partition for media - can't set until after mkpart + PartTable []*PartedPartition // Existing Disk partition table from parted + removedParts []uint64 // List of manually removed partitions } // Version used for reading and writing YAML @@ -116,9 +130,9 @@ var ( avBlockDevices []*BlockDevice lsblkBinary = "lsblk" storageExp = regexp.MustCompile(`^([0-9]*(\.)?[0-9]*)([bkmgtp]{1}){0,1}$`) - devNameSuffixExp = regexp.MustCompile(`([0-9]*)$`) labelExp = regexp.MustCompile(`^([[:word:]-+_]+)$`) mountExp = regexp.MustCompile(`^(/|(/[[:word:]-+_]+)+)$`) + devNameSuffixExp = regexp.MustCompile(`([0-9]*)$`) blockDeviceStateMap = map[BlockDeviceState]string{ BlockDeviceStateRunning: "running", BlockDeviceStateLive: "live", @@ -170,14 +184,17 @@ func (bd *BlockDevice) ExpandName(alias map[string]string) { } } -// SetPartitionNumber is sed when we add a new partition to a disk +// GetNewPartitionName returns the name with the new partition number +func (bd *BlockDevice) GetNewPartitionName(partition uint64) string { + // Replace the last set of digits with the current partition number + return devNameSuffixExp.ReplaceAllString(bd.Name, fmt.Sprintf("%d", partition)) +} + +// SetPartitionNumber is set when we add a new partition to a disk // which stores the newly allocated partition number, and then corrects // the devices partition name func (bd *BlockDevice) SetPartitionNumber(partition uint64) { bd.partition = partition - - // Replace the last set of digits with the current partition number - bd.Name = devNameSuffixExp.ReplaceAllString(bd.Name, fmt.Sprintf("%d", partition)) } // GetDeviceFile formats the block device's file path @@ -239,6 +256,36 @@ func parseBlockDeviceState(bds string) (BlockDeviceState, error) { return BlockDeviceStateUnknown, errors.Errorf("Unrecognized block device state: %s", bds) } +func (bd *BlockDevice) findFree(size uint64) *PartedPartition { + var freePart *PartedPartition + + for _, part := range bd.PartTable { + if part.Number == 0 && part.FileSystem == "free" { + if part.Size >= size { + freePart = part.Clone() + break + } + } + } + + return freePart +} + +// Clone creates a copies a PartedPartition +func (part *PartedPartition) Clone() *PartedPartition { + clone := &PartedPartition{ + Number: part.Number, + Start: part.Start, + End: part.End, + Size: part.Size, + FileSystem: part.FileSystem, + Name: part.Name, + Flags: part.Flags, + } + + return clone +} + // Clone creates a copies a BlockDevice and its children func (bd *BlockDevice) Clone() *BlockDevice { clone := &BlockDevice{ @@ -257,11 +304,13 @@ func (bd *BlockDevice) Clone() *BlockDevice { ReadOnly: bd.ReadOnly, RemovableDevice: bd.RemovableDevice, Parent: bd.Parent, - userDefined: bd.userDefined, + UserDefined: bd.UserDefined, + MakePartition: bd.MakePartition, + FormatPartition: bd.FormatPartition, available: bd.available, - partStart: bd.partStart, - partEnd: bd.partEnd, partition: bd.partition, + PartTable: bd.PartTable, + removedParts: bd.removedParts, } clone.Children = []*BlockDevice{} @@ -279,7 +328,7 @@ func (bd *BlockDevice) Clone() *BlockDevice { // IsUserDefined returns true if the configuration was interactively // defined by the user func (bd *BlockDevice) IsUserDefined() bool { - return bd.userDefined + return bd.UserDefined } // IsAvailable returns true if the media is not a installer media, returns false otherwise @@ -404,6 +453,11 @@ func (bd *BlockDevice) RemoveChild(child *BlockDevice) { } } +// addRemovePartition adds a partition to the list to be removed +func (bd *BlockDevice) addRemovePartition(part uint64) { + bd.removedParts = append(bd.removedParts, part) +} + // AddChild adds a partition to a disk block device func (bd *BlockDevice) AddChild(child *BlockDevice) { if bd.Children == nil { @@ -422,8 +476,13 @@ func (bd *BlockDevice) AddChild(child *BlockDevice) { } if child.Name == "" { - child.Name = fmt.Sprintf("%s%s%d", bd.Name, partPrefix, len(bd.Children)) + if child.partition < 1 { + child.Name = fmt.Sprintf("%s%s?", bd.Name, partPrefix) + } else { + child.Name = fmt.Sprintf("%s%s%d", bd.Name, partPrefix, child.partition) + } } + log.Debug("AddChild: child.Name is %q", child.Name) } // HumanReadableSizeWithUnitAndPrecision converts the size representation in bytes to the @@ -555,6 +614,10 @@ func listBlockDevices(userDefined []*BlockDevice) ([]*BlockDevice, error) { if err = bd.PartProbe(); err != nil { return nil, err } + + // Read the partition table for the device + partTable := bd.getPartitionTable() + bd.setPartitionTable(partTable) } if userDefined == nil || len(userDefined) == 0 { @@ -772,21 +835,6 @@ func getNextBoolToken(dec *json.Decoder, name string) (bool, error) { return false, errors.Errorf("Unknown ro value: %s", str) } -// MaxParitionSize returns largest size the partition -// can be in bytes. Return 0 if there is an error; in theory -// the maximum size should at least be the current size. -func (bd *BlockDevice) MaxParitionSize() uint64 { - - if bd.Parent != nil { - free, err := bd.Parent.FreeSpace() - if err == nil { - return (bd.Size + free) - } - } - - return 0 -} - // IsValidLabel returns empty string if label is valid func IsValidLabel(label string, fstype string) string { if label == "" { @@ -818,7 +866,7 @@ func IsValidMount(str string) string { // -- size is suffixed with B, K, M, G, T, P // -- size is greater than MinimumPartitionSize // -- size is less than (or equal to) current size + free space -func (bd *BlockDevice) IsValidSize(str string) string { +func (bd *BlockDevice) IsValidSize(str string, maxPartSize uint64) string { str = strings.ToLower(str) if !storageExp.MatchString(str) { @@ -832,7 +880,6 @@ func (bd *BlockDevice) IsValidSize(str string) string { return "Size too small" } - maxPartSize := bd.MaxParitionSize() if maxPartSize == 0 { return "Unknown free space" } else if size > maxPartSize { @@ -879,6 +926,43 @@ func ParseVolumeSize(str string) (uint64, error) { return size, nil } +// ParseVolumeHumanSize will parse a string formatted (1M, 10G, 2T) size and +// return its representation in human bytes; MB, not MiB +func ParseVolumeHumanSize(str string) (uint64, error) { + var size uint64 + + str = strings.ToLower(str) + + if !storageExp.MatchString(str) { + return strconv.ParseUint(str, 0, 64) + } + + unit := storageExp.ReplaceAllString(str, `$3`) + fsize, err := strconv.ParseFloat(storageExp.ReplaceAllString(str, `$1`), 64) + if err != nil { + return 0, errors.Wrap(err) + } + + switch unit { + case "b": + fsize = fsize * (1.0) + case "k": + fsize = fsize * (1.0 * 1000.0) + case "m": + fsize = fsize * (1.0 * 1000.0 * 1000.0) + case "g": + fsize = fsize * (1.0 * 1000.0 * 1000.0 * 1000.0) + case "t": + fsize = fsize * (1.0 * 1000.0 * 1000.0 * 1000.0 * 1000.0) + case "p": + fsize = fsize * (1.0 * 1000.0 * 1000.0 * 1000.0 * 1000.0 * 1000.0) + } + + size = uint64(math.Round(fsize)) + + return size, nil +} + // UnmarshalJSON decodes a BlockDevice, targeted to integrate with json // decoding framework func (bd *BlockDevice) UnmarshalJSON(b []byte) error { @@ -1095,7 +1179,8 @@ func (bd *BlockDevice) UnmarshalYAML(unmarshal func(interface{}) error) error { } bd.Type = iType if iType != BlockDeviceTypeDisk { - bd.userDefined = true + bd.MakePartition = true + bd.FormatPartition = true } } @@ -1180,53 +1265,50 @@ func MaxLabelLength(fstype string) int { } // AddBootStandardPartition will add to disk a new standard Boot partition -func AddBootStandardPartition(disk *BlockDevice, start uint64) uint64 { - end := start + bootSize - - disk.AddChild(&BlockDevice{ - Size: bootSize, - Type: BlockDeviceTypePart, - FsType: "vfat", - MountPoint: "/boot", - Label: "boot", - userDefined: true, - partStart: start, - partEnd: end, +func AddBootStandardPartition(disk *BlockDevice) uint64 { + freePart := disk.findFree(bootSize) + disk.AddFromFreePartition(freePart, &BlockDevice{ + Size: bootSize, + Type: BlockDeviceTypePart, + FsType: "vfat", + MountPoint: "/boot", + Label: "boot", + UserDefined: true, + MakePartition: true, + FormatPartition: true, }) - return end + return bootSize } // AddSwapStandardPartition will add to disk a new standard Swap partition -func AddSwapStandardPartition(disk *BlockDevice, start uint64) uint64 { - end := start + swapSize - - disk.AddChild(&BlockDevice{ - Size: swapSize, - Type: BlockDeviceTypePart, - FsType: "swap", - Label: "swap", - userDefined: true, - partStart: start, - partEnd: end, +func AddSwapStandardPartition(disk *BlockDevice) uint64 { + freePart := disk.findFree(swapSize) + disk.AddFromFreePartition(freePart, &BlockDevice{ + Size: swapSize, + Type: BlockDeviceTypePart, + FsType: "swap", + Label: "swap", + UserDefined: true, + MakePartition: true, + FormatPartition: true, }) - return end + return swapSize } // AddRootStandardPartition will add to disk a new standard Root partition -func AddRootStandardPartition(disk *BlockDevice, size uint64, start uint64) { - end := start + size - - disk.AddChild(&BlockDevice{ - Size: size, - Type: BlockDeviceTypePart, - FsType: "ext4", - MountPoint: "/", - Label: "root", - userDefined: true, - partStart: start, - partEnd: end, +func AddRootStandardPartition(disk *BlockDevice, rootSize uint64) { + freePart := disk.findFree(rootSize) + disk.AddFromFreePartition(freePart, &BlockDevice{ + Size: rootSize, + Type: BlockDeviceTypePart, + FsType: "ext4", + MountPoint: "/", + Label: "root", + UserDefined: true, + MakePartition: true, + FormatPartition: true, }) } @@ -1234,33 +1316,51 @@ func AddRootStandardPartition(disk *BlockDevice, size uint64, start uint64) { // default set of partitions required for an installation func NewStandardPartitions(disk *BlockDevice) { disk.Children = nil + newFreePart := &PartedPartition{ + Number: 0, + Start: 0, + End: disk.Size, + Size: disk.Size, + FileSystem: "free", + } + disk.PartTable = nil + disk.PartTable = append(disk.PartTable, newFreePart) rootSize := uint64(disk.Size - bootSize - swapSize) - disk.AddChild(&BlockDevice{ - Size: bootSize, - Type: BlockDeviceTypePart, - FsType: "vfat", - MountPoint: "/boot", - Label: "boot", - userDefined: true, + freePart := disk.findFree(bootSize) + disk.AddFromFreePartition(freePart, &BlockDevice{ + Size: bootSize, + Type: BlockDeviceTypePart, + FsType: "vfat", + MountPoint: "/boot", + Label: "boot", + UserDefined: true, + MakePartition: true, + FormatPartition: true, }) - disk.AddChild(&BlockDevice{ - Size: swapSize, - Type: BlockDeviceTypePart, - FsType: "swap", - Label: "swap", - userDefined: true, + freePart = disk.findFree(swapSize) + disk.AddFromFreePartition(freePart, &BlockDevice{ + Size: swapSize, + Type: BlockDeviceTypePart, + FsType: "swap", + Label: "swap", + UserDefined: true, + MakePartition: true, + FormatPartition: true, }) - disk.AddChild(&BlockDevice{ - Size: rootSize, - Type: BlockDeviceTypePart, - FsType: "ext4", - MountPoint: "/", - Label: "root", - userDefined: true, + freePart = disk.findFree(rootSize) + disk.AddFromFreePartition(freePart, &BlockDevice{ + Size: rootSize, + Type: BlockDeviceTypePart, + FsType: "ext4", + MountPoint: "/", + Label: "root", + UserDefined: true, + MakePartition: true, + FormatPartition: true, }) } diff --git a/storage/storage_test.go b/storage/storage_test.go index 0896bdb..0b0c617 100644 --- a/storage/storage_test.go +++ b/storage/storage_test.go @@ -921,37 +921,45 @@ BYT; ` var start, end, twentyGig, fourGig uint64 + children := make([]*BlockDevice, 0) + bd := &BlockDevice{Name: "sda", Children: children} twentyGig = 21474836480 fourGig = 4294967296 t.Logf("getPartAllFreeOutput: twentyGig: %d, fourGig: %d", twentyGig, fourGig) - start, end = largestContiguousFreeSpace(bytes.NewBuffer([]byte(getPartAllFreeOutput)), twentyGig) + + bd.setPartitionTable(bytes.NewBuffer([]byte(getPartAllFreeOutput))) + start, end = bd.LargestContiguousFreeSpace(twentyGig) if start == 0 && end == 0 { t.Fatalf("Should have found %d free in getPartAllFreeOutput", twentyGig) } t.Logf("getPartAllFreeOutput: start: %d, end: %d", start, end) - start, end = largestContiguousFreeSpace(bytes.NewBuffer([]byte(getPartSomeFreeOutput)), twentyGig) + bd.setPartitionTable(bytes.NewBuffer([]byte(getPartSomeFreeOutput))) + start, end = bd.LargestContiguousFreeSpace(twentyGig) if start == 0 && end == 0 { t.Fatalf("Should have found %d free in getPartSomeFreeOutput", twentyGig) } t.Logf("getPartSomeFreeOutput: start: %d, end: %d", start, end) - start, end = largestContiguousFreeSpace(bytes.NewBuffer([]byte(getPartNotEnoughFreeOutput)), fourGig) + bd.setPartitionTable(bytes.NewBuffer([]byte(getPartNotEnoughFreeOutput))) + start, end = bd.LargestContiguousFreeSpace(fourGig) if start != 0 || end != 0 { t.Logf("getPartNotEnoughFreeOutput: start: %d, end: %d", start, end) t.Fatalf("Should NOT have found %d free in getPartNotEnoughFreeOutput", twentyGig) } t.Logf("getPartNotEnoughFreeOutput: start: %d, end: %d", start, end) - start, end = largestContiguousFreeSpace(bytes.NewBuffer([]byte(getPartNotEnoughFree2Output)), twentyGig) + bd.setPartitionTable(bytes.NewBuffer([]byte(getPartNotEnoughFree2Output))) + start, end = bd.LargestContiguousFreeSpace(twentyGig) if start != 0 || end != 0 { t.Logf("getPartNotEnoughFree2Output: start: %d, end: %d", start, end) t.Fatalf("Should NOT have found %d free in getPartNotEnoughFree2Output", twentyGig) } t.Logf("getPartNotEnoughFree2Output: start: %d, end: %d", start, end) - start, end = largestContiguousFreeSpace(bytes.NewBuffer([]byte(getPartNotEnoughFree3Output)), twentyGig) + bd.setPartitionTable(bytes.NewBuffer([]byte(getPartNotEnoughFree3Output))) + start, end = bd.LargestContiguousFreeSpace(twentyGig) if start != 0 || end != 0 { t.Logf("getPartNotEnoughFree3Output: start: %d, end: %d", start, end) t.Fatalf("Should NOT have found %d free in getPartNotEnoughFree3Output", twentyGig) @@ -982,7 +990,7 @@ func TestSwapCheck(t *testing.T) { } bd = &BlockDevice{Size: MinimumServerInstallSize} - _ = AddBootStandardPartition(bd, 0) + _ = AddBootStandardPartition(bd) if bd.DeviceHasSwap() { t.Fatalf("Device should NOT have swap, but does: %v", bd) } @@ -992,15 +1000,15 @@ func TestSwapCheck(t *testing.T) { func TestAddPartititions(t *testing.T) { bd := &BlockDevice{Size: MinimumServerInstallSize} - end := AddBootStandardPartition(bd, 0) - if end != bootSize { - t.Fatalf("Boot partition should end at %d, but was %d", bootSize, end) + size := AddBootStandardPartition(bd) + if size != bootSize { + t.Fatalf("Boot partition should be %d, but was %d", bootSize, size) } - end = AddSwapStandardPartition(bd, end) - if end != (bootSize + swapSize) { - t.Fatalf("Swap partition should end at %d, but was %d", (bootSize + swapSize), end) + size = AddSwapStandardPartition(bd) + if size != swapSize { + t.Fatalf("Swap partition should be %d, but was %d", swapSize, size) } rootSize := uint64(bd.Size - bootSize - swapSize) - AddRootStandardPartition(bd, rootSize, end) + AddRootStandardPartition(bd, rootSize) }