github.com/rigado/snapd@v2.42.5-go-mod+incompatible/boot/boot.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2019 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 "os" 26 "path/filepath" 27 "strings" 28 29 "github.com/snapcore/snapd/asserts" 30 "github.com/snapcore/snapd/bootloader" 31 "github.com/snapcore/snapd/dirs" 32 "github.com/snapcore/snapd/logger" 33 "github.com/snapcore/snapd/snap" 34 ) 35 36 // A BootParticipant handles the boot process details for a snap involved in it. 37 type BootParticipant interface { 38 // SetNextBoot will schedule the snap to be used in the next boot. For 39 // base snaps it is up to the caller to select the right bootable base 40 // (from the model assertion). 41 SetNextBoot() error 42 // ChangeRequiresReboot returns whether a reboot is required to switch 43 // to the snap. 44 ChangeRequiresReboot() bool 45 // Is this a trivial implementation of the interface? 46 IsTrivial() bool 47 } 48 49 // A BootKernel handles the bootloader setup of a kernel. 50 type BootKernel interface { 51 // RemoveKernelAssets removes the unpacked kernel/initrd for the given 52 // kernel snap. 53 RemoveKernelAssets() error 54 // ExtractKernelAssets extracts kernel/initrd/dtb data from the given 55 // kernel snap, if required, to a versioned bootloader directory so 56 // that the bootloader can use it. 57 ExtractKernelAssets(snap.Container) error 58 // Is this a trivial implementation of the interface? 59 IsTrivial() bool 60 } 61 62 type trivial struct{} 63 64 func (trivial) SetNextBoot() error { return nil } 65 func (trivial) ChangeRequiresReboot() bool { return false } 66 func (trivial) IsTrivial() bool { return true } 67 func (trivial) RemoveKernelAssets() error { return nil } 68 func (trivial) ExtractKernelAssets(snap.Container) error { return nil } 69 70 // ensure trivial is a BootParticipant 71 var _ BootParticipant = trivial{} 72 73 // ensure trivial is a Kernel 74 var _ BootKernel = trivial{} 75 76 // Model carries information about the model that is relevant to boot. 77 // Note *asserts.Model implements this, and that's the expected use case. 78 type Model interface { 79 Kernel() string 80 Base() string 81 Classic() bool 82 } 83 84 // Participant figures out what the BootParticipant is for the given 85 // arguments, and returns it. If the snap does _not_ participate in 86 // the boot process, the returned object will be a NOP, so it's safe 87 // to call anything on it always. 88 // 89 // Currently, on classic, nothing is a boot participant (returned will 90 // always be NOP). 91 func Participant(s snap.PlaceInfo, t snap.Type, model Model, onClassic bool) BootParticipant { 92 if applicable(s, t, model, onClassic) { 93 return &coreBootParticipant{s: s, t: t} 94 } 95 return trivial{} 96 } 97 98 // Kernel checks that the given arguments refer to a kernel snap 99 // that participates in the boot process, and returns the associated 100 // BootKernel, or a trivial implementation otherwise. 101 func Kernel(s snap.PlaceInfo, t snap.Type, model Model, onClassic bool) BootKernel { 102 if t == snap.TypeKernel && applicable(s, t, model, onClassic) { 103 return &coreKernel{s: s} 104 } 105 return trivial{} 106 } 107 108 func applicable(s snap.PlaceInfo, t snap.Type, model Model, onClassic bool) bool { 109 if onClassic { 110 return false 111 } 112 if t != snap.TypeOS && t != snap.TypeKernel && t != snap.TypeBase { 113 // note we don't currently have anything useful to do with gadgets 114 return false 115 } 116 117 if model != nil { 118 switch t { 119 case snap.TypeKernel: 120 if s.InstanceName() != model.Kernel() { 121 // a remodel might leave you in this state 122 return false 123 } 124 case snap.TypeBase, snap.TypeOS: 125 base := model.Base() 126 if base == "" { 127 base = "core" 128 } 129 if s.InstanceName() != base { 130 return false 131 } 132 } 133 } 134 135 return true 136 } 137 138 // InUse checks if the given name/revision is used in the 139 // boot environment 140 func InUse(name string, rev snap.Revision) bool { 141 bootloader, err := bootloader.Find("", nil) 142 if err != nil { 143 logger.Noticef("cannot get boot settings: %s", err) 144 return false 145 } 146 147 bootVars, err := bootloader.GetBootVars("snap_kernel", "snap_try_kernel", "snap_core", "snap_try_core") 148 if err != nil { 149 logger.Noticef("cannot get boot vars: %s", err) 150 return false 151 } 152 153 snapFile := filepath.Base(snap.MountFile(name, rev)) 154 for _, bootVar := range bootVars { 155 if bootVar == snapFile { 156 return true 157 } 158 } 159 160 return false 161 } 162 163 var ( 164 ErrBootNameAndRevisionNotReady = errors.New("boot revision not yet established") 165 ) 166 167 type NameAndRevision struct { 168 Name string 169 Revision snap.Revision 170 } 171 172 // GetCurrentBoot returns the currently set name and revision for boot for the given 173 // type of snap, which can be snap.TypeBase (or snap.TypeOS), or snap.TypeKernel. 174 // Returns ErrBootNameAndRevisionNotReady if the values are temporarily not established. 175 func GetCurrentBoot(t snap.Type) (*NameAndRevision, error) { 176 var bootVar, errName string 177 switch t { 178 case snap.TypeKernel: 179 bootVar = "snap_kernel" 180 errName = "kernel" 181 case snap.TypeOS, snap.TypeBase: 182 bootVar = "snap_core" 183 errName = "base" 184 default: 185 return nil, fmt.Errorf("internal error: cannot find boot revision for snap type %q", t) 186 } 187 188 bloader, err := bootloader.Find("", nil) 189 if err != nil { 190 return nil, fmt.Errorf("cannot get boot settings: %s", err) 191 } 192 193 m, err := bloader.GetBootVars(bootVar, "snap_mode") 194 if err != nil { 195 return nil, fmt.Errorf("cannot get boot variables: %s", err) 196 } 197 198 if m["snap_mode"] == "trying" { 199 return nil, ErrBootNameAndRevisionNotReady 200 } 201 202 nameAndRevno, err := nameAndRevnoFromSnap(m[bootVar]) 203 if err != nil { 204 return nil, fmt.Errorf("cannot get name and revision of boot %s: %v", errName, err) 205 } 206 207 return nameAndRevno, nil 208 } 209 210 // nameAndRevnoFromSnap grabs the snap name and revision from the 211 // value of a boot variable. E.g., foo_2.snap -> name "foo", revno 2 212 func nameAndRevnoFromSnap(sn string) (*NameAndRevision, error) { 213 if sn == "" { 214 return nil, fmt.Errorf("boot variable unset") 215 } 216 idx := strings.IndexByte(sn, '_') 217 if idx < 1 { 218 return nil, fmt.Errorf("input %q has invalid format (not enough '_')", sn) 219 } 220 name := sn[:idx] 221 revnoNSuffix := sn[idx+1:] 222 rev, err := snap.ParseRevision(strings.TrimSuffix(revnoNSuffix, ".snap")) 223 if err != nil { 224 return nil, err 225 } 226 return &NameAndRevision{Name: name, Revision: rev}, nil 227 } 228 229 // MarkBootSuccessful marks the current boot as successful. This means 230 // that snappy will consider this combination of kernel/os a valid 231 // target for rollback. 232 // 233 // The states that a boot goes through are the following: 234 // - By default snap_mode is "" in which case the bootloader loads 235 // two squashfs'es denoted by variables snap_core and snap_kernel. 236 // - On a refresh of core/kernel snapd will set snap_mode=try and 237 // will also set snap_try_{core,kernel} to the core/kernel that 238 // will be tried next. 239 // - On reboot the bootloader will inspect the snap_mode and if the 240 // mode is set to "try" it will set "snap_mode=trying" and then 241 // try to boot the snap_try_{core,kernel}". 242 // - On a successful boot snapd resets snap_mode to "" and copies 243 // snap_try_{core,kernel} to snap_{core,kernel}. The snap_try_* 244 // values are cleared afterwards. 245 // - On a failing boot the bootloader will see snap_mode=trying which 246 // means snapd did not start successfully. In this case the bootloader 247 // will set snap_mode="" and the system will boot with the known good 248 // values from snap_{core,kernel} 249 func MarkBootSuccessful() error { 250 bl, err := bootloader.Find("", nil) 251 if err != nil { 252 return fmt.Errorf("cannot mark boot successful: %s", err) 253 } 254 m, err := bl.GetBootVars("snap_mode", "snap_try_core", "snap_try_kernel") 255 if err != nil { 256 return err 257 } 258 259 // snap_mode goes from "" -> "try" -> "trying" -> "" 260 // so if we are not in "trying" mode, nothing to do here 261 if m["snap_mode"] != "trying" { 262 return nil 263 } 264 265 // update the boot vars 266 for _, k := range []string{"kernel", "core"} { 267 tryBootVar := fmt.Sprintf("snap_try_%s", k) 268 bootVar := fmt.Sprintf("snap_%s", k) 269 // update the boot vars 270 if m[tryBootVar] != "" { 271 m[bootVar] = m[tryBootVar] 272 m[tryBootVar] = "" 273 } 274 } 275 m["snap_mode"] = "" 276 277 return bl.SetBootVars(m) 278 } 279 280 // MakeBootable sets up the image filesystem with the given rootdir 281 // such that it can be booted. bootWith is a map from the paths in the 282 // image seed for kernel and boot base to their snap info. This 283 // entails: 284 // - installing the bootloader configuration from the gadget 285 // - creating symlinks for boot snaps from seed to the runtime blob dir 286 // - setting boot env vars pointing to the revisions of the boot snaps to use 287 // - extracting kernel assets as needed by the bootloader 288 func MakeBootable(model *asserts.Model, rootdir string, bootWith map[string]*snap.Info, unpackedGadgetDir string) error { 289 if len(bootWith) != 2 { 290 return fmt.Errorf("internal error: MakeBootable can only be called with exactly one kernel and exactly one core/base boot info: %v", bootWith) 291 } 292 293 // install the bootloader configuration from the gadget 294 if err := bootloader.InstallBootConfig(unpackedGadgetDir, rootdir); err != nil { 295 return err 296 } 297 298 // setup symlinks for kernel and boot base from the blob directory 299 // to the seed snaps 300 301 snapBlobDir := dirs.SnapBlobDirUnder(rootdir) 302 if err := os.MkdirAll(snapBlobDir, 0755); err != nil { 303 return err 304 } 305 306 for fn := range bootWith { 307 dst := filepath.Join(snapBlobDir, filepath.Base(fn)) 308 // construct a relative symlink from the blob dir 309 // to the seed snap file 310 relSymlink, err := filepath.Rel(snapBlobDir, fn) 311 if err != nil { 312 return fmt.Errorf("cannot build symlink for boot snap: %v", err) 313 } 314 if err := os.Symlink(relSymlink, dst); err != nil { 315 return err 316 } 317 } 318 319 // Set bootvars for kernel/core snaps so the system boots and 320 // does the first-time initialization. There is also no 321 // mounted kernel/core/base snap, but just the blobs. 322 bl, err := bootloader.Find(rootdir, &bootloader.Options{ 323 PrepareImageTime: true, 324 }) 325 if err != nil { 326 return fmt.Errorf("cannot set kernel/core boot variables: %s", err) 327 } 328 329 m := map[string]string{ 330 "snap_mode": "", 331 "snap_try_core": "", 332 "snap_try_kernel": "", 333 } 334 if model.DisplayName() != "" { 335 m["snap_menuentry"] = model.DisplayName() 336 } 337 338 for fn, info := range bootWith { 339 bootvar := "" 340 341 switch info.GetType() { 342 case snap.TypeOS, snap.TypeBase: 343 bootvar = "snap_core" 344 case snap.TypeKernel: 345 snapf, err := snap.Open(fn) 346 if err != nil { 347 return err 348 } 349 350 if err := bl.ExtractKernelAssets(info, snapf); err != nil { 351 return err 352 } 353 bootvar = "snap_kernel" 354 } 355 356 if bootvar != "" { 357 name := filepath.Base(fn) 358 m[bootvar] = name 359 } 360 } 361 if err := bl.SetBootVars(m); err != nil { 362 return err 363 } 364 365 return nil 366 367 }