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 }