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