github.com/meulengracht/snapd@v0.0.0-20210719210640-8bde69bcc84e/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 }