gopkg.in/ubuntu-core/snappy.v0@v0.0.0-20210902073436-25a8614f10a6/bootloader/lkenv/lkenv.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
    21  
    22  import (
    23  	"bytes"
    24  	"encoding/binary"
    25  	"fmt"
    26  	"hash/crc32"
    27  	"os"
    28  
    29  	"golang.org/x/xerrors"
    30  
    31  	"github.com/snapcore/snapd/logger"
    32  	"github.com/snapcore/snapd/osutil"
    33  	"github.com/snapcore/snapd/strutil"
    34  )
    35  
    36  const (
    37  	SNAP_BOOTSELECT_VERSION_V1 = 0x00010001
    38  	SNAP_BOOTSELECT_VERSION_V2 = 0x00010010
    39  )
    40  
    41  // const SNAP_BOOTSELECT_SIGNATURE ('S' | ('B' << 8) | ('s' << 16) | ('e' << 24))
    42  // value comes from S(Snap)B(Boot)se(select)
    43  const SNAP_BOOTSELECT_SIGNATURE = 0x53 | 0x42<<8 | 0x73<<16 | 0x65<<24
    44  
    45  // const SNAP_BOOTSELECT_RECOVERY_SIGNATURE ('S' | ('R' << 8) | ('s' << 16) | ('e' << 24))
    46  // value comes from S(Snap)R(Recovery)se(select)
    47  const SNAP_BOOTSELECT_RECOVERY_SIGNATURE = 0x53 | 0x52<<8 | 0x73<<16 | 0x65<<24
    48  
    49  // SNAP_FILE_NAME_MAX_LEN is the maximum size of a C string representing a snap name,
    50  // such as for a kernel snap revision.
    51  const SNAP_FILE_NAME_MAX_LEN = 256
    52  
    53  // SNAP_BOOTIMG_PART_NUM  is the number of available boot image partitions
    54  const SNAP_BOOTIMG_PART_NUM = 2
    55  
    56  // SNAP_RUN_BOOTIMG_PART_NUM is the number of available boot image partitions
    57  // for uc20 for kernel/try-kernel in run mode
    58  const SNAP_RUN_BOOTIMG_PART_NUM = 2
    59  
    60  /** maximum number of available bootimg partitions for recovery systems, min 5
    61   *  NOTE: the number of actual bootimg partitions usable is determined by the
    62   *  gadget, this just sets the upper bound of maximum number of recovery systems
    63   *  a gadget could define without needing changes here
    64   */
    65  const SNAP_RECOVERY_BOOTIMG_PART_NUM = 10
    66  
    67  /* Default boot image file name to be used from kernel snap */
    68  const BOOTIMG_DEFAULT_NAME = "boot.img"
    69  
    70  // for accessing the 	Bootimg_matrix
    71  const (
    72  	// the boot image partition label itself
    73  	MATRIX_ROW_PARTITION = 0
    74  	// the value of the boot image partition label mapping (i.e. the kernel
    75  	// revision or the recovery system label, depending on which specific
    76  	// matrix is being operated on)
    77  	MATRIX_ROW_VALUE = 1
    78  )
    79  
    80  type Version int
    81  
    82  const (
    83  	V1 Version = iota
    84  	V2Run
    85  	V2Recovery
    86  )
    87  
    88  // Number returns the Version of the lkenv version as it is encoded in the
    89  func (v Version) Number() uint32 {
    90  	switch v {
    91  	case V1:
    92  		return SNAP_BOOTSELECT_VERSION_V1
    93  	case V2Run, V2Recovery:
    94  		return SNAP_BOOTSELECT_VERSION_V2
    95  	default:
    96  		panic(fmt.Sprintf("unknown lkenv version number: %v", v))
    97  	}
    98  }
    99  
   100  // Signature returns the Signature of the lkenv version.
   101  func (v Version) Signature() uint32 {
   102  	switch v {
   103  	case V1, V2Run:
   104  		return SNAP_BOOTSELECT_SIGNATURE
   105  	case V2Recovery:
   106  		return SNAP_BOOTSELECT_RECOVERY_SIGNATURE
   107  	default:
   108  		panic(fmt.Sprintf("unknown lkenv version number: %v", v))
   109  	}
   110  }
   111  
   112  type envVariant interface {
   113  	// get returns the value of a key in the env object.
   114  	get(string) string
   115  
   116  	// set sets a key to a value in the env object.
   117  	set(string, string)
   118  
   119  	// currentCrc32 is a helper method to return the value of the crc32 stored in the
   120  	// environment variable - it is NOT a method to calculate the current value,
   121  	// it is used to store the crc32 for helper methods that validate the crc32
   122  	// independently of what is in the environment.
   123  	currentCrc32() uint32
   124  	// currentVersion is the same kind of helper method as currentCrc32(),
   125  	// always returning the value from the object itself.
   126  	currentVersion() uint32
   127  	// currentSignature is the same kind of helper method as currentCrc32(),
   128  	// always returning the value from the object itself.
   129  	currentSignature() uint32
   130  
   131  	// bootImgKernelMatrix returns the boot image matrix from the environment
   132  	// which stores the kernel revisions for the boot image partitions. The boot
   133  	// image matrix is used for various exported methods such as
   134  	// SetBootPartitionKernel(), etc.
   135  	bootImgKernelMatrix() (bootimgMatrixGeneric, error)
   136  
   137  	// bootImgRecoverySystemMatrix returns the boot image matrix from the
   138  	// environment which stores the recovery system labels for the boot image
   139  	// partitions. The boot image matrix is used for various recovery system
   140  	// methods such as FindFreeRecoverySystemBootPartition(), etc.
   141  	bootImgRecoverySystemMatrix() (bootimgMatrixGeneric, error)
   142  }
   143  
   144  var (
   145  	// the variant implementations must all implement envVariant
   146  	_ = envVariant(&SnapBootSelect_v1{})
   147  	_ = envVariant(&SnapBootSelect_v2_run{})
   148  	_ = envVariant(&SnapBootSelect_v2_recovery{})
   149  )
   150  
   151  // Env contains the data of the little kernel environment
   152  type Env struct {
   153  	// path is the primary lkenv object file, it can be a regular file during
   154  	// build time, or it can be a partition device node at run time
   155  	path string
   156  	// pathbak is the backup lkenv object file, it too can either be a regular
   157  	// file during build time, or a partition device node at run time, and it is
   158  	// typically at prepare-image time given by "<path>" + "bak", i.e.
   159  	// $PWD/lk.conf and $PWD/lk.confbak but will be different device nodes for
   160  	// different partitions at runtime.
   161  	pathbak string
   162  	// version is the configured version of the lkenv object from NewEnv.
   163  	version Version
   164  	// variant is the internal implementation of the lkenv object, dependent on
   165  	// the version. It is tracked separately such that we can verify a given
   166  	// variant matches the specified version when loading an lkenv object from
   167  	// disk.
   168  	variant envVariant
   169  }
   170  
   171  // cToGoString convert string in passed byte array into string type
   172  // if string in byte array is not terminated, empty string is returned
   173  func cToGoString(c []byte) string {
   174  	if end := bytes.IndexByte(c, 0); end >= 0 {
   175  		return string(c[:end])
   176  	}
   177  	// no trailing \0 - return ""
   178  	return ""
   179  }
   180  
   181  // copyString copy passed string into byte array
   182  // make sure string is terminated
   183  // if string does not fit into byte array, it will be concatenated
   184  func copyString(b []byte, s string) {
   185  	sl := len(s)
   186  	bs := len(b)
   187  	if bs > sl {
   188  		copy(b[:], s)
   189  		b[sl] = 0
   190  	} else {
   191  		copy(b[:bs-1], s)
   192  		b[bs-1] = 0
   193  	}
   194  }
   195  
   196  // NewEnv creates a new lkenv object referencing the primary bootloader
   197  // environment file at path with the specified version. If the specified filed
   198  // is expected to be a valid lkenv object, then the object should be loaded with
   199  // the Load() method, otherwise the lkenv object can be manipulated in memory
   200  // and later written to disk with Save().
   201  func NewEnv(path, backupPath string, version Version) *Env {
   202  	if backupPath == "" {
   203  		// legacy behavior is for the backup file to be the same name/dir, but
   204  		// with "bak" appended to it
   205  		backupPath = path + "bak"
   206  	}
   207  	e := &Env{
   208  		path:    path,
   209  		pathbak: backupPath,
   210  		version: version,
   211  	}
   212  
   213  	switch version {
   214  	case V1:
   215  		e.variant = newV1()
   216  	case V2Recovery:
   217  		e.variant = newV2Recovery()
   218  	case V2Run:
   219  		e.variant = newV2Run()
   220  	}
   221  	return e
   222  }
   223  
   224  // Load will load the lk bootloader environment from it's configured primary
   225  // environment file, and if that fails it will fallback to trying the backup
   226  // environment file.
   227  func (l *Env) Load() error {
   228  	err := l.LoadEnv(l.path)
   229  	if err != nil {
   230  		logger.Noticef("cannot load primary bootloader environment: %v\n", err)
   231  		logger.Noticef("attempting to load backup bootloader environment\n")
   232  		return l.LoadEnv(l.pathbak)
   233  	}
   234  	return nil
   235  }
   236  
   237  type compatErrNotExist struct {
   238  	err error
   239  }
   240  
   241  func (e compatErrNotExist) Error() string {
   242  	return e.err.Error()
   243  }
   244  
   245  func (e compatErrNotExist) Unwrap() error {
   246  	// for go 1.9 (and 1.10) xerrors compatibility, we check if os.PathError
   247  	// implements Unwrap(), and if not return os.ErrNotExist directly
   248  	if _, ok := e.err.(interface {
   249  		Unwrap() error
   250  	}); !ok {
   251  		return os.ErrNotExist
   252  	}
   253  	return e.err
   254  }
   255  
   256  // LoadEnv loads the lk bootloader environment from the specified file. The
   257  // bootloader environment in the referenced file must be of the same version
   258  // that the Env object was created with using NewEnv.
   259  // The returned error may wrap os.ErrNotExist, so instead of using
   260  // os.IsNotExist, callers should use xerrors.Is(err,os.ErrNotExist) instead.
   261  func (l *Env) LoadEnv(path string) error {
   262  	f, err := os.Open(path)
   263  	if err != nil {
   264  		// TODO: when we drop support for Go 1.9, this code can go away, in Go
   265  		//       1.9 *os.PathError does not implement Unwrap(), and so callers
   266  		//       that try to call xerrors.Is(err,os.ErrNotExist) will fail, so
   267  		//       instead we do our own wrapping first such that when Unwrap() is
   268  		//       called by xerrors.Is() it will see os.ErrNotExist directly when
   269  		//       compiled with a version of Go that does not implement Unwrap()
   270  		//       on os.PathError
   271  		if os.IsNotExist(err) {
   272  			err = compatErrNotExist{err: err}
   273  		}
   274  		fmtStr := "cannot open LK env file: %w"
   275  		return xerrors.Errorf(fmtStr, err)
   276  	}
   277  
   278  	if err := binary.Read(f, binary.LittleEndian, l.variant); err != nil {
   279  		return fmt.Errorf("cannot read LK env from file: %v", err)
   280  	}
   281  
   282  	// validate the version and signatures
   283  	v := l.variant.currentVersion()
   284  	s := l.variant.currentSignature()
   285  	expV := l.version.Number()
   286  	expS := l.version.Signature()
   287  
   288  	if expV != v {
   289  		return fmt.Errorf("cannot validate %s: expected version 0x%X, got 0x%X", path, expV, v)
   290  	}
   291  
   292  	if expS != s {
   293  		return fmt.Errorf("cannot validate %s: expected signature 0x%X, got 0x%X", path, expS, s)
   294  	}
   295  
   296  	// independently calculate crc32 to validate structure
   297  	w := bytes.NewBuffer(nil)
   298  	ss := binary.Size(l.variant)
   299  	w.Grow(ss)
   300  	if err := binary.Write(w, binary.LittleEndian, l.variant); err != nil {
   301  		return fmt.Errorf("cannot write LK env to buffer for validation: %v", err)
   302  	}
   303  
   304  	crc := crc32.ChecksumIEEE(w.Bytes()[:ss-4]) // size of crc32 itself at the end of the structure
   305  	if crc != l.variant.currentCrc32() {
   306  		return fmt.Errorf("cannot validate %s: expected checksum 0x%X, got 0x%X", path, crc, l.variant.currentCrc32())
   307  	}
   308  	logger.Debugf("validated crc32 as 0x%X for lkenv loaded from file %s", l.variant.currentCrc32(), path)
   309  
   310  	return nil
   311  }
   312  
   313  // Save saves the lk bootloader environment to the configured primary
   314  // environment file, and if the backup environment file exists, the backup too.
   315  // Save will also update the CRC32 of the environment when writing the file(s).
   316  func (l *Env) Save() error {
   317  	buf := bytes.NewBuffer(nil)
   318  	ss := binary.Size(l.variant)
   319  	buf.Grow(ss)
   320  	if err := binary.Write(buf, binary.LittleEndian, l.variant); err != nil {
   321  		return fmt.Errorf("cannot write LK env to buffer for saving: %v", err)
   322  	}
   323  
   324  	// calculate crc32
   325  	newCrc32 := crc32.ChecksumIEEE(buf.Bytes()[:ss-4])
   326  	logger.Debugf("calculated lk bootloader environment crc32 as 0x%X to save", newCrc32)
   327  	// note for efficiency's sake to avoid re-writing the whole structure, we
   328  	// re-write _just_ the crc32 to w as little-endian
   329  	buf.Truncate(ss - 4)
   330  	binary.Write(buf, binary.LittleEndian, &newCrc32)
   331  
   332  	err := l.saveEnv(l.path, buf)
   333  	if err != nil {
   334  		logger.Noticef("failed to save primary bootloader environment: %v", err)
   335  	}
   336  	// if there is backup environment file save to it as well
   337  	if osutil.FileExists(l.pathbak) {
   338  		// TODO: if the primary succeeds but saving to the backup fails, we
   339  		// don't return non-nil error here, should we?
   340  		if err := l.saveEnv(l.pathbak, buf); err != nil {
   341  			logger.Noticef("failed to save backup environment: %v", err)
   342  		}
   343  	}
   344  	return err
   345  }
   346  
   347  func (l *Env) saveEnv(path string, buf *bytes.Buffer) error {
   348  	f, err := os.OpenFile(path, os.O_WRONLY, 0660)
   349  	if err != nil {
   350  		return fmt.Errorf("cannot open LK env file for env storing: %v", err)
   351  	}
   352  	defer f.Close()
   353  
   354  	if _, err := f.Write(buf.Bytes()); err != nil {
   355  		return fmt.Errorf("cannot write LK env buf to LK env file: %v", err)
   356  	}
   357  	if err := f.Sync(); err != nil {
   358  		return fmt.Errorf("cannot sync LK env file: %v", err)
   359  	}
   360  	return nil
   361  }
   362  
   363  // Get returns the value of the key from the environment. If the key specified
   364  // is not supported for the environment, the empty string is returned.
   365  func (l *Env) Get(key string) string {
   366  	return l.variant.get(key)
   367  }
   368  
   369  // Set assigns the value to the key in the environment. If the key specified is
   370  // not supported for the environment, nothing happens.
   371  func (l *Env) Set(key, value string) {
   372  	l.variant.set(key, value)
   373  }
   374  
   375  // InitializeBootPartitions sets the boot image partition label names.
   376  // This function should not be used at run time!
   377  // It should be used only at image build time, if partition labels are not
   378  // pre-filled by gadget built, currently it is only used inside snapd for tests.
   379  func (l *Env) InitializeBootPartitions(bootPartLabels ...string) error {
   380  	var matr bootimgMatrixGeneric
   381  	var err error
   382  	// calculate the min/max limits for bootPartLabels
   383  	var min, max int
   384  	switch l.version {
   385  	case V1, V2Run:
   386  		min = 2
   387  		max = 2
   388  		matr, err = l.variant.bootImgKernelMatrix()
   389  	case V2Recovery:
   390  		min = 1
   391  		max = SNAP_RECOVERY_BOOTIMG_PART_NUM
   392  		matr, err = l.variant.bootImgRecoverySystemMatrix()
   393  	}
   394  	if err != nil {
   395  		return err
   396  	}
   397  
   398  	return matr.initializeBootPartitions(bootPartLabels, min, max)
   399  }
   400  
   401  // FindFreeKernelBootPartition finds a free boot image partition to be used for
   402  // a new kernel revision. It ignores the currently installed boot image
   403  // partition used for the active kernel
   404  func (l *Env) FindFreeKernelBootPartition(kernel string) (string, error) {
   405  	matr, err := l.variant.bootImgKernelMatrix()
   406  	if err != nil {
   407  		return "", err
   408  	}
   409  
   410  	// the reserved boot image partition value is just the current snap_kernel
   411  	// if it is set (it could be unset at image build time where the lkenv is
   412  	// unset and has no kernel revision values set for the boot image partitions)
   413  	installedKernels := []string{}
   414  	if installedKernel := l.variant.get("snap_kernel"); installedKernel != "" {
   415  		installedKernels = []string{installedKernel}
   416  	}
   417  	return matr.findFreeBootPartition(installedKernels, kernel)
   418  }
   419  
   420  // GetKernelBootPartition returns the first found boot image partition label
   421  // that contains a reference to the given kernel revision. If the revision was
   422  // not found, a non-nil error is returned.
   423  func (l *Env) GetKernelBootPartition(kernel string) (string, error) {
   424  	matr, err := l.variant.bootImgKernelMatrix()
   425  	if err != nil {
   426  		return "", err
   427  	}
   428  
   429  	bootPart, err := matr.getBootPartWithValue(kernel)
   430  	if err != nil {
   431  		return "", fmt.Errorf("cannot find kernel %q: %v", kernel, err)
   432  	}
   433  	return bootPart, nil
   434  }
   435  
   436  // SetBootPartitionKernel sets the kernel revision reference for the provided
   437  // boot image partition label. It returns a non-nil err if the provided boot
   438  // image partition label was not found.
   439  func (l *Env) SetBootPartitionKernel(bootpart, kernel string) error {
   440  	matr, err := l.variant.bootImgKernelMatrix()
   441  	if err != nil {
   442  		return err
   443  	}
   444  
   445  	return matr.setBootPart(bootpart, kernel)
   446  }
   447  
   448  // RemoveKernelFromBootPartition removes from the boot image matrix the
   449  // first found boot image partition that contains a reference to the given
   450  // kernel revision. If the referenced kernel revision was not found, a non-nil
   451  // err is returned, otherwise the reference is removed and nil is returned.
   452  func (l *Env) RemoveKernelFromBootPartition(kernel string) error {
   453  	matr, err := l.variant.bootImgKernelMatrix()
   454  	if err != nil {
   455  		return err
   456  	}
   457  
   458  	return matr.dropBootPartValue(kernel)
   459  }
   460  
   461  // FindFreeRecoverySystemBootPartition finds a free recovery system boot image
   462  // partition to be used for the recovery kernel from the recovery system. It
   463  // only considers boot image partitions that are currently not set to a recovery
   464  // system to be free.
   465  func (l *Env) FindFreeRecoverySystemBootPartition(recoverySystem string) (string, error) {
   466  	matr, err := l.variant.bootImgRecoverySystemMatrix()
   467  	if err != nil {
   468  		return "", err
   469  	}
   470  
   471  	// when we create a new recovery system partition, we set all current
   472  	// recovery systems as reserved, so first get that list
   473  	currentRecoverySystems := matr.assignedBootPartValues()
   474  	return matr.findFreeBootPartition(currentRecoverySystems, recoverySystem)
   475  }
   476  
   477  // SetBootPartitionRecoverySystem sets the recovery system reference for the
   478  // provided boot image partition. It returns a non-nil err if the provided boot
   479  // partition reference was not found.
   480  func (l *Env) SetBootPartitionRecoverySystem(bootpart, recoverySystem string) error {
   481  	matr, err := l.variant.bootImgRecoverySystemMatrix()
   482  	if err != nil {
   483  		return err
   484  	}
   485  
   486  	return matr.setBootPart(bootpart, recoverySystem)
   487  }
   488  
   489  // GetRecoverySystemBootPartition returns the first found boot image partition
   490  // label that contains a reference to the given recovery system. If the recovery
   491  // system was not found, a non-nil error is returned.
   492  func (l *Env) GetRecoverySystemBootPartition(recoverySystem string) (string, error) {
   493  	matr, err := l.variant.bootImgRecoverySystemMatrix()
   494  	if err != nil {
   495  		return "", err
   496  	}
   497  
   498  	bootPart, err := matr.getBootPartWithValue(recoverySystem)
   499  	if err != nil {
   500  		return "", fmt.Errorf("cannot find recovery system %q: %v", recoverySystem, err)
   501  	}
   502  	return bootPart, nil
   503  }
   504  
   505  // RemoveRecoverySystemFromBootPartition removes from the boot image matrix the
   506  // first found boot partition that contains a reference to the given recovery
   507  // system. If the referenced recovery system was not found, a non-nil err is
   508  // returned, otherwise the reference is removed and nil is returned.
   509  func (l *Env) RemoveRecoverySystemFromBootPartition(recoverySystem string) error {
   510  	matr, err := l.variant.bootImgRecoverySystemMatrix()
   511  	if err != nil {
   512  		return err
   513  	}
   514  
   515  	return matr.dropBootPartValue(recoverySystem)
   516  }
   517  
   518  // GetBootImageName return expected boot image file name in kernel snap. If
   519  // unset, it will return the default boot.img name.
   520  func (l *Env) GetBootImageName() string {
   521  	fn := l.Get("bootimg_file_name")
   522  	if fn != "" {
   523  		return fn
   524  	}
   525  	return BOOTIMG_DEFAULT_NAME
   526  }
   527  
   528  // common matrix helper methods which operate on the boot image matrix, which is
   529  // a mapping of boot image partition label to either a kernel revision or a
   530  // recovery system label.
   531  
   532  // bootimgMatrixGeneric is a generic slice version of the above two matrix types
   533  // which are both statically sized arrays, and thus not able to be used
   534  // interchangeably while the slice is.
   535  type bootimgMatrixGeneric [][2][SNAP_FILE_NAME_MAX_LEN]byte
   536  
   537  // initializeBootPartitions is a test helper method to set all the boot image
   538  // partition labels for a lkenv object, normally this is done by the gadget at
   539  // image build time and not done by snapd, but we do this in tests.
   540  // The min and max arguments are for size checking of the provided array of
   541  // bootPartLabels
   542  func (matr bootimgMatrixGeneric) initializeBootPartitions(bootPartLabels []string, min, max int) error {
   543  	numBootPartLabels := len(bootPartLabels)
   544  
   545  	if numBootPartLabels < min || numBootPartLabels > max {
   546  		return fmt.Errorf("invalid number of boot image partitions, expected %d got %d", len(matr), numBootPartLabels)
   547  	}
   548  	for x, label := range bootPartLabels {
   549  		copyString(matr[x][MATRIX_ROW_PARTITION][:], label)
   550  	}
   551  	return nil
   552  }
   553  
   554  // dropBootPartValue will remove the specified bootPartValue from the boot image
   555  // matrix - it _only_ deletes the value, not the boot image partition label
   556  // itself, , as the boot image partition labels are static for the lifetime of a
   557  // device and should never be changed (as those values correspond to physical
   558  // names of the formatted partitions and we don't yet support repartitioning of
   559  // any kind).
   560  func (matr bootimgMatrixGeneric) dropBootPartValue(bootPartValue string) error {
   561  	for x := range matr {
   562  		if "" != cToGoString(matr[x][MATRIX_ROW_PARTITION][:]) {
   563  			if bootPartValue == cToGoString(matr[x][MATRIX_ROW_VALUE][:]) {
   564  				// clear the string by setting the first element to 0 or NUL
   565  				matr[x][MATRIX_ROW_VALUE][0] = 0
   566  				return nil
   567  			}
   568  		}
   569  	}
   570  
   571  	return fmt.Errorf("cannot find %q in boot image partitions", bootPartValue)
   572  }
   573  
   574  // setBootPart associates the specified boot image partition label to the
   575  // specified value.
   576  func (matr bootimgMatrixGeneric) setBootPart(bootpart, bootPartValue string) error {
   577  	for x := range matr {
   578  		if bootpart == cToGoString(matr[x][MATRIX_ROW_PARTITION][:]) {
   579  			copyString(matr[x][MATRIX_ROW_VALUE][:], bootPartValue)
   580  			return nil
   581  		}
   582  	}
   583  
   584  	return fmt.Errorf("cannot find boot image partition %s", bootpart)
   585  }
   586  
   587  // findFreeBootPartition will return a boot image partition that can be
   588  // used for a new value, specifically skipping the reserved values. It may
   589  // return either a boot image partition that does not contain any value or
   590  // a boot image partition that already contains the specified value. The
   591  // reserved argument is typically used for already installed values, such as the
   592  // currently installed kernel snap revision, so that a new try kernel snap does
   593  // not overwrite the existing installed kernel snap.
   594  func (matr bootimgMatrixGeneric) findFreeBootPartition(reserved []string, newValue string) (string, error) {
   595  	for x := range matr {
   596  		bootPartLabel := cToGoString(matr[x][MATRIX_ROW_PARTITION][:])
   597  		// skip boot image partition labels that are unset, for example this may
   598  		// happen if a system only has 3 physical boot image partitions for
   599  		// recovery system kernels, but the same matrix structure has 10 slots
   600  		// and all 3 usable slots are in use by installed reserved recovery
   601  		// systems.
   602  		if bootPartLabel == "" {
   603  			continue
   604  		}
   605  
   606  		val := cToGoString(matr[x][MATRIX_ROW_VALUE][:])
   607  
   608  		// if the value is exactly the same, as requested return it, this needs
   609  		// to be handled before checking the reserved values since we may
   610  		// sometimes need to find a "free" boot partition for the specific
   611  		// kernel revision that is already installed, thus it will show up in
   612  		// the reserved list, but it will also be newValue
   613  		// this case happens in practice during seeding of kernels on uc16/uc18,
   614  		// where we already extracted the kernel at image build time and we will
   615  		// go to extract the kernel again during seeding
   616  		if val == newValue {
   617  			return bootPartLabel, nil
   618  		}
   619  
   620  		// if this value was reserved, skip it
   621  		if strutil.ListContains(reserved, val) {
   622  			continue
   623  		}
   624  
   625  		// otherwise consider it to be free, even if it was set to something
   626  		// else - this is because callers should be using reserved to prevent
   627  		// overwriting the wrong boot image partition value
   628  		return bootPartLabel, nil
   629  	}
   630  
   631  	return "", fmt.Errorf("cannot find free boot image partition")
   632  }
   633  
   634  // assignedBootPartValues returns all boot image partitions values that are set.
   635  func (matr bootimgMatrixGeneric) assignedBootPartValues() []string {
   636  	bootPartValues := make([]string, 0, len(matr))
   637  	for x := range matr {
   638  		bootPartLabel := cToGoString(matr[x][MATRIX_ROW_PARTITION][:])
   639  		if bootPartLabel != "" {
   640  			// now check the value
   641  			bootPartValue := cToGoString(matr[x][MATRIX_ROW_VALUE][:])
   642  			if bootPartValue != "" {
   643  				bootPartValues = append(bootPartValues, bootPartValue)
   644  			}
   645  		}
   646  	}
   647  
   648  	return bootPartValues
   649  }
   650  
   651  // getBootPartWithValue returns the boot image partition label for the specified value.
   652  // If the boot image partition label does not exist in the matrix, an error will
   653  // be returned.
   654  func (matr bootimgMatrixGeneric) getBootPartWithValue(value string) (string, error) {
   655  	for x := range matr {
   656  		if value == cToGoString(matr[x][MATRIX_ROW_VALUE][:]) {
   657  			return cToGoString(matr[x][MATRIX_ROW_PARTITION][:]), nil
   658  		}
   659  	}
   660  
   661  	return "", fmt.Errorf("no boot image partition has value %q", value)
   662  }