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