From a14a8be49ee0c4b85705a08d2fc1f62af1f24a9f Mon Sep 17 00:00:00 2001
From: Dave Tucker
Date: Wed, 5 Jul 2017 16:23:19 +0100
Subject: [PATCH] mount: Add support to mount by label, uuid or name
This commit rewrites the mount package in Go.
It adds the ability to mount the by label, UUID or name.
It also fixes the automatic mount logic to check that a drive isn't
already mounted before attempting to mount it. This allows for multiple
uses of the mount pkg in a single YAML file.
Signed-off-by: Dave Tucker
---
pkg/mount/Dockerfile | 17 ++--
pkg/mount/Makefile | 2 +-
pkg/mount/mount.sh | 33 --------
pkg/mount/mountie.go | 192 +++++++++++++++++++++++++++++++++++++++++++
4 files changed, 205 insertions(+), 39 deletions(-)
delete mode 100755 pkg/mount/mount.sh
create mode 100644 pkg/mount/mountie.go
diff --git a/pkg/mount/Dockerfile b/pkg/mount/Dockerfile
index caafe8fcc..7c0592239 100644
--- a/pkg/mount/Dockerfile
+++ b/pkg/mount/Dockerfile
@@ -1,20 +1,27 @@
-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 \
alpine-baselayout \
busybox \
- jq \
musl \
sfdisk \
&& 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 *.go /go/src/mountie/
+RUN go-compile.sh /go/src/mountie
+
FROM scratch
ENTRYPOINT []
CMD []
WORKDIR /
COPY --from=mirror /out/ /
-COPY mount.sh /
-CMD ["/bin/sh", "/mount.sh"]
-LABEL org.mobyproject.config='{"binds": ["/dev:/dev", "/var:/var:rshared,rbind"], "capabilities": ["CAP_SYS_ADMIN"], "rootfsPropagation": "shared", "net": "new", "ipc": "new"}'
+COPY --from=build /go/bin/mountie usr/bin/mountie
+CMD ["/usr/bin/mountie"]
+LABEL org.mobyproject.config='{"binds": ["/dev:/dev", "/var:/var:rshared,rbind", "/:/hostroot"], "capabilities": ["CAP_SYS_ADMIN"], "rootfsPropagation": "shared", "net": "new", "ipc": "new"}'
diff --git a/pkg/mount/Makefile b/pkg/mount/Makefile
index 2deb1dbf3..6e246442b 100644
--- a/pkg/mount/Makefile
+++ b/pkg/mount/Makefile
@@ -1,4 +1,4 @@
IMAGE=mount
-DEPS=mount.sh
+DEPS=mountie.go
include ../package.mk
diff --git a/pkg/mount/mount.sh b/pkg/mount/mount.sh
deleted file mode 100755
index ebf54e8b1..000000000
--- a/pkg/mount/mount.sh
+++ /dev/null
@@ -1,33 +0,0 @@
-#!/bin/sh
-
-MOUNTPOINT="$1"
-
-[ -z "$MOUNTPOINT" ] && echo "No mountpoint specified" && exit 1
-
-mkdir -p "$MOUNTPOINT"
-
-mount_drive()
-{
- # TODO fix for multiple disks, cdroms etc
- DEVS="$(find /dev -maxdepth 1 -type b ! -name 'loop*' ! -name 'nbd*' | grep -v '[0-9]$' | sed 's@.*/dev/@@' | sort)"
-
- for DEV in $DEVS
- do
- DRIVE="/dev/${DEV}"
-
- # see if it has a partition table
- if sfdisk -d "${DRIVE}" >/dev/null 2>/dev/null
- then
- # 83 is Linux partition identifier
- DATA=$(sfdisk -J "$DRIVE" | jq -e -r '.partitiontable.partitions | map(select(.type=="83")) | .[0].node')
- if [ $? -eq 0 ]
- then
- mount "$DATA" "$MOUNTPOINT" && return
- fi
- fi
- done
-
- echo "WARNING: Failed to mount a persistent volume (is there one?)"
-}
-
-mount_drive
diff --git a/pkg/mount/mountie.go b/pkg/mount/mountie.go
new file mode 100644
index 000000000..b304add00
--- /dev/null
+++ b/pkg/mount/mountie.go
@@ -0,0 +1,192 @@
+package main
+
+import (
+ "bufio"
+ "encoding/json"
+ "flag"
+ "fmt"
+ "io/ioutil"
+ "log"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "regexp"
+ "sort"
+ "strings"
+ "syscall"
+)
+
+var (
+ deviceVar, labelVar, uuidVar string
+)
+
+// Fdisk is the JSON output from libfdisk
+type Fdisk struct {
+ PartitionTable struct {
+ Label string `json:"label"`
+ ID string `json:"id"`
+ Device string `json:"device"`
+ Unit string `json:"unit"`
+ FirstLBA int `json:"firstlba"`
+ LastLBA int `json:"lastlba"`
+ Partitions []struct {
+ Node string `json:"node"`
+ Start int `json:"start"`
+ Size int `json:"size"`
+ Type string `json:"type"`
+ UUID string `json:"uuid"`
+ Name string `json:"name"`
+ }
+ } `json:"partitionTable"`
+}
+
+// mount drive/partition to mountpoint
+func mount(device, mountpoint string) error {
+ if out, err := exec.Command("mount", device, mountpoint).CombinedOutput(); err != nil {
+ return fmt.Errorf("Error mounting %s to %s: %v\n%s", device, mountpoint, err, string(out))
+
+ }
+ return nil
+}
+
+func findDevice(pattern string) (string, error) {
+ out, err := exec.Command("findfs", pattern).Output()
+ if err != nil {
+ return "", fmt.Errorf("Error finding device with %s: %v", pattern, err)
+ }
+ device := strings.TrimSpace(string(out))
+ return device, nil
+}
+
+func findFirst(drives []string) (string, error) {
+ var first string
+
+ out, err := exec.Command("mount").Output()
+ if err != nil {
+ return "", err
+ }
+
+ mounted := make(map[string]bool)
+ scanner := bufio.NewScanner(strings.NewReader(string(out)))
+ for scanner.Scan() {
+ parts := strings.Split(scanner.Text(), " ")
+ if _, err := os.Stat(parts[0]); os.IsNotExist(err) {
+ continue
+ }
+ if _, ok := mounted[parts[0]]; !ok {
+ mounted[parts[0]] = true
+ }
+ }
+
+ for _, d := range drives {
+ err := exec.Command("sfdisk", "-d", d).Run()
+ if err != nil {
+ log.Printf("No partition table found on device %s. Skipping.", d)
+ continue
+ }
+
+ data, err := exec.Command("sfdisk", "-J", d).Output()
+ if err != nil {
+ log.Fatalf("Unable to get drive data for %s from sfdisk: %v", d, err)
+ }
+
+ f := Fdisk{}
+ if err := json.Unmarshal(data, &f); err != nil {
+ return "", fmt.Errorf("Unable to unmarshal partition table from sfdisk: %v", err)
+ }
+
+ for _, partition := range f.PartitionTable.Partitions {
+ // ignore anything that isn't a Linux partition
+ if partition.Type != "83" {
+ continue
+ }
+ if _, ok := mounted[partition.Node]; ok {
+ log.Printf("%s already mounted. Skipping", partition.Node)
+ continue
+ }
+ first = partition.Node
+ break
+ }
+ }
+ if first == "" {
+ return "", fmt.Errorf("No eligible disks found")
+ }
+ return first, nil
+}
+
+// return a list of all available drives
+func findDrives() []string {
+ 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)
+ return driveKeys
+}
+
+func init() {
+ flag.StringVar(&deviceVar, "device", "", "Name of the device to mount")
+ flag.StringVar(&labelVar, "label", "", "Label of the device to mount")
+ flag.StringVar(&uuidVar, "uuid", "", "UUID of the device to mount")
+}
+
+func main() {
+ flag.Parse()
+
+ var mountpoint string
+
+ switch flag.NArg() {
+ case 0:
+ log.Fatal("No mountpoints provided")
+ case 1:
+ mountpoint = flag.Args()[0]
+ case 2:
+ deviceVar = flag.Args()[0]
+ mountpoint = flag.Args()[1]
+ default:
+ log.Fatalf("Too many arguments")
+ }
+
+ err := os.MkdirAll(mountpoint, os.ModeDir)
+ if err != nil {
+ log.Fatalf("Unable to create mountpoint %s: %v", mountpoint, err)
+ }
+ if deviceVar == "" && labelVar != "" {
+ deviceVar, err = findDevice(fmt.Sprintf("LABEL=%s", labelVar))
+ if err != nil {
+ log.Fatal(err)
+ }
+ }
+ if deviceVar == "" && uuidVar != "" {
+ deviceVar, err = findDevice(fmt.Sprintf("UUID=%s", uuidVar))
+ if err != nil {
+ log.Fatal(err)
+ }
+ }
+
+ if deviceVar == "" {
+ // find first device
+ drives := findDrives()
+ first, err := findFirst(drives)
+ if err != nil {
+ log.Fatal(err)
+ }
+ deviceVar = first
+ }
+
+ if err := mount(deviceVar, mountpoint); err != nil {
+ log.Fatal(err)
+ }
+}