github.com/kubiko/snapd@v0.0.0-20201013125620-d4f3094d9ddf/gadget/gadget_test.go (about)

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