github.com/bugraaydogar/snapd@v0.0.0-20210315170335-8c70bb858939/gadget/gadget_test.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2019-2020 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/ioutil"
    26  	"os"
    27  	"path/filepath"
    28  	"strings"
    29  	"testing"
    30  
    31  	. "gopkg.in/check.v1"
    32  	"gopkg.in/yaml.v2"
    33  
    34  	"github.com/snapcore/snapd/asserts"
    35  	"github.com/snapcore/snapd/dirs"
    36  	"github.com/snapcore/snapd/gadget"
    37  	"github.com/snapcore/snapd/gadget/quantity"
    38  	"github.com/snapcore/snapd/snap/snapfile"
    39  	"github.com/snapcore/snapd/snap/snaptest"
    40  )
    41  
    42  type gadgetYamlTestSuite struct {
    43  	dir            string
    44  	gadgetYamlPath string
    45  }
    46  
    47  var _ = Suite(&gadgetYamlTestSuite{})
    48  
    49  var mockGadgetSnapYaml = `
    50  name: canonical-pc
    51  type: gadget
    52  `
    53  
    54  var mockGadgetYaml = []byte(`
    55  defaults:
    56    system:
    57      something: true
    58  
    59  connections:
    60    - plug: snapid1:plg1
    61      slot: snapid2:slot
    62    - plug: snapid3:process-control
    63    - plug: snapid4:pctl4
    64      slot: system:process-control
    65  
    66  volumes:
    67    volumename:
    68      schema: mbr
    69      bootloader: u-boot
    70      id:     0C
    71      structure:
    72        - filesystem-label: system-boot
    73          offset: 12345
    74          offset-write: 777
    75          size: 88888
    76          type: 0C
    77          filesystem: vfat
    78          content:
    79            - source: subdir/
    80              target: /
    81              unpack: false
    82            - source: foo
    83              target: /
    84  `)
    85  
    86  var mockMultiVolumeUC20GadgetYaml = []byte(`
    87  volumes:
    88    frobinator-image:
    89      bootloader: u-boot
    90      schema: gpt
    91      structure:
    92        - name: ubuntu-seed
    93          filesystem: ext4
    94          size: 500M
    95          type: 83,0FC63DAF-8483-4772-8E79-3D69D8477DE4
    96          role: system-seed
    97        - name: ubuntu-save
    98          size: 10485760
    99          filesystem: ext4
   100          type: 83,0FC63DAF-8483-4772-8E79-3D69D8477DE4
   101          role: system-save
   102        - name: ubuntu-boot
   103          filesystem: ext4
   104          size: 500M
   105          type: 83,0FC63DAF-8483-4772-8E79-3D69D8477DE4
   106          role: system-boot
   107        - name: ubuntu-data
   108          filesystem: ext4
   109          size: 1G
   110          type: 83,0FC63DAF-8483-4772-8E79-3D69D8477DE4
   111          role: system-data
   112    u-boot-frobinator:
   113      structure:
   114        - name: u-boot
   115          type: bare
   116          size: 623000
   117          offset: 0
   118          content:
   119            - image: u-boot.imz
   120  `)
   121  
   122  var mockMultiVolumeGadgetYaml = []byte(`
   123  volumes:
   124    frobinator-image:
   125      bootloader: u-boot
   126      schema: mbr
   127      structure:
   128        - name: system-boot
   129          type: 0C
   130          filesystem: vfat
   131          filesystem-label: system-boot
   132          size: 128M
   133          role: system-boot
   134          content:
   135            - source: splash.bmp
   136              target: .
   137        - name: writable
   138          type: 83
   139          filesystem: ext4
   140          filesystem-label: writable
   141          size: 380M
   142          role: system-data
   143    u-boot-frobinator:
   144      structure:
   145        - name: u-boot
   146          type: bare
   147          size: 623000
   148          offset: 0
   149          content:
   150            - image: u-boot.imz
   151  `)
   152  
   153  var mockClassicGadgetYaml = []byte(`
   154  defaults:
   155    system:
   156      something: true
   157    otheridididididididididididididi:
   158      foo:
   159        bar: baz
   160  `)
   161  
   162  var mockClassicGadgetCoreDefaultsYaml = []byte(`
   163  defaults:
   164    99T7MUlRhtI3U0QFgl5mXXESAiSwt776:
   165      ssh:
   166        disable: true
   167  `)
   168  
   169  var mockClassicGadgetMultilineDefaultsYaml = []byte(`
   170  defaults:
   171    system:
   172      something: true
   173    otheridididididididididididididi:
   174      foosnap:
   175        multiline: |
   176          foo
   177          bar
   178  `)
   179  
   180  var mockVolumeUpdateGadgetYaml = []byte(`
   181  volumes:
   182    bootloader:
   183      schema: mbr
   184      bootloader: u-boot
   185      id:     0C
   186      structure:
   187        - filesystem-label: system-boot
   188          offset: 12345
   189          offset-write: 777
   190          size: 88888
   191          type: 0C
   192          filesystem: vfat
   193          content:
   194            - source: subdir/
   195              target: /
   196              unpack: false
   197          update:
   198            edition: 5
   199            preserve:
   200             - env.txt
   201             - config.txt
   202  `)
   203  
   204  var gadgetYamlPC = []byte(`
   205  volumes:
   206    pc:
   207      bootloader: grub
   208      structure:
   209        - name: mbr
   210          type: mbr
   211          size: 440
   212          content:
   213            - image: pc-boot.img
   214        - name: BIOS Boot
   215          type: DA,21686148-6449-6E6F-744E-656564454649
   216          size: 1M
   217          offset: 1M
   218          offset-write: mbr+92
   219          content:
   220            - image: pc-core.img
   221        - name: EFI System
   222          type: EF,C12A7328-F81F-11D2-BA4B-00A0C93EC93B
   223          filesystem: vfat
   224          filesystem-label: system-boot
   225          size: 50M
   226          content:
   227            - source: grubx64.efi
   228              target: EFI/boot/grubx64.efi
   229            - source: shim.efi.signed
   230              target: EFI/boot/bootx64.efi
   231            - source: grub.cfg
   232              target: EFI/ubuntu/grub.cfg
   233  `)
   234  
   235  var gadgetYamlUC20PC = []byte(`
   236  volumes:
   237    pc:
   238      # bootloader configuration is shipped and managed by snapd
   239      bootloader: grub
   240      structure:
   241        - name: mbr
   242          type: mbr
   243          size: 440
   244          update:
   245            edition: 1
   246          content:
   247            - image: pc-boot.img
   248        - name: BIOS Boot
   249          type: DA,21686148-6449-6E6F-744E-656564454649
   250          size: 1M
   251          offset: 1M
   252          offset-write: mbr+92
   253          update:
   254            edition: 2
   255          content:
   256            - image: pc-core.img
   257        - name: ubuntu-seed
   258          role: system-seed
   259          filesystem: vfat
   260          # UEFI will boot the ESP partition by default first
   261          type: EF,C12A7328-F81F-11D2-BA4B-00A0C93EC93B
   262          size: 1200M
   263          update:
   264            edition: 2
   265          content:
   266            - source: grubx64.efi
   267              target: EFI/boot/grubx64.efi
   268            - source: shim.efi.signed
   269              target: EFI/boot/bootx64.efi
   270        - name: ubuntu-boot
   271          role: system-boot
   272          filesystem: ext4
   273          type: 83,0FC63DAF-8483-4772-8E79-3D69D8477DE4
   274          # whats the appropriate size?
   275          size: 750M
   276          update:
   277            edition: 1
   278          content:
   279            - source: grubx64.efi
   280              target: EFI/boot/grubx64.efi
   281            - source: shim.efi.signed
   282              target: EFI/boot/bootx64.efi
   283        - name: ubuntu-save
   284          role: system-save
   285          filesystem: ext4
   286          type: 83,0FC63DAF-8483-4772-8E79-3D69D8477DE4
   287          size: 16M
   288        - name: ubuntu-data
   289          role: system-data
   290          filesystem: ext4
   291          type: 83,0FC63DAF-8483-4772-8E79-3D69D8477DE4
   292          size: 1G
   293  `)
   294  
   295  var gadgetYamlRPi = []byte(`
   296  volumes:
   297    pi:
   298      schema: mbr
   299      bootloader: u-boot
   300      structure:
   301        - type: 0C
   302          filesystem: vfat
   303          filesystem-label: system-boot
   304          size: 128M
   305          content:
   306            - source: boot-assets/
   307              target: /
   308  `)
   309  
   310  var gadgetYamlLk = []byte(`
   311  volumes:
   312    volumename:
   313      schema: mbr
   314      bootloader: lk
   315      structure:
   316        - name: BOOTIMG1
   317          size: 25165824
   318          role: system-boot-image
   319          type: 27
   320          content:
   321            - image: boot.img
   322        - name: BOOTIMG2
   323          size: 25165824
   324          role: system-boot-image
   325          type: 27
   326        - name: snapbootsel
   327          size: 131072
   328          role: system-boot-select
   329          type: B2
   330          content:
   331            - image: snapbootsel.bin
   332        - name: snapbootselbak
   333          size: 131072
   334          role: system-boot-select
   335          type: B2
   336          content:
   337            - image: snapbootsel.bin
   338        - name: writable
   339          type: 83
   340          filesystem: ext4
   341          filesystem-label: writable
   342          size: 500M
   343          role: system-data
   344  `)
   345  
   346  var gadgetYamlLkUC20 = []byte(`
   347  volumes:
   348    dragonboard:
   349      schema: gpt
   350      bootloader: lk
   351      structure:
   352        - name: cdt
   353          offset: 17408
   354          size: 2048
   355          type: A19F205F-CCD8-4B6D-8F1E-2D9BC24CFFB1
   356          content:
   357              - image: blobs/sbc_1.0_8016.bin
   358        - name: sbl1
   359          offset: 19456
   360          size: 1048576
   361          content:
   362              - image: blobs/sbl1.mbn
   363          type: DEA0BA2C-CBDD-4805-B4F9-F428251C3E98
   364        - name: rpm
   365          offset: 1068032
   366          size: 1048576
   367          content:
   368              - image: blobs/rpm.mbn
   369          type: 098DF793-D712-413D-9D4E-89D711772228
   370        - name: tz
   371          offset: 2116608
   372          size: 1048576
   373          content:
   374              - image: blobs/tz.mbn
   375          type: A053AA7F-40B8-4B1C-BA08-2F68AC71A4F4
   376        - name: hyp
   377          offset: 3165184
   378          size: 1048576
   379          content:
   380              - image: blobs/hyp.mbn
   381          type: E1A6A689-0C8D-4CC6-B4E8-55A4320FBD8A
   382        - name: sec
   383          offset: 5242880
   384          size: 1048576
   385          type: 303E6AC3-AF15-4C54-9E9B-D9A8FBECF401
   386        - name: aboot
   387          offset: 6291456
   388          size: 2097152
   389          content:
   390              - image: blobs/emmc_appsboot.mbn
   391          type: 400FFDCD-22E0-47E7-9A23-F16ED9382388
   392        - name: snaprecoverysel
   393          offset: 8388608
   394          size: 131072
   395          passthrough:
   396            role: system-seed-select
   397          content:
   398              - image: snaprecoverysel.bin
   399          type: B214D5E4-D442-45E6-B8C6-01BDCD82D396
   400        - name: snaprecoveryselbak
   401          offset: 8519680
   402          size: 131072
   403          passthrough:
   404            role: system-seed-select
   405          content:
   406              - image: snaprecoverysel.bin
   407          type: B214D5E4-D442-45E6-B8C6-01BDCD82D396
   408        - name: snapbootsel
   409          offset: 8650752
   410          size: 131072
   411          role: system-boot-select
   412          content:
   413              - image: blobs/snapbootsel.bin
   414          type: B214D5E4-D442-45E6-B8C6-01BDCD82D396
   415        - name: snapbootselbak
   416          offset: 8781824
   417          size: 131072
   418          role: system-boot-select
   419          content:
   420              - image: blobs/snapbootsel.bin
   421          type: B214D5E4-D442-45E6-B8C6-01BDCD82D396
   422        - name: boot_ra
   423          offset: 9437184
   424          size: 31457280
   425          type: 20117F86-E985-4357-B9EE-374BC1D8487D
   426          passthrough:
   427            role: system-seed-image
   428        - name: boot_rb
   429          offset: 40894464
   430          size: 31457280
   431          type: 20117F86-E985-4357-B9EE-374BC1D8487D
   432          passthrough:
   433            role: system-seed-image
   434        - name: boot_a
   435          offset: 72351744
   436          size: 31457280
   437          type: 20117F86-E985-4357-B9EE-374BC1D8487D
   438          role: system-boot-image
   439        - name: boot_b
   440          offset: 103809024
   441          size: 31457280
   442          type: 20117F86-E985-4357-B9EE-374BC1D8487D
   443          role: system-boot-image
   444        - name: ubuntu-boot
   445          offset: 135266304
   446          filesystem: ext4
   447          size: 10485760
   448          type: 83,0FC63DAF-8483-4772-8E79-3D69D8477DE4
   449          role: system-boot
   450        - name: ubuntu-seed
   451          offset: 145752064
   452          filesystem: ext4
   453          size: 500M
   454          type: 83,0FC63DAF-8483-4772-8E79-3D69D8477DE4
   455          role: system-seed
   456        - name: ubuntu-data
   457          offset: 670040064
   458          filesystem: ext4
   459          size: 1G
   460          type: 83,0FC63DAF-8483-4772-8E79-3D69D8477DE4
   461          role: system-data
   462  `)
   463  
   464  var gadgetYamlLkLegacy = []byte(`
   465  volumes:
   466    volumename:
   467      schema: mbr
   468      bootloader: lk
   469      structure:
   470        - name: BOOTIMG1
   471          size: 25165824
   472          role: bootimg
   473          type: 27
   474          content:
   475            - image: boot.img
   476        - name: BOOTIMG2
   477          size: 25165824
   478          role: bootimg
   479          type: 27
   480        - name: snapbootsel
   481          size: 131072
   482          role: bootselect
   483          type: B2
   484          content:
   485            - image: snapbootsel.bin
   486        - name: snapbootselbak
   487          size: 131072
   488          role: bootselect
   489          type: B2
   490          content:
   491            - image: snapbootsel.bin
   492        - name: writable
   493          type: 83
   494          filesystem: ext4
   495          filesystem-label: writable
   496          size: 500M
   497          role: system-data
   498  `)
   499  
   500  func TestRun(t *testing.T) { TestingT(t) }
   501  
   502  func mustParseGadgetSize(c *C, s string) quantity.Size {
   503  	gs, err := quantity.ParseSize(s)
   504  	c.Assert(err, IsNil)
   505  	return gs
   506  }
   507  
   508  func mustParseGadgetRelativeOffset(c *C, s string) *gadget.RelativeOffset {
   509  	grs, err := gadget.ParseRelativeOffset(s)
   510  	c.Assert(err, IsNil)
   511  	c.Assert(grs, NotNil)
   512  	return grs
   513  }
   514  
   515  func (s *gadgetYamlTestSuite) SetUpTest(c *C) {
   516  	dirs.SetRootDir(c.MkDir())
   517  	s.dir = c.MkDir()
   518  	c.Assert(os.MkdirAll(filepath.Join(s.dir, "meta"), 0755), IsNil)
   519  	s.gadgetYamlPath = filepath.Join(s.dir, "meta", "gadget.yaml")
   520  }
   521  
   522  func (s *gadgetYamlTestSuite) TearDownTest(c *C) {
   523  	dirs.SetRootDir("/")
   524  }
   525  
   526  type modelCharateristics struct {
   527  	classic    bool
   528  	systemSeed bool
   529  }
   530  
   531  func (m *modelCharateristics) Classic() bool {
   532  	return m.classic
   533  }
   534  
   535  func (m *modelCharateristics) Grade() asserts.ModelGrade {
   536  	if m.systemSeed {
   537  		return asserts.ModelSigned
   538  	}
   539  	return asserts.ModelGradeUnset
   540  }
   541  
   542  func (s *gadgetYamlTestSuite) TestReadGadgetYamlMissing(c *C) {
   543  	// if model is nil, we allow a missing yaml
   544  	_, err := gadget.ReadInfo("bogus-path", nil)
   545  	c.Assert(err, IsNil)
   546  
   547  	_, err = gadget.ReadInfo("bogus-path", &modelCharateristics{})
   548  	c.Assert(err, ErrorMatches, ".*meta/gadget.yaml: no such file or directory")
   549  }
   550  
   551  func (s *gadgetYamlTestSuite) TestReadGadgetYamlOnClassicOptional(c *C) {
   552  	// no meta/gadget.yaml
   553  	gi, err := gadget.ReadInfo(s.dir, &modelCharateristics{classic: true})
   554  	c.Assert(err, IsNil)
   555  	c.Check(gi, NotNil)
   556  }
   557  
   558  func (s *gadgetYamlTestSuite) TestReadGadgetYamlOnClassicEmptyIsValid(c *C) {
   559  	err := ioutil.WriteFile(s.gadgetYamlPath, nil, 0644)
   560  	c.Assert(err, IsNil)
   561  
   562  	ginfo, err := gadget.ReadInfo(s.dir, &modelCharateristics{classic: true})
   563  	c.Assert(err, IsNil)
   564  	c.Assert(ginfo, DeepEquals, &gadget.Info{})
   565  }
   566  
   567  func (s *gadgetYamlTestSuite) TestReadGadgetYamlOnClassicOnylDefaultsIsValid(c *C) {
   568  	err := ioutil.WriteFile(s.gadgetYamlPath, mockClassicGadgetYaml, 0644)
   569  	c.Assert(err, IsNil)
   570  
   571  	ginfo, err := gadget.ReadInfo(s.dir, &modelCharateristics{classic: true})
   572  	c.Assert(err, IsNil)
   573  	c.Assert(ginfo, DeepEquals, &gadget.Info{
   574  		Defaults: map[string]map[string]interface{}{
   575  			"system": {"something": true},
   576  			// keep this comment so that gofmt 1.10+ does not
   577  			// realign this, thus breaking our gofmt 1.9 checks
   578  			"otheridididididididididididididi": {"foo": map[string]interface{}{"bar": "baz"}},
   579  		},
   580  	})
   581  }
   582  
   583  func (s *gadgetYamlTestSuite) TestFlatten(c *C) {
   584  	cfg := map[string]interface{}{
   585  		"foo":         "bar",
   586  		"some.option": true,
   587  		"sub": map[string]interface{}{
   588  			"option1": true,
   589  			"option2": map[string]interface{}{
   590  				"deep": "2",
   591  			},
   592  		},
   593  	}
   594  	out := map[string]interface{}{}
   595  	gadget.Flatten("", cfg, out)
   596  	c.Check(out, DeepEquals, map[string]interface{}{
   597  		"foo":              "bar",
   598  		"some.option":      true,
   599  		"sub.option1":      true,
   600  		"sub.option2.deep": "2",
   601  	})
   602  }
   603  
   604  func (s *gadgetYamlTestSuite) TestCoreConfigDefaults(c *C) {
   605  	err := ioutil.WriteFile(s.gadgetYamlPath, mockClassicGadgetCoreDefaultsYaml, 0644)
   606  	c.Assert(err, IsNil)
   607  
   608  	ginfo, err := gadget.ReadInfo(s.dir, &modelCharateristics{classic: true})
   609  	c.Assert(err, IsNil)
   610  	defaults := gadget.SystemDefaults(ginfo.Defaults)
   611  	c.Check(defaults, DeepEquals, map[string]interface{}{
   612  		"ssh.disable": true,
   613  	})
   614  
   615  	yaml := string(mockClassicGadgetCoreDefaultsYaml) + `
   616    system:
   617      something: true
   618  `
   619  
   620  	err = ioutil.WriteFile(s.gadgetYamlPath, []byte(yaml), 0644)
   621  	c.Assert(err, IsNil)
   622  	ginfo, err = gadget.ReadInfo(s.dir, &modelCharateristics{classic: true})
   623  	c.Assert(err, IsNil)
   624  
   625  	defaults = gadget.SystemDefaults(ginfo.Defaults)
   626  	c.Check(defaults, DeepEquals, map[string]interface{}{
   627  		"something": true,
   628  	})
   629  }
   630  
   631  func (s *gadgetYamlTestSuite) TestReadGadgetDefaultsMultiline(c *C) {
   632  	err := ioutil.WriteFile(s.gadgetYamlPath, mockClassicGadgetMultilineDefaultsYaml, 0644)
   633  	c.Assert(err, IsNil)
   634  
   635  	ginfo, err := gadget.ReadInfo(s.dir, &modelCharateristics{classic: true})
   636  	c.Assert(err, IsNil)
   637  	c.Assert(ginfo, DeepEquals, &gadget.Info{
   638  		Defaults: map[string]map[string]interface{}{
   639  			"system": {"something": true},
   640  			// keep this comment so that gofmt 1.10+ does not
   641  			// realign this, thus breaking our gofmt 1.9 checks
   642  			"otheridididididididididididididi": {"foosnap": map[string]interface{}{"multiline": "foo\nbar\n"}},
   643  		},
   644  	})
   645  }
   646  
   647  func asOffsetPtr(offs quantity.Offset) *quantity.Offset {
   648  	goff := offs
   649  	return &goff
   650  }
   651  
   652  var (
   653  	classicMod = &modelCharateristics{
   654  		classic: true,
   655  	}
   656  	coreMod = &modelCharateristics{
   657  		classic: false,
   658  	}
   659  	uc20Mod = &modelCharateristics{
   660  		classic:    false,
   661  		systemSeed: true,
   662  	}
   663  )
   664  
   665  func (s *gadgetYamlTestSuite) TestReadGadgetYamlValid(c *C) {
   666  	err := ioutil.WriteFile(s.gadgetYamlPath, mockGadgetYaml, 0644)
   667  	c.Assert(err, IsNil)
   668  
   669  	ginfo, err := gadget.ReadInfo(s.dir, coreMod)
   670  	c.Assert(err, IsNil)
   671  	c.Assert(ginfo, DeepEquals, &gadget.Info{
   672  		Defaults: map[string]map[string]interface{}{
   673  			"system": {"something": true},
   674  		},
   675  		Connections: []gadget.Connection{
   676  			{Plug: gadget.ConnectionPlug{SnapID: "snapid1", Plug: "plg1"}, Slot: gadget.ConnectionSlot{SnapID: "snapid2", Slot: "slot"}},
   677  			{Plug: gadget.ConnectionPlug{SnapID: "snapid3", Plug: "process-control"}, Slot: gadget.ConnectionSlot{SnapID: "system", Slot: "process-control"}},
   678  			{Plug: gadget.ConnectionPlug{SnapID: "snapid4", Plug: "pctl4"}, Slot: gadget.ConnectionSlot{SnapID: "system", Slot: "process-control"}},
   679  		},
   680  		Volumes: map[string]*gadget.Volume{
   681  			"volumename": {
   682  				Schema:     "mbr",
   683  				Bootloader: "u-boot",
   684  				ID:         "0C",
   685  				Structure: []gadget.VolumeStructure{
   686  					{
   687  						Label:       "system-boot",
   688  						Role:        "system-boot", // implicit
   689  						Offset:      asOffsetPtr(12345),
   690  						OffsetWrite: mustParseGadgetRelativeOffset(c, "777"),
   691  						Size:        88888,
   692  						Type:        "0C",
   693  						Filesystem:  "vfat",
   694  						Content: []gadget.VolumeContent{
   695  							{
   696  								UnresolvedSource: "subdir/",
   697  								Target:           "/",
   698  								Unpack:           false,
   699  							},
   700  							{
   701  								UnresolvedSource: "foo",
   702  								Target:           "/",
   703  								Unpack:           false,
   704  							},
   705  						},
   706  					},
   707  				},
   708  			},
   709  		},
   710  	})
   711  }
   712  
   713  func (s *gadgetYamlTestSuite) TestReadMultiVolumeGadgetYamlValid(c *C) {
   714  	err := ioutil.WriteFile(s.gadgetYamlPath, mockMultiVolumeGadgetYaml, 0644)
   715  	c.Assert(err, IsNil)
   716  
   717  	ginfo, err := gadget.ReadInfo(s.dir, nil)
   718  	c.Assert(err, IsNil)
   719  	c.Check(ginfo.Volumes, HasLen, 2)
   720  	c.Assert(ginfo, DeepEquals, &gadget.Info{
   721  		Volumes: map[string]*gadget.Volume{
   722  			"frobinator-image": {
   723  				Schema:     "mbr",
   724  				Bootloader: "u-boot",
   725  				Structure: []gadget.VolumeStructure{
   726  					{
   727  						Name:       "system-boot",
   728  						Role:       "system-boot",
   729  						Label:      "system-boot",
   730  						Size:       mustParseGadgetSize(c, "128M"),
   731  						Filesystem: "vfat",
   732  						Type:       "0C",
   733  						Content: []gadget.VolumeContent{
   734  							{
   735  								UnresolvedSource: "splash.bmp",
   736  								Target:           ".",
   737  							},
   738  						},
   739  					},
   740  					{
   741  						Role:       "system-data",
   742  						Name:       "writable",
   743  						Label:      "writable",
   744  						Type:       "83",
   745  						Filesystem: "ext4",
   746  						Size:       mustParseGadgetSize(c, "380M"),
   747  					},
   748  				},
   749  			},
   750  			"u-boot-frobinator": {
   751  				Schema: "gpt",
   752  				Structure: []gadget.VolumeStructure{
   753  					{
   754  						Name:   "u-boot",
   755  						Type:   "bare",
   756  						Size:   623000,
   757  						Offset: asOffsetPtr(0),
   758  						Content: []gadget.VolumeContent{
   759  							{
   760  								Image: "u-boot.imz",
   761  							},
   762  						},
   763  					},
   764  				},
   765  			},
   766  		},
   767  	})
   768  }
   769  
   770  func (s *gadgetYamlTestSuite) TestReadGadgetYamlInvalidBootloader(c *C) {
   771  	mockGadgetYamlBroken := []byte(`
   772  volumes:
   773   name:
   774    bootloader: silo
   775  `)
   776  
   777  	err := ioutil.WriteFile(s.gadgetYamlPath, mockGadgetYamlBroken, 0644)
   778  	c.Assert(err, IsNil)
   779  
   780  	_, err = gadget.ReadInfo(s.dir, nil)
   781  	c.Assert(err, ErrorMatches, "bootloader must be one of grub, u-boot, android-boot or lk")
   782  }
   783  
   784  func (s *gadgetYamlTestSuite) TestReadGadgetYamlEmptyBootloader(c *C) {
   785  	mockGadgetYamlBroken := []byte(`
   786  volumes:
   787   name:
   788    bootloader:
   789  `)
   790  
   791  	err := ioutil.WriteFile(s.gadgetYamlPath, mockGadgetYamlBroken, 0644)
   792  	c.Assert(err, IsNil)
   793  
   794  	_, err = gadget.ReadInfo(s.dir, &modelCharateristics{classic: false})
   795  	c.Assert(err, ErrorMatches, "bootloader not declared in any volume")
   796  }
   797  
   798  func (s *gadgetYamlTestSuite) TestReadGadgetYamlMissingBootloader(c *C) {
   799  	err := ioutil.WriteFile(s.gadgetYamlPath, nil, 0644)
   800  	c.Assert(err, IsNil)
   801  
   802  	_, err = gadget.ReadInfo(s.dir, &modelCharateristics{classic: false})
   803  	c.Assert(err, ErrorMatches, "bootloader not declared in any volume")
   804  }
   805  
   806  func (s *gadgetYamlTestSuite) TestReadGadgetYamlInvalidDefaultsKey(c *C) {
   807  	mockGadgetYamlBroken := []byte(`
   808  defaults:
   809   foo:
   810    x: 1
   811  `)
   812  
   813  	err := ioutil.WriteFile(s.gadgetYamlPath, mockGadgetYamlBroken, 0644)
   814  	c.Assert(err, IsNil)
   815  
   816  	_, err = gadget.ReadInfo(s.dir, nil)
   817  	c.Assert(err, ErrorMatches, `default stanza not keyed by "system" or snap-id: foo`)
   818  }
   819  
   820  func (s *gadgetYamlTestSuite) TestReadGadgetYamlInvalidConnection(c *C) {
   821  	mockGadgetYamlBroken := `
   822  connections:
   823   - @INVALID@
   824  `
   825  	tests := []struct {
   826  		invalidConn string
   827  		expectedErr string
   828  	}{
   829  		{``, `gadget connection plug cannot be empty`},
   830  		{`foo:bar baz:quux`, `(?s).*unmarshal errors:.*`},
   831  		{`plug: foo:`, `.*mapping values are not allowed in this context`},
   832  		{`plug: ":"`, `.*in gadget connection plug: expected "\(<snap-id>\|system\):name" not ":"`},
   833  		{`slot: "foo:"`, `.*in gadget connection slot: expected "\(<snap-id>\|system\):name" not "foo:"`},
   834  		{`slot: foo:bar`, `gadget connection plug cannot be empty`},
   835  	}
   836  
   837  	for _, t := range tests {
   838  		mockGadgetYamlBroken := strings.Replace(mockGadgetYamlBroken, "@INVALID@", t.invalidConn, 1)
   839  
   840  		err := ioutil.WriteFile(s.gadgetYamlPath, []byte(mockGadgetYamlBroken), 0644)
   841  		c.Assert(err, IsNil)
   842  
   843  		_, err = gadget.ReadInfo(s.dir, nil)
   844  		c.Check(err, ErrorMatches, t.expectedErr)
   845  	}
   846  }
   847  
   848  func (s *gadgetYamlTestSuite) TestReadGadgetYamlVolumeUpdate(c *C) {
   849  	err := ioutil.WriteFile(s.gadgetYamlPath, mockVolumeUpdateGadgetYaml, 0644)
   850  	c.Assert(err, IsNil)
   851  
   852  	ginfo, err := gadget.ReadInfo(s.dir, coreMod)
   853  	c.Check(err, IsNil)
   854  	c.Assert(ginfo, DeepEquals, &gadget.Info{
   855  		Volumes: map[string]*gadget.Volume{
   856  			"bootloader": {
   857  				Schema:     "mbr",
   858  				Bootloader: "u-boot",
   859  				ID:         "0C",
   860  				Structure: []gadget.VolumeStructure{
   861  					{
   862  						Label:       "system-boot",
   863  						Role:        "system-boot", // implicit
   864  						Offset:      asOffsetPtr(12345),
   865  						OffsetWrite: mustParseGadgetRelativeOffset(c, "777"),
   866  						Size:        88888,
   867  						Type:        "0C",
   868  						Filesystem:  "vfat",
   869  						Content: []gadget.VolumeContent{{
   870  							UnresolvedSource: "subdir/",
   871  							Target:           "/",
   872  							Unpack:           false,
   873  						}},
   874  						Update: gadget.VolumeUpdate{
   875  							Edition: 5,
   876  							Preserve: []string{
   877  								"env.txt",
   878  								"config.txt",
   879  							},
   880  						},
   881  					},
   882  				},
   883  			},
   884  		},
   885  	})
   886  }
   887  
   888  func (s *gadgetYamlTestSuite) TestReadGadgetYamlVolumeUpdateUnhappy(c *C) {
   889  	broken := bytes.Replace(mockVolumeUpdateGadgetYaml, []byte("edition: 5"), []byte("edition: borked"), 1)
   890  	err := ioutil.WriteFile(s.gadgetYamlPath, broken, 0644)
   891  	c.Assert(err, IsNil)
   892  
   893  	_, err = gadget.ReadInfo(s.dir, nil)
   894  	c.Check(err, ErrorMatches, `cannot parse gadget metadata: "edition" must be a positive number, not "borked"`)
   895  
   896  	broken = bytes.Replace(mockVolumeUpdateGadgetYaml, []byte("edition: 5"), []byte("edition: -5"), 1)
   897  	err = ioutil.WriteFile(s.gadgetYamlPath, broken, 0644)
   898  	c.Assert(err, IsNil)
   899  
   900  	_, err = gadget.ReadInfo(s.dir, nil)
   901  	c.Check(err, ErrorMatches, `cannot parse gadget metadata: "edition" must be a positive number, not "-5"`)
   902  }
   903  
   904  func (s *gadgetYamlTestSuite) TestUnmarshalGadgetRelativeOffset(c *C) {
   905  	type foo struct {
   906  		OffsetWrite gadget.RelativeOffset `yaml:"offset-write"`
   907  	}
   908  
   909  	for i, tc := range []struct {
   910  		s   string
   911  		sz  *gadget.RelativeOffset
   912  		err string
   913  	}{
   914  		{"1234", &gadget.RelativeOffset{Offset: 1234}, ""},
   915  		{"1234M", &gadget.RelativeOffset{Offset: 1234 * quantity.OffsetMiB}, ""},
   916  		{"4096M", &gadget.RelativeOffset{Offset: 4096 * quantity.OffsetMiB}, ""},
   917  		{"0", &gadget.RelativeOffset{}, ""},
   918  		{"mbr+0", &gadget.RelativeOffset{RelativeTo: "mbr"}, ""},
   919  		{"foo+1234M", &gadget.RelativeOffset{RelativeTo: "foo", Offset: 1234 * quantity.OffsetMiB}, ""},
   920  		{"foo+1G", &gadget.RelativeOffset{RelativeTo: "foo", Offset: 1024 * quantity.OffsetMiB}, ""},
   921  		{"foo+1G", &gadget.RelativeOffset{RelativeTo: "foo", Offset: 1024 * quantity.OffsetMiB}, ""},
   922  		{"foo+4097M", nil, `cannot parse relative offset "foo\+4097M": offset above 4G limit`},
   923  		{"foo+", nil, `cannot parse relative offset "foo\+": missing offset`},
   924  		{"foo+++12", nil, `cannot parse relative offset "foo\+\+\+12": cannot parse offset "\+\+12": .*`},
   925  		{"+12", nil, `cannot parse relative offset "\+12": missing volume name`},
   926  		{"a0M", nil, `cannot parse relative offset "a0M": cannot parse offset "a0M": no numerical prefix.*`},
   927  		{"-123", nil, `cannot parse relative offset "-123": cannot parse offset "-123": offset cannot be negative`},
   928  		{"123a", nil, `cannot parse relative offset "123a": cannot parse offset "123a": invalid suffix "a"`},
   929  	} {
   930  		c.Logf("tc: %v", i)
   931  
   932  		var f foo
   933  		err := yaml.Unmarshal([]byte(fmt.Sprintf("offset-write: %s", tc.s)), &f)
   934  		if tc.err != "" {
   935  			c.Check(err, ErrorMatches, tc.err)
   936  		} else {
   937  			c.Check(err, IsNil)
   938  			c.Assert(tc.sz, NotNil, Commentf("test case %v data must be not-nil", i))
   939  			c.Check(f.OffsetWrite, Equals, *tc.sz)
   940  		}
   941  	}
   942  }
   943  
   944  var classicModelCharacteristics = []gadget.Model{
   945  	nil,
   946  	&modelCharateristics{classic: false, systemSeed: false},
   947  	&modelCharateristics{classic: true, systemSeed: false},
   948  }
   949  
   950  func (s *gadgetYamlTestSuite) TestReadGadgetYamlPCHappy(c *C) {
   951  	err := ioutil.WriteFile(s.gadgetYamlPath, gadgetYamlPC, 0644)
   952  	c.Assert(err, IsNil)
   953  
   954  	for _, mod := range classicModelCharacteristics {
   955  		_, err = gadget.ReadInfo(s.dir, mod)
   956  		c.Assert(err, IsNil)
   957  	}
   958  }
   959  
   960  func (s *gadgetYamlTestSuite) TestReadGadgetYamlRPiHappy(c *C) {
   961  	err := ioutil.WriteFile(s.gadgetYamlPath, gadgetYamlRPi, 0644)
   962  	c.Assert(err, IsNil)
   963  
   964  	for _, mod := range classicModelCharacteristics {
   965  		_, err = gadget.ReadInfo(s.dir, mod)
   966  		c.Assert(err, IsNil)
   967  	}
   968  }
   969  
   970  func (s *gadgetYamlTestSuite) TestReadGadgetYamlLkHappy(c *C) {
   971  	err := ioutil.WriteFile(s.gadgetYamlPath, gadgetYamlLk, 0644)
   972  	c.Assert(err, IsNil)
   973  
   974  	for _, mod := range classicModelCharacteristics {
   975  		_, err = gadget.ReadInfo(s.dir, mod)
   976  		c.Assert(err, IsNil)
   977  	}
   978  }
   979  
   980  func (s *gadgetYamlTestSuite) TestReadGadgetYamlLkUC20Happy(c *C) {
   981  	err := ioutil.WriteFile(s.gadgetYamlPath, gadgetYamlLkUC20, 0644)
   982  	c.Assert(err, IsNil)
   983  
   984  	uc20Model := &modelCharateristics{
   985  		systemSeed: true,
   986  		classic:    false,
   987  	}
   988  
   989  	_, err = gadget.ReadInfo(s.dir, uc20Model)
   990  	c.Assert(err, IsNil)
   991  }
   992  
   993  func (s *gadgetYamlTestSuite) TestReadGadgetYamlLkLegacyHappy(c *C) {
   994  	err := ioutil.WriteFile(s.gadgetYamlPath, gadgetYamlLkLegacy, 0644)
   995  	c.Assert(err, IsNil)
   996  
   997  	for _, mod := range classicModelCharacteristics {
   998  		_, err = gadget.ReadInfo(s.dir, mod)
   999  		c.Assert(err, IsNil)
  1000  	}
  1001  }
  1002  
  1003  func (s *gadgetYamlTestSuite) TestValidateStructureType(c *C) {
  1004  	for i, tc := range []struct {
  1005  		s      string
  1006  		err    string
  1007  		schema string
  1008  	}{
  1009  		// legacy
  1010  		{"mbr", "", ""},
  1011  		// special case
  1012  		{"bare", "", ""},
  1013  		// plain MBR type
  1014  		{"0C", "", "mbr"},
  1015  		// GPT UUID
  1016  		{"21686148-6449-6E6F-744E-656564454649", "", "gpt"},
  1017  		// GPT UUID (lowercase)
  1018  		{"21686148-6449-6e6f-744e-656564454649", "", "gpt"},
  1019  		// hybrid ID
  1020  		{"EF,21686148-6449-6E6F-744E-656564454649", "", ""},
  1021  		// hybrid ID (UUID lowercase)
  1022  		{"EF,21686148-6449-6e6f-744e-656564454649", "", ""},
  1023  		// hybrid, partially lowercase UUID
  1024  		{"EF,aa686148-6449-6e6f-744E-656564454649", "", ""},
  1025  		// GPT UUID, partially lowercase
  1026  		{"aa686148-6449-6e6f-744E-656564454649", "", ""},
  1027  		// no type specified
  1028  		{"", `invalid type "": type is not specified`, ""},
  1029  		// plain MBR type without mbr schema
  1030  		{"0C", `invalid type "0C": MBR structure type with non-MBR schema ""`, ""},
  1031  		// GPT UUID with non GPT schema
  1032  		{"21686148-6449-6E6F-744E-656564454649", `invalid type "21686148-6449-6E6F-744E-656564454649": GUID structure type with non-GPT schema "mbr"`, "mbr"},
  1033  		// invalid
  1034  		{"1234", `invalid type "1234": invalid format`, ""},
  1035  		// outside of hex range
  1036  		{"FG", `invalid type "FG": invalid format`, ""},
  1037  		{"GG686148-6449-6E6F-744E-656564454649", `invalid type "GG686148-6449-6E6F-744E-656564454649": invalid format`, ""},
  1038  		// too long
  1039  		{"AA686148-6449-6E6F-744E-656564454649123", `invalid type "AA686148-6449-6E6F-744E-656564454649123": invalid format`, ""},
  1040  		// hybrid, missing MBR type
  1041  		{",AA686148-6449-6E6F-744E-656564454649", `invalid type ",AA686148-6449-6E6F-744E-656564454649": invalid format of hybrid type`, ""},
  1042  		// hybrid, missing GPT UUID
  1043  		{"EF,", `invalid type "EF,": invalid format of hybrid type`, ""},
  1044  		// hybrid, MBR type too long
  1045  		{"EFC,AA686148-6449-6E6F-744E-656564454649", `invalid type "EFC,AA686148-6449-6E6F-744E-656564454649": invalid format of hybrid type`, ""},
  1046  		// hybrid, GPT UUID too long
  1047  		{"EF,AAAA686148-6449-6E6F-744E-656564454649", `invalid type "EF,AAAA686148-6449-6E6F-744E-656564454649": invalid format of hybrid type`, ""},
  1048  		// GPT schema with non GPT type
  1049  		{"EF,AAAA686148-6449-6E6F-744E-656564454649", `invalid type "EF,AAAA686148-6449-6E6F-744E-656564454649": invalid format of hybrid type`, "gpt"},
  1050  	} {
  1051  		c.Logf("tc: %v %q", i, tc.s)
  1052  
  1053  		err := gadget.ValidateVolumeStructure(&gadget.VolumeStructure{Type: tc.s, Size: 123}, &gadget.Volume{Schema: tc.schema})
  1054  		if tc.err != "" {
  1055  			c.Check(err, ErrorMatches, tc.err)
  1056  		} else {
  1057  			c.Check(err, IsNil)
  1058  		}
  1059  	}
  1060  }
  1061  
  1062  func mustParseStructureNoImplicit(c *C, s string) *gadget.VolumeStructure {
  1063  	var v gadget.VolumeStructure
  1064  	err := yaml.Unmarshal([]byte(s), &v)
  1065  	c.Assert(err, IsNil)
  1066  	return &v
  1067  }
  1068  
  1069  func mustParseStructure(c *C, s string) *gadget.VolumeStructure {
  1070  	vs := mustParseStructureNoImplicit(c, s)
  1071  	gadget.SetImplicitForVolumeStructure(vs, 0, make(map[string]bool))
  1072  	return vs
  1073  }
  1074  
  1075  func (s *gadgetYamlTestSuite) TestValidateRole(c *C) {
  1076  	uuidType := `
  1077  type: 21686148-6449-6E6F-744E-656564454649
  1078  size: 1023
  1079  `
  1080  	bareType := `
  1081  type: bare
  1082  `
  1083  	mbrTooLarge := bareType + `
  1084  role: mbr
  1085  size: 467`
  1086  	mbrBadOffset := bareType + `
  1087  role: mbr
  1088  size: 446
  1089  offset: 123`
  1090  	mbrBadID := bareType + `
  1091  role: mbr
  1092  id: 123
  1093  size: 446`
  1094  	mbrBadFilesystem := bareType + `
  1095  role: mbr
  1096  size: 446
  1097  filesystem: vfat`
  1098  	mbrNoneFilesystem := `
  1099  type: bare
  1100  role: mbr
  1101  filesystem: none
  1102  size: 446`
  1103  	typeConflictsRole := `
  1104  type: bare
  1105  role: system-data
  1106  size: 1M`
  1107  	validSystemBoot := uuidType + `
  1108  role: system-boot
  1109  `
  1110  	validSystemSeed := uuidType + `
  1111  role: system-seed
  1112  `
  1113  	validSystemSave := uuidType + `
  1114  role: system-save
  1115  size: 5M
  1116  `
  1117  	emptyRole := uuidType + `
  1118  role: system-boot
  1119  size: 123M
  1120  `
  1121  	bogusRole := uuidType + `
  1122  role: foobar
  1123  size: 123M
  1124  `
  1125  	legacyMBR := `
  1126  type: mbr
  1127  size: 446`
  1128  	legacyTypeMatchingRole := `
  1129  type: mbr
  1130  role: mbr
  1131  size: 446`
  1132  	legacyTypeConflictsRole := `
  1133  type: mbr
  1134  role: system-data
  1135  size: 446`
  1136  	legacyTypeAsMBRTooLarge := `
  1137  type: mbr
  1138  size: 447`
  1139  	vol := &gadget.Volume{}
  1140  	mbrVol := &gadget.Volume{Schema: "mbr"}
  1141  	for i, tc := range []struct {
  1142  		s   *gadget.VolumeStructure
  1143  		v   *gadget.Volume
  1144  		err string
  1145  	}{
  1146  		{mustParseStructureNoImplicit(c, validSystemBoot), vol, ""},
  1147  		// empty, ok too
  1148  		{mustParseStructureNoImplicit(c, emptyRole), vol, ""},
  1149  		// invalid role name
  1150  		{mustParseStructureNoImplicit(c, bogusRole), vol, `invalid role "foobar": unsupported role`},
  1151  		// the system-seed role
  1152  		{mustParseStructureNoImplicit(c, validSystemSeed), vol, ""},
  1153  		// system-save role
  1154  		{mustParseStructureNoImplicit(c, validSystemSave), vol, ""},
  1155  		// mbr
  1156  		{mustParseStructureNoImplicit(c, mbrTooLarge), mbrVol, `invalid role "mbr": mbr structures cannot be larger than 446 bytes`},
  1157  		{mustParseStructureNoImplicit(c, mbrBadOffset), mbrVol, `invalid role "mbr": mbr structure must start at offset 0`},
  1158  		{mustParseStructureNoImplicit(c, mbrBadID), mbrVol, `invalid role "mbr": mbr structure must not specify partition ID`},
  1159  		{mustParseStructureNoImplicit(c, mbrBadFilesystem), mbrVol, `invalid role "mbr": mbr structures must not specify a file system`},
  1160  		// filesystem: none is ok for MBR
  1161  		{mustParseStructureNoImplicit(c, mbrNoneFilesystem), mbrVol, ""},
  1162  		// legacy, type: mbr treated like role: mbr
  1163  		{mustParseStructureNoImplicit(c, legacyMBR), mbrVol, ""},
  1164  		{mustParseStructureNoImplicit(c, legacyTypeMatchingRole), mbrVol, ""},
  1165  		{mustParseStructureNoImplicit(c, legacyTypeAsMBRTooLarge), mbrVol, `invalid implicit role "mbr": mbr structures cannot be larger than 446 bytes`},
  1166  		{mustParseStructureNoImplicit(c, legacyTypeConflictsRole), vol, `invalid role "system-data": conflicting legacy type: "mbr"`},
  1167  		// conflicting type/role
  1168  		{mustParseStructureNoImplicit(c, typeConflictsRole), vol, `invalid role "system-data": conflicting type: "bare"`},
  1169  	} {
  1170  		c.Logf("tc: %v %+v", i, tc.s)
  1171  
  1172  		err := gadget.ValidateVolumeStructure(tc.s, tc.v)
  1173  		if tc.err != "" {
  1174  			c.Check(err, ErrorMatches, tc.err)
  1175  		} else {
  1176  			c.Check(err, IsNil)
  1177  		}
  1178  	}
  1179  }
  1180  
  1181  func (s *gadgetYamlTestSuite) TestValidateFilesystem(c *C) {
  1182  	for i, tc := range []struct {
  1183  		s   string
  1184  		err string
  1185  	}{
  1186  		{"vfat", ""},
  1187  		{"ext4", ""},
  1188  		{"none", ""},
  1189  		{"btrfs", `invalid filesystem "btrfs"`},
  1190  	} {
  1191  		c.Logf("tc: %v %+v", i, tc.s)
  1192  
  1193  		err := gadget.ValidateVolumeStructure(&gadget.VolumeStructure{Filesystem: tc.s, Type: "21686148-6449-6E6F-744E-656564454649", Size: 123}, &gadget.Volume{})
  1194  		if tc.err != "" {
  1195  			c.Check(err, ErrorMatches, tc.err)
  1196  		} else {
  1197  			c.Check(err, IsNil)
  1198  		}
  1199  	}
  1200  }
  1201  
  1202  func (s *gadgetYamlTestSuite) TestValidateVolumeSchema(c *C) {
  1203  	for i, tc := range []struct {
  1204  		s   string
  1205  		err string
  1206  	}{
  1207  		{"gpt", ""},
  1208  		{"mbr", ""},
  1209  		// implicit GPT
  1210  		{"", ""},
  1211  		// invalid
  1212  		{"some", `invalid schema "some"`},
  1213  	} {
  1214  		c.Logf("tc: %v %+v", i, tc.s)
  1215  
  1216  		err := gadget.ValidateVolume("name", &gadget.Volume{Schema: tc.s}, nil, nil)
  1217  		if tc.err != "" {
  1218  			c.Check(err, ErrorMatches, tc.err)
  1219  		} else {
  1220  			c.Check(err, IsNil)
  1221  		}
  1222  	}
  1223  }
  1224  
  1225  func (s *gadgetYamlTestSuite) TestValidateVolumeName(c *C) {
  1226  
  1227  	for i, tc := range []struct {
  1228  		s   string
  1229  		err string
  1230  	}{
  1231  		{"valid", ""},
  1232  		{"still-valid", ""},
  1233  		{"123volume", ""},
  1234  		{"volume123", ""},
  1235  		{"PC", ""},
  1236  		{"PC123", ""},
  1237  		{"UPCASE", ""},
  1238  		// invalid
  1239  		{"-valid", "invalid name"},
  1240  		{"in+valid", "invalid name"},
  1241  		{"with whitespace", "invalid name"},
  1242  		{"", "invalid name"},
  1243  	} {
  1244  		c.Logf("tc: %v %+v", i, tc.s)
  1245  
  1246  		err := gadget.ValidateVolume(tc.s, &gadget.Volume{}, nil, nil)
  1247  		if tc.err != "" {
  1248  			c.Check(err, ErrorMatches, tc.err)
  1249  		} else {
  1250  			c.Check(err, IsNil)
  1251  		}
  1252  	}
  1253  }
  1254  
  1255  func (s *gadgetYamlTestSuite) TestValidateVolumeDuplicateStructures(c *C) {
  1256  	err := gadget.ValidateVolume("name", &gadget.Volume{
  1257  		Structure: []gadget.VolumeStructure{
  1258  			{Name: "duplicate", Type: "bare", Size: 1024},
  1259  			{Name: "duplicate", Type: "21686148-6449-6E6F-744E-656564454649", Size: 2048},
  1260  		},
  1261  	}, nil, nil)
  1262  	c.Assert(err, ErrorMatches, `structure name "duplicate" is not unique`)
  1263  }
  1264  
  1265  func (s *gadgetYamlTestSuite) TestValidateVolumeDuplicateFsLabel(c *C) {
  1266  	err := gadget.ValidateVolume("name", &gadget.Volume{
  1267  		Structure: []gadget.VolumeStructure{
  1268  			{Label: "foo", Type: "21686148-6449-6E6F-744E-656564454123", Size: quantity.SizeMiB},
  1269  			{Label: "foo", Type: "21686148-6449-6E6F-744E-656564454649", Size: quantity.SizeMiB},
  1270  		},
  1271  	}, nil, nil)
  1272  	c.Assert(err, ErrorMatches, `filesystem label "foo" is not unique`)
  1273  
  1274  	// writable isn't special
  1275  	for _, x := range []struct {
  1276  		systemSeed bool
  1277  		label      string
  1278  		errMsg     string
  1279  	}{
  1280  		{false, "writable", `filesystem label "writable" is not unique`},
  1281  		{false, "ubuntu-data", `filesystem label "ubuntu-data" is not unique`},
  1282  		{true, "writable", `filesystem label "writable" is not unique`},
  1283  		{true, "ubuntu-data", `filesystem label "ubuntu-data" is not unique`},
  1284  	} {
  1285  		for _, mod := range []*modelCharateristics{
  1286  			{classic: false, systemSeed: x.systemSeed},
  1287  			{classic: true, systemSeed: x.systemSeed},
  1288  		} {
  1289  			err = gadget.ValidateVolume("name", &gadget.Volume{
  1290  				Structure: []gadget.VolumeStructure{{
  1291  					Name:  "data1",
  1292  					Role:  gadget.SystemData,
  1293  					Label: x.label,
  1294  					Type:  "21686148-6449-6E6F-744E-656564454123",
  1295  					Size:  quantity.SizeMiB,
  1296  				}, {
  1297  					Name:  "data2",
  1298  					Role:  gadget.SystemData,
  1299  					Label: x.label,
  1300  					Type:  "21686148-6449-6E6F-744E-656564454649",
  1301  					Size:  quantity.SizeMiB,
  1302  				}},
  1303  			}, mod, nil)
  1304  			c.Assert(err, ErrorMatches, x.errMsg)
  1305  		}
  1306  	}
  1307  
  1308  	// nor is system-boot
  1309  	err = gadget.ValidateVolume("name", &gadget.Volume{
  1310  		Structure: []gadget.VolumeStructure{{
  1311  			Name:  "boot1",
  1312  			Label: "system-boot",
  1313  			Type:  "EF,C12A7328-F81F-11D2-BA4B-00A0C93EC93B",
  1314  			Size:  quantity.SizeMiB,
  1315  		}, {
  1316  			Name:  "boot2",
  1317  			Label: "system-boot",
  1318  			Type:  "EF,C12A7328-F81F-11D2-BA4B-00A0C93EC93B",
  1319  			Size:  quantity.SizeMiB,
  1320  		}},
  1321  	}, nil, nil)
  1322  	c.Assert(err, ErrorMatches, `filesystem label "system-boot" is not unique`)
  1323  }
  1324  
  1325  func (s *gadgetYamlTestSuite) TestValidateVolumeErrorsWrapped(c *C) {
  1326  	err := gadget.ValidateVolume("name", &gadget.Volume{
  1327  		Structure: []gadget.VolumeStructure{
  1328  			{Type: "bare", Size: 1024},
  1329  			{Type: "bogus", Size: 1024},
  1330  		},
  1331  	}, nil, nil)
  1332  	c.Assert(err, ErrorMatches, `invalid structure #1: invalid type "bogus": invalid format`)
  1333  
  1334  	err = gadget.ValidateVolume("name", &gadget.Volume{
  1335  		Structure: []gadget.VolumeStructure{
  1336  			{Type: "bare", Size: 1024},
  1337  			{Type: "bogus", Size: 1024, Name: "foo"},
  1338  		},
  1339  	}, nil, nil)
  1340  	c.Assert(err, ErrorMatches, `invalid structure #1 \("foo"\): invalid type "bogus": invalid format`)
  1341  
  1342  	err = gadget.ValidateVolume("name", &gadget.Volume{
  1343  		Structure: []gadget.VolumeStructure{
  1344  			{Type: "bare", Name: "foo", Size: 1024, Content: []gadget.VolumeContent{{UnresolvedSource: "foo"}}},
  1345  		},
  1346  	}, nil, nil)
  1347  	c.Assert(err, ErrorMatches, `invalid structure #0 \("foo"\): invalid content #0: cannot use non-image content for bare file system`)
  1348  }
  1349  
  1350  func (s *gadgetYamlTestSuite) TestValidateStructureContent(c *C) {
  1351  	bareOnlyOk := `
  1352  type: bare
  1353  size: 1M
  1354  content:
  1355    - image: foo.img
  1356  `
  1357  	bareMixed := `
  1358  type: bare
  1359  size: 1M
  1360  content:
  1361    - image: foo.img
  1362    - source: foo
  1363      target: bar
  1364  `
  1365  	bareMissing := `
  1366  type: bare
  1367  size: 1M
  1368  content:
  1369    - offset: 123
  1370  `
  1371  	fsOk := `
  1372  type: 21686148-6449-6E6F-744E-656564454649
  1373  filesystem: ext4
  1374  size: 1M
  1375  content:
  1376    - source: foo
  1377      target: bar
  1378  `
  1379  	fsMixed := `
  1380  type: 21686148-6449-6E6F-744E-656564454649
  1381  filesystem: ext4
  1382  size: 1M
  1383  content:
  1384    - source: foo
  1385      target: bar
  1386    - image: foo.img
  1387  `
  1388  	fsMissing := `
  1389  type: 21686148-6449-6E6F-744E-656564454649
  1390  filesystem: ext4
  1391  size: 1M
  1392  content:
  1393    - source: foo
  1394  `
  1395  	sourceEmpty := `
  1396  type: 21686148-6449-6E6F-744E-656564454649
  1397  filesystem: ext4
  1398  size: 1M
  1399  content:
  1400    - source:
  1401      target: /
  1402  `
  1403  
  1404  	for i, tc := range []struct {
  1405  		s   *gadget.VolumeStructure
  1406  		v   *gadget.Volume
  1407  		err string
  1408  	}{
  1409  		{mustParseStructure(c, bareOnlyOk), nil, ""},
  1410  		{mustParseStructure(c, bareMixed), nil, `invalid content #1: cannot use non-image content for bare file system`},
  1411  		{mustParseStructure(c, bareMissing), nil, `invalid content #0: missing image file name`},
  1412  		{mustParseStructure(c, fsOk), nil, ""},
  1413  		{mustParseStructure(c, fsMixed), nil, `invalid content #1: cannot use image content for non-bare file system`},
  1414  		{mustParseStructure(c, fsMissing), nil, `invalid content #0: missing target`},
  1415  		{mustParseStructure(c, sourceEmpty), nil, `invalid content #0: missing source`},
  1416  	} {
  1417  		c.Logf("tc: %v %+v", i, tc.s)
  1418  
  1419  		err := gadget.ValidateVolumeStructure(tc.s, &gadget.Volume{})
  1420  		if tc.err != "" {
  1421  			c.Check(err, ErrorMatches, tc.err)
  1422  		} else {
  1423  			c.Check(err, IsNil)
  1424  		}
  1425  	}
  1426  }
  1427  
  1428  func (s *gadgetYamlTestSuite) TestValidateStructureAndContentRelativeOffset(c *C) {
  1429  	gadgetYamlHeader := `
  1430  volumes:
  1431    pc:
  1432      bootloader: grub
  1433      structure:
  1434        - name: my-name-is
  1435          type: mbr
  1436          size: 440
  1437          content:
  1438            - image: pc-boot.img`
  1439  
  1440  	gadgetYamlBadStructureName := gadgetYamlHeader + `
  1441        - name: other-name
  1442          type: DA,21686148-6449-6E6F-744E-656564454649
  1443          size: 1M
  1444          offset: 1M
  1445          offset-write: bad-name+92
  1446          content:
  1447            - image: pc-core.img
  1448  `
  1449  	gadgetYamlBadContentName := gadgetYamlHeader + `
  1450        - name: other-name
  1451          type: DA,21686148-6449-6E6F-744E-656564454649
  1452          size: 1M
  1453          offset: 1M
  1454          offset-write: my-name-is+92
  1455          content:
  1456            - image: pc-core.img
  1457              offset-write: bad-name+123
  1458  `
  1459  
  1460  	err := ioutil.WriteFile(s.gadgetYamlPath, []byte(gadgetYamlBadStructureName), 0644)
  1461  	c.Assert(err, IsNil)
  1462  
  1463  	_, err = gadget.ReadInfo(s.dir, nil)
  1464  	c.Check(err, ErrorMatches, `invalid volume "pc": structure #1 \("other-name"\) refers to an unknown structure "bad-name"`)
  1465  
  1466  	err = ioutil.WriteFile(s.gadgetYamlPath, []byte(gadgetYamlBadContentName), 0644)
  1467  	c.Assert(err, IsNil)
  1468  
  1469  	_, err = gadget.ReadInfo(s.dir, nil)
  1470  	c.Check(err, ErrorMatches, `invalid volume "pc": structure #1 \("other-name"\), content #0 \("pc-core.img"\) refers to an unknown structure "bad-name"`)
  1471  
  1472  }
  1473  
  1474  func (s *gadgetYamlTestSuite) TestValidateStructureUpdatePreserveOnlyForFs(c *C) {
  1475  	gv := &gadget.Volume{}
  1476  
  1477  	err := gadget.ValidateVolumeStructure(&gadget.VolumeStructure{
  1478  		Type:   "bare",
  1479  		Update: gadget.VolumeUpdate{Preserve: []string{"foo"}},
  1480  		Size:   512,
  1481  	}, gv)
  1482  	c.Check(err, ErrorMatches, "preserving files during update is not supported for non-filesystem structures")
  1483  
  1484  	err = gadget.ValidateVolumeStructure(&gadget.VolumeStructure{
  1485  		Type:   "21686148-6449-6E6F-744E-656564454649",
  1486  		Update: gadget.VolumeUpdate{Preserve: []string{"foo"}},
  1487  		Size:   512,
  1488  	}, gv)
  1489  	c.Check(err, ErrorMatches, "preserving files during update is not supported for non-filesystem structures")
  1490  
  1491  	err = gadget.ValidateVolumeStructure(&gadget.VolumeStructure{
  1492  		Type:       "21686148-6449-6E6F-744E-656564454649",
  1493  		Filesystem: "vfat",
  1494  		Update:     gadget.VolumeUpdate{Preserve: []string{"foo"}},
  1495  		Size:       512,
  1496  	}, gv)
  1497  	c.Check(err, IsNil)
  1498  }
  1499  
  1500  func (s *gadgetYamlTestSuite) TestValidateStructureUpdatePreserveDuplicates(c *C) {
  1501  	gv := &gadget.Volume{}
  1502  
  1503  	err := gadget.ValidateVolumeStructure(&gadget.VolumeStructure{
  1504  		Type:       "21686148-6449-6E6F-744E-656564454649",
  1505  		Filesystem: "vfat",
  1506  		Update:     gadget.VolumeUpdate{Edition: 1, Preserve: []string{"foo", "bar"}},
  1507  		Size:       512,
  1508  	}, gv)
  1509  	c.Check(err, IsNil)
  1510  
  1511  	err = gadget.ValidateVolumeStructure(&gadget.VolumeStructure{
  1512  		Type:       "21686148-6449-6E6F-744E-656564454649",
  1513  		Filesystem: "vfat",
  1514  		Update:     gadget.VolumeUpdate{Edition: 1, Preserve: []string{"foo", "bar", "foo"}},
  1515  		Size:       512,
  1516  	}, gv)
  1517  	c.Check(err, ErrorMatches, `duplicate "preserve" entry "foo"`)
  1518  }
  1519  
  1520  func (s *gadgetYamlTestSuite) TestValidateStructureSizeRequired(c *C) {
  1521  
  1522  	gv := &gadget.Volume{}
  1523  
  1524  	err := gadget.ValidateVolumeStructure(&gadget.VolumeStructure{
  1525  		Type:   "bare",
  1526  		Update: gadget.VolumeUpdate{Preserve: []string{"foo"}},
  1527  	}, gv)
  1528  	c.Check(err, ErrorMatches, "missing size")
  1529  
  1530  	err = gadget.ValidateVolumeStructure(&gadget.VolumeStructure{
  1531  		Type:       "21686148-6449-6E6F-744E-656564454649",
  1532  		Filesystem: "vfat",
  1533  		Update:     gadget.VolumeUpdate{Preserve: []string{"foo"}},
  1534  	}, gv)
  1535  	c.Check(err, ErrorMatches, "missing size")
  1536  
  1537  	err = gadget.ValidateVolumeStructure(&gadget.VolumeStructure{
  1538  		Type:       "21686148-6449-6E6F-744E-656564454649",
  1539  		Filesystem: "vfat",
  1540  		Size:       mustParseGadgetSize(c, "123M"),
  1541  		Update:     gadget.VolumeUpdate{Preserve: []string{"foo"}},
  1542  	}, gv)
  1543  	c.Check(err, IsNil)
  1544  }
  1545  
  1546  func (s *gadgetYamlTestSuite) TestValidateLayoutOverlapPreceding(c *C) {
  1547  	overlappingGadgetYaml := `
  1548  volumes:
  1549    pc:
  1550      bootloader: grub
  1551      structure:
  1552        - name: mbr
  1553          type: mbr
  1554          size: 440
  1555          content:
  1556            - image: pc-boot.img
  1557        - name: other-name
  1558          type: DA,21686148-6449-6E6F-744E-656564454649
  1559          size: 1M
  1560          offset: 200
  1561          content:
  1562            - image: pc-core.img
  1563  `
  1564  	err := ioutil.WriteFile(s.gadgetYamlPath, []byte(overlappingGadgetYaml), 0644)
  1565  	c.Assert(err, IsNil)
  1566  
  1567  	_, err = gadget.ReadInfo(s.dir, nil)
  1568  	c.Check(err, ErrorMatches, `invalid volume "pc": structure #1 \("other-name"\) overlaps with the preceding structure #0 \("mbr"\)`)
  1569  }
  1570  
  1571  func (s *gadgetYamlTestSuite) TestValidateLayoutOverlapOutOfOrder(c *C) {
  1572  	outOfOrderGadgetYaml := `
  1573  volumes:
  1574    pc:
  1575      bootloader: grub
  1576      structure:
  1577        - name: overlaps-with-foo
  1578          type: DA,21686148-6449-6E6F-744E-656564454649
  1579          size: 1M
  1580          offset: 200
  1581          content:
  1582            - image: pc-core.img
  1583        - name: foo
  1584          type: DA,21686148-6449-6E6F-744E-656564454648
  1585          size: 1M
  1586          offset: 100
  1587          filesystem: vfat
  1588  `
  1589  	err := ioutil.WriteFile(s.gadgetYamlPath, []byte(outOfOrderGadgetYaml), 0644)
  1590  	c.Assert(err, IsNil)
  1591  
  1592  	_, err = gadget.ReadInfo(s.dir, nil)
  1593  	c.Check(err, ErrorMatches, `invalid volume "pc": structure #0 \("overlaps-with-foo"\) overlaps with the preceding structure #1 \("foo"\)`)
  1594  }
  1595  
  1596  func (s *gadgetYamlTestSuite) TestValidateCrossStructureMBRFixedOffset(c *C) {
  1597  	gadgetYaml := `
  1598  volumes:
  1599    pc:
  1600      bootloader: grub
  1601      structure:
  1602        - name: other-name
  1603          type: DA,21686148-6449-6E6F-744E-656564454649
  1604          size: 1M
  1605          offset: 500
  1606          content:
  1607            - image: pc-core.img
  1608        - name: mbr
  1609          type: mbr
  1610          size: 440
  1611          offset: 0
  1612          content:
  1613            - image: pc-boot.img
  1614  `
  1615  	err := ioutil.WriteFile(s.gadgetYamlPath, []byte(gadgetYaml), 0644)
  1616  	c.Assert(err, IsNil)
  1617  
  1618  	_, err = gadget.ReadInfo(s.dir, nil)
  1619  	c.Check(err, IsNil)
  1620  }
  1621  
  1622  func (s *gadgetYamlTestSuite) TestValidateCrossStructureMBRDefaultOffsetInvalid(c *C) {
  1623  	gadgetYaml := `
  1624  volumes:
  1625    pc:
  1626      bootloader: grub
  1627      structure:
  1628        - name: other-name
  1629          type: DA,21686148-6449-6E6F-744E-656564454649
  1630          size: 1M
  1631          offset: 500
  1632          content:
  1633            - image: pc-core.img
  1634        - name: mbr
  1635          type: mbr
  1636          size: 440
  1637          content:
  1638            - image: pc-boot.img
  1639  `
  1640  	err := ioutil.WriteFile(s.gadgetYamlPath, []byte(gadgetYaml), 0644)
  1641  	c.Assert(err, IsNil)
  1642  
  1643  	_, err = gadget.ReadInfo(s.dir, nil)
  1644  	c.Check(err, ErrorMatches, `invalid volume "pc": structure #1 \("mbr"\) has "mbr" role and must start at offset 0`)
  1645  }
  1646  
  1647  func (s *gadgetYamlTestSuite) TestReadInfoAndValidateConsistencyWithoutModelCharacteristics(c *C) {
  1648  	for i, tc := range []struct {
  1649  		role  string
  1650  		label string
  1651  		err   string
  1652  	}{
  1653  		// when characteristics are nil, the system-seed role and ubuntu-data label on the
  1654  		// system-data structure should be consistent
  1655  		{"system-seed", "writable", `.* must have an implicit label or "ubuntu-data", not "writable"`},
  1656  		{"", "ubuntu-data", `.* must have an implicit label or "writable", not "ubuntu-data"`},
  1657  	} {
  1658  		c.Logf("tc: %v %v %v", i, tc.role, tc.label)
  1659  		b := &bytes.Buffer{}
  1660  
  1661  		fmt.Fprintf(b, `
  1662  volumes:
  1663    pc:
  1664      bootloader: grub
  1665      schema: mbr
  1666      structure:`)
  1667  
  1668  		if tc.role == "system-seed" {
  1669  			fmt.Fprintf(b, `
  1670        - name: Recovery
  1671          size: 10M
  1672          type: 83
  1673          role: system-seed`)
  1674  		}
  1675  
  1676  		fmt.Fprintf(b, `
  1677        - name: Data
  1678          size: 10M
  1679          type: 83
  1680          role: system-data
  1681          filesystem-label: %s`, tc.label)
  1682  
  1683  		err := ioutil.WriteFile(s.gadgetYamlPath, b.Bytes(), 0644)
  1684  		c.Assert(err, IsNil)
  1685  
  1686  		_, err = gadget.ReadInfoAndValidate(s.dir, nil, nil)
  1687  		c.Check(err, ErrorMatches, tc.err)
  1688  	}
  1689  }
  1690  
  1691  func (s *gadgetYamlTestSuite) TestReadInfoAndValidateConsistencyWithModelCharacteristics(c *C) {
  1692  	bloader := `
  1693  volumes:
  1694    pc:
  1695      bootloader: grub
  1696      schema: mbr
  1697      structure:`
  1698  
  1699  	err := ioutil.WriteFile(s.gadgetYamlPath, []byte(bloader), 0644)
  1700  	c.Assert(err, IsNil)
  1701  	mod := &modelCharateristics{
  1702  		systemSeed: true,
  1703  	}
  1704  
  1705  	_, err = gadget.ReadInfoAndValidate(s.dir, mod, nil)
  1706  	c.Assert(err, ErrorMatches, "model requires system-seed partition, but no system-seed or system-data partition found")
  1707  }
  1708  
  1709  func (s *gadgetYamlTestSuite) TestGadgetReadInfoVsFromMeta(c *C) {
  1710  	err := ioutil.WriteFile(s.gadgetYamlPath, gadgetYamlPC, 0644)
  1711  	c.Assert(err, IsNil)
  1712  
  1713  	mod := &modelCharateristics{
  1714  		classic: false,
  1715  	}
  1716  
  1717  	giRead, err := gadget.ReadInfo(s.dir, mod)
  1718  	c.Check(err, IsNil)
  1719  
  1720  	giMeta, err := gadget.InfoFromGadgetYaml(gadgetYamlPC, mod)
  1721  	c.Check(err, IsNil)
  1722  
  1723  	c.Assert(giRead, DeepEquals, giMeta)
  1724  }
  1725  
  1726  func (s *gadgetYamlTestSuite) TestReadInfoValidatesEmptySource(c *C) {
  1727  	var gadgetYamlContent = `
  1728  volumes:
  1729    missing:
  1730      bootloader: grub
  1731      structure:
  1732        - name: missing-content-source
  1733          type: DA,21686148-6449-6E6F-744E-656564454649
  1734          size: 1M
  1735          filesystem: ext4
  1736          content:
  1737            - source: foo
  1738              target: /
  1739            - source:
  1740              target: /
  1741  
  1742  `
  1743  	makeSizedFile(c, filepath.Join(s.dir, "meta/gadget.yaml"), 0, []byte(gadgetYamlContent))
  1744  
  1745  	_, err := gadget.ReadInfo(s.dir, nil)
  1746  	c.Assert(err, ErrorMatches, `invalid volume "missing": invalid structure #0 \("missing-content-source"\): invalid content #1: missing source`)
  1747  }
  1748  
  1749  func (s *gadgetYamlTestSuite) TestGadgetImplicitSchema(c *C) {
  1750  	var minimal = []byte(`
  1751  volumes:
  1752     minimal:
  1753       bootloader: grub
  1754  `)
  1755  
  1756  	tests := map[string][]byte{
  1757  		"minimal": minimal,
  1758  		"pc":      gadgetYamlPC,
  1759  	}
  1760  
  1761  	for volName, yaml := range tests {
  1762  		giMeta, err := gadget.InfoFromGadgetYaml(yaml, nil)
  1763  		c.Assert(err, IsNil)
  1764  
  1765  		vol := giMeta.Volumes[volName]
  1766  		c.Check(vol.Schema, Equals, "gpt")
  1767  	}
  1768  }
  1769  
  1770  func (s *gadgetYamlTestSuite) TestGadgetImplicitRoleMBR(c *C) {
  1771  	var minimal = []byte(`
  1772  volumes:
  1773     minimal:
  1774       bootloader: grub
  1775       structure:
  1776         - name: mbr
  1777           type: mbr
  1778           size: 440
  1779  `)
  1780  
  1781  	tests := map[string][]byte{
  1782  		"minimal": minimal,
  1783  		"pc":      gadgetYamlPC,
  1784  	}
  1785  
  1786  	constr := gadget.LayoutConstraints{NonMBRStartOffset: 1 * quantity.OffsetMiB}
  1787  
  1788  	for volName, yaml := range tests {
  1789  		giMeta, err := gadget.InfoFromGadgetYaml(yaml, nil)
  1790  		c.Assert(err, IsNil)
  1791  
  1792  		vs := giMeta.Volumes[volName].Structure[0]
  1793  		c.Check(vs.Role, Equals, "mbr")
  1794  
  1795  		// also layout the volume and check that when laying out the MBR
  1796  		// structure it retains the role of MBR, as validated by IsRoleMBR
  1797  		ls, err := gadget.LayoutVolumePartially(giMeta.Volumes[volName], constr)
  1798  		c.Assert(err, IsNil)
  1799  		c.Check(gadget.IsRoleMBR(ls.LaidOutStructure[0]), Equals, true)
  1800  	}
  1801  }
  1802  
  1803  func (s *gadgetYamlTestSuite) TestGadgetImplicitRoleLegacySystemBoot(c *C) {
  1804  	minimal := []byte(`
  1805  volumes:
  1806     minimal:
  1807       bootloader: grub
  1808       structure:
  1809         - name: boot
  1810           filesystem-label: system-boot
  1811           type: EF,C12A7328-F81F-11D2-BA4B-00A0C93EC93B
  1812           size: 1G
  1813  `)
  1814  
  1815  	explicit := []byte(`
  1816  volumes:
  1817     explicit:
  1818       bootloader: grub
  1819       schema: mbr
  1820       structure:
  1821         - name: boot
  1822           filesystem-label: system-boot
  1823           role: bootselect
  1824           type: EF
  1825           size: 1G
  1826  `)
  1827  
  1828  	data := []byte(`
  1829  volumes:
  1830     data:
  1831       bootloader: grub
  1832       schema: mbr
  1833       structure:
  1834         - name: dat
  1835           filesystem-label: system-data
  1836           type: 83,0FC63DAF-8483-4772-8E79-3D69D8477DE4
  1837           size: 1G
  1838  `)
  1839  
  1840  	tests := []struct {
  1841  		name      string
  1842  		structure string
  1843  		yaml      []byte
  1844  		model     gadget.Model
  1845  		role      string
  1846  	}{
  1847  		{"pc", "EFI System", gadgetYamlPC, coreMod, gadget.SystemBoot},
  1848  		// XXX later {gadgetYamlUC20PC, uc20Mod},
  1849  		{"minimal", "boot", minimal, nil, ""},
  1850  		{"minimal", "boot", minimal, coreMod, gadget.SystemBoot},
  1851  		// XXX later {minimal, uc20Mod},
  1852  		{"explicit", "boot", explicit, coreMod, "bootselect"},
  1853  		{"data", "dat", data, coreMod, ""},
  1854  	}
  1855  
  1856  	for _, t := range tests {
  1857  		giMeta, err := gadget.InfoFromGadgetYaml(t.yaml, t.model)
  1858  		c.Assert(err, IsNil)
  1859  
  1860  		foundStruct := false
  1861  		vol := giMeta.Volumes[t.name]
  1862  		for _, vs := range vol.Structure {
  1863  			if vs.Name != t.structure {
  1864  				continue
  1865  			}
  1866  			foundStruct = true
  1867  			c.Check(vs.Role, Equals, t.role)
  1868  		}
  1869  		c.Check(foundStruct, Equals, true)
  1870  	}
  1871  }
  1872  
  1873  func (s *gadgetYamlTestSuite) TestGadgetImplicitFSLabelUC16(c *C) {
  1874  	minimal := []byte(`
  1875  volumes:
  1876     minimal:
  1877       bootloader: grub
  1878       structure:
  1879         - name: dat
  1880           role: system-data
  1881           type: 83,0FC63DAF-8483-4772-8E79-3D69D8477DE4
  1882           size: 1G
  1883  `)
  1884  
  1885  	explicit := []byte(`
  1886  volumes:
  1887     explicit:
  1888       bootloader: grub
  1889       structure:
  1890         - name: dat
  1891           filesystem-label: writable
  1892           role: system-data
  1893           type: 83,0FC63DAF-8483-4772-8E79-3D69D8477DE4
  1894           size: 1G
  1895  `)
  1896  	tests := []struct {
  1897  		name      string
  1898  		structure string
  1899  		yaml      []byte
  1900  		fsLabel   string
  1901  	}{
  1902  		{"minimal", "dat", minimal, "writable"},
  1903  		{"explicit", "dat", explicit, "writable"},
  1904  	}
  1905  
  1906  	for _, t := range tests {
  1907  		giMeta, err := gadget.InfoFromGadgetYaml(t.yaml, coreMod)
  1908  		c.Assert(err, IsNil)
  1909  
  1910  		foundStruct := false
  1911  		vol := giMeta.Volumes[t.name]
  1912  		for _, vs := range vol.Structure {
  1913  			if vs.Name != t.structure {
  1914  				continue
  1915  			}
  1916  			foundStruct = true
  1917  			c.Check(vs.Label, Equals, t.fsLabel)
  1918  		}
  1919  		c.Check(foundStruct, Equals, true)
  1920  	}
  1921  }
  1922  
  1923  func (s *gadgetYamlTestSuite) TestGadgetImplicitFSLabelUC20(c *C) {
  1924  	minimal := []byte(`
  1925  volumes:
  1926     minimal:
  1927       bootloader: grub
  1928       structure:
  1929         - name: seed
  1930           role: system-seed
  1931           type: EF,C12A7328-F81F-11D2-BA4B-00A0C93EC93B
  1932           size: 1G
  1933         - name: boot
  1934           role: system-boot
  1935           type: 83,0FC63DAF-8483-4772-8E79-3D69D8477DE4
  1936           size: 500M
  1937         - name: dat
  1938           role: system-data
  1939           type: 83,0FC63DAF-8483-4772-8E79-3D69D8477DE4
  1940           size: 1G
  1941         - name: sav
  1942           role: system-save
  1943           type: 83,0FC63DAF-8483-4772-8E79-3D69D8477DE4
  1944           size: 1G
  1945  `)
  1946  
  1947  	tests := []struct {
  1948  		name      string
  1949  		structure string
  1950  		yaml      []byte
  1951  		fsLabel   string
  1952  	}{
  1953  		{"minimal", "seed", minimal, "ubuntu-seed"},
  1954  		{"minimal", "boot", minimal, "ubuntu-boot"},
  1955  		{"minimal", "dat", minimal, "ubuntu-data"},
  1956  		{"minimal", "sav", minimal, "ubuntu-save"},
  1957  		{"pc", "ubuntu-seed", gadgetYamlUC20PC, "ubuntu-seed"},
  1958  		{"pc", "ubuntu-boot", gadgetYamlUC20PC, "ubuntu-boot"},
  1959  		{"pc", "ubuntu-data", gadgetYamlUC20PC, "ubuntu-data"},
  1960  		{"pc", "ubuntu-save", gadgetYamlUC20PC, "ubuntu-save"},
  1961  	}
  1962  
  1963  	for _, t := range tests {
  1964  		giMeta, err := gadget.InfoFromGadgetYaml(t.yaml, uc20Mod)
  1965  		c.Assert(err, IsNil)
  1966  
  1967  		foundStruct := false
  1968  		vol := giMeta.Volumes[t.name]
  1969  		for _, vs := range vol.Structure {
  1970  			if vs.Name != t.structure {
  1971  				continue
  1972  			}
  1973  			foundStruct = true
  1974  			c.Check(vs.Label, Equals, t.fsLabel)
  1975  		}
  1976  		c.Check(foundStruct, Equals, true)
  1977  	}
  1978  }
  1979  
  1980  func (s *validateGadgetTestSuite) TestGadgetImplicitFSLabelDuplicate(c *C) {
  1981  	const pcYaml = `
  1982  volumes:
  1983    pc:
  1984      bootloader: grub
  1985      structure:
  1986        - name: mbr
  1987          type: mbr
  1988          size: 440
  1989          content:
  1990            - image: pc-boot.img
  1991        - name: BIOS Boot
  1992          type: DA,21686148-6449-6E6F-744E-656564454649
  1993          size: 1M
  1994          offset: 1M
  1995          offset-write: mbr+92
  1996        - name: EFI System
  1997          type: EF,C12A7328-F81F-11D2-BA4B-00A0C93EC93B
  1998          filesystem: vfat
  1999          filesystem-label: system-boot
  2000          size: 50M
  2001        - name: data
  2002          type: 83,0FC63DAF-8483-4772-8E79-3D69D8477DE4
  2003          size: 1G
  2004          role: system-data
  2005  `
  2006  
  2007  	tests := []struct {
  2008  		yaml  string
  2009  		label string
  2010  		mod   gadget.Model
  2011  		err   string
  2012  	}{
  2013  		{pcYaml, "foo", coreMod, ""},
  2014  		{pcYaml, "writable", coreMod, `.*: filesystem label "writable" is implied by system-data role but was already set elsewhere`},
  2015  		{pcYaml, "writable", nil, ""},
  2016  		{string(gadgetYamlUC20PC), "ubuntu-data", nil, ""},
  2017  		{string(gadgetYamlUC20PC), "ubuntu-data", uc20Mod, `.*: filesystem label "ubuntu-data" is implied by system-data role but was already set elsewhere`},
  2018  		{string(gadgetYamlUC20PC), "ubuntu-save", uc20Mod, `.*: filesystem label "ubuntu-save" is implied by system-save role but was already set elsewhere`},
  2019  		{string(gadgetYamlUC20PC), "ubuntu-seed", uc20Mod, `.*: filesystem label "ubuntu-seed" is implied by system-seed role but was already set elsewhere`},
  2020  		{string(gadgetYamlUC20PC), "ubuntu-boot", uc20Mod, `.*: filesystem label "ubuntu-boot" is implied by system-boot role but was already set elsewhere`},
  2021  	}
  2022  
  2023  	for _, t := range tests {
  2024  		dup := fmt.Sprintf(`
  2025        - name: dup
  2026          type: 83,0FC63DAF-8483-4772-8E79-3D69D8477DE4
  2027          filesystem-label: %s
  2028          size: 1M
  2029  
  2030  `, t.label)
  2031  
  2032  		yaml := strings.TrimSpace(t.yaml) + dup
  2033  		_, err := gadget.InfoFromGadgetYaml([]byte(yaml), t.mod)
  2034  		if t.err == "" {
  2035  			c.Check(err, IsNil)
  2036  		} else {
  2037  			c.Check(err, ErrorMatches, t.err)
  2038  		}
  2039  	}
  2040  }
  2041  
  2042  func (s *gadgetYamlTestSuite) TestGadgetFromMetaEmpty(c *C) {
  2043  	// this is ok for classic
  2044  	giClassic, err := gadget.InfoFromGadgetYaml([]byte(""), classicMod)
  2045  	c.Check(err, IsNil)
  2046  	c.Assert(giClassic, DeepEquals, &gadget.Info{})
  2047  
  2048  	// but not so much for core
  2049  	giCore, err := gadget.InfoFromGadgetYaml([]byte(""), coreMod)
  2050  	c.Check(err, ErrorMatches, "bootloader not declared in any volume")
  2051  	c.Assert(giCore, IsNil)
  2052  }
  2053  
  2054  func (s *gadgetYamlTestSuite) TestLaidOutSystemVolumeFromGadgetMultiVolume(c *C) {
  2055  	err := ioutil.WriteFile(s.gadgetYamlPath, mockMultiVolumeUC20GadgetYaml, 0644)
  2056  	c.Assert(err, IsNil)
  2057  
  2058  	lv, err := gadget.LaidOutSystemVolumeFromGadget(s.dir, "", uc20Mod)
  2059  	c.Assert(err, IsNil)
  2060  
  2061  	c.Assert(lv.Volume.Bootloader, Equals, "u-boot")
  2062  	// ubuntu-seed, ubuntu-save, ubuntu-boot and ubuntu-data
  2063  	c.Assert(lv.LaidOutStructure, HasLen, 4)
  2064  }
  2065  
  2066  func (s *gadgetYamlTestSuite) TestLaidOutSystemVolumeFromGadgetHappy(c *C) {
  2067  	err := ioutil.WriteFile(s.gadgetYamlPath, gadgetYamlPC, 0644)
  2068  	c.Assert(err, IsNil)
  2069  	for _, fn := range []string{"pc-boot.img", "pc-core.img"} {
  2070  		err = ioutil.WriteFile(filepath.Join(s.dir, fn), nil, 0644)
  2071  		c.Assert(err, IsNil)
  2072  	}
  2073  
  2074  	lv, err := gadget.LaidOutSystemVolumeFromGadget(s.dir, "", coreMod)
  2075  	c.Assert(err, IsNil)
  2076  	c.Assert(lv.Volume.Bootloader, Equals, "grub")
  2077  	// mbr, bios-boot, efi-system
  2078  	c.Assert(lv.LaidOutStructure, HasLen, 3)
  2079  }
  2080  
  2081  func (s *gadgetYamlTestSuite) TestLaidOutSystemVolumeFromGadgetNeedsModel(c *C) {
  2082  	err := ioutil.WriteFile(s.gadgetYamlPath, gadgetYamlPC, 0644)
  2083  	c.Assert(err, IsNil)
  2084  	for _, fn := range []string{"pc-boot.img", "pc-core.img"} {
  2085  		err = ioutil.WriteFile(filepath.Join(s.dir, fn), nil, 0644)
  2086  		c.Assert(err, IsNil)
  2087  	}
  2088  
  2089  	// need the model in order to lay out system volumes due to the verification
  2090  	// and other metadata we use with the gadget
  2091  	_, err = gadget.LaidOutSystemVolumeFromGadget(s.dir, "", nil)
  2092  	c.Assert(err, ErrorMatches, "internal error: must have model to lay out system volumes from a gadget")
  2093  }
  2094  
  2095  func (s *gadgetYamlTestSuite) TestLaidOutSystemVolumeFromGadgetUC20Happy(c *C) {
  2096  	err := ioutil.WriteFile(s.gadgetYamlPath, gadgetYamlUC20PC, 0644)
  2097  	c.Assert(err, IsNil)
  2098  	for _, fn := range []string{"pc-boot.img", "pc-core.img"} {
  2099  		err = ioutil.WriteFile(filepath.Join(s.dir, fn), nil, 0644)
  2100  		c.Assert(err, IsNil)
  2101  	}
  2102  
  2103  	lv, err := gadget.LaidOutSystemVolumeFromGadget(s.dir, "", uc20Mod)
  2104  	c.Assert(err, IsNil)
  2105  	c.Assert(lv.Volume.Bootloader, Equals, "grub")
  2106  	// mbr, bios-boot, ubuntu-seed, ubuntu-save, ubuntu-boot, and ubuntu-data
  2107  	c.Assert(lv.LaidOutStructure, HasLen, 6)
  2108  }
  2109  
  2110  func (s *gadgetYamlTestSuite) TestStructureBareFilesystem(c *C) {
  2111  	bareType := `
  2112  type: bare
  2113  size: 1M`
  2114  	mbr := `
  2115  role: mbr
  2116  size: 446`
  2117  	mbrLegacy := `
  2118  type: mbr
  2119  size: 446`
  2120  	fs := `
  2121  type: 21686148-6449-6E6F-744E-656564454649
  2122  filesystem: vfat`
  2123  	rawFsNoneExplicit := `
  2124  type: 21686148-6449-6E6F-744E-656564454649
  2125  filesystem: none
  2126  size: 1M`
  2127  	raw := `
  2128  type: 21686148-6449-6E6F-744E-656564454649
  2129  size: 1M`
  2130  	for i, tc := range []struct {
  2131  		s           *gadget.VolumeStructure
  2132  		hasFs       bool
  2133  		isPartition bool
  2134  	}{
  2135  		{mustParseStructure(c, bareType), false, false},
  2136  		{mustParseStructure(c, mbr), false, false},
  2137  		{mustParseStructure(c, mbrLegacy), false, false},
  2138  		{mustParseStructure(c, fs), true, true},
  2139  		{mustParseStructure(c, rawFsNoneExplicit), false, true},
  2140  		{mustParseStructure(c, raw), false, true},
  2141  	} {
  2142  		c.Logf("tc: %v %+v", i, tc.s)
  2143  		c.Check(tc.s.HasFilesystem(), Equals, tc.hasFs)
  2144  		c.Check(tc.s.IsPartition(), Equals, tc.isPartition)
  2145  	}
  2146  }
  2147  
  2148  var mockSnapYaml = `name: pc
  2149  type: gadget
  2150  version: 1.0
  2151  `
  2152  
  2153  func (s *gadgetYamlTestSuite) TestReadGadgetYamlFromSnapFileMissing(c *C) {
  2154  	snapPath := snaptest.MakeTestSnapWithFiles(c, string(mockSnapYaml), nil)
  2155  	snapf, err := snapfile.Open(snapPath)
  2156  	c.Assert(err, IsNil)
  2157  
  2158  	// if model is nil, we allow a missing gadget.yaml
  2159  	_, err = gadget.ReadInfoFromSnapFile(snapf, nil)
  2160  	c.Assert(err, IsNil)
  2161  
  2162  	_, err = gadget.ReadInfoFromSnapFile(snapf, &modelCharateristics{})
  2163  	c.Assert(err, ErrorMatches, ".*meta/gadget.yaml: no such file or directory")
  2164  }
  2165  
  2166  var minimalMockGadgetYaml = `
  2167  volumes:
  2168   pc:
  2169    bootloader: grub
  2170  `
  2171  
  2172  func (s *gadgetYamlTestSuite) TestReadGadgetYamlFromSnapFileValid(c *C) {
  2173  	snapPath := snaptest.MakeTestSnapWithFiles(c, mockSnapYaml, [][]string{
  2174  		{"meta/gadget.yaml", string(minimalMockGadgetYaml)},
  2175  	})
  2176  	snapf, err := snapfile.Open(snapPath)
  2177  	c.Assert(err, IsNil)
  2178  
  2179  	ginfo, err := gadget.ReadInfoFromSnapFile(snapf, nil)
  2180  	c.Assert(err, IsNil)
  2181  	c.Assert(ginfo, DeepEquals, &gadget.Info{
  2182  		Volumes: map[string]*gadget.Volume{
  2183  			"pc": {
  2184  				Bootloader: "grub",
  2185  				Schema:     "gpt",
  2186  			},
  2187  		},
  2188  	})
  2189  }
  2190  
  2191  func (s *gadgetYamlTestSuite) TestReadGadgetYamlFromSnapFileNoVolumesSystemSeed(c *C) {
  2192  	snapPath := snaptest.MakeTestSnapWithFiles(c, mockSnapYaml, [][]string{
  2193  		{"meta/gadget.yaml", string(minimalMockGadgetYaml)},
  2194  	})
  2195  	snapf, err := snapfile.Open(snapPath)
  2196  	c.Assert(err, IsNil)
  2197  
  2198  	_, err = gadget.ReadInfoFromSnapFile(snapf, &modelCharateristics{systemSeed: true})
  2199  	c.Check(err, ErrorMatches, "model requires system-seed partition, but no system-seed or system-data partition found")
  2200  }
  2201  
  2202  type gadgetCompatibilityTestSuite struct{}
  2203  
  2204  var _ = Suite(&gadgetCompatibilityTestSuite{})
  2205  
  2206  func (s *gadgetCompatibilityTestSuite) TestGadgetIsCompatibleSelf(c *C) {
  2207  	giPC1, err := gadget.InfoFromGadgetYaml(gadgetYamlPC, coreMod)
  2208  	c.Assert(err, IsNil)
  2209  	giPC2, err := gadget.InfoFromGadgetYaml(gadgetYamlPC, coreMod)
  2210  	c.Assert(err, IsNil)
  2211  
  2212  	err = gadget.IsCompatible(giPC1, giPC2)
  2213  	c.Check(err, IsNil)
  2214  }
  2215  
  2216  func (s *gadgetCompatibilityTestSuite) TestGadgetIsCompatibleBadVolume(c *C) {
  2217  	var mockYaml = []byte(`
  2218  volumes:
  2219    volumename:
  2220      schema: mbr
  2221      bootloader: u-boot
  2222      id: 0C
  2223  `)
  2224  
  2225  	var mockOtherYaml = []byte(`
  2226  volumes:
  2227    volumename-other:
  2228      schema: mbr
  2229      bootloader: u-boot
  2230      id: 0C
  2231  `)
  2232  	var mockManyYaml = []byte(`
  2233  volumes:
  2234    volumename:
  2235      schema: mbr
  2236      bootloader: u-boot
  2237      id: 0C
  2238    volumename-many:
  2239      schema: mbr
  2240      id: 0C
  2241  `)
  2242  	var mockBadIDYaml = []byte(`
  2243  volumes:
  2244    volumename:
  2245      schema: mbr
  2246      bootloader: u-boot
  2247      id: 0D
  2248  `)
  2249  	var mockSchemaYaml = []byte(`
  2250  volumes:
  2251    volumename:
  2252      schema: gpt
  2253      bootloader: u-boot
  2254      id: 0C
  2255  `)
  2256  	var mockBootloaderYaml = []byte(`
  2257  volumes:
  2258    volumename:
  2259      schema: mbr
  2260      bootloader: grub
  2261      id: 0C
  2262  `)
  2263  	var mockNewStructuresYaml = []byte(`
  2264  volumes:
  2265    volumename:
  2266      schema: mbr
  2267      bootloader: u-boot
  2268      id: 0C
  2269      structure:
  2270        - name: bad-size
  2271          size: 99999
  2272          type: 0C
  2273  `)
  2274  	for _, tc := range []struct {
  2275  		gadgetYaml []byte
  2276  		err        string
  2277  	}{
  2278  		{mockOtherYaml, `cannot find entry for volume "volumename" in updated gadget info`},
  2279  		{mockManyYaml, "gadgets with multiple volumes are unsupported"},
  2280  		{mockNewStructuresYaml, `incompatible layout change: incompatible change in the number of structures from 0 to 1`},
  2281  		{mockBadIDYaml, "incompatible layout change: incompatible ID change from 0C to 0D"},
  2282  		{mockSchemaYaml, "incompatible layout change: incompatible schema change from mbr to gpt"},
  2283  		{mockBootloaderYaml, "incompatible layout change: incompatible bootloader change from u-boot to grub"},
  2284  	} {
  2285  		c.Logf("trying: %v\n", string(tc.gadgetYaml))
  2286  		gi, err := gadget.InfoFromGadgetYaml(mockYaml, coreMod)
  2287  		c.Assert(err, IsNil)
  2288  		giNew, err := gadget.InfoFromGadgetYaml(tc.gadgetYaml, coreMod)
  2289  		c.Assert(err, IsNil)
  2290  		err = gadget.IsCompatible(gi, giNew)
  2291  		if tc.err == "" {
  2292  			c.Check(err, IsNil)
  2293  		} else {
  2294  			c.Check(err, ErrorMatches, tc.err)
  2295  		}
  2296  	}
  2297  }
  2298  
  2299  func (s *gadgetCompatibilityTestSuite) TestGadgetIsCompatibleBadStructure(c *C) {
  2300  	var baseYaml = `
  2301  volumes:
  2302    volumename:
  2303      schema: gpt
  2304      bootloader: grub
  2305      id: 0C
  2306      structure:`
  2307  	var mockYaml = baseYaml + `
  2308        - name: legit
  2309          size: 2M
  2310          type: 00000000-0000-0000-0000-0000deadbeef
  2311          filesystem: ext4
  2312          filesystem-label: fs-legit
  2313  `
  2314  	var mockBadStructureTypeYaml = baseYaml + `
  2315        - name: legit
  2316          size: 2M
  2317          type: 00000000-0000-0000-0000-0000deadcafe
  2318          filesystem: ext4
  2319          filesystem-label: fs-legit
  2320  `
  2321  	var mockBadFsYaml = baseYaml + `
  2322        - name: legit
  2323          size: 2M
  2324          type: 00000000-0000-0000-0000-0000deadbeef
  2325          filesystem: vfat
  2326          filesystem-label: fs-legit
  2327  `
  2328  	var mockBadOffsetYaml = baseYaml + `
  2329        - name: legit
  2330          size: 2M
  2331          type: 00000000-0000-0000-0000-0000deadbeef
  2332          filesystem: ext4
  2333          offset: 1M
  2334          filesystem-label: fs-legit
  2335  `
  2336  	var mockBadLabelYaml = baseYaml + `
  2337        - name: legit
  2338          size: 2M
  2339          type: 00000000-0000-0000-0000-0000deadbeef
  2340          filesystem: ext4
  2341          filesystem-label: fs-non-legit
  2342  `
  2343  	var mockGPTBadNameYaml = baseYaml + `
  2344        - name: non-legit
  2345          size: 2M
  2346          type: 00000000-0000-0000-0000-0000deadbeef
  2347          filesystem: ext4
  2348          filesystem-label: fs-legit
  2349  `
  2350  
  2351  	for i, tc := range []struct {
  2352  		gadgetYaml string
  2353  		err        string
  2354  	}{
  2355  		{mockYaml, ``},
  2356  		{mockBadStructureTypeYaml, `incompatible layout change: incompatible structure #0 \("legit"\) change: cannot change structure type from "00000000-0000-0000-0000-0000deadbeef" to "00000000-0000-0000-0000-0000deadcafe"`},
  2357  		{mockBadFsYaml, `incompatible layout change: incompatible structure #0 \("legit"\) change: cannot change filesystem from "ext4" to "vfat"`},
  2358  		{mockBadOffsetYaml, `incompatible layout change: incompatible structure #0 \("legit"\) change: cannot change structure offset from unspecified to 1048576`},
  2359  		{mockBadLabelYaml, `incompatible layout change: incompatible structure #0 \("legit"\) change: cannot change filesystem label from "fs-legit" to "fs-non-legit"`},
  2360  		{mockGPTBadNameYaml, `incompatible layout change: incompatible structure #0 \("non-legit"\) change: cannot change structure name from "legit" to "non-legit"`},
  2361  	} {
  2362  		c.Logf("trying: %d %v\n", i, string(tc.gadgetYaml))
  2363  		gi, err := gadget.InfoFromGadgetYaml([]byte(mockYaml), coreMod)
  2364  		c.Assert(err, IsNil)
  2365  		giNew, err := gadget.InfoFromGadgetYaml([]byte(tc.gadgetYaml), coreMod)
  2366  		c.Assert(err, IsNil)
  2367  		err = gadget.IsCompatible(gi, giNew)
  2368  		if tc.err == "" {
  2369  			c.Check(err, IsNil)
  2370  		} else {
  2371  			c.Check(err, ErrorMatches, tc.err)
  2372  		}
  2373  
  2374  	}
  2375  }
  2376  
  2377  func (s *gadgetCompatibilityTestSuite) TestGadgetIsCompatibleStructureNameMBR(c *C) {
  2378  	var baseYaml = `
  2379  volumes:
  2380    volumename:
  2381      schema: mbr
  2382      bootloader: grub
  2383      id: 0C
  2384      structure:`
  2385  	var mockYaml = baseYaml + `
  2386        - name: legit
  2387          size: 2M
  2388          type: 0A
  2389  `
  2390  	var mockMBRNameOkYaml = baseYaml + `
  2391        - name: non-legit
  2392          size: 2M
  2393          type: 0A
  2394  `
  2395  
  2396  	gi, err := gadget.InfoFromGadgetYaml([]byte(mockYaml), coreMod)
  2397  	c.Assert(err, IsNil)
  2398  	giNew, err := gadget.InfoFromGadgetYaml([]byte(mockMBRNameOkYaml), coreMod)
  2399  	c.Assert(err, IsNil)
  2400  	err = gadget.IsCompatible(gi, giNew)
  2401  	c.Check(err, IsNil)
  2402  }