github.com/david-imola/snapd@v0.0.0-20210611180407-2de8ddeece6d/gadget/validate_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  	"os"
    26  	"path/filepath"
    27  	"regexp"
    28  	"strings"
    29  
    30  	. "gopkg.in/check.v1"
    31  
    32  	"github.com/snapcore/snapd/gadget"
    33  	"github.com/snapcore/snapd/gadget/quantity"
    34  	"github.com/snapcore/snapd/kernel"
    35  )
    36  
    37  type validateGadgetTestSuite struct {
    38  	dir string
    39  }
    40  
    41  var _ = Suite(&validateGadgetTestSuite{})
    42  
    43  func (s *validateGadgetTestSuite) SetUpTest(c *C) {
    44  	s.dir = c.MkDir()
    45  }
    46  
    47  func (s *validateGadgetTestSuite) TestRuleValidateStructureReservedLabels(c *C) {
    48  	for _, tc := range []struct {
    49  		role, label, err string
    50  		model            gadget.Model
    51  	}{
    52  		{label: "ubuntu-seed", err: `label "ubuntu-seed" is reserved`},
    53  		{label: "ubuntu-data", err: `label "ubuntu-data" is reserved`},
    54  		// ok to allow hybrid 20-ready devices
    55  		{label: "ubuntu-boot"},
    56  		{label: "ubuntu-save"},
    57  		// reserved only if seed present/expected
    58  		{label: "ubuntu-boot", err: `label "ubuntu-boot" is reserved`, model: uc20Mod},
    59  		{label: "ubuntu-save", err: `label "ubuntu-save" is reserved`, model: uc20Mod},
    60  		// these are ok
    61  		{role: "system-boot", label: "ubuntu-boot"},
    62  		{label: "random-ubuntu-label"},
    63  	} {
    64  		gi := &gadget.Info{
    65  			Volumes: map[string]*gadget.Volume{
    66  				"vol0": {
    67  					Structure: []gadget.VolumeStructure{{
    68  						Type:       "21686148-6449-6E6F-744E-656564454649",
    69  						Role:       tc.role,
    70  						Filesystem: "ext4",
    71  						Label:      tc.label,
    72  						Size:       10 * 1024,
    73  					}},
    74  				},
    75  			},
    76  		}
    77  		err := gadget.Validate(gi, tc.model, nil)
    78  		if tc.err == "" {
    79  			c.Check(err, IsNil)
    80  		} else {
    81  			c.Check(err, ErrorMatches, ".*: "+tc.err)
    82  		}
    83  	}
    84  
    85  }
    86  
    87  // rolesYaml produces gadget metadata with volumes with structure withs the given
    88  // role if data, seed or save are != "-", and with their label set to the value
    89  func rolesYaml(c *C, data, seed, save string) *gadget.Info {
    90  	h := `volumes:
    91    roles:
    92      schema: gpt
    93      bootloader: grub
    94      structure:
    95  `
    96  	if data != "-" {
    97  		h += `
    98        - name: data
    99          size: 1G
   100          type: 83,0FC63DAF-8483-4772-8E79-3D69D8477DE4
   101          role: system-data
   102  `
   103  		if data != "" {
   104  			h += fmt.Sprintf("        filesystem-label: %s\n", data)
   105  		}
   106  	}
   107  	if seed != "-" {
   108  		h += `
   109        - name: seed
   110          size: 1G
   111          type: EF,C12A7328-F81F-11D2-BA4B-00A0C93EC93B
   112          role: system-seed
   113  `
   114  		if seed != "" {
   115  			h += fmt.Sprintf("        filesystem-label: %s\n", seed)
   116  		}
   117  	}
   118  
   119  	if save != "-" {
   120  		h += `
   121        - name: save
   122          size: 32M
   123          type: 83,0FC63DAF-8483-4772-8E79-3D69D8477DE4
   124          role: system-save
   125  `
   126  		if save != "" {
   127  			h += fmt.Sprintf("        filesystem-label: %s\n", save)
   128  		}
   129  	}
   130  
   131  	gi, err := gadget.InfoFromGadgetYaml([]byte(h), nil)
   132  	c.Assert(err, IsNil)
   133  	return gi
   134  }
   135  
   136  func (s *validateGadgetTestSuite) TestVolumeRulesConsistencyNoModel(c *C) {
   137  	ginfo := func(hasSeed bool, dataLabel string) *gadget.Info {
   138  		seed := "-"
   139  		if hasSeed {
   140  			seed = ""
   141  		}
   142  		return rolesYaml(c, dataLabel, seed, "-")
   143  	}
   144  
   145  	for i, tc := range []struct {
   146  		gi  *gadget.Info
   147  		err string
   148  	}{
   149  
   150  		// we have the system-seed role
   151  		{ginfo(true, ""), ""},
   152  		{ginfo(true, "foobar"), `.* must have an implicit label or "ubuntu-data", not "foobar"`},
   153  		{ginfo(true, "writable"), `.* must have an implicit label or "ubuntu-data", not "writable"`},
   154  		{ginfo(true, "ubuntu-data"), ""},
   155  
   156  		// we don't have the system-seed role (old systems)
   157  		{ginfo(false, ""), ""}, // implicit is ok
   158  		{ginfo(false, "foobar"), `.* must have an implicit label or "writable", not "foobar"`},
   159  		{ginfo(false, "writable"), ""},
   160  		{ginfo(false, "ubuntu-data"), `.* must have an implicit label or "writable", not "ubuntu-data"`},
   161  	} {
   162  		c.Logf("tc: %d %v", i, tc.gi.Volumes["roles"])
   163  
   164  		err := gadget.Validate(tc.gi, nil, nil)
   165  		if tc.err != "" {
   166  			c.Check(err, ErrorMatches, tc.err)
   167  		} else {
   168  			c.Check(err, IsNil)
   169  		}
   170  	}
   171  
   172  	// Check system-seed label
   173  	for i, tc := range []struct {
   174  		l   string
   175  		err string
   176  	}{
   177  		{"", ""},
   178  		{"foobar", `system-seed structure must have an implicit label or "ubuntu-seed", not "foobar"`},
   179  		{"ubuntu-seed", ""},
   180  	} {
   181  		c.Logf("tc: %v %v", i, tc.l)
   182  		gi := rolesYaml(c, "", tc.l, "-")
   183  		err := gadget.Validate(gi, nil, nil)
   184  		if tc.err != "" {
   185  			c.Check(err, ErrorMatches, tc.err)
   186  		} else {
   187  			c.Check(err, IsNil)
   188  		}
   189  	}
   190  
   191  	// Check system-seed without system-data
   192  	gi := rolesYaml(c, "-", "-", "-")
   193  	err := gadget.Validate(gi, nil, nil)
   194  	c.Assert(err, IsNil)
   195  	gi = rolesYaml(c, "-", "", "-")
   196  	err = gadget.Validate(gi, nil, nil)
   197  	c.Assert(err, ErrorMatches, "the system-seed role requires system-data to be defined")
   198  
   199  	// Check system-save
   200  	giWithSave := rolesYaml(c, "", "", "")
   201  	err = gadget.Validate(giWithSave, nil, nil)
   202  	c.Assert(err, IsNil)
   203  	// use illegal label on system-save
   204  	giWithSave = rolesYaml(c, "", "", "foo")
   205  	err = gadget.Validate(giWithSave, nil, nil)
   206  	c.Assert(err, ErrorMatches, `system-save structure must have an implicit label or "ubuntu-save", not "foo"`)
   207  	// complains when save is alone
   208  	giWithSave = rolesYaml(c, "", "-", "")
   209  	err = gadget.Validate(giWithSave, nil, nil)
   210  	c.Assert(err, ErrorMatches, "model does not support the system-save role")
   211  	giWithSave = rolesYaml(c, "-", "-", "")
   212  	err = gadget.Validate(giWithSave, nil, nil)
   213  	c.Assert(err, ErrorMatches, "model does not support the system-save role")
   214  }
   215  
   216  func (s *validateGadgetTestSuite) TestValidateConsistencyWithoutModelCharateristics(c *C) {
   217  	for i, tc := range []struct {
   218  		role  string
   219  		label string
   220  		err   string
   221  	}{
   222  		// when model is nil, the system-seed role and ubuntu-data label on the
   223  		// system-data structure should be consistent
   224  		{"system-seed", "", ""},
   225  		{"system-seed", "writable", `must have an implicit label or "ubuntu-data", not "writable"`},
   226  		{"system-seed", "ubuntu-data", ""},
   227  		{"", "", ""},
   228  		{"", "writable", ""},
   229  		{"", "ubuntu-data", `must have an implicit label or "writable", not "ubuntu-data"`},
   230  	} {
   231  		c.Logf("tc: %v %v %v", i, tc.role, tc.label)
   232  		b := &bytes.Buffer{}
   233  
   234  		fmt.Fprintf(b, `
   235  volumes:
   236    pc:
   237      bootloader: grub
   238      schema: mbr
   239      structure:`)
   240  
   241  		if tc.role == "system-seed" {
   242  			fmt.Fprintf(b, `
   243        - name: Recovery
   244          size: 10M
   245          type: 83
   246          role: system-seed`)
   247  		}
   248  
   249  		fmt.Fprintf(b, `
   250        - name: Data
   251          size: 10M
   252          type: 83
   253          role: system-data
   254          filesystem-label: %s`, tc.label)
   255  
   256  		makeSizedFile(c, filepath.Join(s.dir, "meta/gadget.yaml"), 0, b.Bytes())
   257  		ginfo, err := gadget.ReadInfo(s.dir, nil)
   258  		c.Assert(err, IsNil)
   259  		err = gadget.Validate(ginfo, nil, nil)
   260  		if tc.err != "" {
   261  			c.Check(err, ErrorMatches, ".* "+tc.err)
   262  		} else {
   263  			c.Check(err, IsNil)
   264  		}
   265  	}
   266  }
   267  
   268  func (s *validateGadgetTestSuite) TestValidateConsistencyWithModelCharateristics(c *C) {
   269  	bloader := `
   270  volumes:
   271    pc:
   272      bootloader: grub
   273      schema: mbr
   274      structure:`
   275  
   276  	for i, tc := range []struct {
   277  		addSeed     bool
   278  		dataLabel   string
   279  		noData      bool
   280  		requireSeed bool
   281  		addSave     bool
   282  		saveLabel   string
   283  		err         string
   284  	}{
   285  		{addSeed: true, noData: true, requireSeed: true, err: "the system-seed role requires system-data to be defined"},
   286  		{addSeed: true, noData: true, requireSeed: false, err: "the system-seed role requires system-data to be defined"},
   287  		{addSeed: true, requireSeed: true},
   288  		{addSeed: true, err: `model does not support the system-seed role`},
   289  		{addSeed: true, dataLabel: "writable", requireSeed: true,
   290  			err: `system-data structure must have an implicit label or "ubuntu-data", not "writable"`},
   291  		{addSeed: true, dataLabel: "writable",
   292  			err: `model does not support the system-seed role`},
   293  		{addSeed: true, dataLabel: "ubuntu-data", requireSeed: true},
   294  		{addSeed: true, dataLabel: "ubuntu-data",
   295  			err: `model does not support the system-seed role`},
   296  		{dataLabel: "writable", requireSeed: true,
   297  			err: `model requires system-seed structure, but none was found`},
   298  		{dataLabel: "writable"},
   299  		{dataLabel: "ubuntu-data", requireSeed: true,
   300  			err: `model requires system-seed structure, but none was found`},
   301  		{dataLabel: "ubuntu-data", err: `system-data structure must have an implicit label or "writable", not "ubuntu-data"`},
   302  		{addSave: true, requireSeed: true, addSeed: true},
   303  		{addSave: true, err: `model does not support the system-save role`},
   304  		{addSeed: true, requireSeed: true, addSave: true, saveLabel: "foo",
   305  			err: `system-save structure must have an implicit label or "ubuntu-save", not "foo"`},
   306  	} {
   307  		c.Logf("tc: %v %v %v %v", i, tc.addSeed, tc.dataLabel, tc.requireSeed)
   308  		b := &bytes.Buffer{}
   309  
   310  		fmt.Fprintf(b, bloader)
   311  		if tc.addSeed {
   312  			fmt.Fprintf(b, `
   313        - name: Recovery
   314          size: 10M
   315          type: 83
   316          role: system-seed`)
   317  		}
   318  
   319  		if !tc.noData {
   320  			fmt.Fprintf(b, `
   321        - name: Data
   322          size: 10M
   323          type: 83
   324          role: system-data
   325          filesystem-label: %s`, tc.dataLabel)
   326  		}
   327  
   328  		if tc.addSave {
   329  			fmt.Fprintf(b, `
   330        - name: Save
   331          size: 10M
   332          type: 83
   333          role: system-save`)
   334  			if tc.saveLabel != "" {
   335  				fmt.Fprintf(b, `
   336          filesystem-label: %s`, tc.saveLabel)
   337  
   338  			}
   339  		}
   340  
   341  		makeSizedFile(c, filepath.Join(s.dir, "meta/gadget.yaml"), 0, b.Bytes())
   342  
   343  		mod := &modelCharateristics{
   344  			classic:    false,
   345  			systemSeed: tc.requireSeed,
   346  		}
   347  		ginfo, err := gadget.ReadInfo(s.dir, mod)
   348  		c.Assert(err, IsNil)
   349  		err = gadget.Validate(ginfo, mod, nil)
   350  		if tc.err != "" {
   351  			c.Check(err, ErrorMatches, tc.err)
   352  		} else {
   353  			c.Check(err, IsNil)
   354  		}
   355  	}
   356  
   357  	// test error with no volumes
   358  	makeSizedFile(c, filepath.Join(s.dir, "meta/gadget.yaml"), 0, []byte(bloader))
   359  
   360  	mod := &modelCharateristics{
   361  		systemSeed: true,
   362  	}
   363  
   364  	ginfo, err := gadget.ReadInfo(s.dir, mod)
   365  	c.Assert(err, IsNil)
   366  	err = gadget.Validate(ginfo, mod, nil)
   367  	c.Assert(err, ErrorMatches, "model requires system-seed partition, but no system-seed or system-data partition found")
   368  }
   369  
   370  func (s *validateGadgetTestSuite) TestValidateSystemRoleSplitAcrossVolumes(c *C) {
   371  	// ATM this is not allowed for UC20
   372  	const gadgetYamlContent = `
   373  volumes:
   374    pc1:
   375      # bootloader configuration is shipped and managed by snapd
   376      bootloader: grub
   377      structure:
   378        - name: mbr
   379          type: mbr
   380          size: 440
   381          update:
   382            edition: 1
   383          content:
   384            - image: pc-boot.img
   385        - name: BIOS Boot
   386          type: DA,21686148-6449-6E6F-744E-656564454649
   387          size: 1M
   388          offset: 1M
   389          offset-write: mbr+92
   390          update:
   391            edition: 2
   392          content:
   393            - image: pc-core.img
   394        - name: ubuntu-seed
   395          role: system-seed
   396          filesystem: vfat
   397          # UEFI will boot the ESP partition by default first
   398          type: EF,C12A7328-F81F-11D2-BA4B-00A0C93EC93B
   399          size: 1200M
   400          update:
   401            edition: 2
   402          content:
   403            - source: grubx64.efi
   404              target: EFI/boot/grubx64.efi
   405            - source: shim.efi.signed
   406              target: EFI/boot/bootx64.efi
   407        - name: ubuntu-boot
   408          role: system-boot
   409          filesystem: ext4
   410          type: 83,0FC63DAF-8483-4772-8E79-3D69D8477DE4
   411          # whats the appropriate size?
   412          size: 750M
   413          update:
   414            edition: 1
   415          content:
   416            - source: grubx64.efi
   417              target: EFI/boot/grubx64.efi
   418            - source: shim.efi.signed
   419              target: EFI/boot/bootx64.efi
   420    pc2:
   421      structure:
   422        - name: ubuntu-save
   423          role: system-save
   424          filesystem: ext4
   425          type: 83,0FC63DAF-8483-4772-8E79-3D69D8477DE4
   426          size: 16M
   427        - name: ubuntu-data
   428          role: system-data
   429          filesystem: ext4
   430          type: 83,0FC63DAF-8483-4772-8E79-3D69D8477DE4
   431          size: 1G
   432  `
   433  
   434  	makeSizedFile(c, filepath.Join(s.dir, "meta/gadget.yaml"), 0, []byte(gadgetYamlContent))
   435  
   436  	ginfo, err := gadget.ReadInfo(s.dir, nil)
   437  	c.Assert(err, IsNil)
   438  	err = gadget.Validate(ginfo, nil, nil)
   439  	c.Assert(err, ErrorMatches, `system-boot, system-data, and system-save are expected to share the same volume as system-seed`)
   440  }
   441  
   442  func (s *validateGadgetTestSuite) TestValidateRoleDuplicated(c *C) {
   443  
   444  	for _, role := range []string{"system-seed", "system-data", "system-boot", "system-save"} {
   445  		gadgetYamlContent := fmt.Sprintf(`
   446  volumes:
   447    pc:
   448      bootloader: grub
   449      structure:
   450        - name: foo
   451          type: DA,21686148-6449-6E6F-744E-656564454649
   452          size: 1M
   453          role: %[1]s
   454        - name: bar
   455          type: DA,21686148-6449-6E6F-744E-656564454649
   456          size: 1M
   457          role: %[1]s
   458  `, role)
   459  		makeSizedFile(c, filepath.Join(s.dir, "meta/gadget.yaml"), 0, []byte(gadgetYamlContent))
   460  
   461  		ginfo, err := gadget.ReadInfo(s.dir, nil)
   462  		c.Assert(err, IsNil)
   463  		err = gadget.Validate(ginfo, nil, nil)
   464  		c.Assert(err, ErrorMatches, fmt.Sprintf(`cannot have more than one partition with %s role`, role))
   465  	}
   466  }
   467  
   468  func (s *validateGadgetTestSuite) TestValidateSystemSeedRoleTwiceAcrossVolumes(c *C) {
   469  
   470  	for _, role := range []string{"system-seed", "system-data", "system-boot", "system-save"} {
   471  		gadgetYamlContent := fmt.Sprintf(`
   472  volumes:
   473    pc:
   474      bootloader: grub
   475      structure:
   476        - name: foo
   477          type: DA,21686148-6449-6E6F-744E-656564454649
   478          size: 1M
   479          role: %[1]s
   480    other:
   481      structure:
   482        - name: bar
   483          type: DA,21686148-6449-6E6F-744E-656564454649
   484          size: 1M
   485          role: %[1]s
   486  `, role)
   487  		makeSizedFile(c, filepath.Join(s.dir, "meta/gadget.yaml"), 0, []byte(gadgetYamlContent))
   488  
   489  		ginfo, err := gadget.ReadInfo(s.dir, nil)
   490  		c.Assert(err, IsNil)
   491  		err = gadget.Validate(ginfo, nil, nil)
   492  		c.Assert(err, ErrorMatches, fmt.Sprintf(`cannot have more than one partition with %s role across volumes`, role))
   493  	}
   494  }
   495  
   496  func (s *validateGadgetTestSuite) TestRuleValidateHybridGadget(c *C) {
   497  	// this is the kind of volumes setup recommended to be
   498  	// prepared for a possible UC18 -> UC20 transition
   499  	hybridyGadgetYaml := []byte(`volumes:
   500    hybrid:
   501      bootloader: grub
   502      structure:
   503        - name: mbr
   504          type: mbr
   505          size: 440
   506          content:
   507            - image: pc-boot.img
   508        - name: BIOS Boot
   509          type: DA,21686148-6449-6E6F-744E-656564454649
   510          size: 1M
   511          offset: 1M
   512          offset-write: mbr+92
   513          content:
   514            - image: pc-core.img
   515        - name: EFI System
   516          type: EF,C12A7328-F81F-11D2-BA4B-00A0C93EC93B
   517          filesystem: vfat
   518          filesystem-label: system-boot
   519          size: 1200M
   520          content:
   521            - source: grubx64.efi
   522              target: EFI/boot/grubx64.efi
   523            - source: shim.efi.signed
   524              target: EFI/boot/bootx64.efi
   525            - source: mmx64.efi
   526              target: EFI/boot/mmx64.efi
   527            - source: grub.cfg
   528              target: EFI/ubuntu/grub.cfg
   529        - name: Ubuntu Boot
   530          type: 0FC63DAF-8483-4772-8E79-3D69D8477DE4
   531          filesystem: ext4
   532          filesystem-label: ubuntu-boot
   533          size: 750M
   534  `)
   535  
   536  	mod := &modelCharateristics{
   537  		classic: false,
   538  	}
   539  	giMeta, err := gadget.InfoFromGadgetYaml(hybridyGadgetYaml, mod)
   540  	c.Assert(err, IsNil)
   541  
   542  	err = gadget.Validate(giMeta, mod, nil)
   543  	c.Check(err, IsNil)
   544  }
   545  
   546  func (s *validateGadgetTestSuite) TestRuleValidateHybridGadgetBrokenDupRole(c *C) {
   547  	// this is consistency-wise broken because of the duplicated
   548  	// system-boot role, of which one is implicit
   549  	brokenGadgetYaml := []byte(`volumes:
   550    hybrid:
   551      bootloader: grub
   552      structure:
   553        - name: mbr
   554          type: mbr
   555          size: 440
   556          content:
   557            - image: pc-boot.img
   558        - name: BIOS Boot
   559          type: DA,21686148-6449-6E6F-744E-656564454649
   560          size: 1M
   561          offset: 1M
   562          offset-write: mbr+92
   563          content:
   564            - image: pc-core.img
   565        - name: EFI System
   566          type: EF,C12A7328-F81F-11D2-BA4B-00A0C93EC93B
   567          filesystem: vfat
   568          filesystem-label: system-boot
   569          size: 1200M
   570          content:
   571            - source: grubx64.efi
   572              target: EFI/boot/grubx64.efi
   573            - source: shim.efi.signed
   574              target: EFI/boot/bootx64.efi
   575            - source: mmx64.efi
   576              target: EFI/boot/mmx64.efi
   577            - source: grub.cfg
   578              target: EFI/ubuntu/grub.cfg
   579        - name: Ubuntu Boot
   580          type: 0FC63DAF-8483-4772-8E79-3D69D8477DE4
   581          filesystem: ext4
   582          filesystem-label: ubuntu-boot
   583          role: system-boot
   584          size: 750M
   585  `)
   586  
   587  	mod := &modelCharateristics{
   588  		classic: false,
   589  	}
   590  	giMeta, err := gadget.InfoFromGadgetYaml(brokenGadgetYaml, mod)
   591  	c.Assert(err, IsNil)
   592  
   593  	err = gadget.Validate(giMeta, mod, nil)
   594  	c.Check(err, ErrorMatches, `cannot have more than one partition with system-boot role`)
   595  }
   596  
   597  func (s *validateGadgetTestSuite) TestValidateContentMissingRawContent(c *C) {
   598  	var gadgetYamlContent = `
   599  volumes:
   600    pc:
   601      bootloader: grub
   602      structure:
   603        - name: foo
   604          type: DA,21686148-6449-6E6F-744E-656564454649
   605          size: 1M
   606          offset: 1M
   607          content:
   608            - image: foo.img
   609  
   610  `
   611  	makeSizedFile(c, filepath.Join(s.dir, "meta/gadget.yaml"), 0, []byte(gadgetYamlContent))
   612  
   613  	ginfo, err := gadget.ReadInfo(s.dir, nil)
   614  	c.Assert(err, IsNil)
   615  	err = gadget.ValidateContent(ginfo, s.dir, "")
   616  	c.Assert(err, ErrorMatches, `invalid layout of volume "pc": cannot lay out structure #0 \("foo"\): content "foo.img": stat .*/foo.img: no such file or directory`)
   617  }
   618  
   619  func (s *validateGadgetTestSuite) TestValidateContentMultiVolumeContent(c *C) {
   620  	var gadgetYamlContent = `
   621  volumes:
   622    first:
   623      bootloader: grub
   624      structure:
   625        - name: first-foo
   626          type: DA,21686148-6449-6E6F-744E-656564454649
   627          size: 1M
   628          content:
   629            - image: first.img
   630    second:
   631      structure:
   632        - name: second-foo
   633          type: DA,21686148-6449-6E6F-744E-656564454649
   634          size: 1M
   635          content:
   636            - image: second.img
   637  
   638  `
   639  	makeSizedFile(c, filepath.Join(s.dir, "meta/gadget.yaml"), 0, []byte(gadgetYamlContent))
   640  	// only content for the first volume
   641  	makeSizedFile(c, filepath.Join(s.dir, "first.img"), 1, nil)
   642  
   643  	ginfo, err := gadget.ReadInfo(s.dir, nil)
   644  	c.Assert(err, IsNil)
   645  	err = gadget.ValidateContent(ginfo, s.dir, "")
   646  	c.Assert(err, ErrorMatches, `invalid layout of volume "second": cannot lay out structure #0 \("second-foo"\): content "second.img": stat .*/second.img: no such file or directory`)
   647  }
   648  
   649  func (s *validateGadgetTestSuite) TestValidateContentFilesystemContent(c *C) {
   650  	var gadgetYamlContent = `
   651  volumes:
   652    bad:
   653      bootloader: grub
   654      structure:
   655        - name: bad-struct
   656          type: DA,21686148-6449-6E6F-744E-656564454649
   657          size: 1M
   658          filesystem: ext4
   659          content:
   660            - source: foo/
   661              target: /
   662  
   663  `
   664  	makeSizedFile(c, filepath.Join(s.dir, "meta/gadget.yaml"), 0, []byte(gadgetYamlContent))
   665  
   666  	ginfo, err := gadget.ReadInfo(s.dir, nil)
   667  	c.Assert(err, IsNil)
   668  	err = gadget.ValidateContent(ginfo, s.dir, "")
   669  	c.Assert(err, ErrorMatches, `invalid volume "bad": structure #0 \("bad-struct"\), content source:foo/: source path does not exist`)
   670  
   671  	// make it a file, which conflicts with foo/ as 'source'
   672  	fooPath := filepath.Join(s.dir, "foo")
   673  	makeSizedFile(c, fooPath, 1, nil)
   674  	err = gadget.ValidateContent(ginfo, s.dir, "")
   675  	c.Assert(err, ErrorMatches, `invalid volume "bad": structure #0 \("bad-struct"\), content source:foo/: cannot specify trailing / for a source which is not a directory`)
   676  
   677  	// make it a directory
   678  	err = os.Remove(fooPath)
   679  	c.Assert(err, IsNil)
   680  	err = os.Mkdir(fooPath, 0755)
   681  	c.Assert(err, IsNil)
   682  	// validate should no longer complain
   683  	err = gadget.ValidateContent(ginfo, s.dir, "")
   684  	c.Assert(err, IsNil)
   685  }
   686  
   687  var gadgetYamlContentNoSave = `
   688  volumes:
   689    vol1:
   690      bootloader: grub
   691      structure:
   692        - name: ubuntu-seed
   693          role: system-seed
   694          type: DA,21686148-6449-6E6F-744E-656564454649
   695          size: 1M
   696          filesystem: ext4
   697        - name: ubuntu-boot
   698          type: DA,21686148-6449-6E6F-744E-656564454649
   699          size: 1M
   700          filesystem: ext4
   701        - name: ubuntu-data
   702          role: system-data
   703          type: DA,21686148-6449-6E6F-744E-656564454649
   704          size: 1M
   705          filesystem: ext4
   706  `
   707  
   708  var gadgetYamlContentWithSave = gadgetYamlContentNoSave + `
   709        - name: ubuntu-save
   710          role: system-save
   711          type: DA,21686148-6449-6E6F-744E-656564454649
   712          size: 1M
   713          filesystem: ext4
   714  `
   715  
   716  func (s *validateGadgetTestSuite) TestValidateEncryptionSupportErr(c *C) {
   717  	makeSizedFile(c, filepath.Join(s.dir, "meta/gadget.yaml"), 0, []byte(gadgetYamlContentNoSave))
   718  
   719  	mod := &modelCharateristics{systemSeed: true}
   720  	ginfo, err := gadget.ReadInfo(s.dir, mod)
   721  	c.Assert(err, IsNil)
   722  	err = gadget.Validate(ginfo, mod, &gadget.ValidationConstraints{
   723  		EncryptedData: true,
   724  	})
   725  	c.Assert(err, ErrorMatches, `gadget does not support encrypted data: volume "vol1" has no structure with system-save role`)
   726  }
   727  
   728  func (s *validateGadgetTestSuite) TestValidateEncryptionSupportHappy(c *C) {
   729  	makeSizedFile(c, filepath.Join(s.dir, "meta/gadget.yaml"), 0, []byte(gadgetYamlContentWithSave))
   730  	mod := &modelCharateristics{systemSeed: true}
   731  	ginfo, err := gadget.ReadInfo(s.dir, mod)
   732  	c.Assert(err, IsNil)
   733  	err = gadget.Validate(ginfo, mod, &gadget.ValidationConstraints{
   734  		EncryptedData: true,
   735  	})
   736  	c.Assert(err, IsNil)
   737  }
   738  
   739  var gadgetYamlContentKernelRef = gadgetYamlContentNoSave + `
   740        - name: other
   741          type: DA,21686148-6449-6E6F-744E-656564454649
   742          size: 10M
   743          filesystem: ext4
   744          content:
   745            - source: REPLACE_WITH_TC
   746              target: /
   747  `
   748  
   749  func (s *validateGadgetTestSuite) TestValidateContentKernelAssetsRef(c *C) {
   750  	for _, tc := range []struct {
   751  		source, asset, content string
   752  		good                   bool
   753  	}{
   754  		{"$kernel:a/b", "a", "b", true},
   755  		{"$kernel:A/b", "A", "b", true},
   756  		{"$kernel:a-a/bb", "a-a", "bb", true},
   757  		{"$kernel:a-a/b-b", "a-a", "b-b", true},
   758  		{"$kernel:aB-0/cD-3", "aB-0", "cD-3", true},
   759  		{"$kernel:aB-0/foo-21B.dtb", "aB-0", "foo-21B.dtb", true},
   760  		{"$kernel:aB-0/nested/bar-77A.raw", "aB-0", "nested/bar-77A.raw", true},
   761  		{"$kernel:a/a/", "a", "a/", true},
   762  		// no starting with "-"
   763  		{source: "$kernel:-/-"},
   764  		// assets and content need to be there
   765  		{source: "$kernel:ab"},
   766  		{source: "$kernel:/"},
   767  		{source: "$kernel:a/"},
   768  		{source: "$kernel:/a"},
   769  		// invalid asset name
   770  		{source: "$kernel:#garbage/a"},
   771  		// invalid content part
   772  		{source: "$kernel:a//"},
   773  		{source: "$kernel:a///"},
   774  		{source: "$kernel:a////"},
   775  		{source: "$kernel:a/a/../"},
   776  	} {
   777  		gadgetYaml := strings.Replace(gadgetYamlContentKernelRef, "REPLACE_WITH_TC", tc.source, -1)
   778  		makeSizedFile(c, filepath.Join(s.dir, "meta/gadget.yaml"), 0, []byte(gadgetYaml))
   779  		ginfo, err := gadget.ReadInfoAndValidate(s.dir, nil, nil)
   780  		c.Assert(err, IsNil)
   781  		err = gadget.ValidateContent(ginfo, s.dir, "")
   782  		if tc.good {
   783  			c.Check(err, IsNil, Commentf(tc.source))
   784  			// asset validates correctly, so let's make sure that
   785  			// individual pieces are correct too
   786  			assetName, content, err := gadget.SplitKernelRef(tc.source)
   787  			c.Assert(err, IsNil)
   788  			c.Check(assetName, Equals, tc.asset)
   789  			c.Check(content, Equals, tc.content)
   790  		} else {
   791  			errStr := fmt.Sprintf(`invalid volume "vol1": cannot use kernel reference "%s": .*`, regexp.QuoteMeta(tc.source))
   792  			c.Check(err, ErrorMatches, errStr, Commentf(tc.source))
   793  		}
   794  	}
   795  }
   796  
   797  func (s *validateGadgetTestSuite) TestSplitKernelRefErrors(c *C) {
   798  	for _, tc := range []struct {
   799  		kernelRef string
   800  		errStr    string
   801  	}{
   802  		{"no-kernel-ref", `internal error: splitKernelRef called for non kernel ref "no-kernel-ref"`},
   803  		{"$kernel:a", `invalid asset and content in kernel ref "\$kernel:a"`},
   804  		{"$kernel:a/", `missing asset name or content in kernel ref "\$kernel:a/"`},
   805  		{"$kernel:/b", `missing asset name or content in kernel ref "\$kernel:/b"`},
   806  		{"$kernel:a!invalid/b", `invalid asset name in kernel ref "\$kernel:a!invalid/b"`},
   807  		{"$kernel:a/b/..", `invalid content in kernel ref "\$kernel:a/b/.."`},
   808  		{"$kernel:a/b//", `invalid content in kernel ref "\$kernel:a/b//"`},
   809  		{"$kernel:a/b/./", `invalid content in kernel ref "\$kernel:a/b/./"`},
   810  	} {
   811  		_, _, err := gadget.SplitKernelRef(tc.kernelRef)
   812  		c.Check(err, ErrorMatches, tc.errStr, Commentf("kernelRef: %s", tc.kernelRef))
   813  	}
   814  }
   815  
   816  func (s *validateGadgetTestSuite) TestCanResolveOneVolumeKernelRef(c *C) {
   817  	lv := &gadget.LaidOutVolume{
   818  		Volume: &gadget.Volume{
   819  			Bootloader: "grub",
   820  			Schema:     "gpt",
   821  			Structure: []gadget.VolumeStructure{
   822  				{
   823  					Name:       "foo",
   824  					Size:       5 * quantity.SizeMiB,
   825  					Filesystem: "ext4",
   826  				},
   827  			},
   828  		},
   829  	}
   830  
   831  	contentNoKernelRef := []gadget.VolumeContent{
   832  		{UnresolvedSource: "/content", Target: "/"},
   833  	}
   834  	contentOneKernelRef := []gadget.VolumeContent{
   835  		{UnresolvedSource: "/content", Target: "/"},
   836  		{UnresolvedSource: "$kernel:ref/foo", Target: "/"},
   837  	}
   838  	contentTwoKernelRefs := []gadget.VolumeContent{
   839  		{UnresolvedSource: "/content", Target: "/"},
   840  		{UnresolvedSource: "$kernel:ref/foo", Target: "/"},
   841  		{UnresolvedSource: "$kernel:ref2/bar", Target: "/"},
   842  	}
   843  
   844  	kInfoNoRefs := &kernel.Info{}
   845  	kInfoOneRefButUpdateFlagFalse := &kernel.Info{
   846  		Assets: map[string]*kernel.Asset{
   847  			"ref": {
   848  				// note that update is false here
   849  				Update:  false,
   850  				Content: []string{"some-file"},
   851  			},
   852  		},
   853  	}
   854  	kInfoOneRef := &kernel.Info{
   855  		Assets: map[string]*kernel.Asset{
   856  			"ref": {
   857  				Update:  true,
   858  				Content: []string{"some-file"},
   859  			},
   860  		},
   861  	}
   862  	kInfoOneRefDifferentName := &kernel.Info{
   863  		Assets: map[string]*kernel.Asset{
   864  			"ref-other": {
   865  				Update:  true,
   866  				Content: []string{"some-file"},
   867  			},
   868  		},
   869  	}
   870  	kInfoTwoRefs := &kernel.Info{
   871  		Assets: map[string]*kernel.Asset{
   872  			"ref": {
   873  				Update:  true,
   874  				Content: []string{"some-file"},
   875  			},
   876  			"ref2": {
   877  				Update:  true,
   878  				Content: []string{"other-file"},
   879  			},
   880  		},
   881  	}
   882  
   883  	for _, tc := range []struct {
   884  		volumeContent []gadget.VolumeContent
   885  		kinfo         *kernel.Info
   886  		expectedErr   string
   887  	}{
   888  		// happy case: trivial
   889  		{contentNoKernelRef, kInfoNoRefs, ""},
   890  
   891  		// happy case: if kernel asset has "Update: false"
   892  		{contentNoKernelRef, kInfoOneRefButUpdateFlagFalse, ""},
   893  
   894  		// unhappy case: kernel has one or more unresolved references in gadget
   895  		{contentNoKernelRef, kInfoOneRef, `gadget does not consume any of the kernel assets needing synced update "ref"`},
   896  		{contentNoKernelRef, kInfoTwoRefs, `gadget does not consume any of the kernel assets needing synced update "ref", "ref2"`},
   897  
   898  		// unhappy case: gadget needs different asset than kernel provides
   899  		{contentOneKernelRef, kInfoOneRefDifferentName, `gadget does not consume any of the kernel assets needing synced update "ref-other"`},
   900  
   901  		// happy case: exactly one matching kernel ref
   902  		{contentOneKernelRef, kInfoOneRef, ""},
   903  		// happy case: one matching, one missing kernel ref, still considered fine
   904  		{contentTwoKernelRefs, kInfoTwoRefs, ""},
   905  	} {
   906  		lv.Structure[0].Content = tc.volumeContent
   907  		err := gadget.GadgetVolumeConsumesOneKernelUpdateAsset(lv.Volume, tc.kinfo)
   908  		if tc.expectedErr == "" {
   909  			c.Check(err, IsNil, Commentf("should not fail %v", tc.volumeContent))
   910  		} else {
   911  			c.Check(err, ErrorMatches, tc.expectedErr, Commentf("should fail %v", tc.volumeContent))
   912  		}
   913  	}
   914  }
   915  
   916  func (s *validateGadgetTestSuite) TestValidateContentKernelRefMissing(c *C) {
   917  	var gadgetYamlContent = `
   918  volumes:
   919    first:
   920      bootloader: grub
   921      structure:
   922        - name: first-foo
   923          type: DA,21686148-6449-6E6F-744E-656564454649
   924          size: 1M
   925          content:
   926            - image: first.img
   927    second:
   928      structure:
   929        - name: second-foo
   930          filesystem: ext4
   931          type: 83,0FC63DAF-8483-4772-8E79-3D69D8477DE4
   932          size: 128M
   933          content:
   934            - source: $kernel:ref/foo
   935              target: /
   936  `
   937  	makeSizedFile(c, filepath.Join(s.dir, "meta/gadget.yaml"), 0, []byte(gadgetYamlContent))
   938  	makeSizedFile(c, filepath.Join(s.dir, "first.img"), 1, nil)
   939  
   940  	// note that there is no kernel.yaml
   941  	kernelUnpackDir := c.MkDir()
   942  
   943  	ginfo, err := gadget.ReadInfo(s.dir, nil)
   944  	c.Assert(err, IsNil)
   945  	err = gadget.ValidateContent(ginfo, s.dir, kernelUnpackDir)
   946  	c.Assert(err, ErrorMatches, `.*cannot find "ref" in kernel info.*`)
   947  }
   948  
   949  func (s *validateGadgetTestSuite) TestValidateContentKernelRefNotInGadget(c *C) {
   950  	var gadgetYamlContent = `
   951  volumes:
   952    first:
   953      bootloader: grub
   954      structure:
   955        - name: first-foo
   956          type: DA,21686148-6449-6E6F-744E-656564454649
   957          size: 1M
   958          content:
   959            - image: first.img
   960    second:
   961      structure:
   962        - name: second-foo
   963          filesystem: ext4
   964          type: 83,0FC63DAF-8483-4772-8E79-3D69D8477DE4
   965          size: 128M
   966          content:
   967            - source: foo
   968              target: /
   969  `
   970  	makeSizedFile(c, filepath.Join(s.dir, "meta/gadget.yaml"), 0, []byte(gadgetYamlContent))
   971  	makeSizedFile(c, filepath.Join(s.dir, "first.img"), 1, nil)
   972  	makeSizedFile(c, filepath.Join(s.dir, "foo"), 1, nil)
   973  
   974  	kernelUnpackDir := c.MkDir()
   975  	kernelYamlContent := `
   976  assets:
   977   ref:
   978    update: true
   979    content:
   980     - dtbs/`
   981  	makeSizedFile(c, filepath.Join(kernelUnpackDir, "meta/kernel.yaml"), 0, []byte(kernelYamlContent))
   982  	makeSizedFile(c, filepath.Join(kernelUnpackDir, "dtbs/foo.dtb"), 0, []byte("foo.dtb content"))
   983  
   984  	ginfo, err := gadget.ReadInfo(s.dir, nil)
   985  	c.Assert(err, IsNil)
   986  	err = gadget.ValidateContent(ginfo, s.dir, kernelUnpackDir)
   987  	c.Assert(err, ErrorMatches, `no asset from the kernel.yaml needing synced update is consumed by the gadget at "/.*"`)
   988  }