gopkg.in/ubuntu-core/snappy.v0@v0.0.0-20210902073436-25a8614f10a6/bootloader/lkenv/lkenv_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 lkenv_test
    21  
    22  import (
    23  	"bytes"
    24  	"compress/gzip"
    25  	"encoding/binary"
    26  	"fmt"
    27  	"hash/crc32"
    28  	"io"
    29  	"io/ioutil"
    30  	"os"
    31  	"path/filepath"
    32  	"testing"
    33  
    34  	"golang.org/x/xerrors"
    35  	. "gopkg.in/check.v1"
    36  
    37  	"github.com/snapcore/snapd/boot"
    38  	"github.com/snapcore/snapd/bootloader/lkenv"
    39  	"github.com/snapcore/snapd/logger"
    40  	"github.com/snapcore/snapd/testutil"
    41  )
    42  
    43  // Hook up check.v1 into the "go test" runner
    44  func Test(t *testing.T) { TestingT(t) }
    45  
    46  type lkenvTestSuite struct {
    47  	envPath    string
    48  	envPathbak string
    49  }
    50  
    51  var _ = Suite(&lkenvTestSuite{})
    52  
    53  var (
    54  	lkversions = []lkenv.Version{
    55  		lkenv.V1,
    56  		lkenv.V2Run,
    57  		lkenv.V2Recovery,
    58  	}
    59  )
    60  
    61  func (l *lkenvTestSuite) SetUpTest(c *C) {
    62  	l.envPath = filepath.Join(c.MkDir(), "snapbootsel.bin")
    63  	l.envPathbak = l.envPath + "bak"
    64  }
    65  
    66  // unpack test data packed with gzip
    67  func unpackTestData(data []byte) (resData []byte, err error) {
    68  	b := bytes.NewBuffer(data)
    69  	var r io.Reader
    70  	r, err = gzip.NewReader(b)
    71  	if err != nil {
    72  		return
    73  	}
    74  	var env bytes.Buffer
    75  	_, err = env.ReadFrom(r)
    76  	if err != nil {
    77  		return
    78  	}
    79  	return env.Bytes(), nil
    80  }
    81  
    82  func (l *lkenvTestSuite) TestCtoGoString(c *C) {
    83  	for _, t := range []struct {
    84  		input    []byte
    85  		expected string
    86  	}{
    87  		{[]byte{0, 0, 0, 0, 0}, ""},
    88  		{[]byte{'a', 0, 0, 0, 0}, "a"},
    89  		{[]byte{'a', 'b', 0, 0, 0}, "ab"},
    90  		{[]byte{'a', 'b', 'c', 0, 0}, "abc"},
    91  		{[]byte{'a', 'b', 'c', 'd', 0}, "abcd"},
    92  		// no trailing \0 - assume corrupted "" ?
    93  		{[]byte{'a', 'b', 'c', 'd', 'e'}, ""},
    94  		// first \0 is the cutof
    95  		{[]byte{'a', 'b', 0, 'z', 0}, "ab"},
    96  	} {
    97  		c.Check(lkenv.CToGoString(t.input), Equals, t.expected)
    98  	}
    99  }
   100  
   101  func (l *lkenvTestSuite) TestCopyStringHappy(c *C) {
   102  	for _, t := range []struct {
   103  		input    string
   104  		expected []byte
   105  	}{
   106  		// input up to the size of the buffer works
   107  		{"", []byte{0, 0, 0, 0, 0}},
   108  		{"a", []byte{'a', 0, 0, 0, 0}},
   109  		{"ab", []byte{'a', 'b', 0, 0, 0}},
   110  		{"abc", []byte{'a', 'b', 'c', 0, 0}},
   111  		{"abcd", []byte{'a', 'b', 'c', 'd', 0}},
   112  		// only what fit is copied
   113  		{"abcde", []byte{'a', 'b', 'c', 'd', 0}},
   114  		{"abcdef", []byte{'a', 'b', 'c', 'd', 0}},
   115  		// strange embedded stuff works
   116  		{"ab\000z", []byte{'a', 'b', 0, 'z', 0}},
   117  	} {
   118  		b := make([]byte, 5)
   119  		lkenv.CopyString(b, t.input)
   120  		c.Check(b, DeepEquals, t.expected)
   121  	}
   122  }
   123  
   124  func (l *lkenvTestSuite) TestCopyStringNoPanic(c *C) {
   125  	// too long, string should get concatenate
   126  	b := make([]byte, 5)
   127  	defer lkenv.CopyString(b, "12345")
   128  	c.Assert(recover(), IsNil)
   129  	defer lkenv.CopyString(b, "123456")
   130  	c.Assert(recover(), IsNil)
   131  }
   132  
   133  func (l *lkenvTestSuite) TestGetBootImageName(c *C) {
   134  	for _, version := range lkversions {
   135  		for _, setValue := range []bool{true, false} {
   136  			env := lkenv.NewEnv(l.envPath, "", version)
   137  			c.Check(env, NotNil)
   138  
   139  			if setValue {
   140  				env.Set("bootimg_file_name", "some-boot-image-name")
   141  			}
   142  
   143  			name := env.GetBootImageName()
   144  
   145  			if setValue {
   146  				c.Assert(name, Equals, "some-boot-image-name")
   147  			} else {
   148  				c.Assert(name, Equals, "boot.img")
   149  			}
   150  		}
   151  	}
   152  }
   153  
   154  func (l *lkenvTestSuite) TestSet(c *C) {
   155  	tt := []struct {
   156  		version lkenv.Version
   157  		key     string
   158  		val     string
   159  	}{
   160  		{
   161  			lkenv.V1,
   162  			"snap_mode",
   163  			boot.TryStatus,
   164  		},
   165  		{
   166  			lkenv.V2Run,
   167  			"kernel_status",
   168  			boot.TryingStatus,
   169  		},
   170  		{
   171  			lkenv.V2Recovery,
   172  			"snapd_recovery_mode",
   173  			"recover",
   174  		},
   175  	}
   176  	for _, t := range tt {
   177  		env := lkenv.NewEnv(l.envPath, "", t.version)
   178  		c.Check(env, NotNil)
   179  		env.Set(t.key, t.val)
   180  		c.Check(env.Get(t.key), Equals, t.val)
   181  	}
   182  }
   183  
   184  func (l *lkenvTestSuite) TestSave(c *C) {
   185  	tt := []struct {
   186  		version       lkenv.Version
   187  		keyValuePairs map[string]string
   188  		comment       string
   189  	}{
   190  		{
   191  			lkenv.V1,
   192  			map[string]string{
   193  				"snap_mode":         boot.TryingStatus,
   194  				"snap_kernel":       "kernel-1",
   195  				"snap_try_kernel":   "kernel-2",
   196  				"snap_core":         "core-1",
   197  				"snap_try_core":     "core-2",
   198  				"snap_gadget":       "gadget-1",
   199  				"snap_try_gadget":   "gadget-2",
   200  				"bootimg_file_name": "boot.img",
   201  			},
   202  			"lkenv v1",
   203  		},
   204  		{
   205  			lkenv.V2Run,
   206  			map[string]string{
   207  				"kernel_status":     boot.TryStatus,
   208  				"snap_kernel":       "kernel-1",
   209  				"snap_try_kernel":   "kernel-2",
   210  				"snap_gadget":       "gadget-1",
   211  				"snap_try_gadget":   "gadget-2",
   212  				"bootimg_file_name": "boot.img",
   213  			},
   214  			"lkenv v2 run",
   215  		},
   216  		{
   217  			lkenv.V2Recovery,
   218  			map[string]string{
   219  				"snapd_recovery_mode":    "recover",
   220  				"snapd_recovery_system":  "11192020",
   221  				"bootimg_file_name":      "boot.img",
   222  				"try_recovery_system":    "1234",
   223  				"recovery_system_status": "tried",
   224  			},
   225  			"lkenv v2 recovery",
   226  		},
   227  	}
   228  	for _, t := range tt {
   229  		for _, makeBackup := range []bool{true, false} {
   230  			var comment CommentInterface
   231  			if makeBackup {
   232  				comment = Commentf("testcase %s with backup", t.comment)
   233  			} else {
   234  				comment = Commentf("testcase %s without backup", t.comment)
   235  			}
   236  
   237  			loggerBuf, restore := logger.MockLogger()
   238  			defer restore()
   239  
   240  			// make unique files per test case
   241  			testFile := filepath.Join(c.MkDir(), "lk.bin")
   242  			testFileBackup := testFile + "bak"
   243  			if makeBackup {
   244  				// create the backup file too
   245  				buf := make([]byte, 4096)
   246  				err := ioutil.WriteFile(testFileBackup, buf, 0644)
   247  				c.Assert(err, IsNil, comment)
   248  			}
   249  
   250  			buf := make([]byte, 4096)
   251  			err := ioutil.WriteFile(testFile, buf, 0644)
   252  			c.Assert(err, IsNil, comment)
   253  
   254  			env := lkenv.NewEnv(testFile, "", t.version)
   255  			c.Check(env, NotNil, comment)
   256  
   257  			for k, v := range t.keyValuePairs {
   258  				env.Set(k, v)
   259  			}
   260  
   261  			err = env.Save()
   262  			c.Assert(err, IsNil, comment)
   263  
   264  			env2 := lkenv.NewEnv(testFile, "", t.version)
   265  			err = env2.Load()
   266  			c.Assert(err, IsNil, comment)
   267  
   268  			for k, v := range t.keyValuePairs {
   269  				c.Check(env2.Get(k), Equals, v, comment)
   270  			}
   271  
   272  			// check the backup too
   273  			if makeBackup {
   274  				env3 := lkenv.NewEnv(testFileBackup, "", t.version)
   275  				err := env3.Load()
   276  				c.Assert(err, IsNil, comment)
   277  
   278  				for k, v := range t.keyValuePairs {
   279  					c.Check(env3.Get(k), Equals, v, comment)
   280  				}
   281  
   282  				// corrupt the main file and then try to load it - we should
   283  				// automatically fallback to the backup file since the backup
   284  				// file will not be corrupt
   285  				buf := make([]byte, 4096)
   286  				f, err := os.OpenFile(testFile, os.O_WRONLY, 0644)
   287  				c.Assert(err, IsNil)
   288  				_, err = io.Copy(f, bytes.NewBuffer(buf))
   289  				c.Assert(err, IsNil, comment)
   290  
   291  				env4 := lkenv.NewEnv(testFile, "", t.version)
   292  				err = env4.Load()
   293  				c.Assert(err, IsNil, comment)
   294  
   295  				for k, v := range t.keyValuePairs {
   296  					c.Check(env4.Get(k), Equals, v, comment)
   297  				}
   298  
   299  				// we should have also had a logged message about being unable
   300  				// to load the main file
   301  				c.Assert(loggerBuf.String(), testutil.Contains, fmt.Sprintf("cannot load primary bootloader environment: cannot validate %s:", testFile))
   302  			}
   303  		}
   304  	}
   305  }
   306  
   307  func (l *lkenvTestSuite) TestLoadValidatesCRC32(c *C) {
   308  	for _, version := range lkversions {
   309  		testFile := filepath.Join(c.MkDir(), "lk.bin")
   310  
   311  		// make an out of band lkenv object and set the wrong signature to be
   312  		// able to export it to a file
   313  		var rawStruct interface{}
   314  		switch version {
   315  		case lkenv.V1:
   316  			rawStruct = lkenv.SnapBootSelect_v1{
   317  				Version:   version.Number(),
   318  				Signature: version.Signature(),
   319  			}
   320  		case lkenv.V2Run:
   321  			rawStruct = lkenv.SnapBootSelect_v2_run{
   322  				Version:   version.Number(),
   323  				Signature: version.Signature(),
   324  			}
   325  		case lkenv.V2Recovery:
   326  			rawStruct = lkenv.SnapBootSelect_v2_recovery{
   327  				Version:   version.Number(),
   328  				Signature: version.Signature(),
   329  			}
   330  		}
   331  
   332  		buf := bytes.NewBuffer(nil)
   333  		ss := binary.Size(rawStruct)
   334  		buf.Grow(ss)
   335  		err := binary.Write(buf, binary.LittleEndian, rawStruct)
   336  		c.Assert(err, IsNil)
   337  
   338  		// calculate the expected checksum but don't put it into the object when
   339  		// we write it out so that the checksum is invalid
   340  		expCrc32 := crc32.ChecksumIEEE(buf.Bytes()[:ss-4])
   341  
   342  		err = ioutil.WriteFile(testFile, buf.Bytes(), 0644)
   343  		c.Assert(err, IsNil)
   344  
   345  		// now try importing the file with LoadEnv()
   346  		env := lkenv.NewEnv(testFile, "", version)
   347  		c.Assert(env, NotNil)
   348  
   349  		err = env.LoadEnv(testFile)
   350  		c.Assert(err, ErrorMatches, fmt.Sprintf("cannot validate %s: expected checksum 0x%X, got 0x%X", testFile, expCrc32, 0))
   351  	}
   352  
   353  }
   354  
   355  func (l *lkenvTestSuite) TestNewBackupFileLocation(c *C) {
   356  	// creating with the second argument as the empty string falls back to
   357  	// the main path + "bak"
   358  	for _, version := range lkversions {
   359  		logbuf, restore := logger.MockLogger()
   360  		defer restore()
   361  
   362  		testFile := filepath.Join(c.MkDir(), "lk.bin")
   363  		c.Assert(testFile, testutil.FileAbsent)
   364  		c.Assert(testFile+"bak", testutil.FileAbsent)
   365  		// make empty files for Save() to overwrite
   366  		err := ioutil.WriteFile(testFile, nil, 0644)
   367  		c.Assert(err, IsNil)
   368  		err = ioutil.WriteFile(testFile+"bak", nil, 0644)
   369  		c.Assert(err, IsNil)
   370  		env := lkenv.NewEnv(testFile, "", version)
   371  		c.Assert(env, NotNil)
   372  		err = env.Save()
   373  		c.Assert(err, IsNil)
   374  
   375  		// make sure both the primary and backup files were written and can be
   376  		// successfully loaded
   377  		env2 := lkenv.NewEnv(testFile, "", version)
   378  		err = env2.Load()
   379  		c.Assert(err, IsNil)
   380  
   381  		env3 := lkenv.NewEnv(testFile+"bak", "", version)
   382  		err = env3.Load()
   383  		c.Assert(err, IsNil)
   384  
   385  		// no messages logged
   386  		c.Assert(logbuf.String(), Equals, "")
   387  	}
   388  
   389  	// now specify a different backup file location
   390  	for _, version := range lkversions {
   391  		logbuf, restore := logger.MockLogger()
   392  		defer restore()
   393  		testFile := filepath.Join(c.MkDir(), "lk.bin")
   394  		testFileBackup := filepath.Join(c.MkDir(), "lkbackup.bin")
   395  		err := ioutil.WriteFile(testFile, nil, 0644)
   396  		c.Assert(err, IsNil)
   397  		err = ioutil.WriteFile(testFileBackup, nil, 0644)
   398  		c.Assert(err, IsNil)
   399  
   400  		env := lkenv.NewEnv(testFile, testFileBackup, version)
   401  		c.Assert(env, NotNil)
   402  		err = env.Save()
   403  		c.Assert(err, IsNil)
   404  
   405  		// make sure both the primary and backup files were written and can be
   406  		// successfully loaded
   407  		env2 := lkenv.NewEnv(testFile, "", version)
   408  		err = env2.Load()
   409  		c.Assert(err, IsNil)
   410  
   411  		env3 := lkenv.NewEnv(testFileBackup, "", version)
   412  		err = env3.Load()
   413  		c.Assert(err, IsNil)
   414  
   415  		// no "bak" files present
   416  		c.Assert(testFile+"bak", testutil.FileAbsent)
   417  		c.Assert(testFileBackup+"bak", testutil.FileAbsent)
   418  
   419  		// no messages logged
   420  		c.Assert(logbuf.String(), Equals, "")
   421  	}
   422  }
   423  
   424  func (l *lkenvTestSuite) TestLoadValidatesVersionSignatureConsistency(c *C) {
   425  
   426  	tt := []struct {
   427  		version          lkenv.Version
   428  		binVersion       uint32
   429  		binSignature     uint32
   430  		validateFailMode string
   431  	}{
   432  		{
   433  			lkenv.V1,
   434  			lkenv.V2Recovery.Number(),
   435  			lkenv.V1.Signature(),
   436  			"version",
   437  		},
   438  		{
   439  			lkenv.V1,
   440  			lkenv.V1.Number(),
   441  			lkenv.V2Recovery.Signature(),
   442  			"signature",
   443  		},
   444  		{
   445  			lkenv.V2Run,
   446  			lkenv.V1.Number(),
   447  			lkenv.V2Run.Signature(),
   448  			"version",
   449  		},
   450  		{
   451  			lkenv.V2Run,
   452  			lkenv.V2Run.Number(),
   453  			lkenv.V2Recovery.Signature(),
   454  			"signature",
   455  		},
   456  		{
   457  			lkenv.V2Recovery,
   458  			lkenv.V1.Number(),
   459  			lkenv.V2Recovery.Signature(),
   460  			"version",
   461  		},
   462  		{
   463  			lkenv.V2Recovery,
   464  			lkenv.V2Recovery.Number(),
   465  			lkenv.V2Run.Signature(),
   466  			"signature",
   467  		},
   468  	}
   469  
   470  	for _, t := range tt {
   471  		testFile := filepath.Join(c.MkDir(), "lk.bin")
   472  
   473  		// make an out of band lkenv object and set the wrong signature to be
   474  		// able to export it to a file
   475  		var rawStruct interface{}
   476  		switch t.version {
   477  		case lkenv.V1:
   478  			rawStruct = lkenv.SnapBootSelect_v1{
   479  				Version:   t.binVersion,
   480  				Signature: t.binSignature,
   481  			}
   482  		case lkenv.V2Run:
   483  			rawStruct = lkenv.SnapBootSelect_v2_run{
   484  				Version:   t.binVersion,
   485  				Signature: t.binSignature,
   486  			}
   487  		case lkenv.V2Recovery:
   488  			rawStruct = lkenv.SnapBootSelect_v2_recovery{
   489  				Version:   t.binVersion,
   490  				Signature: t.binSignature,
   491  			}
   492  		}
   493  
   494  		buf := bytes.NewBuffer(nil)
   495  		ss := binary.Size(rawStruct)
   496  		buf.Grow(ss)
   497  		err := binary.Write(buf, binary.LittleEndian, rawStruct)
   498  		c.Assert(err, IsNil)
   499  
   500  		// calculate crc32
   501  		newCrc32 := crc32.ChecksumIEEE(buf.Bytes()[:ss-4])
   502  		// note for efficiency's sake to avoid re-writing the whole structure,
   503  		// we re-write _just_ the crc32 to w as little-endian
   504  		buf.Truncate(ss - 4)
   505  		binary.Write(buf, binary.LittleEndian, &newCrc32)
   506  
   507  		err = ioutil.WriteFile(testFile, buf.Bytes(), 0644)
   508  		c.Assert(err, IsNil)
   509  
   510  		// now try importing the file with LoadEnv()
   511  		env := lkenv.NewEnv(testFile, "", t.version)
   512  		c.Assert(env, NotNil)
   513  
   514  		var expNum, gotNum uint32
   515  		switch t.validateFailMode {
   516  		case "signature":
   517  			expNum = t.version.Signature()
   518  			gotNum = t.binSignature
   519  		case "version":
   520  			expNum = t.version.Number()
   521  			gotNum = t.binVersion
   522  		}
   523  		expErr := fmt.Sprintf(
   524  			"cannot validate %s: expected %s 0x%X, got 0x%X",
   525  			testFile,
   526  			t.validateFailMode,
   527  			expNum,
   528  			gotNum,
   529  		)
   530  
   531  		err = env.LoadEnv(testFile)
   532  		c.Assert(err, ErrorMatches, expErr)
   533  	}
   534  }
   535  
   536  func (l *lkenvTestSuite) TestLoadPropagatesErrNotExist(c *C) {
   537  	// make sure that if the env file doesn't exist, the error returned from
   538  	// Load() is os.ErrNotExist, even if it isn't exactly that
   539  	env := lkenv.NewEnv("some-nonsense-file-this-doesnt-exist", "", lkenv.V1)
   540  	c.Check(env, NotNil)
   541  
   542  	err := env.Load()
   543  	c.Assert(xerrors.Is(err, os.ErrNotExist), Equals, true, Commentf("err is %+v", err))
   544  	c.Assert(err, ErrorMatches, "cannot open LK env file: open some-nonsense-file-this-doesnt-existbak: no such file or directory")
   545  }
   546  
   547  func (l *lkenvTestSuite) TestLoad(c *C) {
   548  	for _, version := range lkversions {
   549  		for _, makeBackup := range []bool{true, false} {
   550  			loggerBuf, restore := logger.MockLogger()
   551  			defer restore()
   552  			// make unique files per test case
   553  			testFile := filepath.Join(c.MkDir(), "lk.bin")
   554  			testFileBackup := testFile + "bak"
   555  			if makeBackup {
   556  				buf := make([]byte, 100000)
   557  				err := ioutil.WriteFile(testFileBackup, buf, 0644)
   558  				c.Assert(err, IsNil)
   559  			}
   560  
   561  			buf := make([]byte, 100000)
   562  			err := ioutil.WriteFile(testFile, buf, 0644)
   563  			c.Assert(err, IsNil)
   564  
   565  			// create an env for this file and try to load it
   566  			env := lkenv.NewEnv(testFile, "", version)
   567  			c.Check(env, NotNil)
   568  
   569  			err = env.Load()
   570  			// possible error messages could be "cannot open LK env file: ..."
   571  			// or "cannot valid <file>: ..."
   572  			if makeBackup {
   573  				// here we will read the backup file which exists but like the
   574  				// primary file is corrupted
   575  				c.Assert(err, ErrorMatches, fmt.Sprintf("cannot validate %s: expected version 0x%X, got 0x0", testFileBackup, version.Number()))
   576  			} else {
   577  				// here we fail to read the normal file, and automatically try
   578  				// to read the backup, but fail because it doesn't exist
   579  				c.Assert(err, ErrorMatches, fmt.Sprintf("cannot open LK env file: open %s: no such file or directory", testFileBackup))
   580  			}
   581  
   582  			c.Assert(loggerBuf.String(), testutil.Contains, fmt.Sprintf("cannot load primary bootloader environment: cannot validate %s:", testFile))
   583  			c.Assert(loggerBuf.String(), testutil.Contains, "attempting to load backup bootloader environment")
   584  		}
   585  	}
   586  }
   587  
   588  func (l *lkenvTestSuite) TestGetAndSetAndFindBootPartition(c *C) {
   589  	tt := []struct {
   590  		version lkenv.Version
   591  		// use slices instead of a map since we need a consistent ordering
   592  		bootMatrixKeys   []string
   593  		bootMatrixValues []string
   594  		matrixType       string
   595  		comment          string
   596  	}{
   597  		{
   598  			lkenv.V1,
   599  			[]string{
   600  				"boot_a",
   601  				"boot_b",
   602  			},
   603  			[]string{
   604  				"kernel-1",
   605  				"kernel-2",
   606  			},
   607  			"kernel",
   608  			"v1",
   609  		},
   610  		{
   611  			lkenv.V2Run,
   612  			[]string{
   613  				"boot_a",
   614  				"boot_b",
   615  			},
   616  			[]string{
   617  				"kernel-1",
   618  				"kernel-2",
   619  			},
   620  			"kernel",
   621  			"v2 run",
   622  		},
   623  		{
   624  			lkenv.V2Recovery,
   625  			[]string{
   626  				"boot_recovery_1",
   627  			},
   628  			[]string{
   629  				"20201123",
   630  			},
   631  			"recovery-system",
   632  			"v2 recovery 1 slot",
   633  		},
   634  		{
   635  			lkenv.V2Recovery,
   636  			[]string{
   637  				"boot_recovery_1",
   638  				"boot_recovery_2",
   639  			},
   640  			[]string{
   641  				"20201123",
   642  				"20201124",
   643  			},
   644  			"recovery-system",
   645  			"v2 recovery 2 slots",
   646  		},
   647  		{
   648  			lkenv.V2Recovery,
   649  			[]string{
   650  				"boot_recovery_1",
   651  				"boot_recovery_2",
   652  				"boot_recovery_3",
   653  			},
   654  			[]string{
   655  				"20201123",
   656  				"20201124",
   657  				"20201125",
   658  			},
   659  			"recovery-system",
   660  			"v2 recovery 3 slots",
   661  		},
   662  		{
   663  			lkenv.V2Recovery,
   664  			[]string{
   665  				"boot_recovery_1",
   666  				"boot_recovery_2",
   667  				"boot_recovery_3",
   668  				"boot_recovery_4",
   669  				"boot_recovery_5",
   670  				"boot_recovery_6",
   671  				"boot_recovery_7",
   672  				"boot_recovery_8",
   673  				"boot_recovery_9",
   674  				"boot_recovery_10",
   675  			},
   676  			[]string{
   677  				"20201123",
   678  				"20201124",
   679  				"20201125",
   680  				"20201126",
   681  				"20201127",
   682  				"20201128",
   683  				"20201129",
   684  				"20201130",
   685  				"20201131",
   686  				"20201132",
   687  			},
   688  			"recovery-system",
   689  			"v2 recovery max slots",
   690  		},
   691  	}
   692  
   693  	for _, t := range tt {
   694  		comment := Commentf(t.comment)
   695  		// make sure the key and values are the same length for test case
   696  		// consistency check
   697  		c.Assert(t.bootMatrixKeys, HasLen, len(t.bootMatrixValues), comment)
   698  
   699  		buf := make([]byte, 4096)
   700  		err := ioutil.WriteFile(l.envPath, buf, 0644)
   701  		c.Assert(err, IsNil, comment)
   702  
   703  		env := lkenv.NewEnv(l.envPath, "", t.version)
   704  		c.Assert(env, Not(IsNil), comment)
   705  
   706  		var findFunc func(string) (string, error)
   707  		var setFunc func(string, string) error
   708  		var getFunc func(string) (string, error)
   709  		var deleteFunc func(string) error
   710  		switch t.matrixType {
   711  		case "recovery-system":
   712  			findFunc = func(s string) (string, error) { return env.FindFreeRecoverySystemBootPartition(s) }
   713  			setFunc = func(s1, s2 string) error { return env.SetBootPartitionRecoverySystem(s1, s2) }
   714  			getFunc = func(s1 string) (string, error) { return env.GetRecoverySystemBootPartition(s1) }
   715  			deleteFunc = func(s1 string) error { return env.RemoveRecoverySystemFromBootPartition(s1) }
   716  		case "kernel":
   717  			findFunc = func(s string) (string, error) { return env.FindFreeKernelBootPartition(s) }
   718  			setFunc = func(s1, s2 string) error {
   719  				// for assigning the kernel, we need to also set the
   720  				// snap_kernel, since that is used to detect if we should return
   721  				// an unset variable or not
   722  
   723  				err := env.SetBootPartitionKernel(s1, s2)
   724  				c.Assert(err, IsNil, comment)
   725  				if err != nil {
   726  					return err
   727  				}
   728  				if env.Get("snap_kernel") == "" {
   729  					// only set it the first time so that the delete logic test
   730  					// works and we only set the first kernel to be snap_kernel
   731  					env.Set("snap_kernel", s2)
   732  				}
   733  				return nil
   734  			}
   735  			getFunc = func(s1 string) (string, error) { return env.GetKernelBootPartition(s1) }
   736  			deleteFunc = func(s1 string) error { return env.RemoveKernelFromBootPartition(s1) }
   737  		default:
   738  			c.Errorf("unexpected matrix type, test setup broken (%s)", comment)
   739  		}
   740  
   741  		err = env.InitializeBootPartitions(t.bootMatrixKeys...)
   742  		c.Assert(err, IsNil, comment)
   743  
   744  		// before assigning any values to the boot matrix, check that all
   745  		// values we try to assign would go to the first bootPartLabel
   746  		for _, bootPartValue := range t.bootMatrixKeys {
   747  			// we haven't assigned anything yet, so all values should get mapped
   748  			// to the first boot image partition
   749  			bootPartFound, err := findFunc(bootPartValue)
   750  			c.Assert(err, IsNil, comment)
   751  			c.Assert(bootPartFound, Equals, t.bootMatrixKeys[0], comment)
   752  		}
   753  
   754  		// now go and assign them, checking that along the way we are assigning
   755  		// to the next slot
   756  		// iterate over the key list to keep the same order
   757  		for i, bootPart := range t.bootMatrixKeys {
   758  			bootPartValue := t.bootMatrixValues[i]
   759  			// now we will be assigning things, so we should check that the
   760  			// assigned boot image partition matches what we expect
   761  			bootPartFound, err := findFunc(bootPartValue)
   762  			c.Assert(err, IsNil, comment)
   763  			c.Assert(bootPartFound, Equals, bootPart, comment)
   764  
   765  			err = setFunc(bootPart, bootPartValue)
   766  			c.Assert(err, IsNil, comment)
   767  
   768  			// now check that it has the right value
   769  			val, err := getFunc(bootPartValue)
   770  			c.Assert(err, IsNil, comment)
   771  			c.Assert(val, Equals, bootPart, comment)
   772  
   773  			// double-check that finding a free slot for this value returns the
   774  			// existing slot - this logic specifically is important for uc16 and
   775  			// uc18 where during seeding we will end up extracting a kernel to
   776  			// the already extracted slot (since the kernel will already have
   777  			// been extracted during image build time)
   778  			bootPartFound2, err := findFunc(bootPartValue)
   779  			c.Assert(err, IsNil, comment)
   780  			c.Assert(bootPartFound2, Equals, bootPart, comment)
   781  		}
   782  
   783  		// now check that trying to find a free slot for a new recovery system
   784  		// fails because we are full
   785  		if t.matrixType == "recovery-system" {
   786  			thing, err := findFunc("some-random-value")
   787  			c.Check(thing, Equals, "")
   788  			c.Assert(err, ErrorMatches, "cannot find free boot image partition", comment)
   789  		}
   790  
   791  		// test that removing the last one works
   792  		lastIndex := len(t.bootMatrixValues) - 1
   793  		lastValue := t.bootMatrixValues[lastIndex]
   794  		lastKey := t.bootMatrixKeys[lastIndex]
   795  		err = deleteFunc(lastValue)
   796  		c.Assert(err, IsNil, comment)
   797  
   798  		// trying to delete again will fail since it won't exist
   799  		err = deleteFunc(lastValue)
   800  		c.Assert(err, ErrorMatches, fmt.Sprintf("cannot find %q in boot image partitions", lastValue), comment)
   801  
   802  		// trying to find it will return the last slot
   803  		slot, err := findFunc(lastValue)
   804  		c.Assert(err, IsNil, comment)
   805  		c.Assert(slot, Equals, lastKey, comment)
   806  	}
   807  }
   808  
   809  func (l *lkenvTestSuite) TestV1NoRecoverySystemSupport(c *C) {
   810  	env := lkenv.NewEnv(l.envPath, "", lkenv.V1)
   811  	c.Assert(env, NotNil)
   812  
   813  	_, err := env.FindFreeRecoverySystemBootPartition("blah")
   814  	c.Assert(err, ErrorMatches, "internal error: v1 lkenv has no boot image partition recovery system matrix")
   815  
   816  	err = env.SetBootPartitionRecoverySystem("blah", "blah")
   817  	c.Assert(err, ErrorMatches, "internal error: v1 lkenv has no boot image partition recovery system matrix")
   818  
   819  	_, err = env.GetRecoverySystemBootPartition("blah")
   820  	c.Assert(err, ErrorMatches, "internal error: v1 lkenv has no boot image partition recovery system matrix")
   821  
   822  	err = env.RemoveRecoverySystemFromBootPartition("blah")
   823  	c.Assert(err, ErrorMatches, "internal error: v1 lkenv has no boot image partition recovery system matrix")
   824  }
   825  
   826  func (l *lkenvTestSuite) TestV2RunNoRecoverySystemSupport(c *C) {
   827  	env := lkenv.NewEnv(l.envPath, "", lkenv.V2Run)
   828  	c.Assert(env, NotNil)
   829  
   830  	_, err := env.FindFreeRecoverySystemBootPartition("blah")
   831  	c.Assert(err, ErrorMatches, "internal error: v2 run lkenv has no boot image partition recovery system matrix")
   832  
   833  	err = env.SetBootPartitionRecoverySystem("blah", "blah")
   834  	c.Assert(err, ErrorMatches, "internal error: v2 run lkenv has no boot image partition recovery system matrix")
   835  
   836  	_, err = env.GetRecoverySystemBootPartition("blah")
   837  	c.Assert(err, ErrorMatches, "internal error: v2 run lkenv has no boot image partition recovery system matrix")
   838  
   839  	err = env.RemoveRecoverySystemFromBootPartition("blah")
   840  	c.Assert(err, ErrorMatches, "internal error: v2 run lkenv has no boot image partition recovery system matrix")
   841  }
   842  
   843  func (l *lkenvTestSuite) TestV2RecoveryNoKernelSupport(c *C) {
   844  	env := lkenv.NewEnv(l.envPath, "", lkenv.V2Recovery)
   845  	c.Assert(env, NotNil)
   846  
   847  	_, err := env.FindFreeKernelBootPartition("blah")
   848  	c.Assert(err, ErrorMatches, "internal error: v2 recovery lkenv has no boot image partition kernel matrix")
   849  
   850  	err = env.SetBootPartitionKernel("blah", "blah")
   851  	c.Assert(err, ErrorMatches, "internal error: v2 recovery lkenv has no boot image partition kernel matrix")
   852  
   853  	_, err = env.GetKernelBootPartition("blah")
   854  	c.Assert(err, ErrorMatches, "internal error: v2 recovery lkenv has no boot image partition kernel matrix")
   855  
   856  	err = env.RemoveKernelFromBootPartition("blah")
   857  	c.Assert(err, ErrorMatches, "internal error: v2 recovery lkenv has no boot image partition kernel matrix")
   858  }
   859  
   860  func (l *lkenvTestSuite) TestZippedDataSample(c *C) {
   861  	// TODO: add binary data test for v2 structures generated with gadget build
   862  	// tool when it has been updated for v2
   863  
   864  	// test data is generated with gadget build helper tool:
   865  	// $ parts/snap-boot-sel-env/build/lk-boot-env -w test.bin \
   866  	//   --snap-mode="trying" --snap-kernel="kernel-1" --snap-try-kernel="kernel-2" \
   867  	//   --snap-core="core-1" --snap-try-core="core-2" --reboot-reason="" \
   868  	//   --boot-0-part="boot_a" --boot-1-part="boot_b" --boot-0-snap="kernel-1" \
   869  	//   --boot-1-snap="kernel-3" --bootimg-file="boot.img"
   870  	// $ cat test.bin | gzip | xxd -i
   871  	gzipedData := []byte{
   872  		0x1f, 0x8b, 0x08, 0x00, 0x95, 0x88, 0x77, 0x5d, 0x00, 0x03, 0xed, 0xd7,
   873  		0xc1, 0x09, 0xc2, 0x40, 0x10, 0x05, 0xd0, 0xa4, 0x20, 0x05, 0x63, 0x07,
   874  		0x96, 0xa0, 0x05, 0x88, 0x91, 0x25, 0x04, 0x35, 0x0b, 0x6b, 0x2e, 0x1e,
   875  		0xac, 0xcb, 0xf6, 0xc4, 0x90, 0x1e, 0x06, 0xd9, 0xf7, 0x2a, 0xf8, 0xc3,
   876  		0x1f, 0x18, 0xe6, 0x74, 0x78, 0xa6, 0xb6, 0x69, 0x9b, 0xb9, 0xbc, 0xc6,
   877  		0x69, 0x68, 0xaa, 0x75, 0xcd, 0x25, 0x6d, 0x76, 0xd1, 0x29, 0xe2, 0x2c,
   878  		0xf3, 0x77, 0xd1, 0x29, 0xe2, 0xdc, 0x52, 0x99, 0xd2, 0xbd, 0xde, 0x0d,
   879  		0x58, 0xe7, 0xaf, 0x78, 0x03, 0x80, 0x5a, 0xf5, 0x39, 0xcf, 0xe7, 0x4b,
   880  		0x74, 0x8a, 0x38, 0xb5, 0xdf, 0xbf, 0xa5, 0xff, 0x3e, 0x3a, 0x45, 0x9c,
   881  		0xb5, 0xff, 0x7d, 0x74, 0x8e, 0x28, 0xbf, 0xfe, 0xb7, 0xe3, 0xa3, 0xe2,
   882  		0x0f, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   883  		0xf8, 0x17, 0xc7, 0xf7, 0xa7, 0xfb, 0x02, 0x1c, 0xdf, 0x44, 0x21, 0x0c,
   884  		0x3a, 0x00, 0x00}
   885  
   886  	// uncompress test data to sample env file
   887  	rawData, err := unpackTestData(gzipedData)
   888  	c.Assert(err, IsNil)
   889  	err = ioutil.WriteFile(l.envPath, rawData, 0644)
   890  	c.Assert(err, IsNil)
   891  	err = ioutil.WriteFile(l.envPathbak, rawData, 0644)
   892  	c.Assert(err, IsNil)
   893  
   894  	env := lkenv.NewEnv(l.envPath, "", lkenv.V1)
   895  	c.Check(env, NotNil)
   896  	err = env.Load()
   897  	c.Assert(err, IsNil)
   898  	c.Check(env.Get("snap_mode"), Equals, boot.TryingStatus)
   899  	c.Check(env.Get("snap_kernel"), Equals, "kernel-1")
   900  	c.Check(env.Get("snap_try_kernel"), Equals, "kernel-2")
   901  	c.Check(env.Get("snap_core"), Equals, "core-1")
   902  	c.Check(env.Get("snap_try_core"), Equals, "core-2")
   903  	c.Check(env.Get("bootimg_file_name"), Equals, "boot.img")
   904  	c.Check(env.Get("reboot_reason"), Equals, "")
   905  	// first partition should be with label 'boot_a' and 'kernel-1' revision
   906  	p, err := env.GetKernelBootPartition("kernel-1")
   907  	c.Check(p, Equals, "boot_a")
   908  	c.Assert(err, IsNil)
   909  	// test second boot partition is free with label "boot_b"
   910  	p, err = env.FindFreeKernelBootPartition("kernel-2")
   911  	c.Check(p, Equals, "boot_b")
   912  	c.Assert(err, IsNil)
   913  }