gitee.com/mysnapcore/mysnapd@v0.1.0/boot/initramfs.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 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  	"os/exec"
    24  	"time"
    25  
    26  	"gitee.com/mysnapcore/mysnapd/bootloader"
    27  	"gitee.com/mysnapcore/mysnapd/logger"
    28  	"gitee.com/mysnapcore/mysnapd/osutil"
    29  	"gitee.com/mysnapcore/mysnapd/snap"
    30  )
    31  
    32  // InitramfsRunModeSelectSnapsToMount returns a map of the snap paths to mount
    33  // for the specified snap types.
    34  func InitramfsRunModeSelectSnapsToMount(
    35  	typs []snap.Type,
    36  	modeenv *Modeenv,
    37  	rootfsDir string,
    38  ) (map[snap.Type]snap.PlaceInfo, error) {
    39  	var sn snap.PlaceInfo
    40  	var err error
    41  	m := make(map[snap.Type]snap.PlaceInfo)
    42  	for _, typ := range typs {
    43  		// TODO: consider passing a bootStateUpdate20 instead?
    44  		var selectSnapFn func(*Modeenv, string) (snap.PlaceInfo, error)
    45  		switch typ {
    46  		case snap.TypeBase:
    47  			bs := &bootState20Base{}
    48  			selectSnapFn = bs.selectAndCommitSnapInitramfsMount
    49  		case snap.TypeGadget:
    50  			// Do not mount if modeenv does not have gadget entry
    51  			if modeenv.Gadget == "" {
    52  				continue
    53  			}
    54  			selectSnapFn = selectGadgetSnap
    55  		case snap.TypeKernel:
    56  			blOpts := &bootloader.Options{
    57  				Role:        bootloader.RoleRunMode,
    58  				NoSlashBoot: true,
    59  			}
    60  			blDir := InitramfsUbuntuBootDir
    61  			bs := &bootState20Kernel{
    62  				blDir:  blDir,
    63  				blOpts: blOpts,
    64  			}
    65  			selectSnapFn = bs.selectAndCommitSnapInitramfsMount
    66  		}
    67  		sn, err = selectSnapFn(modeenv, rootfsDir)
    68  		if err != nil {
    69  			return nil, err
    70  		}
    71  
    72  		m[typ] = sn
    73  	}
    74  
    75  	return m, nil
    76  }
    77  
    78  // EnsureNextBootToRunMode will mark the bootenv of the recovery bootloader such
    79  // that recover mode is now ready to switch back to run mode upon any reboot.
    80  func EnsureNextBootToRunMode(systemLabel string) error {
    81  	// at the end of the initramfs we need to set the bootenv such that a reboot
    82  	// now at any point will rollback to run mode without additional config or
    83  	// actions
    84  
    85  	opts := &bootloader.Options{
    86  		// setup the recovery bootloader
    87  		Role: bootloader.RoleRecovery,
    88  	}
    89  
    90  	bl, err := bootloader.Find(InitramfsUbuntuSeedDir, opts)
    91  	if err != nil {
    92  		return err
    93  	}
    94  
    95  	m := map[string]string{
    96  		"snapd_recovery_system": systemLabel,
    97  		"snapd_recovery_mode":   "run",
    98  	}
    99  	return bl.SetBootVars(m)
   100  }
   101  
   102  // initramfsReboot triggers a reboot from the initramfs immediately
   103  var initramfsReboot = func() error {
   104  	if osutil.IsTestBinary() {
   105  		panic("initramfsReboot must be mocked in tests")
   106  	}
   107  
   108  	out, err := exec.Command("/sbin/reboot").CombinedOutput()
   109  	if err != nil {
   110  		return osutil.OutputErr(out, err)
   111  	}
   112  
   113  	// reboot command in practice seems to not return, but apparently it is
   114  	// theoretically possible it could return, so to account for this we will
   115  	// loop for a "long" time waiting for the system to be rebooted, and panic
   116  	// after a timeout so that if something goes wrong with the reboot we do
   117  	// exit with some info about the expected reboot
   118  	time.Sleep(10 * time.Minute)
   119  	panic("expected reboot to happen within 10 minutes after calling /sbin/reboot")
   120  }
   121  
   122  func MockInitramfsReboot(f func() error) (restore func()) {
   123  	osutil.MustBeTestBinary("initramfsReboot only can be mocked in tests")
   124  	old := initramfsReboot
   125  	initramfsReboot = f
   126  	return func() {
   127  		initramfsReboot = old
   128  	}
   129  }
   130  
   131  // InitramfsReboot requests the system to reboot. Can be called while in
   132  // initramfs.
   133  func InitramfsReboot() error {
   134  	return initramfsReboot()
   135  }
   136  
   137  // This function implements logic that is usually part of the
   138  // bootloader, but that it is not possible to implement in, for
   139  // instance, piboot. See handling of kernel_status in
   140  // bootloader/assets/data/grub.cfg.
   141  func updateNotScriptableBootloaderStatus(bl bootloader.NotScriptableBootloader) error {
   142  	blVars, err := bl.GetBootVars("kernel_status")
   143  	if err != nil {
   144  		return err
   145  	}
   146  	curKernStatus := blVars["kernel_status"]
   147  	if curKernStatus == "" {
   148  		return nil
   149  	}
   150  
   151  	kVals, err := osutil.KernelCommandLineKeyValues("kernel_status")
   152  	if err != nil {
   153  		return err
   154  	}
   155  	// "" would be the value for the error case, which at this point is any
   156  	// case different to kernel_status=trying in kernel command line and
   157  	// kernel_status=try in configuration file. Note that kernel_status in
   158  	// the file should be only "try" or empty, and for the latter we should
   159  	// have returned a few lines up.
   160  	newStatus := ""
   161  	if kVals["kernel_status"] == "trying" && curKernStatus == "try" {
   162  		newStatus = "trying"
   163  	}
   164  
   165  	logger.Debugf("setting %s kernel_status from %s to %s",
   166  		bl.Name(), curKernStatus, newStatus)
   167  	return bl.SetBootVarsFromInitramfs(map[string]string{"kernel_status": newStatus})
   168  }
   169  
   170  // InitramfsRunModeUpdateBootloaderVars updates bootloader variables
   171  // from the initramfs. This is necessary only for piboot at the
   172  // moment.
   173  func InitramfsRunModeUpdateBootloaderVars() error {
   174  	// For very limited bootloaders we need to change the kernel
   175  	// status from the initramfs as we cannot do that from the
   176  	// bootloader
   177  	blOpts := &bootloader.Options{
   178  		Role:        bootloader.RoleRunMode,
   179  		NoSlashBoot: true,
   180  	}
   181  
   182  	bl, err := bootloader.Find(InitramfsUbuntuBootDir, blOpts)
   183  	if err == nil {
   184  		if nsb, ok := bl.(bootloader.NotScriptableBootloader); ok {
   185  			if err := updateNotScriptableBootloaderStatus(nsb); err != nil {
   186  				logger.Noticef("cannot update %s kernel status: %v", bl.Name(), err)
   187  				return err
   188  			}
   189  		}
   190  	}
   191  
   192  	return nil
   193  }