github.com/chipaca/snappy@v0.0.0-20210104084008-1f06296fe8ad/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 }