github.com/kubiko/snapd@v0.0.0-20201013125620-d4f3094d9ddf/snapdtool/tool_test.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2017-2020 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 snapdtool_test 21 22 import ( 23 "fmt" 24 "io/ioutil" 25 "os" 26 "path/filepath" 27 "testing" 28 29 . "gopkg.in/check.v1" 30 31 "github.com/snapcore/snapd/dirs" 32 "github.com/snapcore/snapd/logger" 33 "github.com/snapcore/snapd/release" 34 "github.com/snapcore/snapd/snapdtool" 35 ) 36 37 func Test(t *testing.T) { TestingT(t) } 38 39 type toolSuite struct { 40 restoreExec func() 41 restoreLogger func() 42 execCalled int 43 lastExecArgv0 string 44 lastExecArgv []string 45 lastExecEnvv []string 46 fakeroot string 47 snapdPath string 48 corePath string 49 } 50 51 var _ = Suite(&toolSuite{}) 52 53 func (s *toolSuite) SetUpTest(c *C) { 54 s.restoreExec = snapdtool.MockSyscallExec(s.syscallExec) 55 _, s.restoreLogger = logger.MockLogger() 56 s.execCalled = 0 57 s.lastExecArgv0 = "" 58 s.lastExecArgv = nil 59 s.lastExecEnvv = nil 60 s.fakeroot = c.MkDir() 61 dirs.SetRootDir(s.fakeroot) 62 s.snapdPath = filepath.Join(dirs.SnapMountDir, "/snapd/42") 63 s.corePath = filepath.Join(dirs.SnapMountDir, "/core/21") 64 c.Assert(os.MkdirAll(filepath.Join(s.fakeroot, "proc/self"), 0755), IsNil) 65 } 66 67 func (s *toolSuite) TearDownTest(c *C) { 68 s.restoreExec() 69 s.restoreLogger() 70 } 71 72 func (s *toolSuite) syscallExec(argv0 string, argv []string, envv []string) (err error) { 73 s.execCalled++ 74 s.lastExecArgv0 = argv0 75 s.lastExecArgv = argv 76 s.lastExecEnvv = envv 77 return fmt.Errorf(">exec of %q in tests<", argv0) 78 } 79 80 func (s *toolSuite) fakeCoreVersion(c *C, coreDir, version string) { 81 p := filepath.Join(coreDir, "/usr/lib/snapd") 82 c.Assert(os.MkdirAll(p, 0755), IsNil) 83 c.Assert(ioutil.WriteFile(filepath.Join(p, "info"), []byte("VERSION="+version), 0644), IsNil) 84 } 85 86 func (s *toolSuite) fakeInternalTool(c *C, coreDir, toolName string) string { 87 s.fakeCoreVersion(c, coreDir, "42") 88 p := filepath.Join(coreDir, "/usr/lib/snapd", toolName) 89 c.Assert(ioutil.WriteFile(p, nil, 0755), IsNil) 90 91 return p 92 } 93 94 func (s *toolSuite) mockReExecingEnv() func() { 95 restore := []func(){ 96 release.MockOnClassic(true), 97 release.MockReleaseInfo(&release.OS{ID: "ubuntu"}), 98 snapdtool.MockCoreSnapdPaths(s.corePath, s.snapdPath), 99 snapdtool.MockVersion("2"), 100 } 101 102 return func() { 103 for i := len(restore) - 1; i >= 0; i-- { 104 restore[i]() 105 } 106 } 107 } 108 109 func (s *toolSuite) mockReExecFor(c *C, coreDir, toolName string) func() { 110 selfExe := filepath.Join(s.fakeroot, "proc/self/exe") 111 restore := []func(){ 112 s.mockReExecingEnv(), 113 snapdtool.MockSelfExe(selfExe), 114 } 115 s.fakeInternalTool(c, coreDir, toolName) 116 c.Assert(os.Symlink(filepath.Join("/usr/lib/snapd", toolName), selfExe), IsNil) 117 118 return func() { 119 for i := len(restore) - 1; i >= 0; i-- { 120 restore[i]() 121 } 122 } 123 } 124 125 func (s *toolSuite) TestDistroSupportsReExec(c *C) { 126 restore := release.MockOnClassic(true) 127 defer restore() 128 129 // Some distributions don't support re-execution yet. 130 for _, id := range []string{"fedora", "centos", "rhel", "opensuse", "suse", "poky"} { 131 restore = release.MockReleaseInfo(&release.OS{ID: id}) 132 defer restore() 133 c.Check(snapdtool.DistroSupportsReExec(), Equals, false, Commentf("ID: %q", id)) 134 } 135 136 // While others do. 137 for _, id := range []string{"debian", "ubuntu"} { 138 restore = release.MockReleaseInfo(&release.OS{ID: id}) 139 defer restore() 140 c.Check(snapdtool.DistroSupportsReExec(), Equals, true, Commentf("ID: %q", id)) 141 } 142 } 143 144 func (s *toolSuite) TestNonClassicDistroNoSupportsReExec(c *C) { 145 restore := release.MockOnClassic(false) 146 defer restore() 147 148 // no distro supports re-exec when not on classic :-) 149 for _, id := range []string{ 150 "fedora", "centos", "rhel", "opensuse", "suse", "poky", 151 "debian", "ubuntu", "arch", "archlinux", 152 } { 153 restore = release.MockReleaseInfo(&release.OS{ID: id}) 154 defer restore() 155 c.Check(snapdtool.DistroSupportsReExec(), Equals, false, Commentf("ID: %q", id)) 156 } 157 } 158 159 func (s *toolSuite) TestCoreSupportsReExecNoInfo(c *C) { 160 // there's no snapd/info in a just-created tmpdir :-p 161 c.Check(snapdtool.CoreSupportsReExec(c.MkDir()), Equals, false) 162 } 163 164 func (s *toolSuite) TestCoreSupportsReExecBadInfo(c *C) { 165 // can't read snapd/info if it's a directory 166 p := s.snapdPath + "/usr/lib/snapd/info" 167 c.Assert(os.MkdirAll(p, 0755), IsNil) 168 169 c.Check(snapdtool.CoreSupportsReExec(s.snapdPath), Equals, false) 170 } 171 172 func (s *toolSuite) TestCoreSupportsReExecBadInfoContent(c *C) { 173 // can't understand snapd/info if all it holds are potatoes 174 p := s.snapdPath + "/usr/lib/snapd" 175 c.Assert(os.MkdirAll(p, 0755), IsNil) 176 c.Assert(ioutil.WriteFile(p+"/info", []byte("potatoes"), 0644), IsNil) 177 178 c.Check(snapdtool.CoreSupportsReExec(s.snapdPath), Equals, false) 179 } 180 181 func (s *toolSuite) TestCoreSupportsReExecBadVersion(c *C) { 182 // can't understand snapd/info if all its version is gibberish 183 s.fakeCoreVersion(c, s.snapdPath, "0:") 184 185 c.Check(snapdtool.CoreSupportsReExec(s.snapdPath), Equals, false) 186 } 187 188 func (s *toolSuite) TestCoreSupportsReExecOldVersion(c *C) { 189 // can't re-exec if core version is too old 190 defer snapdtool.MockVersion("2")() 191 s.fakeCoreVersion(c, s.snapdPath, "0") 192 193 c.Check(snapdtool.CoreSupportsReExec(s.snapdPath), Equals, false) 194 } 195 196 func (s *toolSuite) TestCoreSupportsReExec(c *C) { 197 defer snapdtool.MockVersion("2")() 198 s.fakeCoreVersion(c, s.snapdPath, "9999") 199 200 c.Check(snapdtool.CoreSupportsReExec(s.snapdPath), Equals, true) 201 } 202 203 func (s *toolSuite) TestInternalToolPathNoReexec(c *C) { 204 restore := snapdtool.MockOsReadlink(func(string) (string, error) { 205 return filepath.Join(dirs.DistroLibExecDir, "snapd"), nil 206 }) 207 defer restore() 208 209 path, err := snapdtool.InternalToolPath("potato") 210 c.Check(err, IsNil) 211 c.Check(path, Equals, filepath.Join(dirs.DistroLibExecDir, "potato")) 212 } 213 214 func (s *toolSuite) TestInternalToolPathWithReexec(c *C) { 215 s.fakeInternalTool(c, s.snapdPath, "potato") 216 restore := snapdtool.MockOsReadlink(func(string) (string, error) { 217 return filepath.Join(s.snapdPath, "/usr/lib/snapd/snapd"), nil 218 }) 219 defer restore() 220 221 path, err := snapdtool.InternalToolPath("potato") 222 c.Check(err, IsNil) 223 c.Check(path, Equals, filepath.Join(dirs.SnapMountDir, "snapd/42/usr/lib/snapd/potato")) 224 } 225 226 func (s *toolSuite) TestInternalToolPathWithOtherLocation(c *C) { 227 s.fakeInternalTool(c, s.snapdPath, "potato") 228 restore := snapdtool.MockOsReadlink(func(string) (string, error) { 229 return filepath.Join("/tmp/tmp.foo_1234/usr/lib/snapd/snapd"), nil 230 }) 231 defer restore() 232 233 path, err := snapdtool.InternalToolPath("potato") 234 c.Check(err, IsNil) 235 c.Check(path, Equals, "/tmp/tmp.foo_1234/usr/lib/snapd/potato") 236 } 237 238 func (s *toolSuite) TestInternalToolSnapPathWithOtherLocation(c *C) { 239 restore := snapdtool.MockOsReadlink(func(string) (string, error) { 240 return filepath.Join("/tmp/tmp.foo_1234/usr/bin/snap"), nil 241 }) 242 defer restore() 243 244 path, err := snapdtool.InternalToolPath("potato") 245 c.Check(err, IsNil) 246 c.Check(path, Equals, "/tmp/tmp.foo_1234/usr/lib/snapd/potato") 247 } 248 249 func (s *toolSuite) TestInternalToolPathWithOtherCrazyLocation(c *C) { 250 restore := snapdtool.MockOsReadlink(func(string) (string, error) { 251 return filepath.Join("/usr/foo/usr/tmp/tmp.foo_1234/usr/bin/snap"), nil 252 }) 253 defer restore() 254 255 path, err := snapdtool.InternalToolPath("potato") 256 c.Check(err, IsNil) 257 c.Check(path, Equals, "/usr/foo/usr/tmp/tmp.foo_1234/usr/lib/snapd/potato") 258 } 259 260 func (s *toolSuite) TestInternalToolPathWithDevLocationFallback(c *C) { 261 restore := snapdtool.MockOsReadlink(func(string) (string, error) { 262 return filepath.Join("/home/dev/snapd/snapd"), nil 263 }) 264 defer restore() 265 266 path, err := snapdtool.InternalToolPath("potato") 267 c.Check(err, IsNil) 268 c.Check(path, Equals, filepath.Join(dirs.DistroLibExecDir, "potato")) 269 } 270 271 func (s *toolSuite) TestInternalToolPathWithOtherDevLocationWhenExecutable(c *C) { 272 restore := snapdtool.MockOsReadlink(func(string) (string, error) { 273 return filepath.Join(dirs.GlobalRootDir, "/tmp/snapd"), nil 274 }) 275 defer restore() 276 277 devTool := filepath.Join(dirs.GlobalRootDir, "/tmp/potato") 278 err := os.MkdirAll(filepath.Dir(devTool), 0755) 279 c.Assert(err, IsNil) 280 err = ioutil.WriteFile(devTool, []byte(""), 0755) 281 c.Assert(err, IsNil) 282 283 path, err := snapdtool.InternalToolPath("potato") 284 c.Check(err, IsNil) 285 c.Check(path, Equals, filepath.Join(dirs.GlobalRootDir, "/tmp/potato")) 286 } 287 288 func (s *toolSuite) TestInternalToolPathWithOtherDevLocationNonExecutable(c *C) { 289 restore := snapdtool.MockOsReadlink(func(string) (string, error) { 290 return filepath.Join(dirs.GlobalRootDir, "/tmp/snapd"), nil 291 }) 292 defer restore() 293 294 devTool := filepath.Join(dirs.GlobalRootDir, "/tmp/non-executable-potato") 295 err := os.MkdirAll(filepath.Dir(devTool), 0755) 296 c.Assert(err, IsNil) 297 err = ioutil.WriteFile(devTool, []byte(""), 0644) 298 c.Assert(err, IsNil) 299 300 path, err := snapdtool.InternalToolPath("non-executable-potato") 301 c.Check(err, IsNil) 302 c.Check(path, Equals, filepath.Join(dirs.DistroLibExecDir, "non-executable-potato")) 303 } 304 305 func (s *toolSuite) TestInternalToolPathSnapdPathReexec(c *C) { 306 restore := snapdtool.MockOsReadlink(func(string) (string, error) { 307 return filepath.Join(dirs.SnapMountDir, "core/111/usr/bin/snap"), nil 308 }) 309 defer restore() 310 311 p, err := snapdtool.InternalToolPath("snapd") 312 c.Assert(err, IsNil) 313 c.Check(p, Equals, filepath.Join(dirs.SnapMountDir, "/core/111/usr/lib/snapd/snapd")) 314 } 315 316 func (s *toolSuite) TestInternalToolPathSnapdSnap(c *C) { 317 restore := snapdtool.MockOsReadlink(func(string) (string, error) { 318 return filepath.Join(dirs.SnapMountDir, "snapd/22/usr/bin/snap"), nil 319 }) 320 defer restore() 321 p, err := snapdtool.InternalToolPath("snapd") 322 c.Assert(err, IsNil) 323 c.Check(p, Equals, filepath.Join(dirs.SnapMountDir, "/snapd/22/usr/lib/snapd/snapd")) 324 } 325 326 func (s *toolSuite) TestInternalToolPathWithLibexecdirLocation(c *C) { 327 defer dirs.SetRootDir(s.fakeroot) 328 restore := release.MockReleaseInfo(&release.OS{ID: "fedora"}) 329 defer restore() 330 // reload directory paths 331 dirs.SetRootDir("/") 332 333 restore = snapdtool.MockOsReadlink(func(string) (string, error) { 334 return filepath.Join("/usr/bin/snap"), nil 335 }) 336 defer restore() 337 338 path, err := snapdtool.InternalToolPath("potato") 339 c.Check(err, IsNil) 340 c.Check(path, Equals, filepath.Join("/usr/libexec/snapd/potato")) 341 } 342 343 func (s *toolSuite) TestExecInSnapdOrCoreSnap(c *C) { 344 defer s.mockReExecFor(c, s.snapdPath, "potato")() 345 346 c.Check(snapdtool.ExecInSnapdOrCoreSnap, PanicMatches, `>exec of "[^"]+/potato" in tests<`) 347 c.Check(s.execCalled, Equals, 1) 348 c.Check(s.lastExecArgv0, Equals, filepath.Join(s.snapdPath, "/usr/lib/snapd/potato")) 349 c.Check(s.lastExecArgv, DeepEquals, os.Args) 350 } 351 352 func (s *toolSuite) TestExecInOldCoreSnap(c *C) { 353 defer s.mockReExecFor(c, s.corePath, "potato")() 354 355 c.Check(snapdtool.ExecInSnapdOrCoreSnap, PanicMatches, `>exec of "[^"]+/potato" in tests<`) 356 c.Check(s.execCalled, Equals, 1) 357 c.Check(s.lastExecArgv0, Equals, filepath.Join(s.corePath, "/usr/lib/snapd/potato")) 358 c.Check(s.lastExecArgv, DeepEquals, os.Args) 359 } 360 361 func (s *toolSuite) TestExecInSnapdOrCoreSnapBailsNoCoreSupport(c *C) { 362 defer s.mockReExecFor(c, s.snapdPath, "potato")() 363 364 // no "info" -> no core support: 365 c.Assert(os.Remove(filepath.Join(s.snapdPath, "/usr/lib/snapd/info")), IsNil) 366 367 snapdtool.ExecInSnapdOrCoreSnap() 368 c.Check(s.execCalled, Equals, 0) 369 } 370 371 func (s *toolSuite) TestExecInSnapdOrCoreSnapMissingExe(c *C) { 372 defer s.mockReExecFor(c, s.snapdPath, "potato")() 373 374 // missing exe: 375 c.Assert(os.Remove(filepath.Join(s.snapdPath, "/usr/lib/snapd/potato")), IsNil) 376 377 snapdtool.ExecInSnapdOrCoreSnap() 378 c.Check(s.execCalled, Equals, 0) 379 } 380 381 func (s *toolSuite) TestExecInSnapdOrCoreSnapBadSelfExe(c *C) { 382 defer s.mockReExecFor(c, s.snapdPath, "potato")() 383 384 // missing self/exe: 385 c.Assert(os.Remove(filepath.Join(s.fakeroot, "proc/self/exe")), IsNil) 386 387 snapdtool.ExecInSnapdOrCoreSnap() 388 c.Check(s.execCalled, Equals, 0) 389 } 390 391 func (s *toolSuite) TestExecInSnapdOrCoreSnapBailsNoDistroSupport(c *C) { 392 defer s.mockReExecFor(c, s.snapdPath, "potato")() 393 394 // no distro support: 395 defer release.MockOnClassic(false)() 396 397 snapdtool.ExecInSnapdOrCoreSnap() 398 c.Check(s.execCalled, Equals, 0) 399 } 400 401 func (s *toolSuite) TestExecInSnapdOrCoreSnapNoDouble(c *C) { 402 selfExe := filepath.Join(s.fakeroot, "proc/self/exe") 403 err := os.Symlink(filepath.Join(s.fakeroot, "/snap/core/42/usr/lib/snapd"), selfExe) 404 c.Assert(err, IsNil) 405 snapdtool.MockSelfExe(selfExe) 406 407 snapdtool.ExecInSnapdOrCoreSnap() 408 c.Check(s.execCalled, Equals, 0) 409 } 410 411 func (s *toolSuite) TestExecInSnapdOrCoreSnapDisabled(c *C) { 412 defer s.mockReExecFor(c, s.snapdPath, "potato")() 413 414 os.Setenv("SNAP_REEXEC", "0") 415 defer os.Unsetenv("SNAP_REEXEC") 416 417 snapdtool.ExecInSnapdOrCoreSnap() 418 c.Check(s.execCalled, Equals, 0) 419 } 420 421 func (s *toolSuite) TestIsReexecd(c *C) { 422 mockedSelfExe := filepath.Join(s.fakeroot, "proc/self/exe") 423 restore := snapdtool.MockSelfExe(mockedSelfExe) 424 defer restore() 425 426 // pretend the binary reexecd from snap mount location 427 err := os.Symlink(filepath.Join(s.snapdPath, "usr/lib/snapd/snapd"), mockedSelfExe) 428 c.Assert(err, IsNil) 429 430 is, err := snapdtool.IsReexecd() 431 c.Assert(err, IsNil) 432 c.Assert(is, Equals, true) 433 434 err = os.Remove(mockedSelfExe) 435 c.Assert(err, IsNil) 436 // now it's not 437 err = os.Symlink(filepath.Join(dirs.DistroLibExecDir, "snapd"), mockedSelfExe) 438 c.Assert(err, IsNil) 439 440 is, err = snapdtool.IsReexecd() 441 c.Assert(err, IsNil) 442 c.Assert(is, Equals, false) 443 444 // trouble reading the symlink 445 err = os.Remove(mockedSelfExe) 446 c.Assert(err, IsNil) 447 448 is, err = snapdtool.IsReexecd() 449 c.Assert(err, ErrorMatches, ".*/proc/self/exe: no such file or directory") 450 c.Assert(is, Equals, false) 451 }