linuxkit/pkg/format/format.go
Federico Pellegatta 5fc196c289 Add partition table type selector (defaulted to DOS/MBR) to format package
Signed-off-by: Federico Pellegatta <12744504+federico-pellegatta@users.noreply.github.com>
2020-04-23 10:16:36 +02:00

318 lines
7.9 KiB
Go

package main
import (
"flag"
"fmt"
"io/ioutil"
"log"
"os"
"os/exec"
"path/filepath"
"regexp"
"strings"
"syscall"
"time"
)
const (
timeout = 60
ext4opts = "resize_inode,has_journal,extent,huge_file,flex_bg,uninit_bg,64bit,dir_nlink,extra_isize"
)
var (
labelVar string
fsTypeVar string
partTypeVar string
forceVar bool
verboseVar bool
drives map[string]bool
driveKeys []string
)
func hasPartitions(d string) bool {
err := exec.Command("sfdisk", "-d", d).Run()
return err == nil
}
func isEmptyDevice(d string) (bool, error) {
// default result
isEmpty := false
if verboseVar {
log.Printf("Checking if %s is empty", d)
}
out, err := exec.Command("blkid", d).Output()
if err == nil {
log.Printf("%s has content. blkid returned: %s", d, out)
// there is content, so exit early
return false, nil
}
if exiterr, ok := err.(*exec.ExitError); ok {
if status, ok := exiterr.Sys().(syscall.WaitStatus); ok {
// blkid exitcode 2 (from the non-busybox version) signifies the block device has no detectable content signatures
if status.ExitStatus() == 2 {
if verboseVar {
log.Printf("blkid did not find any existing content on %s.", d)
}
// no content detected, but continue through all checks
isEmpty = true
} else {
return isEmpty, fmt.Errorf("Could not determine if %s was empty. blkid %s returned: %s (exitcode %d)", d, d, out, status.ExitStatus())
}
}
}
if hasPartitions(d) {
log.Printf("Partition table found on device %s. Skipping.", d)
return false, nil
}
// return final result
return isEmpty, nil
}
func autoformat(label, fsType string, partType string) error {
var first string
for _, d := range driveKeys {
if verboseVar {
log.Printf("Considering auto format for device %s", d)
}
// break the loop with the first empty device we find
isEmpty, err := isEmptyDevice(d)
if err != nil {
return err
}
if isEmpty == true {
first = d
break
}
}
if first == "" {
return fmt.Errorf("No eligible disks found")
}
return format(first, label, fsType, partType, false)
}
func refreshDevicesAndWaitFor(awaitedDevice string) error {
exec.Command("mdev", "-s").Run()
// wait for device
var (
done bool
err error
stat os.FileInfo
)
for i := 0; i < timeout; i++ {
stat, err = os.Stat(awaitedDevice)
if err == nil && isBlockDevice(&stat) {
done = true
break
}
time.Sleep(100 * time.Millisecond)
exec.Command("mdev", "-s").Run()
}
if !done {
var statMsg string
if err != nil {
statMsg = fmt.Sprintf(" - stat returned: %v", err)
}
return fmt.Errorf("Failed to find block device %s%s", awaitedDevice, statMsg)
}
// even after the device appears we still have a race
time.Sleep(1 * time.Second)
return nil
}
func format(d, label, fsType string, partType string, forced bool) error {
if forced {
// clear partitions on device if forced format and they exist
if hasPartitions(d) {
if verboseVar {
log.Printf("Clearing partitions on %s because forced format was requested", d)
}
partCmd := exec.Command("sfdisk", "--quiet", "--delete", d)
partCmd.Stdin = strings.NewReader(";")
if out, err := partCmd.CombinedOutput(); err != nil {
return fmt.Errorf("Error deleting partitions with sfdisk: %v\n%s", err, out)
}
} else {
if verboseVar {
log.Printf("No need to clear partitions.")
}
}
}
log.Printf("Creating partition on %s", d)
/* new disks do not have an DOS signature in sector 0
this makes sfdisk complain. We can workaround this by letting
fdisk create that DOS signature, by just do a "w", a write.
http://bugs.alpinelinux.org/issues/145
*/
fdiskCmd := exec.Command("fdisk", d)
switch partType {
case "dos":
fdiskCmd.Stdin = strings.NewReader("w")
case "gpt":
fdiskCmd.Stdin = strings.NewReader("g\nw")
default:
return fmt.Errorf("Unsupported partition table type: %s", partType)
}
if out, err := fdiskCmd.CombinedOutput(); err != nil {
return fmt.Errorf("Error running fdisk: %v\n%s", err, out)
}
// format one large partition
partCmd := exec.Command("sfdisk", "--quiet", d)
partCmd.Stdin = strings.NewReader(";")
if out, err := partCmd.CombinedOutput(); err != nil {
return fmt.Errorf("Error running sfdisk: %v\n%s", err, out)
}
// update status
if err := exec.Command("blockdev", "--rereadpt", d).Run(); err != nil {
return fmt.Errorf("Error running blockdev: %v", err)
}
var partition string
// check if last char is numeric in case of nvme
c := d[len(d)-1]
if c > '0' && c < '9' {
partition = fmt.Sprintf("%sp1", d)
} else {
partition = fmt.Sprintf("%s1", d)
}
if err := refreshDevicesAndWaitFor(partition); err != nil {
return err
}
// mkfs
mkfsArgs := []string{"-t", fsType}
switch fsType {
case "ext4":
ext4Args := []string{"-F", "-O", ext4opts}
if label != "" {
ext4Args = append(ext4Args, []string{"-L", label}...)
}
mkfsArgs = append(mkfsArgs, ext4Args...)
case "btrfs":
btrfsArgs := []string{"-f"}
if label != "" {
btrfsArgs = append(btrfsArgs, []string{"-L", label}...)
}
mkfsArgs = append(mkfsArgs, btrfsArgs...)
case "xfs":
xfsArgs := []string{"-f"}
if label != "" {
xfsArgs = append(xfsArgs, []string{"-L", label}...)
}
mkfsArgs = append(mkfsArgs, xfsArgs...)
default:
log.Println("WARNING: Unsupported filesystem.")
}
mkfsArgs = append(mkfsArgs, partition)
if out, err := exec.Command("mkfs", mkfsArgs...).CombinedOutput(); err != nil {
return fmt.Errorf("Error running mkfs: %v\n%s", err, string(out))
}
log.Printf("Partition %s successfully created!", partition)
return nil
}
func isBlockDevice(d *os.FileInfo) bool {
// this probably shouldn't be so hard
// but d.Mode()&os.ModeDevice == 0 doesn't work as expected
mode := (*d).Sys().(*syscall.Stat_t).Mode
return (mode & syscall.S_IFMT) == syscall.S_IFBLK
}
// return a list of all available drives
func findDrives() {
drives = make(map[string]bool)
driveKeys = []string{}
ignoreExp := regexp.MustCompile(`^loop.*$|^nbd.*$|^[a-z]+[0-9]+$`)
devs, _ := ioutil.ReadDir("/dev")
for _, d := range devs {
if isBlockDevice(&d) {
if verboseVar {
log.Printf("/dev/%s is a block device", d.Name())
}
} else {
if verboseVar {
log.Printf("/dev/%s is not a block device", d.Name())
}
continue
}
// ignore if it matches regexp
if ignoreExp.MatchString(d.Name()) {
if verboseVar {
log.Printf("ignored device /dev/%s during drive autodetection", d.Name())
}
continue
}
driveKeys = append(driveKeys, filepath.Join("/dev", d.Name()))
}
}
func init() {
flag.BoolVar(&forceVar, "force", false, "Force format of specified single device (default false)")
flag.StringVar(&labelVar, "label", "", "Disk label to apply")
flag.StringVar(&fsTypeVar, "type", "ext4", "Type of filesystem to create")
flag.StringVar(&partTypeVar, "partition", "dos", "Type of partition table to create")
flag.BoolVar(&verboseVar, "verbose", false, "Enable verbose output (default false)")
}
func verifyBlockDevice(device string) error {
d, err := os.Stat(device)
if os.IsNotExist(err) {
return fmt.Errorf("%s does not exist", device)
}
if !isBlockDevice(&d) {
return fmt.Errorf("%s is not a block device", device)
}
// passed checks
return nil
}
func main() {
flag.Parse()
if flag.NArg() > 1 {
log.Fatalf("Too many arguments provided")
}
if flag.NArg() == 0 {
// auto-detect drives if a device to format is not explicitly specified
findDrives()
if err := autoformat(labelVar, fsTypeVar, partTypeVar); err != nil {
log.Fatalf("%v", err)
}
} else {
candidateDevice := flag.Args()[0]
if err := verifyBlockDevice(candidateDevice); err != nil {
log.Fatalf("%v", err)
}
if forceVar == true {
if err := format(candidateDevice, labelVar, fsTypeVar, partTypeVar, forceVar); err != nil {
log.Fatalf("%v", err)
}
} else {
// add the deviceVar to the array of devices to consider autoformatting
driveKeys = []string{candidateDevice}
if err := autoformat(labelVar, fsTypeVar, partTypeVar); err != nil {
log.Fatalf("%v", err)
}
}
}
}