github.com/Lephar/snapd@v0.0.0-20210825215435-c7fba9cef4d2/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  	"fmt"
    25  	"io/ioutil"
    26  	"os"
    27  	"path/filepath"
    28  	"testing"
    29  
    30  	. "gopkg.in/check.v1"
    31  
    32  	"github.com/snapcore/snapd/bootloader"
    33  	"github.com/snapcore/snapd/bootloader/assets"
    34  	"github.com/snapcore/snapd/bootloader/bootloadertest"
    35  	"github.com/snapcore/snapd/dirs"
    36  	"github.com/snapcore/snapd/snap"
    37  	"github.com/snapcore/snapd/testutil"
    38  )
    39  
    40  // Hook up check.v1 into the "go test" runner
    41  func Test(t *testing.T) { TestingT(t) }
    42  
    43  const packageKernel = `
    44  name: ubuntu-kernel
    45  version: 4.0-1
    46  type: kernel
    47  vendor: Someone
    48  `
    49  
    50  type baseBootenvTestSuite struct {
    51  	testutil.BaseTest
    52  
    53  	rootdir string
    54  }
    55  
    56  func (s *baseBootenvTestSuite) SetUpTest(c *C) {
    57  	s.BaseTest.SetUpTest(c)
    58  	s.AddCleanup(snap.MockSanitizePlugsSlots(func(snapInfo *snap.Info) {}))
    59  	s.rootdir = c.MkDir()
    60  	dirs.SetRootDir(s.rootdir)
    61  	s.AddCleanup(func() { dirs.SetRootDir("") })
    62  }
    63  
    64  type bootenvTestSuite struct {
    65  	baseBootenvTestSuite
    66  
    67  	b *bootloadertest.MockBootloader
    68  }
    69  
    70  var _ = Suite(&bootenvTestSuite{})
    71  
    72  func (s *bootenvTestSuite) SetUpTest(c *C) {
    73  	s.baseBootenvTestSuite.SetUpTest(c)
    74  
    75  	s.b = bootloadertest.Mock("mocky", c.MkDir())
    76  }
    77  
    78  func (s *bootenvTestSuite) TestForceBootloader(c *C) {
    79  	bootloader.Force(s.b)
    80  	defer bootloader.Force(nil)
    81  
    82  	got, err := bootloader.Find("", nil)
    83  	c.Assert(err, IsNil)
    84  	c.Check(got, Equals, s.b)
    85  }
    86  
    87  func (s *bootenvTestSuite) TestForceBootloaderError(c *C) {
    88  	myErr := errors.New("zap")
    89  	bootloader.ForceError(myErr)
    90  	defer bootloader.ForceError(nil)
    91  
    92  	got, err := bootloader.Find("", nil)
    93  	c.Assert(err, Equals, myErr)
    94  	c.Check(got, IsNil)
    95  }
    96  
    97  func (s *bootenvTestSuite) TestInstallBootloaderConfigNoConfig(c *C) {
    98  	err := bootloader.InstallBootConfig(c.MkDir(), s.rootdir, nil)
    99  	c.Assert(err, ErrorMatches, `cannot find boot config in.*`)
   100  }
   101  
   102  func (s *bootenvTestSuite) TestInstallBootloaderConfigFromGadget(c *C) {
   103  	for _, t := range []struct {
   104  		name                string
   105  		gadgetFile, sysFile string
   106  		gadgetFileContent   []byte
   107  		opts                *bootloader.Options
   108  	}{
   109  		{name: "grub", gadgetFile: "grub.conf", sysFile: "/boot/grub/grub.cfg"},
   110  		// traditional uboot.env - the uboot.env file needs to be non-empty
   111  		{name: "uboot.env", gadgetFile: "uboot.conf", sysFile: "/boot/uboot/uboot.env", gadgetFileContent: []byte{1}},
   112  		// boot.scr in place of uboot.env means we create the boot.sel file
   113  		{
   114  			name:       "uboot boot.scr",
   115  			gadgetFile: "uboot.conf",
   116  			sysFile:    "/uboot/ubuntu/boot.sel",
   117  			opts:       &bootloader.Options{Role: bootloader.RoleRecovery},
   118  		},
   119  		{name: "androidboot", gadgetFile: "androidboot.conf", sysFile: "/boot/androidboot/androidboot.env"},
   120  		{name: "lk", gadgetFile: "lk.conf", sysFile: "/boot/lk/snapbootsel.bin", opts: &bootloader.Options{PrepareImageTime: true}},
   121  	} {
   122  		mockGadgetDir := c.MkDir()
   123  		rootDir := c.MkDir()
   124  		err := ioutil.WriteFile(filepath.Join(mockGadgetDir, t.gadgetFile), t.gadgetFileContent, 0644)
   125  		c.Assert(err, IsNil)
   126  		err = bootloader.InstallBootConfig(mockGadgetDir, rootDir, t.opts)
   127  		c.Assert(err, IsNil, Commentf("installing boot config for %s", t.name))
   128  		fn := filepath.Join(rootDir, t.sysFile)
   129  		c.Assert(fn, testutil.FilePresent, Commentf("boot config missing for %s at %s", t.name, t.sysFile))
   130  	}
   131  }
   132  
   133  func (s *bootenvTestSuite) TestInstallBootloaderConfigFromAssets(c *C) {
   134  	recoveryOpts := &bootloader.Options{
   135  		Role: bootloader.RoleRecovery,
   136  	}
   137  	systemBootOpts := &bootloader.Options{
   138  		Role: bootloader.RoleRunMode,
   139  	}
   140  	defaultRecoveryGrubAsset := assets.Internal("grub-recovery.cfg")
   141  	c.Assert(defaultRecoveryGrubAsset, NotNil)
   142  	defaultGrubAsset := assets.Internal("grub.cfg")
   143  	c.Assert(defaultGrubAsset, NotNil)
   144  
   145  	for _, t := range []struct {
   146  		name                string
   147  		gadgetFile, sysFile string
   148  		gadgetFileContent   []byte
   149  		sysFileContent      []byte
   150  		assetContent        []byte
   151  		assetName           string
   152  		err                 string
   153  		opts                *bootloader.Options
   154  	}{
   155  		{
   156  			name:       "recovery grub",
   157  			opts:       recoveryOpts,
   158  			gadgetFile: "grub.conf",
   159  			// empty file in the gadget
   160  			gadgetFileContent: nil,
   161  			sysFile:           "/EFI/ubuntu/grub.cfg",
   162  			assetName:         "grub-recovery.cfg",
   163  			assetContent:      []byte("hello assets"),
   164  			// boot config from assets
   165  			sysFileContent: []byte("hello assets"),
   166  		}, {
   167  			name:              "recovery grub with non empty gadget file",
   168  			opts:              recoveryOpts,
   169  			gadgetFile:        "grub.conf",
   170  			gadgetFileContent: []byte("not so empty"),
   171  			sysFile:           "/EFI/ubuntu/grub.cfg",
   172  			assetName:         "grub-recovery.cfg",
   173  			assetContent:      []byte("hello assets"),
   174  			// boot config from assets
   175  			sysFileContent: []byte("hello assets"),
   176  		}, {
   177  			name:       "recovery grub with default asset",
   178  			opts:       recoveryOpts,
   179  			gadgetFile: "grub.conf",
   180  			// empty file in the gadget
   181  			gadgetFileContent: nil,
   182  			sysFile:           "/EFI/ubuntu/grub.cfg",
   183  			sysFileContent:    defaultRecoveryGrubAsset,
   184  		}, {
   185  			name:       "recovery grub missing asset",
   186  			opts:       recoveryOpts,
   187  			gadgetFile: "grub.conf",
   188  			// empty file in the gadget
   189  			gadgetFileContent: nil,
   190  			sysFile:           "/EFI/ubuntu/grub.cfg",
   191  			assetName:         "grub-recovery.cfg",
   192  			// no asset content
   193  			err: `internal error: no boot asset for "grub-recovery.cfg"`,
   194  		}, {
   195  			name:       "system-boot grub",
   196  			opts:       systemBootOpts,
   197  			gadgetFile: "grub.conf",
   198  			// empty file in the gadget
   199  			gadgetFileContent: nil,
   200  			sysFile:           "/EFI/ubuntu/grub.cfg",
   201  			assetName:         "grub.cfg",
   202  			assetContent:      []byte("hello assets"),
   203  			sysFileContent:    []byte("hello assets"),
   204  		}, {
   205  			name:       "system-boot grub with default asset",
   206  			opts:       systemBootOpts,
   207  			gadgetFile: "grub.conf",
   208  			// empty file in the gadget
   209  			gadgetFileContent: nil,
   210  			sysFile:           "/EFI/ubuntu/grub.cfg",
   211  			sysFileContent:    defaultGrubAsset,
   212  		},
   213  	} {
   214  		mockGadgetDir := c.MkDir()
   215  		rootDir := c.MkDir()
   216  		fn := filepath.Join(rootDir, t.sysFile)
   217  		err := ioutil.WriteFile(filepath.Join(mockGadgetDir, t.gadgetFile), t.gadgetFileContent, 0644)
   218  		c.Assert(err, IsNil)
   219  		var restoreAsset func()
   220  		if t.assetName != "" {
   221  			restoreAsset = assets.MockInternal(t.assetName, t.assetContent)
   222  		}
   223  		err = bootloader.InstallBootConfig(mockGadgetDir, rootDir, t.opts)
   224  		if t.err == "" {
   225  			c.Assert(err, IsNil, Commentf("installing boot config for %s", t.name))
   226  			// mocked asset content
   227  			c.Assert(fn, testutil.FileEquals, string(t.sysFileContent))
   228  		} else {
   229  			c.Assert(err, ErrorMatches, t.err)
   230  			c.Assert(fn, testutil.FileAbsent)
   231  		}
   232  		if restoreAsset != nil {
   233  			restoreAsset()
   234  		}
   235  	}
   236  }
   237  
   238  func (s *bootenvTestSuite) TestBootloaderFindPresentNonNilError(c *C) {
   239  	rootdir := c.MkDir()
   240  	// add a mock bootloader to the list of bootloaders that Find() uses
   241  	mockBl := bootloadertest.Mock("mock", rootdir)
   242  	restore := bootloader.MockAddBootloaderToFind(func(dir string, opts *bootloader.Options) bootloader.Bootloader {
   243  		c.Assert(dir, Equals, rootdir)
   244  		return mockBl
   245  	})
   246  	defer restore()
   247  
   248  	// make us find our bootloader
   249  	mockBl.MockedPresent = true
   250  
   251  	bl, err := bootloader.Find(rootdir, nil)
   252  	c.Assert(err, IsNil)
   253  	c.Assert(bl, NotNil)
   254  	c.Assert(bl.Name(), Equals, "mock")
   255  	c.Assert(bl, DeepEquals, mockBl)
   256  
   257  	// now make finding our bootloader a fatal error, this time we will get the
   258  	// error back
   259  	mockBl.PresentErr = fmt.Errorf("boom")
   260  	_, err = bootloader.Find(rootdir, nil)
   261  	c.Assert(err, ErrorMatches, "bootloader \"mock\" found but not usable: boom")
   262  }
   263  
   264  func (s *bootenvTestSuite) TestBootloaderFindBadOptions(c *C) {
   265  	_, err := bootloader.Find("", &bootloader.Options{
   266  		PrepareImageTime: true,
   267  		Role:             bootloader.RoleRunMode,
   268  	})
   269  	c.Assert(err, ErrorMatches, "internal error: cannot use run mode bootloader at prepare-image time")
   270  
   271  	_, err = bootloader.Find("", &bootloader.Options{
   272  		NoSlashBoot: true,
   273  		Role:        bootloader.RoleSole,
   274  	})
   275  	c.Assert(err, ErrorMatches, "internal error: bootloader.RoleSole doesn't expect NoSlashBoot set")
   276  }
   277  
   278  func (s *bootenvTestSuite) TestBootloaderFind(c *C) {
   279  	for _, tc := range []struct {
   280  		name    string
   281  		sysFile string
   282  		opts    *bootloader.Options
   283  		expName string
   284  	}{
   285  		{name: "grub", sysFile: "/boot/grub/grub.cfg", expName: "grub"},
   286  		{
   287  			// native run partition layout
   288  			name: "grub", sysFile: "/EFI/ubuntu/grub.cfg",
   289  			opts:    &bootloader.Options{Role: bootloader.RoleRunMode, NoSlashBoot: true},
   290  			expName: "grub",
   291  		},
   292  		{
   293  			// recovery layout
   294  			name: "grub", sysFile: "/EFI/ubuntu/grub.cfg",
   295  			opts:    &bootloader.Options{Role: bootloader.RoleRecovery},
   296  			expName: "grub",
   297  		},
   298  
   299  		// traditional uboot.env - the uboot.env file needs to be non-empty
   300  		{name: "uboot.env", sysFile: "/boot/uboot/uboot.env", expName: "uboot"},
   301  		// boot.sel uboot variant
   302  		{
   303  			name:    "uboot boot.scr",
   304  			sysFile: "/uboot/ubuntu/boot.sel",
   305  			opts:    &bootloader.Options{Role: bootloader.RoleRunMode, NoSlashBoot: true},
   306  			expName: "uboot",
   307  		},
   308  		{name: "androidboot", sysFile: "/boot/androidboot/androidboot.env", expName: "androidboot"},
   309  		// lk is detected differently based on runtime/prepare-image
   310  		{name: "lk", sysFile: "/dev/disk/by-partlabel/snapbootsel", expName: "lk"},
   311  		{
   312  			name: "lk", sysFile: "/boot/lk/snapbootsel.bin",
   313  			expName: "lk", opts: &bootloader.Options{PrepareImageTime: true},
   314  		},
   315  	} {
   316  		c.Logf("tc: %v", tc.name)
   317  		rootDir := c.MkDir()
   318  		err := os.MkdirAll(filepath.Join(rootDir, filepath.Dir(tc.sysFile)), 0755)
   319  		c.Assert(err, IsNil)
   320  		err = ioutil.WriteFile(filepath.Join(rootDir, tc.sysFile), nil, 0644)
   321  		c.Assert(err, IsNil)
   322  		bl, err := bootloader.Find(rootDir, tc.opts)
   323  		c.Assert(err, IsNil)
   324  		c.Assert(bl, NotNil)
   325  		c.Check(bl.Name(), Equals, tc.expName)
   326  	}
   327  }
   328  
   329  func (s *bootenvTestSuite) TestBootloaderForGadget(c *C) {
   330  	for _, tc := range []struct {
   331  		name       string
   332  		gadgetFile string
   333  		opts       *bootloader.Options
   334  		expName    string
   335  	}{
   336  		{name: "grub", gadgetFile: "grub.conf", expName: "grub"},
   337  		{name: "grub", gadgetFile: "grub.conf", opts: &bootloader.Options{Role: bootloader.RoleRunMode, NoSlashBoot: true}, expName: "grub"},
   338  		{name: "grub", gadgetFile: "grub.conf", opts: &bootloader.Options{Role: bootloader.RoleRecovery}, expName: "grub"},
   339  		{name: "uboot", gadgetFile: "uboot.conf", expName: "uboot"},
   340  		{name: "androidboot", gadgetFile: "androidboot.conf", expName: "androidboot"},
   341  		{name: "lk", gadgetFile: "lk.conf", expName: "lk"},
   342  	} {
   343  		c.Logf("tc: %v", tc.name)
   344  		gadgetDir := c.MkDir()
   345  		rootDir := c.MkDir()
   346  		err := os.MkdirAll(filepath.Join(rootDir, filepath.Dir(tc.gadgetFile)), 0755)
   347  		c.Assert(err, IsNil)
   348  		err = ioutil.WriteFile(filepath.Join(gadgetDir, tc.gadgetFile), nil, 0644)
   349  		c.Assert(err, IsNil)
   350  		bl, err := bootloader.ForGadget(gadgetDir, rootDir, tc.opts)
   351  		c.Assert(err, IsNil)
   352  		c.Assert(bl, NotNil)
   353  		c.Check(bl.Name(), Equals, tc.expName)
   354  	}
   355  }
   356  
   357  func (s *bootenvTestSuite) TestBootFileWithPath(c *C) {
   358  	a := bootloader.NewBootFile("", "some/path", bootloader.RoleRunMode)
   359  	b := a.WithPath("other/path")
   360  	c.Assert(a.Path, Equals, "some/path")
   361  	c.Assert(b.Path, Equals, "other/path")
   362  }