github.com/anonymouse64/snapd@v0.0.0-20210824153203-04c4c42d842d/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  	"io/ioutil"
    27  	"os"
    28  	"path/filepath"
    29  	"strings"
    30  
    31  	. "gopkg.in/check.v1"
    32  
    33  	"github.com/snapcore/snapd/gadget"
    34  	"github.com/snapcore/snapd/gadget/quantity"
    35  	"github.com/snapcore/snapd/kernel"
    36  )
    37  
    38  type layoutTestSuite struct {
    39  	dir string
    40  }
    41  
    42  var _ = Suite(&layoutTestSuite{})
    43  
    44  func (p *layoutTestSuite) SetUpTest(c *C) {
    45  	p.dir = c.MkDir()
    46  }
    47  
    48  var defaultConstraints = gadget.LayoutConstraints{
    49  	NonMBRStartOffset: 1 * quantity.OffsetMiB,
    50  }
    51  
    52  func (p *layoutTestSuite) TestVolumeSize(c *C) {
    53  	vol := gadget.Volume{
    54  		Structure: []gadget.VolumeStructure{
    55  			{Size: 2 * quantity.SizeMiB},
    56  		},
    57  	}
    58  	v, err := gadget.LayoutVolume(p.dir, "", &vol, defaultConstraints)
    59  	c.Assert(err, IsNil)
    60  
    61  	c.Assert(v, DeepEquals, &gadget.LaidOutVolume{
    62  		Volume: &gadget.Volume{
    63  			Structure: []gadget.VolumeStructure{
    64  				{Size: 2 * quantity.SizeMiB},
    65  			},
    66  		},
    67  		Size:    3 * quantity.SizeMiB,
    68  		RootDir: p.dir,
    69  		LaidOutStructure: []gadget.LaidOutStructure{{
    70  			VolumeStructure: &gadget.VolumeStructure{Size: 2 * quantity.SizeMiB},
    71  			StartOffset:     1 * quantity.OffsetMiB,
    72  		}},
    73  	})
    74  }
    75  
    76  func mustParseVolume(c *C, gadgetYaml, volume string) *gadget.Volume {
    77  	gi, err := gadget.InfoFromGadgetYaml([]byte(gadgetYaml), nil)
    78  	c.Assert(err, IsNil)
    79  	v, ok := gi.Volumes[volume]
    80  	c.Assert(ok, Equals, true, Commentf("volume %q not found in gadget", volume))
    81  	return v
    82  }
    83  
    84  func (p *layoutTestSuite) TestLayoutVolumeMinimal(c *C) {
    85  	gadgetYaml := `
    86  volumes:
    87    first-image:
    88      bootloader: u-boot
    89      structure:
    90          - type: 00000000-0000-0000-0000-0000deadbeef
    91            size: 400M
    92          - type: 83,00000000-0000-0000-0000-0000feedface
    93            role: system-data
    94            size: 100M
    95  `
    96  	vol := mustParseVolume(c, gadgetYaml, "first-image")
    97  	c.Assert(vol.Structure, HasLen, 2)
    98  
    99  	v, err := gadget.LayoutVolume(p.dir, "", vol, defaultConstraints)
   100  	c.Assert(err, IsNil)
   101  
   102  	c.Assert(v, DeepEquals, &gadget.LaidOutVolume{
   103  		Volume:  vol,
   104  		Size:    501 * quantity.SizeMiB,
   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  		RootDir: p.dir,
   148  		LaidOutStructure: []gadget.LaidOutStructure{
   149  			{
   150  				VolumeStructure: &vol.Structure[0],
   151  				StartOffset:     1 * quantity.OffsetMiB,
   152  				Index:           0,
   153  			},
   154  			{
   155  				VolumeStructure: &vol.Structure[1],
   156  				StartOffset:     401 * quantity.OffsetMiB,
   157  				Index:           1,
   158  			},
   159  			{
   160  				VolumeStructure: &vol.Structure[2],
   161  				StartOffset:     901 * quantity.OffsetMiB,
   162  				Index:           2,
   163  			},
   164  			{
   165  				VolumeStructure: &vol.Structure[3],
   166  				StartOffset:     1001 * quantity.OffsetMiB,
   167  				Index:           3,
   168  			},
   169  		},
   170  	})
   171  }
   172  
   173  func (p *layoutTestSuite) TestLayoutVolumeExplicitOrdering(c *C) {
   174  	gadgetYaml := `
   175  volumes:
   176    first:
   177      schema: gpt
   178      bootloader: grub
   179      structure:
   180          - type: 00000000-0000-0000-0000-dd00deadbeef
   181            size: 400M
   182            offset: 800M
   183          - type: 00000000-0000-0000-0000-cc00deadbeef
   184            role: system-data
   185            size: 500M
   186            offset: 200M
   187          - type: 00000000-0000-0000-0000-bb00deadbeef
   188            size: 100M
   189            offset: 1200M
   190          - type: 00000000-0000-0000-0000-aa00deadbeef
   191            size: 100M
   192            offset: 1M
   193  `
   194  	vol := mustParseVolume(c, gadgetYaml, "first")
   195  	c.Assert(vol.Structure, HasLen, 4)
   196  
   197  	v, err := gadget.LayoutVolume(p.dir, "", vol, defaultConstraints)
   198  	c.Assert(err, IsNil)
   199  
   200  	c.Assert(v, DeepEquals, &gadget.LaidOutVolume{
   201  		Volume:  vol,
   202  		Size:    1300 * quantity.SizeMiB,
   203  		RootDir: p.dir,
   204  		LaidOutStructure: []gadget.LaidOutStructure{
   205  			{
   206  				VolumeStructure: &vol.Structure[3],
   207  				StartOffset:     1 * quantity.OffsetMiB,
   208  				Index:           3,
   209  			},
   210  			{
   211  				VolumeStructure: &vol.Structure[1],
   212  				StartOffset:     200 * quantity.OffsetMiB,
   213  				Index:           1,
   214  			},
   215  			{
   216  				VolumeStructure: &vol.Structure[0],
   217  				StartOffset:     800 * quantity.OffsetMiB,
   218  				Index:           0,
   219  			},
   220  			{
   221  				VolumeStructure: &vol.Structure[2],
   222  				StartOffset:     1200 * quantity.OffsetMiB,
   223  				Index:           2,
   224  			},
   225  		},
   226  	})
   227  }
   228  
   229  func (p *layoutTestSuite) TestLayoutVolumeMixedOrdering(c *C) {
   230  	gadgetYaml := `
   231  volumes:
   232    first:
   233      schema: gpt
   234      bootloader: grub
   235      structure:
   236          - type: 00000000-0000-0000-0000-dd00deadbeef
   237            size: 400M
   238            offset: 800M
   239          - type: 00000000-0000-0000-0000-cc00deadbeef
   240            role: system-data
   241            size: 500M
   242            offset: 200M
   243          - type: 00000000-0000-0000-0000-bb00deadbeef
   244            size: 100M
   245          - type: 00000000-0000-0000-0000-aa00deadbeef
   246            size: 100M
   247            offset: 1M
   248  `
   249  	vol := mustParseVolume(c, gadgetYaml, "first")
   250  	c.Assert(vol.Structure, HasLen, 4)
   251  
   252  	v, err := gadget.LayoutVolume(p.dir, "", vol, defaultConstraints)
   253  	c.Assert(err, IsNil)
   254  
   255  	c.Assert(v, DeepEquals, &gadget.LaidOutVolume{
   256  		Volume:  vol,
   257  		Size:    1200 * quantity.SizeMiB,
   258  		RootDir: p.dir,
   259  		LaidOutStructure: []gadget.LaidOutStructure{
   260  			{
   261  				VolumeStructure: &vol.Structure[3],
   262  				StartOffset:     1 * quantity.OffsetMiB,
   263  				Index:           3,
   264  			},
   265  			{
   266  				VolumeStructure: &vol.Structure[1],
   267  				StartOffset:     200 * quantity.OffsetMiB,
   268  				Index:           1,
   269  			},
   270  			{
   271  				VolumeStructure: &vol.Structure[2],
   272  				StartOffset:     700 * quantity.OffsetMiB,
   273  				Index:           2,
   274  			},
   275  			{
   276  				VolumeStructure: &vol.Structure[0],
   277  				StartOffset:     800 * quantity.OffsetMiB,
   278  				Index:           0,
   279  			},
   280  		},
   281  	})
   282  }
   283  
   284  func (p *layoutTestSuite) TestLayoutVolumeErrorsContentNoSuchFile(c *C) {
   285  	gadgetYaml := `
   286  volumes:
   287    first:
   288      schema: gpt
   289      bootloader: grub
   290      structure:
   291          - type: 00000000-0000-0000-0000-dd00deadbeef
   292            size: 400M
   293            offset: 800M
   294            content:
   295                - image: foo.img
   296  `
   297  	vol := mustParseVolume(c, gadgetYaml, "first")
   298  	v, err := gadget.LayoutVolume(p.dir, "", vol, defaultConstraints)
   299  	c.Assert(v, IsNil)
   300  	c.Assert(err, ErrorMatches, `cannot lay out structure #0: content "foo.img":.*no such file or directory`)
   301  }
   302  
   303  func makeSizedFile(c *C, path string, size quantity.Size, content []byte) {
   304  	err := os.MkdirAll(filepath.Dir(path), 0755)
   305  	c.Assert(err, IsNil)
   306  
   307  	f, err := os.Create(path)
   308  	c.Assert(err, IsNil)
   309  	defer f.Close()
   310  	if size != 0 {
   311  		err = f.Truncate(int64(size))
   312  		c.Assert(err, IsNil)
   313  	}
   314  	if content != nil {
   315  		_, err := io.Copy(f, bytes.NewReader(content))
   316  		c.Assert(err, IsNil)
   317  	}
   318  }
   319  
   320  func (p *layoutTestSuite) TestLayoutVolumeErrorsContentTooLargeSingle(c *C) {
   321  	gadgetYaml := `
   322  volumes:
   323    first:
   324      schema: gpt
   325      bootloader: grub
   326      structure:
   327          - type: 00000000-0000-0000-0000-dd00deadbeef
   328            size: 1M
   329            content:
   330                - image: foo.img
   331  `
   332  	makeSizedFile(c, filepath.Join(p.dir, "foo.img"), quantity.SizeMiB+1, nil)
   333  
   334  	vol := mustParseVolume(c, gadgetYaml, "first")
   335  
   336  	v, err := gadget.LayoutVolume(p.dir, "", vol, defaultConstraints)
   337  	c.Assert(v, IsNil)
   338  	c.Assert(err, ErrorMatches, `cannot lay out structure #0: content "foo.img" does not fit in the structure`)
   339  }
   340  
   341  func (p *layoutTestSuite) TestLayoutVolumeErrorsContentTooLargeMany(c *C) {
   342  	gadgetYaml := `
   343  volumes:
   344    first:
   345      schema: gpt
   346      bootloader: grub
   347      structure:
   348          - type: 00000000-0000-0000-0000-dd00deadbeef
   349            size: 2M
   350            content:
   351                - image: foo.img
   352                - image: bar.img
   353  `
   354  	makeSizedFile(c, filepath.Join(p.dir, "foo.img"), quantity.SizeMiB+1, nil)
   355  	makeSizedFile(c, filepath.Join(p.dir, "bar.img"), quantity.SizeMiB+1, nil)
   356  
   357  	vol := mustParseVolume(c, gadgetYaml, "first")
   358  
   359  	constraints := gadget.LayoutConstraints{
   360  		NonMBRStartOffset: 1 * quantity.OffsetMiB,
   361  	}
   362  	v, err := gadget.LayoutVolume(p.dir, "", vol, constraints)
   363  	c.Assert(v, IsNil)
   364  	c.Assert(err, ErrorMatches, `cannot lay out structure #0: content "bar.img" does not fit in the structure`)
   365  }
   366  
   367  func (p *layoutTestSuite) TestLayoutVolumeErrorsContentTooLargeWithOffset(c *C) {
   368  	gadgetYaml := `
   369  volumes:
   370    first:
   371      schema: gpt
   372      bootloader: grub
   373      structure:
   374          - type: 00000000-0000-0000-0000-dd00deadbeef
   375            size: 1M
   376            content:
   377                - image: foo.img
   378                  # 512kB
   379                  offset: 524288
   380  `
   381  	makeSizedFile(c, filepath.Join(p.dir, "foo.img"), quantity.SizeMiB, nil)
   382  
   383  	vol := mustParseVolume(c, gadgetYaml, "first")
   384  
   385  	v, err := gadget.LayoutVolume(p.dir, "", vol, defaultConstraints)
   386  	c.Assert(v, IsNil)
   387  	c.Assert(err, ErrorMatches, `cannot lay out structure #0: content "foo.img" does not fit in the structure`)
   388  }
   389  
   390  func (p *layoutTestSuite) TestLayoutVolumeErrorsContentLargerThanDeclared(c *C) {
   391  	gadgetYaml := `
   392  volumes:
   393    first:
   394      schema: gpt
   395      bootloader: grub
   396      structure:
   397          - type: 00000000-0000-0000-0000-dd00deadbeef
   398            size: 2M
   399            content:
   400                - image: foo.img
   401                  size: 1M
   402  `
   403  	makeSizedFile(c, filepath.Join(p.dir, "foo.img"), quantity.SizeMiB+1, nil)
   404  
   405  	vol := mustParseVolume(c, gadgetYaml, "first")
   406  
   407  	v, err := gadget.LayoutVolume(p.dir, "", vol, defaultConstraints)
   408  	c.Assert(v, IsNil)
   409  	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))
   410  }
   411  
   412  func (p *layoutTestSuite) TestLayoutVolumeErrorsContentOverlap(c *C) {
   413  	gadgetYaml := `
   414  volumes:
   415    first:
   416      schema: gpt
   417      bootloader: grub
   418      structure:
   419          - type: 00000000-0000-0000-0000-dd00deadbeef
   420            size: 2M
   421            content:
   422                - image: foo.img
   423                  size: 1M
   424                  # 512kB
   425                  offset: 524288
   426                - image: bar.img
   427                  size: 1M
   428                  offset: 0
   429  `
   430  	makeSizedFile(c, filepath.Join(p.dir, "foo.img"), quantity.SizeMiB, nil)
   431  	makeSizedFile(c, filepath.Join(p.dir, "bar.img"), quantity.SizeMiB, nil)
   432  
   433  	vol := mustParseVolume(c, gadgetYaml, "first")
   434  
   435  	v, err := gadget.LayoutVolume(p.dir, "", vol, defaultConstraints)
   436  	c.Assert(v, IsNil)
   437  	c.Assert(err, ErrorMatches, `cannot lay out structure #0: content "foo.img" overlaps with preceding image "bar.img"`)
   438  }
   439  
   440  func (p *layoutTestSuite) TestLayoutVolumeContentExplicitOrder(c *C) {
   441  	gadgetYaml := `
   442  volumes:
   443    first:
   444      schema: gpt
   445      bootloader: grub
   446      structure:
   447          - type: 00000000-0000-0000-0000-0000deadbeef
   448            size: 2M
   449            content:
   450                - image: foo.img
   451                  size: 1M
   452                  offset: 1M
   453                - image: bar.img
   454                  size: 1M
   455                  offset: 0
   456  `
   457  	makeSizedFile(c, filepath.Join(p.dir, "foo.img"), quantity.SizeMiB, nil)
   458  	makeSizedFile(c, filepath.Join(p.dir, "bar.img"), quantity.SizeMiB, nil)
   459  
   460  	vol := mustParseVolume(c, gadgetYaml, "first")
   461  	c.Assert(vol.Structure, HasLen, 1)
   462  	c.Assert(vol.Structure[0].Content, HasLen, 2)
   463  
   464  	v, err := gadget.LayoutVolume(p.dir, "", vol, defaultConstraints)
   465  	c.Assert(err, IsNil)
   466  	c.Assert(v, DeepEquals, &gadget.LaidOutVolume{
   467  		Volume:  vol,
   468  		Size:    3 * quantity.SizeMiB,
   469  		RootDir: p.dir,
   470  		LaidOutStructure: []gadget.LaidOutStructure{
   471  			{
   472  				VolumeStructure: &vol.Structure[0],
   473  				StartOffset:     1 * quantity.OffsetMiB,
   474  				LaidOutContent: []gadget.LaidOutContent{
   475  					{
   476  						VolumeContent: &vol.Structure[0].Content[1],
   477  						StartOffset:   1 * quantity.OffsetMiB,
   478  						Size:          quantity.SizeMiB,
   479  						Index:         1,
   480  					},
   481  					{
   482  						VolumeContent: &vol.Structure[0].Content[0],
   483  						StartOffset:   2 * quantity.OffsetMiB,
   484  						Size:          quantity.SizeMiB,
   485  						Index:         0,
   486  					},
   487  				},
   488  			},
   489  		},
   490  	})
   491  }
   492  
   493  func (p *layoutTestSuite) TestLayoutVolumeContentImplicitOrder(c *C) {
   494  	gadgetYaml := `
   495  volumes:
   496    first:
   497      schema: gpt
   498      bootloader: grub
   499      structure:
   500          - type: 00000000-0000-0000-0000-0000deadbeef
   501            size: 2M
   502            content:
   503                - image: foo.img
   504                  size: 1M
   505                - image: bar.img
   506                  size: 1M
   507  `
   508  	makeSizedFile(c, filepath.Join(p.dir, "foo.img"), quantity.SizeMiB, nil)
   509  	makeSizedFile(c, filepath.Join(p.dir, "bar.img"), quantity.SizeMiB, nil)
   510  
   511  	vol := mustParseVolume(c, gadgetYaml, "first")
   512  	c.Assert(vol.Structure, HasLen, 1)
   513  	c.Assert(vol.Structure[0].Content, HasLen, 2)
   514  
   515  	v, err := gadget.LayoutVolume(p.dir, "", vol, defaultConstraints)
   516  	c.Assert(err, IsNil)
   517  	c.Assert(v, DeepEquals, &gadget.LaidOutVolume{
   518  		Volume:  vol,
   519  		Size:    3 * quantity.SizeMiB,
   520  		RootDir: p.dir,
   521  		LaidOutStructure: []gadget.LaidOutStructure{
   522  			{
   523  				VolumeStructure: &vol.Structure[0],
   524  				StartOffset:     1 * quantity.OffsetMiB,
   525  				LaidOutContent: []gadget.LaidOutContent{
   526  					{
   527  						VolumeContent: &vol.Structure[0].Content[0],
   528  						StartOffset:   1 * quantity.OffsetMiB,
   529  						Size:          quantity.SizeMiB,
   530  						Index:         0,
   531  					},
   532  					{
   533  						VolumeContent: &vol.Structure[0].Content[1],
   534  						StartOffset:   2 * quantity.OffsetMiB,
   535  						Size:          quantity.SizeMiB,
   536  						Index:         1,
   537  					},
   538  				},
   539  			},
   540  		},
   541  	})
   542  }
   543  
   544  func (p *layoutTestSuite) TestLayoutVolumeContentImplicitSize(c *C) {
   545  	gadgetYaml := `
   546  volumes:
   547    first:
   548      schema: gpt
   549      bootloader: grub
   550      structure:
   551          - type: 00000000-0000-0000-0000-0000deadbeef
   552            size: 2M
   553            content:
   554                - image: foo.img
   555  `
   556  	size1_5MiB := quantity.SizeMiB + quantity.SizeMiB/2
   557  	makeSizedFile(c, filepath.Join(p.dir, "foo.img"), size1_5MiB, nil)
   558  
   559  	vol := mustParseVolume(c, gadgetYaml, "first")
   560  	c.Assert(vol.Structure, HasLen, 1)
   561  	c.Assert(vol.Structure[0].Content, HasLen, 1)
   562  
   563  	v, err := gadget.LayoutVolume(p.dir, "", vol, defaultConstraints)
   564  	c.Assert(err, IsNil)
   565  	c.Assert(v, DeepEquals, &gadget.LaidOutVolume{
   566  		Volume:  vol,
   567  		Size:    3 * quantity.SizeMiB,
   568  		RootDir: p.dir,
   569  		LaidOutStructure: []gadget.LaidOutStructure{
   570  			{
   571  				VolumeStructure: &vol.Structure[0],
   572  				StartOffset:     1 * quantity.OffsetMiB,
   573  				LaidOutContent: []gadget.LaidOutContent{
   574  					{
   575  						VolumeContent: &vol.Structure[0].Content[0],
   576  						StartOffset:   1 * quantity.OffsetMiB,
   577  						Size:          size1_5MiB,
   578  					},
   579  				},
   580  			},
   581  		},
   582  	})
   583  }
   584  
   585  func (p *layoutTestSuite) TestLayoutVolumeContentNonBare(c *C) {
   586  	gadgetYaml := `
   587  volumes:
   588    first:
   589      schema: gpt
   590      bootloader: grub
   591      structure:
   592          - type: 00000000-0000-0000-0000-0000deadbeef
   593            filesystem: ext4
   594            size: 2M
   595            content:
   596                - source: foo.txt
   597                  target: /boot
   598  `
   599  	makeSizedFile(c, filepath.Join(p.dir, "foo.txt"), 0, []byte("foobar\n"))
   600  
   601  	vol := mustParseVolume(c, gadgetYaml, "first")
   602  	c.Assert(vol.Structure, HasLen, 1)
   603  	c.Assert(vol.Structure[0].Content, HasLen, 1)
   604  
   605  	v, err := gadget.LayoutVolume(p.dir, "", vol, defaultConstraints)
   606  	c.Assert(err, IsNil)
   607  	c.Assert(v, DeepEquals, &gadget.LaidOutVolume{
   608  		Volume:  vol,
   609  		Size:    3 * quantity.SizeMiB,
   610  		RootDir: p.dir,
   611  		LaidOutStructure: []gadget.LaidOutStructure{
   612  			{
   613  				StartOffset:     1 * quantity.OffsetMiB,
   614  				VolumeStructure: &vol.Structure[0],
   615  				ResolvedContent: []gadget.ResolvedContent{
   616  					{
   617  						VolumeContent: &gadget.VolumeContent{
   618  							UnresolvedSource: "foo.txt",
   619  							Target:           "/boot",
   620  						},
   621  						ResolvedSource: filepath.Join(p.dir, "foo.txt"),
   622  					},
   623  				},
   624  			},
   625  		},
   626  	})
   627  }
   628  
   629  func (p *layoutTestSuite) TestLayoutVolumeConstraintsChange(c *C) {
   630  	gadgetYaml := `
   631  volumes:
   632    first:
   633      schema: gpt
   634      bootloader: grub
   635      structure:
   636          - role: mbr
   637            type: bare
   638            size: 446
   639            offset: 0
   640          - type: 00000000-0000-0000-0000-0000deadbeef
   641            filesystem: ext4
   642            size: 2M
   643            content:
   644                - source: foo.txt
   645                  target: /boot
   646  `
   647  	resolvedContent := []gadget.ResolvedContent{
   648  		{
   649  			VolumeContent: &gadget.VolumeContent{
   650  				UnresolvedSource: "foo.txt",
   651  				Target:           "/boot",
   652  			},
   653  			ResolvedSource: filepath.Join(p.dir, "foo.txt"),
   654  		},
   655  	}
   656  
   657  	makeSizedFile(c, filepath.Join(p.dir, "foo.txt"), 0, []byte("foobar\n"))
   658  
   659  	vol := mustParseVolume(c, gadgetYaml, "first")
   660  	c.Assert(vol.Structure, HasLen, 2)
   661  	c.Assert(vol.Structure[1].Content, HasLen, 1)
   662  
   663  	v, err := gadget.LayoutVolume(p.dir, "", vol, defaultConstraints)
   664  	c.Assert(err, IsNil)
   665  	c.Assert(v, DeepEquals, &gadget.LaidOutVolume{
   666  		Volume:  vol,
   667  		Size:    3 * quantity.SizeMiB,
   668  		RootDir: p.dir,
   669  		LaidOutStructure: []gadget.LaidOutStructure{
   670  			{
   671  				VolumeStructure: &vol.Structure[0],
   672  				StartOffset:     0,
   673  				Index:           0,
   674  			},
   675  			{
   676  				VolumeStructure: &vol.Structure[1],
   677  				StartOffset:     1 * quantity.OffsetMiB,
   678  				Index:           1,
   679  				ResolvedContent: resolvedContent,
   680  			},
   681  		},
   682  	})
   683  
   684  	// still valid
   685  	constraints := gadget.LayoutConstraints{
   686  		// 512kiB
   687  		NonMBRStartOffset: 512 * quantity.OffsetKiB,
   688  	}
   689  	v, err = gadget.LayoutVolume(p.dir, "", vol, constraints)
   690  	c.Assert(err, IsNil)
   691  	c.Assert(v, DeepEquals, &gadget.LaidOutVolume{
   692  		Volume:  vol,
   693  		Size:    2*quantity.SizeMiB + 512*quantity.SizeKiB,
   694  		RootDir: p.dir,
   695  		LaidOutStructure: []gadget.LaidOutStructure{
   696  			{
   697  				VolumeStructure: &vol.Structure[0],
   698  				StartOffset:     0,
   699  				Index:           0,
   700  			},
   701  			{
   702  				VolumeStructure: &vol.Structure[1],
   703  				StartOffset:     512 * quantity.OffsetKiB,
   704  				Index:           1,
   705  				ResolvedContent: resolvedContent,
   706  			},
   707  		},
   708  	})
   709  
   710  	// constraints would make a non MBR structure overlap with MBR, but
   711  	// structures start one after another unless offset is specified
   712  	// explicitly
   713  	constraintsBad := gadget.LayoutConstraints{
   714  		NonMBRStartOffset: 400,
   715  	}
   716  	v, err = gadget.LayoutVolume(p.dir, "", vol, constraintsBad)
   717  	c.Assert(err, IsNil)
   718  	c.Assert(v, DeepEquals, &gadget.LaidOutVolume{
   719  		Volume:  vol,
   720  		Size:    2*quantity.SizeMiB + 446,
   721  		RootDir: p.dir,
   722  		LaidOutStructure: []gadget.LaidOutStructure{
   723  			{
   724  				VolumeStructure: &vol.Structure[0],
   725  				Index:           0,
   726  			},
   727  			{
   728  				VolumeStructure: &vol.Structure[1],
   729  				StartOffset:     446,
   730  				Index:           1,
   731  				ResolvedContent: resolvedContent,
   732  			},
   733  		},
   734  	})
   735  }
   736  
   737  func (p *layoutTestSuite) TestLayoutVolumeMBRImplicitConstraints(c *C) {
   738  	gadgetYaml := `
   739  volumes:
   740    first:
   741      schema: gpt
   742      bootloader: grub
   743      structure:
   744          - name: mbr
   745            type: bare
   746            role: mbr
   747            size: 446
   748          - name: other
   749            type: 00000000-0000-0000-0000-0000deadbeef
   750            size: 1M
   751  `
   752  	vol := mustParseVolume(c, gadgetYaml, "first")
   753  	c.Assert(vol.Structure, HasLen, 2)
   754  
   755  	v, err := gadget.LayoutVolume(p.dir, "", vol, defaultConstraints)
   756  	c.Assert(err, IsNil)
   757  	c.Assert(v, DeepEquals, &gadget.LaidOutVolume{
   758  		Volume:  vol,
   759  		Size:    2 * quantity.SizeMiB,
   760  		RootDir: p.dir,
   761  		LaidOutStructure: []gadget.LaidOutStructure{
   762  			{
   763  				// MBR
   764  				VolumeStructure: &vol.Structure[0],
   765  				StartOffset:     0,
   766  				Index:           0,
   767  			}, {
   768  				VolumeStructure: &vol.Structure[1],
   769  				StartOffset:     1 * quantity.OffsetMiB,
   770  				Index:           1,
   771  			},
   772  		},
   773  	})
   774  }
   775  
   776  func (p *layoutTestSuite) TestLayoutVolumeOffsetWriteAll(c *C) {
   777  	var gadgetYaml = `
   778  volumes:
   779    pc:
   780      bootloader: grub
   781      structure:
   782        - name: mbr
   783          type: mbr
   784          size: 440
   785        - name: foo
   786          type: DA,21686148-6449-6E6F-744E-656564454649
   787          size: 1M
   788          offset: 1M
   789          offset-write: mbr+92
   790          content:
   791            - image: foo.img
   792              offset-write: bar+10
   793        - name: bar
   794          type: DA,21686148-6449-6E6F-744E-656564454649
   795          size: 1M
   796          offset-write: 600
   797          content:
   798            - image: bar.img
   799              offset-write: 450
   800  `
   801  	makeSizedFile(c, filepath.Join(p.dir, "foo.img"), 200*quantity.SizeKiB, []byte(""))
   802  	makeSizedFile(c, filepath.Join(p.dir, "bar.img"), 150*quantity.SizeKiB, []byte(""))
   803  
   804  	vol := mustParseVolume(c, gadgetYaml, "pc")
   805  	c.Assert(vol.Structure, HasLen, 3)
   806  
   807  	v, err := gadget.LayoutVolume(p.dir, "", vol, defaultConstraints)
   808  	c.Assert(err, IsNil)
   809  	c.Assert(v, DeepEquals, &gadget.LaidOutVolume{
   810  		Volume:  vol,
   811  		Size:    3 * quantity.SizeMiB,
   812  		RootDir: p.dir,
   813  		LaidOutStructure: []gadget.LaidOutStructure{
   814  			{
   815  				// mbr
   816  				VolumeStructure: &vol.Structure[0],
   817  				StartOffset:     0,
   818  				Index:           0,
   819  			}, {
   820  				// foo
   821  				VolumeStructure: &vol.Structure[1],
   822  				StartOffset:     1 * quantity.OffsetMiB,
   823  				Index:           1,
   824  				// break for gofmt < 1.11
   825  				AbsoluteOffsetWrite: asOffsetPtr(92),
   826  				LaidOutContent: []gadget.LaidOutContent{
   827  					{
   828  						VolumeContent: &vol.Structure[1].Content[0],
   829  						Size:          200 * quantity.SizeKiB,
   830  						StartOffset:   1 * quantity.OffsetMiB,
   831  						// offset-write: bar+10
   832  						AbsoluteOffsetWrite: asOffsetPtr(2*quantity.OffsetMiB + 10),
   833  					},
   834  				},
   835  			}, {
   836  				// bar
   837  				VolumeStructure: &vol.Structure[2],
   838  				StartOffset:     2 * quantity.OffsetMiB,
   839  				Index:           2,
   840  				// break for gofmt < 1.11
   841  				AbsoluteOffsetWrite: asOffsetPtr(600),
   842  				LaidOutContent: []gadget.LaidOutContent{
   843  					{
   844  						VolumeContent: &vol.Structure[2].Content[0],
   845  						Size:          150 * quantity.SizeKiB,
   846  						StartOffset:   2 * quantity.OffsetMiB,
   847  						// offset-write: bar+10
   848  						AbsoluteOffsetWrite: asOffsetPtr(450),
   849  					},
   850  				},
   851  			},
   852  		},
   853  	})
   854  }
   855  
   856  func (p *layoutTestSuite) TestLayoutVolumeOffsetWriteBadRelativeTo(c *C) {
   857  	// define volumes explicitly as those would not pass validation
   858  	volBadStructure := gadget.Volume{
   859  		Structure: []gadget.VolumeStructure{
   860  			{
   861  				Name: "foo",
   862  				Type: "DA,21686148-6449-6E6F-744E-656564454649",
   863  				Size: 1 * quantity.SizeMiB,
   864  				OffsetWrite: &gadget.RelativeOffset{
   865  					RelativeTo: "bar",
   866  					Offset:     10,
   867  				},
   868  			},
   869  		},
   870  	}
   871  	volBadContent := gadget.Volume{
   872  		Structure: []gadget.VolumeStructure{
   873  			{
   874  				Name: "foo",
   875  				Type: "DA,21686148-6449-6E6F-744E-656564454649",
   876  				Size: 1 * quantity.SizeMiB,
   877  				Content: []gadget.VolumeContent{
   878  					{
   879  						Image: "foo.img",
   880  						OffsetWrite: &gadget.RelativeOffset{
   881  							RelativeTo: "bar",
   882  							Offset:     10,
   883  						},
   884  					},
   885  				},
   886  			},
   887  		},
   888  	}
   889  
   890  	makeSizedFile(c, filepath.Join(p.dir, "foo.img"), 200*quantity.SizeKiB, []byte(""))
   891  
   892  	v, err := gadget.LayoutVolume(p.dir, "", &volBadStructure, defaultConstraints)
   893  	c.Check(v, IsNil)
   894  	c.Check(err, ErrorMatches, `cannot resolve offset-write of structure #0 \("foo"\): refers to an unknown structure "bar"`)
   895  
   896  	v, err = gadget.LayoutVolume(p.dir, "", &volBadContent, defaultConstraints)
   897  	c.Check(v, IsNil)
   898  	c.Check(err, ErrorMatches, `cannot resolve offset-write of structure #0 \("foo"\) content "foo.img": refers to an unknown structure "bar"`)
   899  }
   900  
   901  func (p *layoutTestSuite) TestLayoutVolumeOffsetWriteEnlargesVolume(c *C) {
   902  	var gadgetYamlStructure = `
   903  volumes:
   904    pc:
   905      bootloader: grub
   906      structure:
   907        - name: mbr
   908          type: mbr
   909          size: 440
   910        - name: foo
   911          type: DA,21686148-6449-6E6F-744E-656564454649
   912          size: 1M
   913          offset: 1M
   914          # 1GB
   915          offset-write: mbr+1073741824
   916  
   917  `
   918  	vol := mustParseVolume(c, gadgetYamlStructure, "pc")
   919  
   920  	v, err := gadget.LayoutVolume(p.dir, "", vol, defaultConstraints)
   921  	c.Assert(err, IsNil)
   922  	// offset-write is at 1GB
   923  	c.Check(v.Size, Equals, 1*quantity.SizeGiB+gadget.SizeLBA48Pointer)
   924  
   925  	var gadgetYamlContent = `
   926  volumes:
   927    pc:
   928      bootloader: grub
   929      structure:
   930        - name: mbr
   931          type: mbr
   932          size: 440
   933        - name: foo
   934          type: DA,21686148-6449-6E6F-744E-656564454649
   935          size: 1M
   936          offset: 1M
   937          content:
   938            - image: foo.img
   939              # 2GB
   940              offset-write: mbr+2147483648
   941            - image: bar.img
   942              # 1GB
   943              offset-write: mbr+1073741824
   944            - image: baz.img
   945              # 3GB
   946              offset-write: mbr+3221225472
   947  
   948  `
   949  	makeSizedFile(c, filepath.Join(p.dir, "foo.img"), 200*quantity.SizeKiB, []byte(""))
   950  	makeSizedFile(c, filepath.Join(p.dir, "bar.img"), 150*quantity.SizeKiB, []byte(""))
   951  	makeSizedFile(c, filepath.Join(p.dir, "baz.img"), 100*quantity.SizeKiB, []byte(""))
   952  
   953  	vol = mustParseVolume(c, gadgetYamlContent, "pc")
   954  
   955  	v, err = gadget.LayoutVolume(p.dir, "", vol, defaultConstraints)
   956  	c.Assert(err, IsNil)
   957  	// foo.img offset-write is at 3GB
   958  	c.Check(v.Size, Equals, 3*quantity.SizeGiB+gadget.SizeLBA48Pointer)
   959  }
   960  
   961  func (p *layoutTestSuite) TestLayoutVolumePartialNoSuchFile(c *C) {
   962  	gadgetYaml := `
   963  volumes:
   964    first:
   965      schema: gpt
   966      bootloader: grub
   967      structure:
   968          - type: 00000000-0000-0000-0000-dd00deadbeef
   969            size: 400M
   970            offset: 800M
   971            content:
   972                - image: foo.img
   973  `
   974  	vol := mustParseVolume(c, gadgetYaml, "first")
   975  	c.Assert(vol.Structure, HasLen, 1)
   976  
   977  	v, err := gadget.LayoutVolumePartially(vol, defaultConstraints)
   978  	c.Assert(v, DeepEquals, &gadget.PartiallyLaidOutVolume{
   979  		Volume: vol,
   980  		LaidOutStructure: []gadget.LaidOutStructure{
   981  			{
   982  				VolumeStructure: &vol.Structure[0],
   983  				StartOffset:     800 * quantity.OffsetMiB,
   984  				Index:           0,
   985  			},
   986  		},
   987  	})
   988  	c.Assert(err, IsNil)
   989  }
   990  
   991  func (p *layoutTestSuite) TestLaidOutStructureShift(c *C) {
   992  	var gadgetYamlContent = `
   993  volumes:
   994    pc:
   995      bootloader: grub
   996      structure:
   997        - name: foo
   998          type: DA,21686148-6449-6E6F-744E-656564454649
   999          size: 1M
  1000          offset: 1M
  1001          content:
  1002            - image: foo.img
  1003            - image: bar.img
  1004              # 300KB
  1005              offset: 307200
  1006  
  1007  `
  1008  	makeSizedFile(c, filepath.Join(p.dir, "foo.img"), 200*quantity.SizeKiB, []byte(""))
  1009  	makeSizedFile(c, filepath.Join(p.dir, "bar.img"), 150*quantity.SizeKiB, []byte(""))
  1010  
  1011  	vol := mustParseVolume(c, gadgetYamlContent, "pc")
  1012  
  1013  	v, err := gadget.LayoutVolume(p.dir, "", vol, defaultConstraints)
  1014  	c.Assert(err, IsNil)
  1015  	c.Assert(v.LaidOutStructure, HasLen, 1)
  1016  	c.Assert(v.LaidOutStructure[0].LaidOutContent, HasLen, 2)
  1017  
  1018  	ps := v.LaidOutStructure[0]
  1019  
  1020  	c.Assert(ps, DeepEquals, gadget.LaidOutStructure{
  1021  		// foo
  1022  		VolumeStructure: &vol.Structure[0],
  1023  		StartOffset:     1 * quantity.OffsetMiB,
  1024  		Index:           0,
  1025  		LaidOutContent: []gadget.LaidOutContent{
  1026  			{
  1027  				VolumeContent: &vol.Structure[0].Content[0],
  1028  				Size:          200 * quantity.SizeKiB,
  1029  				StartOffset:   1 * quantity.OffsetMiB,
  1030  				Index:         0,
  1031  			}, {
  1032  				VolumeContent: &vol.Structure[0].Content[1],
  1033  				Size:          150 * quantity.SizeKiB,
  1034  				StartOffset:   1*quantity.OffsetMiB + 300*quantity.OffsetKiB,
  1035  				Index:         1,
  1036  			},
  1037  		},
  1038  	})
  1039  
  1040  	shiftedTo0 := gadget.ShiftStructureTo(ps, 0)
  1041  	c.Assert(shiftedTo0, DeepEquals, gadget.LaidOutStructure{
  1042  		// foo
  1043  		VolumeStructure: &vol.Structure[0],
  1044  		StartOffset:     0,
  1045  		Index:           0,
  1046  		LaidOutContent: []gadget.LaidOutContent{
  1047  			{
  1048  				VolumeContent: &vol.Structure[0].Content[0],
  1049  				Size:          200 * quantity.SizeKiB,
  1050  				StartOffset:   0,
  1051  				Index:         0,
  1052  			}, {
  1053  				VolumeContent: &vol.Structure[0].Content[1],
  1054  				Size:          150 * quantity.SizeKiB,
  1055  				StartOffset:   300 * quantity.OffsetKiB,
  1056  				Index:         1,
  1057  			},
  1058  		},
  1059  	})
  1060  
  1061  	shiftedTo2M := gadget.ShiftStructureTo(ps, 2*quantity.OffsetMiB)
  1062  	c.Assert(shiftedTo2M, DeepEquals, gadget.LaidOutStructure{
  1063  		// foo
  1064  		VolumeStructure: &vol.Structure[0],
  1065  		StartOffset:     2 * quantity.OffsetMiB,
  1066  		Index:           0,
  1067  		LaidOutContent: []gadget.LaidOutContent{
  1068  			{
  1069  				VolumeContent: &vol.Structure[0].Content[0],
  1070  				Size:          200 * quantity.SizeKiB,
  1071  				StartOffset:   2 * quantity.OffsetMiB,
  1072  				Index:         0,
  1073  			}, {
  1074  				VolumeContent: &vol.Structure[0].Content[1],
  1075  				Size:          150 * quantity.SizeKiB,
  1076  				StartOffset:   2*quantity.OffsetMiB + 300*quantity.OffsetKiB,
  1077  				Index:         1,
  1078  			},
  1079  		},
  1080  	})
  1081  }
  1082  
  1083  func mockKernel(c *C, kernelYaml string, filesWithContent map[string]string) string {
  1084  	// sanity
  1085  	_, err := kernel.InfoFromKernelYaml([]byte(kernelYaml))
  1086  	c.Assert(err, IsNil)
  1087  
  1088  	kernelRootDir := c.MkDir()
  1089  	err = os.MkdirAll(filepath.Join(kernelRootDir, "meta"), 0755)
  1090  	c.Assert(err, IsNil)
  1091  	err = ioutil.WriteFile(filepath.Join(kernelRootDir, "meta/kernel.yaml"), []byte(kernelYaml), 0644)
  1092  	c.Assert(err, IsNil)
  1093  
  1094  	for fname, content := range filesWithContent {
  1095  		p := filepath.Join(kernelRootDir, fname)
  1096  		err = os.MkdirAll(filepath.Dir(p), 0755)
  1097  		c.Assert(err, IsNil)
  1098  		err = ioutil.WriteFile(p, []byte(content), 0644)
  1099  		c.Assert(err, IsNil)
  1100  	}
  1101  
  1102  	// ensure we have valid kernel.yaml in our tests
  1103  	err = kernel.Validate(kernelRootDir)
  1104  	c.Assert(err, IsNil)
  1105  
  1106  	return kernelRootDir
  1107  }
  1108  
  1109  var gadgetYamlWithKernelRef = `
  1110   volumes:
  1111    pi:
  1112      bootloader: u-boot
  1113      structure:
  1114        - type: 00000000-0000-0000-0000-dd00deadbeef
  1115          filesystem: vfat
  1116          filesystem-label: system-boot
  1117          size: 128M
  1118          content:
  1119            - source: $kernel:dtbs/boot-assets/
  1120              target: /
  1121            - source: $kernel:dtbs/some-file
  1122              target: /
  1123            - source: file-from-gadget
  1124              target: /
  1125            - source: dir-from-gadget/
  1126              target: /
  1127  `
  1128  
  1129  func (p *layoutTestSuite) TestResolveContentPathsNotInWantedAssets(c *C) {
  1130  	vol := mustParseVolume(c, gadgetYamlWithKernelRef, "pi")
  1131  	c.Assert(vol.Structure, HasLen, 1)
  1132  
  1133  	kernelSnapDir := c.MkDir()
  1134  	_, err := gadget.LayoutVolume(p.dir, kernelSnapDir, vol, defaultConstraints)
  1135  	c.Assert(err, ErrorMatches, `cannot resolve content for structure #0 at index 0: cannot find "dtbs" in kernel info from "/.*"`)
  1136  }
  1137  
  1138  func (p *layoutTestSuite) TestResolveContentPathsErrorInKernelRef(c *C) {
  1139  	// create invalid kernel ref
  1140  	s := strings.Replace(gadgetYamlWithKernelRef, "$kernel:dtbs", "$kernel:-invalid-kernel-ref", -1)
  1141  	// Note that mustParseVolume does not call ValidateContent() which
  1142  	// would be needed to validate "$kernel:" refs.
  1143  	vol := mustParseVolume(c, s, "pi")
  1144  	c.Assert(vol.Structure, HasLen, 1)
  1145  
  1146  	kernelSnapDir := c.MkDir()
  1147  	_, err := gadget.LayoutVolume(p.dir, kernelSnapDir, vol, defaultConstraints)
  1148  	c.Assert(err, ErrorMatches, `cannot resolve content for structure #0 at index 0: cannot parse kernel ref: invalid asset name in kernel ref "\$kernel:-invalid-kernel-ref/boot-assets/"`)
  1149  }
  1150  
  1151  func (p *layoutTestSuite) TestResolveContentPathsNotInWantedeContent(c *C) {
  1152  	kernelYaml := `
  1153  assets:
  1154    dtbs:
  1155      update: true
  1156      content:
  1157        - dtbs/
  1158  `
  1159  
  1160  	vol := mustParseVolume(c, gadgetYamlWithKernelRef, "pi")
  1161  	c.Assert(vol.Structure, HasLen, 1)
  1162  
  1163  	kernelSnapDir := mockKernel(c, kernelYaml, map[string]string{
  1164  		"dtbs/foo.dtb": "foo.dtb content",
  1165  	})
  1166  	_, err := gadget.LayoutVolume(p.dir, kernelSnapDir, vol, defaultConstraints)
  1167  	c.Assert(err, ErrorMatches, `cannot resolve content for structure #0 at index 0: cannot find wanted kernel content "boot-assets/" in "/.*"`)
  1168  }
  1169  
  1170  func (p *layoutTestSuite) TestResolveContentPaths(c *C) {
  1171  	kernelYaml := `
  1172  assets:
  1173    dtbs:
  1174      update: true
  1175      content:
  1176        - boot-assets/
  1177        - some-file
  1178  `
  1179  	vol := mustParseVolume(c, gadgetYamlWithKernelRef, "pi")
  1180  	c.Assert(vol.Structure, HasLen, 1)
  1181  
  1182  	kernelSnapFiles := map[string]string{
  1183  		"boot-assets/foo": "foo-content",
  1184  		"some-file":       "some-file content",
  1185  	}
  1186  	kernelSnapDir := mockKernel(c, kernelYaml, kernelSnapFiles)
  1187  	lv, err := gadget.LayoutVolume(p.dir, kernelSnapDir, vol, defaultConstraints)
  1188  	c.Assert(err, IsNil)
  1189  	// Volume.Content is unchanged
  1190  	c.Assert(lv.Structure, HasLen, 1)
  1191  	c.Check(lv.Structure[0].Content, DeepEquals, []gadget.VolumeContent{
  1192  		{
  1193  			UnresolvedSource: "$kernel:dtbs/boot-assets/",
  1194  			Target:           "/",
  1195  		},
  1196  		{
  1197  			UnresolvedSource: "$kernel:dtbs/some-file",
  1198  			Target:           "/",
  1199  		},
  1200  		{
  1201  			UnresolvedSource: "file-from-gadget",
  1202  			Target:           "/",
  1203  		},
  1204  		{
  1205  			UnresolvedSource: "dir-from-gadget/",
  1206  			Target:           "/",
  1207  		},
  1208  	})
  1209  	// and the LaidOutSturctures ResolvedContent has the correct paths
  1210  	c.Assert(lv.LaidOutStructure, HasLen, 1)
  1211  	c.Check(lv.LaidOutStructure[0].ResolvedContent, DeepEquals, []gadget.ResolvedContent{
  1212  		{
  1213  			VolumeContent:  &lv.Structure[0].Content[0],
  1214  			ResolvedSource: filepath.Join(kernelSnapDir, "boot-assets/") + "/",
  1215  			KernelUpdate:   true,
  1216  		},
  1217  		{
  1218  			VolumeContent:  &lv.Structure[0].Content[1],
  1219  			ResolvedSource: filepath.Join(kernelSnapDir, "some-file"),
  1220  			KernelUpdate:   true,
  1221  		},
  1222  		{
  1223  			VolumeContent:  &lv.Structure[0].Content[2],
  1224  			ResolvedSource: filepath.Join(p.dir, "file-from-gadget"),
  1225  		},
  1226  		{
  1227  			VolumeContent:  &lv.Structure[0].Content[3],
  1228  			ResolvedSource: filepath.Join(p.dir, "dir-from-gadget") + "/",
  1229  		},
  1230  	})
  1231  }
  1232  
  1233  func (p *layoutTestSuite) TestResolveContentPathsLp1907056(c *C) {
  1234  	var gadgetYamlWithKernelRef = `
  1235   volumes:
  1236    pi:
  1237      schema: mbr
  1238      bootloader: u-boot
  1239      structure:
  1240        - name: ubuntu-seed
  1241          role: system-seed
  1242          filesystem: vfat
  1243          type: 0C
  1244          size: 1200M
  1245          content:
  1246            - source: $kernel:pidtbs/dtbs/broadcom/
  1247              target: /
  1248            - source: $kernel:pidtbs/dtbs/overlays/
  1249              target: /overlays
  1250            - source: boot-assets/
  1251              target: /
  1252  `
  1253  
  1254  	kernelYaml := `
  1255  assets:
  1256    pidtbs:
  1257      update: true
  1258      content:
  1259        - dtbs/
  1260  `
  1261  	vol := mustParseVolume(c, gadgetYamlWithKernelRef, "pi")
  1262  	c.Assert(vol.Structure, HasLen, 1)
  1263  
  1264  	kernelSnapDir := mockKernel(c, kernelYaml, map[string]string{
  1265  		"dtbs/foo.dtb": "foo.dtb content",
  1266  	})
  1267  	lv, err := gadget.LayoutVolume(p.dir, kernelSnapDir, vol, defaultConstraints)
  1268  	c.Assert(err, IsNil)
  1269  	// Volume.Content is unchanged
  1270  	c.Assert(lv.Structure, HasLen, 1)
  1271  	c.Check(lv.Structure[0].Content, DeepEquals, []gadget.VolumeContent{
  1272  		{
  1273  			UnresolvedSource: "$kernel:pidtbs/dtbs/broadcom/",
  1274  			Target:           "/",
  1275  		},
  1276  		{
  1277  			UnresolvedSource: "$kernel:pidtbs/dtbs/overlays/",
  1278  			Target:           "/overlays",
  1279  		},
  1280  		{
  1281  			UnresolvedSource: "boot-assets/",
  1282  			Target:           "/",
  1283  		},
  1284  	})
  1285  	// and the LaidOutSturctures ResolvedContent has the correct paths
  1286  	c.Assert(lv.LaidOutStructure, HasLen, 1)
  1287  	c.Check(lv.LaidOutStructure[0].ResolvedContent, DeepEquals, []gadget.ResolvedContent{
  1288  		{
  1289  			VolumeContent:  &lv.Structure[0].Content[0],
  1290  			ResolvedSource: filepath.Join(kernelSnapDir, "dtbs/broadcom/") + "/",
  1291  			KernelUpdate:   true,
  1292  		},
  1293  		{
  1294  			VolumeContent:  &lv.Structure[0].Content[1],
  1295  			ResolvedSource: filepath.Join(kernelSnapDir, "dtbs/overlays") + "/",
  1296  			KernelUpdate:   true,
  1297  		},
  1298  		{
  1299  			VolumeContent:  &lv.Structure[0].Content[2],
  1300  			ResolvedSource: filepath.Join(p.dir, "boot-assets") + "/",
  1301  		},
  1302  	})
  1303  }
  1304  
  1305  func (p *layoutTestSuite) TestResolveContentSamePrefixErrors(c *C) {
  1306  	var gadgetYamlWithKernelRef = `
  1307   volumes:
  1308    pi:
  1309      schema: mbr
  1310      bootloader: u-boot
  1311      structure:
  1312        - name: ubuntu-seed
  1313          role: system-seed
  1314          filesystem: vfat
  1315          type: 0C
  1316          size: 1200M
  1317          content:
  1318            - source: $kernel:dtbs/a
  1319              target: /
  1320            - source: $kernel:dtbs/ab
  1321              target: /
  1322  `
  1323  	kernelYaml := `
  1324  assets:
  1325    dtbs:
  1326      update: true
  1327      content:
  1328        - a
  1329  `
  1330  	vol := mustParseVolume(c, gadgetYamlWithKernelRef, "pi")
  1331  	c.Assert(vol.Structure, HasLen, 1)
  1332  
  1333  	kernelSnapDir := mockKernel(c, kernelYaml, map[string]string{
  1334  		"a/foo.dtb": "foo.dtb content",
  1335  	})
  1336  	_, err := gadget.LayoutVolume(p.dir, kernelSnapDir, vol, defaultConstraints)
  1337  	c.Assert(err, ErrorMatches, `.*: cannot find wanted kernel content "ab" in.*`)
  1338  }