Files
clr-installer/tui/disk_partition.go
T
Mark D Horn c597f7a156 tui: refactor for manual partitioning
Remove the call to the external tool in favor of native ui.

separate mkpart and format flags

Signed-off-by: Mark D Horn <mark.d.horn@intel.com>
2019-04-18 12:48:36 -07:00

470 lines
13 KiB
Go

// Copyright © 2019 Intel Corporation
//
// SPDX-License-Identifier: GPL-3.0-only
package tui
import (
"fmt"
"strings"
"github.com/VladimirMarkelov/clui"
term "github.com/nsf/termbox-go"
"github.com/clearlinux/clr-installer/log"
"github.com/clearlinux/clr-installer/storage"
)
// DiskPartitionPage is the Page implementation for partition configuration page
type DiskPartitionPage struct {
BasePage
fsList *clui.ListBox
fsOriginal string
encryptCheck *clui.CheckBox
formatCheck *clui.CheckBox
labelEdit *clui.EditField
labelWarning *clui.Label
mPointEdit *clui.EditField
mPointWarning *clui.Label
sizeEdit *clui.EditField
confirmBtn *SimpleButton
deleteBtn *SimpleButton
cancelBtn *SimpleButton
sizeWarning *clui.Label
sizeInfo *clui.Label
sizeOriginal string
sizeTrue uint64 // size of the device
}
const (
nPartitionHelp = "Set the new partition's file system, mount point and size."
// partConfirmBtn mask defines a partition configuration page will have a confirm button
partConfirmBtn = 1 << 1
// partDeleteBtn mask defines a partition configuration page will have a delete button
partDeleteBtn = 1 << 2
// partCancelBtn mask defines a partition configuration page will have a cancel button
partCancelBtn = 1 << 3
// partAllBtns mask defines a partition configuration page will have show both:
// delete, add and confirm buttons
partAllBtns = partConfirmBtn | partDeleteBtn | partCancelBtn
)
func (page *DiskPartitionPage) setPartitionButtonsVisible(visible bool, mask int) {
if mask&partConfirmBtn == partConfirmBtn {
page.confirmBtn.SetVisible(visible)
page.setConfirmButton()
}
if mask&partDeleteBtn == partDeleteBtn {
page.deleteBtn.SetVisible(visible)
}
if mask&partCancelBtn == partCancelBtn {
page.cancelBtn.SetVisible(visible)
}
}
func (page *DiskPartitionPage) setPartitionForm(part *storage.BlockDevice) {
idx := page.fsList.FindItem(part.FsType, true)
page.fsList.SelectItem(idx)
if part.Type == storage.BlockDeviceTypeCrypt {
page.encryptCheck.SetState(1)
} else {
page.encryptCheck.SetState(0)
}
page.setFormatCheckbox()
page.labelEdit.SetTitle(part.Label)
page.validateLabel(part.FsType)
page.mPointEdit.SetEnabled(true)
if part.FsType == "swap" {
page.mPointEdit.SetEnabled(false)
} else {
page.mPointEdit.SetTitle(part.MountPoint)
page.validateMountPoint()
}
size, err := part.HumanReadableSize()
if err != nil {
page.Panic(err)
}
page.fsOriginal = part.FsType
page.sizeOriginal = size
page.sizeTrue = part.Size // The actual size, not the human readable
page.sizeEdit.SetTitle(size)
page.setPartitionButtonsVisible(true, partAllBtns)
}
func (page *DiskPartitionPage) setFormatCheckbox() {
sel := page.getSelectedBlockDevice()
// Default to enabled
page.formatCheck.SetEnabled(true)
// If this was a user defined (added) partition, it has to be formatted
if sel.part.UserDefined {
page.formatCheck.SetState(1)
page.formatCheck.SetEnabled(false)
} else {
// We always need to format root (/) for an install
if page.mPointEdit.Title() == "/" {
page.formatCheck.SetState(1)
page.formatCheck.SetEnabled(false)
}
// If the user changed the file system type
if page.fsList.SelectedItemText() != page.fsOriginal {
page.formatCheck.SetState(1)
page.formatCheck.SetEnabled(false)
}
// If the user changed the file system size
if page.sizeEdit.Title() != page.sizeOriginal {
page.formatCheck.SetState(1)
page.formatCheck.SetEnabled(false)
}
}
}
func (page *DiskPartitionPage) getSelectedBlockDevice() *SelectedBlockDevice {
var sel *SelectedBlockDevice
var ok bool
prevPage := page.tui.getPage(TuiPageDiskConfig)
if sel, ok = prevPage.GetData().(*SelectedBlockDevice); !ok {
return nil
}
return sel
}
// Activate is called when the window is "shown", this implementation adjusts
// the currently displayed data
func (page *DiskPartitionPage) Activate() {
sel := page.getSelectedBlockDevice()
if sel == nil {
return
}
page.labelEdit.SetTitle("")
page.labelWarning.SetTitle("")
page.mPointEdit.SetTitle("")
page.mPointWarning.SetTitle("")
page.sizeEdit.SetTitle("")
page.sizeInfo.SetTitle("'+/=' to force Maximum size")
page.sizeWarning.SetTitle("")
page.setPartitionForm(sel.part)
if sel.addMode {
page.setPartitionButtonsVisible(false, partCancelBtn)
// In Add partition mode, the Delete button is really
// our "Cancel" as the new partition was already added.
page.deleteBtn.SetTitle("Cancel")
// and the Confirm button is really our "Add" button
page.confirmBtn.SetTitle("Add")
} else {
page.setPartitionButtonsVisible(true, partCancelBtn)
page.deleteBtn.SetTitle("Delete")
page.confirmBtn.SetTitle("Confirm")
}
}
func (page *DiskPartitionPage) setConfirmButton() {
if page.labelWarning.Title() == "" &&
page.mPointWarning.Title() == "" &&
page.sizeWarning.Title() == "" {
page.confirmBtn.SetEnabled(true)
} else {
page.confirmBtn.SetEnabled(false)
}
}
func (page *DiskPartitionPage) validateLabel(fstype string) {
page.labelWarning.SetTitle(
storage.IsValidLabel(page.labelEdit.Title(), fstype))
page.setConfirmButton()
}
func (page *DiskPartitionPage) validateMountPoint() {
page.mPointWarning.SetTitle(storage.IsValidMount(page.mPointEdit.Title()))
page.setConfirmButton()
}
func newDiskPartitionPage(tui *Tui) (Page, error) {
page := &DiskPartitionPage{}
page.setup(tui, TuiPageDiskPart, NoButtons, TuiPageDiskConfig)
lbl := clui.CreateLabel(page.content, 2, 2, "Partition Setup", Fixed)
lbl.SetPaddings(0, 2)
clui.CreateLabel(page.content, 2, 2, nPartitionHelp, Fixed)
frm := clui.CreateFrame(page.content, AutoSize, AutoSize, BorderNone, Fixed)
frm.SetPack(clui.Horizontal)
lblFrm := clui.CreateFrame(frm, 20, AutoSize, BorderNone, Fixed)
lblFrm.SetPack(clui.Vertical)
lblFrm.SetPaddings(1, 0)
lbl = clui.CreateLabel(lblFrm, AutoSize, 4, "File System:", Fixed)
lbl.SetAlign(AlignRight)
lbl = clui.CreateLabel(lblFrm, AutoSize, 2, "[Optional] Label:", Fixed)
lbl.SetAlign(AlignRight)
lbl = clui.CreateLabel(lblFrm, AutoSize, 2, "Mount Point:", Fixed)
lbl.SetAlign(AlignRight)
lbl = clui.CreateLabel(lblFrm, AutoSize, 2, "Size:", Fixed)
lbl.SetAlign(AlignRight)
fldFrm := clui.CreateFrame(frm, 30, AutoSize, BorderNone, Fixed)
fldFrm.SetPack(clui.Vertical)
partWrapperFrm := clui.CreateFrame(fldFrm, 30, 4, BorderNone, Fixed)
partWrapperFrm.SetPack(clui.Vertical)
partWrapperFrm.SetPaddings(0, 0)
partWrapperFrm.SetGaps(0, 0)
partFrm := clui.CreateFrame(partWrapperFrm, 30, 3, BorderNone, Fixed)
partFrm.SetPack(clui.Vertical)
partFrm.SetPaddings(0, 0)
partFrm.SetGaps(2, 1)
page.fsList = clui.CreateListBox(partFrm, 20, 3, Fixed)
page.fsList.SetAlign(AlignLeft)
page.fsList.SetStyle("List")
page.fsList.OnActive(func(active bool) {
if active {
page.fsList.SetStyle("ListActive")
} else {
page.fsList.SetStyle("List")
}
})
for _, fs := range storage.SupportedFileSystems() {
page.fsList.AddItem(fs)
}
page.fsList.SelectItem(0)
partFrm.SetPack(clui.Horizontal)
page.encryptCheck = clui.CreateCheckBox(partFrm, AutoSize, "Encrypt", AutoSize)
page.encryptCheck.OnChange(func(state int) {
if state != 0 {
if dialog, err := CreateEncryptPassphraseDialogBox(page.getModel()); err == nil {
dialog.OnClose(func() {
if !dialog.Confirmed {
page.encryptCheck.SetState(0)
}
})
}
}
})
page.formatCheck = clui.CreateCheckBox(partFrm, AutoSize, "Format", AutoSize)
labelFrm := clui.CreateFrame(fldFrm, 4, 2, BorderNone, Fixed)
labelFrm.SetPack(clui.Vertical)
labelFrm.SetPaddings(0, 0)
page.labelEdit = clui.CreateEditField(labelFrm, 3, "", Fixed)
page.labelEdit.OnChange(func(ev clui.Event) {
page.validateLabel(page.fsList.SelectedItemText())
})
page.labelWarning = clui.CreateLabel(labelFrm, 1, 1, "", Fixed)
page.labelWarning.SetMultiline(true)
page.labelWarning.SetBackColor(errorLabelBg)
page.labelWarning.SetTextColor(errorLabelFg)
mPointFrm := clui.CreateFrame(fldFrm, 4, 2, BorderNone, Fixed)
mPointFrm.SetPack(clui.Vertical)
mPointFrm.SetPaddings(0, 0)
page.mPointEdit = clui.CreateEditField(mPointFrm, 3, "", Fixed)
page.mPointEdit.OnChange(func(ev clui.Event) {
page.validateMountPoint()
if page.mPointEdit.Title() == "/boot" {
page.encryptCheck.SetState(0)
page.encryptCheck.SetEnabled(false)
} else {
page.encryptCheck.SetEnabled(true)
}
page.setFormatCheckbox()
})
page.mPointWarning = clui.CreateLabel(mPointFrm, 1, 1, "", Fixed)
page.mPointWarning.SetMultiline(true)
page.mPointWarning.SetBackColor(errorLabelBg)
page.mPointWarning.SetTextColor(errorLabelFg)
page.fsList.OnSelectItem(func(evt clui.Event) {
page.mPointEdit.SetEnabled(true)
if page.fsList.SelectedItemText() == "swap" {
page.mPointEdit.SetEnabled(false)
page.mPointEdit.SetTitle("")
page.mPointWarning.SetTitle("")
}
page.setFormatCheckbox()
page.validateLabel(page.fsList.SelectedItemText())
})
sizeFrm := clui.CreateFrame(fldFrm, 5, 3, BorderNone, Fixed)
sizeFrm.SetPack(clui.Vertical)
page.sizeEdit = clui.CreateEditField(sizeFrm, 3, "", Fixed)
page.sizeEdit.OnChange(func(ev clui.Event) {
if page.sizeEdit.Title() != page.sizeOriginal {
sel := page.getSelectedBlockDevice()
page.sizeWarning.SetTitle(sel.part.IsValidSize(page.sizeEdit.Title(), page.sizeTrue))
} else {
page.sizeWarning.SetTitle("")
}
page.setFormatCheckbox()
page.setConfirmButton()
})
page.sizeEdit.OnKeyPress(func(k term.Key, ch rune) bool {
maxSizeKeys := []rune{'=', '+'}
for _, curr := range maxSizeKeys {
if curr == ch {
page.sizeEdit.SetTitle(fmt.Sprintf("%v", page.sizeTrue))
return true
}
}
return false
})
page.sizeInfo = clui.CreateLabel(sizeFrm, 1, 1, "", Fixed)
page.sizeInfo.SetMultiline(false)
page.sizeWarning = clui.CreateLabel(sizeFrm, 1, 1, "", Fixed)
page.sizeWarning.SetMultiline(true)
page.sizeWarning.SetBackColor(errorLabelBg)
page.sizeWarning.SetTextColor(errorLabelFg)
btnFrm := clui.CreateFrame(fldFrm, 30, 1, BorderNone, Fixed)
btnFrm.SetPack(clui.Horizontal)
btnFrm.SetGaps(1, 1)
btnFrm.SetPaddings(2, 0)
page.confirmBtn = CreateSimpleButton(btnFrm, AutoSize, AutoSize, "Confirm", Fixed)
page.confirmBtn.OnClick(func(ev clui.Event) {
var warnings []string
sel := page.getSelectedBlockDevice()
if sel.part != nil {
sel.part.FsType = page.fsList.SelectedItemText()
if page.encryptCheck.State() != 0 {
sel.part.Type = storage.BlockDeviceTypeCrypt
if !sel.addMode {
warnings = append(warnings, "Enabling Encryption")
}
} else {
sel.part.Type = storage.BlockDeviceTypePart
}
if page.formatCheck.State() != 0 {
if !sel.addMode {
warnings = append(warnings, "Enabling Formatting")
}
sel.part.FormatPartition = true
} else {
sel.part.FormatPartition = false
}
sel.part.Label = page.labelEdit.Title()
sel.part.MountPoint = page.mPointEdit.Title()
sizeChanged := false
if page.sizeEdit.Title() == page.sizeOriginal {
// Use the actual size, not the human readable
sel.part.Size = page.sizeTrue
} else {
sizeChanged = true
size, err := storage.ParseVolumeHumanSize(page.sizeEdit.Title())
if err == nil {
sel.part.Size = size
}
}
if sel.addMode {
log.Debug("Updating free %v using size %d", sel.freePartition, sel.part.Size)
sel.bd.AddFromFreePartition(sel.freePartition, sel.part)
} else {
if sel.part.FsType != page.fsOriginal {
warnings = append(warnings, "Changing File System Type")
}
if sizeChanged {
warnings = append(warnings, "Changing Partition Size")
freePart := sel.bd.RemovePartition(sel.part)
sel.part.MakePartition = true
sel.part.FormatPartition = true
sel.bd.AddFromFreePartition(freePart, sel.part)
}
}
// Check for data loss warnings
if len(warnings) > 0 {
sel.dataLoss = true
message := "Data Loss due to: " + strings.Join(warnings, ", ")
if dialog, err := CreateWarningDialogBox(message); err != nil {
log.Warning("%s: %s", message, err)
page.GotoPage(TuiPageDiskConfig)
} else {
dialog.OnClose(func() {
page.GotoPage(TuiPageDiskConfig)
})
}
} else {
page.GotoPage(TuiPageDiskConfig)
}
} else {
page.GotoPage(TuiPageDiskConfig)
}
})
page.deleteBtn = CreateSimpleButton(btnFrm, AutoSize, AutoSize, "Delete", Fixed)
page.deleteBtn.OnClick(func(ev clui.Event) {
sel := page.getSelectedBlockDevice()
log.Debug("Removing partition %v", sel.part)
_ = sel.bd.RemovePartition(sel.part)
sel.dataLoss = true
message := "Deleting a partition results in data loss"
if dialog, err := CreateWarningDialogBox(message); err != nil {
log.Warning("%s: %s", message, err)
} else {
dialog.OnClose(func() {
page.GotoPage(TuiPageDiskConfig)
})
}
})
page.cancelBtn = CreateSimpleButton(btnFrm, AutoSize, AutoSize, "Cancel", Fixed)
page.cancelBtn.OnClick(func(ev clui.Event) {
page.GotoPage(TuiPageDiskConfig)
})
page.activated = page.fsList
return page, nil
}