github.com/tompreston/snapd@v0.0.0-20210817193607-954edfcb9611/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  }