1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200
|
// -*- Mode: Go; indent-tabs-mode: t -*-
/*
* Copyright (C) 2019 Canonical Ltd
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
package boot
import (
"fmt"
"github.com/snapcore/snapd/bootloader"
"github.com/snapcore/snapd/snap"
)
type bootState16 struct {
varSuffix string
errName string
}
func newBootState16(typ snap.Type, dev snap.Device) bootState {
var varSuffix, errName string
switch typ {
case snap.TypeKernel:
varSuffix = "kernel"
errName = "kernel"
case snap.TypeBase:
varSuffix = "core"
errName = "boot base"
default:
panic(fmt.Sprintf("cannot make a bootState16 for snap type %q", typ))
}
return &bootState16{varSuffix: varSuffix, errName: errName}
}
func (s16 *bootState16) revisions() (s, tryS snap.PlaceInfo, status string, err error) {
bloader, err := bootloader.Find("", nil)
if err != nil {
return nil, nil, "", fmt.Errorf("cannot get boot settings: %s", err)
}
snapVar := "snap_" + s16.varSuffix
trySnapVar := "snap_try_" + s16.varSuffix
vars := []string{"snap_mode", snapVar, trySnapVar}
snaps := make(map[string]snap.PlaceInfo, 2)
m, err := bloader.GetBootVars(vars...)
if err != nil {
return nil, nil, "", fmt.Errorf("cannot get boot variables: %s", err)
}
for _, vName := range vars {
v := m[vName]
if v == "" && vName != snapVar {
// snap_mode & snap_try_<type> can be empty
// snap_<type> cannot be! and will fail parsing
// below
continue
}
if vName == "snap_mode" {
status = v
} else {
// TODO: use trySnapError here somehow?
if v == "" {
return nil, nil, "", fmt.Errorf("cannot get name and revision of %s (%s): boot variable unset", s16.errName, vName)
}
snap, err := snap.ParsePlaceInfoFromSnapFileName(v)
if err != nil {
return nil, nil, "", fmt.Errorf("cannot get name and revision of %s (%s): %v", s16.errName, vName, err)
}
snaps[vName] = snap
}
}
return snaps[snapVar], snaps[trySnapVar], status, nil
}
type bootStateUpdate16 struct {
bl bootloader.Bootloader
env map[string]string
toCommit map[string]string
}
func newBootStateUpdate16(u bootStateUpdate, names ...string) (*bootStateUpdate16, error) {
if u != nil {
u16, ok := u.(*bootStateUpdate16)
if !ok {
return nil, fmt.Errorf("internal error: threading unexpected boot state update on UC16/18: %T", u)
}
return u16, nil
}
bl, err := bootloader.Find("", nil)
if err != nil {
return nil, err
}
m, err := bl.GetBootVars(names...)
if err != nil {
return nil, err
}
return &bootStateUpdate16{bl: bl, env: m, toCommit: make(map[string]string)}, nil
}
func (u16 *bootStateUpdate16) commit(markedSuccessful bool) error {
if len(u16.toCommit) == 0 {
// nothing to do
return nil
}
env := u16.env
// TODO: we could just SetBootVars(toCommit) but it's not
// fully backward compatible with the preexisting behavior
for k, v := range u16.toCommit {
env[k] = v
}
return u16.bl.SetBootVars(env)
}
func (s16 *bootState16) markSuccessful(update bootStateUpdate) (bootStateUpdate, error) {
u16, err := newBootStateUpdate16(update, "snap_mode", "snap_try_core", "snap_try_kernel")
if err != nil {
return nil, err
}
env := u16.env
toCommit := u16.toCommit
tryBootVar := fmt.Sprintf("snap_try_%s", s16.varSuffix)
bootVar := fmt.Sprintf("snap_%s", s16.varSuffix)
// snap_mode goes from "" -> "try" -> "trying" -> ""
// so if we are not in "trying" mode, nothing to do here
if env["snap_mode"] != TryingStatus {
// clean the try var anyways in case it was leftover from a rollback,
// etc.
toCommit[tryBootVar] = ""
return u16, nil
}
// update the boot vars
if env[tryBootVar] != "" {
toCommit[bootVar] = env[tryBootVar]
toCommit[tryBootVar] = ""
}
toCommit["snap_mode"] = DefaultStatus
return u16, nil
}
func (s16 *bootState16) setNext(s snap.PlaceInfo, bootCtx NextBootContext) (rbi RebootInfo, u bootStateUpdate, err error) {
nextBootVar := fmt.Sprintf("snap_try_%s", s16.varSuffix)
goodBootVar := fmt.Sprintf("snap_%s", s16.varSuffix)
u16, err := newBootStateUpdate16(nil, "snap_mode", goodBootVar)
if err != nil {
return RebootInfo{RebootRequired: false}, nil, err
}
env := u16.env
toCommit := u16.toCommit
rbi.RebootRequired = true
snapMode := TryStatus
nextBoot := s.Filename()
if env[goodBootVar] == nextBoot {
// If we were in anything but default ("") mode before
// and switched to the good core/kernel again, make
// sure to clean the snap_mode here. This also
// mitigates https://forum.snapcraft.io/t/5253
if env["snap_mode"] == DefaultStatus {
// already clean
return RebootInfo{RebootRequired: false}, nil, nil
}
// clean
snapMode = DefaultStatus
nextBoot = ""
rbi.RebootRequired = false
} else if bootCtx.BootWithoutTry {
toCommit[goodBootVar] = nextBoot
snapMode = DefaultStatus
nextBoot = ""
}
toCommit["snap_mode"] = snapMode
toCommit[nextBootVar] = nextBoot
return rbi, u16, nil
}
|