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