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 <dt@docker.com>
This commit is contained in:
Dave Tucker 2017-07-05 16:23:03 +01:00
parent 7cafad4fba
commit 5273ec1d33
4 changed files with 199 additions and 121 deletions

View File

@ -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 mkdir -p /out/etc/apk && cp -r /etc/apk/* /out/etc/apk/
RUN apk add --no-cache --initdb -p /out \ RUN apk add --no-cache --initdb -p /out \
@ -6,17 +6,27 @@ RUN apk add --no-cache --initdb -p /out \
busybox \ busybox \
e2fsprogs \ e2fsprogs \
e2fsprogs-extra \ e2fsprogs-extra \
jq \ btrfs-progs \
xfsprogs \
musl \ musl \
sfdisk \ sfdisk \
util-linux \
&& true && true
RUN rm -rf /out/etc/apk /out/lib/apk /out/var/cache 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 FROM scratch
ENTRYPOINT [] ENTRYPOINT []
CMD [] CMD []
WORKDIR / WORKDIR /
COPY --from=mirror /out/ / COPY --from=mirror /out/ /
COPY format.sh / COPY --from=build /go/bin/format usr/bin/format
CMD ["/bin/sh", "/format.sh"] CMD ["/usr/bin/format"]
LABEL org.mobyproject.config='{"binds": ["/dev:/dev"], "capabilities": ["CAP_SYS_ADMIN", "CAP_MKNOD"], "net": "new", "ipc": "new"}' LABEL org.mobyproject.config='{"binds": ["/dev:/dev"], "capabilities": ["CAP_SYS_ADMIN", "CAP_MKNOD"], "net": "new", "ipc": "new"}'

View File

@ -1,4 +1,4 @@
IMAGE=format IMAGE=format
DEPS=format.sh DEPS=format.go
include ../package.mk include ../package.mk

184
pkg/format/format.go Normal file
View File

@ -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)
}
}
}

View File

@ -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