github.com/bugraaydogar/snapd@v0.0.0-20210315170335-8c70bb858939/boot/boot.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 "errors" 24 "fmt" 25 26 "github.com/snapcore/snapd/asserts" 27 "github.com/snapcore/snapd/bootloader" 28 "github.com/snapcore/snapd/snap" 29 ) 30 31 const ( 32 // DefaultStatus is the value of a status boot variable when nothing is 33 // being tried 34 DefaultStatus = "" 35 // TryStatus is the value of a status boot variable when something is about 36 // to be tried 37 TryStatus = "try" 38 // TryingStatus is the value of a status boot variable after we have 39 // attempted a boot with a try snap - this status is only set in the early 40 // boot sequence (bootloader, initramfs, etc.) 41 TryingStatus = "trying" 42 ) 43 44 // A BootParticipant handles the boot process details for a snap involved in it. 45 type BootParticipant interface { 46 // SetNextBoot will schedule the snap to be used in the next boot. For 47 // base snaps it is up to the caller to select the right bootable base 48 // (from the model assertion). It is a noop for not relevant snaps. 49 // Otherwise it returns whether a reboot is required. 50 SetNextBoot() (rebootRequired bool, err error) 51 52 // Is this a trivial implementation of the interface? 53 IsTrivial() bool 54 } 55 56 // A BootKernel handles the bootloader setup of a kernel. 57 type BootKernel interface { 58 // RemoveKernelAssets removes the unpacked kernel/initrd for the given 59 // kernel snap. 60 RemoveKernelAssets() error 61 // ExtractKernelAssets extracts kernel/initrd/dtb data from the given 62 // kernel snap, if required, to a versioned bootloader directory so 63 // that the bootloader can use it. 64 ExtractKernelAssets(snap.Container) error 65 // Is this a trivial implementation of the interface? 66 IsTrivial() bool 67 } 68 69 type trivial struct{} 70 71 func (trivial) SetNextBoot() (bool, error) { return false, nil } 72 func (trivial) IsTrivial() bool { return true } 73 func (trivial) RemoveKernelAssets() error { return nil } 74 func (trivial) ExtractKernelAssets(snap.Container) error { return nil } 75 76 // ensure trivial is a BootParticipant 77 var _ BootParticipant = trivial{} 78 79 // ensure trivial is a Kernel 80 var _ BootKernel = trivial{} 81 82 // Device carries information about the device model and mode that is 83 // relevant to boot. Note snapstate.DeviceContext implements this, and that's 84 // the expected use case. 85 type Device interface { 86 RunMode() bool 87 Classic() bool 88 89 Kernel() string 90 Base() string 91 92 HasModeenv() bool 93 94 Model() *asserts.Model 95 } 96 97 // Participant figures out what the BootParticipant is for the given 98 // arguments, and returns it. If the snap does _not_ participate in 99 // the boot process, the returned object will be a NOP, so it's safe 100 // to call anything on it always. 101 // 102 // Currently, on classic, nothing is a boot participant (returned will 103 // always be NOP). 104 func Participant(s snap.PlaceInfo, t snap.Type, dev Device) BootParticipant { 105 if applicable(s, t, dev) { 106 bs, err := bootStateFor(t, dev) 107 if err != nil { 108 // all internal errors at this point 109 panic(err) 110 } 111 return &coreBootParticipant{s: s, bs: bs} 112 } 113 return trivial{} 114 } 115 116 // bootloaderOptionsForDeviceKernel returns a set of bootloader options that 117 // enable correct kernel extraction and removal for given device 118 func bootloaderOptionsForDeviceKernel(dev Device) *bootloader.Options { 119 if !dev.HasModeenv() { 120 return nil 121 } 122 // find the run-mode bootloader with its kernel support for UC20 123 return &bootloader.Options{ 124 Role: bootloader.RoleRunMode, 125 } 126 } 127 128 // Kernel checks that the given arguments refer to a kernel snap 129 // that participates in the boot process, and returns the associated 130 // BootKernel, or a trivial implementation otherwise. 131 func Kernel(s snap.PlaceInfo, t snap.Type, dev Device) BootKernel { 132 if t == snap.TypeKernel && applicable(s, t, dev) { 133 return &coreKernel{s: s, bopts: bootloaderOptionsForDeviceKernel(dev)} 134 } 135 return trivial{} 136 } 137 138 func applicable(s snap.PlaceInfo, t snap.Type, dev Device) bool { 139 if dev.Classic() { 140 return false 141 } 142 // In ephemeral modes we never need to care about updating the boot 143 // config. This will be done via boot.MakeBootable(). 144 if !dev.RunMode() { 145 return false 146 } 147 148 if t != snap.TypeOS && t != snap.TypeKernel && t != snap.TypeBase { 149 // note we don't currently have anything useful to do with gadgets 150 return false 151 } 152 153 switch t { 154 case snap.TypeKernel: 155 if s.InstanceName() != dev.Kernel() { 156 // a remodel might leave you in this state 157 return false 158 } 159 case snap.TypeBase, snap.TypeOS: 160 base := dev.Base() 161 if base == "" { 162 base = "core" 163 } 164 if s.InstanceName() != base { 165 return false 166 } 167 } 168 169 return true 170 } 171 172 // bootState exposes the boot state for a type of boot snap during 173 // normal running state, i.e. after the pivot_root and after the initramfs. 174 type bootState interface { 175 // revisions retrieves the revisions of the current snap and 176 // the try snap (only the latter might not be set), and 177 // the status of the trying snap. 178 // Note that the error could be only specific to the try snap, in which case 179 // curSnap may still be non-nil and valid. Callers concerned with robustness 180 // should always inspect a non-nil error with isTrySnapError, and use 181 // curSnap instead if the error is only for the trySnap or tryingStatus. 182 revisions() (curSnap, trySnap snap.PlaceInfo, tryingStatus string, err error) 183 184 // setNext lazily implements setting the next boot target for 185 // the type's boot snap. actually committing the update 186 // is done via the returned bootStateUpdate's commit method. 187 setNext(s snap.PlaceInfo) (rebootRequired bool, u bootStateUpdate, err error) 188 189 // markSuccessful lazily implements marking the boot 190 // successful for the type's boot snap. The actual committing 191 // of the update is done via bootStateUpdate's commit, that 192 // way different markSuccessful can be folded together. 193 markSuccessful(bootStateUpdate) (bootStateUpdate, error) 194 } 195 196 // successfulBootState exposes the state of resources requiring bookkeeping on a 197 // successful boot. 198 type successfulBootState interface { 199 // markSuccessful lazily implements marking the boot 200 // successful for the given type of resource. 201 markSuccessful(bootStateUpdate) (bootStateUpdate, error) 202 } 203 204 // bootStateFor finds the right bootState implementation of the given 205 // snap type and Device, if applicable. 206 func bootStateFor(typ snap.Type, dev Device) (s bootState, err error) { 207 if !dev.RunMode() { 208 return nil, fmt.Errorf("internal error: no boot state handling for ephemeral modes") 209 } 210 newBootState := newBootState16 211 if dev.HasModeenv() { 212 newBootState = newBootState20 213 } 214 switch typ { 215 case snap.TypeOS, snap.TypeBase: 216 return newBootState(snap.TypeBase, dev), nil 217 case snap.TypeKernel: 218 return newBootState(snap.TypeKernel, dev), nil 219 default: 220 return nil, fmt.Errorf("internal error: no boot state handling for snap type %q", typ) 221 } 222 } 223 224 // InUseFunc is a function to check if the snap is in use or not. 225 type InUseFunc func(name string, rev snap.Revision) bool 226 227 func fixedInUse(inUse bool) InUseFunc { 228 return func(string, snap.Revision) bool { 229 return inUse 230 } 231 } 232 233 // InUse returns a checker for whether a given name/revision is used in the 234 // boot environment for snaps of the relevant snap type. 235 func InUse(typ snap.Type, dev Device) (InUseFunc, error) { 236 if dev.Classic() { 237 // no boot state on classic 238 return fixedInUse(false), nil 239 } 240 if !dev.RunMode() { 241 // ephemeral mode, block manipulations for now 242 return fixedInUse(true), nil 243 } 244 switch typ { 245 case snap.TypeKernel, snap.TypeBase, snap.TypeOS: 246 break 247 default: 248 return fixedInUse(false), nil 249 } 250 cands := make([]snap.PlaceInfo, 0, 2) 251 s, err := bootStateFor(typ, dev) 252 if err != nil { 253 return nil, err 254 } 255 cand, tryCand, _, err := s.revisions() 256 if err != nil { 257 return nil, err 258 } 259 cands = append(cands, cand) 260 if tryCand != nil { 261 cands = append(cands, tryCand) 262 } 263 264 return func(name string, rev snap.Revision) bool { 265 for _, cand := range cands { 266 if cand.SnapName() == name && cand.SnapRevision() == rev { 267 return true 268 } 269 } 270 return false 271 }, nil 272 } 273 274 var ( 275 // ErrBootNameAndRevisionNotReady is returned when the boot revision is not 276 // established yet. 277 ErrBootNameAndRevisionNotReady = errors.New("boot revision not yet established") 278 ) 279 280 // GetCurrentBoot returns the currently set name and revision for boot for the given 281 // type of snap, which can be snap.TypeBase (or snap.TypeOS), or snap.TypeKernel. 282 // Returns ErrBootNameAndRevisionNotReady if the values are temporarily not established. 283 func GetCurrentBoot(t snap.Type, dev Device) (snap.PlaceInfo, error) { 284 s, err := bootStateFor(t, dev) 285 if err != nil { 286 return nil, err 287 } 288 289 snap, _, status, err := s.revisions() 290 if err != nil { 291 return nil, err 292 } 293 294 if status == TryingStatus { 295 return nil, ErrBootNameAndRevisionNotReady 296 } 297 298 return snap, nil 299 } 300 301 // bootStateUpdate carries the state for an on-going boot state update. 302 // At the end it can be used to commit it. 303 type bootStateUpdate interface { 304 commit() error 305 } 306 307 // MarkBootSuccessful marks the current boot as successful. This means 308 // that snappy will consider this combination of kernel/os a valid 309 // target for rollback. 310 // 311 // The states that a boot goes through for UC16/18 are the following: 312 // - By default snap_mode is "" in which case the bootloader loads 313 // two squashfs'es denoted by variables snap_core and snap_kernel. 314 // - On a refresh of core/kernel snapd will set snap_mode=try and 315 // will also set snap_try_{core,kernel} to the core/kernel that 316 // will be tried next. 317 // - On reboot the bootloader will inspect the snap_mode and if the 318 // mode is set to "try" it will set "snap_mode=trying" and then 319 // try to boot the snap_try_{core,kernel}". 320 // - On a successful boot snapd resets snap_mode to "" and copies 321 // snap_try_{core,kernel} to snap_{core,kernel}. The snap_try_* 322 // values are cleared afterwards. 323 // - On a failing boot the bootloader will see snap_mode=trying which 324 // means snapd did not start successfully. In this case the bootloader 325 // will set snap_mode="" and the system will boot with the known good 326 // values from snap_{core,kernel} 327 func MarkBootSuccessful(dev Device) error { 328 const errPrefix = "cannot mark boot successful: %s" 329 330 var u bootStateUpdate 331 for _, t := range []snap.Type{snap.TypeBase, snap.TypeKernel} { 332 s, err := bootStateFor(t, dev) 333 if err != nil { 334 return err 335 } 336 u, err = s.markSuccessful(u) 337 if err != nil { 338 return fmt.Errorf(errPrefix, err) 339 } 340 } 341 342 if dev.HasModeenv() { 343 for _, bs := range []successfulBootState{ 344 trustedAssetsBootState(dev), 345 trustedCommandLineBootState(dev), 346 recoverySystemsBootState(dev), 347 } { 348 var err error 349 u, err = bs.markSuccessful(u) 350 if err != nil { 351 return fmt.Errorf(errPrefix, err) 352 } 353 } 354 } 355 356 if u != nil { 357 if err := u.commit(); err != nil { 358 return fmt.Errorf(errPrefix, err) 359 } 360 } 361 return nil 362 } 363 364 var ErrUnsupportedSystemMode = errors.New("system mode is unsupported") 365 366 // SetRecoveryBootSystemAndMode configures the recovery bootloader to boot into 367 // the given recovery system in a particular mode. Returns 368 // ErrUnsupportedSystemMode when booting into a recovery system is not supported 369 // by the device. 370 func SetRecoveryBootSystemAndMode(dev Device, systemLabel, mode string) error { 371 if !dev.HasModeenv() { 372 // only UC20 devices are supported 373 return ErrUnsupportedSystemMode 374 } 375 if systemLabel == "" { 376 return fmt.Errorf("internal error: system label is unset") 377 } 378 if mode == "" { 379 return fmt.Errorf("internal error: system mode is unset") 380 } 381 382 opts := &bootloader.Options{ 383 // setup the recovery bootloader 384 Role: bootloader.RoleRecovery, 385 } 386 // TODO:UC20: should the recovery partition stay around as RW during run 387 // mode all the time? 388 bl, err := bootloader.Find(InitramfsUbuntuSeedDir, opts) 389 if err != nil { 390 return err 391 } 392 393 m := map[string]string{ 394 "snapd_recovery_system": systemLabel, 395 "snapd_recovery_mode": mode, 396 } 397 return bl.SetBootVars(m) 398 } 399 400 // UpdateManagedBootConfigs updates managed boot config assets if those are 401 // present for the ubuntu-boot bootloader. Returns true when an update was 402 // carried out. 403 func UpdateManagedBootConfigs(dev Device) (updated bool, err error) { 404 if !dev.HasModeenv() { 405 // only UC20 devices use managed boot config 406 return false, nil 407 } 408 if !dev.RunMode() { 409 return false, fmt.Errorf("internal error: boot config can only be updated in run mode") 410 } 411 return updateManagedBootConfigForBootloader(dev, ModeRun) 412 } 413 414 func updateManagedBootConfigForBootloader(dev Device, mode string) (updated bool, err error) { 415 if mode != ModeRun { 416 return false, fmt.Errorf("internal error: updating boot config of recovery bootloader is not supported yet") 417 } 418 419 opts := &bootloader.Options{ 420 Role: bootloader.RoleRunMode, 421 NoSlashBoot: true, 422 } 423 tbl, err := getBootloaderManagingItsAssets(InitramfsUbuntuBootDir, opts) 424 if err != nil { 425 if err == errBootConfigNotManaged { 426 // we're not managing this bootloader's boot config 427 return false, nil 428 } 429 return false, err 430 } 431 // boot config update can lead to a change of kernel command line 432 if err := observeCommandLineUpdate(dev.Model()); err != nil { 433 return false, err 434 } 435 return tbl.UpdateBootConfig() 436 }