github.com/bugraaydogar/snapd@v0.0.0-20210315170335-8c70bb858939/snap/squashfs/squashfs_test.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2014-2015 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 squashfs_test 21 22 import ( 23 "errors" 24 "fmt" 25 "io/ioutil" 26 "math" 27 "os" 28 "os/exec" 29 "path/filepath" 30 "strings" 31 "syscall" 32 "testing" 33 "time" 34 35 . "gopkg.in/check.v1" 36 "gopkg.in/yaml.v2" 37 38 "github.com/snapcore/snapd/dirs" 39 "github.com/snapcore/snapd/osutil" 40 "github.com/snapcore/snapd/snap" 41 "github.com/snapcore/snapd/snap/snapdir" 42 "github.com/snapcore/snapd/snap/squashfs" 43 "github.com/snapcore/snapd/testutil" 44 ) 45 46 // Hook up check.v1 into the "go test" runner 47 func Test(t *testing.T) { TestingT(t) } 48 49 type SquashfsTestSuite struct { 50 oldStdout, oldStderr, outf *os.File 51 testutil.BaseTest 52 } 53 54 var _ = Suite(&SquashfsTestSuite{}) 55 56 func makeSnap(c *C, manifest, data string) *squashfs.Snap { 57 cur, _ := os.Getwd() 58 return makeSnapInDir(c, cur, manifest, data) 59 } 60 61 func makeSnapContents(c *C, manifest, data string) string { 62 tmp := c.MkDir() 63 err := os.MkdirAll(filepath.Join(tmp, "meta", "hooks", "dir"), 0755) 64 c.Assert(err, IsNil) 65 66 // our regular snap.yaml 67 err = ioutil.WriteFile(filepath.Join(tmp, "meta", "snap.yaml"), []byte(manifest), 0644) 68 c.Assert(err, IsNil) 69 70 // some hooks 71 err = ioutil.WriteFile(filepath.Join(tmp, "meta", "hooks", "foo-hook"), nil, 0755) 72 c.Assert(err, IsNil) 73 err = ioutil.WriteFile(filepath.Join(tmp, "meta", "hooks", "bar-hook"), nil, 0755) 74 c.Assert(err, IsNil) 75 // And a file in another directory in there, just for testing (not a valid 76 // hook) 77 err = ioutil.WriteFile(filepath.Join(tmp, "meta", "hooks", "dir", "baz"), nil, 0755) 78 c.Assert(err, IsNil) 79 80 // some empty directories 81 err = os.MkdirAll(filepath.Join(tmp, "food", "bard", "bazd"), 0755) 82 c.Assert(err, IsNil) 83 84 // some data 85 err = ioutil.WriteFile(filepath.Join(tmp, "data.bin"), []byte(data), 0644) 86 c.Assert(err, IsNil) 87 88 return tmp 89 } 90 91 func makeSnapInDir(c *C, dir, manifest, data string) *squashfs.Snap { 92 snapType := "app" 93 var m struct { 94 Type string `yaml:"type"` 95 } 96 if err := yaml.Unmarshal([]byte(manifest), &m); err == nil && m.Type != "" { 97 snapType = m.Type 98 } 99 100 tmp := makeSnapContents(c, manifest, data) 101 // build it 102 sn := squashfs.New(filepath.Join(dir, "foo.snap")) 103 err := sn.Build(tmp, &squashfs.BuildOpts{SnapType: snapType}) 104 c.Assert(err, IsNil) 105 106 return sn 107 } 108 109 func (s *SquashfsTestSuite) SetUpTest(c *C) { 110 d := c.MkDir() 111 dirs.SetRootDir(d) 112 err := os.Chdir(d) 113 c.Assert(err, IsNil) 114 115 restore := osutil.MockMountInfo("") 116 s.AddCleanup(restore) 117 118 s.outf, err = ioutil.TempFile(c.MkDir(), "") 119 c.Assert(err, IsNil) 120 s.oldStdout, s.oldStderr = os.Stdout, os.Stderr 121 os.Stdout, os.Stderr = s.outf, s.outf 122 } 123 124 func (s *SquashfsTestSuite) TearDownTest(c *C) { 125 os.Stdout, os.Stderr = s.oldStdout, s.oldStderr 126 127 // this ensures things were quiet 128 _, err := s.outf.Seek(0, 0) 129 c.Assert(err, IsNil) 130 outbuf, err := ioutil.ReadAll(s.outf) 131 c.Assert(err, IsNil) 132 c.Check(string(outbuf), Equals, "") 133 } 134 135 func (s *SquashfsTestSuite) TestFileHasSquashfsHeader(c *C) { 136 sn := makeSnap(c, "name: test", "") 137 c.Check(squashfs.FileHasSquashfsHeader(sn.Path()), Equals, true) 138 } 139 140 func (s *SquashfsTestSuite) TestNotFileHasSquashfsHeader(c *C) { 141 data := []string{ 142 "hsqs", 143 "hsqs\x00", 144 "hsqs" + strings.Repeat("\x00", squashfs.SuperblockSize-4), 145 "hsqt" + strings.Repeat("\x00", squashfs.SuperblockSize-4+1), 146 "not a snap", 147 } 148 149 for _, d := range data { 150 err := ioutil.WriteFile("not-a-snap", []byte(d), 0644) 151 c.Assert(err, IsNil) 152 153 c.Check(squashfs.FileHasSquashfsHeader("not-a-snap"), Equals, false) 154 } 155 } 156 157 func (s *SquashfsTestSuite) TestInstallSimpleNoCp(c *C) { 158 // mock cp but still cp 159 cmd := testutil.MockCommand(c, "cp", `#!/bin/sh 160 exec /bin/cp "$@" 161 `) 162 defer cmd.Restore() 163 // mock link but still link 164 linked := 0 165 r := squashfs.MockLink(func(a, b string) error { 166 linked++ 167 return os.Link(a, b) 168 }) 169 defer r() 170 171 sn := makeSnap(c, "name: test", "") 172 targetPath := filepath.Join(c.MkDir(), "target.snap") 173 mountDir := c.MkDir() 174 didNothing, err := sn.Install(targetPath, mountDir, nil) 175 c.Assert(err, IsNil) 176 c.Assert(didNothing, Equals, false) 177 c.Check(osutil.FileExists(targetPath), Equals, true) 178 c.Check(linked, Equals, 1) 179 c.Check(cmd.Calls(), HasLen, 0) 180 } 181 182 func (s *SquashfsTestSuite) TestInstallSimpleOnOverlayfs(c *C) { 183 cmd := testutil.MockCommand(c, "cp", "") 184 defer cmd.Restore() 185 186 // mock link but still link 187 linked := 0 188 r := squashfs.MockLink(func(a, b string) error { 189 linked++ 190 return os.Link(a, b) 191 }) 192 defer r() 193 194 // pretend we are on overlayfs 195 restore := squashfs.MockIsRootWritableOverlay(func() (string, error) { 196 return "/upper", nil 197 }) 198 defer restore() 199 200 c.Assert(os.MkdirAll(dirs.SnapSeedDir, 0755), IsNil) 201 sn := makeSnapInDir(c, dirs.SnapSeedDir, "name: test2", "") 202 targetPath := filepath.Join(c.MkDir(), "target.snap") 203 _, err := os.Lstat(targetPath) 204 c.Check(os.IsNotExist(err), Equals, true) 205 206 didNothing, err := sn.Install(targetPath, c.MkDir(), nil) 207 c.Assert(err, IsNil) 208 c.Assert(didNothing, Equals, false) 209 // symlink in place 210 c.Check(osutil.IsSymlink(targetPath), Equals, true) 211 // no link / no cp 212 c.Check(linked, Equals, 0) 213 c.Check(cmd.Calls(), HasLen, 0) 214 } 215 216 func noLink() func() { 217 return squashfs.MockLink(func(string, string) error { return errors.New("no.") }) 218 } 219 220 func (s *SquashfsTestSuite) TestInstallNotCopyTwice(c *C) { 221 // first, disable os.Link 222 defer noLink()() 223 224 // then, mock cp but still cp 225 cmd := testutil.MockCommand(c, "cp", `#!/bin/sh 226 exec /bin/cp "$@" 227 `) 228 defer cmd.Restore() 229 230 sn := makeSnap(c, "name: test2", "") 231 targetPath := filepath.Join(c.MkDir(), "target.snap") 232 mountDir := c.MkDir() 233 didNothing, err := sn.Install(targetPath, mountDir, nil) 234 c.Assert(err, IsNil) 235 c.Assert(didNothing, Equals, false) 236 c.Check(cmd.Calls(), HasLen, 1) 237 238 didNothing, err = sn.Install(targetPath, mountDir, nil) 239 c.Assert(err, IsNil) 240 c.Assert(didNothing, Equals, true) 241 c.Check(cmd.Calls(), HasLen, 1) // and not 2 \o/ 242 } 243 244 func (s *SquashfsTestSuite) TestInstallSeedNoLink(c *C) { 245 defer noLink()() 246 247 c.Assert(os.MkdirAll(dirs.SnapSeedDir, 0755), IsNil) 248 sn := makeSnapInDir(c, dirs.SnapSeedDir, "name: test2", "") 249 targetPath := filepath.Join(c.MkDir(), "target.snap") 250 _, err := os.Lstat(targetPath) 251 c.Check(os.IsNotExist(err), Equals, true) 252 253 didNothing, err := sn.Install(targetPath, c.MkDir(), nil) 254 c.Assert(err, IsNil) 255 c.Assert(didNothing, Equals, false) 256 c.Check(osutil.IsSymlink(targetPath), Equals, true) // \o/ 257 } 258 259 func (s *SquashfsTestSuite) TestInstallUC20SeedNoLink(c *C) { 260 defer noLink()() 261 262 systemSnapsDir := filepath.Join(dirs.SnapSeedDir, "systems", "20200521", "snaps") 263 c.Assert(os.MkdirAll(systemSnapsDir, 0755), IsNil) 264 snap := makeSnapInDir(c, systemSnapsDir, "name: test2", "") 265 targetPath := filepath.Join(c.MkDir(), "target.snap") 266 _, err := os.Lstat(targetPath) 267 c.Check(os.IsNotExist(err), Equals, true) 268 269 didNothing, err := snap.Install(targetPath, c.MkDir(), nil) 270 c.Assert(err, IsNil) 271 c.Assert(didNothing, Equals, false) 272 c.Check(osutil.IsSymlink(targetPath), Equals, true) // \o/ 273 } 274 275 func (s *SquashfsTestSuite) TestInstallMustNotCrossDevices(c *C) { 276 defer noLink()() 277 278 c.Assert(os.MkdirAll(dirs.SnapSeedDir, 0755), IsNil) 279 sn := makeSnapInDir(c, dirs.SnapSeedDir, "name: test2", "") 280 targetPath := filepath.Join(c.MkDir(), "target.snap") 281 _, err := os.Lstat(targetPath) 282 c.Check(os.IsNotExist(err), Equals, true) 283 284 didNothing, err := sn.Install(targetPath, c.MkDir(), &snap.InstallOptions{MustNotCrossDevices: true}) 285 c.Assert(err, IsNil) 286 c.Assert(didNothing, Equals, false) 287 c.Check(osutil.IsSymlink(targetPath), Equals, false) 288 } 289 290 func (s *SquashfsTestSuite) TestInstallNothingToDo(c *C) { 291 sn := makeSnap(c, "name: test2", "") 292 293 targetPath := filepath.Join(c.MkDir(), "foo.snap") 294 c.Assert(os.Symlink(sn.Path(), targetPath), IsNil) 295 296 didNothing, err := sn.Install(targetPath, c.MkDir(), nil) 297 c.Assert(err, IsNil) 298 c.Check(didNothing, Equals, true) 299 } 300 301 func (s *SquashfsTestSuite) TestPath(c *C) { 302 p := "/path/to/foo.snap" 303 sn := squashfs.New("/path/to/foo.snap") 304 c.Assert(sn.Path(), Equals, p) 305 } 306 307 func (s *SquashfsTestSuite) TestReadFile(c *C) { 308 sn := makeSnap(c, "name: foo", "") 309 310 content, err := sn.ReadFile("meta/snap.yaml") 311 c.Assert(err, IsNil) 312 c.Assert(string(content), Equals, "name: foo") 313 } 314 315 func (s *SquashfsTestSuite) TestReadFileFail(c *C) { 316 mockUnsquashfs := testutil.MockCommand(c, "unsquashfs", `echo boom; exit 1`) 317 defer mockUnsquashfs.Restore() 318 319 sn := makeSnap(c, "name: foo", "") 320 _, err := sn.ReadFile("meta/snap.yaml") 321 c.Assert(err, ErrorMatches, "cannot run unsquashfs: boom") 322 } 323 324 func (s *SquashfsTestSuite) TestRandomAccessFile(c *C) { 325 sn := makeSnap(c, "name: foo", "") 326 327 r, err := sn.RandomAccessFile("meta/snap.yaml") 328 c.Assert(err, IsNil) 329 defer r.Close() 330 331 c.Assert(r.Size(), Equals, int64(9)) 332 333 b := make([]byte, 4) 334 n, err := r.ReadAt(b, 4) 335 c.Assert(err, IsNil) 336 c.Assert(n, Equals, 4) 337 c.Check(string(b), Equals, ": fo") 338 } 339 340 func (s *SquashfsTestSuite) TestListDir(c *C) { 341 sn := makeSnap(c, "name: foo", "") 342 343 fileNames, err := sn.ListDir("meta/hooks") 344 c.Assert(err, IsNil) 345 c.Assert(len(fileNames), Equals, 3) 346 c.Check(fileNames[0], Equals, "bar-hook") 347 c.Check(fileNames[1], Equals, "dir") 348 c.Check(fileNames[2], Equals, "foo-hook") 349 } 350 351 func (s *SquashfsTestSuite) TestWalk(c *C) { 352 sub := "." 353 sn := makeSnap(c, "name: foo", "") 354 sqw := map[string]os.FileInfo{} 355 sn.Walk(sub, func(path string, info os.FileInfo, err error) error { 356 if err != nil { 357 return err 358 } 359 if path == "food" { 360 return filepath.SkipDir 361 } 362 sqw[path] = info 363 return nil 364 }) 365 366 base := c.MkDir() 367 c.Assert(sn.Unpack("*", base), IsNil) 368 369 sdw := map[string]os.FileInfo{} 370 snapdir.New(base).Walk(sub, func(path string, info os.FileInfo, err error) error { 371 if err != nil { 372 return err 373 } 374 if path == "food" { 375 return filepath.SkipDir 376 } 377 sdw[path] = info 378 return nil 379 }) 380 381 fpw := map[string]os.FileInfo{} 382 filepath.Walk(filepath.Join(base, sub), func(path string, info os.FileInfo, err error) error { 383 if err != nil { 384 return err 385 } 386 path, err = filepath.Rel(base, path) 387 if err != nil { 388 return err 389 } 390 if path == "food" { 391 return filepath.SkipDir 392 } 393 fpw[path] = info 394 return nil 395 }) 396 397 for k := range fpw { 398 squashfs.Alike(sqw[k], fpw[k], c, Commentf(k)) 399 squashfs.Alike(sdw[k], fpw[k], c, Commentf(k)) 400 } 401 402 for k := range sqw { 403 squashfs.Alike(fpw[k], sqw[k], c, Commentf(k)) 404 squashfs.Alike(sdw[k], sqw[k], c, Commentf(k)) 405 } 406 407 for k := range sdw { 408 squashfs.Alike(fpw[k], sdw[k], c, Commentf(k)) 409 squashfs.Alike(sqw[k], sdw[k], c, Commentf(k)) 410 } 411 412 } 413 414 // TestUnpackGlob tests the internal unpack 415 func (s *SquashfsTestSuite) TestUnpackGlob(c *C) { 416 data := "some random data" 417 sn := makeSnap(c, "", data) 418 419 outputDir := c.MkDir() 420 err := sn.Unpack("data*", outputDir) 421 c.Assert(err, IsNil) 422 423 // this is the file we expect 424 c.Assert(filepath.Join(outputDir, "data.bin"), testutil.FileEquals, data) 425 426 // ensure glob was honored 427 c.Assert(osutil.FileExists(filepath.Join(outputDir, "meta/snap.yaml")), Equals, false) 428 } 429 430 func (s *SquashfsTestSuite) TestUnpackDetectsFailures(c *C) { 431 mockUnsquashfs := testutil.MockCommand(c, "unsquashfs", ` 432 cat >&2 <<EOF 433 Failed to write /tmp/1/modules/4.4.0-112-generic/modules.symbols, skipping 434 435 Write on output file failed because No space left on device 436 437 writer: failed to write data block 0 438 439 Failed to write /tmp/1/modules/4.4.0-112-generic/modules.symbols.bin, skipping 440 441 Write on output file failed because No space left on device 442 443 writer: failed to write data block 0 444 445 Failed to write /tmp/1/modules/4.4.0-112-generic/vdso/vdso32.so, skipping 446 447 Write on output file failed because No space left on device 448 449 writer: failed to write data block 0 450 451 Failed to write /tmp/1/modules/4.4.0-112-generic/vdso/vdso64.so, skipping 452 453 Write on output file failed because No space left on device 454 455 writer: failed to write data block 0 456 457 Failed to write /tmp/1/modules/4.4.0-112-generic/vdso/vdsox32.so, skipping 458 459 Write on output file failed because No space left on device 460 461 writer: failed to write data block 0 462 463 Failed to write /tmp/1/snap/manifest.yaml, skipping 464 465 Write on output file failed because No space left on device 466 467 writer: failed to write data block 0 468 469 Failed to write /tmp/1/snap/snapcraft.yaml, skipping 470 EOF 471 `) 472 defer mockUnsquashfs.Restore() 473 474 data := "mock kernel snap" 475 sn := makeSnap(c, "", data) 476 err := sn.Unpack("*", "some-output-dir") 477 c.Assert(err, NotNil) 478 c.Check(err.Error(), Equals, `cannot extract "*" to "some-output-dir": failed: "Failed to write /tmp/1/modules/4.4.0-112-generic/modules.symbols, skipping", "Write on output file failed because No space left on device", "writer: failed to write data block 0", "Failed to write /tmp/1/modules/4.4.0-112-generic/modules.symbols.bin, skipping", and 15 more`) 479 } 480 481 func (s *SquashfsTestSuite) TestBuild(c *C) { 482 // please keep TestBuildUsesExcludes in sync with this one so it makes sense. 483 buildDir := c.MkDir() 484 err := os.MkdirAll(filepath.Join(buildDir, "/random/dir"), 0755) 485 c.Assert(err, IsNil) 486 err = ioutil.WriteFile(filepath.Join(buildDir, "data.bin"), []byte("data"), 0644) 487 c.Assert(err, IsNil) 488 err = ioutil.WriteFile(filepath.Join(buildDir, "random", "data.bin"), []byte("more data"), 0644) 489 c.Assert(err, IsNil) 490 491 sn := squashfs.New(filepath.Join(c.MkDir(), "foo.snap")) 492 err = sn.Build(buildDir, &squashfs.BuildOpts{SnapType: "app"}) 493 c.Assert(err, IsNil) 494 495 // unsquashfs writes a funny header like: 496 // "Parallel unsquashfs: Using 1 processor" 497 // "1 inodes (1 blocks) to write" 498 outputWithHeader, err := exec.Command("unsquashfs", "-n", "-l", sn.Path()).Output() 499 c.Assert(err, IsNil) 500 split := strings.Split(string(outputWithHeader), "\n") 501 output := strings.Join(split[3:], "\n") 502 c.Assert(string(output), Equals, ` 503 squashfs-root 504 squashfs-root/data.bin 505 squashfs-root/random 506 squashfs-root/random/data.bin 507 squashfs-root/random/dir 508 `[1:]) // skip the first newline :-) 509 } 510 511 func (s *SquashfsTestSuite) TestBuildUsesExcludes(c *C) { 512 // please keep TestBuild in sync with this one so it makes sense. 513 buildDir := c.MkDir() 514 err := os.MkdirAll(filepath.Join(buildDir, "/random/dir"), 0755) 515 c.Assert(err, IsNil) 516 err = ioutil.WriteFile(filepath.Join(buildDir, "data.bin"), []byte("data"), 0644) 517 c.Assert(err, IsNil) 518 err = ioutil.WriteFile(filepath.Join(buildDir, "random", "data.bin"), []byte("more data"), 0644) 519 c.Assert(err, IsNil) 520 521 excludesFilename := filepath.Join(buildDir, ".snapignore") 522 err = ioutil.WriteFile(excludesFilename, []byte(` 523 # ignore just one of the data.bin files we just added (the toplevel one) 524 data.bin 525 # also ignore ourselves 526 .snapignore 527 # oh and anything called "dir" anywhere 528 ... dir 529 `), 0644) 530 c.Assert(err, IsNil) 531 532 sn := squashfs.New(filepath.Join(c.MkDir(), "foo.snap")) 533 err = sn.Build(buildDir, &squashfs.BuildOpts{ 534 SnapType: "app", 535 ExcludeFiles: []string{excludesFilename}, 536 }) 537 c.Assert(err, IsNil) 538 539 outputWithHeader, err := exec.Command("unsquashfs", "-n", "-l", sn.Path()).Output() 540 c.Assert(err, IsNil) 541 split := strings.Split(string(outputWithHeader), "\n") 542 output := strings.Join(split[3:], "\n") 543 // compare with TestBuild 544 c.Assert(string(output), Equals, ` 545 squashfs-root 546 squashfs-root/random 547 squashfs-root/random/data.bin 548 `[1:]) // skip the first newline :-) 549 } 550 551 func (s *SquashfsTestSuite) TestBuildSupportsMultipleExcludesWithOnlyOneWildcardsFlag(c *C) { 552 defer squashfs.MockCommandFromSystemSnap(func(cmd string, args ...string) (*exec.Cmd, error) { 553 c.Check(cmd, Equals, "/usr/bin/mksquashfs") 554 return nil, errors.New("bzzt") 555 })() 556 mksq := testutil.MockCommand(c, "mksquashfs", "") 557 defer mksq.Restore() 558 559 snapPath := filepath.Join(c.MkDir(), "foo.snap") 560 sn := squashfs.New(snapPath) 561 err := sn.Build(c.MkDir(), &squashfs.BuildOpts{ 562 SnapType: "core", 563 ExcludeFiles: []string{"exclude1", "exclude2", "exclude3"}, 564 }) 565 c.Assert(err, IsNil) 566 calls := mksq.Calls() 567 c.Assert(calls, HasLen, 1) 568 c.Check(calls[0], DeepEquals, []string{ 569 // the usual: 570 "mksquashfs", ".", snapPath, "-noappend", "-comp", "xz", "-no-fragments", "-no-progress", 571 // the interesting bits: 572 "-wildcards", "-ef", "exclude1", "-ef", "exclude2", "-ef", "exclude3", 573 }) 574 } 575 576 func (s *SquashfsTestSuite) TestBuildUsesMksquashfsFromCoreIfAvailable(c *C) { 577 usedFromCore := false 578 defer squashfs.MockCommandFromSystemSnap(func(cmd string, args ...string) (*exec.Cmd, error) { 579 usedFromCore = true 580 c.Check(cmd, Equals, "/usr/bin/mksquashfs") 581 return &exec.Cmd{Path: "/bin/true"}, nil 582 })() 583 mksq := testutil.MockCommand(c, "mksquashfs", "exit 1") 584 defer mksq.Restore() 585 586 buildDir := c.MkDir() 587 588 sn := squashfs.New(filepath.Join(c.MkDir(), "foo.snap")) 589 err := sn.Build(buildDir, nil) 590 c.Assert(err, IsNil) 591 c.Check(usedFromCore, Equals, true) 592 c.Check(mksq.Calls(), HasLen, 0) 593 } 594 595 func (s *SquashfsTestSuite) TestBuildUsesMksquashfsFromClassicIfCoreUnavailable(c *C) { 596 triedFromCore := false 597 defer squashfs.MockCommandFromSystemSnap(func(cmd string, args ...string) (*exec.Cmd, error) { 598 triedFromCore = true 599 c.Check(cmd, Equals, "/usr/bin/mksquashfs") 600 return nil, errors.New("bzzt") 601 })() 602 mksq := testutil.MockCommand(c, "mksquashfs", "") 603 defer mksq.Restore() 604 605 buildDir := c.MkDir() 606 607 sn := squashfs.New(filepath.Join(c.MkDir(), "foo.snap")) 608 err := sn.Build(buildDir, nil) 609 c.Assert(err, IsNil) 610 c.Check(triedFromCore, Equals, true) 611 c.Check(mksq.Calls(), HasLen, 1) 612 } 613 614 func (s *SquashfsTestSuite) TestBuildFailsIfNoMksquashfs(c *C) { 615 triedFromCore := false 616 defer squashfs.MockCommandFromSystemSnap(func(cmd string, args ...string) (*exec.Cmd, error) { 617 triedFromCore = true 618 c.Check(cmd, Equals, "/usr/bin/mksquashfs") 619 return nil, errors.New("bzzt") 620 })() 621 mksq := testutil.MockCommand(c, "mksquashfs", "exit 1") 622 defer mksq.Restore() 623 624 buildDir := c.MkDir() 625 626 sn := squashfs.New(filepath.Join(c.MkDir(), "foo.snap")) 627 err := sn.Build(buildDir, nil) 628 c.Assert(err, ErrorMatches, "mksquashfs call failed:.*") 629 c.Check(triedFromCore, Equals, true) 630 c.Check(mksq.Calls(), HasLen, 1) 631 } 632 633 func (s *SquashfsTestSuite) TestBuildVariesArgsByType(c *C) { 634 defer squashfs.MockCommandFromSystemSnap(func(cmd string, args ...string) (*exec.Cmd, error) { 635 return nil, errors.New("bzzt") 636 })() 637 mksq := testutil.MockCommand(c, "mksquashfs", "") 638 defer mksq.Restore() 639 640 buildDir := c.MkDir() 641 filename := filepath.Join(c.MkDir(), "foo.snap") 642 snap := squashfs.New(filename) 643 644 permissiveTypeArgs := []string{".", filename, "-noappend", "-comp", "xz", "-no-fragments", "-no-progress"} 645 restrictedTypeArgs := append(permissiveTypeArgs, "-all-root", "-no-xattrs") 646 tests := []struct { 647 snapType string 648 args []string 649 }{ 650 {"", restrictedTypeArgs}, 651 {"app", restrictedTypeArgs}, 652 {"gadget", restrictedTypeArgs}, 653 {"kernel", restrictedTypeArgs}, 654 {"snapd", restrictedTypeArgs}, 655 {"base", permissiveTypeArgs}, 656 {"os", permissiveTypeArgs}, 657 {"core", permissiveTypeArgs}, 658 } 659 660 for _, t := range tests { 661 mksq.ForgetCalls() 662 comm := Commentf("type: %s", t.snapType) 663 664 c.Check(snap.Build(buildDir, &squashfs.BuildOpts{SnapType: t.snapType}), IsNil, comm) 665 c.Assert(mksq.Calls(), HasLen, 1, comm) 666 c.Assert(mksq.Calls()[0], HasLen, len(t.args)+1) 667 c.Check(mksq.Calls()[0][0], Equals, "mksquashfs", comm) 668 c.Check(mksq.Calls()[0][1:], DeepEquals, t.args, comm) 669 } 670 } 671 672 func (s *SquashfsTestSuite) TestBuildReportsFailures(c *C) { 673 mockUnsquashfs := testutil.MockCommand(c, "mksquashfs", ` 674 echo Yeah, nah. >&2 675 exit 1 676 `) 677 defer mockUnsquashfs.Restore() 678 679 data := "mock kernel snap" 680 dir := makeSnapContents(c, "", data) 681 sn := squashfs.New("foo.snap") 682 c.Check(sn.Build(dir, &squashfs.BuildOpts{SnapType: "kernel"}), ErrorMatches, `mksquashfs call failed: Yeah, nah.`) 683 } 684 685 func (s *SquashfsTestSuite) TestUnsquashfsStderrWriter(c *C) { 686 for _, t := range []struct { 687 inp []string 688 expectedErr string 689 }{ 690 { 691 inp: []string{"failed to write something\n"}, 692 expectedErr: `failed: "failed to write something"`, 693 }, 694 { 695 inp: []string{"fai", "led to write", " something\nunrelated\n"}, 696 expectedErr: `failed: "failed to write something"`, 697 }, 698 { 699 inp: []string{"failed to write\nfailed to read\n"}, 700 expectedErr: `failed: "failed to write", and "failed to read"`, 701 }, 702 { 703 inp: []string{"failed 1\nfailed 2\n3 failed\n"}, 704 expectedErr: `failed: "failed 1", "failed 2", and "3 failed"`, 705 }, 706 { 707 inp: []string{"failed 1\nfailed 2\n3 Failed\n4 Failed\n"}, 708 expectedErr: `failed: "failed 1", "failed 2", "3 Failed", and "4 Failed"`, 709 }, 710 { 711 inp: []string{"failed 1\nfailed 2\n3 Failed\n4 Failed\nfailed #5\n"}, 712 expectedErr: `failed: "failed 1", "failed 2", "3 Failed", "4 Failed", and 1 more`, 713 }, 714 } { 715 usw := squashfs.NewUnsquashfsStderrWriter() 716 for _, l := range t.inp { 717 usw.Write([]byte(l)) 718 } 719 if t.expectedErr != "" { 720 c.Check(usw.Err(), ErrorMatches, t.expectedErr, Commentf("inp: %q failed", t.inp)) 721 } else { 722 c.Check(usw.Err(), IsNil) 723 } 724 } 725 } 726 727 func (s *SquashfsTestSuite) TestBuildDate(c *C) { 728 // This env is used in reproducible builds and will force 729 // squashfs to use a specific date. We need to unset it 730 // for this specific test. 731 if oldEnv := os.Getenv("SOURCE_DATE_EPOCH"); oldEnv != "" { 732 os.Unsetenv("SOURCE_DATE_EPOCH") 733 defer func() { os.Setenv("SOURCE_DATE_EPOCH", oldEnv) }() 734 } 735 736 // make a directory 737 d := c.MkDir() 738 // set its time waaay back 739 now := time.Now() 740 then := now.Add(-10000 * time.Hour) 741 c.Assert(os.Chtimes(d, then, then), IsNil) 742 // make a snap using this directory 743 filename := filepath.Join(c.MkDir(), "foo.snap") 744 sn := squashfs.New(filename) 745 c.Assert(sn.Build(d, nil), IsNil) 746 // and see it's BuildDate is _now_, not _then_. 747 c.Check(squashfs.BuildDate(filename), Equals, sn.BuildDate()) 748 c.Check(math.Abs(now.Sub(sn.BuildDate()).Seconds()) <= 61, Equals, true, Commentf("Unexpected build date %s", sn.BuildDate())) 749 } 750 751 func (s *SquashfsTestSuite) TestBuildChecksReadDifferentFiles(c *C) { 752 if os.Geteuid() == 0 { 753 c.Skip("cannot be tested when running as root") 754 } 755 // make a directory 756 d := c.MkDir() 757 758 err := os.MkdirAll(filepath.Join(d, "ro-dir"), 0755) 759 c.Assert(err, IsNil) 760 err = ioutil.WriteFile(filepath.Join(d, "ro-dir", "in-ro-dir"), []byte("123"), 0664) 761 c.Assert(err, IsNil) 762 err = os.Chmod(filepath.Join(d, "ro-dir"), 0000) 763 c.Assert(err, IsNil) 764 // so that tear down does not complain 765 defer os.Chmod(filepath.Join(d, "ro-dir"), 0755) 766 767 err = ioutil.WriteFile(filepath.Join(d, "ro-file"), []byte("123"), 0000) 768 c.Assert(err, IsNil) 769 err = ioutil.WriteFile(filepath.Join(d, "ro-empty-file"), nil, 0000) 770 c.Assert(err, IsNil) 771 772 err = syscall.Mkfifo(filepath.Join(d, "fifo"), 0000) 773 c.Assert(err, IsNil) 774 775 filename := filepath.Join(c.MkDir(), "foo.snap") 776 sn := squashfs.New(filename) 777 err = sn.Build(d, nil) 778 c.Assert(err, ErrorMatches, `(?s)cannot access the following locations in the snap source directory: 779 - ro-(file|dir) \(owner [0-9]+:[0-9]+ mode 000\) 780 - ro-(file|dir) \(owner [0-9]+:[0-9]+ mode 000\) 781 `) 782 783 } 784 785 func (s *SquashfsTestSuite) TestBuildChecksReadErrorLimit(c *C) { 786 if os.Geteuid() == 0 { 787 c.Skip("cannot be tested when running as root") 788 } 789 // make a directory 790 d := c.MkDir() 791 792 // make more than maxErrPaths entries 793 for i := 0; i < squashfs.MaxErrPaths; i++ { 794 p := filepath.Join(d, fmt.Sprintf("0%d", i)) 795 err := ioutil.WriteFile(p, []byte("123"), 0000) 796 c.Assert(err, IsNil) 797 err = os.Chmod(p, 0000) 798 c.Assert(err, IsNil) 799 } 800 filename := filepath.Join(c.MkDir(), "foo.snap") 801 sn := squashfs.New(filename) 802 err := sn.Build(d, nil) 803 c.Assert(err, ErrorMatches, `(?s)cannot access the following locations in the snap source directory: 804 (- [0-9]+ \(owner [0-9]+:[0-9]+ mode 000.*\).){10}- too many errors, listing first 10 entries 805 `) 806 } 807 808 func (s *SquashfsTestSuite) TestBuildBadSource(c *C) { 809 filename := filepath.Join(c.MkDir(), "foo.snap") 810 sn := squashfs.New(filename) 811 err := sn.Build("does-not-exist", nil) 812 c.Assert(err, ErrorMatches, ".*does-not-exist/: no such file or directory") 813 } 814 815 func (s *SquashfsTestSuite) TestBuildWithCompressionHappy(c *C) { 816 buildDir := c.MkDir() 817 err := os.MkdirAll(filepath.Join(buildDir, "/random/dir"), 0755) 818 c.Assert(err, IsNil) 819 820 defaultComp := "xz" 821 for _, comp := range []string{"", "xz", "gzip", "lzo"} { 822 sn := squashfs.New(filepath.Join(c.MkDir(), "foo.snap")) 823 err = sn.Build(buildDir, &squashfs.BuildOpts{ 824 Compression: comp, 825 }) 826 c.Assert(err, IsNil) 827 828 // check compression 829 outputWithHeader, err := exec.Command("unsquashfs", "-n", "-s", sn.Path()).CombinedOutput() 830 c.Assert(err, IsNil) 831 // ensure default is xz 832 if comp == "" { 833 comp = defaultComp 834 } 835 c.Assert(string(outputWithHeader), Matches, fmt.Sprintf(`(?ms).*Compression %s$`, comp)) 836 } 837 } 838 839 func (s *SquashfsTestSuite) TestBuildWithCompressionUnhappy(c *C) { 840 buildDir := c.MkDir() 841 err := os.MkdirAll(filepath.Join(buildDir, "/random/dir"), 0755) 842 c.Assert(err, IsNil) 843 844 sn := squashfs.New(filepath.Join(c.MkDir(), "foo.snap")) 845 err = sn.Build(buildDir, &squashfs.BuildOpts{ 846 Compression: "silly", 847 }) 848 c.Assert(err, ErrorMatches, "(?m)^mksquashfs call failed: ") 849 }