github.com/hugh712/snapd@v0.0.0-20200910133618-1a99902bd583/overlord/snapstate/backend/link_test.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2014-2016 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 backend_test 21 22 import ( 23 "errors" 24 "fmt" 25 "io/ioutil" 26 "os" 27 "os/exec" 28 "path/filepath" 29 "strings" 30 31 . "gopkg.in/check.v1" 32 33 "github.com/snapcore/snapd/boot" 34 "github.com/snapcore/snapd/boot/boottest" 35 "github.com/snapcore/snapd/bootloader" 36 "github.com/snapcore/snapd/bootloader/bootloadertest" 37 "github.com/snapcore/snapd/dirs" 38 "github.com/snapcore/snapd/osutil" 39 "github.com/snapcore/snapd/progress" 40 "github.com/snapcore/snapd/release" 41 "github.com/snapcore/snapd/snap" 42 "github.com/snapcore/snapd/snap/snaptest" 43 "github.com/snapcore/snapd/systemd" 44 "github.com/snapcore/snapd/testutil" 45 "github.com/snapcore/snapd/timings" 46 47 "github.com/snapcore/snapd/overlord/snapstate/backend" 48 ) 49 50 type linkSuiteCommon struct { 51 testutil.BaseTest 52 53 be backend.Backend 54 55 perfTimings *timings.Timings 56 } 57 58 func (s *linkSuiteCommon) SetUpTest(c *C) { 59 s.BaseTest.SetUpTest(c) 60 dirs.SetRootDir(c.MkDir()) 61 s.AddCleanup(func() { dirs.SetRootDir("") }) 62 63 s.perfTimings = timings.New(nil) 64 restore := systemd.MockSystemctl(func(cmd ...string) ([]byte, error) { 65 return []byte("ActiveState=inactive\n"), nil 66 }) 67 s.AddCleanup(restore) 68 } 69 70 type linkSuite struct { 71 linkSuiteCommon 72 } 73 74 var _ = Suite(&linkSuite{}) 75 76 func (s *linkSuite) TestLinkSnapGivesLastActiveDisabledServicesToWrappers(c *C) { 77 const yaml = `name: hello 78 version: 1.0 79 environment: 80 KEY: value 81 82 apps: 83 bin: 84 command: bin 85 daemon: simple 86 svc: 87 command: svc 88 daemon: simple 89 ` 90 info := snaptest.MockSnap(c, yaml, &snap.SideInfo{Revision: snap.R(11)}) 91 92 svcsDisabled := []string{} 93 r := systemd.MockSystemctl(func(cmd ...string) ([]byte, error) { 94 // drop --root from the cmd 95 if len(cmd) >= 3 && cmd[0] == "--root" { 96 cmd = cmd[2:] 97 } 98 // if it's an enable, save the service name to check later 99 if len(cmd) >= 2 && cmd[0] == "enable" { 100 svcsDisabled = append(svcsDisabled, cmd[1]) 101 } 102 return nil, nil 103 }) 104 defer r() 105 106 linkCtx := backend.LinkContext{ 107 PrevDisabledServices: []string{"svc"}, 108 } 109 _, err := s.be.LinkSnap(info, mockDev, linkCtx, s.perfTimings) 110 c.Assert(err, IsNil) 111 112 c.Assert(svcsDisabled, DeepEquals, []string{"snap.hello.bin.service"}) 113 } 114 115 func (s *linkSuite) TestLinkDoUndoGenerateWrappers(c *C) { 116 const yaml = `name: hello 117 version: 1.0 118 environment: 119 KEY: value 120 121 apps: 122 bin: 123 command: bin 124 svc: 125 command: svc 126 daemon: simple 127 ` 128 info := snaptest.MockSnap(c, yaml, &snap.SideInfo{Revision: snap.R(11)}) 129 130 _, err := s.be.LinkSnap(info, mockDev, backend.LinkContext{}, s.perfTimings) 131 c.Assert(err, IsNil) 132 133 l, err := filepath.Glob(filepath.Join(dirs.SnapBinariesDir, "*")) 134 c.Assert(err, IsNil) 135 c.Assert(l, HasLen, 1) 136 l, err = filepath.Glob(filepath.Join(dirs.SnapServicesDir, "*.service")) 137 c.Assert(err, IsNil) 138 c.Assert(l, HasLen, 1) 139 140 // undo will remove 141 err = s.be.UnlinkSnap(info, backend.LinkContext{}, progress.Null) 142 c.Assert(err, IsNil) 143 144 l, err = filepath.Glob(filepath.Join(dirs.SnapBinariesDir, "*")) 145 c.Assert(err, IsNil) 146 c.Assert(l, HasLen, 0) 147 l, err = filepath.Glob(filepath.Join(dirs.SnapServicesDir, "*.service")) 148 c.Assert(err, IsNil) 149 c.Assert(l, HasLen, 0) 150 } 151 152 func (s *linkSuite) TestLinkDoUndoCurrentSymlink(c *C) { 153 const yaml = `name: hello 154 version: 1.0 155 ` 156 info := snaptest.MockSnap(c, yaml, &snap.SideInfo{Revision: snap.R(11)}) 157 158 reboot, err := s.be.LinkSnap(info, mockDev, backend.LinkContext{}, s.perfTimings) 159 c.Assert(err, IsNil) 160 161 c.Check(reboot, Equals, false) 162 163 mountDir := info.MountDir() 164 dataDir := info.DataDir() 165 currentActiveSymlink := filepath.Join(mountDir, "..", "current") 166 currentActiveDir, err := filepath.EvalSymlinks(currentActiveSymlink) 167 c.Assert(err, IsNil) 168 c.Assert(currentActiveDir, Equals, mountDir) 169 170 currentDataSymlink := filepath.Join(dataDir, "..", "current") 171 currentDataDir, err := filepath.EvalSymlinks(currentDataSymlink) 172 c.Assert(err, IsNil) 173 c.Assert(currentDataDir, Equals, dataDir) 174 175 // undo will remove the symlinks 176 err = s.be.UnlinkSnap(info, backend.LinkContext{}, progress.Null) 177 c.Assert(err, IsNil) 178 179 c.Check(osutil.FileExists(currentActiveSymlink), Equals, false) 180 c.Check(osutil.FileExists(currentDataSymlink), Equals, false) 181 182 } 183 184 func (s *linkSuite) TestLinkSetNextBoot(c *C) { 185 coreDev := boottest.MockDevice("base") 186 187 bl := boottest.MockUC16Bootenv(bootloadertest.Mock("mock", c.MkDir())) 188 bootloader.Force(bl) 189 defer bootloader.Force(nil) 190 bl.SetBootBase("base_1.snap") 191 192 const yaml = `name: base 193 version: 1.0 194 type: base 195 ` 196 info := snaptest.MockSnap(c, yaml, &snap.SideInfo{Revision: snap.R(11)}) 197 198 reboot, err := s.be.LinkSnap(info, coreDev, backend.LinkContext{}, s.perfTimings) 199 c.Assert(err, IsNil) 200 c.Check(reboot, Equals, true) 201 } 202 203 func (s *linkSuite) TestLinkDoIdempotent(c *C) { 204 // make sure that a retry wouldn't stumble on partial work 205 206 const yaml = `name: hello 207 version: 1.0 208 environment: 209 KEY: value 210 apps: 211 bin: 212 command: bin 213 svc: 214 command: svc 215 daemon: simple 216 ` 217 info := snaptest.MockSnap(c, yaml, &snap.SideInfo{Revision: snap.R(11)}) 218 219 _, err := s.be.LinkSnap(info, mockDev, backend.LinkContext{}, s.perfTimings) 220 c.Assert(err, IsNil) 221 222 _, err = s.be.LinkSnap(info, mockDev, backend.LinkContext{}, s.perfTimings) 223 c.Assert(err, IsNil) 224 225 l, err := filepath.Glob(filepath.Join(dirs.SnapBinariesDir, "*")) 226 c.Assert(err, IsNil) 227 c.Assert(l, HasLen, 1) 228 l, err = filepath.Glob(filepath.Join(dirs.SnapServicesDir, "*.service")) 229 c.Assert(err, IsNil) 230 c.Assert(l, HasLen, 1) 231 232 mountDir := info.MountDir() 233 dataDir := info.DataDir() 234 currentActiveSymlink := filepath.Join(mountDir, "..", "current") 235 currentActiveDir, err := filepath.EvalSymlinks(currentActiveSymlink) 236 c.Assert(err, IsNil) 237 c.Assert(currentActiveDir, Equals, mountDir) 238 239 currentDataSymlink := filepath.Join(dataDir, "..", "current") 240 currentDataDir, err := filepath.EvalSymlinks(currentDataSymlink) 241 c.Assert(err, IsNil) 242 c.Assert(currentDataDir, Equals, dataDir) 243 } 244 245 func (s *linkSuite) TestLinkUndoIdempotent(c *C) { 246 // make sure that a retry wouldn't stumble on partial work 247 248 const yaml = `name: hello 249 version: 1.0 250 apps: 251 bin: 252 command: bin 253 svc: 254 command: svc 255 daemon: simple 256 ` 257 info := snaptest.MockSnap(c, yaml, &snap.SideInfo{Revision: snap.R(11)}) 258 259 _, err := s.be.LinkSnap(info, mockDev, backend.LinkContext{}, s.perfTimings) 260 c.Assert(err, IsNil) 261 262 err = s.be.UnlinkSnap(info, backend.LinkContext{}, progress.Null) 263 c.Assert(err, IsNil) 264 265 err = s.be.UnlinkSnap(info, backend.LinkContext{}, progress.Null) 266 c.Assert(err, IsNil) 267 268 // no wrappers 269 l, err := filepath.Glob(filepath.Join(dirs.SnapBinariesDir, "*")) 270 c.Assert(err, IsNil) 271 c.Assert(l, HasLen, 0) 272 l, err = filepath.Glob(filepath.Join(dirs.SnapServicesDir, "*.service")) 273 c.Assert(err, IsNil) 274 c.Assert(l, HasLen, 0) 275 276 // no symlinks 277 currentActiveSymlink := filepath.Join(info.MountDir(), "..", "current") 278 currentDataSymlink := filepath.Join(info.DataDir(), "..", "current") 279 c.Check(osutil.FileExists(currentActiveSymlink), Equals, false) 280 c.Check(osutil.FileExists(currentDataSymlink), Equals, false) 281 } 282 283 func (s *linkSuite) TestLinkFailsForUnsetRevision(c *C) { 284 info := &snap.Info{ 285 SuggestedName: "foo", 286 } 287 _, err := s.be.LinkSnap(info, mockDev, backend.LinkContext{}, s.perfTimings) 288 c.Assert(err, ErrorMatches, `cannot link snap "foo" with unset revision`) 289 } 290 291 func mockSnapdSnapForLink(c *C) (snapdSnap *snap.Info, units [][]string) { 292 const yaml = `name: snapd 293 version: 1.0 294 type: snapd 295 ` 296 snapdUnits := [][]string{ 297 // system services 298 {"lib/systemd/system/snapd.service", "[Unit]\n[Service]\nExecStart=/usr/lib/snapd/snapd\n# X-Snapd-Snap: do-not-start"}, 299 {"lib/systemd/system/snapd.socket", "[Unit]\n[Socket]\nListenStream=/run/snapd.socket"}, 300 {"lib/systemd/system/snapd.snap-repair.timer", "[Unit]\n[Timer]\nOnCalendar=*-*-* 5,11,17,23:00"}, 301 // user services 302 {"usr/lib/systemd/user/snapd.session-agent.service", "[Unit]\n[Service]\nExecStart=/usr/bin/snap session-agent"}, 303 {"usr/lib/systemd/user/snapd.session-agent.socket", "[Unit]\n[Socket]\nListenStream=%t/snap-session.socket"}, 304 } 305 info := snaptest.MockSnapWithFiles(c, yaml, &snap.SideInfo{Revision: snap.R(11)}, snapdUnits) 306 return info, snapdUnits 307 } 308 309 func (s *linkSuite) TestLinkSnapdSnapOnCore(c *C) { 310 restore := release.MockOnClassic(false) 311 defer restore() 312 313 err := os.MkdirAll(dirs.SnapServicesDir, 0755) 314 c.Assert(err, IsNil) 315 err = os.MkdirAll(dirs.SnapUserServicesDir, 0755) 316 c.Assert(err, IsNil) 317 318 info, _ := mockSnapdSnapForLink(c) 319 320 reboot, err := s.be.LinkSnap(info, mockDev, backend.LinkContext{}, s.perfTimings) 321 c.Assert(err, IsNil) 322 c.Assert(reboot, Equals, false) 323 324 // system services 325 c.Check(filepath.Join(dirs.SnapServicesDir, "snapd.service"), testutil.FileContains, 326 fmt.Sprintf("[Service]\nExecStart=%s/usr/lib/snapd/snapd\n", info.MountDir())) 327 c.Check(filepath.Join(dirs.SnapServicesDir, "snapd.socket"), testutil.FileEquals, 328 "[Unit]\n[Socket]\nListenStream=/run/snapd.socket") 329 c.Check(filepath.Join(dirs.SnapServicesDir, "snapd.snap-repair.timer"), testutil.FileEquals, 330 "[Unit]\n[Timer]\nOnCalendar=*-*-* 5,11,17,23:00") 331 // user services 332 c.Check(filepath.Join(dirs.SnapUserServicesDir, "snapd.session-agent.service"), testutil.FileContains, 333 fmt.Sprintf("[Service]\nExecStart=%s/usr/bin/snap session-agent", info.MountDir())) 334 c.Check(filepath.Join(dirs.SnapUserServicesDir, "snapd.session-agent.socket"), testutil.FileEquals, 335 "[Unit]\n[Socket]\nListenStream=%t/snap-session.socket") 336 // auxiliary mount unit 337 mountUnit := fmt.Sprintf(`[Unit] 338 Description=Make the snapd snap tooling available for the system 339 Before=snapd.service 340 341 [Mount] 342 What=%s/usr/lib/snapd 343 Where=/usr/lib/snapd 344 Type=none 345 Options=bind 346 347 [Install] 348 WantedBy=snapd.service 349 `, info.MountDir()) 350 c.Check(filepath.Join(dirs.SnapServicesDir, "usr-lib-snapd.mount"), testutil.FileEquals, mountUnit) 351 } 352 353 type linkCleanupSuite struct { 354 linkSuiteCommon 355 info *snap.Info 356 } 357 358 var _ = Suite(&linkCleanupSuite{}) 359 360 func (s *linkCleanupSuite) SetUpTest(c *C) { 361 s.linkSuiteCommon.SetUpTest(c) 362 363 const yaml = `name: hello 364 version: 1.0 365 environment: 366 KEY: value 367 368 apps: 369 foo: 370 command: foo 371 bar: 372 command: bar 373 svc: 374 command: svc 375 daemon: simple 376 ` 377 cmd := testutil.MockCommand(c, "update-desktop-database", "") 378 s.AddCleanup(cmd.Restore) 379 380 s.info = snaptest.MockSnap(c, yaml, &snap.SideInfo{Revision: snap.R(11)}) 381 382 guiDir := filepath.Join(s.info.MountDir(), "meta", "gui") 383 c.Assert(os.MkdirAll(guiDir, 0755), IsNil) 384 c.Assert(ioutil.WriteFile(filepath.Join(guiDir, "bin.desktop"), []byte(` 385 [Desktop Entry] 386 Name=bin 387 Icon=${SNAP}/bin.png 388 Exec=bin 389 `), 0644), IsNil) 390 391 // sanity checks 392 for _, d := range []string{dirs.SnapBinariesDir, dirs.SnapDesktopFilesDir, dirs.SnapServicesDir} { 393 os.MkdirAll(d, 0755) 394 l, err := filepath.Glob(filepath.Join(d, "*")) 395 c.Assert(err, IsNil, Commentf(d)) 396 c.Assert(l, HasLen, 0, Commentf(d)) 397 } 398 } 399 400 func (s *linkCleanupSuite) testLinkCleanupDirOnFail(c *C, dir string) { 401 c.Assert(os.Chmod(dir, 0555), IsNil) 402 defer os.Chmod(dir, 0755) 403 404 _, err := s.be.LinkSnap(s.info, mockDev, backend.LinkContext{}, s.perfTimings) 405 c.Assert(err, NotNil) 406 _, isPathError := err.(*os.PathError) 407 _, isLinkError := err.(*os.LinkError) 408 c.Assert(isPathError || isLinkError, Equals, true, Commentf("%#v", err)) 409 410 for _, d := range []string{dirs.SnapBinariesDir, dirs.SnapDesktopFilesDir, dirs.SnapServicesDir} { 411 l, err := filepath.Glob(filepath.Join(d, "*")) 412 c.Check(err, IsNil, Commentf(d)) 413 c.Check(l, HasLen, 0, Commentf(d)) 414 } 415 } 416 417 func (s *linkCleanupSuite) TestLinkCleanupOnDesktopFail(c *C) { 418 s.testLinkCleanupDirOnFail(c, dirs.SnapDesktopFilesDir) 419 } 420 421 func (s *linkCleanupSuite) TestLinkCleanupOnBinariesFail(c *C) { 422 // this one is the trivial case _as the code stands today_, 423 // but nothing guarantees that ordering. 424 s.testLinkCleanupDirOnFail(c, dirs.SnapBinariesDir) 425 } 426 427 func (s *linkCleanupSuite) TestLinkCleanupOnServicesFail(c *C) { 428 s.testLinkCleanupDirOnFail(c, dirs.SnapServicesDir) 429 } 430 431 func (s *linkCleanupSuite) TestLinkCleanupOnMountDirFail(c *C) { 432 s.testLinkCleanupDirOnFail(c, filepath.Dir(s.info.MountDir())) 433 } 434 435 func (s *linkCleanupSuite) TestLinkCleanupOnSystemctlFail(c *C) { 436 r := systemd.MockSystemctl(func(...string) ([]byte, error) { 437 return nil, errors.New("ouchie") 438 }) 439 defer r() 440 441 _, err := s.be.LinkSnap(s.info, mockDev, backend.LinkContext{}, s.perfTimings) 442 c.Assert(err, ErrorMatches, "ouchie") 443 444 for _, d := range []string{dirs.SnapBinariesDir, dirs.SnapDesktopFilesDir, dirs.SnapServicesDir} { 445 l, err := filepath.Glob(filepath.Join(d, "*")) 446 c.Check(err, IsNil, Commentf(d)) 447 c.Check(l, HasLen, 0, Commentf(d)) 448 } 449 } 450 451 func (s *linkCleanupSuite) TestLinkCleansUpDataDirAndSymlinksOnSymlinkFail(c *C) { 452 // sanity check 453 c.Assert(s.info.DataDir(), testutil.FileAbsent) 454 455 // the mountdir symlink is currently the last thing in LinkSnap that can 456 // make it fail, creating a symlink requires write permissions 457 d := filepath.Dir(s.info.MountDir()) 458 c.Assert(os.Chmod(d, 0555), IsNil) 459 defer os.Chmod(d, 0755) 460 461 _, err := s.be.LinkSnap(s.info, mockDev, backend.LinkContext{}, s.perfTimings) 462 c.Assert(err, ErrorMatches, `(?i).*symlink.*permission denied.*`) 463 464 c.Check(s.info.DataDir(), testutil.FileAbsent) 465 c.Check(filepath.Join(s.info.DataDir(), "..", "current"), testutil.FileAbsent) 466 c.Check(filepath.Join(s.info.MountDir(), "..", "current"), testutil.FileAbsent) 467 } 468 469 func (s *linkCleanupSuite) TestLinkRunsUpdateFontconfigCachesClassic(c *C) { 470 current := filepath.Join(s.info.MountDir(), "..", "current") 471 472 for _, dev := range []boot.Device{mockDev, mockClassicDev} { 473 var updateFontconfigCaches int 474 restore := backend.MockUpdateFontconfigCaches(func() error { 475 c.Assert(osutil.FileExists(current), Equals, false) 476 updateFontconfigCaches += 1 477 return nil 478 }) 479 defer restore() 480 481 _, err := s.be.LinkSnap(s.info, dev, backend.LinkContext{}, s.perfTimings) 482 c.Assert(err, IsNil) 483 if dev.Classic() { 484 c.Assert(updateFontconfigCaches, Equals, 1) 485 } else { 486 c.Assert(updateFontconfigCaches, Equals, 0) 487 } 488 c.Assert(os.Remove(current), IsNil) 489 } 490 } 491 492 func (s *linkCleanupSuite) TestLinkRunsUpdateFontconfigCachesCallsFromNewCurrent(c *C) { 493 const yaml = `name: core 494 version: 1.0 495 type: os 496 ` 497 // old version is 'current' 498 infoOld := snaptest.MockSnap(c, yaml, &snap.SideInfo{Revision: snap.R(11)}) 499 mountDirOld := infoOld.MountDir() 500 err := os.Symlink(filepath.Base(mountDirOld), filepath.Join(mountDirOld, "..", "current")) 501 c.Assert(err, IsNil) 502 503 oldCmdV6 := testutil.MockCommand(c, filepath.Join(mountDirOld, "bin", "fc-cache-v6"), "") 504 oldCmdV7 := testutil.MockCommand(c, filepath.Join(mountDirOld, "bin", "fc-cache-v7"), "") 505 506 infoNew := snaptest.MockSnap(c, yaml, &snap.SideInfo{Revision: snap.R(12)}) 507 mountDirNew := infoNew.MountDir() 508 509 newCmdV6 := testutil.MockCommand(c, filepath.Join(mountDirNew, "bin", "fc-cache-v6"), "") 510 newCmdV7 := testutil.MockCommand(c, filepath.Join(mountDirNew, "bin", "fc-cache-v7"), "") 511 512 // provide our own mock, osutil.CommandFromCore expects an ELF binary 513 restore := backend.MockCommandFromSystemSnap(func(name string, args ...string) (*exec.Cmd, error) { 514 cmd := filepath.Join(dirs.SnapMountDir, "core", "current", name) 515 c.Logf("command from core: %v", cmd) 516 return exec.Command(cmd, args...), nil 517 }) 518 defer restore() 519 520 _, err = s.be.LinkSnap(infoNew, mockClassicDev, backend.LinkContext{}, s.perfTimings) 521 c.Assert(err, IsNil) 522 523 c.Check(oldCmdV6.Calls(), HasLen, 0) 524 c.Check(oldCmdV7.Calls(), HasLen, 0) 525 526 c.Check(newCmdV6.Calls(), DeepEquals, [][]string{ 527 {"fc-cache-v6", "--system-only"}, 528 }) 529 c.Check(newCmdV7.Calls(), DeepEquals, [][]string{ 530 {"fc-cache-v7", "--system-only"}, 531 }) 532 } 533 534 func (s *linkCleanupSuite) testLinkCleanupFailedSnapdSnapOnCorePastWrappers(c *C, firstInstall bool) { 535 dirs.SetRootDir(c.MkDir()) 536 defer dirs.SetRootDir("") 537 538 info, _ := mockSnapdSnapForLink(c) 539 540 err := os.MkdirAll(dirs.SnapServicesDir, 0755) 541 c.Assert(err, IsNil) 542 err = os.MkdirAll(dirs.SnapUserServicesDir, 0755) 543 c.Assert(err, IsNil) 544 545 // make snap mount dir non-writable, triggers error updating the current symlink 546 snapdSnapDir := filepath.Dir(info.MountDir()) 547 548 if firstInstall { 549 err := os.Remove(filepath.Join(snapdSnapDir, "1234")) 550 c.Assert(err == nil || os.IsNotExist(err), Equals, true, Commentf("err: %v, err")) 551 } else { 552 err := os.Mkdir(filepath.Join(snapdSnapDir, "1234"), 0755) 553 c.Assert(err, IsNil) 554 } 555 556 // triggers permission denied error when symlink is manipulated 557 err = os.Chmod(snapdSnapDir, 0555) 558 c.Assert(err, IsNil) 559 defer os.Chmod(snapdSnapDir, 0755) 560 561 linkCtx := backend.LinkContext{ 562 FirstInstall: firstInstall, 563 } 564 reboot, err := s.be.LinkSnap(info, mockDev, linkCtx, s.perfTimings) 565 c.Assert(err, ErrorMatches, fmt.Sprintf("symlink %s /.*/snapd/current: permission denied", info.Revision)) 566 c.Assert(reboot, Equals, false) 567 568 checker := testutil.FilePresent 569 if firstInstall { 570 checker = testutil.FileAbsent 571 } 572 573 // system services 574 c.Check(filepath.Join(dirs.SnapServicesDir, "snapd.service"), checker) 575 c.Check(filepath.Join(dirs.SnapServicesDir, "snapd.socket"), checker) 576 c.Check(filepath.Join(dirs.SnapServicesDir, "snapd.snap-repair.timer"), checker) 577 // user services 578 c.Check(filepath.Join(dirs.SnapUserServicesDir, "snapd.session-agent.service"), checker) 579 c.Check(filepath.Join(dirs.SnapUserServicesDir, "snapd.session-agent.socket"), checker) 580 c.Check(filepath.Join(dirs.SnapServicesDir, "usr-lib-snapd.mount"), checker) 581 } 582 583 func (s *linkCleanupSuite) TestLinkCleanupFailedSnapdSnapFirstInstallOnCore(c *C) { 584 // test failure mode when snapd is first installed, its units were 585 // correctly written and corresponding services were started, but 586 // current symlink failed 587 restore := release.MockOnClassic(false) 588 defer restore() 589 s.testLinkCleanupFailedSnapdSnapOnCorePastWrappers(c, true) 590 } 591 592 func (s *linkCleanupSuite) TestLinkCleanupFailedSnapdSnapNonFirstInstallOnCore(c *C) { 593 // test failure mode when a new revision of snapd is installed, its was 594 // units were correctly written and corresponding services were started, 595 // but current symlink failed 596 restore := release.MockOnClassic(false) 597 defer restore() 598 s.testLinkCleanupFailedSnapdSnapOnCorePastWrappers(c, false) 599 } 600 601 type snapdOnCoreUnlinkSuite struct { 602 linkSuiteCommon 603 } 604 605 var _ = Suite(&snapdOnCoreUnlinkSuite{}) 606 607 func (s *snapdOnCoreUnlinkSuite) TestUndoGeneratedWrappers(c *C) { 608 restore := release.MockOnClassic(false) 609 defer restore() 610 restore = release.MockReleaseInfo(&release.OS{ID: "ubuntu"}) 611 defer restore() 612 613 err := os.MkdirAll(dirs.SnapServicesDir, 0755) 614 c.Assert(err, IsNil) 615 err = os.MkdirAll(dirs.SnapUserServicesDir, 0755) 616 c.Assert(err, IsNil) 617 618 info, snapdUnits := mockSnapdSnapForLink(c) 619 // all generated untis 620 generatedSnapdUnits := append(snapdUnits, 621 []string{"usr-lib-snapd.mount", "mount unit"}) 622 623 toEtcUnitPath := func(p string) string { 624 if strings.HasPrefix(p, "usr/lib/systemd/user") { 625 return filepath.Join(dirs.SnapUserServicesDir, filepath.Base(p)) 626 } 627 return filepath.Join(dirs.SnapServicesDir, filepath.Base(p)) 628 } 629 630 reboot, err := s.be.LinkSnap(info, mockDev, backend.LinkContext{}, s.perfTimings) 631 c.Assert(err, IsNil) 632 c.Assert(reboot, Equals, false) 633 634 // sanity checks 635 c.Check(filepath.Join(dirs.SnapServicesDir, "snapd.service"), testutil.FileContains, 636 fmt.Sprintf("[Service]\nExecStart=%s/usr/lib/snapd/snapd\n", info.MountDir())) 637 // expecting all generated untis to be present 638 for _, entry := range generatedSnapdUnits { 639 c.Check(toEtcUnitPath(entry[0]), testutil.FilePresent) 640 } 641 642 linkCtx := backend.LinkContext{ 643 FirstInstall: true, 644 } 645 err = s.be.UnlinkSnap(info, linkCtx, nil) 646 c.Assert(err, IsNil) 647 648 // generated wrappers should be gone now 649 for _, entry := range generatedSnapdUnits { 650 c.Check(toEtcUnitPath(entry[0]), testutil.FileAbsent) 651 } 652 653 // unlink is idempotent 654 err = s.be.UnlinkSnap(info, linkCtx, nil) 655 c.Assert(err, IsNil) 656 } 657 658 func (s *snapdOnCoreUnlinkSuite) TestUnlinkNonFirstSnapdOnCoreDoesNothing(c *C) { 659 restore := release.MockOnClassic(false) 660 defer restore() 661 662 err := os.MkdirAll(dirs.SnapServicesDir, 0755) 663 c.Assert(err, IsNil) 664 err = os.MkdirAll(dirs.SnapUserServicesDir, 0755) 665 c.Assert(err, IsNil) 666 667 info, _ := mockSnapdSnapForLink(c) 668 669 units := [][]string{ 670 {filepath.Join(dirs.SnapServicesDir, "snapd.service"), "precious"}, 671 {filepath.Join(dirs.SnapServicesDir, "snapd.socket"), "precious"}, 672 {filepath.Join(dirs.SnapServicesDir, "snapd.snap-repair.timer"), "precious"}, 673 {filepath.Join(dirs.SnapServicesDir, "usr-lib-snapd.mount"), "precious"}, 674 {filepath.Join(dirs.SnapUserServicesDir, "snapd.session-agent.service"), "precious"}, 675 {filepath.Join(dirs.SnapUserServicesDir, "snapd.session-agentsocket"), "precious"}, 676 } 677 // content list uses absolute paths already 678 snaptest.PopulateDir("/", units) 679 err = s.be.UnlinkSnap(info, backend.LinkContext{FirstInstall: false}, nil) 680 c.Assert(err, IsNil) 681 for _, unit := range units { 682 c.Check(unit[0], testutil.FileEquals, "precious") 683 } 684 }