github.com/chipaca/snappy@v0.0.0-20210104084008-1f06296fe8ad/gadget/layout_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  
    20  package gadget_test
    21  
    22  import (
    23  	"bytes"
    24  	"fmt"
    25  	"io"
    26  	"os"
    27  	"path/filepath"
    28  
    29  	. "gopkg.in/check.v1"
    30  
    31  	"github.com/snapcore/snapd/gadget"
    32  	"github.com/snapcore/snapd/gadget/quantity"
    33  )
    34  
    35  type layoutTestSuite struct {
    36  	dir string
    37  }
    38  
    39  var _ = Suite(&layoutTestSuite{})
    40  
    41  func (p *layoutTestSuite) SetUpTest(c *C) {
    42  	p.dir = c.MkDir()
    43  }
    44  
    45  var defaultConstraints = gadget.LayoutConstraints{
    46  	NonMBRStartOffset: 1 * quantity.OffsetMiB,
    47  	SectorSize:        512,
    48  }
    49  
    50  func (p *layoutTestSuite) TestVolumeSize(c *C) {
    51  	vol := gadget.Volume{
    52  		Structure: []gadget.VolumeStructure{
    53  			{Size: 2 * quantity.SizeMiB},
    54  		},
    55  	}
    56  	v, err := gadget.LayoutVolume(p.dir, &vol, defaultConstraints)
    57  	c.Assert(err, IsNil)
    58  
    59  	c.Assert(v, DeepEquals, &gadget.LaidOutVolume{
    60  		Volume: &gadget.Volume{
    61  			Structure: []gadget.VolumeStructure{
    62  				{Size: 2 * quantity.SizeMiB},
    63  			},
    64  		},
    65  		Size:       3 * quantity.SizeMiB,
    66  		SectorSize: 512,
    67  		RootDir:    p.dir,
    68  		LaidOutStructure: []gadget.LaidOutStructure{{
    69  			VolumeStructure: &gadget.VolumeStructure{Size: 2 * quantity.SizeMiB},
    70  			StartOffset:     1 * quantity.OffsetMiB,
    71  		}},
    72  	})
    73  }
    74  
    75  func mustParseVolume(c *C, gadgetYaml, volume string) *gadget.Volume {
    76  	gi, err := gadget.InfoFromGadgetYaml([]byte(gadgetYaml), nil)
    77  	c.Assert(err, IsNil)
    78  	v, ok := gi.Volumes[volume]
    79  	c.Assert(ok, Equals, true, Commentf("volume %q not found in gadget", volume))
    80  	return v
    81  }
    82  
    83  func (p *layoutTestSuite) TestLayoutVolumeMinimal(c *C) {
    84  	gadgetYaml := `
    85  volumes:
    86    first-image:
    87      bootloader: u-boot
    88      structure:
    89          - type: 00000000-0000-0000-0000-0000deadbeef
    90            size: 400M
    91          - type: 83,00000000-0000-0000-0000-0000feedface
    92            role: system-data
    93            size: 100M
    94  `
    95  	vol := mustParseVolume(c, gadgetYaml, "first-image")
    96  	c.Assert(vol.Structure, HasLen, 2)
    97  
    98  	v, err := gadget.LayoutVolume(p.dir, vol, defaultConstraints)
    99  	c.Assert(err, IsNil)
   100  
   101  	c.Assert(v, DeepEquals, &gadget.LaidOutVolume{
   102  		Volume:     vol,
   103  		Size:       501 * quantity.SizeMiB,
   104  		SectorSize: 512,
   105  		RootDir:    p.dir,
   106  		LaidOutStructure: []gadget.LaidOutStructure{
   107  			{
   108  				VolumeStructure: &vol.Structure[0],
   109  				StartOffset:     1 * quantity.OffsetMiB,
   110  				Index:           0,
   111  			},
   112  			{
   113  				VolumeStructure: &vol.Structure[1],
   114  				StartOffset:     401 * quantity.OffsetMiB,
   115  				Index:           1,
   116  			},
   117  		},
   118  	})
   119  }
   120  
   121  func (p *layoutTestSuite) TestLayoutVolumeImplicitOrdering(c *C) {
   122  	gadgetYaml := `
   123  volumes:
   124    first:
   125      schema: gpt
   126      bootloader: grub
   127      structure:
   128          - type: 00000000-0000-0000-0000-dd00deadbeef
   129            size: 400M
   130          - type: 00000000-0000-0000-0000-cc00deadbeef
   131            role: system-data
   132            size: 500M
   133          - type: 00000000-0000-0000-0000-bb00deadbeef
   134            size: 100M
   135          - type: 00000000-0000-0000-0000-aa00deadbeef
   136            size: 100M
   137  `
   138  	vol := mustParseVolume(c, gadgetYaml, "first")
   139  	c.Assert(vol.Structure, HasLen, 4)
   140  
   141  	v, err := gadget.LayoutVolume(p.dir, vol, defaultConstraints)
   142  	c.Assert(err, IsNil)
   143  
   144  	c.Assert(v, DeepEquals, &gadget.LaidOutVolume{
   145  		Volume:     vol,
   146  		Size:       1101 * quantity.SizeMiB,
   147  		SectorSize: 512,
   148  		RootDir:    p.dir,
   149  		LaidOutStructure: []gadget.LaidOutStructure{
   150  			{
   151  				VolumeStructure: &vol.Structure[0],
   152  				StartOffset:     1 * quantity.OffsetMiB,
   153  				Index:           0,
   154  			},
   155  			{
   156  				VolumeStructure: &vol.Structure[1],
   157  				StartOffset:     401 * quantity.OffsetMiB,
   158  				Index:           1,
   159  			},
   160  			{
   161  				VolumeStructure: &vol.Structure[2],
   162  				StartOffset:     901 * quantity.OffsetMiB,
   163  				Index:           2,
   164  			},
   165  			{
   166  				VolumeStructure: &vol.Structure[3],
   167  				StartOffset:     1001 * quantity.OffsetMiB,
   168  				Index:           3,
   169  			},
   170  		},
   171  	})
   172  }
   173  
   174  func (p *layoutTestSuite) TestLayoutVolumeExplicitOrdering(c *C) {
   175  	gadgetYaml := `
   176  volumes:
   177    first:
   178      schema: gpt
   179      bootloader: grub
   180      structure:
   181          - type: 00000000-0000-0000-0000-dd00deadbeef
   182            size: 400M
   183            offset: 800M
   184          - type: 00000000-0000-0000-0000-cc00deadbeef
   185            role: system-data
   186            size: 500M
   187            offset: 200M
   188          - type: 00000000-0000-0000-0000-bb00deadbeef
   189            size: 100M
   190            offset: 1200M
   191          - type: 00000000-0000-0000-0000-aa00deadbeef
   192            size: 100M
   193            offset: 1M
   194  `
   195  	vol := mustParseVolume(c, gadgetYaml, "first")
   196  	c.Assert(vol.Structure, HasLen, 4)
   197  
   198  	v, err := gadget.LayoutVolume(p.dir, vol, defaultConstraints)
   199  	c.Assert(err, IsNil)
   200  
   201  	c.Assert(v, DeepEquals, &gadget.LaidOutVolume{
   202  		Volume:     vol,
   203  		Size:       1300 * quantity.SizeMiB,
   204  		SectorSize: 512,
   205  		RootDir:    p.dir,
   206  		LaidOutStructure: []gadget.LaidOutStructure{
   207  			{
   208  				VolumeStructure: &vol.Structure[3],
   209  				StartOffset:     1 * quantity.OffsetMiB,
   210  				Index:           3,
   211  			},
   212  			{
   213  				VolumeStructure: &vol.Structure[1],
   214  				StartOffset:     200 * quantity.OffsetMiB,
   215  				Index:           1,
   216  			},
   217  			{
   218  				VolumeStructure: &vol.Structure[0],
   219  				StartOffset:     800 * quantity.OffsetMiB,
   220  				Index:           0,
   221  			},
   222  			{
   223  				VolumeStructure: &vol.Structure[2],
   224  				StartOffset:     1200 * quantity.OffsetMiB,
   225  				Index:           2,
   226  			},
   227  		},
   228  	})
   229  }
   230  
   231  func (p *layoutTestSuite) TestLayoutVolumeMixedOrdering(c *C) {
   232  	gadgetYaml := `
   233  volumes:
   234    first:
   235      schema: gpt
   236      bootloader: grub
   237      structure:
   238          - type: 00000000-0000-0000-0000-dd00deadbeef
   239            size: 400M
   240            offset: 800M
   241          - type: 00000000-0000-0000-0000-cc00deadbeef
   242            role: system-data
   243            size: 500M
   244            offset: 200M
   245          - type: 00000000-0000-0000-0000-bb00deadbeef
   246            size: 100M
   247          - type: 00000000-0000-0000-0000-aa00deadbeef
   248            size: 100M
   249            offset: 1M
   250  `
   251  	vol := mustParseVolume(c, gadgetYaml, "first")
   252  	c.Assert(vol.Structure, HasLen, 4)
   253  
   254  	v, err := gadget.LayoutVolume(p.dir, vol, defaultConstraints)
   255  	c.Assert(err, IsNil)
   256  
   257  	c.Assert(v, DeepEquals, &gadget.LaidOutVolume{
   258  		Volume:     vol,
   259  		Size:       1200 * quantity.SizeMiB,
   260  		SectorSize: 512,
   261  		RootDir:    p.dir,
   262  		LaidOutStructure: []gadget.LaidOutStructure{
   263  			{
   264  				VolumeStructure: &vol.Structure[3],
   265  				StartOffset:     1 * quantity.OffsetMiB,
   266  				Index:           3,
   267  			},
   268  			{
   269  				VolumeStructure: &vol.Structure[1],
   270  				StartOffset:     200 * quantity.OffsetMiB,
   271  				Index:           1,
   272  			},
   273  			{
   274  				VolumeStructure: &vol.Structure[2],
   275  				StartOffset:     700 * quantity.OffsetMiB,
   276  				Index:           2,
   277  			},
   278  			{
   279  				VolumeStructure: &vol.Structure[0],
   280  				StartOffset:     800 * quantity.OffsetMiB,
   281  				Index:           0,
   282  			},
   283  		},
   284  	})
   285  }
   286  
   287  func (p *layoutTestSuite) TestLayoutVolumeErrorsContentNoSuchFile(c *C) {
   288  	gadgetYaml := `
   289  volumes:
   290    first:
   291      schema: gpt
   292      bootloader: grub
   293      structure:
   294          - type: 00000000-0000-0000-0000-dd00deadbeef
   295            size: 400M
   296            offset: 800M
   297            content:
   298                - image: foo.img
   299  `
   300  	vol := mustParseVolume(c, gadgetYaml, "first")
   301  	v, err := gadget.LayoutVolume(p.dir, vol, defaultConstraints)
   302  	c.Assert(v, IsNil)
   303  	c.Assert(err, ErrorMatches, `cannot lay out structure #0: content "foo.img":.*no such file or directory`)
   304  }
   305  
   306  func makeSizedFile(c *C, path string, size quantity.Size, content []byte) {
   307  	err := os.MkdirAll(filepath.Dir(path), 0755)
   308  	c.Assert(err, IsNil)
   309  
   310  	f, err := os.Create(path)
   311  	c.Assert(err, IsNil)
   312  	defer f.Close()
   313  	if size != 0 {
   314  		err = f.Truncate(int64(size))
   315  		c.Assert(err, IsNil)
   316  	}
   317  	if content != nil {
   318  		_, err := io.Copy(f, bytes.NewReader(content))
   319  		c.Assert(err, IsNil)
   320  	}
   321  }
   322  
   323  func (p *layoutTestSuite) TestLayoutVolumeErrorsContentTooLargeSingle(c *C) {
   324  	gadgetYaml := `
   325  volumes:
   326    first:
   327      schema: gpt
   328      bootloader: grub
   329      structure:
   330          - type: 00000000-0000-0000-0000-dd00deadbeef
   331            size: 1M
   332            content:
   333                - image: foo.img
   334  `
   335  	makeSizedFile(c, filepath.Join(p.dir, "foo.img"), quantity.SizeMiB+1, nil)
   336  
   337  	vol := mustParseVolume(c, gadgetYaml, "first")
   338  
   339  	v, err := gadget.LayoutVolume(p.dir, vol, defaultConstraints)
   340  	c.Assert(v, IsNil)
   341  	c.Assert(err, ErrorMatches, `cannot lay out structure #0: content "foo.img" does not fit in the structure`)
   342  }
   343  
   344  func (p *layoutTestSuite) TestLayoutVolumeErrorsContentTooLargeMany(c *C) {
   345  	gadgetYaml := `
   346  volumes:
   347    first:
   348      schema: gpt
   349      bootloader: grub
   350      structure:
   351          - type: 00000000-0000-0000-0000-dd00deadbeef
   352            size: 2M
   353            content:
   354                - image: foo.img
   355                - image: bar.img
   356  `
   357  	makeSizedFile(c, filepath.Join(p.dir, "foo.img"), quantity.SizeMiB+1, nil)
   358  	makeSizedFile(c, filepath.Join(p.dir, "bar.img"), quantity.SizeMiB+1, nil)
   359  
   360  	vol := mustParseVolume(c, gadgetYaml, "first")
   361  
   362  	constraints := gadget.LayoutConstraints{
   363  		NonMBRStartOffset: 1 * quantity.OffsetMiB,
   364  		SectorSize:        512,
   365  	}
   366  	v, err := gadget.LayoutVolume(p.dir, vol, constraints)
   367  	c.Assert(v, IsNil)
   368  	c.Assert(err, ErrorMatches, `cannot lay out structure #0: content "bar.img" does not fit in the structure`)
   369  }
   370  
   371  func (p *layoutTestSuite) TestLayoutVolumeErrorsContentTooLargeWithOffset(c *C) {
   372  	gadgetYaml := `
   373  volumes:
   374    first:
   375      schema: gpt
   376      bootloader: grub
   377      structure:
   378          - type: 00000000-0000-0000-0000-dd00deadbeef
   379            size: 1M
   380            content:
   381                - image: foo.img
   382                  # 512kB
   383                  offset: 524288
   384  `
   385  	makeSizedFile(c, filepath.Join(p.dir, "foo.img"), quantity.SizeMiB, nil)
   386  
   387  	vol := mustParseVolume(c, gadgetYaml, "first")
   388  
   389  	v, err := gadget.LayoutVolume(p.dir, vol, defaultConstraints)
   390  	c.Assert(v, IsNil)
   391  	c.Assert(err, ErrorMatches, `cannot lay out structure #0: content "foo.img" does not fit in the structure`)
   392  }
   393  
   394  func (p *layoutTestSuite) TestLayoutVolumeErrorsContentLargerThanDeclared(c *C) {
   395  	gadgetYaml := `
   396  volumes:
   397    first:
   398      schema: gpt
   399      bootloader: grub
   400      structure:
   401          - type: 00000000-0000-0000-0000-dd00deadbeef
   402            size: 2M
   403            content:
   404                - image: foo.img
   405                  size: 1M
   406  `
   407  	makeSizedFile(c, filepath.Join(p.dir, "foo.img"), quantity.SizeMiB+1, nil)
   408  
   409  	vol := mustParseVolume(c, gadgetYaml, "first")
   410  
   411  	v, err := gadget.LayoutVolume(p.dir, vol, defaultConstraints)
   412  	c.Assert(v, IsNil)
   413  	c.Assert(err, ErrorMatches, fmt.Sprintf(`cannot lay out structure #0: content "foo.img" size %v is larger than declared %v`, quantity.SizeMiB+1, quantity.SizeMiB))
   414  }
   415  
   416  func (p *layoutTestSuite) TestLayoutVolumeErrorsContentOverlap(c *C) {
   417  	gadgetYaml := `
   418  volumes:
   419    first:
   420      schema: gpt
   421      bootloader: grub
   422      structure:
   423          - type: 00000000-0000-0000-0000-dd00deadbeef
   424            size: 2M
   425            content:
   426                - image: foo.img
   427                  size: 1M
   428                  # 512kB
   429                  offset: 524288
   430                - image: bar.img
   431                  size: 1M
   432                  offset: 0
   433  `
   434  	makeSizedFile(c, filepath.Join(p.dir, "foo.img"), quantity.SizeMiB, nil)
   435  	makeSizedFile(c, filepath.Join(p.dir, "bar.img"), quantity.SizeMiB, nil)
   436  
   437  	vol := mustParseVolume(c, gadgetYaml, "first")
   438  
   439  	v, err := gadget.LayoutVolume(p.dir, vol, defaultConstraints)
   440  	c.Assert(v, IsNil)
   441  	c.Assert(err, ErrorMatches, `cannot lay out structure #0: content "foo.img" overlaps with preceding image "bar.img"`)
   442  }
   443  
   444  func (p *layoutTestSuite) TestLayoutVolumeContentExplicitOrder(c *C) {
   445  	gadgetYaml := `
   446  volumes:
   447    first:
   448      schema: gpt
   449      bootloader: grub
   450      structure:
   451          - type: 00000000-0000-0000-0000-0000deadbeef
   452            size: 2M
   453            content:
   454                - image: foo.img
   455                  size: 1M
   456                  offset: 1M
   457                - image: bar.img
   458                  size: 1M
   459                  offset: 0
   460  `
   461  	makeSizedFile(c, filepath.Join(p.dir, "foo.img"), quantity.SizeMiB, nil)
   462  	makeSizedFile(c, filepath.Join(p.dir, "bar.img"), quantity.SizeMiB, nil)
   463  
   464  	vol := mustParseVolume(c, gadgetYaml, "first")
   465  	c.Assert(vol.Structure, HasLen, 1)
   466  	c.Assert(vol.Structure[0].Content, HasLen, 2)
   467  
   468  	v, err := gadget.LayoutVolume(p.dir, vol, defaultConstraints)
   469  	c.Assert(err, IsNil)
   470  	c.Assert(v, DeepEquals, &gadget.LaidOutVolume{
   471  		Volume:     vol,
   472  		Size:       3 * quantity.SizeMiB,
   473  		SectorSize: 512,
   474  		RootDir:    p.dir,
   475  		LaidOutStructure: []gadget.LaidOutStructure{
   476  			{
   477  				VolumeStructure: &vol.Structure[0],
   478  				StartOffset:     1 * quantity.OffsetMiB,
   479  				LaidOutContent: []gadget.LaidOutContent{
   480  					{
   481  						VolumeContent: &vol.Structure[0].Content[1],
   482  						StartOffset:   1 * quantity.OffsetMiB,
   483  						Size:          quantity.SizeMiB,
   484  						Index:         1,
   485  					},
   486  					{
   487  						VolumeContent: &vol.Structure[0].Content[0],
   488  						StartOffset:   2 * quantity.OffsetMiB,
   489  						Size:          quantity.SizeMiB,
   490  						Index:         0,
   491  					},
   492  				},
   493  			},
   494  		},
   495  	})
   496  }
   497  
   498  func (p *layoutTestSuite) TestLayoutVolumeContentImplicitOrder(c *C) {
   499  	gadgetYaml := `
   500  volumes:
   501    first:
   502      schema: gpt
   503      bootloader: grub
   504      structure:
   505          - type: 00000000-0000-0000-0000-0000deadbeef
   506            size: 2M
   507            content:
   508                - image: foo.img
   509                  size: 1M
   510                - image: bar.img
   511                  size: 1M
   512  `
   513  	makeSizedFile(c, filepath.Join(p.dir, "foo.img"), quantity.SizeMiB, nil)
   514  	makeSizedFile(c, filepath.Join(p.dir, "bar.img"), quantity.SizeMiB, nil)
   515  
   516  	vol := mustParseVolume(c, gadgetYaml, "first")
   517  	c.Assert(vol.Structure, HasLen, 1)
   518  	c.Assert(vol.Structure[0].Content, HasLen, 2)
   519  
   520  	v, err := gadget.LayoutVolume(p.dir, vol, defaultConstraints)
   521  	c.Assert(err, IsNil)
   522  	c.Assert(v, DeepEquals, &gadget.LaidOutVolume{
   523  		Volume:     vol,
   524  		Size:       3 * quantity.SizeMiB,
   525  		SectorSize: 512,
   526  		RootDir:    p.dir,
   527  		LaidOutStructure: []gadget.LaidOutStructure{
   528  			{
   529  				VolumeStructure: &vol.Structure[0],
   530  				StartOffset:     1 * quantity.OffsetMiB,
   531  				LaidOutContent: []gadget.LaidOutContent{
   532  					{
   533  						VolumeContent: &vol.Structure[0].Content[0],
   534  						StartOffset:   1 * quantity.OffsetMiB,
   535  						Size:          quantity.SizeMiB,
   536  						Index:         0,
   537  					},
   538  					{
   539  						VolumeContent: &vol.Structure[0].Content[1],
   540  						StartOffset:   2 * quantity.OffsetMiB,
   541  						Size:          quantity.SizeMiB,
   542  						Index:         1,
   543  					},
   544  				},
   545  			},
   546  		},
   547  	})
   548  }
   549  
   550  func (p *layoutTestSuite) TestLayoutVolumeContentImplicitSize(c *C) {
   551  	gadgetYaml := `
   552  volumes:
   553    first:
   554      schema: gpt
   555      bootloader: grub
   556      structure:
   557          - type: 00000000-0000-0000-0000-0000deadbeef
   558            size: 2M
   559            content:
   560                - image: foo.img
   561  `
   562  	size1_5MiB := quantity.SizeMiB + quantity.SizeMiB/2
   563  	makeSizedFile(c, filepath.Join(p.dir, "foo.img"), size1_5MiB, nil)
   564  
   565  	vol := mustParseVolume(c, gadgetYaml, "first")
   566  	c.Assert(vol.Structure, HasLen, 1)
   567  	c.Assert(vol.Structure[0].Content, HasLen, 1)
   568  
   569  	v, err := gadget.LayoutVolume(p.dir, vol, defaultConstraints)
   570  	c.Assert(err, IsNil)
   571  	c.Assert(v, DeepEquals, &gadget.LaidOutVolume{
   572  		Volume:     vol,
   573  		Size:       3 * quantity.SizeMiB,
   574  		SectorSize: 512,
   575  		RootDir:    p.dir,
   576  		LaidOutStructure: []gadget.LaidOutStructure{
   577  			{
   578  				VolumeStructure: &vol.Structure[0],
   579  				StartOffset:     1 * quantity.OffsetMiB,
   580  				LaidOutContent: []gadget.LaidOutContent{
   581  					{
   582  						VolumeContent: &vol.Structure[0].Content[0],
   583  						StartOffset:   1 * quantity.OffsetMiB,
   584  						Size:          size1_5MiB,
   585  					},
   586  				},
   587  			},
   588  		},
   589  	})
   590  }
   591  
   592  func (p *layoutTestSuite) TestLayoutVolumeContentNonBare(c *C) {
   593  	gadgetYaml := `
   594  volumes:
   595    first:
   596      schema: gpt
   597      bootloader: grub
   598      structure:
   599          - type: 00000000-0000-0000-0000-0000deadbeef
   600            filesystem: ext4
   601            size: 2M
   602            content:
   603                - source: foo.txt
   604                  target: /boot
   605  `
   606  	makeSizedFile(c, filepath.Join(p.dir, "foo.txt"), 0, []byte("foobar\n"))
   607  
   608  	vol := mustParseVolume(c, gadgetYaml, "first")
   609  	c.Assert(vol.Structure, HasLen, 1)
   610  	c.Assert(vol.Structure[0].Content, HasLen, 1)
   611  
   612  	v, err := gadget.LayoutVolume(p.dir, vol, defaultConstraints)
   613  	c.Assert(err, IsNil)
   614  	c.Assert(v, DeepEquals, &gadget.LaidOutVolume{
   615  		Volume:     vol,
   616  		Size:       3 * quantity.SizeMiB,
   617  		SectorSize: 512,
   618  		RootDir:    p.dir,
   619  		LaidOutStructure: []gadget.LaidOutStructure{
   620  			{
   621  				VolumeStructure: &vol.Structure[0],
   622  				StartOffset:     1 * quantity.OffsetMiB,
   623  			},
   624  		},
   625  	})
   626  }
   627  
   628  func (p *layoutTestSuite) TestLayoutVolumeConstraintsChange(c *C) {
   629  	gadgetYaml := `
   630  volumes:
   631    first:
   632      schema: gpt
   633      bootloader: grub
   634      structure:
   635          - role: mbr
   636            type: bare
   637            size: 446
   638            offset: 0
   639          - type: 00000000-0000-0000-0000-0000deadbeef
   640            filesystem: ext4
   641            size: 2M
   642            content:
   643                - source: foo.txt
   644                  target: /boot
   645  `
   646  	makeSizedFile(c, filepath.Join(p.dir, "foo.txt"), 0, []byte("foobar\n"))
   647  
   648  	vol := mustParseVolume(c, gadgetYaml, "first")
   649  	c.Assert(vol.Structure, HasLen, 2)
   650  	c.Assert(vol.Structure[1].Content, HasLen, 1)
   651  
   652  	v, err := gadget.LayoutVolume(p.dir, vol, defaultConstraints)
   653  	c.Assert(err, IsNil)
   654  	c.Assert(v, DeepEquals, &gadget.LaidOutVolume{
   655  		Volume:     vol,
   656  		Size:       3 * quantity.SizeMiB,
   657  		SectorSize: 512,
   658  		RootDir:    p.dir,
   659  		LaidOutStructure: []gadget.LaidOutStructure{
   660  			{
   661  				VolumeStructure: &vol.Structure[0],
   662  				StartOffset:     0,
   663  				Index:           0,
   664  			},
   665  			{
   666  				VolumeStructure: &vol.Structure[1],
   667  				StartOffset:     1 * quantity.OffsetMiB,
   668  				Index:           1,
   669  			},
   670  		},
   671  	})
   672  
   673  	// still valid
   674  	constraints := gadget.LayoutConstraints{
   675  		// 512kiB
   676  		NonMBRStartOffset: 512 * quantity.OffsetKiB,
   677  		SectorSize:        512,
   678  	}
   679  	v, err = gadget.LayoutVolume(p.dir, vol, constraints)
   680  	c.Assert(err, IsNil)
   681  	c.Assert(v, DeepEquals, &gadget.LaidOutVolume{
   682  		Volume:     vol,
   683  		Size:       2*quantity.SizeMiB + 512*quantity.SizeKiB,
   684  		SectorSize: 512,
   685  		RootDir:    p.dir,
   686  		LaidOutStructure: []gadget.LaidOutStructure{
   687  			{
   688  				VolumeStructure: &vol.Structure[0],
   689  				StartOffset:     0,
   690  				Index:           0,
   691  			},
   692  			{
   693  				VolumeStructure: &vol.Structure[1],
   694  				StartOffset:     512 * quantity.OffsetKiB,
   695  				Index:           1,
   696  			},
   697  		},
   698  	})
   699  
   700  	// constraints would make a non MBR structure overlap with MBR, but
   701  	// structures start one after another unless offset is specified
   702  	// explicitly
   703  	constraintsBad := gadget.LayoutConstraints{
   704  		NonMBRStartOffset: 400,
   705  		SectorSize:        512,
   706  	}
   707  	v, err = gadget.LayoutVolume(p.dir, vol, constraintsBad)
   708  	c.Assert(err, IsNil)
   709  	c.Assert(v, DeepEquals, &gadget.LaidOutVolume{
   710  		Volume:     vol,
   711  		Size:       2*quantity.SizeMiB + 446,
   712  		SectorSize: 512,
   713  		RootDir:    p.dir,
   714  		LaidOutStructure: []gadget.LaidOutStructure{
   715  			{
   716  				VolumeStructure: &vol.Structure[0],
   717  				Index:           0,
   718  			},
   719  			{
   720  				VolumeStructure: &vol.Structure[1],
   721  				StartOffset:     446,
   722  				Index:           1,
   723  			},
   724  		},
   725  	})
   726  
   727  	// sector size is properly recorded
   728  	constraintsSector := gadget.LayoutConstraints{
   729  		NonMBRStartOffset: 1 * quantity.OffsetMiB,
   730  		SectorSize:        1024,
   731  	}
   732  	v, err = gadget.LayoutVolume(p.dir, vol, constraintsSector)
   733  	c.Assert(err, IsNil)
   734  	c.Assert(v, DeepEquals, &gadget.LaidOutVolume{
   735  		Volume:     vol,
   736  		Size:       3 * quantity.SizeMiB,
   737  		SectorSize: 1024,
   738  		RootDir:    p.dir,
   739  		LaidOutStructure: []gadget.LaidOutStructure{
   740  			{
   741  				VolumeStructure: &vol.Structure[0],
   742  				Index:           0,
   743  			},
   744  			{
   745  				VolumeStructure: &vol.Structure[1],
   746  				StartOffset:     1 * quantity.OffsetMiB,
   747  				Index:           1,
   748  			},
   749  		},
   750  	})
   751  }
   752  
   753  func (p *layoutTestSuite) TestLayoutVolumeConstraintsSectorSize(c *C) {
   754  	gadgetYaml := `
   755  volumes:
   756    first:
   757      schema: gpt
   758      bootloader: grub
   759      structure:
   760          - role: mbr
   761            type: bare
   762            size: 446
   763            offset: 0
   764          - type: 00000000-0000-0000-0000-0000deadbeef
   765            filesystem: ext4
   766            size: 2M
   767            content:
   768                - source: foo.txt
   769                  target: /boot
   770  `
   771  	makeSizedFile(c, filepath.Join(p.dir, "foo.txt"), 0, []byte("foobar\n"))
   772  
   773  	vol := mustParseVolume(c, gadgetYaml, "first")
   774  
   775  	constraintsBadSectorSize := gadget.LayoutConstraints{
   776  		NonMBRStartOffset: 1 * quantity.OffsetMiB,
   777  		SectorSize:        384,
   778  	}
   779  	_, err := gadget.LayoutVolume(p.dir, vol, constraintsBadSectorSize)
   780  	c.Assert(err, ErrorMatches, "cannot lay out volume, structure #1 size is not a multiple of sector size 384")
   781  }
   782  
   783  func (p *layoutTestSuite) TestLayoutVolumeConstraintsNeedsSectorSize(c *C) {
   784  	constraintsBadSectorSize := gadget.LayoutConstraints{
   785  		NonMBRStartOffset: 1 * quantity.OffsetMiB,
   786  		// SectorSize left unspecified
   787  	}
   788  	_, err := gadget.LayoutVolume(p.dir, &gadget.Volume{}, constraintsBadSectorSize)
   789  	c.Assert(err, ErrorMatches, "cannot lay out volume, invalid constraints: sector size cannot be 0")
   790  }
   791  
   792  func (p *layoutTestSuite) TestLayoutVolumeMBRImplicitConstraints(c *C) {
   793  	gadgetYaml := `
   794  volumes:
   795    first:
   796      schema: gpt
   797      bootloader: grub
   798      structure:
   799          - name: mbr
   800            type: bare
   801            role: mbr
   802            size: 446
   803          - name: other
   804            type: 00000000-0000-0000-0000-0000deadbeef
   805            size: 1M
   806  `
   807  	vol := mustParseVolume(c, gadgetYaml, "first")
   808  	c.Assert(vol.Structure, HasLen, 2)
   809  
   810  	v, err := gadget.LayoutVolume(p.dir, vol, defaultConstraints)
   811  	c.Assert(err, IsNil)
   812  	c.Assert(v, DeepEquals, &gadget.LaidOutVolume{
   813  		Volume:     vol,
   814  		Size:       2 * quantity.SizeMiB,
   815  		SectorSize: 512,
   816  		RootDir:    p.dir,
   817  		LaidOutStructure: []gadget.LaidOutStructure{
   818  			{
   819  				// MBR
   820  				VolumeStructure: &vol.Structure[0],
   821  				StartOffset:     0,
   822  				Index:           0,
   823  			}, {
   824  				VolumeStructure: &vol.Structure[1],
   825  				StartOffset:     1 * quantity.OffsetMiB,
   826  				Index:           1,
   827  			},
   828  		},
   829  	})
   830  }
   831  
   832  func (p *layoutTestSuite) TestLayoutVolumeOffsetWriteAll(c *C) {
   833  	var gadgetYaml = `
   834  volumes:
   835    pc:
   836      bootloader: grub
   837      structure:
   838        - name: mbr
   839          type: mbr
   840          size: 440
   841        - name: foo
   842          type: DA,21686148-6449-6E6F-744E-656564454649
   843          size: 1M
   844          offset: 1M
   845          offset-write: mbr+92
   846          content:
   847            - image: foo.img
   848              offset-write: bar+10
   849        - name: bar
   850          type: DA,21686148-6449-6E6F-744E-656564454649
   851          size: 1M
   852          offset-write: 600
   853          content:
   854            - image: bar.img
   855              offset-write: 450
   856  `
   857  	makeSizedFile(c, filepath.Join(p.dir, "foo.img"), 200*quantity.SizeKiB, []byte(""))
   858  	makeSizedFile(c, filepath.Join(p.dir, "bar.img"), 150*quantity.SizeKiB, []byte(""))
   859  
   860  	vol := mustParseVolume(c, gadgetYaml, "pc")
   861  	c.Assert(vol.Structure, HasLen, 3)
   862  
   863  	v, err := gadget.LayoutVolume(p.dir, vol, defaultConstraints)
   864  	c.Assert(err, IsNil)
   865  	c.Assert(v, DeepEquals, &gadget.LaidOutVolume{
   866  		Volume:     vol,
   867  		Size:       3 * quantity.SizeMiB,
   868  		SectorSize: 512,
   869  		RootDir:    p.dir,
   870  		LaidOutStructure: []gadget.LaidOutStructure{
   871  			{
   872  				// mbr
   873  				VolumeStructure: &vol.Structure[0],
   874  				StartOffset:     0,
   875  				Index:           0,
   876  			}, {
   877  				// foo
   878  				VolumeStructure: &vol.Structure[1],
   879  				StartOffset:     1 * quantity.OffsetMiB,
   880  				Index:           1,
   881  				// break for gofmt < 1.11
   882  				AbsoluteOffsetWrite: asOffsetPtr(92),
   883  				LaidOutContent: []gadget.LaidOutContent{
   884  					{
   885  						VolumeContent: &vol.Structure[1].Content[0],
   886  						Size:          200 * quantity.SizeKiB,
   887  						StartOffset:   1 * quantity.OffsetMiB,
   888  						// offset-write: bar+10
   889  						AbsoluteOffsetWrite: asOffsetPtr(2*quantity.OffsetMiB + 10),
   890  					},
   891  				},
   892  			}, {
   893  				// bar
   894  				VolumeStructure: &vol.Structure[2],
   895  				StartOffset:     2 * quantity.OffsetMiB,
   896  				Index:           2,
   897  				// break for gofmt < 1.11
   898  				AbsoluteOffsetWrite: asOffsetPtr(600),
   899  				LaidOutContent: []gadget.LaidOutContent{
   900  					{
   901  						VolumeContent: &vol.Structure[2].Content[0],
   902  						Size:          150 * quantity.SizeKiB,
   903  						StartOffset:   2 * quantity.OffsetMiB,
   904  						// offset-write: bar+10
   905  						AbsoluteOffsetWrite: asOffsetPtr(450),
   906  					},
   907  				},
   908  			},
   909  		},
   910  	})
   911  }
   912  
   913  func (p *layoutTestSuite) TestLayoutVolumeOffsetWriteBadRelativeTo(c *C) {
   914  	// define volumes explicitly as those would not pass validation
   915  	volBadStructure := gadget.Volume{
   916  		Structure: []gadget.VolumeStructure{
   917  			{
   918  				Name: "foo",
   919  				Type: "DA,21686148-6449-6E6F-744E-656564454649",
   920  				Size: 1 * quantity.SizeMiB,
   921  				OffsetWrite: &gadget.RelativeOffset{
   922  					RelativeTo: "bar",
   923  					Offset:     10,
   924  				},
   925  			},
   926  		},
   927  	}
   928  	volBadContent := gadget.Volume{
   929  		Structure: []gadget.VolumeStructure{
   930  			{
   931  				Name: "foo",
   932  				Type: "DA,21686148-6449-6E6F-744E-656564454649",
   933  				Size: 1 * quantity.SizeMiB,
   934  				Content: []gadget.VolumeContent{
   935  					{
   936  						Image: "foo.img",
   937  						OffsetWrite: &gadget.RelativeOffset{
   938  							RelativeTo: "bar",
   939  							Offset:     10,
   940  						},
   941  					},
   942  				},
   943  			},
   944  		},
   945  	}
   946  
   947  	makeSizedFile(c, filepath.Join(p.dir, "foo.img"), 200*quantity.SizeKiB, []byte(""))
   948  
   949  	v, err := gadget.LayoutVolume(p.dir, &volBadStructure, defaultConstraints)
   950  	c.Check(v, IsNil)
   951  	c.Check(err, ErrorMatches, `cannot resolve offset-write of structure #0 \("foo"\): refers to an unknown structure "bar"`)
   952  
   953  	v, err = gadget.LayoutVolume(p.dir, &volBadContent, defaultConstraints)
   954  	c.Check(v, IsNil)
   955  	c.Check(err, ErrorMatches, `cannot resolve offset-write of structure #0 \("foo"\) content "foo.img": refers to an unknown structure "bar"`)
   956  }
   957  
   958  func (p *layoutTestSuite) TestLayoutVolumeOffsetWriteEnlargesVolume(c *C) {
   959  	var gadgetYamlStructure = `
   960  volumes:
   961    pc:
   962      bootloader: grub
   963      structure:
   964        - name: mbr
   965          type: mbr
   966          size: 440
   967        - name: foo
   968          type: DA,21686148-6449-6E6F-744E-656564454649
   969          size: 1M
   970          offset: 1M
   971          # 1GB
   972          offset-write: mbr+1073741824
   973  
   974  `
   975  	vol := mustParseVolume(c, gadgetYamlStructure, "pc")
   976  
   977  	v, err := gadget.LayoutVolume(p.dir, vol, defaultConstraints)
   978  	c.Assert(err, IsNil)
   979  	// offset-write is at 1GB
   980  	c.Check(v.Size, Equals, 1*quantity.SizeGiB+gadget.SizeLBA48Pointer)
   981  
   982  	var gadgetYamlContent = `
   983  volumes:
   984    pc:
   985      bootloader: grub
   986      structure:
   987        - name: mbr
   988          type: mbr
   989          size: 440
   990        - name: foo
   991          type: DA,21686148-6449-6E6F-744E-656564454649
   992          size: 1M
   993          offset: 1M
   994          content:
   995            - image: foo.img
   996              # 2GB
   997              offset-write: mbr+2147483648
   998            - image: bar.img
   999              # 1GB
  1000              offset-write: mbr+1073741824
  1001            - image: baz.img
  1002              # 3GB
  1003              offset-write: mbr+3221225472
  1004  
  1005  `
  1006  	makeSizedFile(c, filepath.Join(p.dir, "foo.img"), 200*quantity.SizeKiB, []byte(""))
  1007  	makeSizedFile(c, filepath.Join(p.dir, "bar.img"), 150*quantity.SizeKiB, []byte(""))
  1008  	makeSizedFile(c, filepath.Join(p.dir, "baz.img"), 100*quantity.SizeKiB, []byte(""))
  1009  
  1010  	vol = mustParseVolume(c, gadgetYamlContent, "pc")
  1011  
  1012  	v, err = gadget.LayoutVolume(p.dir, vol, defaultConstraints)
  1013  	c.Assert(err, IsNil)
  1014  	// foo.img offset-write is at 3GB
  1015  	c.Check(v.Size, Equals, 3*quantity.SizeGiB+gadget.SizeLBA48Pointer)
  1016  }
  1017  
  1018  func (p *layoutTestSuite) TestLayoutVolumePartialNoSuchFile(c *C) {
  1019  	gadgetYaml := `
  1020  volumes:
  1021    first:
  1022      schema: gpt
  1023      bootloader: grub
  1024      structure:
  1025          - type: 00000000-0000-0000-0000-dd00deadbeef
  1026            size: 400M
  1027            offset: 800M
  1028            content:
  1029                - image: foo.img
  1030  `
  1031  	vol := mustParseVolume(c, gadgetYaml, "first")
  1032  	c.Assert(vol.Structure, HasLen, 1)
  1033  
  1034  	v, err := gadget.LayoutVolumePartially(vol, defaultConstraints)
  1035  	c.Assert(v, DeepEquals, &gadget.PartiallyLaidOutVolume{
  1036  		Volume:     vol,
  1037  		SectorSize: 512,
  1038  		LaidOutStructure: []gadget.LaidOutStructure{
  1039  			{
  1040  				VolumeStructure: &vol.Structure[0],
  1041  				StartOffset:     800 * quantity.OffsetMiB,
  1042  				Index:           0,
  1043  			},
  1044  		},
  1045  	})
  1046  	c.Assert(err, IsNil)
  1047  }
  1048  
  1049  func (p *layoutTestSuite) TestLaidOutStructureShift(c *C) {
  1050  	var gadgetYamlContent = `
  1051  volumes:
  1052    pc:
  1053      bootloader: grub
  1054      structure:
  1055        - name: foo
  1056          type: DA,21686148-6449-6E6F-744E-656564454649
  1057          size: 1M
  1058          offset: 1M
  1059          content:
  1060            - image: foo.img
  1061            - image: bar.img
  1062              # 300KB
  1063              offset: 307200
  1064  
  1065  `
  1066  	makeSizedFile(c, filepath.Join(p.dir, "foo.img"), 200*quantity.SizeKiB, []byte(""))
  1067  	makeSizedFile(c, filepath.Join(p.dir, "bar.img"), 150*quantity.SizeKiB, []byte(""))
  1068  
  1069  	vol := mustParseVolume(c, gadgetYamlContent, "pc")
  1070  
  1071  	v, err := gadget.LayoutVolume(p.dir, vol, defaultConstraints)
  1072  	c.Assert(err, IsNil)
  1073  	c.Assert(v.LaidOutStructure, HasLen, 1)
  1074  	c.Assert(v.LaidOutStructure[0].LaidOutContent, HasLen, 2)
  1075  
  1076  	ps := v.LaidOutStructure[0]
  1077  
  1078  	c.Assert(ps, DeepEquals, gadget.LaidOutStructure{
  1079  		// foo
  1080  		VolumeStructure: &vol.Structure[0],
  1081  		StartOffset:     1 * quantity.OffsetMiB,
  1082  		Index:           0,
  1083  		LaidOutContent: []gadget.LaidOutContent{
  1084  			{
  1085  				VolumeContent: &vol.Structure[0].Content[0],
  1086  				Size:          200 * quantity.SizeKiB,
  1087  				StartOffset:   1 * quantity.OffsetMiB,
  1088  				Index:         0,
  1089  			}, {
  1090  				VolumeContent: &vol.Structure[0].Content[1],
  1091  				Size:          150 * quantity.SizeKiB,
  1092  				StartOffset:   1*quantity.OffsetMiB + 300*quantity.OffsetKiB,
  1093  				Index:         1,
  1094  			},
  1095  		},
  1096  	})
  1097  
  1098  	shiftedTo0 := gadget.ShiftStructureTo(ps, 0)
  1099  	c.Assert(shiftedTo0, DeepEquals, gadget.LaidOutStructure{
  1100  		// foo
  1101  		VolumeStructure: &vol.Structure[0],
  1102  		StartOffset:     0,
  1103  		Index:           0,
  1104  		LaidOutContent: []gadget.LaidOutContent{
  1105  			{
  1106  				VolumeContent: &vol.Structure[0].Content[0],
  1107  				Size:          200 * quantity.SizeKiB,
  1108  				StartOffset:   0,
  1109  				Index:         0,
  1110  			}, {
  1111  				VolumeContent: &vol.Structure[0].Content[1],
  1112  				Size:          150 * quantity.SizeKiB,
  1113  				StartOffset:   300 * quantity.OffsetKiB,
  1114  				Index:         1,
  1115  			},
  1116  		},
  1117  	})
  1118  
  1119  	shiftedTo2M := gadget.ShiftStructureTo(ps, 2*quantity.OffsetMiB)
  1120  	c.Assert(shiftedTo2M, DeepEquals, gadget.LaidOutStructure{
  1121  		// foo
  1122  		VolumeStructure: &vol.Structure[0],
  1123  		StartOffset:     2 * quantity.OffsetMiB,
  1124  		Index:           0,
  1125  		LaidOutContent: []gadget.LaidOutContent{
  1126  			{
  1127  				VolumeContent: &vol.Structure[0].Content[0],
  1128  				Size:          200 * quantity.SizeKiB,
  1129  				StartOffset:   2 * quantity.OffsetMiB,
  1130  				Index:         0,
  1131  			}, {
  1132  				VolumeContent: &vol.Structure[0].Content[1],
  1133  				Size:          150 * quantity.SizeKiB,
  1134  				StartOffset:   2*quantity.OffsetMiB + 300*quantity.OffsetKiB,
  1135  				Index:         1,
  1136  			},
  1137  		},
  1138  	})
  1139  }