github.com/stolowski/snapd@v0.0.0-20210407085831-115137ce5a22/boot/modeenv_test.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2019-2020 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 boot_test 21 22 import ( 23 "bytes" 24 "encoding/json" 25 "fmt" 26 "io/ioutil" 27 "os" 28 "path/filepath" 29 "strings" 30 31 "github.com/mvo5/goconfigparser" 32 . "gopkg.in/check.v1" 33 34 "github.com/snapcore/snapd/boot" 35 "github.com/snapcore/snapd/dirs" 36 "github.com/snapcore/snapd/testutil" 37 ) 38 39 // baseBootSuite is used to setup the common test environment 40 type modeenvSuite struct { 41 testutil.BaseTest 42 43 tmpdir string 44 mockModeenvPath string 45 } 46 47 var _ = Suite(&modeenvSuite{}) 48 49 func (s *modeenvSuite) SetUpTest(c *C) { 50 s.tmpdir = c.MkDir() 51 s.mockModeenvPath = filepath.Join(s.tmpdir, dirs.SnapModeenvFile) 52 } 53 54 func (s *modeenvSuite) TestKnownKnown(c *C) { 55 // double check keys as found with reflect 56 c.Check(boot.ModeenvKnownKeys, DeepEquals, map[string]bool{ 57 "mode": true, 58 "recovery_system": true, 59 "current_recovery_systems": true, 60 "good_recovery_systems": true, 61 // keep this comment to make old go fmt happy 62 "base": true, 63 "try_base": true, 64 "base_status": true, 65 "current_kernels": true, 66 "model": true, 67 "grade": true, 68 // keep this comment to make old go fmt happy 69 "current_kernel_command_lines": true, 70 "current_trusted_boot_assets": true, 71 "current_trusted_recovery_boot_assets": true, 72 }) 73 } 74 75 func (s *modeenvSuite) TestReadEmptyErrors(c *C) { 76 modeenv, err := boot.ReadModeenv("/no/such/file") 77 c.Assert(os.IsNotExist(err), Equals, true) 78 c.Assert(modeenv, IsNil) 79 } 80 81 func (s *modeenvSuite) makeMockModeenvFile(c *C, content string) { 82 err := os.MkdirAll(filepath.Dir(s.mockModeenvPath), 0755) 83 c.Assert(err, IsNil) 84 err = ioutil.WriteFile(s.mockModeenvPath, []byte(content), 0644) 85 c.Assert(err, IsNil) 86 } 87 88 func (s *modeenvSuite) TestWasReadSanity(c *C) { 89 modeenv := &boot.Modeenv{} 90 c.Check(modeenv.WasRead(), Equals, false) 91 } 92 93 func (s *modeenvSuite) TestReadEmpty(c *C) { 94 s.makeMockModeenvFile(c, "") 95 96 modeenv, err := boot.ReadModeenv(s.tmpdir) 97 c.Assert(err, ErrorMatches, "internal error: mode is unset") 98 c.Assert(modeenv, IsNil) 99 } 100 101 func (s *modeenvSuite) TestReadMode(c *C) { 102 s.makeMockModeenvFile(c, "mode=run") 103 104 modeenv, err := boot.ReadModeenv(s.tmpdir) 105 c.Assert(err, IsNil) 106 c.Check(modeenv.Mode, Equals, "run") 107 c.Check(modeenv.RecoverySystem, Equals, "") 108 c.Check(modeenv.Base, Equals, "") 109 } 110 111 func (s *modeenvSuite) TestDeepEqualDiskVsMemoryInvariant(c *C) { 112 s.makeMockModeenvFile(c, `mode=recovery 113 recovery_system=20191126 114 base=core20_123.snap 115 try_base=core20_124.snap 116 base_status=try 117 `) 118 119 diskModeenv, err := boot.ReadModeenv(s.tmpdir) 120 c.Assert(err, IsNil) 121 inMemoryModeenv := &boot.Modeenv{ 122 Mode: "recovery", 123 RecoverySystem: "20191126", 124 Base: "core20_123.snap", 125 TryBase: "core20_124.snap", 126 BaseStatus: "try", 127 } 128 c.Assert(inMemoryModeenv.DeepEqual(diskModeenv), Equals, true) 129 c.Assert(diskModeenv.DeepEqual(inMemoryModeenv), Equals, true) 130 } 131 132 func (s *modeenvSuite) TestCopyDeepEquals(c *C) { 133 s.makeMockModeenvFile(c, `mode=recovery 134 recovery_system=20191126 135 base=core20_123.snap 136 try_base=core20_124.snap 137 base_status=try 138 current_trusted_boot_assets={"thing1":["hash1","hash2"],"thing2":["hash3"]} 139 current_kernel_command_lines=["foo", "bar"] 140 `) 141 142 diskModeenv, err := boot.ReadModeenv(s.tmpdir) 143 c.Assert(err, IsNil) 144 inMemoryModeenv := &boot.Modeenv{ 145 Mode: "recovery", 146 RecoverySystem: "20191126", 147 Base: "core20_123.snap", 148 TryBase: "core20_124.snap", 149 BaseStatus: "try", 150 CurrentTrustedBootAssets: boot.BootAssetsMap{ 151 "thing1": {"hash1", "hash2"}, 152 "thing2": {"hash3"}, 153 }, 154 CurrentKernelCommandLines: boot.BootCommandLines{ 155 "foo", "bar", 156 }, 157 } 158 159 c.Assert(inMemoryModeenv.DeepEqual(diskModeenv), Equals, true) 160 c.Assert(diskModeenv.DeepEqual(inMemoryModeenv), Equals, true) 161 162 diskModeenv2, err := diskModeenv.Copy() 163 c.Assert(err, IsNil) 164 c.Assert(diskModeenv.DeepEqual(diskModeenv2), Equals, true) 165 c.Assert(diskModeenv2.DeepEqual(diskModeenv), Equals, true) 166 c.Assert(inMemoryModeenv.DeepEqual(diskModeenv2), Equals, true) 167 c.Assert(diskModeenv2.DeepEqual(inMemoryModeenv), Equals, true) 168 169 inMemoryModeenv2, err := inMemoryModeenv.Copy() 170 c.Assert(err, IsNil) 171 c.Assert(inMemoryModeenv.DeepEqual(inMemoryModeenv2), Equals, true) 172 c.Assert(inMemoryModeenv2.DeepEqual(inMemoryModeenv), Equals, true) 173 c.Assert(inMemoryModeenv2.DeepEqual(diskModeenv), Equals, true) 174 c.Assert(diskModeenv.DeepEqual(inMemoryModeenv2), Equals, true) 175 } 176 177 func (s *modeenvSuite) TestCopyDiskWriteWorks(c *C) { 178 s.makeMockModeenvFile(c, `mode=recovery 179 recovery_system=20191126 180 base=core20_123.snap 181 try_base=core20_124.snap 182 base_status=try 183 `) 184 185 diskModeenv, err := boot.ReadModeenv(s.tmpdir) 186 c.Assert(err, IsNil) 187 dupDiskModeenv, err := diskModeenv.Copy() 188 c.Assert(err, IsNil) 189 190 // move the original file out of the way 191 err = os.Rename(dirs.SnapModeenvFileUnder(s.tmpdir), dirs.SnapModeenvFileUnder(s.tmpdir)+".orig") 192 c.Assert(err, IsNil) 193 c.Assert(dirs.SnapModeenvFileUnder(s.tmpdir), testutil.FileAbsent) 194 195 // write the duplicate, it should write to the same original location and it 196 // should be the same content 197 err = dupDiskModeenv.Write() 198 c.Assert(err, IsNil) 199 c.Assert(dirs.SnapModeenvFileUnder(s.tmpdir), testutil.FilePresent) 200 origBytes, err := ioutil.ReadFile(dirs.SnapModeenvFileUnder(s.tmpdir) + ".orig") 201 c.Assert(err, IsNil) 202 // the files should be the same 203 c.Assert(dirs.SnapModeenvFileUnder(s.tmpdir), testutil.FileEquals, string(origBytes)) 204 } 205 206 func (s *modeenvSuite) TestCopyMemoryWriteFails(c *C) { 207 inMemoryModeenv := &boot.Modeenv{ 208 Mode: "recovery", 209 RecoverySystem: "20191126", 210 Base: "core20_123.snap", 211 TryBase: "core20_124.snap", 212 BaseStatus: "try", 213 } 214 dupInMemoryModeenv, err := inMemoryModeenv.Copy() 215 c.Assert(err, IsNil) 216 217 // write the duplicate, it should fail 218 err = dupInMemoryModeenv.Write() 219 c.Assert(err, ErrorMatches, "internal error: must use WriteTo with modeenv not read from disk") 220 } 221 222 func (s *modeenvSuite) TestDeepEquals(c *C) { 223 // start with two identical modeenvs 224 modeenv1 := &boot.Modeenv{ 225 Mode: "recovery", 226 RecoverySystem: "20191126", 227 CurrentRecoverySystems: []string{"1", "2"}, 228 GoodRecoverySystems: []string{"3"}, 229 230 Base: "core20_123.snap", 231 TryBase: "core20_124.snap", 232 BaseStatus: "try", 233 CurrentKernels: []string{"k1", "k2"}, 234 235 Model: "model", 236 BrandID: "brand", 237 Grade: "secured", 238 239 CurrentTrustedBootAssets: boot.BootAssetsMap{ 240 "thing1": {"hash1", "hash2"}, 241 "thing2": {"hash3"}, 242 }, 243 244 CurrentKernelCommandLines: boot.BootCommandLines{ 245 "foo", 246 "foo bar", 247 }, 248 } 249 250 modeenv2 := &boot.Modeenv{ 251 Mode: "recovery", 252 RecoverySystem: "20191126", 253 CurrentRecoverySystems: []string{"1", "2"}, 254 GoodRecoverySystems: []string{"3"}, 255 256 Base: "core20_123.snap", 257 TryBase: "core20_124.snap", 258 BaseStatus: "try", 259 CurrentKernels: []string{"k1", "k2"}, 260 261 Model: "model", 262 BrandID: "brand", 263 Grade: "secured", 264 265 CurrentTrustedBootAssets: boot.BootAssetsMap{ 266 "thing1": {"hash1", "hash2"}, 267 "thing2": {"hash3"}, 268 }, 269 270 CurrentKernelCommandLines: boot.BootCommandLines{ 271 "foo", 272 "foo bar", 273 }, 274 } 275 276 // same object should be the same 277 c.Assert(modeenv1.DeepEqual(modeenv1), Equals, true) 278 279 // no difference should be the same at the start 280 c.Assert(modeenv1.DeepEqual(modeenv2), Equals, true) 281 c.Assert(modeenv2.DeepEqual(modeenv1), Equals, true) 282 283 // invert CurrentKernels 284 modeenv2.CurrentKernels = []string{"k2", "k1"} 285 c.Assert(modeenv1.DeepEqual(modeenv2), Equals, false) 286 c.Assert(modeenv2.DeepEqual(modeenv1), Equals, false) 287 288 // make CurrentKernels capitalized 289 modeenv2.CurrentKernels = []string{"K1", "k2"} 290 c.Assert(modeenv1.DeepEqual(modeenv2), Equals, false) 291 c.Assert(modeenv2.DeepEqual(modeenv1), Equals, false) 292 293 // make CurrentKernels disappear 294 modeenv2.CurrentKernels = nil 295 c.Assert(modeenv1.DeepEqual(modeenv2), Equals, false) 296 c.Assert(modeenv2.DeepEqual(modeenv1), Equals, false) 297 298 // make it identical again 299 modeenv2.CurrentKernels = []string{"k1", "k2"} 300 c.Assert(modeenv1.DeepEqual(modeenv2), Equals, true) 301 // change kernel command lines 302 modeenv2.CurrentKernelCommandLines = boot.BootCommandLines{ 303 // reversed order 304 "foo bar", 305 "foo", 306 } 307 c.Assert(modeenv1.DeepEqual(modeenv2), Equals, false) 308 // clear kernel command lines list 309 modeenv2.CurrentKernelCommandLines = nil 310 c.Assert(modeenv1.DeepEqual(modeenv2), Equals, false) 311 312 // make it identical again 313 modeenv2.CurrentKernelCommandLines = boot.BootCommandLines{ 314 "foo", 315 "foo bar", 316 } 317 c.Assert(modeenv1.DeepEqual(modeenv2), Equals, true) 318 319 // change the list of current recovery systems 320 modeenv2.CurrentRecoverySystems = append(modeenv2.CurrentRecoverySystems, "1234") 321 c.Assert(modeenv1.DeepEqual(modeenv2), Equals, false) 322 // make it identical again 323 modeenv2.CurrentRecoverySystems = []string{"1", "2"} 324 c.Assert(modeenv1.DeepEqual(modeenv2), Equals, true) 325 326 // change the list of good recovery systems 327 modeenv2.GoodRecoverySystems = append(modeenv2.GoodRecoverySystems, "999") 328 c.Assert(modeenv1.DeepEqual(modeenv2), Equals, false) 329 } 330 331 func (s *modeenvSuite) TestReadModeWithRecoverySystem(c *C) { 332 s.makeMockModeenvFile(c, `mode=recovery 333 recovery_system=20191126 334 `) 335 336 modeenv, err := boot.ReadModeenv(s.tmpdir) 337 c.Assert(err, IsNil) 338 c.Check(modeenv.Mode, Equals, "recovery") 339 c.Check(modeenv.RecoverySystem, Equals, "20191126") 340 } 341 342 func (s *modeenvSuite) TestReadModeenvWithUnknownKeysKeepsWrites(c *C) { 343 s.makeMockModeenvFile(c, `first_unknown=thing 344 mode=recovery 345 recovery_system=20191126 346 unknown_key=some unknown value 347 a_key=other 348 `) 349 350 modeenv, err := boot.ReadModeenv(s.tmpdir) 351 c.Assert(err, IsNil) 352 c.Check(modeenv.Mode, Equals, "recovery") 353 c.Check(modeenv.RecoverySystem, Equals, "20191126") 354 355 c.Assert(modeenv.Write(), IsNil) 356 357 c.Assert(s.mockModeenvPath, testutil.FileEquals, `mode=recovery 358 recovery_system=20191126 359 a_key=other 360 first_unknown=thing 361 unknown_key=some unknown value 362 `) 363 } 364 365 func (s *modeenvSuite) TestReadModeenvWithUnknownKeysDeepEqualsSameWithoutUnknownKeys(c *C) { 366 s.makeMockModeenvFile(c, `first_unknown=thing 367 mode=recovery 368 recovery_system=20191126 369 try_base=core20_124.snap 370 base_status=try 371 unknown_key=some unknown value 372 current_trusted_boot_assets={"grubx64.efi":["hash1","hash2"]} 373 current_trusted_recovery_boot_assets={"bootx64.efi":["shimhash1","shimhash2"],"grubx64.efi":["recovery-hash1"]} 374 a_key=other 375 `) 376 377 modeenvWithExtraKeys, err := boot.ReadModeenv(s.tmpdir) 378 c.Assert(err, IsNil) 379 c.Check(modeenvWithExtraKeys.Mode, Equals, "recovery") 380 c.Check(modeenvWithExtraKeys.RecoverySystem, Equals, "20191126") 381 382 // should be the same as one that with just those keys in memory 383 c.Assert(modeenvWithExtraKeys.DeepEqual(&boot.Modeenv{ 384 Mode: "recovery", 385 RecoverySystem: "20191126", 386 TryBase: "core20_124.snap", 387 BaseStatus: boot.TryStatus, 388 CurrentTrustedBootAssets: boot.BootAssetsMap{ 389 "grubx64.efi": []string{"hash1", "hash2"}, 390 }, 391 CurrentTrustedRecoveryBootAssets: boot.BootAssetsMap{ 392 "bootx64.efi": []string{"shimhash1", "shimhash2"}, 393 "grubx64.efi": []string{"recovery-hash1"}, 394 }, 395 }), Equals, true) 396 } 397 398 func (s *modeenvSuite) TestReadModeWithBase(c *C) { 399 s.makeMockModeenvFile(c, `mode=recovery 400 recovery_system=20191126 401 base=core20_123.snap 402 try_base=core20_124.snap 403 base_status=try 404 `) 405 406 modeenv, err := boot.ReadModeenv(s.tmpdir) 407 c.Assert(err, IsNil) 408 c.Check(modeenv.Mode, Equals, "recovery") 409 c.Check(modeenv.RecoverySystem, Equals, "20191126") 410 c.Check(modeenv.Base, Equals, "core20_123.snap") 411 c.Check(modeenv.TryBase, Equals, "core20_124.snap") 412 c.Check(modeenv.BaseStatus, Equals, boot.TryStatus) 413 } 414 415 func (s *modeenvSuite) TestReadModeWithGrade(c *C) { 416 s.makeMockModeenvFile(c, `mode=run 417 grade=dangerous 418 `) 419 modeenv, err := boot.ReadModeenv(s.tmpdir) 420 c.Assert(err, IsNil) 421 c.Check(modeenv.Mode, Equals, "run") 422 c.Check(modeenv.Grade, Equals, "dangerous") 423 424 s.makeMockModeenvFile(c, `mode=run 425 grade=some-random-grade-string 426 `) 427 modeenv, err = boot.ReadModeenv(s.tmpdir) 428 c.Assert(err, IsNil) 429 c.Check(modeenv.Mode, Equals, "run") 430 c.Check(modeenv.Grade, Equals, "some-random-grade-string") 431 } 432 433 func (s *modeenvSuite) TestReadModeWithModel(c *C) { 434 tt := []struct { 435 entry string 436 model, brand string 437 }{ 438 { 439 entry: "my-brand/my-model", 440 brand: "my-brand", 441 model: "my-model", 442 }, { 443 entry: "my-brand/", 444 }, { 445 entry: "my-model/", 446 }, { 447 entry: "foobar", 448 }, { 449 entry: "/", 450 }, { 451 entry: ",", 452 }, { 453 entry: "", 454 }, 455 } 456 457 for _, t := range tt { 458 s.makeMockModeenvFile(c, `mode=run 459 model=`+t.entry+"\n") 460 modeenv, err := boot.ReadModeenv(s.tmpdir) 461 c.Assert(err, IsNil) 462 c.Check(modeenv.Mode, Equals, "run") 463 c.Check(modeenv.Model, Equals, t.model) 464 c.Check(modeenv.BrandID, Equals, t.brand) 465 } 466 } 467 468 func (s *modeenvSuite) TestReadModeWithCurrentKernels(c *C) { 469 470 tt := []struct { 471 kernelString string 472 expectedKernels []string 473 }{ 474 { 475 "pc-kernel_1.snap", 476 []string{"pc-kernel_1.snap"}, 477 }, 478 { 479 "pc-kernel_1.snap,pc-kernel_2.snap", 480 []string{"pc-kernel_1.snap", "pc-kernel_2.snap"}, 481 }, 482 { 483 "pc-kernel_1.snap,,,,,pc-kernel_2.snap", 484 []string{"pc-kernel_1.snap", "pc-kernel_2.snap"}, 485 }, 486 // we should be robust in parsing the modeenv against garbage 487 { 488 `pc-kernel_1.snap,this-is-not-a-real-snap$%^&^%$#@#$%^%"$,pc-kernel_2.snap`, 489 []string{"pc-kernel_1.snap", `this-is-not-a-real-snap$%^&^%$#@#$%^%"$`, "pc-kernel_2.snap"}, 490 }, 491 {",,,", nil}, 492 {"", nil}, 493 } 494 495 for _, t := range tt { 496 s.makeMockModeenvFile(c, `mode=recovery 497 recovery_system=20191126 498 current_kernels=`+t.kernelString+"\n") 499 500 modeenv, err := boot.ReadModeenv(s.tmpdir) 501 c.Assert(err, IsNil) 502 c.Check(modeenv.Mode, Equals, "recovery") 503 c.Check(modeenv.RecoverySystem, Equals, "20191126") 504 c.Check(len(modeenv.CurrentKernels), Equals, len(t.expectedKernels)) 505 if len(t.expectedKernels) != 0 { 506 c.Check(modeenv.CurrentKernels, DeepEquals, t.expectedKernels) 507 } 508 } 509 } 510 511 func (s *modeenvSuite) TestWriteToNonExisting(c *C) { 512 c.Assert(s.mockModeenvPath, testutil.FileAbsent) 513 514 modeenv := &boot.Modeenv{Mode: "run"} 515 err := modeenv.WriteTo(s.tmpdir) 516 c.Assert(err, IsNil) 517 518 c.Assert(s.mockModeenvPath, testutil.FileEquals, "mode=run\n") 519 } 520 521 func (s *modeenvSuite) TestWriteToExisting(c *C) { 522 s.makeMockModeenvFile(c, "mode=run") 523 524 modeenv, err := boot.ReadModeenv(s.tmpdir) 525 c.Assert(err, IsNil) 526 modeenv.Mode = "recovery" 527 err = modeenv.WriteTo(s.tmpdir) 528 c.Assert(err, IsNil) 529 530 c.Assert(s.mockModeenvPath, testutil.FileEquals, "mode=recovery\n") 531 } 532 533 func (s *modeenvSuite) TestWriteExisting(c *C) { 534 s.makeMockModeenvFile(c, "mode=run") 535 536 modeenv, err := boot.ReadModeenv(s.tmpdir) 537 c.Assert(err, IsNil) 538 modeenv.Mode = "recovery" 539 err = modeenv.Write() 540 c.Assert(err, IsNil) 541 542 c.Assert(s.mockModeenvPath, testutil.FileEquals, "mode=recovery\n") 543 } 544 545 func (s *modeenvSuite) TestWriteFreshError(c *C) { 546 modeenv := &boot.Modeenv{Mode: "recovery"} 547 548 err := modeenv.Write() 549 c.Assert(err, ErrorMatches, `internal error: must use WriteTo with modeenv not read from disk`) 550 } 551 552 func (s *modeenvSuite) TestWriteToNonExistingFull(c *C) { 553 c.Assert(s.mockModeenvPath, testutil.FileAbsent) 554 555 modeenv := &boot.Modeenv{ 556 Mode: "run", 557 RecoverySystem: "20191128", 558 CurrentRecoverySystems: []string{"20191128", "2020-02-03", "20240101-FOO"}, 559 // keep this comment to make gofmt 1.9 happy 560 Base: "core20_321.snap", 561 TryBase: "core20_322.snap", 562 BaseStatus: boot.TryStatus, 563 CurrentKernels: []string{"pc-kernel_1.snap", "pc-kernel_2.snap"}, 564 } 565 err := modeenv.WriteTo(s.tmpdir) 566 c.Assert(err, IsNil) 567 568 c.Assert(s.mockModeenvPath, testutil.FileEquals, `mode=run 569 recovery_system=20191128 570 current_recovery_systems=20191128,2020-02-03,20240101-FOO 571 base=core20_321.snap 572 try_base=core20_322.snap 573 base_status=try 574 current_kernels=pc-kernel_1.snap,pc-kernel_2.snap 575 `) 576 } 577 578 func (s *modeenvSuite) TestReadRecoverySystems(c *C) { 579 tt := []struct { 580 systemsString string 581 expectedSystems []string 582 }{ 583 { 584 "20191126", 585 []string{"20191126"}, 586 }, { 587 "20191128,2020-02-03,20240101-FOO", 588 []string{"20191128", "2020-02-03", "20240101-FOO"}, 589 }, 590 {",,,", nil}, 591 {"", nil}, 592 } 593 594 for _, t := range tt { 595 c.Logf("tc: %q", t.systemsString) 596 s.makeMockModeenvFile(c, fmt.Sprintf(`mode=recovery 597 recovery_system=20191126 598 current_recovery_systems=%[1]s 599 good_recovery_systems=%[1]s 600 `, t.systemsString)) 601 602 modeenv, err := boot.ReadModeenv(s.tmpdir) 603 c.Assert(err, IsNil) 604 c.Check(modeenv.Mode, Equals, "recovery") 605 c.Check(modeenv.RecoverySystem, Equals, "20191126") 606 c.Check(modeenv.CurrentRecoverySystems, DeepEquals, t.expectedSystems) 607 c.Check(modeenv.GoodRecoverySystems, DeepEquals, t.expectedSystems) 608 } 609 } 610 611 type fancyDataBothMarshallers struct { 612 Foo []string 613 } 614 615 func (f *fancyDataBothMarshallers) MarshalModeenvValue() (string, error) { 616 return strings.Join(f.Foo, "#"), nil 617 } 618 619 func (f *fancyDataBothMarshallers) UnmarshalModeenvValue(v string) error { 620 f.Foo = strings.Split(v, "#") 621 return nil 622 } 623 624 func (f *fancyDataBothMarshallers) MarshalJSON() ([]byte, error) { 625 return nil, fmt.Errorf("unexpected call to JSON marshaller") 626 } 627 628 func (f *fancyDataBothMarshallers) UnmarshalJSON(data []byte) error { 629 return fmt.Errorf("unexpected call to JSON unmarshaller") 630 } 631 632 type fancyDataJSONOnly struct { 633 Foo []string 634 } 635 636 func (f *fancyDataJSONOnly) MarshalJSON() ([]byte, error) { 637 return json.Marshal(f.Foo) 638 } 639 640 func (f *fancyDataJSONOnly) UnmarshalJSON(data []byte) error { 641 return json.Unmarshal(data, &f.Foo) 642 } 643 644 func (s *modeenvSuite) TestFancyMarshalUnmarshal(c *C) { 645 var buf bytes.Buffer 646 647 dboth := fancyDataBothMarshallers{Foo: []string{"1", "two"}} 648 err := boot.MarshalModeenvEntryTo(&buf, "fancy", &dboth) 649 c.Assert(err, IsNil) 650 c.Check(buf.String(), Equals, `fancy=1#two 651 `) 652 653 djson := fancyDataJSONOnly{Foo: []string{"1", "two", "with\nnewline"}} 654 err = boot.MarshalModeenvEntryTo(&buf, "fancy_json", &djson) 655 c.Assert(err, IsNil) 656 c.Check(buf.String(), Equals, `fancy=1#two 657 fancy_json=["1","two","with\nnewline"] 658 `) 659 660 cfg := goconfigparser.New() 661 cfg.AllowNoSectionHeader = true 662 err = cfg.Read(&buf) 663 c.Assert(err, IsNil) 664 665 var dbothRev fancyDataBothMarshallers 666 err = boot.UnmarshalModeenvValueFromCfg(cfg, "fancy", &dbothRev) 667 c.Assert(err, IsNil) 668 c.Check(dbothRev, DeepEquals, dboth) 669 670 var djsonRev fancyDataJSONOnly 671 err = boot.UnmarshalModeenvValueFromCfg(cfg, "fancy_json", &djsonRev) 672 c.Assert(err, IsNil) 673 c.Check(djsonRev, DeepEquals, djson) 674 } 675 676 func (s *modeenvSuite) TestFancyUnmarshalJSONEmpty(c *C) { 677 var buf bytes.Buffer 678 679 cfg := goconfigparser.New() 680 cfg.AllowNoSectionHeader = true 681 err := cfg.Read(&buf) 682 c.Assert(err, IsNil) 683 684 var djsonRev fancyDataJSONOnly 685 err = boot.UnmarshalModeenvValueFromCfg(cfg, "fancy_json", &djsonRev) 686 c.Assert(err, IsNil) 687 c.Check(djsonRev.Foo, IsNil) 688 } 689 690 func (s *modeenvSuite) TestMarshalCurrentTrustedBootAssets(c *C) { 691 c.Assert(s.mockModeenvPath, testutil.FileAbsent) 692 693 modeenv := &boot.Modeenv{ 694 Mode: "run", 695 RecoverySystem: "20191128", 696 CurrentTrustedBootAssets: boot.BootAssetsMap{ 697 "grubx64.efi": []string{"hash1", "hash2"}, 698 }, 699 CurrentTrustedRecoveryBootAssets: boot.BootAssetsMap{ 700 "grubx64.efi": []string{"recovery-hash1"}, 701 "bootx64.efi": []string{"shimhash1", "shimhash2"}, 702 }, 703 } 704 err := modeenv.WriteTo(s.tmpdir) 705 c.Assert(err, IsNil) 706 707 c.Assert(s.mockModeenvPath, testutil.FileEquals, `mode=run 708 recovery_system=20191128 709 current_trusted_boot_assets={"grubx64.efi":["hash1","hash2"]} 710 current_trusted_recovery_boot_assets={"bootx64.efi":["shimhash1","shimhash2"],"grubx64.efi":["recovery-hash1"]} 711 `) 712 713 modeenvRead, err := boot.ReadModeenv(s.tmpdir) 714 c.Assert(err, IsNil) 715 c.Assert(modeenvRead.CurrentTrustedBootAssets, DeepEquals, boot.BootAssetsMap{ 716 "grubx64.efi": []string{"hash1", "hash2"}, 717 }) 718 c.Assert(modeenvRead.CurrentTrustedRecoveryBootAssets, DeepEquals, boot.BootAssetsMap{ 719 "grubx64.efi": []string{"recovery-hash1"}, 720 "bootx64.efi": []string{"shimhash1", "shimhash2"}, 721 }) 722 } 723 724 func (s *modeenvSuite) TestMarshalKernelCommandLines(c *C) { 725 c.Assert(s.mockModeenvPath, testutil.FileAbsent) 726 727 modeenv := &boot.Modeenv{ 728 Mode: "run", 729 RecoverySystem: "20191128", 730 CurrentKernelCommandLines: boot.BootCommandLines{ 731 `snapd_recovery_mode=run panic=-1 console=ttyS0,io,9600n8`, 732 `snapd_recovery_mode=run candidate panic=-1 console=ttyS0,io,9600n8`, 733 }, 734 } 735 err := modeenv.WriteTo(s.tmpdir) 736 c.Assert(err, IsNil) 737 738 c.Assert(s.mockModeenvPath, testutil.FileEquals, `mode=run 739 recovery_system=20191128 740 current_kernel_command_lines=["snapd_recovery_mode=run panic=-1 console=ttyS0,io,9600n8","snapd_recovery_mode=run candidate panic=-1 console=ttyS0,io,9600n8"] 741 `) 742 743 modeenvRead, err := boot.ReadModeenv(s.tmpdir) 744 c.Assert(err, IsNil) 745 c.Assert(modeenvRead.CurrentKernelCommandLines, DeepEquals, boot.BootCommandLines{ 746 `snapd_recovery_mode=run panic=-1 console=ttyS0,io,9600n8`, 747 `snapd_recovery_mode=run candidate panic=-1 console=ttyS0,io,9600n8`, 748 }) 749 }