diff options
| author | Michael Vogt <mvo@ubuntu.com> | 2015-11-20 14:23:47 +0100 |
|---|---|---|
| committer | Michael Vogt <mvo@ubuntu.com> | 2015-11-20 14:30:48 +0100 |
| commit | b1bcfc1e895658de6a5de380ab9bc297ddff8dac (patch) | |
| tree | 885bbb1f1c5f1ab76981300d820c4085965e1bb7 | |
| parent | 8a2da74bd7acb9c5dc738c477b67db4e81c352ee (diff) | |
Remove most of partition, we only need the bootvars for all-snapfeature/slash-and-burn
| -rw-r--r-- | cmd/snappy/cmd_booted.go | 3 | ||||
| -rw-r--r-- | cmd/snappy/cmd_grub_migrate.go | 46 | ||||
| -rw-r--r-- | partition/assets.go | 87 | ||||
| -rw-r--r-- | partition/assets_test.go | 36 | ||||
| -rw-r--r-- | partition/bootloader.go | 246 | ||||
| -rw-r--r-- | partition/bootloader_grub.go | 43 | ||||
| -rw-r--r-- | partition/bootloader_grub_test.go | 108 | ||||
| -rw-r--r-- | partition/bootloader_public.go | 51 | ||||
| -rw-r--r-- | partition/bootloader_test.go | 62 | ||||
| -rw-r--r-- | partition/bootloader_uboot.go | 49 | ||||
| -rw-r--r-- | partition/bootloader_uboot_test.go | 278 | ||||
| -rw-r--r-- | partition/migrate_grub.go | 153 | ||||
| -rw-r--r-- | partition/migrate_grub_test.go | 58 | ||||
| -rw-r--r-- | partition/mount.go | 153 | ||||
| -rw-r--r-- | partition/mount_test.go | 64 | ||||
| -rw-r--r-- | partition/partition.go | 607 | ||||
| -rw-r--r-- | partition/partition_test.go | 483 |
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", - }) -} |
