github.com/stulluk/snapd@v0.0.0-20210611110309-f6d5d5bd24b0/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  }