github.com/anonymouse64/snapd@v0.0.0-20210824153203-04c4c42d842d/overlord/devicestate/handlers_remodel.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 /* 3 * Copyright (C) 2016-2017 Canonical Ltd 4 * 5 * This program is free software: you can redistribute it and/or modify 6 * it under the terms of the GNU General Public License version 3 as 7 * published by the Free Software Foundation. 8 * 9 * This program is distributed in the hope that it will be useful, 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 * GNU General Public License for more details. 13 * 14 * You should have received a copy of the GNU General Public License 15 * along with this program. If not, see <http://www.gnu.org/licenses/>. 16 * 17 */ 18 19 package devicestate 20 21 import ( 22 "fmt" 23 24 "gopkg.in/tomb.v2" 25 26 "github.com/snapcore/snapd/asserts" 27 "github.com/snapcore/snapd/boot" 28 "github.com/snapcore/snapd/gadget" 29 "github.com/snapcore/snapd/logger" 30 "github.com/snapcore/snapd/overlord/assertstate" 31 "github.com/snapcore/snapd/overlord/snapstate" 32 "github.com/snapcore/snapd/overlord/state" 33 "github.com/snapcore/snapd/release" 34 "github.com/snapcore/snapd/snap" 35 "github.com/snapcore/snapd/snap/naming" 36 ) 37 38 func isSameAssertsRevision(err error) bool { 39 if e, ok := err.(*asserts.RevisionError); ok { 40 if e.Used == e.Current { 41 return true 42 } 43 } 44 return false 45 } 46 47 var injectedSetModelError error 48 49 // InjectSetModelError will trigger the selected error in the doSetModel 50 // handler. This is only useful for testing. 51 func InjectSetModelError(err error) { 52 injectedSetModelError = err 53 } 54 55 func (m *DeviceManager) doSetModel(t *state.Task, _ *tomb.Tomb) (err error) { 56 st := t.State() 57 st.Lock() 58 defer st.Unlock() 59 60 remodCtx, err := remodelCtxFromTask(t) 61 if err != nil { 62 return err 63 } 64 new := remodCtx.Model() 65 66 // unmark no-longer required snaps 67 var cleanedRequiredSnaps []string 68 requiredSnaps := getAllRequiredSnapsForModel(new) 69 // TODO|XXX: have AllByRef 70 snapStates, err := snapstate.All(st) 71 if err != nil { 72 return err 73 } 74 for snapName, snapst := range snapStates { 75 // TODO: remove this type restriction once we remodel 76 // gadgets and add tests that ensure 77 // that the required flag is properly set/unset 78 typ, err := snapst.Type() 79 if err != nil { 80 return err 81 } 82 if typ != snap.TypeApp && typ != snap.TypeBase && typ != snap.TypeKernel { 83 continue 84 } 85 // clean required flag if no-longer needed 86 if snapst.Flags.Required && !requiredSnaps.Contains(naming.Snap(snapName)) { 87 snapst.Flags.Required = false 88 snapstate.Set(st, snapName, snapst) 89 cleanedRequiredSnaps = append(cleanedRequiredSnaps, snapName) 90 } 91 // TODO: clean "required" flag of "core" if a remodel 92 // moves from the "core" snap to a different 93 // bootable base snap. 94 } 95 // ensure we undo the cleanedRequiredSnaps if e.g. remodCtx 96 defer func() { 97 if err == nil { 98 return 99 } 100 var snapst snapstate.SnapState 101 for _, snapName := range cleanedRequiredSnaps { 102 if err := snapstate.Get(st, snapName, &snapst); err == nil { 103 snapst.Flags.Required = true 104 snapstate.Set(st, snapName, &snapst) 105 } 106 } 107 }() 108 109 // only useful for testing 110 if injectedSetModelError != nil { 111 return injectedSetModelError 112 } 113 114 // add the assertion only after everything else was successful 115 err = assertstate.Add(st, new) 116 if err != nil && !isSameAssertsRevision(err) { 117 return err 118 } 119 120 var recoverySetup *recoverySystemSetup 121 if new.Grade() != asserts.ModelGradeUnset { 122 var triedSystems []string 123 if err := st.Get("tried-systems", &triedSystems); err != nil { 124 return fmt.Errorf("cannot obtain tried recovery systems: %v", err) 125 } 126 recoverySetup, err = taskRecoverySystemSetup(t) 127 if err != nil { 128 return err 129 } 130 // should promoting or any of the later steps fails, the cleanup 131 // will be done in finalize-recovery-system undo 132 if err := boot.PromoteTriedRecoverySystem(remodCtx, recoverySetup.Label, triedSystems); err != nil { 133 return err 134 } 135 remodCtx.setRecoverySystemLabel(recoverySetup.Label) 136 } 137 138 logEverywhere := func(format string, args ...interface{}) { 139 t.Logf(format, args) 140 logger.Noticef(format, args) 141 } 142 143 // and finish (this will set the new model), note that changes done in 144 // here are not recoverable even if an error occurs 145 if err := remodCtx.Finish(); err != nil { 146 logEverywhere("cannot complete remodel: %v", err) 147 } 148 149 t.SetStatus(state.DoneStatus) 150 151 return nil 152 } 153 154 func (m *DeviceManager) cleanupRemodel(t *state.Task, _ *tomb.Tomb) error { 155 st := t.State() 156 st.Lock() 157 defer st.Unlock() 158 // cleanup the cached remodel context 159 cleanupRemodelCtx(t.Change()) 160 return nil 161 } 162 163 func (m *DeviceManager) doPrepareRemodeling(t *state.Task, tmb *tomb.Tomb) error { 164 st := t.State() 165 st.Lock() 166 defer st.Unlock() 167 168 remodCtx, err := remodelCtxFromTask(t) 169 if err != nil { 170 return err 171 } 172 current, err := findModel(st) 173 if err != nil { 174 return err 175 } 176 177 sto := remodCtx.Store() 178 if sto == nil { 179 return fmt.Errorf("internal error: re-registration remodeling should have built a store") 180 } 181 // ensure a new session accounting for the new brand/model 182 st.Unlock() 183 _, err = sto.EnsureDeviceSession() 184 st.Lock() 185 if err != nil { 186 return fmt.Errorf("cannot get a store session based on the new model assertion: %v", err) 187 } 188 189 chgID := t.Change().ID() 190 191 tss, err := remodelTasks(tmb.Context(nil), st, current, remodCtx.Model(), remodCtx, chgID) 192 if err != nil { 193 return err 194 } 195 196 allTs := state.NewTaskSet() 197 for _, ts := range tss { 198 allTs.AddAll(ts) 199 } 200 snapstate.InjectTasks(t, allTs) 201 202 st.EnsureBefore(0) 203 t.SetStatus(state.DoneStatus) 204 205 return nil 206 } 207 208 var ( 209 gadgetIsCompatible = gadget.IsCompatible 210 ) 211 212 func checkGadgetRemodelCompatible(st *state.State, snapInfo, curInfo *snap.Info, snapf snap.Container, flags snapstate.Flags, deviceCtx snapstate.DeviceContext) error { 213 if release.OnClassic { 214 return nil 215 } 216 if snapInfo.Type() != snap.TypeGadget { 217 // We are only interested in gadget snaps. 218 return nil 219 } 220 if deviceCtx == nil || !deviceCtx.ForRemodeling() { 221 // We are only interesting in a remodeling scenario. 222 return nil 223 } 224 225 if curInfo == nil { 226 // snap isn't installed yet, we are likely remodeling to a new 227 // gadget, identify the old gadget 228 curInfo, _ = snapstate.GadgetInfo(st, deviceCtx.GroundContext()) 229 } 230 if curInfo == nil { 231 return fmt.Errorf("cannot identify the current gadget snap") 232 } 233 234 pendingInfo, err := gadget.ReadInfoFromSnapFile(snapf, deviceCtx.Model()) 235 if err != nil { 236 return fmt.Errorf("cannot read new gadget metadata: %v", err) 237 } 238 239 currentData, err := gadgetDataFromInfo(curInfo, deviceCtx.GroundContext().Model()) 240 if err != nil { 241 return fmt.Errorf("cannot read current gadget metadata: %v", err) 242 } 243 244 if err := gadgetIsCompatible(currentData.Info, pendingInfo); err != nil { 245 return fmt.Errorf("cannot remodel to an incompatible gadget: %v", err) 246 } 247 return nil 248 }