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