From 4704219c5473e1b9d76a2b20a7c078827d764960 Mon Sep 17 00:00:00 2001 From: xpetit <32063953+xpetit@users.noreply.github.com> Date: Thu, 17 Jun 2021 12:50:51 +0200 Subject: [PATCH] Add format tool --- sh/debian/ubuntu/configure.sh | 4 + .../ubuntu/system/usr/local/src/format/go.mod | 5 + .../ubuntu/system/usr/local/src/format/go.sum | 4 + .../system/usr/local/src/format/main.go | 239 ++++++++++++++++++ 4 files changed, 252 insertions(+) create mode 100644 sh/debian/ubuntu/system/usr/local/src/format/go.mod create mode 100644 sh/debian/ubuntu/system/usr/local/src/format/go.sum create mode 100644 sh/debian/ubuntu/system/usr/local/src/format/main.go diff --git a/sh/debian/ubuntu/configure.sh b/sh/debian/ubuntu/configure.sh index 16852a16..a940a761 100755 --- a/sh/debian/ubuntu/configure.sh +++ b/sh/debian/ubuntu/configure.sh @@ -434,6 +434,10 @@ cd /tmp/system cp --preserve=mode -RT . / +cd /usr/local/src/format +sudo -iu student go mod download +sudo -iu student go build -o /usr/local/bin/format + cd "$script_dir" rm -rf /tmp/system diff --git a/sh/debian/ubuntu/system/usr/local/src/format/go.mod b/sh/debian/ubuntu/system/usr/local/src/format/go.mod new file mode 100644 index 00000000..dcfbc9d9 --- /dev/null +++ b/sh/debian/ubuntu/system/usr/local/src/format/go.mod @@ -0,0 +1,5 @@ +module main + +go 1.16 + +require github.com/olekukonko/tablewriter v0.0.5 diff --git a/sh/debian/ubuntu/system/usr/local/src/format/go.sum b/sh/debian/ubuntu/system/usr/local/src/format/go.sum new file mode 100644 index 00000000..56c7ba45 --- /dev/null +++ b/sh/debian/ubuntu/system/usr/local/src/format/go.sum @@ -0,0 +1,4 @@ +github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= +github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= +github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= diff --git a/sh/debian/ubuntu/system/usr/local/src/format/main.go b/sh/debian/ubuntu/system/usr/local/src/format/main.go new file mode 100644 index 00000000..0ab1d73e --- /dev/null +++ b/sh/debian/ubuntu/system/usr/local/src/format/main.go @@ -0,0 +1,239 @@ +package main + +import ( + "encoding/json" + "errors" + "fmt" + "math" + "os" + "os/exec" + "strconv" + "strings" + + "github.com/olekukonko/tablewriter" +) + +func fatalln(a ...interface{}) { + fmt.Println(a...) + os.Exit(1) +} + +func expect(target, err error, action string) { + if err != nil && err != target && !errors.Is(err, target) { + fatalln("Failed to", action, ":", err) + } +} + +func renderTable(data [][]string) { + table := tablewriter.NewWriter(os.Stdout) + table.SetColumnSeparator(" ") + table.SetCenterSeparator(" ") + table.SetRowSeparator(" ") + table.SetAlignment(tablewriter.ALIGN_CENTER) + table.SetHeader(data[0]) + for _, row := range data[1:] { + table.Append(row) + } + table.Render() +} + +// run returns the output of the command +// In case of error, display the action intended, the command and the error +func run(action, name string, arg ...string) []byte { + b, err := exec.Command(name, arg...).CombinedOutput() + if err != nil { + fmt.Println("Failed to", action) + fmt.Printf("%s %#v\n", name, arg) + if _, ok := err.(*exec.ExitError); ok { + fatalln(string(b)) + } + fatalln(err) + } + return b +} + +type ( + size int + stringTrim string + + device struct { + Path string // path to the device node + RO bool // read-only device + RM bool // removable device + Hotplug bool // removable or hotplug device (usb, pcmcia, ...) + Model stringTrim // device identifier + Size size // size of the device + Type string // device type + Tran string // device transport type + Vendor stringTrim // device vendor + } +) + +func (s stringTrim) String() string { + return strings.TrimSpace(string(s)) +} + +// format size with SI prefix +func (s size) String() string { + f := float64(s) + if f < 0 { + f = 0 + } + unit := " kMGTPEZY" + for f >= 1000 { + unit = unit[1:] + f /= 1000 + } + var prec int + if unit[0] != ' ' && math.Round(f) < 10 { + prec = 1 + } + return fmt.Sprintf("%.*f %cB", prec, f, unit[0]) +} + +func main() { + // Usage + fmt.Println("While using this formatting tool:") + fmt.Println(" - do not plug/unplug any drive") + fmt.Println(" - make sure the drive is not in use") + fmt.Print("Press ENTER to continue") + fmt.Scanln() + + // Get a list of the block devices + var data struct { + Blockdevices []device + } + b := run("list the block devices", + "lsblk", + "--all", + "--bytes", + "--nodeps", + "--json", + "--output", "path,ro,rm,hotplug,model,size,type,tran,vendor", + ) + expect(nil, json.Unmarshal(b, &data), "list the block devices") + + // Filter the block devices to get only the USB flash drives + var devices []device + for _, device := range data.Blockdevices { + if !device.RO && device.RM && device.Hotplug && device.Type == "disk" && device.Tran == "usb" { + devices = append(devices, device) + } + } + + // If no USB flash drive is found, exit the program with an explanation + if len(devices) == 0 { + fmt.Println("No available USB devices were found.") + if len(data.Blockdevices) > 0 { + fmt.Println("Here is a list of the block devices found:") + table := [][]string{{"Path", "RO", "RM", "Hotplug", "Model", "Size", "Type", "Tran", "Vendor"}} + for _, device := range data.Blockdevices { + table = append(table, []string{ + device.Path, + strconv.FormatBool(device.RO), + strconv.FormatBool(device.RM), + strconv.FormatBool(device.Hotplug), + device.Model.String(), + device.Size.String(), + device.Type, + device.Tran, + device.Vendor.String(), + }) + } + renderTable(table) + } + return + } + + // Display the found USB flash drives + table := [][]string{{"Number", "Vendor", "Model", "Size"}} + for i, device := range devices { + table = append(table, []string{ + strconv.Itoa(i), + device.Vendor.String(), + device.Model.String(), + device.Size.String(), + }) + } + renderTable(table) + + // Select the USB flash drive to format + if len(devices) == 1 { + fmt.Println(`Press ENTER to format the disk (or "exit")`) + } else { + fmt.Println(`Enter the number of the disk you want to format (or "exit"):`) + } + var choice string + fmt.Scanln(&choice) + if strings.TrimSpace(strings.ToLower(choice)) == "exit" { + return + } + var nb int + if len(devices) > 1 { + nb, err := strconv.Atoi(choice) + if err != nil { + fatalln("Wrong disk number", choice) + } + if nb < 0 || nb >= len(devices) { + fatalln("Wrong disk number, choose between", 0, "and", len(devices)-1) + } + } + device := devices[nb] + + // Get a list of the USB flash drive mount points + b, err := os.ReadFile("/proc/mounts") + expect(nil, err, "list the mount points") + lines := strings.Split(string(b), "\n") + var mountPoints []string + for _, line := range lines { + fields := strings.Fields(line) + if len(fields) < 2 { + continue + } + deviceName := fields[0] + mountPoint := fields[1] + if strings.Contains(deviceName, device.Path) { + mountPoints = append(mountPoints, mountPoint) + } + } + + // Unmount the USB flash drive + if len(mountPoints) > 0 { + mountPointOccurrences := map[string]int{} + for _, mountPoint := range mountPoints { + mountPoint = mountPoint[1:] // remove leading '/' + parts := strings.Split(mountPoint, "/") + for i := 1; i <= len(parts); i++ { + mountPointOccurrences[strings.Join(parts[:i], "/")]++ + } + } + for mountPoint, occurrences := range mountPointOccurrences { + parts := strings.Split(mountPoint, "/") + for i := len(parts) - 1; i > 0; i-- { + parent := strings.Join(parts[:i], "/") + if occurrences < mountPointOccurrences[parent] { + delete(mountPointOccurrences, mountPoint) + } else { + delete(mountPointOccurrences, parent) + } + } + } + for mountPoint := range mountPointOccurrences { + mountPoint = "/" + mountPoint // put back leading '/' + fmt.Print("The selected device is mounted on ", mountPoint, ". Trying to unmount it... ") + run("umount the selected device", "umount", "--recursive", mountPoint) + fmt.Println("done") + } + } + + // Format USB flash drive + fmt.Print("Formatting... ") + run("erase disk data", "wipefs", "--all", device.Path) + run("erase disk data", "sgdisk", "--zap-all", device.Path) + run("create partition table", "sgdisk", "--largest-new", "0", device.Path) + run("create partition table", "sgdisk", "--change-name", "0:01-home", device.Path) + run("inform the OS of partition table changes", "partx", "--update", device.Path) + run("format partition", "mkfs.f2fs", "-f", device.Path+"1") + fmt.Println("done") + fmt.Println(device.Vendor, device.Model, device.Size, "has been formatted, logout and login to use it") +}