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