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