From 5273ec1d337908477f3a494b28bd2d46eaf6cf9c Mon Sep 17 00:00:00 2001
From: Dave Tucker
Date: Wed, 5 Jul 2017 16:23:03 +0100
Subject: [PATCH] format: Add partition labels and new filesystems
This commit re-writes the format package in Go and adds the ability to
add partition labels and also specify either ext4, btrfs or xfs
Signed-off-by: Dave Tucker
---
pkg/format/Dockerfile | 18 ++++-
pkg/format/Makefile | 2 +-
pkg/format/format.go | 184 ++++++++++++++++++++++++++++++++++++++++++
pkg/format/format.sh | 116 --------------------------
4 files changed, 199 insertions(+), 121 deletions(-)
create mode 100644 pkg/format/format.go
delete mode 100755 pkg/format/format.sh
diff --git a/pkg/format/Dockerfile b/pkg/format/Dockerfile
index eefa553db..9f44a74cb 100644
--- a/pkg/format/Dockerfile
+++ b/pkg/format/Dockerfile
@@ -1,4 +1,4 @@
-FROM linuxkit/alpine:9bcf61f605ef0ce36cc94d59b8eac307862de6e1 AS mirror
+FROM linuxkit/alpine:488aa6f5dd2d8121a3c5c5c7a1ecf97c424b96ac AS mirror
RUN mkdir -p /out/etc/apk && cp -r /etc/apk/* /out/etc/apk/
RUN apk add --no-cache --initdb -p /out \
@@ -6,17 +6,27 @@ RUN apk add --no-cache --initdb -p /out \
busybox \
e2fsprogs \
e2fsprogs-extra \
- jq \
+ btrfs-progs \
+ xfsprogs \
musl \
sfdisk \
+ util-linux \
&& true
RUN rm -rf /out/etc/apk /out/lib/apk /out/var/cache
+FROM linuxkit/alpine:488aa6f5dd2d8121a3c5c5c7a1ecf97c424b96ac AS build
+
+RUN apk add --no-cache go musl-dev
+ENV GOPATH=/go PATH=$PATH:/go/bin
+
+COPY format.go /go/src/format/
+RUN go-compile.sh /go/src/format
+
FROM scratch
ENTRYPOINT []
CMD []
WORKDIR /
COPY --from=mirror /out/ /
-COPY format.sh /
-CMD ["/bin/sh", "/format.sh"]
+COPY --from=build /go/bin/format usr/bin/format
+CMD ["/usr/bin/format"]
LABEL org.mobyproject.config='{"binds": ["/dev:/dev"], "capabilities": ["CAP_SYS_ADMIN", "CAP_MKNOD"], "net": "new", "ipc": "new"}'
diff --git a/pkg/format/Makefile b/pkg/format/Makefile
index b7a136dea..587146eb1 100644
--- a/pkg/format/Makefile
+++ b/pkg/format/Makefile
@@ -1,4 +1,4 @@
IMAGE=format
-DEPS=format.sh
+DEPS=format.go
include ../package.mk
diff --git a/pkg/format/format.go b/pkg/format/format.go
new file mode 100644
index 000000000..443f035d8
--- /dev/null
+++ b/pkg/format/format.go
@@ -0,0 +1,184 @@
+package main
+
+import (
+ "flag"
+ "fmt"
+ "io/ioutil"
+ "log"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "regexp"
+ "sort"
+ "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
+ drives map[string]bool
+ driveKeys []string
+)
+
+func autoformat(label, fsType string) error {
+ var first string
+ for _, d := range driveKeys {
+ err := exec.Command("sfdisk", "-d", d).Run()
+ if err == nil {
+ log.Printf("Partition table found on device %s. Skipping.", d)
+ continue
+ }
+ first = d
+ break
+ }
+
+ if first == "" {
+ return fmt.Errorf("No eligible disks found")
+ }
+
+ if err := format(first, label, fsType); err != nil {
+ return err
+ }
+
+ return nil
+}
+
+func format(d, label, fsType string) error {
+ 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)
+ fdiskCmd.Stdin = strings.NewReader("w")
+ 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)
+ }
+
+ exec.Command("mdev", "-s").Run()
+
+ partition := fmt.Sprintf("%s1", d)
+ // wait for device
+ var done bool
+ for i := 0; i < timeout; i++ {
+ stat, err := os.Stat(partition)
+ if err == nil {
+ mode := stat.Sys().(*syscall.Stat_t).Mode
+ if (mode & syscall.S_IFMT) == syscall.S_IFBLK {
+ done = true
+ break
+ }
+ }
+ time.Sleep(100 * time.Millisecond)
+ exec.Command("mdev", "-s").Run()
+ }
+ if !done {
+ return fmt.Errorf("Error waiting for device %s", partition)
+ }
+ // even after the device appears we still have a race
+ time.Sleep(1 * time.Second)
+
+ // 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
+}
+
+// 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 {
+ // 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
+ if (mode & syscall.S_IFMT) != syscall.S_IFBLK {
+ continue
+ }
+ // ignore if it matches regexp
+ if ignoreExp.MatchString(d.Name()) {
+ continue
+ }
+ driveKeys = append(driveKeys, filepath.Join("/dev", d.Name()))
+ }
+ sort.Strings(driveKeys)
+ for _, d := range driveKeys {
+ drives[d] = true
+ }
+}
+
+func init() {
+ flag.StringVar(&labelVar, "label", "", "Disk label to apply")
+ flag.StringVar(&fsTypeVar, "type", "ext4", "Type of filesystem to create")
+}
+
+func main() {
+ flag.Parse()
+
+ findDrives()
+
+ if flag.NArg() > 1 {
+ log.Fatalf("Too many arguments provided")
+ }
+
+ if flag.NArg() == 0 {
+ if err := autoformat(labelVar, fsTypeVar); err != nil {
+ log.Fatalf("%v", err)
+ }
+ } else {
+ if err := format(flag.Args()[0], labelVar, fsTypeVar); err != nil {
+ log.Fatalf("%v", err)
+ }
+ }
+}
diff --git a/pkg/format/format.sh b/pkg/format/format.sh
deleted file mode 100755
index 6ae912bdb..000000000
--- a/pkg/format/format.sh
+++ /dev/null
@@ -1,116 +0,0 @@
-#!/bin/sh
-
-# this script assumes anything on the disk can be removed if corrupted
-# other use cases may need different scripts.
-
-# currently only supports ext4 but should be expanded
-
-do_fsck()
-{
- # preen
- /sbin/e2fsck -p $*
- EXIT_CODE=$?
- # exit code 1 is errors corrected
- [ "${EXIT_CODE}" -eq 1 ] && EXIT_CODE=0
- # exit code 2 or 3 means need to reboot
- [ "${EXIT_CODE}" -eq 2 -o "${EXIT_CODE}" -eq 3 ] && /sbin/reboot
- # exit code 4 or over is fatal
- [ "${EXIT_CODE}" -lt 4 ] && return "${EXIT_CODE}"
-
- # try harder
- /sbin/e2fsck -y $*
- # exit code 1 is errors corrected
- [ "${EXIT_CODE}" -eq 1 ] && EXIT_CODE=0
- # exit code 2 or 3 means need to reboot
- [ "${EXIT_CODE}" -eq 2 -o "${EXIT_CODE}" -eq 3 ] && /sbin/reboot
- # exit code 4 or over is fatal
- [ "${EXIT_CODE}" -ge 4 ] && printf "Filesystem unrecoverably corrupted, will reformat\n"
-
- return "${EXIT_CODE}"
-}
-
-do_fsck_extend_mount()
-{
- DRIVE="$1"
- DATA="$2"
-
- do_fsck "$DATA" || return 1
-
- # only try to extend if there is a single partition and free space
- PARTITIONS=$(sfdisk -J "$DRIVE" | jq '.partitiontable.partitions | length')
-
- if [ "$PARTITIONS" -eq 1 ] && \
- sfdisk -F "$DRIVE" | grep -q 'Unpartitioned space' &&
- ! sfdisk -F "$DRIVE" | grep -q '0 B, 0 bytes, 0 sectors'
- then
- SPACE=$(sfdisk -F "$DRIVE" | grep 'Unpartitioned space')
- printf "Resizing disk partition: $SPACE\n"
-
- # 83 is Linux partition id
- START=$(sfdisk -J "$DRIVE" | jq -e '.partitiontable.partitions | map(select(.type=="83")) | .[0].start')
-
- sfdisk -q --delete "$DRIVE" 2> /dev/null
- echo "${START},,83;" | sfdisk -q "$DRIVE"
-
- # set bootable flag
- sfdisk -A "$DRIVE" 1
-
- # update status
- blockdev --rereadpt $diskdev 2> /dev/null
- mdev -s
-
- # wait for device
- for i in $(seq 1 50); do test -b "$DATA" && break || sleep .1; mdev -s; done
-
- # resize2fs fails unless we use -f here
- do_fsck -f "$DATA" || return 1
- resize2fs "$DATA"
-
- do_fsck "$DATA" || return 1
- fi
-}
-
-do_mkfs()
-{
- diskdev="$1"
-
- # new disks does 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
- echo "w" | fdisk $diskdev >/dev/null
-
- # format one large partition
- echo ";" | sfdisk --quiet $diskdev
-
- # update status
- blockdev --rereadpt $diskdev 2> /dev/null
-
- # wait for device
- for i in $(seq 1 50); do test -b "$DATA" && break || sleep .1; mdev -s; done
-
- FSOPTS="-O resize_inode,has_journal,extent,huge_file,flex_bg,uninit_bg,64bit,dir_nlink,extra_isize"
-
- mkfs.ext4 -q -F $FSOPTS ${diskdev}1
-}
-
-# TODO fix for multiple disks, cdroms etc
-DEV="$(find /dev -maxdepth 1 -type b ! -name 'loop*' | grep -v '[0-9]$' | sed 's@.*/dev/@@' | sort | head -1 )"
-
-[ -z "${DEV}" ] && exit 1
-
-DRIVE="/dev/${DEV}"
-
-# see if it has a partition table already
-if sfdisk -d "${DRIVE}" >/dev/null 2>/dev/null
-then
- DATA=$(sfdisk -J "$DRIVE" | jq -e -r '.partitiontable.partitions | map(select(.type=="83")) | .[0].node')
- if [ $? -eq 0 ]
- then
- do_fsck_extend_mount "$DRIVE" "$DATA" || do_mkfs "$DRIVE"
- else
- do_mkfs "$DRIVE"
- fi
-else
- do_mkfs "$DRIVE"
-fi