github.com/Lephar/snapd@v0.0.0-20210825215435-c7fba9cef4d2/boot/bootstate16.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 "fmt" 24 25 "github.com/snapcore/snapd/bootloader" 26 "github.com/snapcore/snapd/snap" 27 ) 28 29 type bootState16 struct { 30 varSuffix string 31 errName string 32 } 33 34 func newBootState16(typ snap.Type, dev Device) bootState { 35 var varSuffix, errName string 36 switch typ { 37 case snap.TypeKernel: 38 varSuffix = "kernel" 39 errName = "kernel" 40 case snap.TypeBase: 41 varSuffix = "core" 42 errName = "boot base" 43 default: 44 panic(fmt.Sprintf("cannot make a bootState16 for snap type %q", typ)) 45 } 46 return &bootState16{varSuffix: varSuffix, errName: errName} 47 } 48 49 func (s16 *bootState16) revisions() (s, tryS snap.PlaceInfo, status string, err error) { 50 bloader, err := bootloader.Find("", nil) 51 if err != nil { 52 return nil, nil, "", fmt.Errorf("cannot get boot settings: %s", err) 53 } 54 55 snapVar := "snap_" + s16.varSuffix 56 trySnapVar := "snap_try_" + s16.varSuffix 57 vars := []string{"snap_mode", snapVar, trySnapVar} 58 snaps := make(map[string]snap.PlaceInfo, 2) 59 60 m, err := bloader.GetBootVars(vars...) 61 if err != nil { 62 return nil, nil, "", fmt.Errorf("cannot get boot variables: %s", err) 63 } 64 65 for _, vName := range vars { 66 v := m[vName] 67 if v == "" && vName != snapVar { 68 // snap_mode & snap_try_<type> can be empty 69 // snap_<type> cannot be! and will fail parsing 70 // below 71 continue 72 } 73 74 if vName == "snap_mode" { 75 status = v 76 } else { 77 // TODO: use trySnapError here somehow? 78 if v == "" { 79 return nil, nil, "", fmt.Errorf("cannot get name and revision of %s (%s): boot variable unset", s16.errName, vName) 80 } 81 snap, err := snap.ParsePlaceInfoFromSnapFileName(v) 82 if err != nil { 83 return nil, nil, "", fmt.Errorf("cannot get name and revision of %s (%s): %v", s16.errName, vName, err) 84 } 85 snaps[vName] = snap 86 } 87 } 88 89 return snaps[snapVar], snaps[trySnapVar], status, nil 90 } 91 92 type bootStateUpdate16 struct { 93 bl bootloader.Bootloader 94 env map[string]string 95 toCommit map[string]string 96 } 97 98 func newBootStateUpdate16(u bootStateUpdate, names ...string) (*bootStateUpdate16, error) { 99 if u != nil { 100 u16, ok := u.(*bootStateUpdate16) 101 if !ok { 102 return nil, fmt.Errorf("internal error: threading unexpected boot state update on UC16/18: %T", u) 103 } 104 return u16, nil 105 } 106 bl, err := bootloader.Find("", nil) 107 if err != nil { 108 return nil, err 109 } 110 m, err := bl.GetBootVars(names...) 111 if err != nil { 112 return nil, err 113 } 114 return &bootStateUpdate16{bl: bl, env: m, toCommit: make(map[string]string)}, nil 115 } 116 117 func (u16 *bootStateUpdate16) commit() error { 118 if len(u16.toCommit) == 0 { 119 // nothing to do 120 return nil 121 } 122 env := u16.env 123 // TODO: we could just SetBootVars(toCommit) but it's not 124 // fully backward compatible with the preexisting behavior 125 for k, v := range u16.toCommit { 126 env[k] = v 127 } 128 return u16.bl.SetBootVars(env) 129 } 130 131 func (s16 *bootState16) markSuccessful(update bootStateUpdate) (bootStateUpdate, error) { 132 u16, err := newBootStateUpdate16(update, "snap_mode", "snap_try_core", "snap_try_kernel") 133 if err != nil { 134 return nil, err 135 } 136 137 env := u16.env 138 toCommit := u16.toCommit 139 140 tryBootVar := fmt.Sprintf("snap_try_%s", s16.varSuffix) 141 bootVar := fmt.Sprintf("snap_%s", s16.varSuffix) 142 143 // snap_mode goes from "" -> "try" -> "trying" -> "" 144 // so if we are not in "trying" mode, nothing to do here 145 if env["snap_mode"] != TryingStatus { 146 // clean the try var anyways in case it was leftover from a rollback, 147 // etc. 148 toCommit[tryBootVar] = "" 149 return u16, nil 150 } 151 152 // update the boot vars 153 if env[tryBootVar] != "" { 154 toCommit[bootVar] = env[tryBootVar] 155 toCommit[tryBootVar] = "" 156 } 157 toCommit["snap_mode"] = DefaultStatus 158 159 return u16, nil 160 } 161 162 func (s16 *bootState16) setNext(s snap.PlaceInfo) (rebootRequired bool, u bootStateUpdate, err error) { 163 nextBoot := s.Filename() 164 165 nextBootVar := fmt.Sprintf("snap_try_%s", s16.varSuffix) 166 goodBootVar := fmt.Sprintf("snap_%s", s16.varSuffix) 167 168 u16, err := newBootStateUpdate16(nil, "snap_mode", goodBootVar) 169 if err != nil { 170 return false, nil, err 171 } 172 173 env := u16.env 174 toCommit := u16.toCommit 175 176 snapMode := TryStatus 177 rebootRequired = true 178 if env[goodBootVar] == nextBoot { 179 // If we were in anything but default ("") mode before 180 // and switched to the good core/kernel again, make 181 // sure to clean the snap_mode here. This also 182 // mitigates https://forum.snapcraft.io/t/5253 183 if env["snap_mode"] == DefaultStatus { 184 // already clean 185 return false, nil, nil 186 } 187 // clean 188 snapMode = DefaultStatus 189 nextBoot = "" 190 rebootRequired = false 191 } 192 193 toCommit["snap_mode"] = snapMode 194 toCommit[nextBootVar] = nextBoot 195 196 return rebootRequired, u16, nil 197 }