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