github.com/Lephar/snapd@v0.0.0-20210825215435-c7fba9cef4d2/boot/bootstate20_bloader_kernel_state.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2019-2020 Canonical Ltd
     5   *
     6   * This program is free software: you can redistribute it and/or modify
     7   * it under the terms of the GNU General Public License version 3 as
     8   * published by the Free Software Foundation.
     9   *
    10   * This program is distributed in the hope that it will be useful,
    11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
    12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    13   * GNU General Public License for more details.
    14   *
    15   * You should have received a copy of the GNU General Public License
    16   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
    17   *
    18   */
    19  
    20  package boot
    21  
    22  import (
    23  	"fmt"
    24  
    25  	"github.com/snapcore/snapd/bootloader"
    26  	"github.com/snapcore/snapd/snap"
    27  )
    28  
    29  // extractedRunKernelImageBootloaderKernelState implements bootloaderKernelState20 for
    30  // bootloaders that implement ExtractedRunKernelImageBootloader
    31  type extractedRunKernelImageBootloaderKernelState struct {
    32  	// the bootloader
    33  	ebl bootloader.ExtractedRunKernelImageBootloader
    34  	// the current kernel status as read by the bootloader's bootenv
    35  	currentKernelStatus string
    36  	// the current kernel on the bootloader (not the try-kernel)
    37  	currentKernel snap.PlaceInfo
    38  }
    39  
    40  func (bks *extractedRunKernelImageBootloaderKernelState) load() error {
    41  	// get the kernel_status
    42  	m, err := bks.ebl.GetBootVars("kernel_status")
    43  	if err != nil {
    44  		return err
    45  	}
    46  
    47  	bks.currentKernelStatus = m["kernel_status"]
    48  
    49  	// get the current kernel for this bootloader to compare during commit() for
    50  	// markSuccessful() if we booted the current kernel or not
    51  	kernel, err := bks.ebl.Kernel()
    52  	if err != nil {
    53  		return fmt.Errorf("cannot identify kernel snap with bootloader %s: %v", bks.ebl.Name(), err)
    54  	}
    55  
    56  	bks.currentKernel = kernel
    57  
    58  	return nil
    59  }
    60  
    61  func (bks *extractedRunKernelImageBootloaderKernelState) kernel() snap.PlaceInfo {
    62  	return bks.currentKernel
    63  }
    64  
    65  func (bks *extractedRunKernelImageBootloaderKernelState) tryKernel() (snap.PlaceInfo, error) {
    66  	return bks.ebl.TryKernel()
    67  }
    68  
    69  func (bks *extractedRunKernelImageBootloaderKernelState) kernelStatus() string {
    70  	return bks.currentKernelStatus
    71  }
    72  
    73  func (bks *extractedRunKernelImageBootloaderKernelState) markSuccessfulKernel(sn snap.PlaceInfo) error {
    74  	// set the boot vars first, then enable the successful kernel, then disable
    75  	// the old try-kernel, see the comment in bootState20MarkSuccessful.commit()
    76  	// for details
    77  
    78  	// the ordering here is very important for boot reliability!
    79  
    80  	// If we have successfully just booted from a try-kernel and are
    81  	// marking it successful (this implies that snap_kernel=="trying" as set
    82  	// by the boot script), we need to do the following in order (since we
    83  	// have the added complexity of moving the kernel symlink):
    84  	// 1. Update kernel_status to ""
    85  	// 2. Move kernel symlink to point to the new try kernel
    86  	// 3. Remove try-kernel symlink
    87  	// 4. Remove old kernel from modeenv (this happens one level up from this
    88  	//    function)
    89  	//
    90  	// If we got rebooted after step 1, then the bootloader is booting the wrong
    91  	// kernel, but is at least booting a known good kernel and snapd in
    92  	// user-space would be able to figure out the inconsistency.
    93  	// If we got rebooted after step 2, the bootloader would boot from the new
    94  	// try-kernel which is okay because we were in the middle of committing
    95  	// that new kernel as good and all that's left is for snapd to cleanup
    96  	// the left-over try-kernel symlink.
    97  	//
    98  	// If instead we had moved the kernel symlink first to point to the new try
    99  	// kernel, and got rebooted before the kernel_status was updated, we would
   100  	// have kernel_status="trying" which would cause the bootloader to think
   101  	// the boot failed, and revert to booting using the kernel symlink, but that
   102  	// now points to the new kernel we were trying and we did not successfully
   103  	// boot from that kernel to know we should trust it.
   104  	//
   105  	// Removing the old kernel from the modeenv needs to happen after it is
   106  	// impossible for the bootloader to boot from that kernel, otherwise we
   107  	// could end up in a state where the bootloader doesn't want to boot the
   108  	// new kernel, but the initramfs doesn't trust the old kernel and we are
   109  	// stuck. As such, do this last, after the symlink no longer exists.
   110  	//
   111  	// The try-kernel symlink removal should happen last because it will not
   112  	// affect anything, except that if it was removed before updating
   113  	// kernel_status to "", the bootloader will think that the try kernel failed
   114  	// to boot and fall back to booting the old kernel which is safe.
   115  
   116  	// always set the boot vars first before mutating any of the kernel symlinks
   117  	// etc.
   118  	// for markSuccessful, we will always set the status to Default, even if
   119  	// technically this boot wasn't "successful" - it was successful in the
   120  	// sense that we booted some combination of boot snaps and made it all the
   121  	// way to snapd in user space
   122  	if bks.currentKernelStatus != DefaultStatus {
   123  		m := map[string]string{
   124  			"kernel_status": DefaultStatus,
   125  		}
   126  
   127  		// set the boot variables
   128  		err := bks.ebl.SetBootVars(m)
   129  		if err != nil {
   130  			return err
   131  		}
   132  	}
   133  
   134  	// if the kernel we booted is not the current one, we must have tried
   135  	// a new kernel, so enable that one as the current one now
   136  	if bks.currentKernel.Filename() != sn.Filename() {
   137  		err := bks.ebl.EnableKernel(sn)
   138  		if err != nil {
   139  			return err
   140  		}
   141  	}
   142  
   143  	// always disable the try kernel snap to cleanup in case we have upgrade
   144  	// failures which leave behind try-kernel.efi
   145  	err := bks.ebl.DisableTryKernel()
   146  	if err != nil {
   147  		return err
   148  	}
   149  
   150  	return nil
   151  }
   152  
   153  func (bks *extractedRunKernelImageBootloaderKernelState) setNextKernel(sn snap.PlaceInfo, status string) error {
   154  	// always enable the try-kernel first, if we did the reverse and got
   155  	// rebooted after setting the boot vars but before enabling the try-kernel
   156  	// we could get stuck where the bootloader can't find the try-kernel and
   157  	// gets stuck waiting for a user to reboot, at which point we would fallback
   158  	// see i.e. https://github.com/snapcore/pc-amd64-gadget/issues/36
   159  	if sn.Filename() != bks.currentKernel.Filename() {
   160  		err := bks.ebl.EnableTryKernel(sn)
   161  		if err != nil {
   162  			return err
   163  		}
   164  	}
   165  
   166  	// only if the new kernel status is different from what we read should we
   167  	// run SetBootVars() to minimize wear/corruption possibility on the bootenv
   168  	if status != bks.currentKernelStatus {
   169  		m := map[string]string{
   170  			"kernel_status": status,
   171  		}
   172  
   173  		// set the boot variables
   174  		return bks.ebl.SetBootVars(m)
   175  	}
   176  
   177  	return nil
   178  }
   179  
   180  // envRefExtractedKernelBootloaderKernelState implements bootloaderKernelState20 for
   181  // bootloaders that only support using bootloader env and i.e. don't support
   182  // ExtractedRunKernelImageBootloader
   183  type envRefExtractedKernelBootloaderKernelState struct {
   184  	// the bootloader
   185  	bl bootloader.Bootloader
   186  
   187  	// the current state of env
   188  	env map[string]string
   189  
   190  	// the state of env to commit
   191  	toCommit map[string]string
   192  
   193  	// the current kernel
   194  	kern snap.PlaceInfo
   195  }
   196  
   197  func (envbks *envRefExtractedKernelBootloaderKernelState) load() error {
   198  	// for uc20, we only care about kernel_status, snap_kernel, and
   199  	// snap_try_kernel
   200  	m, err := envbks.bl.GetBootVars("kernel_status", "snap_kernel", "snap_try_kernel")
   201  	if err != nil {
   202  		return err
   203  	}
   204  
   205  	// the default commit env is the same state as the current env
   206  	envbks.env = m
   207  	envbks.toCommit = make(map[string]string, len(m))
   208  	for k, v := range m {
   209  		envbks.toCommit[k] = v
   210  	}
   211  
   212  	// snap_kernel is the current kernel snap
   213  	// parse the filename here because the kernel() method doesn't return an err
   214  	sn, err := snap.ParsePlaceInfoFromSnapFileName(envbks.env["snap_kernel"])
   215  	if err != nil {
   216  		return err
   217  	}
   218  
   219  	envbks.kern = sn
   220  
   221  	return nil
   222  }
   223  
   224  func (envbks *envRefExtractedKernelBootloaderKernelState) kernel() snap.PlaceInfo {
   225  	return envbks.kern
   226  }
   227  
   228  func (envbks *envRefExtractedKernelBootloaderKernelState) tryKernel() (snap.PlaceInfo, error) {
   229  	// empty snap_try_kernel is special case
   230  	if envbks.env["snap_try_kernel"] == "" {
   231  		return nil, bootloader.ErrNoTryKernelRef
   232  	}
   233  	sn, err := snap.ParsePlaceInfoFromSnapFileName(envbks.env["snap_try_kernel"])
   234  	if err != nil {
   235  		return nil, err
   236  	}
   237  
   238  	return sn, nil
   239  }
   240  
   241  func (envbks *envRefExtractedKernelBootloaderKernelState) kernelStatus() string {
   242  	return envbks.env["kernel_status"]
   243  }
   244  
   245  func (envbks *envRefExtractedKernelBootloaderKernelState) commonStateCommitUpdate(sn snap.PlaceInfo, bootvar string) bool {
   246  	envChanged := false
   247  
   248  	// check kernel_status
   249  	if envbks.env["kernel_status"] != envbks.toCommit["kernel_status"] {
   250  		envChanged = true
   251  	}
   252  
   253  	// if the specified snap is not the current snap, update the bootvar
   254  	if sn.Filename() != envbks.kern.Filename() {
   255  		envbks.toCommit[bootvar] = sn.Filename()
   256  		envChanged = true
   257  	}
   258  
   259  	return envChanged
   260  }
   261  
   262  func (envbks *envRefExtractedKernelBootloaderKernelState) markSuccessfulKernel(sn snap.PlaceInfo) error {
   263  	// the ordering here doesn't matter, as the only actual state we mutate is
   264  	// writing the bootloader env vars, so just do that once at the end after
   265  	// processing all the changes
   266  
   267  	// always set kernel_status to DefaultStatus
   268  	envbks.toCommit["kernel_status"] = DefaultStatus
   269  	envChanged := envbks.commonStateCommitUpdate(sn, "snap_kernel")
   270  
   271  	// if the snap_try_kernel is set, we should unset that to both cleanup after
   272  	// a successful trying -> "" transition, but also to cleanup if we got
   273  	// rebooted during the process and have it leftover
   274  	if envbks.env["snap_try_kernel"] != "" {
   275  		envChanged = true
   276  		envbks.toCommit["snap_try_kernel"] = ""
   277  	}
   278  
   279  	if envChanged {
   280  		return envbks.bl.SetBootVars(envbks.toCommit)
   281  	}
   282  
   283  	return nil
   284  }
   285  
   286  func (envbks *envRefExtractedKernelBootloaderKernelState) setNextKernel(sn snap.PlaceInfo, status string) error {
   287  	envbks.toCommit["kernel_status"] = status
   288  	bootenvChanged := envbks.commonStateCommitUpdate(sn, "snap_try_kernel")
   289  
   290  	if bootenvChanged {
   291  		return envbks.bl.SetBootVars(envbks.toCommit)
   292  	}
   293  
   294  	return nil
   295  }