github.com/kubiko/snapd@v0.0.0-20201013125620-d4f3094d9ddf/bootloader/bootloader_test.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2014-2015 Canonical Ltd
     5   *
     6   * This program is free software: you can redistribute it and/or modify
     7   * it under the terms of the GNU General Public License version 3 as
     8   * published by the Free Software Foundation.
     9   *
    10   * This program is distributed in the hope that it will be useful,
    11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
    12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    13   * GNU General Public License for more details.
    14   *
    15   * You should have received a copy of the GNU General Public License
    16   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
    17   *
    18   */
    19  
    20  package bootloader_test
    21  
    22  import (
    23  	"errors"
    24  	"io/ioutil"
    25  	"os"
    26  	"path/filepath"
    27  	"testing"
    28  
    29  	. "gopkg.in/check.v1"
    30  
    31  	"github.com/snapcore/snapd/bootloader"
    32  	"github.com/snapcore/snapd/bootloader/assets"
    33  	"github.com/snapcore/snapd/bootloader/bootloadertest"
    34  	"github.com/snapcore/snapd/snap"
    35  	"github.com/snapcore/snapd/testutil"
    36  )
    37  
    38  // Hook up check.v1 into the "go test" runner
    39  func Test(t *testing.T) { TestingT(t) }
    40  
    41  const packageKernel = `
    42  name: ubuntu-kernel
    43  version: 4.0-1
    44  type: kernel
    45  vendor: Someone
    46  `
    47  
    48  type baseBootenvTestSuite struct {
    49  	testutil.BaseTest
    50  
    51  	rootdir string
    52  }
    53  
    54  func (s *baseBootenvTestSuite) SetUpTest(c *C) {
    55  	s.BaseTest.SetUpTest(c)
    56  	s.AddCleanup(snap.MockSanitizePlugsSlots(func(snapInfo *snap.Info) {}))
    57  	s.rootdir = c.MkDir()
    58  }
    59  
    60  type bootenvTestSuite struct {
    61  	baseBootenvTestSuite
    62  
    63  	b *bootloadertest.MockBootloader
    64  }
    65  
    66  var _ = Suite(&bootenvTestSuite{})
    67  
    68  func (s *bootenvTestSuite) SetUpTest(c *C) {
    69  	s.baseBootenvTestSuite.SetUpTest(c)
    70  
    71  	s.b = bootloadertest.Mock("mocky", c.MkDir())
    72  }
    73  
    74  func (s *bootenvTestSuite) TestForceBootloader(c *C) {
    75  	bootloader.Force(s.b)
    76  	defer bootloader.Force(nil)
    77  
    78  	got, err := bootloader.Find("", nil)
    79  	c.Assert(err, IsNil)
    80  	c.Check(got, Equals, s.b)
    81  }
    82  
    83  func (s *bootenvTestSuite) TestForceBootloaderError(c *C) {
    84  	myErr := errors.New("zap")
    85  	bootloader.ForceError(myErr)
    86  	defer bootloader.ForceError(nil)
    87  
    88  	got, err := bootloader.Find("", nil)
    89  	c.Assert(err, Equals, myErr)
    90  	c.Check(got, IsNil)
    91  }
    92  
    93  func (s *bootenvTestSuite) TestInstallBootloaderConfigNoConfig(c *C) {
    94  	err := bootloader.InstallBootConfig(c.MkDir(), s.rootdir, nil)
    95  	c.Assert(err, ErrorMatches, `cannot find boot config in.*`)
    96  }
    97  
    98  func (s *bootenvTestSuite) TestInstallBootloaderConfigFromGadget(c *C) {
    99  	for _, t := range []struct {
   100  		name                string
   101  		gadgetFile, sysFile string
   102  		gadgetFileContent   []byte
   103  		opts                *bootloader.Options
   104  	}{
   105  		{name: "grub", gadgetFile: "grub.conf", sysFile: "/boot/grub/grub.cfg"},
   106  		// traditional uboot.env - the uboot.env file needs to be non-empty
   107  		{name: "uboot.env", gadgetFile: "uboot.conf", sysFile: "/boot/uboot/uboot.env", gadgetFileContent: []byte{1}},
   108  		// boot.scr in place of uboot.env means we create the boot.sel file
   109  		{
   110  			name:       "uboot boot.scr",
   111  			gadgetFile: "uboot.conf",
   112  			sysFile:    "/uboot/ubuntu/boot.sel",
   113  			opts:       &bootloader.Options{Role: bootloader.RoleRecovery},
   114  		},
   115  		{name: "androidboot", gadgetFile: "androidboot.conf", sysFile: "/boot/androidboot/androidboot.env"},
   116  		{name: "lk", gadgetFile: "lk.conf", sysFile: "/boot/lk/snapbootsel.bin"},
   117  	} {
   118  		mockGadgetDir := c.MkDir()
   119  		rootDir := c.MkDir()
   120  		err := ioutil.WriteFile(filepath.Join(mockGadgetDir, t.gadgetFile), t.gadgetFileContent, 0644)
   121  		c.Assert(err, IsNil)
   122  		err = bootloader.InstallBootConfig(mockGadgetDir, rootDir, t.opts)
   123  		c.Assert(err, IsNil, Commentf("installing boot config for %s", t.name))
   124  		fn := filepath.Join(rootDir, t.sysFile)
   125  		c.Assert(fn, testutil.FilePresent, Commentf("boot config missing for %s at %s", t.name, t.sysFile))
   126  	}
   127  }
   128  
   129  func (s *bootenvTestSuite) TestInstallBootloaderConfigFromAssets(c *C) {
   130  	recoveryOpts := &bootloader.Options{
   131  		Role: bootloader.RoleRecovery,
   132  	}
   133  	systemBootOpts := &bootloader.Options{
   134  		Role: bootloader.RoleRunMode,
   135  	}
   136  	defaultRecoveryGrubAsset := assets.Internal("grub-recovery.cfg")
   137  	c.Assert(defaultRecoveryGrubAsset, NotNil)
   138  	defaultGrubAsset := assets.Internal("grub.cfg")
   139  	c.Assert(defaultGrubAsset, NotNil)
   140  
   141  	for _, t := range []struct {
   142  		name                string
   143  		gadgetFile, sysFile string
   144  		gadgetFileContent   []byte
   145  		sysFileContent      []byte
   146  		assetContent        []byte
   147  		assetName           string
   148  		err                 string
   149  		opts                *bootloader.Options
   150  	}{
   151  		{
   152  			name:       "recovery grub",
   153  			opts:       recoveryOpts,
   154  			gadgetFile: "grub.conf",
   155  			// empty file in the gadget
   156  			gadgetFileContent: nil,
   157  			sysFile:           "/EFI/ubuntu/grub.cfg",
   158  			assetName:         "grub-recovery.cfg",
   159  			assetContent:      []byte("hello assets"),
   160  			// boot config from assets
   161  			sysFileContent: []byte("hello assets"),
   162  		}, {
   163  			name:              "recovery grub with non empty gadget file",
   164  			opts:              recoveryOpts,
   165  			gadgetFile:        "grub.conf",
   166  			gadgetFileContent: []byte("not so empty"),
   167  			sysFile:           "/EFI/ubuntu/grub.cfg",
   168  			assetName:         "grub-recovery.cfg",
   169  			assetContent:      []byte("hello assets"),
   170  			// boot config from assets
   171  			sysFileContent: []byte("hello assets"),
   172  		}, {
   173  			name:       "recovery grub with default asset",
   174  			opts:       recoveryOpts,
   175  			gadgetFile: "grub.conf",
   176  			// empty file in the gadget
   177  			gadgetFileContent: nil,
   178  			sysFile:           "/EFI/ubuntu/grub.cfg",
   179  			sysFileContent:    defaultRecoveryGrubAsset,
   180  		}, {
   181  			name:       "recovery grub missing asset",
   182  			opts:       recoveryOpts,
   183  			gadgetFile: "grub.conf",
   184  			// empty file in the gadget
   185  			gadgetFileContent: nil,
   186  			sysFile:           "/EFI/ubuntu/grub.cfg",
   187  			assetName:         "grub-recovery.cfg",
   188  			// no asset content
   189  			err: `internal error: no boot asset for "grub-recovery.cfg"`,
   190  		}, {
   191  			name:       "system-boot grub",
   192  			opts:       systemBootOpts,
   193  			gadgetFile: "grub.conf",
   194  			// empty file in the gadget
   195  			gadgetFileContent: nil,
   196  			sysFile:           "/EFI/ubuntu/grub.cfg",
   197  			assetName:         "grub.cfg",
   198  			assetContent:      []byte("hello assets"),
   199  			sysFileContent:    []byte("hello assets"),
   200  		}, {
   201  			name:       "system-boot grub with default asset",
   202  			opts:       systemBootOpts,
   203  			gadgetFile: "grub.conf",
   204  			// empty file in the gadget
   205  			gadgetFileContent: nil,
   206  			sysFile:           "/EFI/ubuntu/grub.cfg",
   207  			sysFileContent:    defaultGrubAsset,
   208  		},
   209  	} {
   210  		mockGadgetDir := c.MkDir()
   211  		rootDir := c.MkDir()
   212  		fn := filepath.Join(rootDir, t.sysFile)
   213  		err := ioutil.WriteFile(filepath.Join(mockGadgetDir, t.gadgetFile), t.gadgetFileContent, 0644)
   214  		c.Assert(err, IsNil)
   215  		var restoreAsset func()
   216  		if t.assetName != "" {
   217  			restoreAsset = assets.MockInternal(t.assetName, t.assetContent)
   218  		}
   219  		err = bootloader.InstallBootConfig(mockGadgetDir, rootDir, t.opts)
   220  		if t.err == "" {
   221  			c.Assert(err, IsNil, Commentf("installing boot config for %s", t.name))
   222  			// mocked asset content
   223  			c.Assert(fn, testutil.FileEquals, string(t.sysFileContent))
   224  		} else {
   225  			c.Assert(err, ErrorMatches, t.err)
   226  			c.Assert(fn, testutil.FileAbsent)
   227  		}
   228  		if restoreAsset != nil {
   229  			restoreAsset()
   230  		}
   231  	}
   232  }
   233  
   234  func (s *bootenvTestSuite) TestBootloaderFind(c *C) {
   235  	for _, tc := range []struct {
   236  		name    string
   237  		sysFile string
   238  		opts    *bootloader.Options
   239  		expName string
   240  	}{
   241  		{name: "grub", sysFile: "/boot/grub/grub.cfg", expName: "grub"},
   242  		{
   243  			// native run partition layout
   244  			name: "grub", sysFile: "/EFI/ubuntu/grub.cfg",
   245  			opts:    &bootloader.Options{Role: bootloader.RoleRunMode, NoSlashBoot: true},
   246  			expName: "grub",
   247  		},
   248  		{
   249  			// recovery layout
   250  			name: "grub", sysFile: "/EFI/ubuntu/grub.cfg",
   251  			opts:    &bootloader.Options{Role: bootloader.RoleRecovery},
   252  			expName: "grub",
   253  		},
   254  
   255  		// traditional uboot.env - the uboot.env file needs to be non-empty
   256  		{name: "uboot.env", sysFile: "/boot/uboot/uboot.env", expName: "uboot"},
   257  		// boot.sel variant
   258  		{
   259  			name:    "uboot boot.scr",
   260  			sysFile: "/uboot/ubuntu/boot.sel",
   261  			opts:    &bootloader.Options{Role: bootloader.RoleRunMode, NoSlashBoot: true},
   262  			expName: "uboot",
   263  		},
   264  		{name: "androidboot", sysFile: "/boot/androidboot/androidboot.env", expName: "androidboot"},
   265  		// lk is detected differently based on runtime/prepare-image
   266  		{name: "lk", sysFile: "/dev/disk/by-partlabel/snapbootsel", expName: "lk"},
   267  		{
   268  			name: "lk", sysFile: "/boot/lk/snapbootsel.bin",
   269  			expName: "lk", opts: &bootloader.Options{PrepareImageTime: true},
   270  		},
   271  	} {
   272  		c.Logf("tc: %v", tc.name)
   273  		rootDir := c.MkDir()
   274  		err := os.MkdirAll(filepath.Join(rootDir, filepath.Dir(tc.sysFile)), 0755)
   275  		c.Assert(err, IsNil)
   276  		err = ioutil.WriteFile(filepath.Join(rootDir, tc.sysFile), nil, 0644)
   277  		c.Assert(err, IsNil)
   278  		bl, err := bootloader.Find(rootDir, tc.opts)
   279  		c.Assert(err, IsNil)
   280  		c.Assert(bl, NotNil)
   281  		c.Check(bl.Name(), Equals, tc.expName)
   282  	}
   283  }
   284  
   285  func (s *bootenvTestSuite) TestBootloaderForGadget(c *C) {
   286  	for _, tc := range []struct {
   287  		name       string
   288  		gadgetFile string
   289  		opts       *bootloader.Options
   290  		expName    string
   291  	}{
   292  		{name: "grub", gadgetFile: "grub.conf", expName: "grub"},
   293  		{name: "grub", gadgetFile: "grub.conf", opts: &bootloader.Options{Role: bootloader.RoleRunMode, NoSlashBoot: true}, expName: "grub"},
   294  		{name: "grub", gadgetFile: "grub.conf", opts: &bootloader.Options{Role: bootloader.RoleRecovery}, expName: "grub"},
   295  		{name: "uboot", gadgetFile: "uboot.conf", expName: "uboot"},
   296  		{name: "androidboot", gadgetFile: "androidboot.conf", expName: "androidboot"},
   297  		{name: "lk", gadgetFile: "lk.conf", expName: "lk"},
   298  	} {
   299  		c.Logf("tc: %v", tc.name)
   300  		gadgetDir := c.MkDir()
   301  		rootDir := c.MkDir()
   302  		err := os.MkdirAll(filepath.Join(rootDir, filepath.Dir(tc.gadgetFile)), 0755)
   303  		c.Assert(err, IsNil)
   304  		err = ioutil.WriteFile(filepath.Join(gadgetDir, tc.gadgetFile), nil, 0644)
   305  		c.Assert(err, IsNil)
   306  		bl, err := bootloader.ForGadget(gadgetDir, rootDir, tc.opts)
   307  		c.Assert(err, IsNil)
   308  		c.Assert(bl, NotNil)
   309  		c.Check(bl.Name(), Equals, tc.expName)
   310  	}
   311  }
   312  
   313  func (s *bootenvTestSuite) TestBootFileWithPath(c *C) {
   314  	a := bootloader.NewBootFile("", "some/path", bootloader.RoleRunMode)
   315  	b := a.WithPath("other/path")
   316  	c.Assert(a.Path, Equals, "some/path")
   317  	c.Assert(b.Path, Equals, "other/path")
   318  }