Revert commit r151 and use lsblk again to find partition layout.

This commit is contained in:
Michael Vogt
2015-02-11 08:02:24 -05:00
committed by Snappy Tarmac
2 changed files with 128 additions and 411 deletions

View File

@@ -34,7 +34,6 @@ import (
"os/signal"
"path"
"regexp"
"strconv"
"strings"
"syscall"
@@ -64,12 +63,6 @@ const BOOT_PARTITION_LABEL = "system-boot"
// FIXME: Should query system-image-cli (see bug LP:#1380574).
const DEFAULT_CACHE_DIR = "/writable/cache"
// Directory created by udev that allows a non-priv user to list the
// available disk partitions by partition label.
var diskDeviceDir = "/dev/disk/by-partlabel"
var mountsFile = "/proc/self/mounts"
// Directory to mount writable root filesystem below the cache
// diretory.
const MOUNT_TARGET = "system"
@@ -158,31 +151,6 @@ type blockDevice struct {
mountpoint string
}
// similar to a "struct mntent"
type mntEnt struct {
Device string
MountPoint string
Type string
Options []string
DumpFreq int
FsckPassNo int
}
// Return the mountpoint for the specified disk partition by inspecting the
// provided mount entries.
func getMountPoint(mounts []mntEnt, partition string) string {
for _, entry := range mounts {
// Note that we have to special-case the rootfs mount
// since there are *two* entries in the mount table; one
// for mountpoint "/root", the other for "/".
if entry.Device == partition && entry.MountPoint != "/root" {
return entry.MountPoint
}
}
return ""
}
// Representation of HARDWARE_SPEC_FILE
type hardwareSpecType struct {
Kernel string `yaml:"kernel"`
@@ -330,136 +298,84 @@ func stringInSlice(slice []string, value string) int {
return -1
}
// Returns all current mounts
var getMounts = func(mountsFile string) (mounts []mntEnt, err error) {
lines, err := readLines(mountsFile)
if err != nil {
return mounts, err
}
for _, line := range lines {
fields := strings.Fields(line)
if len(fields) < 6 {
// handle invalid input.
continue
}
options := strings.Split(fields[3], ",")
dumpFreq, err := strconv.Atoi(fields[4])
if err != nil {
return mounts, err
}
fsckPassNo, err := strconv.Atoi(fields[5])
if err != nil {
return mounts, err
}
m := mntEnt{
Device: fields[0],
MountPoint: fields[1],
Type: fields[2],
Options: options,
DumpFreq: dumpFreq,
FsckPassNo: fsckPassNo,
}
mounts = append(mounts, m)
}
return mounts, err
}
// Returns a hash of recognised partitions in the following form:
//
// key: name of recognised partition.
// value: full path to disk device for the partition.
func getPartitions(deviceDir string) (m map[string]string, err error) {
recognised := allPartitionLabels()
m = make(map[string]string)
entries, err := ioutil.ReadDir(deviceDir)
if err != nil {
return nil, err
}
for _, entry := range entries {
pos := stringInSlice(recognised, entry.Name())
if pos < 0 {
// ignore unrecognised partitions
continue
}
isSymLink := (entry.Mode() & os.ModeSymlink) == os.ModeSymlink
fullPath := path.Join(deviceDir, entry.Name())
if isSymLink {
dest, err := os.Readlink(fullPath)
if err != nil {
return nil, err
}
var cleaned string
if strings.HasPrefix(dest, "..") {
cleaned = path.Clean(path.Join(deviceDir, dest))
} else {
cleaned = path.Clean(dest)
}
fullPath = cleaned
}
m[entry.Name()] = fullPath
}
return m, nil
var runLsblk = func() (output []string, err error) {
return runCommandWithStdout(
"/bin/lsblk",
"--ascii",
"--output=NAME,LABEL,PKNAME,MOUNTPOINT",
"--pairs")
}
// Determine details of the recognised disk partitions
// available on the system via lsblk
func loadPartitionDetails() (partitions []blockDevice, err error) {
var recognised []string = allPartitionLabels()
pm, err := getPartitions(diskDeviceDir)
lines, err := runLsblk()
if err != nil {
return nil, err
return partitions, err
}
pattern := regexp.MustCompile(`(?:[^\s"]|"(?:[^"])*")+`)
mounts, err := getMounts(mountsFile)
if err != nil {
return nil, err
}
for _, line := range lines {
fields := make(map[string]string)
// XXX: allow a prefix (".*") for test code.
diskPattern := regexp.MustCompile(`(.*/dev/[^\d]*)\d+`)
// split the line into 'NAME="quoted value"' fields
matches := pattern.FindAllString(line, -1)
for name, device := range pm {
// convert partition device to parent disk device by stripping
// off the numeric partition number.
// FIXME: this is crude and probably fragile.
matches := diskPattern.FindAllStringSubmatch(device, -1)
for _, match := range matches {
tmp := strings.Split(match, "=")
name := tmp[0]
if matches == nil {
return nil,
errors.New(fmt.Sprintf("failed to find disk associated with partition %q", device))
// remove quotes
value := strings.Trim(tmp[1], `"`)
// store
fields[name] = value
}
disk := matches[0][1]
if !fileExists(disk) {
// Look for expected partition labels
name, ok := fields["LABEL"]
if !ok {
continue
}
mountPoint := getMountPoint(mounts, device)
if name == "" || name == "\"\"" {
continue
}
pos := stringInSlice(recognised, name)
if pos < 0 {
// ignore unrecognised partitions
continue
}
// reconstruct full path to disk partition device
device := fmt.Sprintf("/dev/%s", fields["NAME"])
// FIXME: we should have a way to mock the "/dev" dir
// or we skip this test lsblk never returns non-existing
// devices
/*
if err := FileExists(device); err != nil {
continue
}
*/
// reconstruct full path to entire disk device
disk := fmt.Sprintf("/dev/%s", fields["PKNAME"])
// FIXME: we should have a way to mock the "/dev" dir
// or we skip this test lsblk never returns non-existing
// files
/*
if err := FileExists(disk); err != nil {
continue
}
*/
bd := blockDevice{
name: name,
name: fields["LABEL"],
device: device,
mountpoint: mountPoint,
mountpoint: fields["MOUNTPOINT"],
parentName: disk,
}
@@ -711,7 +627,7 @@ func (p *Partition) rootPartition() (result *blockDevice) {
}
// Return pointer to blockDevice representing the "other" root
// filesystem (which is not currently active)
// filesystem (which is not currently mounted)
func (p *Partition) otherRootPartition() (result *blockDevice) {
for _, part := range p.rootPartitions() {
if part.mountpoint != "/" {
@@ -791,11 +707,17 @@ func (p *Partition) unmountOtherRootfs() (err error) {
// The bootloader requires a few filesystems to be mounted when
// run from within a chroot.
func (p *Partition) bindmountRequiredFilesystems() (err error) {
var boot *blockDevice
// Minimal list of mounts required for running grub-install
// within a chroot.
for _, fs := range []string{"/dev", "/proc", "/sys"} {
// we always requires these
requiredChrootMounts := []string{"/dev", "/proc", "/sys"}
// if there is a boot partition we also bind-mount it
boot := p.bootPartition()
if boot != nil && boot.mountpoint != "" {
requiredChrootMounts = append(requiredChrootMounts, boot.mountpoint)
}
for _, fs := range requiredChrootMounts {
target := path.Join(p.MountTarget(), fs)
err := bindmountAndAddToGlobalMountList(fs, target)
@@ -804,24 +726,7 @@ func (p *Partition) bindmountRequiredFilesystems() (err error) {
}
}
boot = p.bootPartition()
if boot == nil {
// No separate boot partition
return nil
}
if boot.mountpoint == "" {
// Impossible situation
return nil
}
target := path.Join(p.MountTarget(), boot.mountpoint)
err = bindmountAndAddToGlobalMountList(boot.mountpoint, target)
if err != nil {
return err
}
return err
return nil
}
// Undo the effects of BindmountRequiredFilesystems()

View File

@@ -2,11 +2,8 @@ package partition
import (
"errors"
"fmt"
"io/ioutil"
"os"
"os/exec"
"path"
"strings"
"testing"
@@ -22,28 +19,6 @@ type PartitionTestSuite struct {
var _ = Suite(&PartitionTestSuite{})
// Create an empty file specified by path
func createEmptyFile(path string) (err error) {
return ioutil.WriteFile(path, []byte(""), 0640)
}
// Run specified function from directory dir
func runChdir(dir string, f func() (err error)) (err error) {
cwd, err := os.Getwd()
if err != nil {
return err
}
if err = os.Chdir(dir); err != nil {
return err
}
defer func() {
err = os.Chdir(cwd)
}()
return f()
}
func makeHardwareYaml() (tmp *os.File, err error) {
tmp, err = ioutil.TempFile("", "hw-")
if err != nil {
@@ -59,130 +34,16 @@ bootloader: uboot
return tmp, err
}
// Create a fake "/proc/self/mounts"-format file containing only the entries
// we care about.
func makeMountsFile(replaceMap map[string]string) (filename string, err error) {
template := []string{"/dev/sda2 /boot/efi vfat rw,relatime,fmask=0022,dmask=0022,codepage=437,iocharset=iso8859-1,shortname=mixed,errors=remount-ro 0 0",
"/dev/sda2 /boot/grub vfat rw,relatime,fmask=0022,dmask=0022,codepage=437,iocharset=iso8859-1,shortname=mixed,errors=remount-ro 0 0",
"/dev/sda3 /root ext4 ro,relatime,data=ordered 0 0",
"/dev/sda3 / ext4 ro,relatime,data=ordered 0 0",
"/dev/sda4 /writable/cache/system ext4 ro,relatime,data=ordered 0 0",
"/dev/sda5 /writable ext4 rw,relatime,discard,data=ordered 0 0"}
var lines []string
for _, line := range template {
for from, to := range replaceMap {
line = strings.Replace(line, from, to, -1)
}
lines = append(lines, line)
}
tmp, err := ioutil.TempFile("", "mounts-")
if err != nil {
return "", err
}
for _, line := range lines {
tmp.WriteString(fmt.Sprintf("%s\n", line))
}
tmp.Close()
return tmp.Name(), err
}
// Create a fake directory tree that is similar to that created by udev
// as /dev/disk/by-partlabel/.
func makeDeviceDirs(disk string, deviceMap map[string]string) (parent string, linkDir string, err error) {
parent, err = ioutil.TempDir("", "mounts-parent-")
if err != nil {
return "", "", err
}
devDir := path.Join(parent, "dev")
linkDir = path.Join(devDir, "disk", "by-partlabel")
os.MkdirAll(linkDir, 0755)
// Create the fake overall disk device
if err = createEmptyFile(path.Join(devDir, disk)); err != nil {
return "", "", err
}
for name, label := range deviceMap {
// Create the fake partition device
if err = createEmptyFile(path.Join(devDir, name)); err != nil {
return "", "", err
}
relativePath := fmt.Sprintf("../../%s", name)
// create the symlink pointing to the fake device
err = runChdir(linkDir, func() (err error) {
cmd := exec.Command("/bin/ln", "-s", relativePath, label)
return cmd.Run()
})
if err != nil {
return "", "", err
}
}
return parent, linkDir, nil
}
func makeMountsFileAndDevices(dualRootfs bool) (mountsFile string, parentDevDir string, devDir string, err error) {
disk := "sda"
devs := map[string]string{
"sda1": "grub",
"sda2": "system-boot",
"sda3": "system-a",
"sda5": "writable",
}
if dualRootfs {
devs["sda4"] = "system-b"
}
parentDevDir, devDir, err = makeDeviceDirs(disk, devs)
if err != nil {
return "", "", "", err
}
replacements := make(map[string]string)
for dev, _ := range devs {
replacements[fmt.Sprintf("/dev/%s", dev)] =
path.Join(parentDevDir, "/dev", dev)
}
if mountsFile, err = makeMountsFile(replacements); err != nil {
return mountsFile, parentDevDir, devDir, err
}
return mountsFile, parentDevDir, devDir, err
}
func (s *PartitionTestSuite) TestHardwareSpec(c *C) {
mockMountsFile, parentDevDir, deviceDir, err := makeMountsFileAndDevices(true)
c.Assert(err, IsNil)
defer func() {
os.Remove(mockMountsFile)
os.RemoveAll(parentDevDir)
}()
diskDeviceDir = deviceDir
mountsFile = mockMountsFile
p := New()
c.Assert(p, NotNil)
tmp2, err := makeHardwareYaml()
tmp, err := makeHardwareYaml()
defer func() {
os.Remove(tmp2.Name())
os.Remove(tmp.Name())
}()
p.hardwareSpecFile = tmp2.Name()
p.hardwareSpecFile = tmp.Name()
hw, err := p.hardwareSpec()
c.Assert(err, IsNil)
c.Assert(hw.Kernel, Equals, "assets/vmlinuz")
@@ -192,82 +53,60 @@ func (s *PartitionTestSuite) TestHardwareSpec(c *C) {
c.Assert(hw.Bootloader, Equals, "uboot")
}
func mockRunLsblkDualSnappy() (output []string, err error) {
dualData := `
NAME="sda" LABEL="" PKNAME="" MOUNTPOINT=""
NAME="sda1" LABEL="" PKNAME="sda" MOUNTPOINT=""
NAME="sda2" LABEL="system-boot" PKNAME="sda" MOUNTPOINT="/boot/efi"
NAME="sda3" LABEL="system-a" PKNAME="sda" MOUNTPOINT="/"
NAME="sda4" LABEL="system-b" PKNAME="sda" MOUNTPOINT=""
NAME="sda5" LABEL="writable" PKNAME="sda" MOUNTPOINT="/writable"
NAME="sr0" LABEL="" PKNAME="" MOUNTPOINT=""
`
return strings.Split(dualData, "\n"), err
}
func (s *PartitionTestSuite) TestSnappyDualRoot(c *C) {
mockMountsFile, parentDevDir, deviceDir, err := makeMountsFileAndDevices(true)
c.Assert(err, IsNil)
defer func() {
os.Remove(mockMountsFile)
os.RemoveAll(parentDevDir)
}()
diskDeviceDir = deviceDir
mountsFile = mockMountsFile
runLsblk = mockRunLsblkDualSnappy
p := New()
c.Assert(p.dualRootPartitions(), Equals, true)
c.Assert(p.singleRootPartition(), Equals, false)
rootPartitions := p.rootPartitions()
// XXX: getPartitions() returns a map, and iteration is random.
// Hence, we cannot rely on the order of the array returned by
// rootPartitions() so take a sniff first...
var aIndex int
var bIndex int
if rootPartitions[0].name == "system-a" {
aIndex = 0
bIndex = 1
} else {
aIndex = 1
bIndex = 0
}
c.Assert(rootPartitions[aIndex].name, Equals, "system-a")
c.Assert(strings.HasSuffix(rootPartitions[aIndex].device, "/dev/sda3"), Equals, true)
c.Assert(strings.HasSuffix(rootPartitions[aIndex].parentName, "/dev/sda"), Equals, true)
c.Assert(rootPartitions[bIndex].name, Equals, "system-b")
c.Assert(strings.HasSuffix(rootPartitions[bIndex].device, "/dev/sda4"), Equals, true)
c.Assert(strings.HasSuffix(rootPartitions[bIndex].parentName, "/dev/sda"), Equals, true)
c.Assert(rootPartitions[0].name, Equals, "system-a")
c.Assert(rootPartitions[0].device, Equals, "/dev/sda3")
c.Assert(rootPartitions[0].parentName, Equals, "/dev/sda")
c.Assert(rootPartitions[1].name, Equals, "system-b")
c.Assert(rootPartitions[1].device, Equals, "/dev/sda4")
c.Assert(rootPartitions[1].parentName, Equals, "/dev/sda")
wp := p.writablePartition()
c.Assert(wp.name, Equals, "writable")
c.Assert(strings.HasSuffix(wp.device, "/dev/sda5"), Equals, true)
c.Assert(strings.HasSuffix(wp.parentName, "/dev/sda"), Equals, true)
c.Assert(wp.device, Equals, "/dev/sda5")
c.Assert(wp.parentName, Equals, "/dev/sda")
boot := p.bootPartition()
c.Assert(boot.name, Equals, "system-boot")
c.Assert(strings.HasSuffix(boot.device, "/dev/sda2"), Equals, true)
c.Assert(strings.HasSuffix(boot.parentName, "/dev/sda"), Equals, true)
c.Assert(boot.device, Equals, "/dev/sda2")
c.Assert(boot.parentName, Equals, "/dev/sda")
root := p.rootPartition()
c.Assert(root, Not(IsNil))
c.Assert(root.name, Equals, "system-a")
c.Assert(strings.HasSuffix(root.device, "/dev/sda3"), Equals, true)
c.Assert(strings.HasSuffix(root.parentName, "/dev/sda"), Equals, true)
c.Assert(root.device, Equals, "/dev/sda3")
c.Assert(root.parentName, Equals, "/dev/sda")
other := p.otherRootPartition()
c.Assert(other.name, Equals, "system-b")
c.Assert(strings.HasSuffix(other.device, "/dev/sda4"), Equals, true)
c.Assert(strings.HasSuffix(other.parentName, "/dev/sda"), Equals, true)
c.Assert(other.device, Equals, "/dev/sda4")
c.Assert(other.parentName, Equals, "/dev/sda")
}
func (s *PartitionTestSuite) TestRunWithOtherDualParitionRO(c *C) {
mockMountsFile, parentDevDir, deviceDir, err := makeMountsFileAndDevices(true)
c.Assert(err, IsNil)
defer func() {
os.Remove(mockMountsFile)
os.RemoveAll(parentDevDir)
}()
diskDeviceDir = deviceDir
mountsFile = mockMountsFile
runLsblk = mockRunLsblkDualSnappy
p := New()
reportedRoot := ""
err = p.RunWithOther(RO, func(otherRoot string) (err error) {
err := p.RunWithOther(RO, func(otherRoot string) (err error) {
reportedRoot = otherRoot
return nil
})
@@ -276,14 +115,7 @@ func (s *PartitionTestSuite) TestRunWithOtherDualParitionRO(c *C) {
}
func (s *PartitionTestSuite) TestRunWithOtherDualParitionRWFuncErr(c *C) {
mockMountsFile, parentDevDir, deviceDir, err := makeMountsFileAndDevices(true)
c.Assert(err, IsNil)
defer func() {
os.Remove(mockMountsFile)
os.RemoveAll(parentDevDir)
}()
diskDeviceDir = deviceDir
mountsFile = mockMountsFile
runLsblk = mockRunLsblkDualSnappy
savedRunCommand := runCommand
defer func() {
@@ -292,7 +124,7 @@ func (s *PartitionTestSuite) TestRunWithOtherDualParitionRWFuncErr(c *C) {
runCommand = mockRunCommand
p := New()
err = p.RunWithOther(RW, func(otherRoot string) (err error) {
err := p.RunWithOther(RW, func(otherRoot string) (err error) {
return errors.New("canary")
})
@@ -307,31 +139,26 @@ func (s *PartitionTestSuite) TestRunWithOtherDualParitionRWFuncErr(c *C) {
}
func (s *PartitionTestSuite) TestRunWithOtherSingleParitionRO(c *C) {
mockMountsFile, parentDevDir, deviceDir, err := makeMountsFileAndDevices(false)
c.Assert(err, IsNil)
defer func() {
os.Remove(mockMountsFile)
os.RemoveAll(parentDevDir)
}()
diskDeviceDir = deviceDir
mountsFile = mockMountsFile
runLsblk = mockRunLsblkSingleRootSnappy
p := New()
err = p.RunWithOther(RO, func(otherRoot string) (err error) {
err := p.RunWithOther(RO, func(otherRoot string) (err error) {
return nil
})
c.Assert(err, Equals, NoDualPartitionError)
}
func mockRunLsblkSingleRootSnappy() (output []string, err error) {
dualData := `
NAME="sda" LABEL="" PKNAME="" MOUNTPOINT=""
NAME="sda1" LABEL="" PKNAME="sda" MOUNTPOINT=""
NAME="sda2" LABEL="system-boot" PKNAME="sda" MOUNTPOINT=""
NAME="sda3" LABEL="system-a" PKNAME="sda" MOUNTPOINT="/"
NAME="sda5" LABEL="writable" PKNAME="sda" MOUNTPOINT="/writable"
`
return strings.Split(dualData, "\n"), err
}
func (s *PartitionTestSuite) TestSnappySingleRoot(c *C) {
mockMountsFile, parentDevDir, deviceDir, err := makeMountsFileAndDevices(false)
c.Assert(err, IsNil)
defer func() {
os.Remove(mockMountsFile)
os.RemoveAll(parentDevDir)
}()
diskDeviceDir = deviceDir
mountsFile = mockMountsFile
runLsblk = mockRunLsblkSingleRootSnappy
p := New()
c.Assert(p.dualRootPartitions(), Equals, false)
@@ -339,8 +166,8 @@ func (s *PartitionTestSuite) TestSnappySingleRoot(c *C) {
root := p.rootPartition()
c.Assert(root.name, Equals, "system-a")
c.Assert(strings.HasSuffix(root.device, "/dev/sda3"), Equals, true)
c.Assert(strings.HasSuffix(root.parentName, "/dev/sda"), Equals, true)
c.Assert(root.device, Equals, "/dev/sda3")
c.Assert(root.parentName, Equals, "/dev/sda")
other := p.otherRootPartition()
c.Assert(other, IsNil)
@@ -354,14 +181,7 @@ func mockRunCommand(args ...string) (err error) {
}
func (s *PartitionTestSuite) TestMountUnmountTracking(c *C) {
mockMountsFile, parentDevDir, deviceDir, err := makeMountsFileAndDevices(true)
c.Assert(err, IsNil)
defer func() {
os.Remove(mockMountsFile)
os.RemoveAll(parentDevDir)
}()
diskDeviceDir = deviceDir
mountsFile = mockMountsFile
runLsblk = mockRunLsblkDualSnappy
// FIXME: there should be a generic
// mockFunc(func) (restorer func())
@@ -397,14 +217,7 @@ func (s *PartitionTestSuite) TestStringSliceRemoveNoexistingNoOp(c *C) {
}
func (s *PartitionTestSuite) TestUndoMounts(c *C) {
mockMountsFile, parentDevDir, deviceDir, err := makeMountsFileAndDevices(true)
c.Assert(err, IsNil)
defer func() {
os.Remove(mockMountsFile)
os.RemoveAll(parentDevDir)
}()
diskDeviceDir = deviceDir
mountsFile = mockMountsFile
runLsblk = mockRunLsblkDualSnappy
// FIXME: there should be a generic
// mockFunc(func) (restorer func())
@@ -419,13 +232,12 @@ func (s *PartitionTestSuite) TestUndoMounts(c *C) {
// FIXME: mounts is global
c.Assert(mounts, DeepEquals, []string{})
p.bindmountRequiredFilesystems()
// check expected values
c.Assert(stringInSlice(mounts, "/writable/cache/system/dev"), Not(Equals), -1)
c.Assert(stringInSlice(mounts, "/writable/cache/system/proc"), Not(Equals), -1)
c.Assert(stringInSlice(mounts, "/writable/cache/system/sys"), Not(Equals), -1)
c.Assert(stringInSlice(mounts, "/writable/cache/system/boot/efi"), Not(Equals), -1)
c.Assert(mounts, DeepEquals, []string{
p.MountTarget() + "/dev",
p.MountTarget() + "/proc",
p.MountTarget() + "/sys",
p.MountTarget() + "/boot/efi",
})
p.unmountRequiredFilesystems()
c.Assert(mounts, DeepEquals, []string{})
}