github.com/anonymouse64/snapd@v0.0.0-20210824153203-04c4c42d842d/cmd/snap-preseed/main_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 main_test 21 22 import ( 23 "fmt" 24 "io/ioutil" 25 "os" 26 "path/filepath" 27 "strings" 28 "testing" 29 30 "github.com/jessevdk/go-flags" 31 . "gopkg.in/check.v1" 32 33 "github.com/snapcore/snapd/asserts" 34 "github.com/snapcore/snapd/asserts/assertstest" 35 "github.com/snapcore/snapd/cmd/snap-preseed" 36 "github.com/snapcore/snapd/cmd/snaplock/runinhibit" 37 "github.com/snapcore/snapd/dirs" 38 "github.com/snapcore/snapd/osutil" 39 "github.com/snapcore/snapd/osutil/squashfs" 40 apparmor_sandbox "github.com/snapcore/snapd/sandbox/apparmor" 41 "github.com/snapcore/snapd/seed" 42 "github.com/snapcore/snapd/snap" 43 "github.com/snapcore/snapd/testutil" 44 "github.com/snapcore/snapd/timings" 45 ) 46 47 func Test(t *testing.T) { TestingT(t) } 48 49 var _ = Suite(&startPreseedSuite{}) 50 51 type startPreseedSuite struct { 52 testutil.BaseTest 53 } 54 55 func (s *startPreseedSuite) SetUpTest(c *C) { 56 s.BaseTest.SetUpTest(c) 57 restore := squashfs.MockNeedsFuse(false) 58 s.BaseTest.AddCleanup(restore) 59 } 60 61 func (s *startPreseedSuite) TearDownTest(c *C) { 62 s.BaseTest.TearDownTest(c) 63 dirs.SetRootDir("") 64 } 65 66 func testParser(c *C) *flags.Parser { 67 parser := main.Parser() 68 _, err := parser.ParseArgs([]string{}) 69 c.Assert(err, IsNil) 70 return parser 71 } 72 73 func mockVersionFiles(c *C, rootDir1, version1, rootDir2, version2 string) { 74 versions := []string{version1, version2} 75 for i, root := range []string{rootDir1, rootDir2} { 76 c.Assert(os.MkdirAll(filepath.Join(root, dirs.CoreLibExecDir), 0755), IsNil) 77 infoFile := filepath.Join(root, dirs.CoreLibExecDir, "info") 78 c.Assert(ioutil.WriteFile(infoFile, []byte(fmt.Sprintf("VERSION=%s", versions[i])), 0644), IsNil) 79 } 80 } 81 82 func mockChrootDirs(c *C, rootDir string, apparmorDir bool) func() { 83 if apparmorDir { 84 c.Assert(os.MkdirAll(filepath.Join(rootDir, "/sys/kernel/security/apparmor"), 0755), IsNil) 85 } 86 mockMountInfo := `912 920 0:57 / ${rootDir}/proc rw,nosuid,nodev,noexec,relatime - proc proc rw 87 914 913 0:7 / ${rootDir}/sys/kernel/security rw,nosuid,nodev,noexec,relatime master:8 - securityfs securityfs rw 88 915 920 0:58 / ${rootDir}/dev rw,relatime - tmpfs none rw,size=492k,mode=755,uid=100000,gid=100000 89 ` 90 return osutil.MockMountInfo(strings.Replace(mockMountInfo, "${rootDir}", rootDir, -1)) 91 } 92 93 func (s *startPreseedSuite) TestRequiresRoot(c *C) { 94 restore := main.MockOsGetuid(func() int { 95 return 1000 96 }) 97 defer restore() 98 99 parser := testParser(c) 100 c.Check(main.Run(parser, []string{"/"}), ErrorMatches, `must be run as root`) 101 } 102 103 func (s *startPreseedSuite) TestMissingArg(c *C) { 104 restore := main.MockOsGetuid(func() int { 105 return 0 106 }) 107 defer restore() 108 109 parser := testParser(c) 110 c.Check(main.Run(parser, nil), ErrorMatches, `need chroot path as argument`) 111 } 112 113 func (s *startPreseedSuite) TestChrootDoesntExist(c *C) { 114 restore := main.MockOsGetuid(func() int { return 0 }) 115 defer restore() 116 117 parser := testParser(c) 118 c.Check(main.Run(parser, []string{"/non-existing-dir"}), ErrorMatches, `cannot verify "/non-existing-dir": is not a directory`) 119 } 120 121 func (s *startPreseedSuite) TestChrootValidationUnhappy(c *C) { 122 restore := main.MockOsGetuid(func() int { return 0 }) 123 defer restore() 124 125 tmpDir := c.MkDir() 126 defer osutil.MockMountInfo("")() 127 128 parser := testParser(c) 129 c.Check(main.Run(parser, []string{tmpDir}), ErrorMatches, "cannot preseed without the following mountpoints:\n - .*/dev\n - .*/proc\n - .*/sys/kernel/security") 130 } 131 132 func (s *startPreseedSuite) TestRunPreseedMountUnhappy(c *C) { 133 tmpDir := c.MkDir() 134 dirs.SetRootDir(tmpDir) 135 defer mockChrootDirs(c, tmpDir, true)() 136 137 restoreOsGuid := main.MockOsGetuid(func() int { return 0 }) 138 defer restoreOsGuid() 139 140 restoreSyscallChroot := main.MockSyscallChroot(func(path string) error { return nil }) 141 defer restoreSyscallChroot() 142 143 mockMountCmd := testutil.MockCommand(c, "mount", `echo "something went wrong" 144 exit 32 145 `) 146 defer mockMountCmd.Restore() 147 148 targetSnapdRoot := filepath.Join(tmpDir, "target-core-mounted-here") 149 restoreMountPath := main.MockSnapdMountPath(targetSnapdRoot) 150 defer restoreMountPath() 151 152 restoreSystemSnapFromSeed := main.MockSystemSnapFromSeed(func(string) (string, error) { return "/a/core.snap", nil }) 153 defer restoreSystemSnapFromSeed() 154 155 parser := testParser(c) 156 c.Check(main.Run(parser, []string{tmpDir}), ErrorMatches, `cannot mount .+ at .+ in preseed mode: exit status 32\n'mount -t squashfs -o ro,x-gdu.hide,x-gvfs-hide /a/core.snap .*/target-core-mounted-here' failed with: something went wrong\n`) 157 } 158 159 func (s *startPreseedSuite) TestChrootValidationUnhappyNoApparmor(c *C) { 160 restore := main.MockOsGetuid(func() int { return 0 }) 161 defer restore() 162 163 tmpDir := c.MkDir() 164 defer mockChrootDirs(c, tmpDir, false)() 165 166 parser := testParser(c) 167 c.Check(main.Run(parser, []string{tmpDir}), ErrorMatches, `cannot preseed without access to ".*sys/kernel/security/apparmor"`) 168 } 169 170 func (s *startPreseedSuite) TestChrootValidationAlreadyPreseeded(c *C) { 171 restore := main.MockOsGetuid(func() int { return 0 }) 172 defer restore() 173 174 tmpDir := c.MkDir() 175 snapdDir := filepath.Dir(dirs.SnapStateFile) 176 c.Assert(os.MkdirAll(filepath.Join(tmpDir, snapdDir), 0755), IsNil) 177 c.Assert(ioutil.WriteFile(filepath.Join(tmpDir, dirs.SnapStateFile), nil, os.ModePerm), IsNil) 178 179 parser := testParser(c) 180 c.Check(main.Run(parser, []string{tmpDir}), ErrorMatches, fmt.Sprintf("the system at %q appears to be preseeded, pass --reset flag to clean it up", tmpDir)) 181 } 182 183 func (s *startPreseedSuite) TestChrootFailure(c *C) { 184 restoreOsGuid := main.MockOsGetuid(func() int { return 0 }) 185 defer restoreOsGuid() 186 187 restoreSyscallChroot := main.MockSyscallChroot(func(path string) error { 188 return fmt.Errorf("FAIL: %s", path) 189 }) 190 defer restoreSyscallChroot() 191 192 tmpDir := c.MkDir() 193 defer mockChrootDirs(c, tmpDir, true)() 194 195 parser := testParser(c) 196 c.Check(main.Run(parser, []string{tmpDir}), ErrorMatches, fmt.Sprintf("cannot chroot into %s: FAIL: %s", tmpDir, tmpDir)) 197 } 198 199 func (s *startPreseedSuite) TestRunPreseedHappy(c *C) { 200 tmpDir := c.MkDir() 201 dirs.SetRootDir(tmpDir) 202 defer mockChrootDirs(c, tmpDir, true)() 203 204 restoreOsGuid := main.MockOsGetuid(func() int { return 0 }) 205 defer restoreOsGuid() 206 207 restoreSyscallChroot := main.MockSyscallChroot(func(path string) error { return nil }) 208 defer restoreSyscallChroot() 209 210 mockMountCmd := testutil.MockCommand(c, "mount", "") 211 defer mockMountCmd.Restore() 212 213 mockUmountCmd := testutil.MockCommand(c, "umount", "") 214 defer mockUmountCmd.Restore() 215 216 targetSnapdRoot := filepath.Join(tmpDir, "target-core-mounted-here") 217 restoreMountPath := main.MockSnapdMountPath(targetSnapdRoot) 218 defer restoreMountPath() 219 220 restoreSystemSnapFromSeed := main.MockSystemSnapFromSeed(func(string) (string, error) { return "/a/core.snap", nil }) 221 defer restoreSystemSnapFromSeed() 222 223 mockTargetSnapd := testutil.MockCommand(c, filepath.Join(targetSnapdRoot, "usr/lib/snapd/snapd"), `#!/bin/sh 224 if [ "$SNAPD_PRESEED" != "1" ]; then 225 exit 1 226 fi 227 `) 228 defer mockTargetSnapd.Restore() 229 230 mockSnapdFromDeb := testutil.MockCommand(c, filepath.Join(tmpDir, "usr/lib/snapd/snapd"), `#!/bin/sh 231 exit 1 232 `) 233 defer mockSnapdFromDeb.Restore() 234 235 // snapd from the snap is newer than deb 236 mockVersionFiles(c, targetSnapdRoot, "2.44.0", tmpDir, "2.41.0") 237 238 parser := testParser(c) 239 c.Check(main.Run(parser, []string{tmpDir}), IsNil) 240 241 c.Assert(mockMountCmd.Calls(), HasLen, 1) 242 // note, tmpDir, targetSnapdRoot are contactenated again cause we're not really chrooting in the test 243 // and mocking dirs.RootDir 244 c.Check(mockMountCmd.Calls()[0], DeepEquals, []string{"mount", "-t", "squashfs", "-o", "ro,x-gdu.hide,x-gvfs-hide", "/a/core.snap", filepath.Join(tmpDir, targetSnapdRoot)}) 245 246 c.Assert(mockTargetSnapd.Calls(), HasLen, 1) 247 c.Check(mockTargetSnapd.Calls()[0], DeepEquals, []string{"snapd"}) 248 249 c.Assert(mockSnapdFromDeb.Calls(), HasLen, 0) 250 251 // relative chroot path works too 252 tmpDirPath, relativeChroot := filepath.Split(tmpDir) 253 pwd, err := os.Getwd() 254 c.Assert(err, IsNil) 255 defer func() { 256 os.Chdir(pwd) 257 }() 258 c.Assert(os.Chdir(tmpDirPath), IsNil) 259 c.Check(main.Run(parser, []string{relativeChroot}), IsNil) 260 } 261 262 func (s *startPreseedSuite) TestRunPreseedHappyDebVersionIsNewer(c *C) { 263 tmpDir := c.MkDir() 264 dirs.SetRootDir(tmpDir) 265 defer mockChrootDirs(c, tmpDir, true)() 266 267 restoreOsGuid := main.MockOsGetuid(func() int { return 0 }) 268 defer restoreOsGuid() 269 270 restoreSyscallChroot := main.MockSyscallChroot(func(path string) error { return nil }) 271 defer restoreSyscallChroot() 272 273 mockMountCmd := testutil.MockCommand(c, "mount", "") 274 defer mockMountCmd.Restore() 275 276 mockUmountCmd := testutil.MockCommand(c, "umount", "") 277 defer mockUmountCmd.Restore() 278 279 targetSnapdRoot := filepath.Join(tmpDir, "target-core-mounted-here") 280 restoreMountPath := main.MockSnapdMountPath(targetSnapdRoot) 281 defer restoreMountPath() 282 283 restoreSystemSnapFromSeed := main.MockSystemSnapFromSeed(func(string) (string, error) { return "/a/core.snap", nil }) 284 defer restoreSystemSnapFromSeed() 285 286 c.Assert(os.MkdirAll(filepath.Join(targetSnapdRoot, "usr/lib/snapd/"), 0755), IsNil) 287 mockSnapdFromSnap := testutil.MockCommand(c, filepath.Join(targetSnapdRoot, "usr/lib/snapd/snapd"), `#!/bin/sh 288 exit 1 289 `) 290 defer mockSnapdFromSnap.Restore() 291 292 mockSnapdFromDeb := testutil.MockCommand(c, filepath.Join(tmpDir, "usr/lib/snapd/snapd"), `#!/bin/sh 293 if [ "$SNAPD_PRESEED" != "1" ]; then 294 exit 1 295 fi 296 `) 297 defer mockSnapdFromDeb.Restore() 298 299 // snapd from the deb is newer than snap 300 mockVersionFiles(c, targetSnapdRoot, "2.44.0", tmpDir, "2.45.0") 301 302 parser := testParser(c) 303 c.Check(main.Run(parser, []string{tmpDir}), IsNil) 304 305 c.Assert(mockMountCmd.Calls(), HasLen, 1) 306 // note, tmpDir, targetSnapdRoot are contactenated again cause we're not really chrooting in the test 307 // and mocking dirs.RootDir 308 c.Check(mockMountCmd.Calls()[0], DeepEquals, []string{"mount", "-t", "squashfs", "-o", "ro,x-gdu.hide,x-gvfs-hide", "/a/core.snap", filepath.Join(tmpDir, targetSnapdRoot)}) 309 310 c.Assert(mockSnapdFromDeb.Calls(), HasLen, 1) 311 c.Check(mockSnapdFromDeb.Calls()[0], DeepEquals, []string{"snapd"}) 312 c.Assert(mockSnapdFromSnap.Calls(), HasLen, 0) 313 } 314 315 type Fake16Seed struct { 316 AssertsModel *asserts.Model 317 Essential []*seed.Snap 318 LoadMetaErr error 319 LoadAssertionsErr error 320 UsesSnapd bool 321 } 322 323 // Fake implementation of seed.Seed interface 324 325 func mockClassicModel() *asserts.Model { 326 headers := map[string]interface{}{ 327 "type": "model", 328 "authority-id": "brand", 329 "series": "16", 330 "brand-id": "brand", 331 "model": "classicbaz-3000", 332 "classic": "true", 333 "timestamp": "2018-01-01T08:00:00+00:00", 334 } 335 return assertstest.FakeAssertion(headers, nil).(*asserts.Model) 336 } 337 338 func (fs *Fake16Seed) LoadAssertions(db asserts.RODatabase, commitTo func(*asserts.Batch) error) error { 339 return fs.LoadAssertionsErr 340 } 341 342 func (fs *Fake16Seed) Model() *asserts.Model { 343 return fs.AssertsModel 344 } 345 346 func (fs *Fake16Seed) Brand() (*asserts.Account, error) { 347 headers := map[string]interface{}{ 348 "type": "account", 349 "account-id": "brand", 350 "display-name": "fake brand", 351 "username": "brand", 352 "timestamp": "2018-01-01T08:00:00+00:00", 353 } 354 return assertstest.FakeAssertion(headers, nil).(*asserts.Account), nil 355 } 356 357 func (fs *Fake16Seed) LoadEssentialMeta(essentialTypes []snap.Type, tm timings.Measurer) error { 358 panic("unexpected") 359 } 360 361 func (fs *Fake16Seed) LoadMeta(tm timings.Measurer) error { 362 return fs.LoadMetaErr 363 } 364 365 func (fs *Fake16Seed) UsesSnapdSnap() bool { 366 return fs.UsesSnapd 367 } 368 369 func (fs *Fake16Seed) EssentialSnaps() []*seed.Snap { 370 return fs.Essential 371 } 372 373 func (fs *Fake16Seed) ModeSnaps(mode string) ([]*seed.Snap, error) { 374 return nil, nil 375 } 376 377 func (s *startPreseedSuite) TestSystemSnapFromSeed(c *C) { 378 tmpDir := c.MkDir() 379 380 restore := main.MockSeedOpen(func(rootDir, label string) (seed.Seed, error) { 381 return &Fake16Seed{ 382 AssertsModel: mockClassicModel(), 383 Essential: []*seed.Snap{{Path: "/some/path/core", SideInfo: &snap.SideInfo{RealName: "core"}}}, 384 }, nil 385 }) 386 defer restore() 387 388 path, err := main.SystemSnapFromSeed(tmpDir) 389 c.Assert(err, IsNil) 390 c.Check(path, Equals, "/some/path/core") 391 } 392 393 func (s *startPreseedSuite) TestSystemSnapFromSnapdSeed(c *C) { 394 tmpDir := c.MkDir() 395 396 restore := main.MockSeedOpen(func(rootDir, label string) (seed.Seed, error) { 397 return &Fake16Seed{ 398 AssertsModel: mockClassicModel(), 399 Essential: []*seed.Snap{{Path: "/some/path/snapd.snap", SideInfo: &snap.SideInfo{RealName: "snapd"}}}, 400 UsesSnapd: true, 401 }, nil 402 }) 403 defer restore() 404 405 path, err := main.SystemSnapFromSeed(tmpDir) 406 c.Assert(err, IsNil) 407 c.Check(path, Equals, "/some/path/snapd.snap") 408 } 409 410 func (s *startPreseedSuite) TestSystemSnapFromSeedOpenError(c *C) { 411 tmpDir := c.MkDir() 412 413 restore := main.MockSeedOpen(func(rootDir, label string) (seed.Seed, error) { return nil, fmt.Errorf("fail") }) 414 defer restore() 415 416 _, err := main.SystemSnapFromSeed(tmpDir) 417 c.Assert(err, ErrorMatches, "fail") 418 } 419 420 func (s *startPreseedSuite) TestSystemSnapFromSeedErrors(c *C) { 421 tmpDir := c.MkDir() 422 423 fakeSeed := &Fake16Seed{} 424 fakeSeed.AssertsModel = mockClassicModel() 425 426 restore := main.MockSeedOpen(func(rootDir, label string) (seed.Seed, error) { return fakeSeed, nil }) 427 defer restore() 428 429 fakeSeed.Essential = []*seed.Snap{{Path: "", SideInfo: &snap.SideInfo{RealName: "core"}}} 430 _, err := main.SystemSnapFromSeed(tmpDir) 431 c.Assert(err, ErrorMatches, "core snap not found") 432 433 fakeSeed.Essential = []*seed.Snap{{Path: "/some/path", SideInfo: &snap.SideInfo{RealName: "foosnap"}}} 434 _, err = main.SystemSnapFromSeed(tmpDir) 435 c.Assert(err, ErrorMatches, "core snap not found") 436 437 fakeSeed.LoadMetaErr = fmt.Errorf("load meta failed") 438 _, err = main.SystemSnapFromSeed(tmpDir) 439 c.Assert(err, ErrorMatches, "load meta failed") 440 441 fakeSeed.LoadMetaErr = nil 442 fakeSeed.LoadAssertionsErr = fmt.Errorf("load assertions failed") 443 _, err = main.SystemSnapFromSeed(tmpDir) 444 c.Assert(err, ErrorMatches, "load assertions failed") 445 } 446 447 func (s *startPreseedSuite) TestClassicRequired(c *C) { 448 tmpDir := c.MkDir() 449 450 headers := map[string]interface{}{ 451 "type": "model", 452 "authority-id": "brand", 453 "series": "16", 454 "brand-id": "brand", 455 "model": "baz-3000", 456 "architecture": "armhf", 457 "gadget": "brand-gadget", 458 "kernel": "kernel", 459 "timestamp": "2018-01-01T08:00:00+00:00", 460 } 461 462 fakeSeed := &Fake16Seed{} 463 fakeSeed.AssertsModel = assertstest.FakeAssertion(headers, nil).(*asserts.Model) 464 465 restore := main.MockSeedOpen(func(rootDir, label string) (seed.Seed, error) { return fakeSeed, nil }) 466 defer restore() 467 468 _, err := main.SystemSnapFromSeed(tmpDir) 469 c.Assert(err, ErrorMatches, "preseeding is only supported on classic systems") 470 } 471 472 func (s *startPreseedSuite) TestRunPreseedUnsupportedVersion(c *C) { 473 tmpDir := c.MkDir() 474 dirs.SetRootDir(tmpDir) 475 c.Assert(os.MkdirAll(filepath.Join(tmpDir, "usr/lib/snapd/"), 0755), IsNil) 476 defer mockChrootDirs(c, tmpDir, true)() 477 478 restoreOsGuid := main.MockOsGetuid(func() int { return 0 }) 479 defer restoreOsGuid() 480 481 restoreSyscallChroot := main.MockSyscallChroot(func(path string) error { return nil }) 482 defer restoreSyscallChroot() 483 484 mockMountCmd := testutil.MockCommand(c, "mount", "") 485 defer mockMountCmd.Restore() 486 487 targetSnapdRoot := filepath.Join(tmpDir, "target-core-mounted-here") 488 restoreMountPath := main.MockSnapdMountPath(targetSnapdRoot) 489 defer restoreMountPath() 490 491 restoreSystemSnapFromSeed := main.MockSystemSnapFromSeed(func(string) (string, error) { return "/a/core.snap", nil }) 492 defer restoreSystemSnapFromSeed() 493 494 c.Assert(os.MkdirAll(filepath.Join(targetSnapdRoot, "usr/lib/snapd/"), 0755), IsNil) 495 mockTargetSnapd := testutil.MockCommand(c, filepath.Join(targetSnapdRoot, "usr/lib/snapd/snapd"), "") 496 defer mockTargetSnapd.Restore() 497 498 infoFile := filepath.Join(targetSnapdRoot, dirs.CoreLibExecDir, "info") 499 c.Assert(ioutil.WriteFile(infoFile, []byte("VERSION=2.43.0"), 0644), IsNil) 500 501 // simulate snapd version from the deb 502 infoFile = filepath.Join(filepath.Join(tmpDir, dirs.CoreLibExecDir, "info")) 503 c.Assert(ioutil.WriteFile(infoFile, []byte("VERSION=2.41.0"), 0644), IsNil) 504 505 parser := testParser(c) 506 c.Check(main.Run(parser, []string{tmpDir}), ErrorMatches, 507 `snapd 2.43.0 from the target system does not support preseeding, the minimum required version is 2.43.3\+`) 508 } 509 510 func (s *startPreseedSuite) TestChooseTargetSnapdVersion(c *C) { 511 tmpDir := c.MkDir() 512 dirs.SetRootDir(tmpDir) 513 c.Assert(os.MkdirAll(filepath.Join(tmpDir, "usr/lib/snapd/"), 0755), IsNil) 514 515 targetSnapdRoot := filepath.Join(tmpDir, "target-core-mounted-here") 516 c.Assert(os.MkdirAll(filepath.Join(targetSnapdRoot, "usr/lib/snapd/"), 0755), IsNil) 517 restoreMountPath := main.MockSnapdMountPath(targetSnapdRoot) 518 defer restoreMountPath() 519 520 var versions = []struct { 521 fromSnap string 522 fromDeb string 523 expectedPath string 524 expectedVersion string 525 expectedErr string 526 }{ 527 { 528 fromDeb: "2.44.0", 529 fromSnap: "2.45.3+git123", 530 // snap version wins 531 expectedVersion: "2.45.3+git123", 532 expectedPath: filepath.Join(tmpDir, "target-core-mounted-here/usr/lib/snapd/snapd"), 533 }, 534 { 535 fromDeb: "2.44.0", 536 fromSnap: "2.44.0", 537 // snap version wins 538 expectedVersion: "2.44.0", 539 expectedPath: filepath.Join(tmpDir, "target-core-mounted-here/usr/lib/snapd/snapd"), 540 }, 541 { 542 fromDeb: "2.45.1+20.04", 543 fromSnap: "2.45.1", 544 // deb version wins 545 expectedVersion: "2.45.1+20.04", 546 expectedPath: filepath.Join(tmpDir, "usr/lib/snapd/snapd"), 547 }, 548 { 549 fromDeb: "2.45.2", 550 fromSnap: "2.45.1", 551 // deb version wins 552 expectedVersion: "2.45.2", 553 expectedPath: filepath.Join(tmpDir, "usr/lib/snapd/snapd"), 554 }, 555 { 556 fromSnap: "2.45.1", 557 expectedErr: fmt.Sprintf("cannot open snapd info file %q.*", filepath.Join(tmpDir, "usr/lib/snapd/info")), 558 }, 559 { 560 fromDeb: "2.45.1", 561 expectedErr: fmt.Sprintf("cannot open snapd info file %q.*", filepath.Join(tmpDir, "target-core-mounted-here/usr/lib/snapd/info")), 562 }, 563 } 564 565 for _, test := range versions { 566 infoFile := filepath.Join(tmpDir, "usr/lib/snapd/info") 567 os.Remove(infoFile) 568 if test.fromDeb != "" { 569 c.Assert(ioutil.WriteFile(infoFile, []byte(fmt.Sprintf("VERSION=%s", test.fromDeb)), 0644), IsNil) 570 } 571 infoFile = filepath.Join(targetSnapdRoot, "usr/lib/snapd/info") 572 os.Remove(infoFile) 573 if test.fromSnap != "" { 574 c.Assert(ioutil.WriteFile(infoFile, []byte(fmt.Sprintf("VERSION=%s", test.fromSnap)), 0644), IsNil) 575 } 576 577 targetSnapd, err := main.ChooseTargetSnapdVersion() 578 if test.expectedErr != "" { 579 c.Assert(err, ErrorMatches, test.expectedErr) 580 } else { 581 c.Assert(err, IsNil) 582 c.Assert(targetSnapd, NotNil) 583 path, version := main.SnapdPathAndVersion(targetSnapd) 584 c.Check(path, Equals, test.expectedPath) 585 c.Check(version, Equals, test.expectedVersion) 586 } 587 } 588 } 589 590 func (s *startPreseedSuite) TestRunPreseedAgainstFilesystemRoot(c *C) { 591 restore := main.MockOsGetuid(func() int { return 0 }) 592 defer restore() 593 594 parser := testParser(c) 595 c.Assert(main.Run(parser, []string{"/"}), ErrorMatches, `cannot run snap-preseed against /`) 596 } 597 598 func (s *startPreseedSuite) TestReset(c *C) { 599 restore := main.MockOsGetuid(func() int { return 0 }) 600 defer restore() 601 602 startDir, err := os.Getwd() 603 c.Assert(err, IsNil) 604 defer func() { 605 os.Chdir(startDir) 606 }() 607 608 for _, isRelative := range []bool{false, true} { 609 tmpDir := c.MkDir() 610 resetDirArg := tmpDir 611 if isRelative { 612 var parentDir string 613 parentDir, resetDirArg = filepath.Split(tmpDir) 614 os.Chdir(parentDir) 615 } 616 617 // mock some preseeding artifacts 618 artifacts := []struct { 619 path string 620 // if symlinkTarget is not empty, then a path -> symlinkTarget symlink 621 // will be created instead of a regular file. 622 symlinkTarget string 623 }{ 624 {dirs.SnapStateFile, ""}, 625 {dirs.SnapSystemKeyFile, ""}, 626 {filepath.Join(dirs.SnapDesktopFilesDir, "foo.desktop"), ""}, 627 {filepath.Join(dirs.SnapDesktopIconsDir, "foo.png"), ""}, 628 {filepath.Join(dirs.SnapMountPolicyDir, "foo.fstab"), ""}, 629 {filepath.Join(dirs.SnapBlobDir, "foo.snap"), ""}, 630 {filepath.Join(dirs.SnapUdevRulesDir, "foo-snap.bar.rules"), ""}, 631 {filepath.Join(dirs.SnapDBusSystemPolicyDir, "snap.foo.bar.conf"), ""}, 632 {filepath.Join(dirs.SnapDBusSessionServicesDir, "org.example.Session.service"), ""}, 633 {filepath.Join(dirs.SnapDBusSystemServicesDir, "org.example.System.service"), ""}, 634 {filepath.Join(dirs.SnapServicesDir, "snap.foo.service"), ""}, 635 {filepath.Join(dirs.SnapServicesDir, "snap.foo.timer"), ""}, 636 {filepath.Join(dirs.SnapServicesDir, "snap.foo.socket"), ""}, 637 {filepath.Join(dirs.SnapServicesDir, "snap-foo.mount"), ""}, 638 {filepath.Join(dirs.SnapServicesDir, "multi-user.target.wants", "snap-foo.mount"), ""}, 639 {filepath.Join(dirs.SnapDataDir, "foo", "bar"), ""}, 640 {filepath.Join(dirs.SnapCacheDir, "foocache", "bar"), ""}, 641 {filepath.Join(apparmor_sandbox.CacheDir, "foo", "bar"), ""}, 642 {filepath.Join(dirs.SnapAppArmorDir, "foo"), ""}, 643 {filepath.Join(dirs.SnapAssertsDBDir, "foo"), ""}, 644 {filepath.Join(dirs.FeaturesDir, "foo"), ""}, 645 {filepath.Join(dirs.SnapDeviceDir, "foo-1", "bar"), ""}, 646 {filepath.Join(dirs.SnapCookieDir, "foo"), ""}, 647 {filepath.Join(dirs.SnapSeqDir, "foo.json"), ""}, 648 {filepath.Join(dirs.SnapMountDir, "foo", "bin"), ""}, 649 {filepath.Join(dirs.SnapSeccompDir, "foo.bin"), ""}, 650 {filepath.Join(runinhibit.InhibitDir, "foo.lock"), ""}, 651 // bash-completion symlinks 652 {filepath.Join(dirs.CompletersDir, "foo.bar"), "/a/snapd/complete.sh"}, 653 {filepath.Join(dirs.CompletersDir, "foo"), "foo.bar"}, 654 } 655 656 for _, art := range artifacts { 657 fullPath := filepath.Join(tmpDir, art.path) 658 // create parent dir 659 c.Assert(os.MkdirAll(filepath.Dir(fullPath), 0755), IsNil) 660 if art.symlinkTarget != "" { 661 // note, symlinkTarget is not relative to tmpDir 662 c.Assert(os.Symlink(art.symlinkTarget, fullPath), IsNil) 663 } else { 664 c.Assert(ioutil.WriteFile(fullPath, nil, os.ModePerm), IsNil) 665 } 666 } 667 668 checkArtifacts := func(exists bool) { 669 for _, art := range artifacts { 670 fullPath := filepath.Join(tmpDir, art.path) 671 if art.symlinkTarget != "" { 672 c.Check(osutil.IsSymlink(fullPath), Equals, exists, Commentf("offending symlink: %s", fullPath)) 673 } else { 674 c.Check(osutil.FileExists(fullPath), Equals, exists, Commentf("offending file: %s", fullPath)) 675 } 676 } 677 } 678 679 // sanity 680 checkArtifacts(true) 681 682 snapdDir := filepath.Dir(dirs.SnapStateFile) 683 c.Assert(os.MkdirAll(filepath.Join(tmpDir, snapdDir), 0755), IsNil) 684 c.Assert(ioutil.WriteFile(filepath.Join(tmpDir, dirs.SnapStateFile), nil, os.ModePerm), IsNil) 685 686 parser := testParser(c) 687 c.Assert(main.Run(parser, []string{"--reset", resetDirArg}), IsNil) 688 689 checkArtifacts(false) 690 691 // running reset again is ok 692 parser = testParser(c) 693 c.Assert(main.Run(parser, []string{"--reset", resetDirArg}), IsNil) 694 695 // reset complains if target directory doesn't exist 696 c.Assert(main.Run(parser, []string{"--reset", "/non/existing/chrootpath"}), ErrorMatches, `cannot reset non-existing directory "/non/existing/chrootpath"`) 697 698 // reset complains if target is not a directory 699 dummyFile := filepath.Join(resetDirArg, "foo") 700 c.Assert(ioutil.WriteFile(dummyFile, nil, os.ModePerm), IsNil) 701 err = main.Run(parser, []string{"--reset", dummyFile}) 702 // the error message is always with an absolute file, so make the path 703 // absolute if we are running the relative test to properly match 704 if isRelative { 705 var err2 error 706 dummyFile, err2 = filepath.Abs(dummyFile) 707 c.Assert(err2, IsNil) 708 } 709 c.Assert(err, ErrorMatches, fmt.Sprintf(`cannot reset %q, it is not a directory`, dummyFile)) 710 } 711 712 } 713 714 func (s *startPreseedSuite) TestReadInfoSanity(c *C) { 715 var called bool 716 inf := &snap.Info{ 717 BadInterfaces: make(map[string]string), 718 Plugs: map[string]*snap.PlugInfo{ 719 "foo": { 720 Interface: "bad"}, 721 }, 722 } 723 724 // set a dummy sanitize method. 725 snap.SanitizePlugsSlots = func(*snap.Info) { called = true } 726 727 parser := testParser(c) 728 tmpDir := c.MkDir() 729 _ = main.Run(parser, []string{tmpDir}) 730 731 // real sanitize method should be set after Run() 732 snap.SanitizePlugsSlots(inf) 733 c.Assert(called, Equals, false) 734 c.Assert(inf.BadInterfaces, HasLen, 1) 735 }