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