summaryrefslogtreecommitdiff
diff options
authorMichael Vogt <mvo@ubuntu.com>2015-11-20 14:23:47 +0100
committerMichael Vogt <mvo@ubuntu.com>2015-11-20 14:30:48 +0100
commitb1bcfc1e895658de6a5de380ab9bc297ddff8dac (patch)
tree885bbb1f1c5f1ab76981300d820c4085965e1bb7
parent8a2da74bd7acb9c5dc738c477b67db4e81c352ee (diff)
Remove most of partition, we only need the bootvars for all-snapfeature/slash-and-burn
-rw-r--r--cmd/snappy/cmd_booted.go3
-rw-r--r--cmd/snappy/cmd_grub_migrate.go46
-rw-r--r--partition/assets.go87
-rw-r--r--partition/assets_test.go36
-rw-r--r--partition/bootloader.go246
-rw-r--r--partition/bootloader_grub.go43
-rw-r--r--partition/bootloader_grub_test.go108
-rw-r--r--partition/bootloader_public.go51
-rw-r--r--partition/bootloader_test.go62
-rw-r--r--partition/bootloader_uboot.go49
-rw-r--r--partition/bootloader_uboot_test.go278
-rw-r--r--partition/migrate_grub.go153
-rw-r--r--partition/migrate_grub_test.go58
-rw-r--r--partition/mount.go153
-rw-r--r--partition/mount_test.go64
-rw-r--r--partition/partition.go607
-rw-r--r--partition/partition_test.go483
17 files changed, 137 insertions, 2390 deletions
diff --git a/cmd/snappy/cmd_booted.go b/cmd/snappy/cmd_booted.go
index de3c1938e9..a658fa3c05 100644
--- a/cmd/snappy/cmd_booted.go
+++ b/cmd/snappy/cmd_booted.go
@@ -42,6 +42,5 @@ func (x *cmdBooted) Execute(args []string) error {
}
func (x *cmdBooted) doBooted() error {
- p := partition.New()
- return p.MarkBootSuccessful()
+ return partition.MarkBootSuccessful()
}
diff --git a/cmd/snappy/cmd_grub_migrate.go b/cmd/snappy/cmd_grub_migrate.go
deleted file mode 100644
index 6ebefa7fcd..0000000000
--- a/cmd/snappy/cmd_grub_migrate.go
+++ /dev/null
@@ -1,46 +0,0 @@
-// -*- Mode: Go; indent-tabs-mode: t -*-
-
-/*
- * Copyright (C) 2014-2015 Canonical Ltd
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 3 as
- * published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
- */
-
-package main
-
-import (
- "github.com/ubuntu-core/snappy/logger"
- "github.com/ubuntu-core/snappy/partition"
-)
-
-type cmdGrubMigrate struct {
-}
-
-func init() {
- _, err := parser.AddCommand("grub-migrate",
- "internal",
- "internal",
- &cmdGrubMigrate{})
- if err != nil {
- logger.Panicf("Unable to grub-migrate: %v", err)
- }
-}
-
-func (x *cmdGrubMigrate) Execute(args []string) error {
- return withMutexAndRetry(x.doGrubMigrate)
-}
-
-func (x *cmdGrubMigrate) doGrubMigrate() error {
- return partition.MigrateToDynamicGrub()
-}
diff --git a/partition/assets.go b/partition/assets.go
deleted file mode 100644
index 04a56bbec7..0000000000
--- a/partition/assets.go
+++ /dev/null
@@ -1,87 +0,0 @@
-// -*- Mode: Go; indent-tabs-mode: t -*-
-
-/*
- * Copyright (C) 2014-2015 Canonical Ltd
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 3 as
- * published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
- */
-
-package partition
-
-import (
- "errors"
- "io/ioutil"
- "os"
- "path/filepath"
-
- "gopkg.in/yaml.v2"
-)
-
-// Representation of the yaml in the hardwareSpecFile
-type hardwareSpecType struct {
- Kernel string `yaml:"kernel"`
- Initrd string `yaml:"initrd"`
- DtbDir string `yaml:"dtbs"`
- PartitionLayout string `yaml:"partition-layout"`
- Bootloader bootloaderName `yaml:"bootloader"`
-}
-
-var (
- // ErrNoHardwareYaml is returned when no hardware yaml is found in
- // the update, this means that there is nothing to process with regards
- // to device parts.
- ErrNoHardwareYaml = errors.New("no hardware.yaml")
-
- // Declarative specification of the type of system which specifies such
- // details as:
- //
- // - the location of initrd+kernel within the system-image archive.
- // - the location of hardware-specific .dtb files within the
- // system-image archive.
- // - the type of bootloader that should be used for this system.
- // - expected system partition layout (single or dual rootfs's).
- hardwareSpecFileReal = filepath.Join(cacheDir, "hardware.yaml")
-
- // useful to override in the tests
- hardwareSpecFile = hardwareSpecFileReal
-
- // Directory that _may_ get automatically created on unpack that
- // contains updated hardware-specific boot assets (such as initrd,
- // kernel)
- assetsDir = filepath.Join(cacheDir, "assets")
-
- // Directory that _may_ get automatically created on unpack that
- // contains updated hardware-specific assets that require flashing
- // to the disk (such as uBoot, MLO)
- flashAssetsDir = filepath.Join(cacheDir, "flashtool-assets")
-)
-
-func readHardwareSpec() (*hardwareSpecType, error) {
- var h hardwareSpecType
-
- data, err := ioutil.ReadFile(hardwareSpecFile)
- // if hardware.yaml does not exist it just means that there was no
- // device part in the update.
- if os.IsNotExist(err) {
- return nil, ErrNoHardwareYaml
- } else if err != nil {
- return nil, err
- }
-
- if err := yaml.Unmarshal([]byte(data), &h); err != nil {
- return nil, err
- }
-
- return &h, nil
-}
diff --git a/partition/assets_test.go b/partition/assets_test.go
deleted file mode 100644
index 628cdf7e24..0000000000
--- a/partition/assets_test.go
+++ /dev/null
@@ -1,36 +0,0 @@
-// -*- Mode: Go; indent-tabs-mode: t -*-
-
-/*
- * Copyright (C) 2014-2015 Canonical Ltd
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 3 as
- * published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
- */
-
-package partition
-
-import (
- . "gopkg.in/check.v1"
-)
-
-func (s *PartitionTestSuite) TestHardwareSpec(c *C) {
-
- hardwareSpecFile = makeHardwareYaml(c, "")
- hw, err := readHardwareSpec()
- c.Assert(err, IsNil)
- c.Assert(hw.Kernel, Equals, "assets/vmlinuz")
- c.Assert(hw.Initrd, Equals, "assets/initrd.img")
- c.Assert(hw.DtbDir, Equals, "assets/dtbs")
- c.Assert(hw.PartitionLayout, Equals, bootloaderSystemAB)
- c.Assert(hw.Bootloader, Equals, bootloaderNameUboot)
-}
diff --git a/partition/bootloader.go b/partition/bootloader.go
index ba076a71a9..ead848c878 100644
--- a/partition/bootloader.go
+++ b/partition/bootloader.go
@@ -19,17 +19,8 @@
package partition
-import (
- "fmt"
- "os"
- "path/filepath"
- "strings"
-
- "github.com/ubuntu-core/snappy/helpers"
-)
-
const (
- // bootloader variable used to denote which rootfs to boot from
+ // FIXME: killme, only used in tests right now
bootloaderRootfsVar = "snappy_ab"
// bootloader variable used to determine if boot was successful.
@@ -43,9 +34,6 @@ const (
// Initial and final values
bootloaderBootmodeTry = "try"
bootloaderBootmodeSuccess = "regular"
-
- // textual description in hardware.yaml for AB systems
- bootloaderSystemAB = "system-AB"
)
type bootloaderName string
@@ -54,38 +42,12 @@ type bootLoader interface {
// Name of the bootloader
Name() bootloaderName
- // Switch bootloader configuration so that the "other" root
- // filesystem partition will be used on next boot.
- ToggleRootFS(otherRootfs string) error
-
- // Hook function called before system-image starts downloading
- // and applying archives that allows files to be copied between
- // partitions.
- SyncBootFiles(bootAssets map[string]string) error
-
- // Install any hardware-specific files that system-image
- // downloaded.
- HandleAssets() error
-
// Return the value of the specified bootloader variable
GetBootVar(name string) (string, error)
// Set the value of the specified bootloader variable
SetBootVar(name, value string) error
- // Return the 1-character name corresponding to the
- // rootfs that will be used on _next_ boot.
- //
- // XXX: Note the distinction between this method and
- // GetOtherRootFSName(): the latter corresponds to the other
- // partition, whereas the value returned by this method is
- // queried directly from the bootloader.
- GetNextBootRootFSName() (string, error)
-
- // Update the bootloader configuration to mark the
- // currently-booted rootfs as having booted successfully.
- MarkCurrentBootSuccessful(currentRootfs string) error
-
// BootDir returns the (writable) bootloader-specific boot
// directory.
BootDir() string
@@ -94,14 +56,14 @@ type bootLoader interface {
// Factory method that returns a new bootloader for the given partition
var bootloader = bootloaderImpl
-func bootloaderImpl(p *Partition) (bootLoader, error) {
+func bootloaderImpl() (bootLoader, error) {
// try uboot
- if uboot := newUboot(p); uboot != nil {
+ if uboot := newUboot(); uboot != nil {
return uboot, nil
}
// no, try grub
- if grub := newGrub(p); grub != nil {
+ if grub := newGrub(); grub != nil {
return grub, nil
}
@@ -110,207 +72,7 @@ func bootloaderImpl(p *Partition) (bootLoader, error) {
}
type bootloaderType struct {
- partition *Partition
-
- // each rootfs partition has a corresponding u-boot directory named
- // from the last character of the partition name ('a' or 'b').
- currentRootfs string
- otherRootfs string
-
- // full path to rootfs-specific assets on boot partition
- currentBootPath string
- otherBootPath string
-
// FIXME: this should /boot if possible
// the dir that the bootloader lives in (e.g. /boot/uboot)
bootloaderDir string
}
-
-func newBootLoader(partition *Partition, bootloaderDir string) *bootloaderType {
- // no root partition means we are in an all-snap system
- if partition.rootPartition() == nil || partition.otherRootPartition() == nil {
- // FIXME: once we no longer support snappy-ab remove
- // everything except bootloaderDir from the
- // bootloaderType struct
- return &bootloaderType{
- partition: partition,
- bootloaderDir: bootloaderDir,
- }
- }
-
- // full label of the system {system-a,system-b}
- currentLabel := partition.rootPartition().name
- otherLabel := partition.otherRootPartition().name
-
- // single letter description of the rootfs {a,b}
- currentRootfs := string(currentLabel[len(currentLabel)-1])
- otherRootfs := string(otherLabel[len(otherLabel)-1])
-
- return &bootloaderType{
- partition: partition,
-
- currentRootfs: currentRootfs,
- otherRootfs: otherRootfs,
-
- // the paths that the kernel/initramfs are loaded, e.g.
- // /boot/uboot/a
- currentBootPath: filepath.Join(bootloaderDir, currentRootfs),
- otherBootPath: filepath.Join(bootloaderDir, otherRootfs),
-
- // the base bootloader dir, e.g. /boot/uboot or /boot/grub
- bootloaderDir: bootloaderDir,
- }
-}
-
-// FIXME:
-// - populate kernel if missing
-func (b *bootloaderType) SyncBootFiles(bootAssets map[string]string) (err error) {
- for src, dst := range bootAssets {
- if err := helpers.CopyIfDifferent(src, filepath.Join(b.bootloaderDir, dst)); err != nil {
- return err
- }
- }
-
- srcDir := b.currentBootPath
- destDir := b.otherBootPath
-
- // ensure they exist
- for _, dir := range []string{srcDir, destDir} {
- if err := os.MkdirAll(dir, 0755); err != nil {
- return err
- }
-
- }
- return helpers.RSyncWithDelete(srcDir, destDir)
-}
-
-// FIXME: once we stop supporting snappy-ab this can go and
-// HandleAssets can go as well
-//
-// noramlizeAssetName transforms like "vmlinuz-4.1.0" -> "vmlinuz"
-func normalizeKernelInitrdName(name string) string {
- name = filepath.Base(name)
- return strings.SplitN(name, "-", 2)[0]
-}
-
-// FIXME:
-// - if this fails it will never be re-tried because the "other" patition
-// is updated to revision-N in /etc/system-image/channel.ini
-// so the system only downloads from revision-N onwards even though the
-// complete update was not applied (i.e. kernel missing)
-func (b *bootloaderType) HandleAssets() (err error) {
- // check if we have anything, if there is no hardware yaml, there is nothing
- // to process.
- hardware, err := readHardwareSpec()
- if err == ErrNoHardwareYaml {
- return nil
- } else if err != nil {
- return err
- }
- // ensure to remove the file if there are no errors
- defer func() {
- if err == nil {
- os.Remove(hardwareSpecFile)
- }
- }()
-
- /*
- // validate bootloader
- if hardware.Bootloader != b.Name() {
- return fmt.Errorf(
- "bootloader is of type %s but hardware spec requires %s",
- b.Name(),
- hardware.Bootloader)
- }
- */
-
- // validate partition layout
- if b.partition.dualRootPartitions() && hardware.PartitionLayout != bootloaderSystemAB {
- return fmt.Errorf("hardware spec requires dual root partitions")
- }
-
- // ensure we have the destdir
- destDir := b.otherBootPath
- if err := os.MkdirAll(destDir, dirMode); err != nil {
- return err
- }
-
- // install kernel+initrd
- for _, file := range []string{hardware.Kernel, hardware.Initrd} {
-
- if file == "" {
- continue
- }
-
- // expand path
- path := filepath.Join(cacheDir, file)
-
- // It may happen that a delta update does not contain
- // the kernel. The reason is that e.g. the initramfs tools
- // got updated so the generated initrd is different but
- // the kernel stayed the same. Because the hardware.yaml
- // is build on cdimage and the delta is generated later
- // we cannot know this at hardware.yaml generation time.
- // So we simply ignore missing files here. There is no
- // risk because snappy will always sync the known good
- // kernel first
- if !helpers.FileExists(path) {
- continue
- }
-
- // ensure we remove the dir later
- defer func() {
- if err == nil {
- os.RemoveAll(filepath.Dir(path))
- }
- }()
-
- target := filepath.Join(destDir, normalizeKernelInitrdName(file))
- if err := runCommand("/bin/cp", path, target); err != nil {
- return err
- }
- }
-
- // TODO: look at the OEM package for dtb changes too once that is
- // fully speced
-
- // install .dtb files
- dtbSrcDir := filepath.Join(cacheDir, hardware.DtbDir)
- // ensure there is a DtbDir specified
- if hardware.DtbDir != "" && helpers.FileExists(dtbSrcDir) {
- // ensure we cleanup the source dir
- defer func() {
- if err == nil {
- os.RemoveAll(dtbSrcDir)
- }
- }()
-
- dtbDestDir := filepath.Join(destDir, "dtbs")
- if err := os.MkdirAll(dtbDestDir, dirMode); err != nil {
- return err
- }
-
- files, err := filepath.Glob(filepath.Join(dtbSrcDir, "*"))
- if err != nil {
- return err
- }
-
- for _, file := range files {
- if err := runCommand("/bin/cp", file, dtbDestDir); err != nil {
- return err
- }
- }
- }
-
- if helpers.FileExists(flashAssetsDir) {
- // FIXME: we don't currently do anything with the
- // MLO + uImage files since they are not specified in
- // the hardware spec. So for now, just remove them.
-
- if err := os.RemoveAll(flashAssetsDir); err != nil {
- return err
- }
- }
-
- return err
-}
diff --git a/partition/bootloader_grub.go b/partition/bootloader_grub.go
index 1a8fdcd7a3..012b86e9b5 100644
--- a/partition/bootloader_grub.go
+++ b/partition/bootloader_grub.go
@@ -59,17 +59,12 @@ func bootloaderGrubEnvFile() string {
}
// newGrub create a new Grub bootloader object
-func newGrub(partition *Partition) bootLoader {
+func newGrub() bootLoader {
if !helpers.FileExists(bootloaderGrubConfigFile()) {
return nil
}
- b := newBootLoader(partition, bootloaderGrubDir())
- if b == nil {
- return nil
- }
- g := grub{bootloaderType: *b}
-
+ g := grub{}
return &g
}
@@ -77,23 +72,6 @@ func (g *grub) Name() bootloaderName {
return bootloaderNameGrub
}
-// ToggleRootFS make the Grub bootloader switch rootfs's.
-//
-// Approach:
-//
-// Update the grub configuration.
-func (g *grub) ToggleRootFS(otherRootfs string) (err error) {
-
- if err := g.SetBootVar(bootloaderBootmodeVar, bootloaderBootmodeTry); err != nil {
- return err
- }
-
- // Record the partition that will be used for next boot. This
- // isn't necessary for correct operation under grub, but allows
- // us to query the next boot device easily.
- return g.SetBootVar(bootloaderRootfsVar, otherRootfs)
-}
-
func (g *grub) GetBootVar(name string) (value string, err error) {
// Grub doesn't provide a get verb, so retrieve all values and
// search for the required variable ourselves.
@@ -119,23 +97,6 @@ func (g *grub) SetBootVar(name, value string) (err error) {
return runCommand(bootloaderGrubEnvCmd, bootloaderGrubEnvFile(), "set", arg)
}
-func (g *grub) GetNextBootRootFSName() (label string, err error) {
- return g.GetBootVar(bootloaderRootfsVar)
-}
-
-func (g *grub) MarkCurrentBootSuccessful(currentRootfs string) (err error) {
- // Clear the variable set on boot to denote a good boot.
- if err := g.SetBootVar(bootloaderTrialBootVar, "0"); err != nil {
- return err
- }
-
- if err := g.SetBootVar(bootloaderRootfsVar, currentRootfs); err != nil {
- return err
- }
-
- return g.SetBootVar(bootloaderBootmodeVar, bootloaderBootmodeSuccess)
-}
-
func (g *grub) BootDir() string {
return bootloaderGrubDir()
}
diff --git a/partition/bootloader_grub_test.go b/partition/bootloader_grub_test.go
index 3ed27f2a90..ddfdc7f502 100644
--- a/partition/bootloader_grub_test.go
+++ b/partition/bootloader_grub_test.go
@@ -23,10 +23,8 @@ import (
"fmt"
"io/ioutil"
"os"
- "path/filepath"
"github.com/ubuntu-core/snappy/dirs"
- "github.com/ubuntu-core/snappy/helpers"
. "gopkg.in/check.v1"
)
@@ -36,7 +34,7 @@ func mockGrubFile(c *C, newPath string, mode os.FileMode) {
c.Assert(err, IsNil)
}
-func (s *PartitionTestSuite) makeFakeGrubEnv(c *C) {
+func (s *BootloaderTestSuite) makeFakeGrubEnv(c *C) {
// create bootloader
err := os.MkdirAll(bootloaderGrubDir(), 0755)
c.Assert(err, IsNil)
@@ -49,19 +47,17 @@ func (s *PartitionTestSuite) makeFakeGrubEnv(c *C) {
runCommand = mockRunCommandWithCapture
}
-func (s *PartitionTestSuite) TestNewGrubNoGrubReturnsNil(c *C) {
+func (s *BootloaderTestSuite) TestNewGrubNoGrubReturnsNil(c *C) {
dirs.GlobalRootDir = "/something/not/there"
- partition := New()
- g := newGrub(partition)
+ g := newGrub()
c.Assert(g, IsNil)
}
-func (s *PartitionTestSuite) TestNewGrub(c *C) {
+func (s *BootloaderTestSuite) TestNewGrub(c *C) {
s.makeFakeGrubEnv(c)
- partition := New()
- g := newGrub(partition)
+ g := newGrub()
c.Assert(g, NotNil)
c.Assert(g.Name(), Equals, bootloaderNameGrub)
}
@@ -75,109 +71,25 @@ func mockRunCommandWithCapture(args ...string) (err error) {
return nil
}
-func (s *PartitionTestSuite) TestToggleRootFS(c *C) {
- s.makeFakeGrubEnv(c)
- allCommands = []singleCommand{}
-
- partition := New()
- g := newGrub(partition)
- c.Assert(g, NotNil)
- err := g.ToggleRootFS("b")
- c.Assert(err, IsNil)
-
- // this is always called
- mp := singleCommand{"/bin/mountpoint", mountTarget}
- c.Assert(allCommands[0], DeepEquals, mp)
-
- expectedGrubSet := singleCommand{bootloaderGrubEnvCmd, bootloaderGrubEnvFile(), "set", "snappy_mode=try"}
- c.Assert(allCommands[1], DeepEquals, expectedGrubSet)
-
- // the https://developer.ubuntu.com/en/snappy/porting guide says
- // we always use the short names
- expectedGrubSet = singleCommand{bootloaderGrubEnvCmd, bootloaderGrubEnvFile(), "set", "snappy_ab=b"}
- c.Assert(allCommands[2], DeepEquals, expectedGrubSet)
-
- c.Assert(len(allCommands), Equals, 3)
-}
-
func mockGrubEditenvList(cmd ...string) (string, error) {
mockGrubEditenvOutput := fmt.Sprintf("%s=regular", bootloaderBootmodeVar)
return mockGrubEditenvOutput, nil
}
-func (s *PartitionTestSuite) TestGetBootVer(c *C) {
+func (s *BootloaderTestSuite) TestGetBootVer(c *C) {
s.makeFakeGrubEnv(c)
runCommandWithStdout = mockGrubEditenvList
- partition := New()
- g := newGrub(partition)
-
+ g := newGrub()
v, err := g.GetBootVar(bootloaderBootmodeVar)
c.Assert(err, IsNil)
c.Assert(v, Equals, "regular")
}
-func (s *PartitionTestSuite) TestGetBootloaderWithGrub(c *C) {
+func (s *BootloaderTestSuite) TestGetBootloaderWithGrub(c *C) {
s.makeFakeGrubEnv(c)
- p := New()
- bootloader, err := bootloader(p)
- c.Assert(err, IsNil)
- c.Assert(bootloader.Name(), Equals, bootloaderNameGrub)
-}
-
-func (s *PartitionTestSuite) TestGrubMarkCurrentBootSuccessful(c *C) {
- s.makeFakeGrubEnv(c)
- allCommands = []singleCommand{}
-
- partition := New()
- g := newGrub(partition)
- c.Assert(g, NotNil)
- err := g.MarkCurrentBootSuccessful("a")
- c.Assert(err, IsNil)
-
- // this is always called
- mp := singleCommand{"/bin/mountpoint", mountTarget}
- c.Assert(allCommands[0], DeepEquals, mp)
-
- expectedGrubSet := singleCommand{bootloaderGrubEnvCmd, bootloaderGrubEnvFile(), "set", "snappy_trial_boot=0"}
-
- c.Assert(allCommands[1], DeepEquals, expectedGrubSet)
-
- expectedGrubSet2 := singleCommand{bootloaderGrubEnvCmd, bootloaderGrubEnvFile(), "set", "snappy_ab=a"}
-
- c.Assert(allCommands[2], DeepEquals, expectedGrubSet2)
-
- expectedGrubSet3 := singleCommand{bootloaderGrubEnvCmd, bootloaderGrubEnvFile(), "set", "snappy_mode=regular"}
-
- c.Assert(allCommands[3], DeepEquals, expectedGrubSet3)
-
-}
-func (s *PartitionTestSuite) TestSyncBootFilesWithAssets(c *C) {
- err := os.MkdirAll(bootloaderGrubDir(), 0755)
+ bootloader, err := bootloader()
c.Assert(err, IsNil)
-
- runCommand = mockRunCommand
- b := grub{
- bootloaderType{
- currentBootPath: c.MkDir(),
- otherBootPath: c.MkDir(),
- bootloaderDir: c.MkDir(),
- },
- }
-
- bootfile := filepath.Join(c.MkDir(), "bootfile")
- err = ioutil.WriteFile(bootfile, []byte(bootfile), 0644)
- c.Assert(err, IsNil)
-
- bootassets := map[string]string{
- bootfile: filepath.Base(bootfile),
- }
-
- err = b.SyncBootFiles(bootassets)
- c.Assert(err, IsNil)
-
- dst := filepath.Join(b.bootloaderDir, bootassets[bootfile])
- c.Check(helpers.FileExists(dst), Equals, true)
- c.Check(helpers.FilesAreEqual(bootfile, dst), Equals, true)
+ c.Assert(bootloader.Name(), Equals, bootloaderNameGrub)
}
diff --git a/partition/bootloader_public.go b/partition/bootloader_public.go
index ccc282dec7..9ba8d82365 100644
--- a/partition/bootloader_public.go
+++ b/partition/bootloader_public.go
@@ -20,7 +20,7 @@
package partition
import (
- "fmt"
+ "strings"
"github.com/ubuntu-core/snappy/helpers"
)
@@ -46,11 +46,7 @@ func BootloaderDir() string {
// SetBootVar sets the given boot variable.
func SetBootVar(key, val string) error {
- p := New()
- if p == nil {
- return fmt.Errorf("cannot set %s boot variable: cannot find partition", key)
- }
- b, err := bootloader(p)
+ b, err := bootloader()
if err != nil {
return err
}
@@ -60,14 +56,47 @@ func SetBootVar(key, val string) error {
// GetBootVar returns the value of the given boot variable.
func GetBootVar(key string) (string, error) {
- p := New()
- if p == nil {
- return "", fmt.Errorf("cannot get %s boot variable: cannot find partition", key)
- }
- b, err := bootloader(p)
+ b, err := bootloader()
if err != nil {
return "", err
}
return b.GetBootVar(key)
}
+
+// MarkBootSuccessful marks the current boot success
+func MarkBootSuccessful() error {
+ bootloader, err := bootloader()
+ if err != nil {
+ return err
+ }
+
+ // FIXME: we should have something better here, i.e. one write
+ // to the bootloader environment only (instead of three)
+ // We need to figure out if that is possible with grub/uboot
+ // (if we could also do atomic writes to the boot env, that would
+ // be even better)
+ for _, k := range []string{"snappy_os", "snappy_kernel"} {
+ value, err := bootloader.GetBootVar(k)
+ if err != nil {
+ return err
+ }
+
+ // FIXME: ugly string replace
+ newKey := strings.Replace(k, "snappy_", "snappy_good_", -1)
+ if err := bootloader.SetBootVar(newKey, value); err != nil {
+ return err
+ }
+
+ if err := bootloader.SetBootVar("snappy_mode", "regular"); err != nil {
+ return err
+ }
+
+ if err := bootloader.SetBootVar("snappy_trial_boot", "0"); err != nil {
+ return err
+ }
+
+ }
+
+ return nil
+}
diff --git a/partition/bootloader_test.go b/partition/bootloader_test.go
index 4d32cc9ef6..0ee307f94d 100644
--- a/partition/bootloader_test.go
+++ b/partition/bootloader_test.go
@@ -20,12 +20,64 @@
package partition
import (
+ "testing"
+
+ "github.com/ubuntu-core/snappy/dirs"
+
. "gopkg.in/check.v1"
)
-func (s *PartitionTestSuite) TestNormalizeAssetsName(c *C) {
- c.Check(normalizeKernelInitrdName("subdir/vmlinuz-3.14"), Equals, "vmlinuz")
- c.Check(normalizeKernelInitrdName("vmlinuz-3.14"), Equals, "vmlinuz")
- c.Check(normalizeKernelInitrdName("initrd.img-2.71"), Equals, "initrd.img")
- c.Check(normalizeKernelInitrdName("x-y-z"), Equals, "x")
+// Hook up check.v1 into the "go test" runner
+func Test(t *testing.T) { TestingT(t) }
+
+type BootloaderTestSuite struct {
+}
+
+var _ = Suite(&BootloaderTestSuite{})
+
+type mockBootloader struct {
+ BootVars map[string]string
+}
+
+func newMockBootloader() *mockBootloader {
+ return &mockBootloader{
+ BootVars: make(map[string]string),
+ }
+}
+func (b *mockBootloader) Name() bootloaderName {
+ return "mocky"
+}
+func (b *mockBootloader) GetBootVar(name string) (string, error) {
+ return b.BootVars[name], nil
+}
+func (b *mockBootloader) SetBootVar(name, value string) error {
+ b.BootVars[name] = value
+ return nil
+}
+func (b *mockBootloader) BootDir() string {
+ return ""
+}
+
+func (s *BootloaderTestSuite) SetUpTest(c *C) {
+ dirs.SetRootDir(c.MkDir())
+}
+
+func (s *BootloaderTestSuite) TestMarkBootSuccessfulAllSnap(c *C) {
+ b := newMockBootloader()
+ bootloader = func() (bootLoader, error) {
+ return b, nil
+ }
+
+ b.BootVars["snappy_os"] = "os1"
+ b.BootVars["snappy_kernel"] = "k1"
+ err := MarkBootSuccessful()
+ c.Assert(err, IsNil)
+ c.Assert(b.BootVars, DeepEquals, map[string]string{
+ "snappy_mode": "regular",
+ "snappy_trial_boot": "0",
+ "snappy_kernel": "k1",
+ "snappy_good_kernel": "k1",
+ "snappy_os": "os1",
+ "snappy_good_os": "os1",
+ })
}
diff --git a/partition/bootloader_uboot.go b/partition/bootloader_uboot.go
index 1ed008ea9e..5975f7c43a 100644
--- a/partition/bootloader_uboot.go
+++ b/partition/bootloader_uboot.go
@@ -59,8 +59,6 @@ var (
const bootloaderNameUboot bootloaderName = "u-boot"
type uboot struct {
- bootloaderType
-
// set to true if the legacy uboot environemnt text file
// needs to be used
useLegacy bool
@@ -94,17 +92,12 @@ func bootloaderUbootFwEnvFile() string {
}
// newUboot create a new Uboot bootloader object
-func newUboot(partition *Partition) bootLoader {
+func newUboot() bootLoader {
if !helpers.FileExists(bootloaderUbootConfigFile()) {
return nil
}
- b := newBootLoader(partition, bootloaderUbootDir())
- if b == nil {
- return nil
- }
- u := uboot{bootloaderType: *b}
-
+ u := uboot{}
if !helpers.FileExists(bootloaderUbootFwEnvFile()) {
u.useLegacy = true
}
@@ -116,14 +109,6 @@ func (u *uboot) Name() bootloaderName {
return bootloaderNameUboot
}
-func (u *uboot) ToggleRootFS(otherRootfs string) (err error) {
- if err := u.SetBootVar(bootloaderRootfsVar, string(otherRootfs)); err != nil {
- return err
- }
-
- return u.SetBootVar(bootloaderBootmodeVar, bootloaderBootmodeTry)
-}
-
func getBootVarLegacy(name string) (value string, err error) {
cfg := goconfigparser.New()
cfg.AllowNoSectionHeader = true
@@ -190,36 +175,6 @@ func (u *uboot) SetBootVar(name, value string) error {
return setBootVarFwEnv(name, value)
}
-func (u *uboot) GetNextBootRootFSName() (label string, err error) {
- value, err := u.GetBootVar(bootloaderRootfsVar)
- if err != nil {
- // should never happen
- return "", err
- }
-
- return value, nil
-}
-
-// FIXME: this is super similar to grub now, refactor to extract the
-// common code
-func (u *uboot) MarkCurrentBootSuccessful(currentRootfs string) error {
- // Clear the variable set on boot to denote a good boot.
- if err := u.SetBootVar(bootloaderTrialBootVar, "0"); err != nil {
- return err
- }
-
- if err := u.SetBootVar(bootloaderRootfsVar, currentRootfs); err != nil {
- return err
- }
-
- if err := u.SetBootVar(bootloaderBootmodeVar, bootloaderBootmodeSuccess); err != nil {
- return err
- }
-
- // legacy support, does not error if the file is not there
- return os.RemoveAll(bootloaderUbootStampFile())
-}
-
func (u *uboot) BootDir() string {
return bootloaderUbootDir()
}
diff --git a/partition/bootloader_uboot_test.go b/partition/bootloader_uboot_test.go
index 24c1f23764..c16d7c7b72 100644
--- a/partition/bootloader_uboot_test.go
+++ b/partition/bootloader_uboot_test.go
@@ -22,18 +22,13 @@ package partition
import (
"io/ioutil"
"os"
- "path/filepath"
- "strings"
"time"
- "github.com/mvo5/uboot-go/uenv"
. "gopkg.in/check.v1"
- "github.com/ubuntu-core/snappy/helpers"
+ "github.com/mvo5/uboot-go/uenv"
)
-// TODO move to uboot specific test suite.
-
const fakeUbootEnvData = `
# This is a snappy variables and boot logic file and is entirely generated and
# managed by Snappy. Modifications may break boot
@@ -66,7 +61,7 @@ snappy_trial_boot=0
snappy_boot=if test "${snappy_mode}" = "try"; then if test -e mmc ${bootpart} ${snappy_stamp}; then if test "${snappy_ab}" = "a"; then setenv snappy_ab "b"; else setenv snappy_ab "a"; fi; else fatwrite mmc ${mmcdev}:${mmcpart} 0x0 ${snappy_stamp} 0; fi; fi; run loadfiles; setenv mmcroot /dev/disk/by-label/system-${snappy_ab} ${snappy_cmdline}; run mmcargs; bootz ${loadaddr} ${initrd_addr}:${initrd_size} ${fdtaddr}
`
-func (s *PartitionTestSuite) makeFakeUbootEnv(c *C) {
+func (s *BootloaderTestSuite) makeFakeUbootEnv(c *C) {
err := os.MkdirAll(bootloaderUbootDir(), 0755)
c.Assert(err, IsNil)
@@ -79,60 +74,34 @@ func (s *PartitionTestSuite) makeFakeUbootEnv(c *C) {
c.Assert(err, IsNil)
}
-func (s *PartitionTestSuite) TestNewUbootNoUbootReturnsNil(c *C) {
- partition := New()
- u := newUboot(partition)
+func (s *BootloaderTestSuite) TestNewUbootNoUbootReturnsNil(c *C) {
+ u := newUboot()
c.Assert(u, IsNil)
}
-func (s *PartitionTestSuite) TestNewUboot(c *C) {
+func (s *BootloaderTestSuite) TestNewUboot(c *C) {
s.makeFakeUbootEnv(c)
- partition := New()
- u := newUboot(partition)
+ u := newUboot()
c.Assert(u, NotNil)
c.Assert(u.Name(), Equals, bootloaderNameUboot)
}
-func (s *PartitionTestSuite) TestUbootGetBootVar(c *C) {
+func (s *BootloaderTestSuite) TestUbootGetBootVar(c *C) {
s.makeFakeUbootEnv(c)
- partition := New()
- u := newUboot(partition)
-
+ u := newUboot()
nextBoot, err := u.GetBootVar(bootloaderRootfsVar)
c.Assert(err, IsNil)
// the https://developer.ubuntu.com/en/snappy/porting guide says
// we always use the short names
c.Assert(nextBoot, Equals, "a")
-
- // ensure that nextBootIsOther works too
- c.Assert(partition.IsNextBootOther(), Equals, false)
-}
-
-func (s *PartitionTestSuite) TestUbootToggleRootFS(c *C) {
- s.makeFakeUbootEnv(c)
-
- partition := New()
- u := newUboot(partition)
- c.Assert(u, NotNil)
-
- err := u.ToggleRootFS("b")
- c.Assert(err, IsNil)
-
- nextBoot, err := u.GetBootVar(bootloaderRootfsVar)
- c.Assert(err, IsNil)
- c.Assert(nextBoot, Equals, "b")
-
- // ensure that nextBootIsOther works too
- c.Assert(partition.IsNextBootOther(), Equals, true)
}
-func (s *PartitionTestSuite) TestUbootGetEnvVar(c *C) {
+func (s *BootloaderTestSuite) TestUbootGetEnvVar(c *C) {
s.makeFakeUbootEnv(c)
- partition := New()
- u := newUboot(partition)
+ u := newUboot()
c.Assert(u, NotNil)
v, err := u.GetBootVar(bootloaderBootmodeVar)
@@ -144,216 +113,15 @@ func (s *PartitionTestSuite) TestUbootGetEnvVar(c *C) {
c.Assert(v, Equals, "a")
}
-func (s *PartitionTestSuite) TestGetBootloaderWithUboot(c *C) {
+func (s *BootloaderTestSuite) TestGetBootloaderWithUboot(c *C) {
s.makeFakeUbootEnv(c)
- p := New()
- bootloader, err := bootloader(p)
- c.Assert(err, IsNil)
- c.Assert(bootloader.Name(), Equals, bootloaderNameUboot)
-}
-
-func makeMockAssetsDir(c *C) {
- for _, f := range []string{"assets/vmlinuz", "assets/initrd.img", "assets/dtbs/foo.dtb", "assets/dtbs/bar.dtb"} {
- p := filepath.Join(cacheDir, f)
- os.MkdirAll(filepath.Dir(p), 0755)
- err := ioutil.WriteFile(p, []byte(f), 0644)
- c.Assert(err, IsNil)
- }
-}
-
-func (s *PartitionTestSuite) TestHandleAssets(c *C) {
- s.makeFakeUbootEnv(c)
- p := New()
- bootloader, err := bootloader(p)
- c.Assert(err, IsNil)
-
- // mock the hardwareYaml and the cacheDir
- hardwareSpecFile = makeHardwareYaml(c, "")
- cacheDir = c.MkDir()
-
- // create mock assets/
- makeMockAssetsDir(c)
- // run the handle assets code
- err = bootloader.HandleAssets()
+ bootloader, err := bootloader()
c.Assert(err, IsNil)
-
- // ensure the files are where we expect them
- otherBootPath := bootloader.(*uboot).otherBootPath
- for _, f := range []string{"vmlinuz", "initrd.img", "dtbs/foo.dtb", "dtbs/bar.dtb"} {
- content, err := ioutil.ReadFile(filepath.Join(otherBootPath, f))
- c.Assert(err, IsNil)
- // match content
- c.Assert(strings.HasSuffix(string(content), f), Equals, true)
- }
-
- // ensure nothing left behind
- c.Assert(helpers.FileExists(filepath.Join(cacheDir, "assets")), Equals, false)
- c.Assert(helpers.FileExists(hardwareSpecFile), Equals, false)
-}
-
-func (s *PartitionTestSuite) TestHandleAssetsVerifyBootloader(c *C) {
- s.makeFakeUbootEnv(c)
- p := New()
- bootloader, err := bootloader(p)
- c.Assert(err, IsNil)
-
- // mock the hardwareYaml and the cacheDir
- hardwareSpecFile = makeHardwareYaml(c, "bootloader: grub")
- cacheDir = c.MkDir()
-
- err = bootloader.HandleAssets()
- c.Assert(err, NotNil)
-}
-
-func (s *PartitionTestSuite) TestHandleAssetsFailVerifyPartitionLayout(c *C) {
- s.makeFakeUbootEnv(c)
- p := New()
- bootloader, err := bootloader(p)
- c.Assert(err, IsNil)
-
- // mock the hardwareYaml and the cacheDir
- hardwareSpecFile = makeHardwareYaml(c, `
-bootloader: u-boot
-partition-layout: inplace
-`)
- err = bootloader.HandleAssets()
- c.Assert(err, NotNil)
-}
-
-func (s *PartitionTestSuite) TestHandleAssetsNoHardwareYaml(c *C) {
- s.makeFakeUbootEnv(c)
- hardwareSpecFile = filepath.Join(c.MkDir(), "non-existent.yaml")
-
- p := New()
- bootloader, err := bootloader(p)
- c.Assert(err, IsNil)
-
- c.Assert(bootloader.HandleAssets(), IsNil)
-}
-
-func (s *PartitionTestSuite) TestHandleAssetsBadHardwareYaml(c *C) {
- s.makeFakeUbootEnv(c)
- p := New()
- bootloader, err := bootloader(p)
- c.Assert(err, IsNil)
-
- hardwareSpecFile = makeHardwareYaml(c, `
-bootloader u-boot
-`)
-
- c.Assert(bootloader.HandleAssets(), NotNil)
-}
-
-func (s *PartitionTestSuite) TestUbootMarkCurrentBootSuccessful(c *C) {
- s.makeFakeUbootEnv(c)
-
- // To simulate what uboot does for a "try" mode boot, create a
- // stamp file. uboot will expect this file to be removed by
- // "snappy booted" if the system boots successfully. If this
- // file exists when uboot starts, it will know that the previous
- // boot failed, and will therefore toggle to the other rootfs.
- err := ioutil.WriteFile(bootloaderUbootStampFile(), []byte(""), 0640)
- c.Assert(err, IsNil)
- c.Assert(helpers.FileExists(bootloaderUbootStampFile()), Equals, true)
-
- partition := New()
- u := newUboot(partition)
- c.Assert(u, NotNil)
-
- // enter "try" mode so that we check to ensure that snappy
- // correctly modifies the snappy_mode variable from "try" to
- // "regular" to denote a good boot.
- err = u.ToggleRootFS("b")
- c.Assert(err, IsNil)
-
- c.Assert(helpers.FileExists(bootloaderUbootEnvFile()), Equals, true)
- bytes, err := ioutil.ReadFile(bootloaderUbootEnvFile())
- c.Assert(err, IsNil)
- c.Assert(strings.Contains(string(bytes), "snappy_mode=try"), Equals, true)
- c.Assert(strings.Contains(string(bytes), "snappy_mode=regular"), Equals, false)
- c.Assert(strings.Contains(string(bytes), "snappy_ab=b"), Equals, true)
-
- err = u.MarkCurrentBootSuccessful("b")
- c.Assert(err, IsNil)
-
- c.Assert(helpers.FileExists(bootloaderUbootStampFile()), Equals, false)
- c.Assert(helpers.FileExists(bootloaderUbootEnvFile()), Equals, true)
-
- bytes, err = ioutil.ReadFile(bootloaderUbootEnvFile())
- c.Assert(err, IsNil)
- c.Assert(strings.Contains(string(bytes), "snappy_mode=try"), Equals, false)
- c.Assert(strings.Contains(string(bytes), "snappy_mode=regular"), Equals, true)
- c.Assert(strings.Contains(string(bytes), "snappy_ab=b"), Equals, true)
-}
-
-func (s *PartitionTestSuite) TestNoWriteNotNeeded(c *C) {
- s.makeFakeUbootEnv(c)
-
- atomiCall := false
- atomicWriteFile = func(a string, b []byte, c os.FileMode, f helpers.AtomicWriteFlags) error {
- atomiCall = true
- return helpers.AtomicWriteFile(a, b, c, f)
- }
-
- partition := New()
- u := newUboot(partition)
- c.Assert(u, NotNil)
-
- c.Check(u.MarkCurrentBootSuccessful("a"), IsNil)
- c.Assert(atomiCall, Equals, false)
-}
-
-func (s *PartitionTestSuite) TestWriteDueToMissingValues(c *C) {
- s.makeFakeUbootEnv(c)
-
- // this file needs specific data
- c.Assert(ioutil.WriteFile(bootloaderUbootEnvFile(), []byte(""), 0644), IsNil)
-
- atomiCall := false
- atomicWriteFile = func(a string, b []byte, c os.FileMode, f helpers.AtomicWriteFlags) error {
- atomiCall = true
- return helpers.AtomicWriteFile(a, b, c, f)
- }
-
- partition := New()
- u := newUboot(partition)
- c.Assert(u, NotNil)
-
- c.Check(u.MarkCurrentBootSuccessful("a"), IsNil)
- c.Assert(atomiCall, Equals, true)
-
- bytes, err := ioutil.ReadFile(bootloaderUbootEnvFile())
- c.Assert(err, IsNil)
- c.Check(strings.Contains(string(bytes), "snappy_mode=try"), Equals, false)
- c.Check(strings.Contains(string(bytes), "snappy_mode=regular"), Equals, true)
- c.Check(strings.Contains(string(bytes), "snappy_ab=a"), Equals, true)
-}
-
-func (s *PartitionTestSuite) TestUbootMarkCurrentBootSuccessfulFwEnv(c *C) {
- s.makeFakeUbootEnv(c)
-
- env, err := uenv.Create(bootloaderUbootFwEnvFile(), 4096)
- c.Assert(err, IsNil)
- env.Set("snappy_ab", "b")
- env.Set("snappy_mode", "try")
- env.Set("snappy_trial_boot", "1")
- err = env.Save()
- c.Assert(err, IsNil)
-
- partition := New()
- u := newUboot(partition)
- c.Assert(u, NotNil)
-
- err = u.MarkCurrentBootSuccessful("b")
- c.Assert(err, IsNil)
-
- env, err = uenv.Open(bootloaderUbootFwEnvFile())
- c.Assert(err, IsNil)
- c.Assert(env.String(), Equals, "snappy_ab=b\nsnappy_mode=regular\nsnappy_trial_boot=0\n")
+ c.Assert(bootloader.Name(), Equals, bootloaderNameUboot)
}
-func (s *PartitionTestSuite) TestUbootSetEnvNoUselessWrites(c *C) {
+func (s *BootloaderTestSuite) TestUbootSetEnvNoUselessWrites(c *C) {
s.makeFakeUbootEnv(c)
env, err := uenv.Create(bootloaderUbootFwEnvFile(), 4096)
@@ -367,8 +135,7 @@ func (s *PartitionTestSuite) TestUbootSetEnvNoUselessWrites(c *C) {
c.Assert(err, IsNil)
time.Sleep(100 * time.Millisecond)
- partition := New()
- u := newUboot(partition)
+ u := newUboot()
c.Assert(u, NotNil)
// note that we set to the same var as above
@@ -384,11 +151,10 @@ func (s *PartitionTestSuite) TestUbootSetEnvNoUselessWrites(c *C) {
c.Assert(st.ModTime(), Equals, st2.ModTime())
}
-func (s *PartitionTestSuite) TestUbootSetBootVarLegacy(c *C) {
+func (s *BootloaderTestSuite) TestUbootSetBootVarLegacy(c *C) {
s.makeFakeUbootEnv(c)
- partition := New()
- u := newUboot(partition)
+ u := newUboot()
c.Assert(u, NotNil)
content, err := getBootVarLegacy(bootloaderRootfsVar)
@@ -401,7 +167,7 @@ func (s *PartitionTestSuite) TestUbootSetBootVarLegacy(c *C) {
c.Assert(content, Equals, "b")
}
-func (s *PartitionTestSuite) TestUbootSetBootVarFwEnv(c *C) {
+func (s *BootloaderTestSuite) TestUbootSetBootVarFwEnv(c *C) {
s.makeFakeUbootEnv(c)
env, err := uenv.Create(bootloaderUbootFwEnvFile(), 4096)
c.Assert(err, IsNil)
@@ -411,14 +177,13 @@ func (s *PartitionTestSuite) TestUbootSetBootVarFwEnv(c *C) {
err = setBootVarFwEnv("key", "value")
c.Assert(err, IsNil)
- partition := New()
- u := newUboot(partition)
+ u := newUboot()
content, err := u.GetBootVar("key")
c.Assert(err, IsNil)
c.Assert(content, Equals, "value")
}
-func (s *PartitionTestSuite) TestUbootGetBootVarFwEnv(c *C) {
+func (s *BootloaderTestSuite) TestUbootGetBootVarFwEnv(c *C) {
s.makeFakeUbootEnv(c)
env, err := uenv.Create(bootloaderUbootFwEnvFile(), 4096)
c.Assert(err, IsNil)
@@ -426,8 +191,7 @@ func (s *PartitionTestSuite) TestUbootGetBootVarFwEnv(c *C) {
err = env.Save()
c.Assert(err, IsNil)
- partition := New()
- u := newUboot(partition)
+ u := newUboot()
content, err := u.GetBootVar("key2")
c.Assert(err, IsNil)
c.Assert(content, Equals, "value2")
diff --git a/partition/migrate_grub.go b/partition/migrate_grub.go
deleted file mode 100644
index c8c0e91655..0000000000
--- a/partition/migrate_grub.go
+++ /dev/null
@@ -1,153 +0,0 @@
-// -*- Mode: Go; indent-tabs-mode: t -*-
-
-/*
- * Copyright (C) 2014-2015 Canonical Ltd
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 3 as
- * published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
- */
-
-package partition
-
-import (
- "fmt"
- "io/ioutil"
- "os"
- "path/filepath"
- "strings"
-
- "github.com/ubuntu-core/snappy/helpers"
- "github.com/ubuntu-core/snappy/logger"
-)
-
-const newGrubConfig string = `
-set default=0
-set timeout=3
-
-insmod part_gpt
-insmod ext2
-
-if [ -s $prefix/grubenv ]; then
- load_env
-fi
-
-if [ -z "$snappy_mode" ]; then
- set snappy_mode=regular
- save_env snappy_mode
-fi
-if [ -z "$snappy_ab" ]; then
- set snappy_ab=a
- save_env snappy_ab
-fi
-
-if [ "$snappy_mode" = "try" ]; then
- if [ "$snappy_trial_boot" = "1" ]; then
- # Previous boot failed to unset snappy_trial_boot, so toggle
- # rootfs.
- if [ "$snappy_ab" = "a" ]; then
- set snappy_ab=b
- else
- set snappy_ab=a
- fi
- save_env snappy_ab
- else
- # Trial mode so set the snappy_trial_boot (which snappy is
- # expected to unset).
- #
- # Note: don't use the standard recordfail variable since that forces
- # the menu to be displayed and sets an infinite timeout if set.
- set snappy_trial_boot=1
- save_env snappy_trial_boot
- fi
-fi
-
-set label="system-$snappy_ab"
-set cmdline="root=LABEL=$label ro init=/lib/systemd/systemd console=ttyS0 console=tty1 panic=-1"
-
-menuentry "$label" {
- if [ -e "$prefix/$snappy_ab/vmlinuz" ]; then
- linux $prefix/$snappy_ab/vmlinuz $cmdline
- initrd $prefix/$snappy_ab/initrd.img
- else
- # old-style kernel-in-os-partition
- search --no-floppy --set --label "$label"
- linux /vmlinuz $cmdline
- initrd /initrd.img
- fi
-}
-`
-
-var oldGrubConfigHeader = `#
-# DO NOT EDIT THIS FILE
-#
-`
-
-func isOldGrubConfig(grubConf string) bool {
- return strings.HasPrefix(grubConf, oldGrubConfigHeader)
-}
-
-func copyKernelAssets(prefixDir, grubTargetDir string) error {
- for _, p := range []string{"/boot/vmlinuz-*", "/boot/initrd.img-*"} {
- matches, err := filepath.Glob(filepath.Join(prefixDir, p))
- if err != nil {
- return err
- }
- if len(matches) != 1 {
- return fmt.Errorf("Incorrect matches for %v: %v", p, matches)
- }
- name := normalizeKernelInitrdName(filepath.Base(matches[0]))
- targetPath := filepath.Join(bootloaderGrubDir(), grubTargetDir, name)
- os.MkdirAll(filepath.Dir(targetPath), 0755)
- // FIXME: valid?
- if helpers.FileExists(targetPath) {
- continue
- }
- if err := helpers.CopyFile(matches[0], targetPath, 0); err != nil {
- return err
- }
- logger.Noticef("Copied file %v -> %v", matches[0], targetPath)
- }
-
- return nil
-}
-
-// MigrateToDynamicGrub rearranges things to work with the old,
-// dynamic grub setup. Needed for when you rollback over the switch to
-// static grub.
-func MigrateToDynamicGrub() error {
- grubConfigRaw, err := ioutil.ReadFile(bootloaderGrubConfigFile())
- if err != nil && !os.IsNotExist(err) {
- return err
- }
-
- grubConfig := string(grubConfigRaw)
- if !isOldGrubConfig(grubConfig) {
- // nothing to do
- return nil
- }
-
- part := New()
- // first copy current kernel/initrd to /boot/grub/$current (a or b)
- if err := copyKernelAssets("/", part.rootPartition().shortName); err != nil {
- return err
- }
-
- // then copy other kernel/initrd to /boot/grub/$other (a or b)
- if helpers.FileExists("/writable/cache/system/boot") {
- if err := copyKernelAssets("/writable/cache/system/", part.otherRootPartition().shortName); err != nil {
- return err
- }
- }
-
- return helpers.AtomicWriteFile(bootloaderGrubConfigFile(), []byte(newGrubConfig), 0644, 0)
-}
diff --git a/partition/migrate_grub_test.go b/partition/migrate_grub_test.go
deleted file mode 100644
index 0661a802a1..0000000000
--- a/partition/migrate_grub_test.go
+++ /dev/null
@@ -1,58 +0,0 @@
-// -*- Mode: Go; indent-tabs-mode: t -*-
-
-/*
- * Copyright (C) 2014-2015 Canonical Ltd
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 3 as
- * published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
- */
-
-package partition
-
-import (
- "bytes"
- "io/ioutil"
-
- . "gopkg.in/check.v1"
-)
-
-const oldConfigHeader string = `#
-# DO NOT EDIT THIS FILE
-#
-# It is automatically generated by grub-mkconfig using templates
-# from /etc/grub.d and settings from /etc/default/grub
-#
-
-### BEGIN /etc/grub.d/00_header ###
-if [ -s $prefix/grubenv ]; then
- set have_grubenv=true
- load_env
-fi
-if [ "${next_entry}" ] ; then
-`
-
-func (s *PartitionTestSuite) TestMigrateDetectsOldConfig(c *C) {
- err := ioutil.WriteFile(bootloaderGrubConfigFile(), []byte(oldConfigHeader), 0644)
- c.Assert(err, IsNil)
-
- r := bytes.NewBufferString(oldConfigHeader)
- c.Assert(isOldGrubConfig(r.String()), Equals, true)
-}
-
-func (s *PartitionTestSuite) TestMigrateNotMisdetects(c *C) {
- err := ioutil.WriteFile(bootloaderGrubConfigFile(), []byte(newGrubConfig), 0644)
- c.Assert(err, IsNil)
-
- r := bytes.NewBufferString(oldConfigHeader)
- c.Assert(isOldGrubConfig(r.String()), Equals, true)
-}
diff --git a/partition/mount.go b/partition/mount.go
deleted file mode 100644
index 6847b13c8c..0000000000
--- a/partition/mount.go
+++ /dev/null
@@ -1,153 +0,0 @@
-// -*- Mode: Go; indent-tabs-mode: t -*-
-
-/*
- * Copyright (C) 2014-2015 Canonical Ltd
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 3 as
- * published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
- */
-
-package partition
-
-import (
- "fmt"
- "sort"
-)
-
-// MountOption represents how the partition should be mounted, currently
-// RO (read-only) and RW (read-write) are supported
-type MountOption int
-
-const (
- // RO mounts the partition read-only
- RO MountOption = iota
- // RW mounts the partition read-only
- RW
-)
-
-// mountEntry represents a mount this package has created.
-type mountEntry struct {
- source string
- target string
-
- options string
-
- // true if target refers to a bind mount. We could derive this
- // from options, but this field saves the effort.
- bindMount bool
-}
-
-// mountEntryArray represents an array of mountEntry objects.
-type mountEntryArray []mountEntry
-
-// current mounts that this package has created.
-var mounts mountEntryArray
-
-// Len is part of the sort interface, required to allow sort to work
-// with an array of Mount objects.
-func (mounts mountEntryArray) Len() int {
- return len(mounts)
-}
-
-// Less is part of the sort interface, required to allow sort to work
-// with an array of Mount objects.
-func (mounts mountEntryArray) Less(i, j int) bool {
- return mounts[i].target < mounts[j].target
-}
-
-// Swap is part of the sort interface, required to allow sort to work
-// with an array of Mount objects.
-func (mounts mountEntryArray) Swap(i, j int) {
- mounts[i], mounts[j] = mounts[j], mounts[i]
-}
-
-// removeMountByTarget removes the Mount specified by the target from
-// the global mounts array.
-func removeMountByTarget(mnts mountEntryArray, target string) (results mountEntryArray) {
-
- for _, m := range mnts {
- if m.target != target {
- results = append(results, m)
- }
- }
-
- return results
-}
-
-// undoMounts unmounts all mounts this package has mounted optionally
-// only unmounting bind mounts and leaving all remaining mounts.
-func undoMounts(bindMountsOnly bool) error {
-
- mountsCopy := make(mountEntryArray, len(mounts), cap(mounts))
- copy(mountsCopy, mounts)
-
- // reverse sort to ensure unmounts are handled in the correct
- // order.
- sort.Sort(sort.Reverse(mountsCopy))
-
- // Iterate backwards since we want a reverse-sorted list of
- // mounts to ensure we can unmount in order.
- for _, mount := range mountsCopy {
- if bindMountsOnly && !mount.bindMount {
- continue
- }
-
- if err := unmountAndRemoveFromGlobalMountList(mount.target); err != nil {
- return err
- }
- }
-
- return nil
-}
-
-// FIXME: use syscall.Mount() here
-func mount(source, target, options string) (err error) {
- var args []string
-
- args = append(args, "/bin/mount")
- if options != "" {
- args = append(args, fmt.Sprintf("-o%s", options))
- }
-
- args = append(args, source)
- args = append(args, target)
-
- return runCommand(args...)
-}
-
-// Mount the given directory and add it to the global mounts slice
-func mountAndAddToGlobalMountList(m mountEntry) (err error) {
-
- err = mount(m.source, m.target, m.options)
- if err == nil {
- mounts = append(mounts, m)
- }
-
- return err
-}
-
-// Unmount the given directory and remove it from the global "mounts" slice
-func unmountAndRemoveFromGlobalMountList(target string) (err error) {
- err = runCommand("/bin/umount", target)
- if err != nil {
- return err
-
- }
-
- results := removeMountByTarget(mounts, target)
-
- // Update global
- mounts = results
-
- return nil
-}
diff --git a/partition/mount_test.go b/partition/mount_test.go
deleted file mode 100644
index de13e54e38..0000000000
--- a/partition/mount_test.go
+++ /dev/null
@@ -1,64 +0,0 @@
-// -*- Mode: Go; indent-tabs-mode: t -*-
-
-/*
- * Copyright (C) 2014-2015 Canonical Ltd
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 3 as
- * published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
- */
-
-package partition
-
-import (
- . "gopkg.in/check.v1"
-)
-
-func (s *PartitionTestSuite) TestMountEntryArray(c *C) {
- mea := mountEntryArray{}
-
- c.Assert(mea.Len(), Equals, 0)
-
- me := mountEntry{source: "/dev",
- target: "/dev",
- options: "bind",
- bindMount: true}
-
- mea = append(mea, me)
- c.Assert(mea.Len(), Equals, 1)
-
- me = mountEntry{source: "/foo",
- target: "/foo",
- options: "",
- bindMount: false}
-
- mea = append(mea, me)
- c.Assert(mea.Len(), Equals, 2)
-
- c.Assert(mea.Less(0, 1), Equals, true)
- c.Assert(mea.Less(1, 0), Equals, false)
-
- mea.Swap(0, 1)
- c.Assert(mea.Less(0, 1), Equals, false)
- c.Assert(mea.Less(1, 0), Equals, true)
-
- results := removeMountByTarget(mea, "invalid")
-
- // No change expected
- c.Assert(results, DeepEquals, mea)
-
- results = removeMountByTarget(mea, "/dev")
-
- c.Assert(len(results), Equals, 1)
- c.Assert(results[0], Equals, mountEntry{source: "/foo",
- target: "/foo", options: "", bindMount: false})
-}
diff --git a/partition/partition.go b/partition/partition.go
index 42d339e885..c0e1eaa83a 100644
--- a/partition/partition.go
+++ b/partition/partition.go
@@ -22,16 +22,6 @@ package partition
import (
"errors"
- "fmt"
- "os"
- "os/signal"
- "path/filepath"
- "regexp"
- "strings"
- "sync"
- "syscall"
-
- "github.com/ubuntu-core/snappy/logger"
)
const (
@@ -67,600 +57,3 @@ var (
// partition feature on a single partition
ErrNoDualPartition = errors.New("No dual partition")
)
-
-// Interface provides the interface to interact with a partition
-type Interface interface {
- ToggleNextBoot() error
-
- MarkBootSuccessful() error
- // FIXME: could we make SyncBootloaderFiles part of ToogleBootloader
- // to expose even less implementation details?
- SyncBootloaderFiles(bootAssets map[string]string) error
- IsNextBootOther() bool
-
- // run the function f with the otherRoot mounted
- RunWithOther(rw MountOption, f func(otherRoot string) (err error)) (err error)
-}
-
-// Partition is the type to interact with the partition
-type Partition struct {
- // all partitions
- partitions []blockDevice
-
- // just root partitions
- roots []string
-}
-
-type blockDevice struct {
- // label for partition
- name string
-
- // the last char of the partition label
- shortName string
-
- // full path to device on which partition exists
- // (for example "/dev/sda3")
- device string
-
- // full path to disk device (for example "/dev/sda")
- parentName string
-
- // mountpoint (or nil if not mounted)
- mountpoint string
-}
-
-var once sync.Once
-
-func init() {
- once.Do(setupSignalHandler)
-}
-
-func signalHandler(sig os.Signal) {
- err := undoMounts(false)
- if err != nil {
- logger.Noticef("Failed to unmount: %v", err)
- }
-}
-
-func setupSignalHandler() {
- ch := make(chan os.Signal, 1)
-
- // add the signals we care about
- signal.Notify(ch, os.Interrupt)
- signal.Notify(ch, syscall.SIGTERM)
-
- go func() {
- // block waiting for a signal
- sig := <-ch
-
- // handle it
- signalHandler(sig)
- os.Exit(1)
- }()
-}
-
-// Returns a list of root filesystem partition labels
-func rootPartitionLabels() []string {
- return []string{rootfsAlabel, rootfsBlabel}
-}
-
-// Returns a list of all recognised partition labels
-func allPartitionLabels() []string {
- var labels []string
-
- labels = rootPartitionLabels()
- labels = append(labels, bootPartitionLabel)
- labels = append(labels, writablePartitionLabel)
-
- return labels
-}
-
-var runLsblk = func() (out []string, err error) {
- output, err := runCommandWithStdout(
- "/bin/lsblk",
- "--ascii",
- "--output=NAME,LABEL,PKNAME,MOUNTPOINT",
- "--pairs")
- if err != nil {
- return out, err
- }
-
- return strings.Split(output, "\n"), nil
-}
-
-// Determine details of the recognised disk partitions
-// available on the system via lsblk
-func loadPartitionDetails() (partitions []blockDevice, err error) {
- recognised := allPartitionLabels()
-
- lines, err := runLsblk()
- if err != nil {
- return partitions, err
- }
- pattern := regexp.MustCompile(`(?:[^\s"]|"(?:[^"])*")+`)
-
- for _, line := range lines {
- fields := make(map[string]string)
-
- // split the line into 'NAME="quoted value"' fields
- matches := pattern.FindAllString(line, -1)
-
- for _, match := range matches {
- tmp := strings.Split(match, "=")
- name := tmp[0]
-
- // remove quotes
- value := strings.Trim(tmp[1], "\"")
-
- // store
- fields[name] = value
- }
-
- // Look for expected partition labels
- name, ok := fields["LABEL"]
- if !ok {
- continue
- }
-
- 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
- }
- */
- shortName := string(name[len(name)-1])
- bd := blockDevice{
- name: fields["LABEL"],
- shortName: shortName,
- device: device,
- mountpoint: fields["MOUNTPOINT"],
- parentName: disk,
- }
-
- partitions = append(partitions, bd)
- }
-
- return partitions, nil
-}
-
-// New creates a new partition type
-func New() *Partition {
- p := new(Partition)
-
- p.getPartitionDetails()
-
- return p
-}
-
-// RunWithOther mount the other rootfs partition, execute the
-// specified function and unmount "other" before returning. If "other"
-// is mounted read-write, /proc, /sys and /dev will also be
-// bind-mounted at the time the specified function is called.
-func (p *Partition) RunWithOther(option MountOption, f func(otherRoot string) (err error)) (err error) {
- dual := p.dualRootPartitions()
-
- if !dual {
- return ErrNoDualPartition
- }
-
- if option == RW {
- if err := p.remountOther(RW); err != nil {
- return err
- }
-
- defer func() {
- // we can't reuse err here as this will override
- // the error value we got from calling "f()"
- derr := p.remountOther(RO)
- if derr != nil && err == nil {
- err = derr
- }
- }()
-
- if err := p.bindmountRequiredFilesystems(); err != nil {
- return err
- }
-
- defer func() {
- // we can't reuse err here as this will override
- // the error value we got from calling "f()"
- derr := p.unmountRequiredFilesystems()
- if derr != nil && err == nil {
- err = derr
- }
- }()
- }
-
- err = f(mountTarget)
- return err
-}
-
-// SyncBootloaderFiles syncs the bootloader files
-//
-// We need this code solve the following scenario:
-//
-// 1. start with: /boot/a/k1, /boot/b/k1
-// 2. upgrade with new kernel k2: /boot/a/k1, /boot/b/k2
-// 3. reboot into b, running with /boot/b/k2
-// 4. new update without a changed kernel, system-a updated
-//
-// 6. reboot to system-a with /boot/a/k1 (WRONG!)
-// But it should be system-a with /boot/a/k2 it was just not part of
-// the s-i delta as we already got it. So as step (5) above we do the
-// SyncBootloaderFiles that copies /boot/b/k2 -> /boot/a/
-// (and that is ok because we know /boot/b/k2 works)
-//
-func (p *Partition) SyncBootloaderFiles(bootAssets map[string]string) (err error) {
- bootloader, err := bootloader(p)
- if err != nil {
- return err
- }
-
- return bootloader.SyncBootFiles(bootAssets)
-}
-
-// ToggleNextBoot toggles the roofs that should be used on the next boot
-func (p *Partition) ToggleNextBoot() (err error) {
- if p.dualRootPartitions() {
- return p.toggleBootloaderRootfs()
- }
- return err
-}
-
-// MarkBootSuccessful marks the boot as successful
-func (p *Partition) MarkBootSuccessful() error {
- if p.rootPartition() != nil {
- return p.markBootSuccessfulSnappyAB()
- }
-
- return p.markBootSuccessfulAllSnaps()
-}
-
-func (p *Partition) markBootSuccessfulSnappyAB() error {
- bootloader, err := bootloader(p)
- if err != nil {
- return err
- }
-
- currentRootfs := p.rootPartition().shortName
- return bootloader.MarkCurrentBootSuccessful(currentRootfs)
-}
-
-func (p *Partition) markBootSuccessfulAllSnaps() error {
- bootloader, err := bootloader(p)
- if err != nil {
- return err
- }
-
- // FIXME: we should have something better here, i.e. one write
- // to the bootloader environment only (instead of three)
- // We need to figure out if that is possible with grub/uboot
- // (if we could also do atomic writes to the boot env, that would
- // be even better)
- for _, k := range []string{"snappy_os", "snappy_kernel"} {
- value, err := bootloader.GetBootVar(k)
- if err != nil {
- return err
- }
-
- // FIXME: ugly string replace
- newKey := strings.Replace(k, "snappy_", "snappy_good_", -1)
- if err := bootloader.SetBootVar(newKey, value); err != nil {
- return err
- }
-
- if err := bootloader.SetBootVar("snappy_mode", "regular"); err != nil {
- return err
- }
-
- if err := bootloader.SetBootVar("snappy_trial_boot", "0"); err != nil {
- return err
- }
-
- }
-
- return nil
-}
-
-// IsNextBootOther return true if the next boot will use the other rootfs
-// partition.
-func (p *Partition) IsNextBootOther() bool {
- bootloader, err := bootloader(p)
- if err != nil {
- return false
- }
-
- value, err := bootloader.GetBootVar(bootloaderBootmodeVar)
- if err != nil {
- return false
- }
-
- if value != bootloaderBootmodeTry {
- return false
- }
-
- fsname, err := bootloader.GetNextBootRootFSName()
- if err != nil {
- return false
- }
-
- otherRootfs := p.otherRootPartition().shortName
- if fsname == otherRootfs {
- return true
- }
-
- return false
-}
-
-func (p *Partition) getPartitionDetails() (err error) {
- p.partitions, err = loadPartitionDetails()
- if err != nil {
- return err
- }
-
- if !p.dualRootPartitions() && !p.singleRootPartition() {
- return ErrPartitionDetection
- }
-
- if p.dualRootPartitions() {
- // XXX: this will soon be handled automatically at boot by
- // initramfs-tools-ubuntu-core.
- return p.ensureOtherMountedRO()
- }
-
- return err
-}
-
-// Return array of blockDevices representing available root partitions
-func (p *Partition) rootPartitions() (roots []blockDevice) {
- for _, part := range p.partitions {
- pos := stringInSlice(rootPartitionLabels(), part.name)
- if pos >= 0 {
- roots = append(roots, part)
- }
- }
-
- return roots
-}
-
-// Return true if system has dual root partitions configured in the
-// expected manner for a snappy system.
-func (p *Partition) dualRootPartitions() bool {
- return len(p.rootPartitions()) == 2
-}
-
-// Return true if system has a single root partition configured in the
-// expected manner for a snappy system.
-func (p *Partition) singleRootPartition() bool {
- return len(p.rootPartitions()) == 1
-}
-
-// Return pointer to blockDevice representing writable partition
-func (p *Partition) writablePartition() (result *blockDevice) {
- for _, part := range p.partitions {
- if part.name == writablePartitionLabel {
- return &part
- }
- }
-
- return nil
-}
-
-// Return pointer to blockDevice representing boot partition (if any)
-func (p *Partition) bootPartition() (result *blockDevice) {
- for _, part := range p.partitions {
- if part.name == bootPartitionLabel {
- return &part
- }
- }
-
- return nil
-}
-
-// Return pointer to blockDevice representing currently mounted root
-// filesystem
-func (p *Partition) rootPartition() (result *blockDevice) {
- for _, part := range p.rootPartitions() {
- if part.mountpoint == "/" {
- return &part
- }
- }
-
- return nil
-}
-
-// Return pointer to blockDevice representing the "other" root
-// filesystem (which is not currently mounted)
-func (p *Partition) otherRootPartition() (result *blockDevice) {
- for _, part := range p.rootPartitions() {
- if part.mountpoint != "/" {
- return &part
- }
- }
-
- return nil
-}
-
-// Mount the "other" root filesystem
-func (p *Partition) mountOtherRootfs(readOnly bool) (err error) {
- var other *blockDevice
-
- if err := os.MkdirAll(mountTarget, dirMode); err != nil {
- return err
- }
-
- other = p.otherRootPartition()
-
- m := mountEntry{source: other.device, target: mountTarget}
-
- if readOnly {
- m.options = "ro"
- err = mountAndAddToGlobalMountList(m)
- } else {
- err = fsck(m.source)
- if err != nil {
- return err
- }
- err = mountAndAddToGlobalMountList(m)
- }
-
- return err
-}
-
-// Create a read-only bindmount of the currently-mounted rootfs at the
-// specified mountpoint location (which must already exist).
-func (p *Partition) bindmountThisRootfsRO(target string) (err error) {
- return mountAndAddToGlobalMountList(mountEntry{source: "/",
- target: target,
- options: "bind,ro",
- bindMount: true})
-}
-
-// Ensure the other partition is mounted read-only.
-func (p *Partition) ensureOtherMountedRO() (err error) {
- if err = runCommand("/bin/mountpoint", mountTarget); err == nil {
- // already mounted
- return err
- }
-
- return p.mountOtherRootfs(true)
-}
-
-// Remount the already-mounted other partition. Whether the mount
-// should become writable is specified by the writable argument.
-//
-// XXX: Note that in the case where writable=true, this isn't a simple
-// toggle - if the partition is already mounted read-only, it needs to
-// be unmounted, fsck(8)'d, then (re-)mounted read-write.
-func (p *Partition) remountOther(option MountOption) (err error) {
- other := p.otherRootPartition()
-
- if option == RW {
- // r/o -> r/w: initially r/o, so no need to fsck before
- // switching to r/w.
- err = p.unmountOtherRootfs()
- if err != nil {
- return err
- }
-
- err = fsck(other.device)
- if err != nil {
- return err
- }
-
- return mountAndAddToGlobalMountList(mountEntry{
- source: other.device,
- target: mountTarget})
- }
- // r/w -> r/o: no fsck required.
- return mount(other.device, mountTarget, "remount,ro")
-}
-
-func (p *Partition) unmountOtherRootfs() (err error) {
- return unmountAndRemoveFromGlobalMountList(mountTarget)
-}
-
-// The bootloader requires a few filesystems to be mounted when
-// run from within a chroot.
-func (p *Partition) bindmountRequiredFilesystems() (err error) {
-
- // 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 := filepath.Join(mountTarget, fs)
-
- err := mountAndAddToGlobalMountList(mountEntry{source: fs,
- target: target,
- options: "bind",
- bindMount: true})
- if err != nil {
- return err
- }
- }
-
- // Grub also requires access to both rootfs's when run from
- // within a chroot (to allow it to create menu entries for
- // both), so bindmount the real rootfs.
- targetInChroot := filepath.Join(mountTarget, mountTarget)
-
- // FIXME: we should really remove this after the unmount
-
- if err = os.MkdirAll(targetInChroot, dirMode); err != nil {
- return err
- }
-
- return p.bindmountThisRootfsRO(targetInChroot)
-}
-
-// Undo the effects of BindmountRequiredFilesystems()
-func (p *Partition) unmountRequiredFilesystems() (err error) {
- if err = undoMounts(true); err != nil {
- return err
- }
-
- return nil
-}
-
-func (p *Partition) toggleBootloaderRootfs() (err error) {
-
- if !p.dualRootPartitions() {
- return errors.New("System is not dual root")
- }
-
- bootloader, err := bootloader(p)
- if err != nil {
- return err
- }
-
- // ensure we have updated kernels etc
- if err := bootloader.HandleAssets(); err != nil {
- return err
- }
-
- otherRootfs := p.otherRootPartition().shortName
- return bootloader.ToggleRootFS(otherRootfs)
-}
-
-// BootloaderDir returns the full path to the (mounted and writable)
-// bootloader-specific boot directory.
-func (p *Partition) BootloaderDir() string {
- bootloader, err := bootloader(p)
- if err != nil {
- return ""
- }
-
- return bootloader.BootDir()
-}
diff --git a/partition/partition_test.go b/partition/partition_test.go
deleted file mode 100644
index 17dda118c9..0000000000
--- a/partition/partition_test.go
+++ /dev/null
@@ -1,483 +0,0 @@
-// -*- Mode: Go; indent-tabs-mode: t -*-
-
-/*
- * Copyright (C) 2014-2015 Canonical Ltd
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 3 as
- * published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
- */
-
-package partition
-
-import (
- "errors"
- "io/ioutil"
- "os"
- "strings"
- "testing"
-
- . "gopkg.in/check.v1"
-
- "github.com/ubuntu-core/snappy/dirs"
-)
-
-// Hook up check.v1 into the "go test" runner
-func Test(t *testing.T) { TestingT(t) }
-
-// partition specific testsuite
-type PartitionTestSuite struct {
- tempdir string
-}
-
-var _ = Suite(&PartitionTestSuite{})
-
-func mockRunCommand(args ...string) (err error) {
- return err
-}
-
-func (s *PartitionTestSuite) SetUpTest(c *C) {
- s.tempdir = c.MkDir()
- runLsblk = mockRunLsblkDualSnappy
-
- // custom mount target
- mountTarget = c.MkDir()
-
- // global roto
- dirs.SetRootDir(s.tempdir)
- err := os.MkdirAll(bootloaderGrubDir(), 0755)
- c.Assert(err, IsNil)
- err = os.MkdirAll(bootloaderUbootDir(), 0755)
- c.Assert(err, IsNil)
-
- c.Assert(mounts, DeepEquals, mountEntryArray(nil))
-}
-
-func (s *PartitionTestSuite) TearDownTest(c *C) {
- os.RemoveAll(s.tempdir)
-
- // always restore what we might have mocked away
- runCommand = runCommandImpl
- bootloader = bootloaderImpl
- cacheDir = cacheDirReal
- hardwareSpecFile = hardwareSpecFileReal
- mountTarget = mountTargetReal
-
- c.Assert(mounts, DeepEquals, mountEntryArray(nil))
-}
-
-func makeHardwareYaml(c *C, hardwareYaml string) (outPath string) {
- tmp, err := ioutil.TempFile(c.MkDir(), "hw-")
- c.Assert(err, IsNil)
- defer tmp.Close()
-
- if hardwareYaml == "" {
- hardwareYaml = `
-kernel: assets/vmlinuz
-initrd: assets/initrd.img
-dtbs: assets/dtbs
-partition-layout: system-AB
-bootloader: u-boot
-`
- }
- _, err = tmp.Write([]byte(hardwareYaml))
- c.Assert(err, IsNil)
-
- return tmp.Name()
-}
-
-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) {
- p := New()
- c.Assert(p.dualRootPartitions(), Equals, true)
- c.Assert(p.singleRootPartition(), Equals, false)
-
- rootPartitions := p.rootPartitions()
- 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(wp.device, Equals, "/dev/sda5")
- c.Assert(wp.parentName, Equals, "/dev/sda")
-
- boot := p.bootPartition()
- c.Assert(boot.name, Equals, "system-boot")
- c.Assert(boot.device, Equals, "/dev/sda2")
- c.Assert(boot.parentName, Equals, "/dev/sda")
-
- root := p.rootPartition()
- c.Assert(root.name, Equals, "system-a")
- 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(other.device, Equals, "/dev/sda4")
- c.Assert(other.parentName, Equals, "/dev/sda")
-}
-
-func (s *PartitionTestSuite) TestRunWithOtherDualParitionRO(c *C) {
- p := New()
- reportedRoot := ""
- err := p.RunWithOther(RO, func(otherRoot string) (err error) {
- reportedRoot = otherRoot
- return nil
- })
- c.Assert(err, IsNil)
- c.Assert(reportedRoot, Equals, mountTarget)
-}
-
-func (s *PartitionTestSuite) TestRunWithOtherDualParitionRWFuncErr(c *C) {
- c.Assert(mounts, DeepEquals, mountEntryArray(nil))
-
- runCommand = mockRunCommand
-
- p := New()
- err := p.RunWithOther(RW, func(otherRoot string) (err error) {
- return errors.New("canary")
- })
-
- // ensure we actually got the right error
- c.Assert(err, NotNil)
- c.Assert(err.Error(), Equals, "canary")
-
- // ensure cleanup happend
-
- // FIXME: mounts are global
- expected := mountEntry{
- source: "/dev/sda4",
- target: mountTarget,
- options: "",
- bindMount: false,
- }
-
- // At program exit, "other" should still be mounted
- c.Assert(mounts, DeepEquals, mountEntryArray{expected})
-
- undoMounts(false)
-
- c.Assert(mounts, DeepEquals, mountEntryArray(nil))
-}
-
-func (s *PartitionTestSuite) TestRunWithOtherSingleParitionRO(c *C) {
- runLsblk = mockRunLsblkSingleRootSnappy
- p := New()
- err := p.RunWithOther(RO, func(otherRoot string) (err error) {
- return nil
- })
- c.Assert(err, Equals, ErrNoDualPartition)
-}
-
-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) {
- runLsblk = mockRunLsblkSingleRootSnappy
-
- p := New()
- c.Assert(p.dualRootPartitions(), Equals, false)
- c.Assert(p.singleRootPartition(), Equals, true)
-
- root := p.rootPartition()
- c.Assert(root.name, Equals, "system-a")
- c.Assert(root.device, Equals, "/dev/sda3")
- c.Assert(root.parentName, Equals, "/dev/sda")
-
- other := p.otherRootPartition()
- c.Assert(other, IsNil)
-
- rootPartitions := p.rootPartitions()
- c.Assert(&rootPartitions[0], DeepEquals, root)
-}
-
-func (s *PartitionTestSuite) TestMountUnmountTracking(c *C) {
- runCommand = mockRunCommand
-
- p := New()
- c.Assert(p, NotNil)
-
- p.mountOtherRootfs(false)
- expected := mountEntry{
- source: "/dev/sda4",
- target: mountTarget,
- options: "",
- bindMount: false,
- }
-
- c.Assert(mounts, DeepEquals, mountEntryArray{expected})
-
- p.unmountOtherRootfs()
- c.Assert(mounts, DeepEquals, mountEntryArray(nil))
-}
-
-func (s *PartitionTestSuite) TestUnmountRequiredFilesystems(c *C) {
- runCommand = mockRunCommand
- s.makeFakeGrubEnv(c)
-
- p := New()
- c.Assert(c, NotNil)
-
- p.bindmountRequiredFilesystems()
- c.Assert(mounts, DeepEquals, mountEntryArray{
- mountEntry{source: "/dev", target: mountTarget + "/dev",
- options: "bind", bindMount: true},
-
- mountEntry{source: "/proc", target: mountTarget + "/proc",
- options: "bind", bindMount: true},
-
- mountEntry{source: "/sys", target: mountTarget + "/sys",
- options: "bind", bindMount: true},
- mountEntry{source: "/boot/efi", target: mountTarget + "/boot/efi",
- options: "bind", bindMount: true},
-
- // Required to allow grub inside the chroot to access
- // the "current" rootfs outside the chroot (used
- // to generate the grub menuitems).
- mountEntry{source: "/",
- target: mountTarget + mountTarget,
- options: "bind,ro", bindMount: true},
- })
- p.unmountRequiredFilesystems()
- c.Assert(mounts, DeepEquals, mountEntryArray(nil))
-}
-
-func (s *PartitionTestSuite) TestUndoMounts(c *C) {
- runCommand = mockRunCommand
-
- p := New()
- c.Assert(c, NotNil)
-
- err := p.remountOther(RW)
- c.Assert(err, IsNil)
-
- p.bindmountRequiredFilesystems()
- c.Assert(mounts, DeepEquals, mountEntryArray{
-
- mountEntry{source: "/dev/sda4", target: mountTarget,
- options: "", bindMount: false},
-
- mountEntry{source: "/dev", target: mountTarget + "/dev",
- options: "bind", bindMount: true},
-
- mountEntry{source: "/proc", target: mountTarget + "/proc",
- options: "bind", bindMount: true},
-
- mountEntry{source: "/sys", target: mountTarget + "/sys",
- options: "bind", bindMount: true},
-
- mountEntry{source: "/boot/efi", target: mountTarget + "/boot/efi",
- options: "bind", bindMount: true},
-
- mountEntry{source: "/",
- target: mountTarget + mountTarget,
- options: "bind,ro", bindMount: true},
- })
-
- // should leave non-bind mounts
- undoMounts(true)
-
- c.Assert(mounts, DeepEquals, mountEntryArray{
- mountEntry{
- source: "/dev/sda4",
- target: mountTarget,
- options: "",
- bindMount: false,
- },
- })
-
- // should unmount everything
- undoMounts(false)
-
- c.Assert(mounts, DeepEquals, mountEntryArray(nil))
-}
-
-func mockRunLsblkNoSnappy() (output []string, err error) {
- dualData := `
-NAME="sda" LABEL="" PKNAME="" MOUNTPOINT=""
-NAME="sda1" LABEL="meep" PKNAME="sda" MOUNTPOINT="/"
-NAME="sr0" LABEL="" PKNAME="" MOUNTPOINT=""
-`
- return strings.Split(dualData, "\n"), err
-}
-
-func (s *PartitionTestSuite) TestSnappyNoSnappyPartitions(c *C) {
- runLsblk = mockRunLsblkNoSnappy
-
- p := New()
- err := p.getPartitionDetails()
- c.Assert(err, Equals, ErrPartitionDetection)
-
- c.Assert(p.dualRootPartitions(), Equals, false)
- c.Assert(p.singleRootPartition(), Equals, false)
-
- c.Assert(p.rootPartition(), IsNil)
- c.Assert(p.bootPartition(), IsNil)
- c.Assert(p.writablePartition(), IsNil)
- c.Assert(p.otherRootPartition(), IsNil)
-}
-
-// mock bootloader for the tests
-type mockBootloader struct {
- ToggleRootFSCalled bool
- HandleAssetsCalled bool
- MarkCurrentBootSuccessfulCalled bool
- SyncBootFilesCalled bool
- BootVars map[string]string
-}
-
-func newMockBootloader() *mockBootloader {
- return &mockBootloader{
- BootVars: make(map[string]string),
- }
-}
-
-func (b *mockBootloader) Name() bootloaderName {
- return "mocky"
-}
-func (b *mockBootloader) ToggleRootFS(otherRootfs string) error {
- b.ToggleRootFSCalled = true
- return nil
-}
-func (b *mockBootloader) SyncBootFiles(bootAssets map[string]string) error {
- b.SyncBootFilesCalled = true
- return nil
-}
-func (b *mockBootloader) HandleAssets() error {
- b.HandleAssetsCalled = true
- return nil
-}
-func (b *mockBootloader) GetBootVar(name string) (string, error) {
- return b.BootVars[name], nil
-}
-func (b *mockBootloader) SetBootVar(name, value string) error {
- b.BootVars[name] = value
- return nil
-}
-func (b *mockBootloader) GetNextBootRootFSName() (string, error) {
- return "", nil
-}
-func (b *mockBootloader) MarkCurrentBootSuccessful(currentRootfs string) error {
- b.MarkCurrentBootSuccessfulCalled = true
- return nil
-}
-func (b *mockBootloader) BootDir() string {
- return ""
-}
-
-func (s *PartitionTestSuite) TestToggleBootloaderRootfs(c *C) {
- runCommand = mockRunCommand
- b := newMockBootloader()
- bootloader = func(p *Partition) (bootLoader, error) {
- return b, nil
- }
-
- p := New()
- c.Assert(c, NotNil)
-
- err := p.toggleBootloaderRootfs()
- c.Assert(err, IsNil)
- c.Assert(b.ToggleRootFSCalled, Equals, true)
- c.Assert(b.HandleAssetsCalled, Equals, true)
-
- p.unmountOtherRootfs()
- c.Assert(mounts, DeepEquals, mountEntryArray(nil))
-}
-
-func (s *PartitionTestSuite) TestMarkBootSuccessful(c *C) {
- runCommand = mockRunCommand
- b := newMockBootloader()
- bootloader = func(p *Partition) (bootLoader, error) {
- return b, nil
- }
-
- p := New()
- c.Assert(c, NotNil)
-
- err := p.MarkBootSuccessful()
- c.Assert(err, IsNil)
- c.Assert(b.MarkCurrentBootSuccessfulCalled, Equals, true)
-}
-
-func (s *PartitionTestSuite) TestSyncBootFiles(c *C) {
- runCommand = mockRunCommand
- b := newMockBootloader()
- bootloader = func(p *Partition) (bootLoader, error) {
- return b, nil
- }
-
- p := New()
- c.Assert(c, NotNil)
-
- err := p.SyncBootloaderFiles(nil)
- c.Assert(err, IsNil)
- c.Assert(b.SyncBootFilesCalled, Equals, true)
-}
-
-func mockRunLsblkAllSnap() (output []string, err error) {
- allSnapData := `
-NAME="sda" LABEL="" PKNAME="" MOUNTPOINT=""
-NAME="sda1" LABEL="" PKNAME="sda" MOUNTPOINT=""
-NAME="sda2" LABEL="system-boot" PKNAME="sda" MOUNTPOINT="/boot/efi"
-NAME="sda5" LABEL="writable" PKNAME="sda" MOUNTPOINT="/writable"
-NAME="loop0" LABEL="" PKNAME="" MOUNTPOINT="/"
-`
- return strings.Split(allSnapData, "\n"), err
-}
-
-func (s *PartitionTestSuite) TestMarkBootSuccessfulAllSnap(c *C) {
- runCommand = mockRunCommand
- b := newMockBootloader()
- bootloader = func(p *Partition) (bootLoader, error) {
- return b, nil
- }
- runLsblk = mockRunLsblkAllSnap
-
- p := New()
- c.Assert(c, NotNil)
-
- b.BootVars["snappy_os"] = "os1"
- b.BootVars["snappy_kernel"] = "k1"
- err := p.MarkBootSuccessful()
- c.Assert(err, IsNil)
- c.Assert(b.BootVars, DeepEquals, map[string]string{
- "snappy_mode": "regular",
- "snappy_trial_boot": "0",
- "snappy_kernel": "k1",
- "snappy_good_kernel": "k1",
- "snappy_os": "os1",
- "snappy_good_os": "os1",
- })
-}