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