github.com/rigado/snapd@v2.42.5-go-mod+incompatible/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 "io/ioutil" 25 "os" 26 "os/exec" 27 "path/filepath" 28 29 . "gopkg.in/check.v1" 30 31 "github.com/snapcore/snapd/dirs" 32 "github.com/snapcore/snapd/osutil" 33 "github.com/snapcore/snapd/progress" 34 "github.com/snapcore/snapd/release" 35 "github.com/snapcore/snapd/snap" 36 "github.com/snapcore/snapd/snap/snaptest" 37 "github.com/snapcore/snapd/systemd" 38 "github.com/snapcore/snapd/testutil" 39 "github.com/snapcore/snapd/timings" 40 41 "github.com/snapcore/snapd/overlord/snapstate/backend" 42 ) 43 44 type linkSuite struct { 45 be backend.Backend 46 47 systemctlRestorer func() 48 49 perfTimings *timings.Timings 50 } 51 52 var _ = Suite(&linkSuite{}) 53 54 func (s *linkSuite) SetUpTest(c *C) { 55 dirs.SetRootDir(c.MkDir()) 56 57 s.perfTimings = timings.New(nil) 58 s.systemctlRestorer = systemd.MockSystemctl(func(cmd ...string) ([]byte, error) { 59 return []byte("ActiveState=inactive\n"), nil 60 }) 61 } 62 63 func (s *linkSuite) TearDownTest(c *C) { 64 dirs.SetRootDir("") 65 s.systemctlRestorer() 66 } 67 68 func (s *linkSuite) TestLinkDoUndoGenerateWrappers(c *C) { 69 const yaml = `name: hello 70 version: 1.0 71 environment: 72 KEY: value 73 74 apps: 75 bin: 76 command: bin 77 svc: 78 command: svc 79 daemon: simple 80 ` 81 info := snaptest.MockSnap(c, yaml, &snap.SideInfo{Revision: snap.R(11)}) 82 83 err := s.be.LinkSnap(info, nil, s.perfTimings) 84 c.Assert(err, IsNil) 85 86 l, err := filepath.Glob(filepath.Join(dirs.SnapBinariesDir, "*")) 87 c.Assert(err, IsNil) 88 c.Assert(l, HasLen, 1) 89 l, err = filepath.Glob(filepath.Join(dirs.SnapServicesDir, "*.service")) 90 c.Assert(err, IsNil) 91 c.Assert(l, HasLen, 1) 92 93 // undo will remove 94 err = s.be.UnlinkSnap(info, progress.Null) 95 c.Assert(err, IsNil) 96 97 l, err = filepath.Glob(filepath.Join(dirs.SnapBinariesDir, "*")) 98 c.Assert(err, IsNil) 99 c.Assert(l, HasLen, 0) 100 l, err = filepath.Glob(filepath.Join(dirs.SnapServicesDir, "*.service")) 101 c.Assert(err, IsNil) 102 c.Assert(l, HasLen, 0) 103 } 104 105 func (s *linkSuite) TestLinkDoUndoCurrentSymlink(c *C) { 106 const yaml = `name: hello 107 version: 1.0 108 ` 109 const contents = "" 110 111 info := snaptest.MockSnap(c, yaml, &snap.SideInfo{Revision: snap.R(11)}) 112 113 err := s.be.LinkSnap(info, nil, s.perfTimings) 114 c.Assert(err, IsNil) 115 116 mountDir := info.MountDir() 117 dataDir := info.DataDir() 118 currentActiveSymlink := filepath.Join(mountDir, "..", "current") 119 currentActiveDir, err := filepath.EvalSymlinks(currentActiveSymlink) 120 c.Assert(err, IsNil) 121 c.Assert(currentActiveDir, Equals, mountDir) 122 123 currentDataSymlink := filepath.Join(dataDir, "..", "current") 124 currentDataDir, err := filepath.EvalSymlinks(currentDataSymlink) 125 c.Assert(err, IsNil) 126 c.Assert(currentDataDir, Equals, dataDir) 127 128 // undo will remove the symlinks 129 err = s.be.UnlinkSnap(info, progress.Null) 130 c.Assert(err, IsNil) 131 132 c.Check(osutil.FileExists(currentActiveSymlink), Equals, false) 133 c.Check(osutil.FileExists(currentDataSymlink), Equals, false) 134 135 } 136 137 func (s *linkSuite) TestLinkDoIdempotent(c *C) { 138 // make sure that a retry wouldn't stumble on partial work 139 140 const yaml = `name: hello 141 version: 1.0 142 environment: 143 KEY: value 144 apps: 145 bin: 146 command: bin 147 svc: 148 command: svc 149 daemon: simple 150 ` 151 const contents = "" 152 153 info := snaptest.MockSnap(c, yaml, &snap.SideInfo{Revision: snap.R(11)}) 154 155 err := s.be.LinkSnap(info, nil, s.perfTimings) 156 c.Assert(err, IsNil) 157 158 err = s.be.LinkSnap(info, nil, s.perfTimings) 159 c.Assert(err, IsNil) 160 161 l, err := filepath.Glob(filepath.Join(dirs.SnapBinariesDir, "*")) 162 c.Assert(err, IsNil) 163 c.Assert(l, HasLen, 1) 164 l, err = filepath.Glob(filepath.Join(dirs.SnapServicesDir, "*.service")) 165 c.Assert(err, IsNil) 166 c.Assert(l, HasLen, 1) 167 168 mountDir := info.MountDir() 169 dataDir := info.DataDir() 170 currentActiveSymlink := filepath.Join(mountDir, "..", "current") 171 currentActiveDir, err := filepath.EvalSymlinks(currentActiveSymlink) 172 c.Assert(err, IsNil) 173 c.Assert(currentActiveDir, Equals, mountDir) 174 175 currentDataSymlink := filepath.Join(dataDir, "..", "current") 176 currentDataDir, err := filepath.EvalSymlinks(currentDataSymlink) 177 c.Assert(err, IsNil) 178 c.Assert(currentDataDir, Equals, dataDir) 179 } 180 181 func (s *linkSuite) TestLinkUndoIdempotent(c *C) { 182 // make sure that a retry wouldn't stumble on partial work 183 184 const yaml = `name: hello 185 version: 1.0 186 apps: 187 bin: 188 command: bin 189 svc: 190 command: svc 191 daemon: simple 192 ` 193 const contents = "" 194 195 info := snaptest.MockSnap(c, yaml, &snap.SideInfo{Revision: snap.R(11)}) 196 197 err := s.be.LinkSnap(info, nil, s.perfTimings) 198 c.Assert(err, IsNil) 199 200 err = s.be.UnlinkSnap(info, progress.Null) 201 c.Assert(err, IsNil) 202 203 err = s.be.UnlinkSnap(info, progress.Null) 204 c.Assert(err, IsNil) 205 206 // no wrappers 207 l, err := filepath.Glob(filepath.Join(dirs.SnapBinariesDir, "*")) 208 c.Assert(err, IsNil) 209 c.Assert(l, HasLen, 0) 210 l, err = filepath.Glob(filepath.Join(dirs.SnapServicesDir, "*.service")) 211 c.Assert(err, IsNil) 212 c.Assert(l, HasLen, 0) 213 214 // no symlinks 215 currentActiveSymlink := filepath.Join(info.MountDir(), "..", "current") 216 currentDataSymlink := filepath.Join(info.DataDir(), "..", "current") 217 c.Check(osutil.FileExists(currentActiveSymlink), Equals, false) 218 c.Check(osutil.FileExists(currentDataSymlink), Equals, false) 219 } 220 221 func (s *linkSuite) TestLinkFailsForUnsetRevision(c *C) { 222 info := &snap.Info{ 223 SuggestedName: "foo", 224 } 225 err := s.be.LinkSnap(info, nil, s.perfTimings) 226 c.Assert(err, ErrorMatches, `cannot link snap "foo" with unset revision`) 227 } 228 229 type linkCleanupSuite struct { 230 linkSuite 231 info *snap.Info 232 } 233 234 var _ = Suite(&linkCleanupSuite{}) 235 236 func (s *linkCleanupSuite) SetUpTest(c *C) { 237 s.linkSuite.SetUpTest(c) 238 239 const yaml = `name: hello 240 version: 1.0 241 environment: 242 KEY: value 243 244 apps: 245 foo: 246 command: foo 247 bar: 248 command: bar 249 svc: 250 command: svc 251 daemon: simple 252 ` 253 s.info = snaptest.MockSnap(c, yaml, &snap.SideInfo{Revision: snap.R(11)}) 254 255 guiDir := filepath.Join(s.info.MountDir(), "meta", "gui") 256 c.Assert(os.MkdirAll(guiDir, 0755), IsNil) 257 c.Assert(ioutil.WriteFile(filepath.Join(guiDir, "bin.desktop"), []byte(` 258 [Desktop Entry] 259 Name=bin 260 Icon=${SNAP}/bin.png 261 Exec=bin 262 `), 0644), IsNil) 263 264 r := systemd.MockSystemctl(func(...string) ([]byte, error) { 265 return nil, nil 266 }) 267 defer r() 268 269 // sanity checks 270 for _, d := range []string{dirs.SnapBinariesDir, dirs.SnapDesktopFilesDir, dirs.SnapServicesDir} { 271 os.MkdirAll(d, 0755) 272 l, err := filepath.Glob(filepath.Join(d, "*")) 273 c.Assert(err, IsNil, Commentf(d)) 274 c.Assert(l, HasLen, 0, Commentf(d)) 275 } 276 } 277 278 func (s *linkCleanupSuite) testLinkCleanupDirOnFail(c *C, dir string) { 279 c.Assert(os.Chmod(dir, 0), IsNil) 280 defer os.Chmod(dir, 0755) 281 282 err := s.be.LinkSnap(s.info, nil, s.perfTimings) 283 c.Assert(err, NotNil) 284 _, isPathError := err.(*os.PathError) 285 _, isLinkError := err.(*os.LinkError) 286 c.Assert(isPathError || isLinkError, Equals, true, Commentf("%T", err)) 287 288 for _, d := range []string{dirs.SnapBinariesDir, dirs.SnapDesktopFilesDir, dirs.SnapServicesDir} { 289 l, err := filepath.Glob(filepath.Join(d, "*")) 290 c.Check(err, IsNil, Commentf(d)) 291 c.Check(l, HasLen, 0, Commentf(d)) 292 } 293 } 294 295 func (s *linkCleanupSuite) TestLinkCleanupOnDesktopFail(c *C) { 296 s.testLinkCleanupDirOnFail(c, dirs.SnapDesktopFilesDir) 297 } 298 299 func (s *linkCleanupSuite) TestLinkCleanupOnBinariesFail(c *C) { 300 // this one is the trivial case _as the code stands today_, 301 // but nothing guarantees that ordering. 302 s.testLinkCleanupDirOnFail(c, dirs.SnapBinariesDir) 303 } 304 305 func (s *linkCleanupSuite) TestLinkCleanupOnServicesFail(c *C) { 306 s.testLinkCleanupDirOnFail(c, dirs.SnapServicesDir) 307 } 308 309 func (s *linkCleanupSuite) TestLinkCleanupOnMountDirFail(c *C) { 310 s.testLinkCleanupDirOnFail(c, filepath.Dir(s.info.MountDir())) 311 } 312 313 func (s *linkCleanupSuite) TestLinkCleanupOnSystemctlFail(c *C) { 314 r := systemd.MockSystemctl(func(...string) ([]byte, error) { 315 return nil, errors.New("ouchie") 316 }) 317 defer r() 318 319 err := s.be.LinkSnap(s.info, nil, s.perfTimings) 320 c.Assert(err, ErrorMatches, "ouchie") 321 322 for _, d := range []string{dirs.SnapBinariesDir, dirs.SnapDesktopFilesDir, dirs.SnapServicesDir} { 323 l, err := filepath.Glob(filepath.Join(d, "*")) 324 c.Check(err, IsNil, Commentf(d)) 325 c.Check(l, HasLen, 0, Commentf(d)) 326 } 327 } 328 329 func (s *linkCleanupSuite) TestLinkCleansUpDataDirAndSymlinksOnSymlinkFail(c *C) { 330 // sanity check 331 c.Assert(s.info.DataDir(), testutil.FileAbsent) 332 333 // the mountdir symlink is currently the last thing in 334 // LinkSnap that can make it fail 335 d := filepath.Dir(s.info.MountDir()) 336 c.Assert(os.Chmod(d, 0), IsNil) 337 defer os.Chmod(d, 0755) 338 339 err := s.be.LinkSnap(s.info, nil, s.perfTimings) 340 c.Assert(err, ErrorMatches, `(?i).*symlink.*permission denied.*`) 341 342 c.Check(s.info.DataDir(), testutil.FileAbsent) 343 c.Check(filepath.Join(s.info.DataDir(), "..", "current"), testutil.FileAbsent) 344 c.Check(filepath.Join(s.info.MountDir(), "..", "current"), testutil.FileAbsent) 345 } 346 347 func (s *linkCleanupSuite) TestLinkRunsUpdateFontconfigCachesClassic(c *C) { 348 current := filepath.Join(s.info.MountDir(), "..", "current") 349 350 for _, onClassic := range []bool{false, true} { 351 restore := release.MockOnClassic(onClassic) 352 defer restore() 353 354 var updateFontconfigCaches int 355 restore = backend.MockUpdateFontconfigCaches(func() error { 356 c.Assert(osutil.FileExists(current), Equals, false) 357 updateFontconfigCaches += 1 358 return nil 359 }) 360 defer restore() 361 362 err := s.be.LinkSnap(s.info, nil, s.perfTimings) 363 c.Assert(err, IsNil) 364 if onClassic { 365 c.Assert(updateFontconfigCaches, Equals, 1) 366 } else { 367 c.Assert(updateFontconfigCaches, Equals, 0) 368 } 369 c.Assert(os.Remove(current), IsNil) 370 } 371 } 372 373 func (s *linkCleanupSuite) TestLinkRunsUpdateFontconfigCachesCallsFromNewCurrent(c *C) { 374 restore := release.MockOnClassic(true) 375 defer restore() 376 377 const yaml = `name: core 378 version: 1.0 379 type: os 380 ` 381 // old version is 'current' 382 infoOld := snaptest.MockSnap(c, yaml, &snap.SideInfo{Revision: snap.R(11)}) 383 mountDirOld := infoOld.MountDir() 384 err := os.Symlink(filepath.Base(mountDirOld), filepath.Join(mountDirOld, "..", "current")) 385 c.Assert(err, IsNil) 386 387 err = os.MkdirAll(filepath.Join(mountDirOld, "bin"), 0755) 388 c.Assert(err, IsNil) 389 390 oldCmdV6 := testutil.MockCommand(c, filepath.Join(mountDirOld, "bin", "fc-cache-v6"), "") 391 oldCmdV7 := testutil.MockCommand(c, filepath.Join(mountDirOld, "bin", "fc-cache-v7"), "") 392 393 infoNew := snaptest.MockSnap(c, yaml, &snap.SideInfo{Revision: snap.R(12)}) 394 mountDirNew := infoNew.MountDir() 395 396 err = os.MkdirAll(filepath.Join(mountDirNew, "bin"), 0755) 397 c.Assert(err, IsNil) 398 399 newCmdV6 := testutil.MockCommand(c, filepath.Join(mountDirNew, "bin", "fc-cache-v6"), "") 400 newCmdV7 := testutil.MockCommand(c, filepath.Join(mountDirNew, "bin", "fc-cache-v7"), "") 401 402 // provide our own mock, osutil.CommandFromCore expects an ELF binary 403 restore = backend.MockCommandFromSystemSnap(func(name string, args ...string) (*exec.Cmd, error) { 404 cmd := filepath.Join(dirs.SnapMountDir, "core", "current", name) 405 c.Logf("command from core: %v", cmd) 406 return exec.Command(cmd, args...), nil 407 }) 408 defer restore() 409 410 err = s.be.LinkSnap(infoNew, nil, s.perfTimings) 411 c.Assert(err, IsNil) 412 413 c.Check(oldCmdV6.Calls(), HasLen, 0) 414 c.Check(oldCmdV7.Calls(), HasLen, 0) 415 416 c.Check(newCmdV6.Calls(), HasLen, 1) 417 c.Check(newCmdV7.Calls(), HasLen, 1) 418 }