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