github.com/rigado/snapd@v2.42.5-go-mod+incompatible/gadget/filesystemimage_test.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2019 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  package gadget_test
    20  
    21  import (
    22  	"errors"
    23  	"fmt"
    24  	"os"
    25  	"path/filepath"
    26  
    27  	. "gopkg.in/check.v1"
    28  
    29  	"github.com/snapcore/snapd/gadget"
    30  	"github.com/snapcore/snapd/osutil"
    31  	"github.com/snapcore/snapd/testutil"
    32  )
    33  
    34  type filesystemImageTestSuite struct {
    35  	testutil.BaseTest
    36  
    37  	dir       string
    38  	work      string
    39  	content   string
    40  	psTrivial *gadget.LaidOutStructure
    41  }
    42  
    43  var _ = Suite(&filesystemImageTestSuite{})
    44  
    45  func (s *filesystemImageTestSuite) SetUpTest(c *C) {
    46  	s.BaseTest.SetUpTest(c)
    47  
    48  	s.dir = c.MkDir()
    49  	// work directory
    50  	s.work = filepath.Join(s.dir, "work")
    51  	err := os.MkdirAll(s.work, 0755)
    52  	c.Assert(err, IsNil)
    53  
    54  	// gadget content directory
    55  	s.content = filepath.Join(s.dir, "content")
    56  
    57  	s.psTrivial = &gadget.LaidOutStructure{
    58  		VolumeStructure: &gadget.VolumeStructure{
    59  			Filesystem: "happyfs",
    60  			Size:       2 * gadget.SizeMiB,
    61  			Content:    []gadget.VolumeContent{},
    62  		},
    63  		Index: 1,
    64  	}
    65  }
    66  
    67  func (s *filesystemImageTestSuite) TearDownTest(c *C) {
    68  	s.BaseTest.TearDownTest(c)
    69  }
    70  
    71  func (s *filesystemImageTestSuite) imgForPs(c *C, ps *gadget.LaidOutStructure) string {
    72  	c.Assert(ps, NotNil)
    73  	img := filepath.Join(s.dir, "img")
    74  	makeSizedFile(c, img, ps.Size, nil)
    75  	return img
    76  }
    77  
    78  type filesystemImageMockedTestSuite struct {
    79  	filesystemImageTestSuite
    80  }
    81  
    82  var _ = Suite(&filesystemImageMockedTestSuite{})
    83  
    84  func (s *filesystemImageMockedTestSuite) SetUpTest(c *C) {
    85  	s.filesystemImageTestSuite.SetUpTest(c)
    86  
    87  	unexpectedMkfs := func(imgFile, label, contentsRootDir string) error {
    88  		return errors.New("unexpected mkfs call")
    89  	}
    90  	s.AddCleanup(gadget.MockMkfsHandlers(map[string]gadget.MkfsFunc{
    91  		"happyfs": unexpectedMkfs,
    92  	}))
    93  }
    94  
    95  func (s *filesystemImageMockedTestSuite) TearDownTest(c *C) {
    96  	s.filesystemImageTestSuite.TearDownTest(c)
    97  }
    98  
    99  func (s *filesystemImageMockedTestSuite) TestSimpleErrors(c *C) {
   100  	psValid := &gadget.LaidOutStructure{
   101  		VolumeStructure: &gadget.VolumeStructure{
   102  			Filesystem: "ext4",
   103  			Size:       2 * gadget.SizeMiB,
   104  		},
   105  	}
   106  
   107  	fiw, err := gadget.NewFilesystemImageWriter("", psValid, "")
   108  	c.Assert(err, ErrorMatches, "internal error: gadget content directory cannot be unset")
   109  	c.Assert(fiw, IsNil)
   110  
   111  	fiw, err = gadget.NewFilesystemImageWriter(s.dir, nil, "")
   112  	c.Assert(err, ErrorMatches, `internal error: \*LaidOutStructure is nil`)
   113  	c.Assert(fiw, IsNil)
   114  
   115  	psNoFs := &gadget.LaidOutStructure{
   116  		VolumeStructure: &gadget.VolumeStructure{
   117  			Filesystem: "none",
   118  			Size:       2 * gadget.SizeMiB,
   119  		},
   120  	}
   121  
   122  	fiw, err = gadget.NewFilesystemImageWriter(s.dir, psNoFs, "")
   123  	c.Assert(err, ErrorMatches, "internal error: structure has no filesystem")
   124  	c.Assert(fiw, IsNil)
   125  
   126  	psInvalidFs := &gadget.LaidOutStructure{
   127  		VolumeStructure: &gadget.VolumeStructure{
   128  			Filesystem: "xfs",
   129  			Size:       2 * gadget.SizeMiB,
   130  		},
   131  	}
   132  	fiw, err = gadget.NewFilesystemImageWriter(s.dir, psInvalidFs, "")
   133  	c.Assert(err, ErrorMatches, `internal error: filesystem "xfs" has no handler`)
   134  	c.Assert(fiw, IsNil)
   135  }
   136  
   137  func (s *filesystemImageMockedTestSuite) TestHappyFull(c *C) {
   138  	ps := &gadget.LaidOutStructure{
   139  		VolumeStructure: &gadget.VolumeStructure{
   140  			Filesystem: "happyfs",
   141  			Label:      "so-happy",
   142  			Size:       2 * gadget.SizeMiB,
   143  			Content: []gadget.VolumeContent{
   144  				{Source: "/foo", Target: "/"},
   145  			},
   146  		},
   147  		Index: 2,
   148  	}
   149  
   150  	// image file
   151  	img := s.imgForPs(c, ps)
   152  
   153  	// mock gadget data
   154  	gd := []gadgetData{
   155  		{name: "foo", target: "foo", content: "hello foo"},
   156  	}
   157  	makeGadgetData(c, s.content, gd)
   158  
   159  	var cbCalled bool
   160  	var mkfsCalled bool
   161  
   162  	cb := func(rootDir string, cbPs *gadget.LaidOutStructure) error {
   163  		c.Assert(cbPs, DeepEquals, ps)
   164  		c.Assert(rootDir, Equals, filepath.Join(s.work, "snap-stage-content-part-0002"))
   165  		verifyWrittenGadgetData(c, rootDir, gd)
   166  
   167  		cbCalled = true
   168  		return nil
   169  	}
   170  
   171  	mkfsHappyFs := func(imgFile, label, contentsRootDir string) error {
   172  		c.Assert(imgFile, Equals, img)
   173  		c.Assert(label, Equals, "so-happy")
   174  		c.Assert(contentsRootDir, Equals, filepath.Join(s.work, "snap-stage-content-part-0002"))
   175  		mkfsCalled = true
   176  		return nil
   177  	}
   178  
   179  	restore := gadget.MockMkfsHandlers(map[string]gadget.MkfsFunc{
   180  		"happyfs": mkfsHappyFs,
   181  	})
   182  	defer restore()
   183  
   184  	fiw, err := gadget.NewFilesystemImageWriter(s.content, ps, s.work)
   185  	c.Assert(err, IsNil)
   186  
   187  	err = fiw.Write(img, cb)
   188  	c.Assert(err, IsNil)
   189  	c.Assert(cbCalled, Equals, true)
   190  	c.Assert(mkfsCalled, Equals, true)
   191  	// nothing left in temporary staging location
   192  	matches, err := filepath.Glob(s.work + "/*")
   193  	c.Assert(err, IsNil)
   194  	c.Assert(matches, HasLen, 0)
   195  }
   196  
   197  func (s *filesystemImageMockedTestSuite) TestPostStageOptional(c *C) {
   198  	var mkfsCalled bool
   199  	mkfsHappyFs := func(imgFile, label, contentsRootDir string) error {
   200  		mkfsCalled = true
   201  		return nil
   202  	}
   203  
   204  	restore := gadget.MockMkfsHandlers(map[string]gadget.MkfsFunc{
   205  		"happyfs": mkfsHappyFs,
   206  	})
   207  	defer restore()
   208  
   209  	fiw, err := gadget.NewFilesystemImageWriter(s.content, s.psTrivial, s.work)
   210  	c.Assert(err, IsNil)
   211  
   212  	img := s.imgForPs(c, s.psTrivial)
   213  
   214  	err = fiw.Write(img, nil)
   215  	c.Assert(err, IsNil)
   216  	c.Assert(mkfsCalled, Equals, true)
   217  }
   218  
   219  func (s *filesystemImageMockedTestSuite) TestChecksImage(c *C) {
   220  	cb := func(rootDir string, cbPs *gadget.LaidOutStructure) error {
   221  		return errors.New("unexpected call")
   222  	}
   223  
   224  	fiw, err := gadget.NewFilesystemImageWriter(s.content, s.psTrivial, s.work)
   225  	c.Assert(err, IsNil)
   226  
   227  	img := filepath.Join(s.dir, "img")
   228  
   229  	// no image file
   230  	err = fiw.Write(img, cb)
   231  	c.Assert(err, ErrorMatches, "cannot stat image file: .*/img: no such file or directory")
   232  
   233  	// image file smaller than expected
   234  	makeSizedFile(c, img, s.psTrivial.Size/2, nil)
   235  
   236  	err = fiw.Write(img, cb)
   237  	c.Assert(err, ErrorMatches, fmt.Sprintf("size of image file %v is different from declared structure size %v", s.psTrivial.Size/2, s.psTrivial.Size))
   238  }
   239  
   240  func (s *filesystemImageMockedTestSuite) TestPostStageError(c *C) {
   241  	cb := func(rootDir string, cbPs *gadget.LaidOutStructure) error {
   242  		return errors.New("post stage exploded")
   243  	}
   244  
   245  	fiw, err := gadget.NewFilesystemImageWriter(s.content, s.psTrivial, s.work)
   246  	c.Assert(err, IsNil)
   247  
   248  	img := s.imgForPs(c, s.psTrivial)
   249  
   250  	err = fiw.Write(img, cb)
   251  	c.Assert(err, ErrorMatches, "post stage callback failed: post stage exploded")
   252  }
   253  
   254  func (s *filesystemImageMockedTestSuite) TestMkfsError(c *C) {
   255  	mkfsHappyFs := func(imgFile, label, contentsRootDir string) error {
   256  		return errors.New("will not mkfs")
   257  	}
   258  	restore := gadget.MockMkfsHandlers(map[string]gadget.MkfsFunc{
   259  		"happyfs": mkfsHappyFs,
   260  	})
   261  	defer restore()
   262  
   263  	fiw, err := gadget.NewFilesystemImageWriter(s.content, s.psTrivial, s.work)
   264  	c.Assert(err, IsNil)
   265  
   266  	img := s.imgForPs(c, s.psTrivial)
   267  
   268  	err = fiw.Write(img, nil)
   269  	c.Assert(err, ErrorMatches, `cannot create "happyfs" filesystem: will not mkfs`)
   270  }
   271  
   272  func (s *filesystemImageMockedTestSuite) TestFilesystemExtraCheckError(c *C) {
   273  	ps := &gadget.LaidOutStructure{
   274  		VolumeStructure: &gadget.VolumeStructure{
   275  			Filesystem: "happyfs",
   276  			Size:       2 * gadget.SizeMiB,
   277  			Content:    []gadget.VolumeContent{},
   278  		},
   279  	}
   280  
   281  	fiw, err := gadget.NewFilesystemImageWriter(s.content, ps, s.work)
   282  	c.Assert(err, IsNil)
   283  
   284  	img := s.imgForPs(c, ps)
   285  
   286  	// modify filesystem
   287  	ps.Filesystem = "foofs"
   288  
   289  	err = fiw.Write(img, nil)
   290  	c.Assert(err, ErrorMatches, `internal error: filesystem "foofs" has no handler`)
   291  }
   292  
   293  func (s *filesystemImageMockedTestSuite) TestMountedWriterError(c *C) {
   294  	ps := &gadget.LaidOutStructure{
   295  		VolumeStructure: &gadget.VolumeStructure{
   296  			Filesystem: "happyfs",
   297  			Size:       2 * gadget.SizeMiB,
   298  			Content: []gadget.VolumeContent{
   299  				{Source: "/foo", Target: "/"},
   300  			},
   301  		},
   302  	}
   303  
   304  	cb := func(rootDir string, cbPs *gadget.LaidOutStructure) error {
   305  		return errors.New("unexpected call")
   306  	}
   307  
   308  	fiw, err := gadget.NewFilesystemImageWriter(s.content, ps, s.work)
   309  	c.Assert(err, IsNil)
   310  
   311  	img := s.imgForPs(c, ps)
   312  
   313  	// declared content does not exist in the content directory
   314  	err = fiw.Write(img, cb)
   315  	c.Assert(err, ErrorMatches, `cannot prepare filesystem content: cannot write filesystem content of source:/foo: .* no such file or directory`)
   316  }
   317  
   318  func (s *filesystemImageMockedTestSuite) TestBadWorkDirError(c *C) {
   319  	cb := func(rootDir string, cbPs *gadget.LaidOutStructure) error {
   320  		return errors.New("unexpected call")
   321  	}
   322  
   323  	badWork := filepath.Join(s.dir, "bad-work")
   324  	fiw, err := gadget.NewFilesystemImageWriter(s.content, s.psTrivial, badWork)
   325  	c.Assert(err, IsNil)
   326  
   327  	img := s.imgForPs(c, s.psTrivial)
   328  
   329  	err = fiw.Write(img, cb)
   330  	c.Assert(err, ErrorMatches, `cannot prepare staging directory: mkdir .*/bad-work/snap-stage-content-part-0001: no such file or directory`)
   331  
   332  	err = os.MkdirAll(filepath.Join(badWork, "snap-stage-content-part-0001"), 0755)
   333  	c.Assert(err, IsNil)
   334  
   335  	err = fiw.Write(img, cb)
   336  	c.Assert(err, ErrorMatches, `cannot prepare staging directory .*/bad-work/snap-stage-content-part-0001: path exists`)
   337  }
   338  
   339  func (s *filesystemImageMockedTestSuite) TestKeepsStagingDir(c *C) {
   340  	cb := func(rootDir string, cbPs *gadget.LaidOutStructure) error {
   341  		return nil
   342  	}
   343  	mkfsHappyFs := func(imgFile, label, contentsRootDir string) error {
   344  		return nil
   345  	}
   346  	restore := gadget.MockMkfsHandlers(map[string]gadget.MkfsFunc{
   347  		"happyfs": mkfsHappyFs,
   348  	})
   349  	defer restore()
   350  
   351  	fiw, err := gadget.NewFilesystemImageWriter(s.content, s.psTrivial, s.work)
   352  	c.Assert(err, IsNil)
   353  
   354  	img := s.imgForPs(c, s.psTrivial)
   355  
   356  	os.Setenv("SNAP_DEBUG_IMAGE_NO_CLEANUP", "1")
   357  	defer os.Unsetenv("SNAP_DEBUG_IMAGE_NO_CLEANUP")
   358  	err = fiw.Write(img, cb)
   359  	c.Assert(err, IsNil)
   360  
   361  	matches, err := filepath.Glob(s.work + "/*")
   362  	c.Assert(err, IsNil)
   363  	c.Assert(matches, HasLen, 1)
   364  	c.Assert(osutil.IsDirectory(filepath.Join(s.work, "snap-stage-content-part-0001")), Equals, true)
   365  }
   366  
   367  type filesystemImageMkfsTestSuite struct {
   368  	filesystemImageTestSuite
   369  
   370  	cmdFakeroot *testutil.MockCmd
   371  	cmdMkfsVfat *testutil.MockCmd
   372  	cmdMcopy    *testutil.MockCmd
   373  }
   374  
   375  func (s *filesystemImageMkfsTestSuite) SetUpTest(c *C) {
   376  	s.filesystemImageTestSuite.SetUpTest(c)
   377  
   378  	// mkfs.ext4 is called via fakeroot wrapper
   379  	s.cmdFakeroot = testutil.MockCommand(c, "fakeroot", "")
   380  	s.AddCleanup(s.cmdFakeroot.Restore)
   381  
   382  	s.cmdMkfsVfat = testutil.MockCommand(c, "mkfs.vfat", "")
   383  	s.AddCleanup(s.cmdMkfsVfat.Restore)
   384  
   385  	s.cmdMcopy = testutil.MockCommand(c, "mcopy", "")
   386  	s.AddCleanup(s.cmdMcopy.Restore)
   387  }
   388  
   389  func (s *filesystemImageMkfsTestSuite) TearDownTest(c *C) {
   390  	s.filesystemImageTestSuite.TearDownTest(c)
   391  }
   392  
   393  var _ = Suite(&filesystemImageMkfsTestSuite{})
   394  
   395  func (s *filesystemImageMkfsTestSuite) TestExt4(c *C) {
   396  	psExt4 := &gadget.LaidOutStructure{
   397  		VolumeStructure: &gadget.VolumeStructure{
   398  			Filesystem: "ext4",
   399  			Size:       2 * gadget.SizeMiB,
   400  			Content:    []gadget.VolumeContent{{Source: "/", Target: "/"}},
   401  		},
   402  	}
   403  
   404  	makeSizedFile(c, filepath.Join(s.content, "foo"), 1024, nil)
   405  
   406  	fiw, err := gadget.NewFilesystemImageWriter(s.content, psExt4, s.work)
   407  	c.Assert(err, IsNil)
   408  
   409  	img := s.imgForPs(c, psExt4)
   410  	err = fiw.Write(img, nil)
   411  	c.Assert(err, IsNil)
   412  
   413  	c.Check(s.cmdFakeroot.Calls(), HasLen, 1)
   414  	c.Check(s.cmdMkfsVfat.Calls(), HasLen, 0)
   415  	c.Check(s.cmdMcopy.Calls(), HasLen, 0)
   416  }
   417  
   418  func (s *filesystemImageMkfsTestSuite) TestVfat(c *C) {
   419  	psVfat := &gadget.LaidOutStructure{
   420  		VolumeStructure: &gadget.VolumeStructure{
   421  			Filesystem: "vfat",
   422  			Size:       2 * gadget.SizeMiB,
   423  			Content:    []gadget.VolumeContent{{Source: "/", Target: "/"}},
   424  		},
   425  	}
   426  
   427  	makeSizedFile(c, filepath.Join(s.content, "foo"), 1024, nil)
   428  
   429  	fiw, err := gadget.NewFilesystemImageWriter(s.content, psVfat, s.work)
   430  	c.Assert(err, IsNil)
   431  
   432  	img := s.imgForPs(c, psVfat)
   433  	err = fiw.Write(img, nil)
   434  	c.Assert(err, IsNil)
   435  
   436  	c.Check(s.cmdFakeroot.Calls(), HasLen, 0)
   437  	c.Check(s.cmdMkfsVfat.Calls(), HasLen, 1)
   438  	c.Check(s.cmdMcopy.Calls(), HasLen, 1)
   439  }